refactor: 프론트 템플릿 리펙토링
This commit is contained in:
@@ -0,0 +1,247 @@
|
||||
import { PRESIGNED_TRANSFER_PROTOCOL } from "../../src/application/ports/browser-transfer/presigned-transfer.ts";
|
||||
import type {
|
||||
PresignedDownloadByteSource,
|
||||
} from "../../src/application/ports/browser-transfer/presigned-transfer.ts";
|
||||
import { RESUMABLE_UPLOAD_PROTOCOL } from "../../src/application/ports/browser-transfer/resumable-upload.ts";
|
||||
import type { BrowserDataObservation } from "../../src/application/ports/browser-file-storage/shared.ts";
|
||||
import {
|
||||
createPresignedCapabilityVault,
|
||||
createSingleUsePresignedReplayGuard,
|
||||
} from "../../src/adapters/browser-transfer/presigned/presigned-capability-vault.ts";
|
||||
import { createPresignedCapabilityHttpProvider } from "../../src/adapters/browser-transfer/presigned/presigned-capability-http-provider.ts";
|
||||
import {
|
||||
createPresignedTransferExecutor,
|
||||
type PresignedTransferExecutorOptions,
|
||||
} from "../../src/adapters/browser-transfer/presigned/presigned-transfer-executor.ts";
|
||||
import { sha256Hex } from "../../src/adapters/browser-transfer/presigned/incremental-sha256.ts";
|
||||
|
||||
export const NOW = 1_000_000;
|
||||
export const CONTROL_ENDPOINT = "https://api.example/capabilities";
|
||||
export const DATA_ORIGIN = "https://objects.example";
|
||||
export const DOWNLOAD_PATH = "/files/resource-1";
|
||||
export const DOWNLOAD_HREF =
|
||||
`${DATA_ORIGIN}${DOWNLOAD_PATH}?sig=do-not-log-this`;
|
||||
export const POLICY_HEADER = "x-policy-version";
|
||||
export const DIGEST_HEADER = "x-content-sha256";
|
||||
export const CHECKSUM_HEADER = "x-checksum-sha256";
|
||||
export const UPLOAD_SESSION_ID = "upload-session-1";
|
||||
export const REQUEST_BINDING_SHA256 = "c".repeat(64);
|
||||
|
||||
export function downloadCapabilityPayload(
|
||||
bytes: Uint8Array,
|
||||
overrides: Readonly<Record<string, unknown>> = {},
|
||||
) {
|
||||
const digest = sha256Hex(bytes);
|
||||
return {
|
||||
protocol: PRESIGNED_TRANSFER_PROTOCOL,
|
||||
capabilityReceipt: "capability-download-1",
|
||||
method: "GET",
|
||||
binding: {
|
||||
kind: "DOWNLOAD",
|
||||
resourceId: "resource-1",
|
||||
},
|
||||
href: DOWNLOAD_HREF,
|
||||
origin: DATA_ORIGIN,
|
||||
path: DOWNLOAD_PATH,
|
||||
allowedQueryParameters: ["sig"],
|
||||
requestHeaders: [
|
||||
{ name: "accept", value: "application/octet-stream" },
|
||||
],
|
||||
requiredResponseHeaders: [
|
||||
{ name: POLICY_HEADER, value: "v1" },
|
||||
],
|
||||
digestRequestHeader: null,
|
||||
digestResponseHeader: DIGEST_HEADER,
|
||||
receiptResponseHeader: null,
|
||||
expectedStatus: 200,
|
||||
expectedResponseByteLength: null,
|
||||
mediaType: "application/octet-stream",
|
||||
byteLength: bytes.byteLength,
|
||||
maxBytes: 64,
|
||||
expectedSha256: digest,
|
||||
expiresAtEpochMs: NOW + 30_000,
|
||||
singleUse: true,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
export type CapabilityPayload = ReturnType<typeof downloadCapabilityPayload>;
|
||||
|
||||
export function uploadCapabilityPayload(input: Readonly<{
|
||||
bytes: Uint8Array;
|
||||
checksum: string;
|
||||
}>, overrides: Readonly<Record<string, unknown>> = {}) {
|
||||
return {
|
||||
protocol: PRESIGNED_TRANSFER_PROTOCOL,
|
||||
capabilityReceipt: "capability-upload-1",
|
||||
method: "PUT",
|
||||
binding: {
|
||||
kind: "UPLOAD_PART",
|
||||
protocol: RESUMABLE_UPLOAD_PROTOCOL,
|
||||
sessionId: UPLOAD_SESSION_ID,
|
||||
requestBindingSha256: REQUEST_BINDING_SHA256,
|
||||
uploadBindingSha256: "b".repeat(64),
|
||||
partNumber: 1,
|
||||
offset: 0,
|
||||
idempotencyKey: "part-attempt-1",
|
||||
},
|
||||
href: `${DATA_ORIGIN}/uploads/session-1/part-1?sig=secret`,
|
||||
origin: DATA_ORIGIN,
|
||||
path: "/uploads/session-1/part-1",
|
||||
allowedQueryParameters: ["sig"],
|
||||
requestHeaders: [
|
||||
{ name: "content-type", value: "application/octet-stream" },
|
||||
{ name: CHECKSUM_HEADER, value: input.checksum },
|
||||
],
|
||||
requiredResponseHeaders: [
|
||||
{ name: POLICY_HEADER, value: "v1" },
|
||||
],
|
||||
digestRequestHeader: CHECKSUM_HEADER,
|
||||
digestResponseHeader: null,
|
||||
receiptResponseHeader: "etag",
|
||||
expectedStatus: 200,
|
||||
expectedResponseByteLength: 0,
|
||||
mediaType: "application/octet-stream",
|
||||
byteLength: input.bytes.byteLength,
|
||||
maxBytes: 64,
|
||||
expectedSha256: input.checksum,
|
||||
expiresAtEpochMs: NOW + 30_000,
|
||||
singleUse: true,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
export function jsonResponse(
|
||||
value: unknown,
|
||||
url = CONTROL_ENDPOINT,
|
||||
): Response {
|
||||
return responseWithUrl(
|
||||
new Response(JSON.stringify(value), {
|
||||
status: 200,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
}),
|
||||
url,
|
||||
);
|
||||
}
|
||||
|
||||
export function downloadResponse(
|
||||
body: BodyInit | null,
|
||||
payload: CapabilityPayload,
|
||||
headers: Record<string, string> = {},
|
||||
): Response {
|
||||
return responseWithUrl(
|
||||
new Response(body, {
|
||||
status: payload.expectedStatus as number,
|
||||
headers: {
|
||||
"Content-Type": String(payload.mediaType),
|
||||
"Content-Length": String(payload.byteLength),
|
||||
[DIGEST_HEADER]: String(payload.expectedSha256),
|
||||
[POLICY_HEADER]: "v1",
|
||||
...headers,
|
||||
},
|
||||
}),
|
||||
String(payload.href),
|
||||
);
|
||||
}
|
||||
|
||||
export function responseWithUrl(response: Response, href: string): Response {
|
||||
Object.defineProperty(response, "url", {
|
||||
configurable: true,
|
||||
value: href,
|
||||
});
|
||||
return response;
|
||||
}
|
||||
|
||||
export function createHarness(input: Readonly<{
|
||||
fetcher: typeof fetch;
|
||||
maxActiveCapabilities?: number;
|
||||
now?: () => number;
|
||||
digestBytes?: PresignedTransferExecutorOptions["digestBytes"];
|
||||
scheduler?: PresignedTransferExecutorOptions["scheduler"];
|
||||
observer?: Readonly<{
|
||||
record(observation: BrowserDataObservation): void;
|
||||
}>;
|
||||
}>) {
|
||||
const now = input.now ?? (() => NOW);
|
||||
const vault = createPresignedCapabilityVault({
|
||||
maxActiveCapabilities: input.maxActiveCapabilities ?? 16,
|
||||
now,
|
||||
});
|
||||
const replayGuard = createSingleUsePresignedReplayGuard();
|
||||
const provider = createPresignedCapabilityHttpProvider({
|
||||
endpoint: CONTROL_ENDPOINT,
|
||||
vault,
|
||||
allowedDataOrigins: [DATA_ORIGIN],
|
||||
allowedDataPathPrefixes: ["/files/", "/uploads/"],
|
||||
allowedQueryParameters: ["sig"],
|
||||
allowedRequestHeaders: [
|
||||
"accept",
|
||||
"content-type",
|
||||
CHECKSUM_HEADER,
|
||||
],
|
||||
allowedResponseHeaders: [
|
||||
POLICY_HEADER,
|
||||
DIGEST_HEADER,
|
||||
"etag",
|
||||
],
|
||||
hardMaxTransferBytes: 64,
|
||||
hardMaxUploadResponseBytes: 16,
|
||||
maxCapabilityTtlMs: 60_000,
|
||||
minimumRemainingLifetimeMs: 1_000,
|
||||
timeoutMs: 5_000,
|
||||
fetcher: input.fetcher,
|
||||
now,
|
||||
scheduler: input.scheduler,
|
||||
observer: input.observer,
|
||||
});
|
||||
const executor = createPresignedTransferExecutor({
|
||||
vault,
|
||||
replayGuard,
|
||||
hardMaxTransferBytes: 64,
|
||||
hardMaxChunkBytes: 2,
|
||||
hardMaxUploadResponseBytes: 16,
|
||||
minimumRemainingLifetimeMs: 1_000,
|
||||
timeoutMs: 5_000,
|
||||
fetcher: input.fetcher,
|
||||
now,
|
||||
scheduler: input.scheduler,
|
||||
digestBytes: input.digestBytes,
|
||||
observer: input.observer,
|
||||
});
|
||||
return { provider, executor, vault };
|
||||
}
|
||||
|
||||
export async function collect(
|
||||
source: PresignedDownloadByteSource,
|
||||
signal = new AbortController().signal,
|
||||
) {
|
||||
const results = [];
|
||||
for await (const result of source.stream(signal)) {
|
||||
results.push(result);
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* BT-PRE-01. The download lease is lazy, so a response-shape rejection is
|
||||
* observed on first consumption rather than at open().
|
||||
*/
|
||||
export async function firstStreamResult(
|
||||
opened: Awaited<
|
||||
ReturnType<
|
||||
ReturnType<typeof createHarness>["executor"]["downloadSources"]["open"]
|
||||
>
|
||||
>,
|
||||
): Promise<unknown> {
|
||||
if (!opened.ok) return opened;
|
||||
try {
|
||||
for await (const chunk of opened.value.stream(
|
||||
new AbortController().signal,
|
||||
)) {
|
||||
if (!chunk.ok) return chunk;
|
||||
}
|
||||
return { ok: true };
|
||||
} finally {
|
||||
opened.value.close();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user