import type { PresignedDownloadCapability, PresignedDownloadByteSource, PresignedDownloadSourcePort, PresignedTransferCapability, PresignedTransferReplayGuard, PresignedUploadPartCapability, PresignedUploadPartOutcome, PresignedUploadPartPort, } from "../../../application/ports/browser-transfer/presigned-transfer.ts"; import { RESUMABLE_UPLOAD_PROTOCOL } from "../../../application/ports/browser-transfer/resumable-upload.ts"; import type { BrowserDataFailureCode, BrowserDataObserver, BrowserDataOperation, BrowserDataRecovery, BrowserDataResult, } from "../../../application/ports/browser-file-storage/shared.ts"; import { browserDataFailure, browserDataSuccess, observeBrowserData, } from "../../browser-file-storage/result.ts"; import { createStreamingSha256Verifier, type StreamingSha256Verifier, } from "./incremental-sha256.ts"; import type { PresignedCapabilityBinding, PresignedCapabilityVault, } from "./presigned-capability-vault.ts"; const SHA256 = /^[a-f0-9]{64}$/; const RECEIPT_TOKEN = /^[A-Za-z0-9][A-Za-z0-9._~:+/=-]{0,511}$/; type Scheduler = Readonly<{ setTimeout(callback: () => void, milliseconds: number): unknown; clearTimeout(handle: unknown): void; }>; export type PresignedTransferExecutorOptions = Readonly<{ vault: PresignedCapabilityVault; replayGuard: PresignedTransferReplayGuard; hardMaxTransferBytes: number; hardMaxChunkBytes: number; hardMaxUploadResponseBytes: number; minimumRemainingLifetimeMs: number; timeoutMs: number; fetcher?: typeof fetch; now?: () => number; scheduler?: Scheduler; createStreamingVerifier?: ( expectedSha256: string, ) => StreamingSha256Verifier; digestBytes?: ( bytes: Uint8Array, ) => Promise; observer?: BrowserDataObserver; }>; export type PresignedTransferExecutor = Readonly<{ downloadSources: PresignedDownloadSourcePort; uploadParts: PresignedUploadPartPort; }>; /** * Consumes only exact handles issued into the supplied identity vault. Raw * href/query/header values never enter either public executor method. */ export function createPresignedTransferExecutor( options: PresignedTransferExecutorOptions, ): PresignedTransferExecutor { const resolve = options.vault.resolve.bind(options.vault); const consume = options.vault.consume.bind(options.vault); const claim = options.replayGuard.claim.bind(options.replayGuard); const hardMaxTransferBytes = positiveSafeInteger( options.hardMaxTransferBytes, ); const hardMaxChunkBytes = positiveSafeInteger( options.hardMaxChunkBytes, ); if (hardMaxChunkBytes > hardMaxTransferBytes) { throw new TypeError("Presigned chunk limit exceeds transfer limit."); } const hardMaxUploadResponseBytes = positiveSafeInteger( options.hardMaxUploadResponseBytes, ); const minimumRemainingLifetimeMs = positiveSafeInteger( options.minimumRemainingLifetimeMs, ); const timeoutMs = positiveSafeInteger(options.timeoutMs); const fetcher = (options.fetcher ?? fetch).bind(globalThis); const now = options.now ?? Date.now; const scheduler = options.scheduler ?? ({ setTimeout: (callback, milliseconds) => globalThis.setTimeout(callback, milliseconds), clearTimeout: (handle) => globalThis.clearTimeout( handle as ReturnType, ), } satisfies Scheduler); const createVerifier = options.createStreamingVerifier ?? createStreamingSha256Verifier; if (options.digestBytes === undefined && !globalThis.crypto?.subtle) { throw new TypeError( "WebCrypto SHA-256 is required for presigned uploads.", ); } const digestBytes = options.digestBytes ?? digestSha256WithWebCrypto; const observer = options.observer; async function openDownload( input: Parameters[0], ): Promise> { let resourceId: string; let capability: PresignedDownloadCapability; let signal: AbortSignal; try { resourceId = input.resourceId; capability = input.capability; signal = input.signal; if (!isAbortSignal(signal)) { throw new TypeError("Abort signal is invalid."); } } catch { return browserDataFailure("INVALID_INPUT", "PRESIGNED_TRANSFER"); } if (signal.aborted) { return browserDataFailure("ABORTED", "PRESIGNED_TRANSFER"); } const resolved = resolve(capability); if (!resolved.ok) return resolved; const binding = resolved.value; if ( binding.method !== "GET" || binding.binding.kind !== "DOWNLOAD" || binding.binding.resourceId !== resourceId || capability.binding.kind !== "DOWNLOAD" || capability.binding.resourceId !== resourceId || !validCommonBinding(binding, capability, hardMaxTransferBytes) ) { return browserDataFailure("POLICY_REJECTED", "PRESIGNED_TRANSFER"); } const active = validateExpiry( capability, minimumRemainingLifetimeMs, now(), ); if (!active.ok) return active; const claimed = claim(capability); if (!claimed.ok) return claimed; const consumed = consume(capability); if (!consumed.ok) return consumed; const scope = createAbortScope(signal, timeoutMs, scheduler); try { const response = await fetcher(binding.href, { method: "GET", headers: headersFor(binding), credentials: "omit", redirect: "error", referrerPolicy: "no-referrer", cache: "no-store", signal: scope.signal, }); const validated = validateDownloadResponse( response, binding, ); if (!validated.ok) { cancelBody(response); scope.release(); return validated; } const source = createDownloadSource({ response, binding, capability, externalSignal: signal, scope, hardMaxChunkBytes, createVerifier, observer, }); return browserDataSuccess(source); } catch { scope.release(); return transferFailure(signal, scope.timedOut()); } } async function putUploadPart( input: Parameters[0], ): Promise> { let capability: PresignedUploadPartCapability; let bytes: Uint8Array; let request: Readonly<{ sessionId: string; requestBindingSha256: string; uploadBindingSha256: string; partNumber: number; offset: number; byteLength: number; checksumSha256: string; idempotencyKey: string; signal: AbortSignal; }>; try { capability = input.capability; if (!(input.bytes instanceof Uint8Array)) { throw new TypeError("Upload bytes are invalid."); } if ( input.bytes.byteLength > hardMaxTransferBytes || input.bytes.byteLength !== input.byteLength ) { return browserDataFailure( input.bytes.byteLength > hardMaxTransferBytes ? "LIMIT_EXCEEDED" : "INTEGRITY_FAILED", "PRESIGNED_TRANSFER", ); } if (!isAbortSignal(input.signal)) { throw new TypeError("Abort signal is invalid."); } // Snapshot before hashing or network awaits so caller mutation cannot // alter the verified bytes after the capability check. bytes = input.bytes.slice(); request = Object.freeze({ sessionId: opaqueId(input.sessionId), requestBindingSha256: normalizedSha256( input.requestBindingSha256, ), uploadBindingSha256: normalizedSha256( input.uploadBindingSha256, ), partNumber: positiveSafeInteger(input.partNumber), offset: nonNegativeSafeInteger(input.offset), byteLength: positiveSafeInteger(input.byteLength), checksumSha256: normalizedSha256(input.checksumSha256), idempotencyKey: opaqueId(input.idempotencyKey), signal: input.signal, }); } catch { return browserDataFailure("INVALID_INPUT", "PRESIGNED_TRANSFER"); } if (request.signal.aborted) { return browserDataFailure("ABORTED", "PRESIGNED_TRANSFER"); } const resolved = resolve(capability); if (!resolved.ok) return resolved; const binding = resolved.value; if ( binding.method !== "PUT" || binding.binding.kind !== "UPLOAD_PART" || capability.binding.kind !== "UPLOAD_PART" || binding.binding.protocol !== RESUMABLE_UPLOAD_PROTOCOL || !validCommonBinding(binding, capability, hardMaxTransferBytes) || binding.binding.sessionId !== request.sessionId || binding.binding.requestBindingSha256 !== request.requestBindingSha256 || binding.binding.uploadBindingSha256 !== request.uploadBindingSha256 || binding.binding.partNumber !== request.partNumber || binding.binding.offset !== request.offset || binding.binding.idempotencyKey !== request.idempotencyKey || capability.byteLength !== request.byteLength || capability.expectedSha256 !== request.checksumSha256 || bytes.byteLength !== capability.byteLength ) { return browserDataFailure("POLICY_REJECTED", "PRESIGNED_TRANSFER"); } let actualDigest: string; try { actualDigest = normalizedSha256(await digestBytes(bytes)); } catch { return browserDataFailure( "INTEGRITY_FAILED", "PRESIGNED_TRANSFER", ); } if (actualDigest !== capability.expectedSha256) { return browserDataFailure( "INTEGRITY_FAILED", "PRESIGNED_TRANSFER", ); } if (request.signal.aborted) { return browserDataFailure("ABORTED", "PRESIGNED_TRANSFER"); } const active = validateExpiry( capability, minimumRemainingLifetimeMs, now(), ); if (!active.ok) return active; const claimed = claim(capability); if (!claimed.ok) return claimed; const consumed = consume(capability); if (!consumed.ok) return consumed; const scope = createAbortScope(request.signal, timeoutMs, scheduler); try { const response = await fetcher(binding.href, { method: "PUT", headers: headersFor(binding), body: bytes.buffer, credentials: "omit", redirect: "error", referrerPolicy: "no-referrer", cache: "no-store", signal: scope.signal, }); const validated = validateUploadResponse( response, binding, hardMaxUploadResponseBytes, ); if (!validated.ok) { cancelBody(response); return validated; } const drained = await drainUploadResponse( response, validated.value.responseByteLength, hardMaxUploadResponseBytes, scope.signal, ); if (!drained.ok) return drained; return browserDataSuccess( Object.freeze({ bytesWritten: bytes.byteLength, checksumSha256: capability.expectedSha256, receiptToken: validated.value.receiptToken, } satisfies PresignedUploadPartOutcome), ); } catch { return transferFailure(request.signal, scope.timedOut()); } finally { scope.release(); } } const downloadSources: PresignedDownloadSourcePort = Object.freeze({ async open( input: Parameters[0], ) { let result: BrowserDataResult; try { result = await openDownload(input); } catch { result = browserDataFailure( "UNAVAILABLE", "PRESIGNED_TRANSFER", { retryable: true, recovery: "REISSUE_CAPABILITY", }, ); } observeTransferResult( observer, "PRESIGNED_TRANSFER", result, result.ok ? result.value.byteLength : safeInputByteLength(input), ); return result; }, }); const uploadParts: PresignedUploadPartPort = Object.freeze({ async put( input: Parameters[0], ) { let result: BrowserDataResult; try { result = await putUploadPart(input); } catch { result = browserDataFailure( "UNAVAILABLE", "PRESIGNED_TRANSFER", { retryable: true, recovery: "REISSUE_CAPABILITY", }, ); } observeTransferResult( observer, "UPLOAD_PART", result, result.ok ? result.value.bytesWritten : safeInputByteLength(input), ); return result; }, }); return Object.freeze({ downloadSources, uploadParts }); } function createDownloadSource(input: Readonly<{ response: Response; binding: PresignedCapabilityBinding; capability: PresignedDownloadCapability; externalSignal: AbortSignal; scope: ReturnType; hardMaxChunkBytes: number; createVerifier: ( expectedSha256: string, ) => StreamingSha256Verifier; observer: BrowserDataObserver | undefined; }>): PresignedDownloadByteSource { let started = false; return Object.freeze({ byteLength: input.capability.byteLength, capability: input.capability, integrity: "VERIFIED_ON_SUCCESSFUL_EXHAUSTION" as const, async *stream( consumerSignal: AbortSignal, ): AsyncIterable> { if (!isAbortSignal(consumerSignal)) { started = true; cancelBody(input.response); input.scope.release(); const failure = browserDataFailure( "INVALID_INPUT", "PRESIGNED_TRANSFER", ); observeTransferResult(input.observer, "DOWNLOAD", failure, 0); yield failure; return; } if (started) { const failure = browserDataFailure( "CONFLICT", "PRESIGNED_TRANSFER", { recovery: "REISSUE_CAPABILITY", }, ); observeTransferResult(input.observer, "DOWNLOAD", failure, 0); yield failure; return; } started = true; let combined: | ReturnType | undefined; let reader: | ReadableStreamDefaultReader | undefined; let completed = false; let transferred = 0; let terminalFailureCode: BrowserDataFailureCode | undefined; const fail = ( code: BrowserDataFailureCode, options: Readonly<{ retryable?: boolean; recovery?: BrowserDataRecovery; }> = {}, ): BrowserDataResult => { terminalFailureCode = code; return browserDataFailure(code, "PRESIGNED_TRANSFER", options); }; try { combined = combineConsumerAbort( input.scope, consumerSignal, ); const verifier = input.createVerifier( input.capability.expectedSha256, ); if (!input.response.body) { let verified = false; try { verified = input.capability.byteLength === 0 && verifier.verify(); } catch { verified = false; } if (verified) { completed = true; return; } yield fail("INTEGRITY_FAILED"); return; } reader = input.response.body.getReader(); while (true) { if ( input.externalSignal.aborted || consumerSignal.aborted ) { yield fail("ABORTED"); return; } if (input.scope.timedOut()) { yield fail("UNAVAILABLE", { retryable: true, recovery: "REISSUE_CAPABILITY", }); return; } const result = await readWithSignal( reader, input.scope.signal, ); if (result.done) break; const chunk = result.value; if (!(chunk instanceof Uint8Array)) { yield fail("CORRUPT_DATA"); return; } const chunkEnd = transferred + chunk.byteLength; if ( !Number.isSafeInteger(chunkEnd) || chunkEnd > input.capability.maxBytes ) { yield fail("LIMIT_EXCEEDED"); return; } if (chunkEnd > input.capability.byteLength) { yield fail("INTEGRITY_FAILED"); return; } // Browser fetch chunk sizing is implementation-defined. Re-slice into // owned bounded chunks instead of rejecting a valid large chunk. for ( let offset = 0; offset < chunk.byteLength; offset += input.hardMaxChunkBytes ) { if ( input.externalSignal.aborted || consumerSignal.aborted ) { yield fail("ABORTED"); return; } if (input.scope.timedOut()) { yield fail("UNAVAILABLE", { retryable: true, recovery: "REISSUE_CAPABILITY", }); return; } const owned = chunk .subarray( offset, Math.min( chunk.byteLength, offset + input.hardMaxChunkBytes, ), ) .slice(); try { verifier.update(owned); } catch { yield fail("INTEGRITY_FAILED"); return; } transferred += owned.byteLength; yield browserDataSuccess(owned); } } let verified = false; try { verified = verifier.verify(); } catch { verified = false; } if ( transferred !== input.capability.byteLength || !verified ) { yield fail("INTEGRITY_FAILED"); return; } completed = true; } catch { if ( input.externalSignal.aborted || consumerSignal.aborted ) { yield fail("ABORTED"); } else if (input.scope.timedOut()) { yield fail("UNAVAILABLE", { retryable: true, recovery: "REISSUE_CAPABILITY", }); } else { yield fail("NOT_READABLE", { retryable: true, recovery: "REISSUE_CAPABILITY", }); } } finally { combined?.release(); if (!completed) { if (reader) cancelReader(reader); else cancelBody(input.response); } try { reader?.releaseLock(); } catch { // Reader cleanup cannot change stream success or failure. } input.scope.release(); observeBrowserData(input.observer, { operation: "DOWNLOAD", outcome: completed ? "SUCCEEDED" : terminalFailureCode ? "FAILED" : "DEGRADED", ...(terminalFailureCode ? { failureCode: terminalFailureCode } : {}), byteBucket: byteBucket(transferred), }); } }, }); } function validateDownloadResponse( response: Response, binding: PresignedCapabilityBinding, ): BrowserDataResult { if ( response.redirected || response.type === "opaqueredirect" || !responseUrlMatches( response, binding.href, ) ) { return browserDataFailure("POLICY_REJECTED", "PRESIGNED_TRANSFER"); } if (response.status !== binding.expectedStatus) { return statusFailure(response.status); } if (!validateRequiredHeaders(response, binding)) { return browserDataFailure( "INTEGRITY_FAILED", "PRESIGNED_TRANSFER", ); } const mediaType = response.headers .get("content-type") ?.trim() .toLowerCase(); const declaredLength = response.headers.get("content-length"); const digestHeader = binding.digestResponseHeader; const contentEncoding = response.headers.get("content-encoding"); if ( mediaType !== binding.mediaType || declaredLength === null || Number(declaredLength) !== binding.byteLength || digestHeader === null || response.headers.get(digestHeader)?.trim().toLowerCase() !== binding.expectedSha256 || (contentEncoding !== null && contentEncoding.trim().toLowerCase() !== "identity") || response.headers.has("content-range") || (binding.byteLength > 0 && !response.body) ) { return browserDataFailure( "INTEGRITY_FAILED", "PRESIGNED_TRANSFER", ); } return browserDataSuccess(true); } function validateUploadResponse( response: Response, binding: PresignedCapabilityBinding, hardMaxUploadResponseBytes: number, ): BrowserDataResult< Readonly<{ receiptToken: string; responseByteLength: number; }> > { if ( response.redirected || response.type === "opaqueredirect" || !responseUrlMatches( response, binding.href, ) ) { return browserDataFailure("POLICY_REJECTED", "PRESIGNED_TRANSFER"); } if (response.status !== binding.expectedStatus) { return statusFailure(response.status); } if (!validateRequiredHeaders(response, binding)) { return browserDataFailure( "INTEGRITY_FAILED", "PRESIGNED_TRANSFER", ); } const headerName = binding.receiptResponseHeader; const expectedResponseByteLength = binding.expectedResponseByteLength; const declaredLength = normalizedContentLength( response.headers.get("content-length"), response.status === 204 && expectedResponseByteLength === 0, ); if ( !headerName || expectedResponseByteLength === null || expectedResponseByteLength > hardMaxUploadResponseBytes || declaredLength === null || declaredLength !== expectedResponseByteLength || (expectedResponseByteLength > 0 && !response.body) ) { return browserDataFailure("POLICY_REJECTED", "PRESIGNED_TRANSFER"); } const receipt = normalizeReceiptToken( response.headers.get(headerName), ); return receipt ? browserDataSuccess( Object.freeze({ receiptToken: receipt, responseByteLength: expectedResponseByteLength, }), ) : browserDataFailure("INTEGRITY_FAILED", "PRESIGNED_TRANSFER"); } async function drainUploadResponse( response: Response, expectedByteLength: number, hardMaxBytes: number, signal: AbortSignal, ): Promise> { if (!response.body) { return expectedByteLength === 0 ? browserDataSuccess(true) : browserDataFailure( "INTEGRITY_FAILED", "PRESIGNED_TRANSFER", ); } const reader = response.body.getReader(); let completed = false; let total = 0; try { while (true) { const result = await readWithSignal(reader, signal); if (result.done) break; if (!(result.value instanceof Uint8Array)) { return browserDataFailure( "CORRUPT_DATA", "PRESIGNED_TRANSFER", ); } total += result.value.byteLength; if (!Number.isSafeInteger(total) || total > hardMaxBytes) { return browserDataFailure( "LIMIT_EXCEEDED", "PRESIGNED_TRANSFER", ); } if (total > expectedByteLength) { return browserDataFailure( "INTEGRITY_FAILED", "PRESIGNED_TRANSFER", ); } } if (total !== expectedByteLength) { return browserDataFailure( "INTEGRITY_FAILED", "PRESIGNED_TRANSFER", ); } completed = true; return browserDataSuccess(true); } finally { if (!completed) cancelReader(reader); try { reader.releaseLock(); } catch { // Reader cleanup cannot alter the already classified result. } } } function validateRequiredHeaders( response: Response, binding: PresignedCapabilityBinding, ): boolean { return binding.requiredResponseHeaders.every( (header) => response.headers.get(header.name) === header.value, ); } function normalizedContentLength( value: string | null, allowImplicitZero: boolean, ): number | null { if (value === null) return allowImplicitZero ? 0 : null; const trimmed = value.trim(); if (!/^(?:0|[1-9][0-9]{0,15})$/.test(trimmed)) return null; const length = Number(trimmed); return Number.isSafeInteger(length) ? length : null; } function headersFor(binding: PresignedCapabilityBinding): Headers { const headers = new Headers(); for (const header of binding.requestHeaders) { headers.set(header.name, header.value); } return headers; } function validCommonBinding( binding: PresignedCapabilityBinding, capability: PresignedTransferCapability, hardMaxTransferBytes: number, ): boolean { return ( binding.capability === capability && binding.capabilityReceipt === capability.capabilityReceipt && binding.method === capability.method && binding.binding === capability.binding && binding.mediaType === capability.mediaType && binding.byteLength === capability.byteLength && binding.maxBytes === capability.maxBytes && binding.expectedSha256 === capability.expectedSha256 && binding.expiresAtEpochMs === capability.expiresAtEpochMs && capability.byteLength >= 0 && capability.byteLength <= capability.maxBytes && capability.maxBytes <= hardMaxTransferBytes && SHA256.test(capability.expectedSha256) && Object.isFrozen(capability) && Object.isFrozen(capability.binding) ); } function validateExpiry( capability: PresignedTransferCapability, minimumRemainingLifetimeMs: number, nowEpochMs: number, ): BrowserDataResult { const remainingLifetimeMs = capability.expiresAtEpochMs - nowEpochMs; if ( !Number.isSafeInteger(nowEpochMs) || nowEpochMs < 0 || !Number.isSafeInteger(remainingLifetimeMs) ) { return browserDataFailure("POLICY_REJECTED", "PRESIGNED_TRANSFER"); } return remainingLifetimeMs >= minimumRemainingLifetimeMs ? browserDataSuccess(true) : browserDataFailure( "EXPIRED_RESOURCE", "PRESIGNED_TRANSFER", { recovery: "REISSUE_CAPABILITY" }, ); } function statusFailure( status: number, ): BrowserDataResult { let code: BrowserDataFailureCode = "UNAVAILABLE"; let recovery: BrowserDataRecovery = "REISSUE_CAPABILITY"; let retryable = status === 429 || status >= 500; if (status === 401 || status === 403) { code = "PERMISSION_DENIED"; recovery = "REISSUE_CAPABILITY"; retryable = false; } else if (status === 404) { code = "NOT_FOUND"; recovery = "NONE"; retryable = false; } else if (status === 409) { code = "CONFLICT"; retryable = false; } else if (status === 410) { code = "EXPIRED_RESOURCE"; retryable = false; } else if (status === 413) { code = "LIMIT_EXCEEDED"; recovery = "NONE"; retryable = false; } else if (status >= 400 && status < 500 && status !== 429) { code = "POLICY_REJECTED"; recovery = "NONE"; retryable = false; } return browserDataFailure(code, "PRESIGNED_TRANSFER", { retryable, recovery, }); } function transferFailure( externalSignal: AbortSignal, timedOut: boolean, ): BrowserDataResult { if (externalSignal.aborted) { return browserDataFailure("ABORTED", "PRESIGNED_TRANSFER"); } return browserDataFailure( timedOut ? "UNAVAILABLE" : "NOT_READABLE", "PRESIGNED_TRANSFER", { retryable: true, recovery: "REISSUE_CAPABILITY", }, ); } function createAbortScope( external: AbortSignal, timeoutMs: number, scheduler: Scheduler, ) { const controller = new AbortController(); let timedOut = false; let released = false; const onAbort = () => controller.abort(external.reason); const releaseListener = () => { external.removeEventListener("abort", onAbort); }; external.addEventListener("abort", onAbort, { once: true }); if (external.aborted) onAbort(); const timer = scheduler.setTimeout(() => { timedOut = true; controller.abort("timeout"); releaseListener(); }, timeoutMs); return Object.freeze({ signal: controller.signal, timedOut: () => timedOut, abort(reason?: unknown) { controller.abort(reason); }, release() { if (released) return; released = true; scheduler.clearTimeout(timer); releaseListener(); }, }); } function combineConsumerAbort( scope: ReturnType, consumer: AbortSignal, ) { const onAbort = () => scope.abort(consumer.reason); consumer.addEventListener("abort", onAbort, { once: true }); if (consumer.aborted) onAbort(); return Object.freeze({ release() { consumer.removeEventListener("abort", onAbort); }, }); } function cancelBody(response: Response): void { try { void response.body?.cancel().catch(() => { // Cancellation is best effort after the result is classified. }); } catch { // A response already classified as failure/success is not reclassified by // best-effort body cancellation. } } function cancelReader( reader: ReadableStreamDefaultReader, ): void { try { void reader.cancel().catch(() => { // Cancellation is best effort after the result is classified. }); } catch { // Reader cancellation cannot alter the terminal closed result. } } function readWithSignal( reader: ReadableStreamDefaultReader, signal: AbortSignal, ): Promise> { if (signal.aborted) { return Promise.reject(new DOMException("Aborted", "AbortError")); } return new Promise((resolve, reject) => { const onAbort = () => { reject(new DOMException("Aborted", "AbortError")); }; signal.addEventListener("abort", onAbort, { once: true }); reader.read().then( (result) => { signal.removeEventListener("abort", onAbort); resolve(result); }, (error: unknown) => { signal.removeEventListener("abort", onAbort); reject(error); }, ); }); } function normalizeReceiptToken(value: string | null): string | null { if (!value) return null; const trimmed = value.trim(); const unquoted = trimmed.length >= 2 && trimmed.startsWith("\"") && trimmed.endsWith("\"") ? trimmed.slice(1, -1) : trimmed; return RECEIPT_TOKEN.test(unquoted) && !unquoted.includes("://") ? unquoted : null; } function responseUrlMatches( response: Response, expectedHref: string, ): boolean { if (response.url.length === 0) return false; try { return new URL(response.url).href === new URL(expectedHref).href; } catch { return false; } } function normalizedSha256(value: unknown): string { if (typeof value !== "string") throw new TypeError("SHA-256 is invalid."); const normalized = value.toLowerCase(); if (!SHA256.test(normalized)) throw new TypeError("SHA-256 is invalid."); return normalized; } function opaqueId(value: unknown): string { if ( typeof value !== "string" || value.length === 0 || value.length > 256 || !/^[a-z0-9][a-z0-9._:-]*$/i.test(value) ) { throw new TypeError("Opaque identifier is invalid."); } return value; } function positiveSafeInteger(value: unknown): number { if (!Number.isSafeInteger(value) || (value as number) <= 0) { throw new TypeError("Expected a positive safe integer."); } return value as number; } function nonNegativeSafeInteger(value: unknown): number { if (!Number.isSafeInteger(value) || (value as number) < 0) { throw new TypeError("Expected a non-negative safe integer."); } return value as number; } async function digestSha256WithWebCrypto( bytes: Uint8Array, ): Promise { if (!(bytes instanceof Uint8Array) || !globalThis.crypto?.subtle) { throw new TypeError("WebCrypto SHA-256 is unavailable."); } const digest = await globalThis.crypto.subtle.digest( "SHA-256", bytes.slice(), ); return Array.from(new Uint8Array(digest), (value) => value.toString(16).padStart(2, "0"), ).join(""); } function isAbortSignal(value: unknown): value is AbortSignal { try { if (!value || typeof value !== "object") return false; const abortedGetter = Object.getOwnPropertyDescriptor( AbortSignal.prototype, "aborted", )?.get; return Boolean( abortedGetter && typeof abortedGetter.call(value) === "boolean" && typeof (value as AbortSignal).addEventListener === "function" && typeof (value as AbortSignal).removeEventListener === "function", ); } catch { return false; } } function safeInputByteLength(value: unknown): number | undefined { try { if (!value || typeof value !== "object") return undefined; const input = value as Readonly<{ byteLength?: unknown; bytes?: unknown; capability?: unknown; }>; if ( Number.isSafeInteger(input.byteLength) && (input.byteLength as number) >= 0 ) { return input.byteLength as number; } if (input.bytes instanceof Uint8Array) { return input.bytes.byteLength; } if ( input.capability && typeof input.capability === "object" && Number.isSafeInteger( (input.capability as Readonly<{ byteLength?: unknown }>) .byteLength, ) ) { const byteLength = ( input.capability as Readonly<{ byteLength: number }> ).byteLength; return byteLength >= 0 ? byteLength : undefined; } } catch { return undefined; } return undefined; } function observeTransferResult( observer: BrowserDataObserver | undefined, operation: BrowserDataOperation, result: BrowserDataResult, byteLength?: number, ): void { const bucket = byteBucket(byteLength); observeBrowserData(observer, { operation, outcome: result.ok ? "SUCCEEDED" : "FAILED", ...(!result.ok ? { failureCode: result.error.code } : {}), ...(bucket !== undefined ? { byteBucket: bucket } : {}), }); } function byteBucket( byteLength: number | undefined, ): | "ZERO" | "LT1MIB" | "1_TO_9MIB" | "10_TO_99MIB" | "GTE100MIB" | undefined { if ( byteLength === undefined || !Number.isSafeInteger(byteLength) || byteLength < 0 ) { return undefined; } if (byteLength === 0) return "ZERO"; if (byteLength < 1_048_576) return "LT1MIB"; if (byteLength < 10_485_760) return "1_TO_9MIB"; if (byteLength < 104_857_600) return "10_TO_99MIB"; return "GTE100MIB"; }