fix(snapshots): normalize content-type on serve so stored images render
Cameras (Hikvision) return `Content-Type: image/jpeg; charset="UTF-8"` — a charset param on a binary body is malformed, and browsers refuse to decode an <img> declared that way. Old capture code persisted that raw header into snapshots.content_type (100/101 dev-DB rows); GET /api/snapshots/:id re-emitted it verbatim, so every legacy snapshot rendered blank in the booth modal. Capture was already hardened (encodeForStorage re-encodes to a clean image/jpeg, fail-soft via cleanType), but the serve route trusted the stored value. Export cleanType and apply it when setting the response header, so a bare image/jpeg is sent regardless of what was stored — un-breaks all legacy rows with no data migration. A stored value from an untrusted device is itself input; normalize on capture AND on serve. Adds cleanType unit tests. Verified: a previously-unrenderable 2560x1440 row now decodes in-browser. Claude-Session: https://claude.ai/code/session_01Xcm6ikLgGoCxxHrxtjkk5V
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import sharp from "sharp";
|
||||
import type { CameraDevice, Snapshot } from "@parking/devices";
|
||||
import { captureSnapshotShared, encodeForStorage } from "./snapshot.js";
|
||||
import { captureSnapshotShared, cleanType, encodeForStorage } from "./snapshot.js";
|
||||
import { silentLogger } from "./test-helpers.js";
|
||||
|
||||
// captureSnapshotShared: one HTTP pull per camera per vehicle. A Hikvision unit serves
|
||||
@@ -139,3 +139,20 @@ describe("encodeForStorage", () => {
|
||||
expect(out.contentType).toBe("text/plain"); // charset stripped even on the fallback
|
||||
});
|
||||
});
|
||||
|
||||
describe("cleanType", () => {
|
||||
it("strips a camera's charset cruft so a binary JPEG renders", () => {
|
||||
// The exact malformed value some cameras (Hikvision) return, which broke the
|
||||
// snapshot strip for every legacy row until the serve route normalized it.
|
||||
expect(cleanType('image/jpeg; charset="UTF-8"')).toBe("image/jpeg");
|
||||
expect(cleanType("image/jpeg; charset=utf-8")).toBe("image/jpeg");
|
||||
});
|
||||
|
||||
it("passes a clean type through and defaults a missing one", () => {
|
||||
expect(cleanType("image/jpeg")).toBe("image/jpeg");
|
||||
expect(cleanType("image/png")).toBe("image/png");
|
||||
expect(cleanType(null)).toBe("image/jpeg");
|
||||
expect(cleanType(undefined)).toBe("image/jpeg");
|
||||
expect(cleanType("")).toBe("image/jpeg");
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user