mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-11-12 20:47:30 +00:00
fix(media): gate proxying on source eligibility
This commit is contained in:
@@ -89,7 +89,11 @@ describe("maybeProxyProactively", () => {
|
||||
|
||||
it("swaps when hostile and representativeMime is null (no canPlayType check possible)", () => {
|
||||
window.__HF_MEDIA_CODEC_MAP__ = {
|
||||
"/video.mp4": { codecName: "prores", browserHostile: true, representativeMime: null },
|
||||
"/video.mp4": {
|
||||
codecName: "prores",
|
||||
browserHostile: true,
|
||||
representativeMime: null,
|
||||
},
|
||||
};
|
||||
const el = createVideo("/video.mp4");
|
||||
stubCanPlayType(el, "probably"); // should not even be consulted
|
||||
@@ -235,6 +239,23 @@ describe("handleMetadataForProxy (reactive trigger)", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("does not proxy a mapped browser-safe codec on the metadata path", () => {
|
||||
window.__HF_MEDIA_CODEC_MAP__ = { "/video.mp4": H264_ENTRY };
|
||||
const el = createVideo("/video.mp4");
|
||||
markZeroWidth(el);
|
||||
|
||||
handleMetadataForProxy(el);
|
||||
|
||||
expect(isProxied(el)).toBe(false);
|
||||
expect(el.load).not.toHaveBeenCalled();
|
||||
expect(postRuntimeMessageMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
code: "runtime_media_proxy_unavailable",
|
||||
details: expect.objectContaining({ reason: "browser_safe_codec" }),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("a second zero-width metadata event does not loop (no second swap)", () => {
|
||||
window.__HF_MEDIA_CODEC_MAP__ = {};
|
||||
const el = createVideo("/video.mp4");
|
||||
@@ -385,6 +406,23 @@ describe("handleErrorForProxy (tertiary trigger)", () => {
|
||||
});
|
||||
|
||||
describe("swapToProxy", () => {
|
||||
it("does not poison swap state when the source URL is malformed", () => {
|
||||
const el = createVideo("/video.mp4");
|
||||
Object.defineProperty(el, "currentSrc", {
|
||||
value: "http://[",
|
||||
configurable: true,
|
||||
});
|
||||
|
||||
swapToProxy(el, HEVC_ENTRY, "reactive");
|
||||
|
||||
expect(el.load).not.toHaveBeenCalled();
|
||||
expect(postRuntimeMessageMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
code: "runtime_media_proxy_unavailable",
|
||||
details: expect.objectContaining({ reason: "invalid_source_url" }),
|
||||
}),
|
||||
);
|
||||
});
|
||||
it("evicts per-source sync state so the swapped element is treated as a first tick", () => {
|
||||
const el = createVideo("/video.mp4");
|
||||
// Populate sync state as a real active-clip tick would.
|
||||
|
||||
@@ -123,7 +123,7 @@ function lookupCodecMapEntry(
|
||||
if (!pathname.endsWith(key)) continue;
|
||||
if (bestKey === null || key.length > bestKey.length) bestKey = key;
|
||||
}
|
||||
return bestKey ? map[bestKey] : null;
|
||||
return bestKey ? (map[bestKey] ?? null) : null;
|
||||
}
|
||||
|
||||
function appendProxyParam(src: string): string {
|
||||
@@ -136,7 +136,8 @@ type UnavailableReason =
|
||||
| "cross_origin"
|
||||
| "proxy_playback_failed"
|
||||
| "browser_safe_codec"
|
||||
| "alpha_source";
|
||||
| "alpha_source"
|
||||
| "invalid_source_url";
|
||||
|
||||
const UNAVAILABLE_NOTES: Record<UnavailableReason, string> = {
|
||||
cross_origin:
|
||||
@@ -146,6 +147,7 @@ const UNAVAILABLE_NOTES: Record<UnavailableReason, string> = {
|
||||
"the file errored but its codec is browser-decodable; an H.264 proxy cannot help (the file itself is likely corrupt)",
|
||||
alpha_source:
|
||||
"the source carries an alpha channel; an H.264 proxy would destroy the transparency, so it is never proxied",
|
||||
invalid_source_url: "the media source URL is malformed and cannot be proxied",
|
||||
};
|
||||
|
||||
function emitUnavailableDiagnostic(
|
||||
@@ -184,15 +186,16 @@ export function swapToProxy(
|
||||
trigger: ProxyTrigger = "reactive",
|
||||
): void {
|
||||
if (swappedElements.has(el)) return;
|
||||
swappedElements.add(el);
|
||||
const originalSrc = currentSrcValue(el);
|
||||
let proxiedSrc: string;
|
||||
try {
|
||||
proxiedSrc = appendProxyParam(originalSrc);
|
||||
} catch (err) {
|
||||
swallow("runtime.mediaProxy.swap", err);
|
||||
emitUnavailableDiagnostic(el, "invalid_source_url", originalSrc);
|
||||
return;
|
||||
}
|
||||
swappedElements.add(el);
|
||||
// The swapped src points at a different file — sync state (drift offsets,
|
||||
// seek-retry latches, volume tracking) computed against the original
|
||||
// source must not carry over, or the next tick misreads a fresh file's
|
||||
@@ -281,6 +284,10 @@ export function handleMetadataForProxy(el: HTMLMediaElement): void {
|
||||
return;
|
||||
}
|
||||
const entry = lookupCodecMapEntry(key, map);
|
||||
if (entry && !entry.browserHostile) {
|
||||
emitUnavailableDiagnostic(el, "browser_safe_codec", src);
|
||||
return;
|
||||
}
|
||||
if (entry?.hasAlpha) {
|
||||
emitUnavailableDiagnostic(el, "alpha_source", src);
|
||||
return;
|
||||
|
||||
@@ -5,6 +5,7 @@ import { tmpdir } from "node:os";
|
||||
import type { FfprobeRunner } from "./mediaMetadata.js";
|
||||
import {
|
||||
BROWSER_HOSTILE_CODECS,
|
||||
decideMediaProxyEligibility,
|
||||
createMediaCodecProbeCache,
|
||||
probeAssetCodec,
|
||||
scanProjectMediaCodecMap,
|
||||
@@ -24,13 +25,22 @@ function makeRunner(
|
||||
const entry = codecByPath[filePath];
|
||||
const normalized = typeof entry === "string" ? { codecName: entry } : entry;
|
||||
const streams = normalized
|
||||
? [{ codec_type: "video", codec_name: normalized.codecName, pix_fmt: normalized.pixFmt }]
|
||||
? [
|
||||
{
|
||||
codec_type: "video",
|
||||
codec_name: normalized.codecName,
|
||||
pix_fmt: normalized.pixFmt,
|
||||
},
|
||||
]
|
||||
: [];
|
||||
return { status: 0, stdout: JSON.stringify({ streams }), stderr: "" };
|
||||
};
|
||||
}
|
||||
|
||||
function countingRunner(runner: FfprobeRunner): { runner: FfprobeRunner; calls: () => number } {
|
||||
function countingRunner(runner: FfprobeRunner): {
|
||||
runner: FfprobeRunner;
|
||||
calls: () => number;
|
||||
} {
|
||||
let calls = 0;
|
||||
return {
|
||||
runner: (command, args, options) => {
|
||||
@@ -122,7 +132,9 @@ describe("probeAssetCodec", () => {
|
||||
|
||||
const facts = await probeAssetCodec(
|
||||
videoPath,
|
||||
makeRunner({ [videoPath]: { codecName: "prores", pixFmt: "yuva444p10le" } }),
|
||||
makeRunner({
|
||||
[videoPath]: { codecName: "prores", pixFmt: "yuva444p10le" },
|
||||
}),
|
||||
);
|
||||
|
||||
expect(facts).toEqual({
|
||||
@@ -145,6 +157,42 @@ describe("probeAssetCodec", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("decideMediaProxyEligibility", () => {
|
||||
it("allows only hostile opaque video through the H.264 proxy path", () => {
|
||||
expect(
|
||||
decideMediaProxyEligibility({
|
||||
codecName: "hevc",
|
||||
browserHostile: true,
|
||||
representativeMime: null,
|
||||
hasAlpha: false,
|
||||
}),
|
||||
).toEqual({ eligible: true });
|
||||
});
|
||||
|
||||
it("rejects alpha and browser-safe sources before transcoding", () => {
|
||||
expect(
|
||||
decideMediaProxyEligibility({
|
||||
codecName: "prores",
|
||||
browserHostile: true,
|
||||
representativeMime: null,
|
||||
hasAlpha: true,
|
||||
}),
|
||||
).toEqual({ eligible: false, reason: "alpha_source" });
|
||||
expect(
|
||||
decideMediaProxyEligibility({
|
||||
codecName: "h264",
|
||||
browserHostile: false,
|
||||
representativeMime: null,
|
||||
hasAlpha: false,
|
||||
}),
|
||||
).toEqual({ eligible: false, reason: "browser_safe_codec" });
|
||||
expect(decideMediaProxyEligibility(null)).toEqual({
|
||||
eligible: false,
|
||||
reason: "unknown_codec",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("scanProjectMediaCodecMap", () => {
|
||||
it("omits an asset from the map (no throw) when ffprobe is unresolvable", async () => {
|
||||
const project = tmpProject();
|
||||
@@ -172,9 +220,9 @@ describe("scanProjectMediaCodecMap", () => {
|
||||
}),
|
||||
});
|
||||
|
||||
expect(Object.keys(map).sort()).toEqual(["/assets/my clip.mp4", "/assets/sub/clip.mp4"]);
|
||||
expect(Object.keys(map)).toEqual(["/assets/sub/clip.mp4"]);
|
||||
expect(map["/assets/sub/clip.mp4"]?.browserHostile).toBe(true);
|
||||
expect(map["/assets/my clip.mp4"]?.browserHostile).toBe(false);
|
||||
expect(map["/assets/my clip.mp4"]).toBeUndefined();
|
||||
});
|
||||
|
||||
it("rewrites a sub-composition's ../-traversing src via compSrcPath before resolving (rewriteAssetPath)", async () => {
|
||||
@@ -185,7 +233,12 @@ describe("scanProjectMediaCodecMap", () => {
|
||||
|
||||
const map = await scanProjectMediaCodecMap(
|
||||
project,
|
||||
[{ html: videoHtml("../assets/clip.mp4"), compSrcPath: "compositions/scene.html" }],
|
||||
[
|
||||
{
|
||||
html: videoHtml("../assets/clip.mp4"),
|
||||
compSrcPath: "compositions/scene.html",
|
||||
},
|
||||
],
|
||||
{ runner: makeRunner({ [join(project, "assets", "clip.mp4")]: "hevc" }) },
|
||||
);
|
||||
|
||||
|
||||
@@ -44,6 +44,20 @@ export const BROWSER_HOSTILE_CODECS: Record<string, string | null> = {
|
||||
av1: 'video/mp4; codecs="av01.0.08M.08"',
|
||||
};
|
||||
|
||||
export type MediaProxyIneligibilityReason = "alpha_source" | "browser_safe_codec" | "unknown_codec";
|
||||
|
||||
export type MediaProxyEligibility =
|
||||
| { eligible: true }
|
||||
| { eligible: false; reason: MediaProxyIneligibilityReason };
|
||||
|
||||
/** Single policy gate shared by proactive scans and on-demand proxy routes. */
|
||||
export function decideMediaProxyEligibility(facts: AssetCodecFacts | null): MediaProxyEligibility {
|
||||
if (!facts) return { eligible: false, reason: "unknown_codec" };
|
||||
if (facts.hasAlpha) return { eligible: false, reason: "alpha_source" };
|
||||
if (!facts.browserHostile) return { eligible: false, reason: "browser_safe_codec" };
|
||||
return { eligible: true };
|
||||
}
|
||||
|
||||
function codecFactsFor(codecName: string, hasAlpha: boolean): AssetCodecFacts {
|
||||
const isHostile = Object.hasOwn(BROWSER_HOSTILE_CODECS, codecName);
|
||||
return {
|
||||
@@ -219,7 +233,7 @@ export async function scanProjectMediaCodecMap(
|
||||
const map: MediaCodecMap = {};
|
||||
entries.forEach(([, pathname], index) => {
|
||||
const entryFacts = facts[index];
|
||||
if (entryFacts) map[pathname] = entryFacts;
|
||||
if (entryFacts?.browserHostile) map[pathname] = entryFacts;
|
||||
});
|
||||
return map;
|
||||
}
|
||||
|
||||
@@ -78,8 +78,8 @@ function injectScriptTagIntoHead(html: string, scriptTag: string): string {
|
||||
* The single shared implementation for every auto-proxy surface — the studio
|
||||
* preview route (via `injectMediaCodecMap` below) and the CLI's composition /
|
||||
* static project servers (via the `./media-proxy-preview` subpath export).
|
||||
* The map is injected even when empty: the runtime treats map PRESENCE as
|
||||
* "this surface serves `?hf-proxy=`", gating its reactive/tertiary swaps.
|
||||
* Empty maps leave HTML untouched, preserving the normal no-hostile-media
|
||||
* preview path. On-demand proxy requests enforce the same eligibility gate.
|
||||
*/
|
||||
export async function injectMediaCodecMapIntoHtml(
|
||||
html: string,
|
||||
@@ -98,6 +98,7 @@ export async function injectMediaCodecMapIntoHtml(
|
||||
// Best-effort: a scan failure must never block serving the page.
|
||||
return html;
|
||||
}
|
||||
if (Object.keys(map).length === 0) return html;
|
||||
for (const [rootRelativePathname, facts] of Object.entries(map)) {
|
||||
if (!facts.browserHostile || facts.hasAlpha) continue;
|
||||
resolveProxy(projectDir, resolve(projectDir, rootRelativePathname.replace(/^\/+/, ""))).catch(
|
||||
|
||||
@@ -672,13 +672,23 @@ describe("hf-proxy negotiation and media codec map injection (U3)", () => {
|
||||
) => Promise<
|
||||
Record<
|
||||
string,
|
||||
{ codecName: string; browserHostile: boolean; representativeMime: string | null }
|
||||
{
|
||||
codecName: string;
|
||||
browserHostile: boolean;
|
||||
representativeMime: string | null;
|
||||
}
|
||||
>
|
||||
>;
|
||||
|
||||
async function loadPreviewModule(opts: {
|
||||
resolveProxyImpl?: (projectDir: string, absoluteSourcePath: string) => Promise<string>;
|
||||
scanMapImpl?: ScanMapImpl;
|
||||
probeAssetCodecImpl?: () => Promise<{
|
||||
codecName: string;
|
||||
browserHostile: boolean;
|
||||
representativeMime: string | null;
|
||||
hasAlpha: boolean;
|
||||
} | null>;
|
||||
}): Promise<typeof import("./preview.js")> {
|
||||
vi.resetModules();
|
||||
const resolveProxy =
|
||||
@@ -699,6 +709,27 @@ describe("hf-proxy negotiation and media codec map injection (U3)", () => {
|
||||
vi.doMock("../helpers/mediaCodecMap.js", () => ({
|
||||
scanProjectMediaCodecMap: opts.scanMapImpl ?? (async () => ({})),
|
||||
createMediaCodecProbeCache: () => new Map(),
|
||||
probeAssetCodec:
|
||||
opts.probeAssetCodecImpl ??
|
||||
(async () => ({
|
||||
codecName: "hevc",
|
||||
browserHostile: true,
|
||||
representativeMime: null,
|
||||
hasAlpha: false,
|
||||
})),
|
||||
decideMediaProxyEligibility: (
|
||||
facts: {
|
||||
browserHostile: boolean;
|
||||
hasAlpha: boolean;
|
||||
} | null,
|
||||
) => {
|
||||
if (!facts) return { eligible: false, reason: "unknown_codec" };
|
||||
if (facts.hasAlpha) return { eligible: false, reason: "alpha_source" };
|
||||
if (!facts.browserHostile) {
|
||||
return { eligible: false, reason: "browser_safe_codec" };
|
||||
}
|
||||
return { eligible: true };
|
||||
},
|
||||
}));
|
||||
return import("./preview.js");
|
||||
}
|
||||
@@ -807,6 +838,39 @@ describe("hf-proxy negotiation and media codec map injection (U3)", () => {
|
||||
expect(resolveProxyMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("rejects alpha-bearing and browser-safe sources before transcoding", async () => {
|
||||
for (const facts of [
|
||||
{
|
||||
codecName: "prores",
|
||||
browserHostile: true,
|
||||
representativeMime: null,
|
||||
hasAlpha: true,
|
||||
},
|
||||
{
|
||||
codecName: "h264",
|
||||
browserHostile: false,
|
||||
representativeMime: null,
|
||||
hasAlpha: false,
|
||||
},
|
||||
]) {
|
||||
const projectDir = createProjectDir();
|
||||
writeFileSync(join(projectDir, "clip.mp4"), "video-bytes");
|
||||
const resolveProxyMock = vi.fn(async () => "should-not-be-called");
|
||||
const { registerPreviewRoutes: register } = await loadPreviewModule({
|
||||
resolveProxyImpl: resolveProxyMock,
|
||||
probeAssetCodecImpl: async () => facts,
|
||||
});
|
||||
const app = new Hono();
|
||||
register(app, createAdapter(projectDir));
|
||||
|
||||
const res = await app.request(
|
||||
"http://localhost/projects/demo/preview/clip.mp4?hf-proxy=h264",
|
||||
);
|
||||
expect(res.status).toBe(422);
|
||||
expect(resolveProxyMock).not.toHaveBeenCalled();
|
||||
}
|
||||
});
|
||||
|
||||
it("returns 404 without transcoding when the param value is not exactly h264", async () => {
|
||||
const projectDir = createProjectDir();
|
||||
writeFileSync(join(projectDir, "clip.mp4"), "original-hevc-bytes");
|
||||
@@ -884,6 +948,18 @@ describe("hf-proxy negotiation and media codec map injection (U3)", () => {
|
||||
});
|
||||
|
||||
describe("__HF_MEDIA_CODEC_MAP__ injection into composition HTML", () => {
|
||||
it("keeps HTML byte-identical when the scan finds no proxy-eligible media", async () => {
|
||||
const projectDir = createProjectDir();
|
||||
const { registerPreviewRoutes: register } = await loadPreviewModule({
|
||||
scanMapImpl: async () => ({}),
|
||||
});
|
||||
const app = new Hono();
|
||||
register(app, createAdapter(projectDir));
|
||||
|
||||
const res = await app.request("http://localhost/projects/demo/preview");
|
||||
const html = await res.text();
|
||||
expect(html).not.toContain("data-hf-media-codec-map");
|
||||
});
|
||||
it("injects the scanned map naming the hostile fixture, and pre-warms resolveProxy for it", async () => {
|
||||
const projectDir = createProjectDir();
|
||||
const resolveProxyMock = vi.fn(async () => join(projectDir, ".transcode-cache", "x.mp4"));
|
||||
@@ -924,7 +1000,11 @@ describe("hf-proxy negotiation and media codec map injection (U3)", () => {
|
||||
writeFileSync(join(projectDir, "clip.mp4"), "bytes");
|
||||
const resolveProxyMock = vi.fn(async () => "should-not-be-called");
|
||||
const scanMapMock = vi.fn(async () => ({
|
||||
"/clip.mp4": { codecName: "hevc", browserHostile: true, representativeMime: null },
|
||||
"/clip.mp4": {
|
||||
codecName: "hevc",
|
||||
browserHostile: true,
|
||||
representativeMime: null,
|
||||
},
|
||||
}));
|
||||
const { registerPreviewRoutes: register } = await loadPreviewModule({
|
||||
resolveProxyImpl: resolveProxyMock,
|
||||
|
||||
@@ -16,6 +16,7 @@ import { ensureHfIds } from "@hyperframes/parsers/hf-ids";
|
||||
import { persistHfIdsIfNeeded, stampFileHfIds } from "../helpers/hfIdPersist.js";
|
||||
import { isVariablesPayload, VARIABLES_PAYLOAD_ERROR } from "../helpers/variablesPayload.js";
|
||||
import { resolveProxy, ProxyTranscodeError } from "../helpers/proxyTranscoder.js";
|
||||
import { decideMediaProxyEligibility, probeAssetCodec } from "../helpers/mediaCodecMap.js";
|
||||
import {
|
||||
isAutoProxyEnabled,
|
||||
injectMediaCodecMap,
|
||||
@@ -61,7 +62,9 @@ function parseStudioMotionManifestContent(content: string): {
|
||||
hasCustomEase: boolean;
|
||||
} {
|
||||
try {
|
||||
const parsed = JSON.parse(content) as { motions?: Array<{ customEase?: unknown }> };
|
||||
const parsed = JSON.parse(content) as {
|
||||
motions?: Array<{ customEase?: unknown }>;
|
||||
};
|
||||
const motions = Array.isArray(parsed.motions) ? parsed.motions : [];
|
||||
return {
|
||||
hasMotion: motions.length > 0,
|
||||
@@ -247,11 +250,13 @@ function variablesEtagSalt(raw: string | undefined): string {
|
||||
* route should 400; otherwise `values` is the override object (or null when
|
||||
* the param is absent) and `raw` feeds the ETag salt.
|
||||
*/
|
||||
function previewVariablesFromRequest(
|
||||
rawVariables: string | undefined,
|
||||
):
|
||||
function previewVariablesFromRequest(rawVariables: string | undefined):
|
||||
| { error: string }
|
||||
| { error?: undefined; raw: string | undefined; values: Record<string, unknown> | null } {
|
||||
| {
|
||||
error?: undefined;
|
||||
raw: string | undefined;
|
||||
values: Record<string, unknown> | null;
|
||||
} {
|
||||
const parse = parsePreviewVariablesParam(rawVariables);
|
||||
if (!parse.ok) return { error: parse.error };
|
||||
return { raw: rawVariables, values: parse.values };
|
||||
@@ -299,11 +304,17 @@ function resolveProjectMainHtml(
|
||||
): { html: string; compositionPath: string } | null {
|
||||
const indexPath = join(projectDir, "index.html");
|
||||
if (existsSync(indexPath)) {
|
||||
return { html: readFileSync(indexPath, "utf-8"), compositionPath: "index.html" };
|
||||
return {
|
||||
html: readFileSync(indexPath, "utf-8"),
|
||||
compositionPath: "index.html",
|
||||
};
|
||||
}
|
||||
const blockHtmlPath = join(projectDir, `${projectId}.html`);
|
||||
if (existsSync(blockHtmlPath)) {
|
||||
return { html: readFileSync(blockHtmlPath, "utf-8"), compositionPath: `${projectId}.html` };
|
||||
return {
|
||||
html: readFileSync(blockHtmlPath, "utf-8"),
|
||||
compositionPath: `${projectId}.html`,
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -334,7 +345,10 @@ export function registerPreviewRoutes(api: Hono, adapter: PreviewApiAdapter): vo
|
||||
const etag = `"preview:${signature}${variablesEtagSalt(vars.raw)}"`;
|
||||
const ifNoneMatch = c.req.header("If-None-Match");
|
||||
if (ifNoneMatch === etag) {
|
||||
return new Response(null, { status: 304, headers: previewCacheHeaders(etag) });
|
||||
return new Response(null, {
|
||||
status: 304,
|
||||
headers: previewCacheHeaders(etag),
|
||||
});
|
||||
}
|
||||
|
||||
// Normalize + persist data-hf-id to disk before bundle reads it. Idempotent.
|
||||
@@ -472,7 +486,10 @@ export function registerPreviewRoutes(api: Hono, adapter: PreviewApiAdapter): vo
|
||||
const etag = `"comp:v2:${compPath}:${signature}${variablesEtagSalt(vars.raw)}"`;
|
||||
const ifNoneMatch = c.req.header("If-None-Match");
|
||||
if (ifNoneMatch === etag) {
|
||||
return new Response(null, { status: 304, headers: previewCacheHeaders(etag) });
|
||||
return new Response(null, {
|
||||
status: 304,
|
||||
headers: previewCacheHeaders(etag),
|
||||
});
|
||||
}
|
||||
|
||||
const stamped = pinSubCompHfIds(compFile, compPath);
|
||||
@@ -527,12 +544,19 @@ export function registerPreviewRoutes(api: Hono, adapter: PreviewApiAdapter): vo
|
||||
) {
|
||||
return c.text("not found", 404);
|
||||
}
|
||||
const eligibility = decideMediaProxyEligibility(await probeAssetCodec(file));
|
||||
if (!eligibility.eligible) {
|
||||
return c.text(`media proxy unavailable: ${eligibility.reason}`, 422);
|
||||
}
|
||||
}
|
||||
|
||||
const etag = `"${stat.mtimeMs.toString(36)}-${stat.size.toString(36)}${proxyEtagSalt(proxyParam)}"`;
|
||||
const cacheHeaders: Record<string, string> = isText
|
||||
? { "Cache-Control": "no-store" }
|
||||
: { "Cache-Control": "private, max-age=3600, must-revalidate", ETag: etag };
|
||||
: {
|
||||
"Cache-Control": "private, max-age=3600, must-revalidate",
|
||||
ETag: etag,
|
||||
};
|
||||
|
||||
if (!isText) {
|
||||
const ifNoneMatch = c.req.header("If-None-Match");
|
||||
|
||||
Reference in New Issue
Block a user