UNPKG

tus-js-client-stall-detection

Version:

A pure JavaScript client for the tus resumable upload protocol

188 lines (156 loc) 5.96 kB
import type { Readable as NodeReadableStream } from 'node:stream' import type { DetailedError } from './DetailedError.js' export const PROTOCOL_TUS_V1 = 'tus-v1' export const PROTOCOL_IETF_DRAFT_03 = 'ietf-draft-03' export const PROTOCOL_IETF_DRAFT_05 = 'ietf-draft-05' /** * ReactNativeFile describes the structure that is returned from the * Expo image picker (see https://docs.expo.dev/versions/latest/sdk/imagepicker/) * TODO: Should these properties be fileName and fileSize instead? * TODO: What about other file pickers without Expo? * TODO: Should this be renamed to Expo? * TODO: Only size is relevant for us. Not the rest. */ export interface ReactNativeFile { uri: string name?: string size?: string exif?: Record<string, unknown> } /** * PathReference is a reference to a file on disk. Currently, it's only supported * in Node.js. It can be supplied as a normal object or as an instance of `fs.ReadStream`, * which also statisfies this interface. * * Optionally, a start and/or end position can be defined to define a range of bytes from * the file that should be uploaded instead of the entire file. Both start and end are * inclusive and start counting at 0, similar to the options accepted by `fs.createReadStream`. */ export interface PathReference { path: string | Buffer start?: number end?: number } export type UploadInput = // available in all environments | Blob // includes File | ArrayBuffer | SharedArrayBuffer | ArrayBufferView // includes Node.js' Buffer | ReadableStream // Web Streams // available in Node.js | NodeReadableStream | PathReference // available in React Native | ReactNativeFile /** * Options for configuring stall detection behavior */ export interface StallDetectionOptions { enabled: boolean stallTimeout: number // Time in ms before considering progress stalled checkInterval: number // How often to check for stalls } export interface UploadOptions { endpoint?: string uploadUrl?: string metadata: { [key: string]: string } metadataForPartialUploads: UploadOptions['metadata'] fingerprint: (file: UploadInput, options: UploadOptions) => Promise<string | null> uploadSize?: number onProgress?: (bytesSent: number, bytesTotal: number | null) => void onChunkComplete?: (chunkSize: number, bytesAccepted: number, bytesTotal: number | null) => void onSuccess?: (payload: OnSuccessPayload) => void onError?: (error: Error | DetailedError) => void onShouldRetry?: (error: DetailedError, retryAttempt: number, options: UploadOptions) => boolean onUploadUrlAvailable?: () => void | Promise<void> overridePatchMethod: boolean headers: { [key: string]: string } addRequestId: boolean onBeforeRequest?: (req: HttpRequest) => void | Promise<void> onAfterResponse?: (req: HttpRequest, res: HttpResponse) => void | Promise<void> chunkSize: number retryDelays: number[] parallelUploads: number parallelUploadBoundaries?: { start: number; end: number }[] storeFingerprintForResuming: boolean removeFingerprintOnSuccess: boolean progressiveUrlSaving: boolean uploadLengthDeferred: boolean uploadDataDuringCreation: boolean urlStorage: UrlStorage fileReader: FileReader httpStack: HttpStack protocol: typeof PROTOCOL_TUS_V1 | typeof PROTOCOL_IETF_DRAFT_03 | typeof PROTOCOL_IETF_DRAFT_05 stallDetection?: StallDetectionOptions } export interface OnSuccessPayload { lastResponse: HttpResponse } export interface UrlStorage { findAllUploads(): Promise<PreviousUpload[]> findUploadsByFingerprint(fingerprint: string): Promise<PreviousUpload[]> removeUpload(urlStorageKey: string): Promise<void> // Returns the URL storage key, which can be used for removing the upload. addUpload(fingerprint: string, upload: PreviousUpload): Promise<string | undefined> } export interface PreviousUpload { size: number | null metadata: { [key: string]: string } creationTime: string uploadUrl?: string parallelUploadUrls?: (string | null)[] urlStorageKey: string } export interface FileReader { openFile(input: UploadInput, chunkSize: number): Promise<FileSource> } export interface FileSource { size: number | null slice(start: number, end: number): Promise<SliceResult> close(): void } // TODO: Allow Web Streams' ReadableStream as well export type SliceType = Blob | ArrayBufferView | NodeReadableStream export type SliceResult = | { done: true value: null size: null } | { done: boolean value: NonNullable<SliceType> // TODO: How should sizes be handled? If we want to allow `slice()` to return // streams without buffering them before, this cannot return a known size. // Should size be returned by the HTTP stack based on the number of uploaded bytes? // It would make sense since it likely already counts progress. size: number } export interface HttpStack { createRequest(method: string, url: string): HttpRequest getName(): string // Indicates whether this HTTP stack implementation // supports progress events during upload. supportsProgressEvents: () => boolean } export type HttpProgressHandler = (bytesSent: number) => void export interface HttpRequest { getMethod(): string getURL(): string setHeader(header: string, value: string): void getHeader(header: string): string | undefined setProgressHandler(handler: HttpProgressHandler): void // TODO: Should this be something like { value: unknown, size: number }? send(body?: SliceType): Promise<HttpResponse> abort(): Promise<void> // Return an environment specific object, e.g. the XMLHttpRequest object in browsers. getUnderlyingObject(): unknown } export interface HttpResponse { getStatus(): number getHeader(header: string): string | undefined getBody(): string // Return an environment specific object, e.g. the XMLHttpRequest object in browsers. getUnderlyingObject(): unknown }