Files
tech-log-frontend/src/adapters/browser-transfer/resumable-upload/presigned-upload-part-executor.ts
T

115 lines
4.0 KiB
TypeScript

import type {
PresignedUploadPartCapability,
PresignedUploadPartPort,
} from "../../../application/ports/browser-transfer/presigned-transfer.ts";
import type {
UploadPartExecutor,
UploadPartReceipt,
UploadProviderResult,
} from "../../../application/ports/browser-transfer/resumable-upload.ts";
import {
browserDataFailure,
browserDataSuccess,
} from "../../browser-file-storage/result.ts";
import {
MEDIA_TYPE,
isSafeUploadReceiptToken,
SHA256_HEX,
} from "./checkpoint-schema.ts";
export function createPresignedUploadPartExecutor(
inputPort: PresignedUploadPartPort,
now: () => number = Date.now,
): UploadPartExecutor<PresignedUploadPartCapability> {
const put = inputPort?.put;
if (typeof put !== "function" || typeof now !== "function") {
throw new TypeError("Presigned upload part dependency is invalid.");
}
const executor: UploadPartExecutor<PresignedUploadPartCapability> = {
async uploadPart(
input: Parameters<
UploadPartExecutor<PresignedUploadPartCapability>["uploadPart"]
>[0],
): Promise<
UploadProviderResult<UploadPartReceipt>
> {
const capability = input.capability;
let nowEpochMs: number;
try {
nowEpochMs = now();
} catch {
return browserDataFailure("UNAVAILABLE", "UPLOAD_PART", {
retryable: true,
recovery: "RESUME",
});
}
if (
!capability ||
capability.method !== "PUT" ||
capability.binding.kind !== "UPLOAD_PART" ||
capability.binding.protocol !== input.protocol ||
capability.binding.sessionId !== input.sessionId ||
capability.binding.requestBindingSha256 !==
input.requestBindingSha256 ||
capability.binding.uploadBindingSha256 !==
input.uploadBindingSha256 ||
capability.binding.partNumber !== input.part.partNumber ||
capability.binding.offset !== input.part.offset ||
capability.binding.idempotencyKey !== input.idempotencyKey ||
capability.mediaType !== input.mediaType ||
!MEDIA_TYPE.test(capability.mediaType) ||
capability.byteLength !== input.part.byteLength ||
capability.maxBytes < capability.byteLength ||
capability.maxBytes !== input.part.byteLength ||
capability.expectedSha256 !== input.part.checksumSha256 ||
!SHA256_HEX.test(capability.expectedSha256) ||
capability.expiresAtEpochMs <= nowEpochMs ||
!(input.bytes instanceof Uint8Array) ||
input.bytes.byteLength !== input.part.byteLength ||
typeof capability.capabilityReceipt !== "string"
) {
return browserDataFailure("POLICY_REJECTED", "UPLOAD_PART");
}
let uploaded;
try {
uploaded = await put.call(inputPort, {
capability,
sessionId: input.sessionId,
requestBindingSha256: input.requestBindingSha256,
uploadBindingSha256: input.uploadBindingSha256,
partNumber: input.part.partNumber,
offset: input.part.offset,
byteLength: input.part.byteLength,
checksumSha256: input.part.checksumSha256,
idempotencyKey: input.idempotencyKey,
bytes: Uint8Array.from(input.bytes),
signal: input.signal,
});
} catch {
return browserDataFailure("UNAVAILABLE", "UPLOAD_PART", {
retryable: true,
recovery: "RESUME",
});
}
if (!uploaded.ok) return uploaded;
if (
uploaded.value.bytesWritten !== input.part.byteLength ||
uploaded.value.checksumSha256 !== input.part.checksumSha256 ||
typeof uploaded.value.receiptToken !== "string" ||
!isSafeUploadReceiptToken(uploaded.value.receiptToken)
) {
return browserDataFailure("INTEGRITY_FAILED", "UPLOAD_PART", {
recovery: "RECONCILE",
});
}
return browserDataSuccess(
Object.freeze({
...input.part,
receiptToken: uploaded.value.receiptToken,
}),
);
},
};
return Object.freeze(executor);
}