251 lines
6.9 KiB
TypeScript
251 lines
6.9 KiB
TypeScript
import type {
|
|
BrowserDataResult,
|
|
TransferProgress,
|
|
} from "./shared.ts";
|
|
import type { AuthorizedDownloadCapability } from "../browser-transfer/authorized-download.ts";
|
|
|
|
/**
|
|
* Application-owned browser-file contracts.
|
|
*
|
|
* Native File, Blob, FileList, FileSystemHandle, Response and ReadableStream
|
|
* intentionally do not cross this boundary. A transient object URL may cross
|
|
* only through the presentation-local PreviewLease below; it must never enter
|
|
* domain state, persistence, diagnostics or a general application cache.
|
|
*/
|
|
|
|
declare const localFileRefBrand: unique symbol;
|
|
declare const fileVerificationReceiptBrand: unique symbol;
|
|
declare const filePolicyKeyBrand: unique symbol;
|
|
declare const filePolicyIntentionBrand: unique symbol;
|
|
declare const browserManagedCapabilityReceiptBrand: unique symbol;
|
|
|
|
export type LocalFileRef = string & {
|
|
readonly [localFileRefBrand]: "LocalFileRef";
|
|
};
|
|
|
|
export type FileVerificationReceipt = string & {
|
|
readonly [fileVerificationReceiptBrand]: "FileVerificationReceipt";
|
|
};
|
|
|
|
/**
|
|
* Registry-issued, non-semantic identifiers. A feature receives a frozen
|
|
* reference from its composition root; presentation must not construct policy
|
|
* definitions or select another feature's registered policy.
|
|
*/
|
|
export type FilePolicyKey = string & {
|
|
readonly [filePolicyKeyBrand]: "FilePolicyKey";
|
|
};
|
|
|
|
export type FilePolicyIntention = string & {
|
|
readonly [filePolicyIntentionBrand]: "FilePolicyIntention";
|
|
};
|
|
|
|
export type FilePolicyReference = Readonly<{
|
|
policyKey: FilePolicyKey;
|
|
intention: FilePolicyIntention;
|
|
}>;
|
|
|
|
export type FileSelectionSource =
|
|
| "NATIVE_INPUT"
|
|
| "SYSTEM_PICKER"
|
|
| "DROP";
|
|
|
|
export type FileSelectionLimitReduction = Readonly<{
|
|
maxCount?: number;
|
|
maxFileBytes?: number;
|
|
maxTotalBytes?: number;
|
|
}>;
|
|
|
|
export type FileCandidate = Readonly<{
|
|
ref: LocalFileRef;
|
|
/**
|
|
* Untrusted, potentially personal display metadata. It must never be used as
|
|
* a resource identifier or diagnostics attribute.
|
|
*/
|
|
displayName: string;
|
|
sizeBytes: number;
|
|
reportedMediaType: string | null;
|
|
lastModifiedEpochMs: number | null;
|
|
source: FileSelectionSource;
|
|
}>;
|
|
|
|
export type FileSelectionOutcome =
|
|
| Readonly<{ kind: "SELECTED"; files: readonly FileCandidate[] }>
|
|
| Readonly<{ kind: "DISMISSED" }>;
|
|
|
|
export type FilePickerSupport = Readonly<{
|
|
nativeInput: true;
|
|
systemOpenPicker: boolean;
|
|
systemSavePicker: boolean;
|
|
}>;
|
|
|
|
export interface FilePickerPort {
|
|
readonly support: FilePickerSupport;
|
|
|
|
/**
|
|
* Must be invoked as the first browser action in a trusted user activation.
|
|
* A dismissed picker is a successful DISMISSED outcome, not an error.
|
|
*/
|
|
select(input: {
|
|
policy: FilePolicyReference;
|
|
limits?: FileSelectionLimitReduction;
|
|
signal?: AbortSignal;
|
|
}): Promise<BrowserDataResult<FileSelectionOutcome>>;
|
|
|
|
release(ref: LocalFileRef): void;
|
|
}
|
|
|
|
export type FileSignatureResult =
|
|
| "MATCHED"
|
|
| "MISMATCHED"
|
|
| "UNKNOWN";
|
|
|
|
export type FileInspection = Readonly<{
|
|
byteLength: number;
|
|
reportedMediaType: string | null;
|
|
detectedMediaType: string | null;
|
|
normalizedExtension: string | null;
|
|
signature: FileSignatureResult;
|
|
/**
|
|
* Issued only for a matched signature and bound inside the transient vault
|
|
* to this file snapshot and inspection policy.
|
|
*/
|
|
verificationReceipt: FileVerificationReceipt | null;
|
|
}>;
|
|
|
|
/**
|
|
* File-capability byte stream with a closed failure channel. Implementations
|
|
* must convert native exceptions to BrowserDataResult and never throw a raw
|
|
* DOMException across the application boundary.
|
|
*/
|
|
export interface FileByteSource {
|
|
readonly byteLength: number | null;
|
|
stream(
|
|
signal: AbortSignal,
|
|
): AsyncIterable<BrowserDataResult<Uint8Array>>;
|
|
}
|
|
|
|
export interface FileContentPort {
|
|
inspect(input: {
|
|
ref: LocalFileRef;
|
|
policy: FilePolicyReference;
|
|
maxInspectionBytes?: number;
|
|
signal: AbortSignal;
|
|
}): Promise<BrowserDataResult<FileInspection>>;
|
|
|
|
readRange(input: {
|
|
ref: LocalFileRef;
|
|
offset: number;
|
|
length: number;
|
|
signal: AbortSignal;
|
|
}): Promise<BrowserDataResult<Uint8Array>>;
|
|
|
|
openSource(input: {
|
|
ref: LocalFileRef;
|
|
signal: AbortSignal;
|
|
}): Promise<BrowserDataResult<FileByteSource>>;
|
|
|
|
release(ref: LocalFileRef): void;
|
|
}
|
|
|
|
export type PreviewLease = Readonly<{
|
|
url: string;
|
|
mediaType: string;
|
|
release(): void;
|
|
}>;
|
|
|
|
export interface TransientPreviewPort {
|
|
create(input: {
|
|
ref: LocalFileRef;
|
|
verificationReceipt: FileVerificationReceipt;
|
|
policy: FilePolicyReference;
|
|
maxPreviewBytes?: number;
|
|
signal: AbortSignal;
|
|
}): Promise<BrowserDataResult<PreviewLease>>;
|
|
|
|
dispose(): void;
|
|
}
|
|
|
|
export type BrowserManagedDownloadCapabilityReceipt = string & {
|
|
readonly [browserManagedCapabilityReceiptBrand]:
|
|
"BrowserManagedDownloadCapabilityReceipt";
|
|
};
|
|
|
|
export type DownloadSource =
|
|
| Readonly<{
|
|
kind: "BROWSER_MANAGED_RESOURCE";
|
|
resourceId: string;
|
|
capabilityReceipt: BrowserManagedDownloadCapabilityReceipt;
|
|
}>
|
|
| Readonly<{
|
|
kind: "AUTHORIZED_STREAM_RESOURCE";
|
|
resourceId: string;
|
|
/**
|
|
* Exact provider-issued handle. Raw href/query/header values are never
|
|
* caller inputs and an equal-looking fabricated handle must be rejected.
|
|
*/
|
|
capability: AuthorizedDownloadCapability;
|
|
}>
|
|
| Readonly<{
|
|
kind: "GENERATED";
|
|
bytes: FileByteSource;
|
|
expectedSha256?: string;
|
|
}>;
|
|
|
|
export type DownloadStrategy =
|
|
| "BROWSER_MANAGED"
|
|
| "PROMPT_AND_STREAM"
|
|
| "BOUNDED_OBJECT_URL";
|
|
|
|
export type DownloadOutcome =
|
|
| Readonly<{
|
|
kind: "BROWSER_HANDOFF";
|
|
transferId: string;
|
|
}>
|
|
| Readonly<{
|
|
kind: "SAVED";
|
|
transferId: string;
|
|
bytesWritten: number;
|
|
integrity: "VERIFIED" | "NOT_PROVIDED";
|
|
}>
|
|
| Readonly<{ kind: "DISMISSED" }>;
|
|
|
|
export interface DownloadDeliveryPort {
|
|
deliver(input: {
|
|
policy: FilePolicyReference;
|
|
source: DownloadSource;
|
|
suggestedFileName: string;
|
|
/**
|
|
* Optional reductions of the composition-owned policy ceiling. These
|
|
* values can never raise the registered or absolute runtime limits.
|
|
*/
|
|
maxTransferBytes?: number;
|
|
maxBufferedBytes?: number;
|
|
signal: AbortSignal;
|
|
onProgress(progress: TransferProgress): void;
|
|
}): Promise<BrowserDataResult<DownloadOutcome>>;
|
|
}
|
|
|
|
/**
|
|
* Synchronously resolved, server-enforced handoff capability. The endpoint
|
|
* behind href must bind and enforce every field, including expiry and the
|
|
* optional digest; the browser adapter cannot observe navigation bytes.
|
|
*/
|
|
export type BrowserManagedDownloadCapability = Readonly<{
|
|
capabilityReceipt: BrowserManagedDownloadCapabilityReceipt;
|
|
href: string;
|
|
resourceId: string;
|
|
mediaType: string;
|
|
safeExtension: string;
|
|
maxBytes: number;
|
|
expectedSha256?: string;
|
|
expiresAtEpochMs: number;
|
|
}>;
|
|
|
|
export interface BrowserManagedDownloadCapabilityResolver {
|
|
resolve(input: Readonly<{
|
|
resourceId: string;
|
|
capabilityReceipt: BrowserManagedDownloadCapabilityReceipt;
|
|
}>): BrowserDataResult<BrowserManagedDownloadCapability>;
|
|
}
|