feat: 기능 추가 과정중
This commit is contained in:
@@ -0,0 +1,366 @@
|
||||
import { createHash } from "node:crypto";
|
||||
|
||||
import { expect, test } from "../support/browser/strict-browser-test.ts";
|
||||
|
||||
const RESOURCE_ID = "browser-artifact-1";
|
||||
const OBJECT_ORIGIN = "https://objects.example.test";
|
||||
const OBJECT_PATH = "/files/browser-artifact-1";
|
||||
const OBJECT_HREF =
|
||||
`${OBJECT_ORIGIN}${OBJECT_PATH}?sig=opaque-browser-signature`;
|
||||
const DOWNLOAD_BYTES = Buffer.from([
|
||||
0x10, 0x20, 0x30, 0x40, 0x50,
|
||||
0x60, 0x70, 0x80, 0x90, 0xa0,
|
||||
]);
|
||||
const DOWNLOAD_SHA256 = createHash("sha256")
|
||||
.update(DOWNLOAD_BYTES)
|
||||
.digest("hex");
|
||||
|
||||
test("issues an opaque capability and streams a verified object into the writable save path", async ({
|
||||
page,
|
||||
}) => {
|
||||
const bffRequests: unknown[] = [];
|
||||
const objectRequests: Readonly<{
|
||||
url: string;
|
||||
method: string;
|
||||
accept: string | undefined;
|
||||
cookie: string | undefined;
|
||||
}>[] = [];
|
||||
|
||||
await page.route("**/api/download-capability", async (route) => {
|
||||
bffRequests.push(route.request().postDataJSON());
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
contentType: "application/json",
|
||||
body: JSON.stringify({
|
||||
capabilityReceipt: "browser-download-capability-1",
|
||||
method: "GET",
|
||||
binding: {
|
||||
kind: "DOWNLOAD",
|
||||
resourceId: RESOURCE_ID,
|
||||
},
|
||||
href: OBJECT_HREF,
|
||||
origin: OBJECT_ORIGIN,
|
||||
path: OBJECT_PATH,
|
||||
allowedQueryParameters: ["sig"],
|
||||
requestHeaders: [
|
||||
{
|
||||
name: "accept",
|
||||
value: "application/octet-stream",
|
||||
},
|
||||
],
|
||||
requiredResponseHeaders: [
|
||||
{
|
||||
name: "x-policy-version",
|
||||
value: "v1",
|
||||
},
|
||||
],
|
||||
digestRequestHeader: null,
|
||||
digestResponseHeader: "x-content-sha256",
|
||||
receiptResponseHeader: null,
|
||||
expectedStatus: 200,
|
||||
expectedResponseByteLength: null,
|
||||
mediaType: "application/octet-stream",
|
||||
byteLength: DOWNLOAD_BYTES.byteLength,
|
||||
maxBytes: 64,
|
||||
expectedSha256: DOWNLOAD_SHA256,
|
||||
expiresAtEpochMs: Date.now() + 5 * 60_000,
|
||||
singleUse: true,
|
||||
}),
|
||||
});
|
||||
});
|
||||
await page.route(`${OBJECT_ORIGIN}/**`, async (route) => {
|
||||
const headers = route.request().headers();
|
||||
objectRequests.push({
|
||||
url: route.request().url(),
|
||||
method: route.request().method(),
|
||||
accept: headers.accept,
|
||||
cookie: headers.cookie,
|
||||
});
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
body: DOWNLOAD_BYTES,
|
||||
headers: {
|
||||
"access-control-allow-origin": "*",
|
||||
"access-control-expose-headers":
|
||||
"content-length, x-content-sha256, x-policy-version",
|
||||
"content-length": String(DOWNLOAD_BYTES.byteLength),
|
||||
"content-type": "application/octet-stream",
|
||||
"x-content-sha256": DOWNLOAD_SHA256,
|
||||
"x-policy-version": "v1",
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
await page.goto("/");
|
||||
const result = await page.evaluate(
|
||||
async ({ resourceId }) => {
|
||||
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 policyModulePath =
|
||||
"/src/adapters/browser-files/browser-file-policy-registry.ts";
|
||||
const deliveryModulePath =
|
||||
"/src/adapters/browser-files/download-delivery-adapter.ts";
|
||||
const [
|
||||
{
|
||||
createPresignedCapabilityVault,
|
||||
createSingleUsePresignedReplayGuard,
|
||||
},
|
||||
{ createPresignedCapabilityHttpProvider },
|
||||
{ createPresignedTransferExecutor },
|
||||
{
|
||||
BrowserFilePolicyRegistry,
|
||||
browserFilePolicyReference,
|
||||
},
|
||||
{ createDownloadDeliveryAdapter },
|
||||
] = await Promise.all([
|
||||
import(
|
||||
/* @vite-ignore */ vaultModulePath
|
||||
) as Promise<
|
||||
typeof import("../../src/adapters/browser-transfer/presigned/presigned-capability-vault.ts")
|
||||
>,
|
||||
import(
|
||||
/* @vite-ignore */ providerModulePath
|
||||
) as Promise<
|
||||
typeof import("../../src/adapters/browser-transfer/presigned/presigned-capability-http-provider.ts")
|
||||
>,
|
||||
import(
|
||||
/* @vite-ignore */ executorModulePath
|
||||
) as Promise<
|
||||
typeof import("../../src/adapters/browser-transfer/presigned/presigned-transfer-executor.ts")
|
||||
>,
|
||||
import(
|
||||
/* @vite-ignore */ policyModulePath
|
||||
) as Promise<
|
||||
typeof import("../../src/adapters/browser-files/browser-file-policy-registry.ts")
|
||||
>,
|
||||
import(
|
||||
/* @vite-ignore */ deliveryModulePath
|
||||
) as Promise<
|
||||
typeof import("../../src/adapters/browser-files/download-delivery-adapter.ts")
|
||||
>,
|
||||
]);
|
||||
|
||||
const vault = createPresignedCapabilityVault({
|
||||
maxActiveCapabilities: 4,
|
||||
});
|
||||
const provider = createPresignedCapabilityHttpProvider({
|
||||
endpoint: `${location.origin}/api/download-capability`,
|
||||
vault,
|
||||
allowedDataOrigins: ["https://objects.example.test"],
|
||||
allowedDataPathPrefixes: ["/files/"],
|
||||
allowedQueryParameters: ["sig"],
|
||||
allowedRequestHeaders: ["accept"],
|
||||
allowedResponseHeaders: [
|
||||
"x-content-sha256",
|
||||
"x-policy-version",
|
||||
],
|
||||
hardMaxTransferBytes: 64,
|
||||
hardMaxUploadResponseBytes: 1_024,
|
||||
maxCapabilityTtlMs: 10 * 60_000,
|
||||
minimumRemainingLifetimeMs: 30_000,
|
||||
timeoutMs: 5_000,
|
||||
allowInsecureLocalhost: true,
|
||||
});
|
||||
const executor = createPresignedTransferExecutor({
|
||||
vault,
|
||||
replayGuard: createSingleUsePresignedReplayGuard(),
|
||||
hardMaxTransferBytes: 64,
|
||||
hardMaxChunkBytes: 3,
|
||||
hardMaxUploadResponseBytes: 1_024,
|
||||
minimumRemainingLifetimeMs: 30_000,
|
||||
timeoutMs: 5_000,
|
||||
});
|
||||
const signal = new AbortController().signal;
|
||||
const issued = await provider.issueDownload({
|
||||
resourceId,
|
||||
signal,
|
||||
});
|
||||
if (!issued.ok) {
|
||||
vault.dispose();
|
||||
return {
|
||||
issued,
|
||||
delivery: null,
|
||||
capabilityKeys: [],
|
||||
capabilityBindingKeys: [],
|
||||
written: [],
|
||||
writeChunkSizes: [],
|
||||
writableClosed: false,
|
||||
writableAborted: false,
|
||||
progressPhases: [],
|
||||
};
|
||||
}
|
||||
|
||||
const downloadPolicy = browserFilePolicyReference(
|
||||
"browser-presigned-download",
|
||||
"save-verified-artifact",
|
||||
);
|
||||
const policies = new BrowserFilePolicyRegistry({
|
||||
profiles: [
|
||||
{
|
||||
reference: downloadPolicy,
|
||||
download: {
|
||||
strategy: "PROMPT_AND_STREAM",
|
||||
mediaType: "application/octet-stream",
|
||||
safeExtension: ".bin",
|
||||
maxTransferBytes: 64,
|
||||
maxBufferedBytes: 8,
|
||||
integrity: "REQUIRED",
|
||||
},
|
||||
},
|
||||
],
|
||||
hardLimits: {
|
||||
maxInspectionBytes: 64,
|
||||
maxRetainedFileBytes: 64,
|
||||
maxPreviewBytes: 64,
|
||||
maxObjectUrlBytes: 64,
|
||||
maxTransferBytes: 64,
|
||||
},
|
||||
});
|
||||
const written: number[] = [];
|
||||
const writeChunkSizes: number[] = [];
|
||||
let writableClosed = false;
|
||||
let writableAborted = false;
|
||||
const downloads = createDownloadDeliveryAdapter({
|
||||
host: {
|
||||
handoff() {
|
||||
throw new TypeError(
|
||||
"Writable delivery must not use browser handoff.",
|
||||
);
|
||||
},
|
||||
},
|
||||
policies,
|
||||
hardMaxObjectUrlBytes: 64,
|
||||
hardMaxTransferBytes: 64,
|
||||
browserManagedCapabilities: {
|
||||
resolve() {
|
||||
throw new TypeError(
|
||||
"Browser-managed capability is not used.",
|
||||
);
|
||||
},
|
||||
},
|
||||
openAuthorizedSource:
|
||||
executor.downloadSources.open.bind(
|
||||
executor.downloadSources,
|
||||
),
|
||||
showSaveFilePicker: async () => ({
|
||||
async createWritable() {
|
||||
return new WritableStream<Uint8Array>({
|
||||
write(chunk) {
|
||||
writeChunkSizes.push(chunk.byteLength);
|
||||
written.push(...chunk);
|
||||
},
|
||||
close() {
|
||||
writableClosed = true;
|
||||
},
|
||||
abort() {
|
||||
writableAborted = true;
|
||||
},
|
||||
});
|
||||
},
|
||||
}),
|
||||
createTransferId: () => "transfer:browser-presigned",
|
||||
progressMinIntervalMs: 0,
|
||||
userActivation: { isActive: true },
|
||||
});
|
||||
const progressPhases: string[] = [];
|
||||
const capabilityKeys = Object.keys(issued.value).sort();
|
||||
const capabilityBindingKeys = Object.keys(
|
||||
issued.value.binding,
|
||||
).sort();
|
||||
const delivery = await downloads.deliver({
|
||||
policy: downloadPolicy,
|
||||
source: {
|
||||
kind: "AUTHORIZED_STREAM_RESOURCE",
|
||||
resourceId,
|
||||
capability: issued.value,
|
||||
},
|
||||
suggestedFileName: "browser-artifact.bin",
|
||||
signal,
|
||||
onProgress(progress) {
|
||||
progressPhases.push(progress.phase);
|
||||
},
|
||||
});
|
||||
downloads.dispose();
|
||||
vault.dispose();
|
||||
return {
|
||||
issued: {
|
||||
ok: true as const,
|
||||
exposedHref: "href" in issued.value,
|
||||
exposedRequestHeaders: "requestHeaders" in issued.value,
|
||||
},
|
||||
delivery,
|
||||
capabilityKeys,
|
||||
capabilityBindingKeys,
|
||||
written,
|
||||
writeChunkSizes,
|
||||
writableClosed,
|
||||
writableAborted,
|
||||
progressPhases,
|
||||
};
|
||||
},
|
||||
{ resourceId: RESOURCE_ID },
|
||||
);
|
||||
|
||||
expect(bffRequests).toEqual([
|
||||
{
|
||||
method: "GET",
|
||||
binding: {
|
||||
kind: "DOWNLOAD",
|
||||
resourceId: RESOURCE_ID,
|
||||
},
|
||||
},
|
||||
]);
|
||||
expect(objectRequests).toEqual([
|
||||
{
|
||||
url: OBJECT_HREF,
|
||||
method: "GET",
|
||||
accept: "application/octet-stream",
|
||||
cookie: undefined,
|
||||
},
|
||||
]);
|
||||
expect(result.issued).toEqual({
|
||||
ok: true,
|
||||
exposedHref: false,
|
||||
exposedRequestHeaders: false,
|
||||
});
|
||||
expect(result.capabilityKeys).toEqual([
|
||||
"binding",
|
||||
"byteLength",
|
||||
"capabilityReceipt",
|
||||
"expectedSha256",
|
||||
"expiresAtEpochMs",
|
||||
"maxBytes",
|
||||
"mediaType",
|
||||
"method",
|
||||
]);
|
||||
expect(result.capabilityBindingKeys).toEqual([
|
||||
"kind",
|
||||
"resourceId",
|
||||
]);
|
||||
expect(result.delivery).toEqual({
|
||||
ok: true,
|
||||
value: {
|
||||
kind: "SAVED",
|
||||
transferId: "transfer:browser-presigned",
|
||||
bytesWritten: DOWNLOAD_BYTES.byteLength,
|
||||
integrity: "VERIFIED",
|
||||
},
|
||||
});
|
||||
expect(result.written).toEqual([...DOWNLOAD_BYTES]);
|
||||
expect(result.writeChunkSizes).toEqual([3, 3, 3, 1]);
|
||||
expect(result.writableClosed).toBe(true);
|
||||
expect(result.writableAborted).toBe(false);
|
||||
expect(result.progressPhases).toEqual([
|
||||
"PREPARING",
|
||||
"TRANSFERRING",
|
||||
"TRANSFERRING",
|
||||
"TRANSFERRING",
|
||||
"TRANSFERRING",
|
||||
"VERIFYING",
|
||||
"FINALIZING",
|
||||
]);
|
||||
});
|
||||
Reference in New Issue
Block a user