import { createHash } from "node:crypto"; import type { Route } from "@playwright/test"; import { expect, test } from "../support/browser/strict-browser-test.ts"; const PAGE_ORIGIN = "http://127.0.0.1:4174"; const API_ORIGIN = "https://upload-api.example.test"; const OBJECT_ORIGIN = "https://upload-objects.example.test"; const SESSION_ID = "session_browser_01"; const RESOURCE_ID = "resource_browser_01"; const UPLOAD_PROTOCOL = "PRESIGNED_MULTIPART_V1"; type JsonRecord = Readonly>; function corsHeaders(): Record { return { "access-control-allow-credentials": "true", "access-control-allow-headers": "content-type", "access-control-allow-methods": "POST, OPTIONS", "access-control-allow-origin": PAGE_ORIGIN, }; } async function fulfillJson( route: Route, status: number, value: unknown, ): Promise { const body = JSON.stringify(value); await route.fulfill({ status, body, headers: { ...corsHeaders(), "content-length": String(Buffer.byteLength(body)), "content-type": "application/json; charset=utf-8", }, }); } test("uploads three presigned parts through native IndexedDB, Web Locks and fetch", async ({ page, }) => { let session: JsonRecord | null = null; const acceptedParts = new Map(); const issuedParts = new Map(); const controlRequests: Readonly<{ path: string; body: JsonRecord; }>[] = []; const uploadedBodies: Readonly<{ partNumber: number; bytes: number[]; checksum: string | undefined; }>[] = []; let completedParts: readonly JsonRecord[] = []; await page.route(`${API_ORIGIN}/**`, async (route) => { if (route.request().method() === "OPTIONS") { await route.fulfill({ status: 204, headers: corsHeaders() }); return; } const path = new URL(route.request().url()).pathname; const body = route.request().postDataJSON() as JsonRecord; controlRequests.push({ path, body }); if (path === "/uploads/create") { const fingerprint = body.fingerprint as JsonRecord; session = { protocol: UPLOAD_PROTOCOL, sessionId: SESSION_ID, requestBindingSha256: body.requestBindingSha256, fingerprint, partSizeBytes: body.requestedPartSizeBytes, partCount: fingerprint.partCount, maxConcurrency: 1, expiresAtEpochMs: Date.now() + 5 * 60_000, }; await fulfillJson(route, 201, session); return; } if (path === "/uploads/status") { if (!session) { await fulfillJson(route, 404, { state: "NOT_FOUND" }); return; } await fulfillJson(route, 200, { state: "ACTIVE", session, acceptedParts: [...acceptedParts.values()].sort( (left, right) => Number(left.partNumber) - Number(right.partNumber), ), }); return; } if (path === "/uploads/complete") { completedParts = body.orderedParts as readonly JsonRecord[]; await fulfillJson(route, 200, { state: "QUARANTINED", protocol: UPLOAD_PROTOCOL, sessionId: body.sessionId, requestBindingSha256: body.requestBindingSha256, fingerprint: body.fingerprint, resourceId: RESOURCE_ID, }); return; } if (path === "/uploads/abort") { await fulfillJson(route, 200, { state: "ABORTED" }); return; } if (path === "/uploads/part-capability") { const binding = body.binding as JsonRecord; const partNumber = Number(binding.partNumber); const descriptor = { partNumber, offset: binding.offset, byteLength: body.byteLength, checksumSha256: body.expectedSha256, }; issuedParts.set(partNumber, descriptor); const objectPath = `/uploads/${SESSION_ID}/parts/${String(partNumber)}`; await fulfillJson(route, 200, { // The part capability travels in a presigned envelope, so it names the // transfer protocol even though its binding names the upload one. // Without it every part was refused before any object PUT was made. protocol: "PRESIGNED_TRANSFER_V1", capabilityReceipt: `browser-part-capability-${String(partNumber)}`, method: "PUT", binding, href: `${OBJECT_ORIGIN}${objectPath}?sig=opaque-${String(partNumber)}`, origin: OBJECT_ORIGIN, path: objectPath, allowedQueryParameters: ["sig"], requestHeaders: [ { name: "content-type", value: body.mediaType, }, { name: "x-checksum-sha256", value: body.expectedSha256, }, ], requiredResponseHeaders: [ { name: "x-policy-version", value: "v1", }, ], digestRequestHeader: "x-checksum-sha256", digestResponseHeader: null, receiptResponseHeader: "etag", expectedResponseByteLength: 0, expectedStatus: 204, mediaType: body.mediaType, byteLength: body.byteLength, maxBytes: body.byteLength, expectedSha256: body.expectedSha256, expiresAtEpochMs: Number(session?.expiresAtEpochMs) - 10_000, singleUse: true, }); return; } await route.fulfill({ status: 404, headers: corsHeaders() }); }); await page.route(`${OBJECT_ORIGIN}/**`, async (route) => { if (route.request().method() === "OPTIONS") { await route.fulfill({ status: 204, headers: { "access-control-allow-headers": "content-type, x-checksum-sha256", "access-control-allow-methods": "PUT, OPTIONS", "access-control-allow-origin": "*", }, }); return; } const match = new URL(route.request().url()).pathname.match( /\/parts\/([1-9][0-9]*)$/u, ); const partNumber = Number(match?.[1]); const body = route.request().postDataBuffer(); if (!body || !Number.isSafeInteger(partNumber)) { await route.fulfill({ status: 400 }); return; } const checksum = route.request().headers()["x-checksum-sha256"]; const actualChecksum = createHash("sha256") .update(body) .digest("hex"); const descriptor = issuedParts.get(partNumber); if ( !descriptor || checksum !== actualChecksum || descriptor.checksumSha256 !== actualChecksum || descriptor.byteLength !== body.byteLength ) { await route.fulfill({ status: 422 }); return; } const receiptToken = `etag-part-${String(partNumber)}`; acceptedParts.set(partNumber, { ...descriptor, receiptToken, }); uploadedBodies.push({ partNumber, bytes: [...body], checksum, }); await route.fulfill({ status: 204, headers: { "access-control-allow-origin": "*", "access-control-expose-headers": "etag, x-policy-version", etag: `"${receiptToken}"`, "x-policy-version": "v1", }, }); }); await page.goto("/"); const result = await page.evaluate( async ({ apiOrigin, objectOrigin }) => { const vaultModulePath = "/src/adapters/browser-transfer/presigned/presigned-capability-vault.ts"; const providerModulePath = "/src/adapters/browser-transfer/presigned/presigned-capability-http-provider.ts"; const executorModulePath = "/src/adapters/browser-transfer/presigned/presigned-transfer-executor.ts"; const fetchTransportModulePath = "/src/adapters/browser-transfer/resumable-upload/fetch-json-transport.ts"; const controlPlaneModulePath = "/src/adapters/browser-transfer/resumable-upload/http-control-plane-adapter.ts"; const checkpointModulePath = "/src/adapters/browser-transfer/resumable-upload/indexeddb-checkpoint-store.ts"; const partExecutorModulePath = "/src/adapters/browser-transfer/resumable-upload/presigned-upload-part-executor.ts"; const uploadRuntimeModulePath = "/src/adapters/browser-transfer/resumable-upload/resumable-upload-runtime.ts"; const cancellationModulePath = "/src/adapters/browser-transfer/resumable-upload/upload-cancellation-channel.ts"; const lockModulePath = "/src/adapters/browser-transfer/resumable-upload/upload-mutation-lock.ts"; const [ { createPresignedCapabilityVault, createSingleUsePresignedReplayGuard, }, { createPresignedCapabilityHttpProvider }, { createPresignedTransferExecutor }, { createResumableUploadFetchJsonTransport }, { createResumableUploadHttpControlPlane }, { createIndexedDbResumableUploadCheckpointRuntime }, { createPresignedUploadPartExecutor }, { createResumableUploadRuntime }, { createBrowserUploadCancellationChannel }, { createResumableUploadWebLock }, ] = await Promise.all([ import(/* @vite-ignore */ vaultModulePath), import(/* @vite-ignore */ providerModulePath), import(/* @vite-ignore */ executorModulePath), import(/* @vite-ignore */ fetchTransportModulePath), import(/* @vite-ignore */ controlPlaneModulePath), import(/* @vite-ignore */ checkpointModulePath), import(/* @vite-ignore */ partExecutorModulePath), import(/* @vite-ignore */ uploadRuntimeModulePath), import(/* @vite-ignore */ cancellationModulePath), import(/* @vite-ignore */ lockModulePath), ]); const suffix = crypto.randomUUID().replaceAll("-", ""); const uploadKey = `upload_browser_${suffix}`; const checkpointRuntime = createIndexedDbResumableUploadCheckpointRuntime({ scope: { authorityToken: `authority_${suffix}`, namespaceToken: `namespace_${suffix}`, partitionToken: `partition_${suffix}`, }, blockedTimeoutMs: 2_000, }); const vault = createPresignedCapabilityVault({ maxActiveCapabilities: 8, }); const capabilityProvider = createPresignedCapabilityHttpProvider({ endpoint: `${apiOrigin}/uploads/part-capability`, vault, allowedDataOrigins: [objectOrigin], allowedDataPathPrefixes: ["/uploads/"], allowedQueryParameters: ["sig"], allowedRequestHeaders: [ "content-type", "x-checksum-sha256", ], allowedResponseHeaders: ["etag", "x-policy-version"], hardMaxTransferBytes: 8, hardMaxUploadResponseBytes: 1_024, maxCapabilityTtlMs: 10 * 60_000, minimumRemainingLifetimeMs: 30_000, timeoutMs: 5_000, controlPlaneCredentials: "include", }); const transferExecutor = createPresignedTransferExecutor({ vault, replayGuard: createSingleUsePresignedReplayGuard(), hardMaxTransferBytes: 8, hardMaxChunkBytes: 2, hardMaxUploadResponseBytes: 1_024, minimumRemainingLifetimeMs: 30_000, timeoutMs: 5_000, }); const transport = createResumableUploadFetchJsonTransport({ endpoints: { CREATE_SESSION: `${apiOrigin}/uploads/create`, GET_STATUS: `${apiOrigin}/uploads/status`, COMPLETE: `${apiOrigin}/uploads/complete`, ABORT: `${apiOrigin}/uploads/abort`, }, allowedOrigins: [apiOrigin], credentials: "include", timeoutMs: 5_000, maxRequestBytes: 64 * 1024, maxResponseBytes: 64 * 1024, maxRetryAfterMs: 1_000, }); const controlPlane = createResumableUploadHttpControlPlane({ transport, partCapabilities: capabilityProvider, }); const cancellationChannelName = `browser-upload-cancel-${suffix}`; const crossContextCancellation = createBrowserUploadCancellationChannel({ channelName: cancellationChannelName, }); const cancellationPeer = createBrowserUploadCancellationChannel({ channelName: cancellationChannelName, }); const runtime = createResumableUploadRuntime({ controlPlane, partExecutor: createPresignedUploadPartExecutor( transferExecutor.uploadParts, ), checkpoints: checkpointRuntime.store, mutationLock: createResumableUploadWebLock( navigator.locks, "browser-resumable-upload", ), ...(crossContextCancellation ? { crossContextCancellation } : {}), crypto, policy: { partSizeBytes: 2, maxFileBytes: 6, maxPartCount: 3, maxConcurrency: 2, maxInFlightBytes: 16, partBufferCopyFactor: 4, maxSourceChunkBytes: 2, maxRetries: 1, retryBaseDelayMs: 1, retryMaxDelayMs: 5, maxRetryAfterMs: 1_000, capabilityRefreshSkewMs: 1_000, maxSessionLifetimeMs: 10 * 60_000, providerAttemptTimeoutMs: 10_000, }, }); const bytes = new Uint8Array([1, 2, 3, 4, 5]); const progress: string[] = []; let upload: unknown; let checkpoint: unknown; let deletion: unknown; let cancellationDelivered = false; try { if (crossContextCancellation && cancellationPeer) { cancellationDelivered = await new Promise( (resolve) => { const timer = setTimeout(() => resolve(false), 2_000); const release = cancellationPeer.subscribe((key: string) => { if (key !== `upload_probe_${suffix}`) return; clearTimeout(timer); release(); resolve(true); }); if ( !crossContextCancellation.publish( `upload_probe_${suffix}`, ) ) { clearTimeout(timer); release(); resolve(false); } }, ); } upload = await runtime.upload({ uploadKey, purpose: "browser_attachment", mediaType: "application/octet-stream", source: { kind: "RANGE_READER", reader: { byteLength: bytes.byteLength, async readRange(input: { offset: number; length: number; signal: AbortSignal; }) { return input.signal.aborted ? { ok: false as const, error: { code: "ABORTED" as const, operation: "FILE_READ" as const, retryable: false, recovery: "NONE" as const, }, } : { ok: true as const, value: bytes.slice( input.offset, input.offset + input.length, ), }; }, }, }, signal: new AbortController().signal, onProgress(value: { phase: string }) { progress.push(value.phase); }, }); checkpoint = await checkpointRuntime.store.read(uploadKey); } finally { runtime.close(); cancellationPeer?.close(); deletion = await checkpointRuntime.admin.deletePartition(); vault.dispose(); } return { upload, checkpoint, deletion, progress, webLocksAvailable: Boolean(navigator.locks), broadcastCancellationAvailable: Boolean(crossContextCancellation), cancellationDelivered, }; }, { apiOrigin: API_ORIGIN, objectOrigin: OBJECT_ORIGIN }, ); expect(result.webLocksAvailable).toBe(true); expect(result.broadcastCancellationAvailable).toBe(true); expect(result.cancellationDelivered).toBe(true); expect(result.upload).toMatchObject({ ok: true, value: { state: "QUARANTINED", resourceId: RESOURCE_ID, byteLength: 5, replayed: false, }, }); expect(result.checkpoint).toEqual({ ok: true, value: null }); // The deletion reports the physical effect it observed, not only the state it // reached, so a caller can tell a delete that happened from one that found // nothing to do. The fixture asserts it rather than ignoring it. expect(result.deletion).toEqual({ ok: true, value: { state: "DELETED", effect: "APPLIED" }, }); expect( uploadedBodies .slice() .sort((left, right) => left.partNumber - right.partNumber) .map(({ partNumber, bytes }) => ({ partNumber, bytes })), ).toEqual([ { partNumber: 1, bytes: [1, 2] }, { partNumber: 2, bytes: [3, 4] }, { partNumber: 3, bytes: [5] }, ]); expect(completedParts.map((part) => part.partNumber)).toEqual([ 1, 2, 3, ]); expect(completedParts.map((part) => part.receiptToken)).toEqual([ "etag-part-1", "etag-part-2", "etag-part-3", ]); expect(controlRequests.map((request) => request.path)).toEqual([ "/uploads/create", "/uploads/status", "/uploads/part-capability", "/uploads/part-capability", "/uploads/part-capability", "/uploads/status", "/uploads/complete", ]); expect(JSON.stringify(controlRequests)).not.toContain("?sig="); expect(JSON.stringify(controlRequests)).not.toContain(OBJECT_ORIGIN); });