The product was materialized from the template at `4dc033c` and has stayed on it through 43 template commits, so it was missing all three rounds of adapter remediation — including files it never had, such as the shared `abortable-operation` primitive and the `exact-snapshot` decoder that later fixes are written against. Taking only the newest round was not possible for that reason: the delta is coherent only as a whole. The product had not touched `src/adapters` at all since materialization, so the 140-file delta applied with a three-way merge and no conflicts. `package.json` was the single overlap and merged cleanly: the product owns `name`, the template contributed `check:adapter-inventory`, `check:remediation-ledger` and the image-resolve-signal type fixture. All 24 product-owned files — README, index.html, CI workflow, i18n catalog, home page, generated schemas, evidence scripts, component and visual snapshots — are byte-identical to `main`. `template.lock.json` now pins the synced revision and tree. Verified in this repository, not inherited from the template: six type projects, lint, nine gates (adapter inventory, remediation ledger, registries, diagnostics, realtime boundaries, architecture, browser file/storage boundaries, optional recipes, documentation), the production build, and 2,054 of 2,073 tests. The 19 failures are all in `tests/unit/ci-artifact-contract.test.ts` and are the same pre-existing sandbox RLIMIT, EMFILE, umask and `/tmp` permission behaviour the template records; four suites that failed once under parallel load pass in isolation. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1352 lines
39 KiB
TypeScript
1352 lines
39 KiB
TypeScript
import type {
|
|
BackendIssuedImageAsset,
|
|
ImageAssetReference,
|
|
ImageCapabilityVerifier,
|
|
ImageCdnRuntime,
|
|
ImageOutputFormat,
|
|
ImagePresentationDescriptor,
|
|
ImagePresetReference,
|
|
ImageProbeReceipt,
|
|
ImageRasterMediaType,
|
|
ImageResourceProbePort,
|
|
PublicImmutableImageAsset,
|
|
} from "../../../application/ports/browser-transfer/image-cdn.ts";
|
|
import type {
|
|
BrowserDataObserver,
|
|
BrowserDataResult,
|
|
} from "../../../application/ports/browser-file-storage/shared.ts";
|
|
import {
|
|
browserDataFailure,
|
|
browserDataSuccess,
|
|
observeBrowserData,
|
|
} from "../../browser-file-storage/result.ts";
|
|
import {
|
|
IMAGE_FORMAT_MEDIA_TYPE,
|
|
type ImageCandidateGeometry,
|
|
type ImageCdnHardLimits,
|
|
type ImageCdnPolicyRegistry,
|
|
type ResolvedImageCdnOrigin,
|
|
type ResolvedImageCdnPreset,
|
|
} from "./image-cdn-policy.ts";
|
|
|
|
export type ImageCdnRuntimeDependencies = Readonly<{
|
|
policies: ImageCdnPolicyRegistry;
|
|
now?: () => number;
|
|
subtle?: Pick<SubtleCrypto, "digest">;
|
|
capabilityVerifier?: ImageCapabilityVerifier;
|
|
capabilityVerificationTimeoutMs?: number;
|
|
capabilityVerificationScheduler?: ImageCapabilityVerificationScheduler;
|
|
probe?: ImageResourceProbePort;
|
|
observer?: BrowserDataObserver;
|
|
}>;
|
|
|
|
export type ImageCapabilityVerificationScheduler = Readonly<{
|
|
setTimeout(callback: () => void, milliseconds: number): unknown;
|
|
clearTimeout(handle: unknown): void;
|
|
}>;
|
|
|
|
export const DEFAULT_IMAGE_CAPABILITY_VERIFICATION_TIMEOUT_MS = 5_000;
|
|
export const MAX_IMAGE_CAPABILITY_VERIFICATION_TIMEOUT_MS = 60_000;
|
|
|
|
type AssetSnapshot = Readonly<{
|
|
origin: ResolvedImageCdnOrigin;
|
|
assetId: string;
|
|
revision: string;
|
|
mediaType: ImageRasterMediaType;
|
|
intrinsicWidth: number;
|
|
intrinsicHeight: number;
|
|
delivery: "PUBLIC_IMMUTABLE" | "PRIVATE_SIGNED";
|
|
capability: Readonly<{
|
|
capabilityId: string;
|
|
expiresAtEpochMs: number;
|
|
allowedPresetBindingIds: ReadonlySet<string>;
|
|
keyId: string;
|
|
capabilityBindingDigestHex: string;
|
|
signatureBase64Url: string;
|
|
}> | null;
|
|
}>;
|
|
|
|
type IssuedDescriptorSnapshot = BackendIssuedImageAsset;
|
|
|
|
const OPAQUE_TOKEN = /^[A-Za-z0-9_-]{8,128}$/u;
|
|
const SIGNATURE = /^[A-Za-z0-9_-]{16,512}$/u;
|
|
const SHA256_HEX = /^[a-f0-9]{64}$/u;
|
|
const PUBLIC_DESCRIPTOR_KEYS = Object.freeze([
|
|
"kind",
|
|
"originKey",
|
|
"assetId",
|
|
"revision",
|
|
"mediaType",
|
|
"contentKind",
|
|
"intrinsicWidth",
|
|
"intrinsicHeight",
|
|
] as const);
|
|
const ISSUED_DESCRIPTOR_KEYS = Object.freeze([
|
|
"kind",
|
|
"issuer",
|
|
"originKey",
|
|
"assetId",
|
|
"revision",
|
|
"mediaType",
|
|
"contentKind",
|
|
"intrinsicWidth",
|
|
"intrinsicHeight",
|
|
"capabilityId",
|
|
"issuedAtEpochMs",
|
|
"expiresAtEpochMs",
|
|
"allowedPresetBindingIds",
|
|
"signature",
|
|
] as const);
|
|
const SIGNATURE_KEYS = Object.freeze([
|
|
"algorithm",
|
|
"keyId",
|
|
"capabilityBindingDigestHex",
|
|
"valueBase64Url",
|
|
] as const);
|
|
const RESOLVE_KEYS = Object.freeze([
|
|
"asset",
|
|
"preset",
|
|
"signal",
|
|
] as const);
|
|
const ACCEPT_OPTIONS_KEYS = Object.freeze(["signal"] as const);
|
|
|
|
/**
|
|
* Creates the public runtime factory. Composition should expose
|
|
* `presentation` to features and keep `assets` at the backend gateway seam.
|
|
*/
|
|
export function createImageCdnRuntime(
|
|
dependencies: ImageCdnRuntimeDependencies,
|
|
): ImageCdnRuntime {
|
|
const resolveOrigin =
|
|
dependencies.policies.resolveOrigin.bind(dependencies.policies);
|
|
const resolvePreset =
|
|
dependencies.policies.resolvePreset.bind(dependencies.policies);
|
|
const hasPresetBinding =
|
|
dependencies.policies.hasPresetBinding.bind(dependencies.policies);
|
|
const hardLimits = snapshotHardLimits(
|
|
dependencies.policies.hardLimits(),
|
|
);
|
|
const resolvedCapabilityPolicy =
|
|
dependencies.policies.capabilityPolicy();
|
|
const capabilityPolicy = Object.freeze({
|
|
issuer: resolvedCapabilityPolicy.issuer,
|
|
acceptedKeyIds: new Set(
|
|
resolvedCapabilityPolicy.acceptedKeyIds,
|
|
),
|
|
});
|
|
const now = dependencies.now ?? Date.now;
|
|
const digest = dependencies.subtle?.digest.bind(dependencies.subtle);
|
|
const verifyCapability =
|
|
dependencies.capabilityVerifier?.verify.bind(
|
|
dependencies.capabilityVerifier,
|
|
);
|
|
const acceptsCapabilityKey =
|
|
dependencies.capabilityVerifier?.acceptsKey.bind(
|
|
dependencies.capabilityVerifier,
|
|
);
|
|
if (dependencies.capabilityVerifier) {
|
|
try {
|
|
if (
|
|
!verifyCapability ||
|
|
!acceptsCapabilityKey ||
|
|
[...capabilityPolicy.acceptedKeyIds].some(
|
|
(keyId) => acceptsCapabilityKey(keyId) !== true,
|
|
)
|
|
) {
|
|
throw new TypeError(
|
|
"Image capability verifier registry does not cover policy keys.",
|
|
);
|
|
}
|
|
} catch {
|
|
throw new TypeError(
|
|
"Image capability verifier registry does not cover policy keys.",
|
|
);
|
|
}
|
|
}
|
|
const capabilityVerificationTimeoutMs =
|
|
dependencies.capabilityVerificationTimeoutMs ??
|
|
DEFAULT_IMAGE_CAPABILITY_VERIFICATION_TIMEOUT_MS;
|
|
if (
|
|
!Number.isSafeInteger(capabilityVerificationTimeoutMs) ||
|
|
capabilityVerificationTimeoutMs < 1 ||
|
|
capabilityVerificationTimeoutMs >
|
|
MAX_IMAGE_CAPABILITY_VERIFICATION_TIMEOUT_MS
|
|
) {
|
|
throw new TypeError(
|
|
"Image capability verification timeout is invalid.",
|
|
);
|
|
}
|
|
const capabilityVerificationScheduler =
|
|
snapshotCapabilityVerificationScheduler(
|
|
dependencies.capabilityVerificationScheduler ??
|
|
defaultCapabilityVerificationScheduler(),
|
|
);
|
|
const probe =
|
|
dependencies.probe?.probe.bind(dependencies.probe);
|
|
const observer = snapshotObserver(dependencies.observer);
|
|
let acceptedAssets =
|
|
new WeakMap<ImageAssetReference, AssetSnapshot>();
|
|
const lifetime = new AbortController();
|
|
let activeCapabilityVerifications = 0;
|
|
let closed = false;
|
|
|
|
const acceptPublicImmutableCore:
|
|
ImageCdnRuntime["assets"]["acceptPublicImmutable"] = (
|
|
descriptor,
|
|
) => {
|
|
if (closed) return imageRuntimeClosedFailure();
|
|
const snapshot = snapshotPublicDescriptor(descriptor);
|
|
if (!snapshot) {
|
|
return browserDataFailure("INVALID_INPUT", "IMAGE_RESOLVE");
|
|
}
|
|
const origin = resolveOrigin(snapshot.originKey);
|
|
if (
|
|
snapshot.kind !== "ALLOWLISTED_PUBLIC" ||
|
|
!origin ||
|
|
!validAssetMetadata(snapshot, hardLimits)
|
|
) {
|
|
return browserDataFailure("POLICY_REJECTED", "IMAGE_RESOLVE");
|
|
}
|
|
const reference = createAssetReference();
|
|
acceptedAssets.set(
|
|
reference,
|
|
Object.freeze({
|
|
origin,
|
|
assetId: snapshot.assetId,
|
|
revision: snapshot.revision,
|
|
mediaType: snapshot.mediaType,
|
|
intrinsicWidth: snapshot.intrinsicWidth,
|
|
intrinsicHeight: snapshot.intrinsicHeight,
|
|
delivery: "PUBLIC_IMMUTABLE",
|
|
capability: null,
|
|
}),
|
|
);
|
|
return browserDataSuccess(reference);
|
|
};
|
|
|
|
const acceptPublicImmutable:
|
|
ImageCdnRuntime["assets"]["acceptPublicImmutable"] = (
|
|
descriptor,
|
|
) => {
|
|
const result = acceptPublicImmutableCore(descriptor);
|
|
return observeImageTerminal(
|
|
observer,
|
|
result,
|
|
result.ok ? 1 : 0,
|
|
);
|
|
};
|
|
|
|
const acceptBackendIssuedCore:
|
|
ImageCdnRuntime["assets"]["acceptBackendIssued"] = async (
|
|
descriptor,
|
|
options = {},
|
|
) => {
|
|
if (closed) return imageRuntimeClosedFailure();
|
|
if (
|
|
!hasOnlyOwnKeys(options, ACCEPT_OPTIONS_KEYS) ||
|
|
!validOptionalSignal(options.signal)
|
|
) {
|
|
return browserDataFailure("INVALID_INPUT", "IMAGE_RESOLVE");
|
|
}
|
|
if (options.signal?.aborted) {
|
|
return browserDataFailure("ABORTED", "IMAGE_RESOLVE");
|
|
}
|
|
const snapshot = snapshotIssuedDescriptor(descriptor);
|
|
if (!snapshot) {
|
|
return browserDataFailure("INVALID_INPUT", "IMAGE_RESOLVE");
|
|
}
|
|
const origin = resolveOrigin(snapshot.originKey);
|
|
if (
|
|
!origin ||
|
|
!validAssetMetadata(snapshot, hardLimits) ||
|
|
snapshot.issuer !== capabilityPolicy.issuer ||
|
|
!capabilityPolicy.acceptedKeyIds.has(
|
|
snapshot.signature.keyId,
|
|
) ||
|
|
snapshot.allowedPresetBindingIds.length >
|
|
hardLimits.maxPresetBindingsPerCapability ||
|
|
snapshot.allowedPresetBindingIds.some(
|
|
(bindingId) => !hasPresetBinding(bindingId),
|
|
)
|
|
) {
|
|
return browserDataFailure("POLICY_REJECTED", "IMAGE_RESOLVE");
|
|
}
|
|
const acceptedAt = safeNow(now);
|
|
if (acceptedAt === null) {
|
|
return browserDataFailure("UNAVAILABLE", "IMAGE_RESOLVE", {
|
|
retryable: true,
|
|
recovery: "RETRY",
|
|
});
|
|
}
|
|
const capabilityLifetimeMs =
|
|
snapshot.expiresAtEpochMs - snapshot.issuedAtEpochMs;
|
|
if (
|
|
snapshot.issuedAtEpochMs >
|
|
acceptedAt + hardLimits.maxClockSkewMs ||
|
|
capabilityLifetimeMs <= 0 ||
|
|
capabilityLifetimeMs > hardLimits.maxCapabilityLifetimeMs
|
|
) {
|
|
return browserDataFailure("POLICY_REJECTED", "IMAGE_RESOLVE");
|
|
}
|
|
if (
|
|
snapshot.expiresAtEpochMs - acceptedAt <
|
|
hardLimits.minCapabilityRemainingMs
|
|
) {
|
|
return browserDataFailure(
|
|
"EXPIRED_RESOURCE",
|
|
"IMAGE_RESOLVE",
|
|
);
|
|
}
|
|
if (!digest || !verifyCapability || !acceptsCapabilityKey) {
|
|
return browserDataFailure("UNSUPPORTED", "IMAGE_RESOLVE");
|
|
}
|
|
let verifierAcceptsDescriptorKey: boolean;
|
|
try {
|
|
verifierAcceptsDescriptorKey =
|
|
acceptsCapabilityKey(snapshot.signature.keyId) === true;
|
|
} catch {
|
|
return browserDataFailure("UNAVAILABLE", "IMAGE_RESOLVE", {
|
|
retryable: true,
|
|
recovery: "RETRY",
|
|
});
|
|
}
|
|
if (!verifierAcceptsDescriptorKey) {
|
|
return browserDataFailure("POLICY_REJECTED", "IMAGE_RESOLVE");
|
|
}
|
|
if (
|
|
activeCapabilityVerifications >=
|
|
hardLimits.maxConcurrentCapabilityVerifications
|
|
) {
|
|
return browserDataFailure(
|
|
"LIMIT_EXCEEDED",
|
|
"IMAGE_RESOLVE",
|
|
);
|
|
}
|
|
activeCapabilityVerifications += 1;
|
|
// TR-RR-07. The slot belongs to the raw verifier, not to this wrapper.
|
|
// Releasing it when the wrapper's deadline expired let an abandoned
|
|
// verification keep running while a new one was admitted, so repeated
|
|
// timeouts produced more physical work than the configured cap allows.
|
|
const rawVerificationTasks: Promise<unknown>[] = [];
|
|
try {
|
|
const canonicalPayload =
|
|
canonicalImageCapabilityPayload(snapshot);
|
|
let bindingDigest: string;
|
|
let verified: boolean;
|
|
let deadline: ImageCapabilityVerificationDeadline;
|
|
try {
|
|
deadline = createCapabilityVerificationDeadline(
|
|
[options.signal, lifetime.signal],
|
|
capabilityVerificationTimeoutMs,
|
|
capabilityVerificationScheduler,
|
|
);
|
|
} catch {
|
|
return browserDataFailure("UNAVAILABLE", "IMAGE_RESOLVE", {
|
|
retryable: true,
|
|
recovery: "RETRY",
|
|
});
|
|
}
|
|
try {
|
|
if (deadline.signal.aborted) {
|
|
throw capabilityVerificationAbortException();
|
|
}
|
|
const digestTask = sha256Hex(digest, canonicalPayload);
|
|
rawVerificationTasks.push(digestTask);
|
|
bindingDigest = await awaitImageRuntimeAbort(
|
|
digestTask,
|
|
deadline.signal,
|
|
);
|
|
if (
|
|
!constantTimeHexEqual(
|
|
bindingDigest,
|
|
snapshot.signature.capabilityBindingDigestHex,
|
|
)
|
|
) {
|
|
return browserDataFailure(
|
|
"INTEGRITY_FAILED",
|
|
"IMAGE_RESOLVE",
|
|
);
|
|
}
|
|
if (deadline.signal.aborted) {
|
|
throw capabilityVerificationAbortException();
|
|
}
|
|
const verifyTask = verifyCapability({
|
|
algorithm: snapshot.signature.algorithm,
|
|
keyId: snapshot.signature.keyId,
|
|
canonicalPayload: Uint8Array.from(canonicalPayload),
|
|
signatureBase64Url: snapshot.signature.valueBase64Url,
|
|
});
|
|
rawVerificationTasks.push(verifyTask);
|
|
verified = await awaitImageRuntimeAbort(
|
|
verifyTask,
|
|
deadline.signal,
|
|
);
|
|
} catch {
|
|
if (closed) return imageRuntimeClosedFailure();
|
|
return capabilityVerificationFailure(options.signal);
|
|
} finally {
|
|
deadline.release();
|
|
}
|
|
if (closed) return imageRuntimeClosedFailure();
|
|
if (options.signal?.aborted) {
|
|
return browserDataFailure("ABORTED", "IMAGE_RESOLVE");
|
|
}
|
|
if (verified !== true) {
|
|
return browserDataFailure(
|
|
"INTEGRITY_FAILED",
|
|
"IMAGE_RESOLVE",
|
|
);
|
|
}
|
|
const verifiedAt = safeNow(now);
|
|
if (
|
|
verifiedAt === null ||
|
|
snapshot.expiresAtEpochMs <= verifiedAt
|
|
) {
|
|
return browserDataFailure(
|
|
"EXPIRED_RESOURCE",
|
|
"IMAGE_RESOLVE",
|
|
);
|
|
}
|
|
|
|
const reference = createAssetReference();
|
|
acceptedAssets.set(
|
|
reference,
|
|
Object.freeze({
|
|
origin,
|
|
assetId: snapshot.assetId,
|
|
revision: snapshot.revision,
|
|
mediaType: snapshot.mediaType,
|
|
intrinsicWidth: snapshot.intrinsicWidth,
|
|
intrinsicHeight: snapshot.intrinsicHeight,
|
|
delivery: "PRIVATE_SIGNED",
|
|
capability: Object.freeze({
|
|
capabilityId: snapshot.capabilityId,
|
|
expiresAtEpochMs: snapshot.expiresAtEpochMs,
|
|
allowedPresetBindingIds: new Set(
|
|
snapshot.allowedPresetBindingIds,
|
|
),
|
|
keyId: snapshot.signature.keyId,
|
|
capabilityBindingDigestHex:
|
|
snapshot.signature.capabilityBindingDigestHex,
|
|
signatureBase64Url:
|
|
snapshot.signature.valueBase64Url,
|
|
}),
|
|
}),
|
|
);
|
|
return browserDataSuccess(reference);
|
|
} finally {
|
|
// Released only once the physical work this slot admitted has settled.
|
|
void Promise.allSettled(rawVerificationTasks).then(() => {
|
|
activeCapabilityVerifications -= 1;
|
|
});
|
|
}
|
|
};
|
|
|
|
const acceptBackendIssued:
|
|
ImageCdnRuntime["assets"]["acceptBackendIssued"] = async (
|
|
descriptor,
|
|
options,
|
|
) => {
|
|
const result = await acceptBackendIssuedCore(
|
|
descriptor,
|
|
options,
|
|
);
|
|
return observeImageTerminal(
|
|
observer,
|
|
result,
|
|
result.ok ? 1 : 0,
|
|
);
|
|
};
|
|
|
|
const resolveCore:
|
|
ImageCdnRuntime["presentation"]["resolve"] = async (
|
|
request,
|
|
) => {
|
|
if (closed) return imageRuntimeClosedFailure();
|
|
if (
|
|
!hasOnlyOwnKeys(request, RESOLVE_KEYS) ||
|
|
!("asset" in request) ||
|
|
!("preset" in request) ||
|
|
!validOptionalSignal(request.signal)
|
|
) {
|
|
return browserDataFailure("INVALID_INPUT", "IMAGE_RESOLVE");
|
|
}
|
|
if (request.signal?.aborted) {
|
|
return browserDataFailure("ABORTED", "IMAGE_RESOLVE");
|
|
}
|
|
const asset = acceptedAssets.get(request.asset);
|
|
const preset = resolvePreset(request.preset);
|
|
if (!asset || !preset) {
|
|
return browserDataFailure("POLICY_REJECTED", "IMAGE_RESOLVE");
|
|
}
|
|
if (
|
|
asset.capability &&
|
|
!asset.capability.allowedPresetBindingIds.has(
|
|
preset.bindingId,
|
|
)
|
|
) {
|
|
return browserDataFailure("POLICY_REJECTED", "IMAGE_RESOLVE");
|
|
}
|
|
const expiryFailure = checkExpiry(
|
|
asset,
|
|
now,
|
|
hardLimits.minCapabilityRemainingMs,
|
|
);
|
|
if (expiryFailure) return expiryFailure;
|
|
if (
|
|
asset.delivery === "PRIVATE_SIGNED" &&
|
|
(preset.loading !== "eager" ||
|
|
preset.fetchPriority === "low" ||
|
|
preset.probeMode !== "PRIMARY_REQUIRED")
|
|
) {
|
|
return browserDataFailure("POLICY_REJECTED", "IMAGE_RESOLVE");
|
|
}
|
|
if (
|
|
!preset.allowUpscale &&
|
|
preset.candidates.some(
|
|
(candidate) =>
|
|
candidate.pixelWidth > asset.intrinsicWidth ||
|
|
candidate.pixelHeight > asset.intrinsicHeight,
|
|
)
|
|
) {
|
|
return browserDataFailure(
|
|
"LIMIT_EXCEEDED",
|
|
"IMAGE_RESOLVE",
|
|
);
|
|
}
|
|
|
|
let descriptor: ImagePresentationDescriptor;
|
|
try {
|
|
descriptor = buildPresentationDescriptor(
|
|
asset,
|
|
preset,
|
|
hardLimits.maxUrlLength,
|
|
);
|
|
} catch {
|
|
return browserDataFailure("POLICY_REJECTED", "IMAGE_RESOLVE");
|
|
}
|
|
|
|
if (preset.probeMode === "PRIMARY_REQUIRED") {
|
|
if (!probe || !request.signal) {
|
|
return browserDataFailure("UNSUPPORTED", "IMAGE_RESOLVE");
|
|
}
|
|
const primary = primaryCandidate(preset);
|
|
let probeResult: BrowserDataResult<ImageProbeReceipt>;
|
|
const probeScope = combineImageRuntimeAbortSignals([
|
|
request.signal,
|
|
lifetime.signal,
|
|
]);
|
|
try {
|
|
probeResult = await awaitImageRuntimeAbort(
|
|
probe({
|
|
absoluteUrl: descriptor.src,
|
|
expectedMediaType: descriptor.fallbackMediaType,
|
|
expectedWidth: primary.pixelWidth,
|
|
expectedHeight: primary.pixelHeight,
|
|
maxEncodedBytes: preset.maxEncodedBytes,
|
|
maxDecodedPixels: preset.maxTransformedPixels,
|
|
maxDecodedBytes: preset.maxDecodedBytes,
|
|
delivery: asset.delivery,
|
|
minimumPublicMaxAgeSeconds:
|
|
asset.origin.minimumPublicMaxAgeSeconds,
|
|
referrerPolicy: descriptor.referrerPolicy,
|
|
signal: probeScope.signal,
|
|
}),
|
|
probeScope.signal,
|
|
);
|
|
} catch {
|
|
probeResult = closed
|
|
? imageRuntimeClosedFailure()
|
|
: request.signal.aborted
|
|
? browserDataFailure("ABORTED", "IMAGE_RESOLVE")
|
|
: browserDataFailure("UNAVAILABLE", "IMAGE_RESOLVE", {
|
|
retryable: true,
|
|
recovery: "RETRY",
|
|
});
|
|
} finally {
|
|
probeScope.release();
|
|
}
|
|
if (closed) probeResult = imageRuntimeClosedFailure();
|
|
observeImageTerminal(
|
|
observer,
|
|
probeResult,
|
|
1,
|
|
probeResult.ok
|
|
? probeResult.value.encodedBytes
|
|
: undefined,
|
|
);
|
|
if (!probeResult.ok) return probeResult;
|
|
if (
|
|
!validProbeReceipt(
|
|
probeResult.value,
|
|
descriptor.src,
|
|
descriptor.fallbackMediaType,
|
|
primary,
|
|
preset.maxEncodedBytes,
|
|
)
|
|
) {
|
|
return browserDataFailure(
|
|
"INTEGRITY_FAILED",
|
|
"IMAGE_RESOLVE",
|
|
);
|
|
}
|
|
if (request.signal.aborted) {
|
|
return browserDataFailure("ABORTED", "IMAGE_RESOLVE");
|
|
}
|
|
const postProbeExpiry = checkExpiry(
|
|
asset,
|
|
now,
|
|
hardLimits.minCapabilityRemainingMs,
|
|
);
|
|
if (postProbeExpiry) return postProbeExpiry;
|
|
}
|
|
|
|
if (closed) return imageRuntimeClosedFailure();
|
|
return browserDataSuccess(descriptor);
|
|
};
|
|
|
|
const resolve:
|
|
ImageCdnRuntime["presentation"]["resolve"] = async (
|
|
request,
|
|
) => {
|
|
const result = await resolveCore(request);
|
|
return observeImageTerminal(
|
|
observer,
|
|
result,
|
|
result.ok ? 1 + result.value.sources.length : 0,
|
|
result.ok
|
|
? result.value.decodeBudget.maximumEncodedBytes
|
|
: undefined,
|
|
);
|
|
};
|
|
|
|
return Object.freeze({
|
|
assets: Object.freeze({
|
|
acceptPublicImmutable,
|
|
acceptBackendIssued,
|
|
}),
|
|
presentation: Object.freeze({ resolve }),
|
|
close(): void {
|
|
if (closed) return;
|
|
closed = true;
|
|
lifetime.abort();
|
|
acceptedAssets =
|
|
new WeakMap<ImageAssetReference, AssetSnapshot>();
|
|
},
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Canonical signed payload. Backend and CDN implementations must reproduce
|
|
* this exact JSON-array encoding and UTF-8 bytes for schema version 1.
|
|
*/
|
|
export function canonicalImageCapabilityPayload(
|
|
descriptor: BackendIssuedImageAsset,
|
|
): Uint8Array {
|
|
return new TextEncoder().encode(
|
|
JSON.stringify([
|
|
"image-cdn-capability-v1",
|
|
descriptor.issuer,
|
|
descriptor.originKey,
|
|
descriptor.assetId,
|
|
descriptor.revision,
|
|
descriptor.mediaType,
|
|
descriptor.contentKind,
|
|
descriptor.intrinsicWidth,
|
|
descriptor.intrinsicHeight,
|
|
descriptor.capabilityId,
|
|
descriptor.issuedAtEpochMs,
|
|
descriptor.expiresAtEpochMs,
|
|
[...descriptor.allowedPresetBindingIds].sort(),
|
|
descriptor.signature.algorithm,
|
|
descriptor.signature.keyId,
|
|
]),
|
|
);
|
|
}
|
|
|
|
export async function computeImageCapabilityBindingDigestHex(
|
|
subtle: Pick<SubtleCrypto, "digest">,
|
|
descriptor: BackendIssuedImageAsset,
|
|
): Promise<string> {
|
|
return sha256Hex(
|
|
subtle.digest.bind(subtle),
|
|
canonicalImageCapabilityPayload(descriptor),
|
|
);
|
|
}
|
|
|
|
function buildPresentationDescriptor(
|
|
asset: AssetSnapshot,
|
|
preset: ResolvedImageCdnPreset,
|
|
maxUrlLength: number,
|
|
): ImagePresentationDescriptor {
|
|
const fallbackFormat = preset.formats.at(-1);
|
|
if (!fallbackFormat) {
|
|
throw new TypeError("Image CDN fallback format is missing.");
|
|
}
|
|
const primary = primaryCandidate(preset);
|
|
const sourceSets = preset.formats.map((format) =>
|
|
Object.freeze({
|
|
format,
|
|
type: IMAGE_FORMAT_MEDIA_TYPE[format],
|
|
srcSet: buildSrcSet(
|
|
asset,
|
|
preset,
|
|
format,
|
|
maxUrlLength,
|
|
),
|
|
}),
|
|
);
|
|
const fallback = sourceSets.at(-1);
|
|
if (!fallback) {
|
|
throw new TypeError("Image CDN fallback source is missing.");
|
|
}
|
|
const src = buildCandidateUrl(
|
|
asset,
|
|
preset,
|
|
fallbackFormat,
|
|
primary,
|
|
maxUrlLength,
|
|
);
|
|
const maximumCandidatePixels = Math.max(
|
|
...preset.candidates.map((candidate) => candidate.pixels),
|
|
);
|
|
const maximumDecodedBytes = Math.max(
|
|
...preset.candidates.map(
|
|
(candidate) => candidate.decodedBytes,
|
|
),
|
|
);
|
|
const isPublic = asset.delivery === "PUBLIC_IMMUTABLE";
|
|
return Object.freeze({
|
|
src,
|
|
srcSet: fallback.srcSet,
|
|
sources: Object.freeze(
|
|
sourceSets.slice(0, -1).map(({ type, srcSet }) =>
|
|
Object.freeze({ type, srcSet }),
|
|
),
|
|
),
|
|
sizes: preset.sizes,
|
|
width: preset.width,
|
|
height: preset.height,
|
|
fallbackMediaType: fallback.type,
|
|
loading: preset.loading,
|
|
decoding: preset.decoding,
|
|
fetchPriority: preset.fetchPriority,
|
|
referrerPolicy: isPublic
|
|
? preset.referrerPolicy
|
|
: "no-referrer",
|
|
crossOrigin: "anonymous",
|
|
delivery: Object.freeze({
|
|
class: asset.delivery,
|
|
assetVersion: asset.revision,
|
|
browserCache: isPublic
|
|
? "PUBLIC_IMMUTABLE"
|
|
: "NO_STORE",
|
|
sharedCache: isPublic
|
|
? "PUBLIC_IMMUTABLE"
|
|
: "FORBIDDEN",
|
|
purge: isPublic
|
|
? "REVISION_ROLLOVER"
|
|
: "CAPABILITY_REVOCATION_OR_EXPIRY",
|
|
expiresAtEpochMs:
|
|
asset.capability?.expiresAtEpochMs ?? null,
|
|
}),
|
|
decodeBudget: Object.freeze({
|
|
maximumCandidatePixels,
|
|
maximumDecodedBytes,
|
|
maximumEncodedBytes: preset.maxEncodedBytes,
|
|
}),
|
|
});
|
|
}
|
|
|
|
function buildSrcSet(
|
|
asset: AssetSnapshot,
|
|
preset: ResolvedImageCdnPreset,
|
|
format: ImageOutputFormat,
|
|
maxUrlLength: number,
|
|
): string {
|
|
return preset.candidates
|
|
.map(
|
|
(candidate) =>
|
|
`${buildCandidateUrl(
|
|
asset,
|
|
preset,
|
|
format,
|
|
candidate,
|
|
maxUrlLength,
|
|
)} ${candidate.pixelWidth}w`,
|
|
)
|
|
.join(", ");
|
|
}
|
|
|
|
function buildCandidateUrl(
|
|
asset: AssetSnapshot,
|
|
preset: ResolvedImageCdnPreset,
|
|
format: ImageOutputFormat,
|
|
candidate: ImageCandidateGeometry,
|
|
maxUrlLength: number,
|
|
): string {
|
|
const pathname =
|
|
`${asset.origin.assetPathPrefix}` +
|
|
`${encodeURIComponent(asset.assetId)}/` +
|
|
`${encodeURIComponent(asset.revision)}`;
|
|
const url = new URL(pathname, asset.origin.origin);
|
|
url.searchParams.set("dpr", formatDpr(candidate.dpr));
|
|
url.searchParams.set("fit", preset.fit);
|
|
url.searchParams.set("format", format);
|
|
url.searchParams.set("height", String(candidate.cssHeight));
|
|
url.searchParams.set("preset", preset.bindingId);
|
|
url.searchParams.set("quality", String(preset.quality));
|
|
url.searchParams.set("width", String(candidate.cssWidth));
|
|
|
|
const expectedQueryNames = new Set([
|
|
"dpr",
|
|
"fit",
|
|
"format",
|
|
"height",
|
|
"preset",
|
|
"quality",
|
|
"width",
|
|
]);
|
|
if (asset.capability) {
|
|
url.searchParams.set(
|
|
"binding",
|
|
asset.capability.capabilityBindingDigestHex,
|
|
);
|
|
url.searchParams.set(
|
|
"capability",
|
|
asset.capability.capabilityId,
|
|
);
|
|
url.searchParams.set(
|
|
"expires",
|
|
String(asset.capability.expiresAtEpochMs),
|
|
);
|
|
url.searchParams.set("key", asset.capability.keyId);
|
|
url.searchParams.set(
|
|
"signature",
|
|
asset.capability.signatureBase64Url,
|
|
);
|
|
for (const name of [
|
|
"binding",
|
|
"capability",
|
|
"expires",
|
|
"key",
|
|
"signature",
|
|
]) {
|
|
expectedQueryNames.add(name);
|
|
}
|
|
}
|
|
url.searchParams.sort();
|
|
|
|
const queryNames = [...url.searchParams.keys()];
|
|
if (
|
|
url.protocol !== "https:" ||
|
|
url.origin !== asset.origin.origin ||
|
|
url.username !== "" ||
|
|
url.password !== "" ||
|
|
url.hash !== "" ||
|
|
url.pathname !== pathname ||
|
|
queryNames.length !== expectedQueryNames.size ||
|
|
new Set(queryNames).size !== queryNames.length ||
|
|
queryNames.some((name) => !expectedQueryNames.has(name)) ||
|
|
url.href.length > maxUrlLength
|
|
) {
|
|
throw new TypeError("Image CDN URL policy was violated.");
|
|
}
|
|
return url.href;
|
|
}
|
|
|
|
function primaryCandidate(
|
|
preset: ResolvedImageCdnPreset,
|
|
): ImageCandidateGeometry {
|
|
const primary = preset.candidates.find(
|
|
(candidate) =>
|
|
candidate.cssWidth === preset.width &&
|
|
candidate.dpr === 1,
|
|
);
|
|
if (!primary) {
|
|
throw new TypeError("Image CDN primary candidate is missing.");
|
|
}
|
|
return primary;
|
|
}
|
|
|
|
function snapshotPublicDescriptor(
|
|
input: PublicImmutableImageAsset,
|
|
): PublicImmutableImageAsset | null {
|
|
if (!hasExactOwnKeys(input, PUBLIC_DESCRIPTOR_KEYS)) {
|
|
return null;
|
|
}
|
|
try {
|
|
return Object.freeze({
|
|
kind: input.kind,
|
|
originKey: input.originKey,
|
|
assetId: input.assetId,
|
|
revision: input.revision,
|
|
mediaType: input.mediaType,
|
|
contentKind: input.contentKind,
|
|
intrinsicWidth: input.intrinsicWidth,
|
|
intrinsicHeight: input.intrinsicHeight,
|
|
});
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
function snapshotIssuedDescriptor(
|
|
input: BackendIssuedImageAsset,
|
|
): IssuedDescriptorSnapshot | null {
|
|
if (
|
|
!hasExactOwnKeys(input, ISSUED_DESCRIPTOR_KEYS) ||
|
|
!hasExactOwnKeys(input.signature, SIGNATURE_KEYS)
|
|
) {
|
|
return null;
|
|
}
|
|
try {
|
|
const allowedPresetBindingIds = [
|
|
...input.allowedPresetBindingIds,
|
|
];
|
|
const snapshot: IssuedDescriptorSnapshot = Object.freeze({
|
|
kind: input.kind,
|
|
issuer: input.issuer,
|
|
originKey: input.originKey,
|
|
assetId: input.assetId,
|
|
revision: input.revision,
|
|
mediaType: input.mediaType,
|
|
contentKind: input.contentKind,
|
|
intrinsicWidth: input.intrinsicWidth,
|
|
intrinsicHeight: input.intrinsicHeight,
|
|
capabilityId: input.capabilityId,
|
|
issuedAtEpochMs: input.issuedAtEpochMs,
|
|
expiresAtEpochMs: input.expiresAtEpochMs,
|
|
allowedPresetBindingIds: Object.freeze(
|
|
allowedPresetBindingIds,
|
|
),
|
|
signature: Object.freeze({
|
|
algorithm: input.signature.algorithm,
|
|
keyId: input.signature.keyId,
|
|
capabilityBindingDigestHex:
|
|
input.signature.capabilityBindingDigestHex,
|
|
valueBase64Url: input.signature.valueBase64Url,
|
|
}),
|
|
});
|
|
if (
|
|
snapshot.kind !== "BACKEND_ISSUED_PRIVATE" ||
|
|
snapshot.contentKind !== "RASTER_STATIC" ||
|
|
!OPAQUE_TOKEN.test(snapshot.capabilityId) ||
|
|
!Number.isSafeInteger(snapshot.issuedAtEpochMs) ||
|
|
snapshot.issuedAtEpochMs < 0 ||
|
|
!Number.isSafeInteger(snapshot.expiresAtEpochMs) ||
|
|
snapshot.expiresAtEpochMs < 0 ||
|
|
!Array.isArray(input.allowedPresetBindingIds) ||
|
|
allowedPresetBindingIds.length < 1 ||
|
|
new Set(allowedPresetBindingIds).size !==
|
|
allowedPresetBindingIds.length ||
|
|
allowedPresetBindingIds.some(
|
|
(bindingId) =>
|
|
typeof bindingId !== "string" ||
|
|
!/^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u.test(
|
|
bindingId,
|
|
),
|
|
) ||
|
|
snapshot.signature.algorithm !== "ECDSA_P256_SHA256" ||
|
|
!/^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u.test(
|
|
snapshot.issuer,
|
|
) ||
|
|
!/^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u.test(
|
|
snapshot.signature.keyId,
|
|
) ||
|
|
!SHA256_HEX.test(
|
|
snapshot.signature.capabilityBindingDigestHex,
|
|
) ||
|
|
!SIGNATURE.test(snapshot.signature.valueBase64Url)
|
|
) {
|
|
return null;
|
|
}
|
|
return snapshot;
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
function validAssetMetadata(
|
|
input: Readonly<{
|
|
assetId: string;
|
|
revision: string;
|
|
mediaType: string;
|
|
contentKind: string;
|
|
intrinsicWidth: number;
|
|
intrinsicHeight: number;
|
|
}>,
|
|
hardLimits: ImageCdnHardLimits,
|
|
): input is Readonly<{
|
|
assetId: string;
|
|
revision: string;
|
|
mediaType: ImageRasterMediaType;
|
|
contentKind: "RASTER_STATIC";
|
|
intrinsicWidth: number;
|
|
intrinsicHeight: number;
|
|
}> {
|
|
return (
|
|
OPAQUE_TOKEN.test(input.assetId) &&
|
|
OPAQUE_TOKEN.test(input.revision) &&
|
|
input.contentKind === "RASTER_STATIC" &&
|
|
hardLimits.allowedSourceMediaTypes.includes(
|
|
input.mediaType as ImageRasterMediaType,
|
|
) &&
|
|
Number.isSafeInteger(input.intrinsicWidth) &&
|
|
input.intrinsicWidth > 0 &&
|
|
input.intrinsicWidth <= hardLimits.maxIntrinsicWidth &&
|
|
Number.isSafeInteger(input.intrinsicHeight) &&
|
|
input.intrinsicHeight > 0 &&
|
|
input.intrinsicHeight <= hardLimits.maxIntrinsicHeight &&
|
|
input.intrinsicWidth * input.intrinsicHeight <=
|
|
hardLimits.maxSourcePixels
|
|
);
|
|
}
|
|
|
|
function validProbeReceipt(
|
|
receipt: ImageProbeReceipt,
|
|
expectedUrl: string,
|
|
expectedMediaType: ImageRasterMediaType,
|
|
expectedGeometry: ImageCandidateGeometry,
|
|
maxEncodedBytes: number,
|
|
): boolean {
|
|
return (
|
|
receipt.absoluteUrl === expectedUrl &&
|
|
receipt.mediaType === expectedMediaType &&
|
|
Number.isSafeInteger(receipt.encodedBytes) &&
|
|
receipt.encodedBytes > 0 &&
|
|
receipt.encodedBytes <= maxEncodedBytes &&
|
|
receipt.decodedWidth === expectedGeometry.pixelWidth &&
|
|
receipt.decodedHeight === expectedGeometry.pixelHeight
|
|
);
|
|
}
|
|
|
|
function checkExpiry(
|
|
asset: AssetSnapshot,
|
|
now: () => number,
|
|
minimumRemainingMs: number,
|
|
): BrowserDataResult<never> | null {
|
|
if (!asset.capability) return null;
|
|
const current = safeNow(now);
|
|
if (
|
|
current === null ||
|
|
asset.capability.expiresAtEpochMs - current <
|
|
minimumRemainingMs
|
|
) {
|
|
return browserDataFailure(
|
|
"EXPIRED_RESOURCE",
|
|
"IMAGE_RESOLVE",
|
|
);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function safeNow(now: () => number): number | null {
|
|
try {
|
|
const value = now();
|
|
return Number.isSafeInteger(value) && value >= 0
|
|
? value
|
|
: null;
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
type ImageCapabilityVerificationDeadline = Readonly<{
|
|
signal: AbortSignal;
|
|
release(): void;
|
|
}>;
|
|
|
|
function createCapabilityVerificationDeadline(
|
|
signals: readonly (AbortSignal | undefined)[],
|
|
timeoutMs: number,
|
|
scheduler: ImageCapabilityVerificationScheduler,
|
|
): ImageCapabilityVerificationDeadline {
|
|
const controller = new AbortController();
|
|
let released = false;
|
|
const abortListeners = new Map<AbortSignal, () => void>();
|
|
const activeSignals = signals.filter(
|
|
(signal): signal is AbortSignal => signal !== undefined,
|
|
);
|
|
for (const signal of new Set(activeSignals)) {
|
|
const onAbort = () => controller.abort(signal.reason);
|
|
abortListeners.set(signal, onAbort);
|
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
if (signal.aborted) onAbort();
|
|
}
|
|
|
|
let timeoutHandle: unknown;
|
|
try {
|
|
timeoutHandle = scheduler.setTimeout(() => {
|
|
if (released) return;
|
|
controller.abort(capabilityVerificationAbortException());
|
|
}, timeoutMs);
|
|
} catch (error) {
|
|
for (const [signal, onAbort] of abortListeners) {
|
|
signal.removeEventListener("abort", onAbort);
|
|
}
|
|
throw error;
|
|
}
|
|
|
|
return Object.freeze({
|
|
signal: controller.signal,
|
|
release() {
|
|
if (released) return;
|
|
released = true;
|
|
try {
|
|
scheduler.clearTimeout(timeoutHandle);
|
|
} catch {
|
|
// Scheduler cleanup cannot alter an already closed adapter result.
|
|
}
|
|
for (const [signal, onAbort] of abortListeners) {
|
|
signal.removeEventListener("abort", onAbort);
|
|
}
|
|
abortListeners.clear();
|
|
},
|
|
});
|
|
}
|
|
|
|
function awaitImageRuntimeAbort<Value>(
|
|
task: Promise<Value>,
|
|
signal: AbortSignal,
|
|
): Promise<Value> {
|
|
return new Promise<Value>((resolve, reject) => {
|
|
let settled = false;
|
|
const onAbort = () => {
|
|
if (settled) return;
|
|
settled = true;
|
|
signal.removeEventListener("abort", onAbort);
|
|
reject(capabilityVerificationAbortException());
|
|
};
|
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
if (signal.aborted) onAbort();
|
|
void task.then(
|
|
(value) => {
|
|
if (settled) return;
|
|
settled = true;
|
|
signal.removeEventListener("abort", onAbort);
|
|
resolve(value);
|
|
},
|
|
(error: unknown) => {
|
|
if (settled) return;
|
|
settled = true;
|
|
signal.removeEventListener("abort", onAbort);
|
|
reject(error);
|
|
},
|
|
);
|
|
});
|
|
}
|
|
|
|
function combineImageRuntimeAbortSignals(
|
|
signals: readonly AbortSignal[],
|
|
): Readonly<{ signal: AbortSignal; release(): void }> {
|
|
const controller = new AbortController();
|
|
const abortListeners = new Map<AbortSignal, () => void>();
|
|
for (const signal of new Set(signals)) {
|
|
const onAbort = () => controller.abort(signal.reason);
|
|
abortListeners.set(signal, onAbort);
|
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
if (signal.aborted) onAbort();
|
|
}
|
|
let released = false;
|
|
return Object.freeze({
|
|
signal: controller.signal,
|
|
release() {
|
|
if (released) return;
|
|
released = true;
|
|
for (const [signal, onAbort] of abortListeners) {
|
|
signal.removeEventListener("abort", onAbort);
|
|
}
|
|
abortListeners.clear();
|
|
},
|
|
});
|
|
}
|
|
|
|
function capabilityVerificationFailure(
|
|
externalSignal: AbortSignal | undefined,
|
|
): BrowserDataResult<never> {
|
|
if (externalSignal?.aborted) {
|
|
return browserDataFailure("ABORTED", "IMAGE_RESOLVE");
|
|
}
|
|
return browserDataFailure("UNAVAILABLE", "IMAGE_RESOLVE", {
|
|
retryable: true,
|
|
recovery: "RETRY",
|
|
});
|
|
}
|
|
|
|
function imageRuntimeClosedFailure(): BrowserDataResult<never> {
|
|
return browserDataFailure("UNAVAILABLE", "IMAGE_RESOLVE");
|
|
}
|
|
|
|
function capabilityVerificationAbortException(): DOMException {
|
|
return new DOMException(
|
|
"Image capability verification was aborted.",
|
|
"AbortError",
|
|
);
|
|
}
|
|
|
|
function snapshotCapabilityVerificationScheduler(
|
|
scheduler: ImageCapabilityVerificationScheduler,
|
|
): ImageCapabilityVerificationScheduler {
|
|
if (
|
|
!scheduler ||
|
|
typeof scheduler.setTimeout !== "function" ||
|
|
typeof scheduler.clearTimeout !== "function"
|
|
) {
|
|
throw new TypeError(
|
|
"Image capability verification scheduler is invalid.",
|
|
);
|
|
}
|
|
return Object.freeze({
|
|
setTimeout: scheduler.setTimeout.bind(scheduler),
|
|
clearTimeout: scheduler.clearTimeout.bind(scheduler),
|
|
});
|
|
}
|
|
|
|
function defaultCapabilityVerificationScheduler(): ImageCapabilityVerificationScheduler {
|
|
const schedule = globalThis.setTimeout.bind(globalThis);
|
|
const clear = globalThis.clearTimeout.bind(globalThis);
|
|
return Object.freeze({
|
|
setTimeout(callback: () => void, milliseconds: number) {
|
|
return schedule(callback, milliseconds);
|
|
},
|
|
clearTimeout(handle: unknown) {
|
|
clear(
|
|
handle as ReturnType<typeof globalThis.setTimeout>,
|
|
);
|
|
},
|
|
});
|
|
}
|
|
|
|
function createAssetReference(): ImageAssetReference {
|
|
return Object.freeze({}) as ImageAssetReference;
|
|
}
|
|
|
|
function snapshotHardLimits(
|
|
input: ImageCdnHardLimits,
|
|
): ImageCdnHardLimits {
|
|
return Object.freeze({
|
|
...input,
|
|
allowedSourceMediaTypes: Object.freeze([
|
|
...input.allowedSourceMediaTypes,
|
|
]),
|
|
formatQualityCeilings: Object.freeze({
|
|
...input.formatQualityCeilings,
|
|
}),
|
|
});
|
|
}
|
|
|
|
function snapshotObserver(
|
|
observer: BrowserDataObserver | undefined,
|
|
): BrowserDataObserver | undefined {
|
|
if (!observer) return undefined;
|
|
const record = observer.record.bind(observer);
|
|
return Object.freeze({ record });
|
|
}
|
|
|
|
function observeImageTerminal<Value>(
|
|
observer: BrowserDataObserver | undefined,
|
|
result: BrowserDataResult<Value>,
|
|
count: number,
|
|
bytes?: number,
|
|
): BrowserDataResult<Value> {
|
|
observeBrowserData(observer, {
|
|
operation: "IMAGE_RESOLVE",
|
|
outcome: result.ok ? "SUCCEEDED" : "FAILED",
|
|
...(!result.ok ? { failureCode: result.error.code } : {}),
|
|
countBucket: countBucket(count),
|
|
...(bytes === undefined
|
|
? {}
|
|
: { byteBucket: byteBucket(bytes) }),
|
|
});
|
|
return result;
|
|
}
|
|
|
|
function countBucket(
|
|
value: number,
|
|
): "ZERO" | "ONE" | "TWO_TO_TEN" | "ELEVEN_TO_HUNDRED" | "GT_HUNDRED" {
|
|
if (value <= 0) return "ZERO";
|
|
if (value === 1) return "ONE";
|
|
if (value <= 10) return "TWO_TO_TEN";
|
|
if (value <= 100) return "ELEVEN_TO_HUNDRED";
|
|
return "GT_HUNDRED";
|
|
}
|
|
|
|
function byteBucket(
|
|
value: number,
|
|
): "ZERO" | "LT1MIB" | "1_TO_9MIB" | "10_TO_99MIB" | "GTE100MIB" {
|
|
if (value <= 0) return "ZERO";
|
|
if (value < 1024 * 1024) return "LT1MIB";
|
|
if (value < 10 * 1024 * 1024) return "1_TO_9MIB";
|
|
if (value < 100 * 1024 * 1024) return "10_TO_99MIB";
|
|
return "GTE100MIB";
|
|
}
|
|
|
|
function validOptionalSignal(
|
|
signal: AbortSignal | undefined,
|
|
): boolean {
|
|
return (
|
|
signal === undefined ||
|
|
(typeof signal === "object" &&
|
|
signal !== null &&
|
|
typeof signal.aborted === "boolean" &&
|
|
typeof signal.addEventListener === "function" &&
|
|
typeof signal.removeEventListener === "function")
|
|
);
|
|
}
|
|
|
|
function hasExactOwnKeys(
|
|
input: unknown,
|
|
keys: readonly string[],
|
|
): boolean {
|
|
if (!input || typeof input !== "object" || Array.isArray(input)) {
|
|
return false;
|
|
}
|
|
const actual = Object.keys(input).sort();
|
|
const expected = [...keys].sort();
|
|
return (
|
|
actual.length === expected.length &&
|
|
actual.every((key, index) => key === expected[index])
|
|
);
|
|
}
|
|
|
|
function hasOnlyOwnKeys(
|
|
input: unknown,
|
|
keys: readonly string[],
|
|
): boolean {
|
|
if (!input || typeof input !== "object" || Array.isArray(input)) {
|
|
return false;
|
|
}
|
|
return Object.keys(input).every((key) => keys.includes(key));
|
|
}
|
|
|
|
function formatDpr(value: number): string {
|
|
return String(value);
|
|
}
|
|
|
|
async function sha256Hex(
|
|
digest: SubtleCrypto["digest"],
|
|
bytes: Uint8Array,
|
|
): Promise<string> {
|
|
const payload = new Uint8Array(bytes.byteLength);
|
|
payload.set(bytes);
|
|
const result = await digest("SHA-256", payload.buffer);
|
|
return [...new Uint8Array(result)]
|
|
.map((value) => value.toString(16).padStart(2, "0"))
|
|
.join("");
|
|
}
|
|
|
|
function constantTimeHexEqual(
|
|
left: string,
|
|
right: string,
|
|
): boolean {
|
|
if (left.length !== right.length) return false;
|
|
let difference = 0;
|
|
for (let index = 0; index < left.length; index += 1) {
|
|
difference |=
|
|
(left.charCodeAt(index) || 0) ^
|
|
(right.charCodeAt(index) || 0);
|
|
}
|
|
return difference === 0;
|
|
}
|