wretch
Version:
A tiny wrapper built around fetch with an intuitive syntax.
1 lines • 9.98 kB
Source Map (JSON)
{"version":3,"file":"progress.min.mjs","names":[],"sources":["../../../src/addons/progress.ts"],"sourcesContent":["import type { ConfiguredMiddleware, Wretch, WretchAddon, WretchOptions, WretchResponseChain } from \"../types.js\"\n\nexport type ProgressCallback = (loaded: number, total: number) => void\n\nexport interface ProgressResolver {\n /**\n * Provides a way to register a callback to be invoked one or multiple times during the download.\n * The callback receives the current progress as two arguments, the number of bytes downloaded and the total number of bytes to download.\n *\n * _Under the hood: this method adds a middleware to the chain that will intercept the response and replace the body with a new one that will emit the progress event._\n *\n * ```js\n * import ProgressAddon from \"wretch/addons/progress\"\n *\n * wretch(\"some_url\")\n * .addon(ProgressAddon())\n * .get()\n * .progress((loaded, total) => console.log(`${(loaded / total * 100).toFixed(0)}%`))\n * ```\n *\n * @param onProgress - A callback function for download progress\n */\n progress: <T, C extends ProgressResolver, R>(\n this: C & WretchResponseChain<T, C, R>,\n onProgress: ProgressCallback\n ) => this\n}\n\nexport interface ProgressAddon {\n /**\n * Provides a way to register a callback to be invoked one or multiple times during the upload.\n * The callback receives the current progress as two arguments, the number of bytes uploaded and the total number of bytes to upload.\n *\n * **Browser Limitations:**\n * - **Firefox**: Does not support request body streaming (request.body is not readable). Upload progress monitoring will not work.\n * - **Chrome/Chromium**: Requires HTTPS connections (HTTP/2). Will fail with `ERR_ALPN_NEGOTIATION_FAILED` on HTTP servers.\n * - **Node.js**: Full support for both HTTP and HTTPS.\n *\n * _Compatible with platforms implementing the [TransformStream WebAPI](https://developer.mozilla.org/en-US/docs/Web/API/TransformStream#browser_compatibility) and supporting request body streaming._\n *\n * ```js\n * import ProgressAddon from \"wretch/addons/progress\"\n *\n * wretch(\"https://example.com/upload\") // Note: HTTPS required for Chrome\n * .addon(ProgressAddon())\n * .onUpload((loaded, total) => console.log(`Upload: ${(loaded / total * 100).toFixed(0)}%`))\n * .post(formData)\n * .res()\n * ```\n *\n * @param onUpload - A callback that will be called one or multiple times with the number of bytes uploaded and the total number of bytes to upload.\n */\n onUpload<T extends ProgressAddon, C, R, E>(this: T & Wretch<T, C, R, E>, onUpload: (loaded: number, total: number) => void): this\n\n /**\n * Provides a way to register a callback to be invoked one or multiple times during the download.\n * The callback receives the current progress as two arguments, the number of bytes downloaded and the total number of bytes to download.\n *\n * _Compatible with all platforms implementing the [TransformStream WebAPI](https://developer.mozilla.org/en-US/docs/Web/API/TransformStream#browser_compatibility)._\n *\n * ```js\n * import ProgressAddon from \"wretch/addons/progress\"\n *\n * wretch(\"some_url\")\n * .addon(ProgressAddon())\n * .onDownload((loaded, total) => console.log(`Download: ${(loaded / total * 100).toFixed(0)}%`))\n * .get()\n * .res()\n * ```\n *\n * @param onDownload - A callback that will be called one or multiple times with the number of bytes downloaded and the total number of bytes to download.\n */\n onDownload<T extends ProgressAddon, C, R, E>(this: T & Wretch<T, C, R, E>, onDownload: (loaded: number, total: number) => void): this\n}\n\nfunction toStream<T extends Request | Response>(requestOrResponse: T, bodySize: number, callback: ProgressCallback | undefined): T {\n try {\n const contentLength = requestOrResponse.headers.get(\"content-length\")\n let total = bodySize || (contentLength ? +contentLength : 0)\n let loaded = 0\n const transform = new TransformStream({\n start() {\n callback?.(loaded, total)\n },\n transform(chunk, controller) {\n loaded += chunk.length\n if (total < loaded) {\n total = loaded\n }\n callback?.(loaded, total)\n controller.enqueue(chunk)\n }\n })\n\n const stream = requestOrResponse.body.pipeThrough(transform)\n\n if(\"status\" in requestOrResponse) {\n return new Response(stream, requestOrResponse) as T\n } else {\n // @ts-expect-error RequestInit does not yet include duplex\n return new Request(requestOrResponse, { body: stream, duplex: \"half\" }) as T\n }\n } catch {\n return requestOrResponse\n }\n}\n\nconst defaultGetUploadTotal = async (url: string, opts: WretchOptions): Promise<number> => {\n let total =\n opts.body instanceof ArrayBuffer ? +opts.body.byteLength :\n opts.body instanceof Blob ? +opts.body.size :\n 0\n try {\n // Try to determine body size by reading it as a blob\n total ||= (await new Request(url, opts).blob()).size\n } catch {\n // Cannot determine body size\n }\n\n return total\n}\n\n\n/**\n * Adds the ability to monitor progress when downloading a response.\n *\n * _Compatible with all platforms implementing the [TransformStream WebAPI](https://developer.mozilla.org/en-US/docs/Web/API/TransformStream#browser_compatibility)._\n *\n * ```js\n * import ProgressAddon from \"wretch/addons/progress\"\n *\n * wretch(\"some_url\")\n * // Register the addon\n * .addon(ProgressAddon())\n * .get()\n * // Log the progress as a percentage of completion\n * .progress((loaded, total) => console.log(`${(loaded / total * 100).toFixed(0)}%`))\n * ```\n */\nconst progress: (options?: {\n /**\n * Function used to determine the total upload size before streaming the request body.\n * Receives the final request URL and options, returns the total byte size (sync or async). Defaults to trying the `byteLength` property\n * for `ArrayBuffer` and the `.size` property for `Blob` (e.g., `FormData` or `File`), then falling back to `Request#blob()` when available.\n *\n * _Note_: The fallback of using `Request#blob()` is memory consuming as it loads the entire body into memory.\n *\n * @param url The request URL\n * @param opts The request options\n * @returns The total upload size in bytes\n */\n getUploadTotal?: (url: string, opts: WretchOptions) => number | Promise<number>\n}) => WretchAddon<ProgressAddon, ProgressResolver> = ({\n getUploadTotal = defaultGetUploadTotal\n} = {}) => {\n function downloadMiddleware(state: Record<any, any>) : ConfiguredMiddleware {\n return next => (url, opts) => {\n return next(url, opts).then(response => {\n if (!state.progress) {\n return response\n }\n return toStream(response, 0, state.progress)\n })\n }\n }\n\n function uploadMiddleware(state: Record<any, any>): ConfiguredMiddleware {\n return next => async (url, opts) => {\n const body = opts.body\n\n if (!body || !state.upload) {\n return next(url, opts)\n }\n\n const streameableRequest = toStream(\n new Request(url, opts),\n await getUploadTotal(url, opts), state.upload\n )\n return next(url, streameableRequest)\n }\n }\n\n return {\n beforeRequest(wretch, options, state) {\n const middlewares = []\n if (options.__uploadProgressCallback) {\n state.upload = options.__uploadProgressCallback\n delete options.__uploadProgressCallback\n }\n if (options.__downloadProgressCallback) {\n state.progress = options.__downloadProgressCallback\n delete options.__downloadProgressCallback\n }\n middlewares.push(uploadMiddleware(state))\n middlewares.push(downloadMiddleware(state))\n return wretch.middlewares(middlewares)\n },\n wretch: {\n onUpload(onUpload: (loaded: number, total: number) => void) {\n return this.options({ __uploadProgressCallback: onUpload })\n },\n onDownload(onDownload: (loaded: number, total: number) => void) {\n return this.options({ __downloadProgressCallback: onDownload })\n }\n },\n resolver: {\n progress(onProgress: ProgressCallback) {\n this._sharedState.progress = onProgress\n return this\n }\n },\n }\n}\n\nexport default progress\n"],"mappings":"AA2EA,SAAS,EAAuC,EAAsB,EAAkB,EAA2C,CACjI,GAAI,CACF,IAAM,EAAgB,EAAkB,QAAQ,IAAI,iBAAiB,CACjE,EAAQ,IAAa,EAAgB,CAAC,EAAgB,GACtD,EAAS,EACP,EAAY,IAAI,gBAAgB,CACpC,OAAQ,CACN,IAAW,EAAQ,EAAM,EAE3B,UAAU,EAAO,EAAY,CAC3B,GAAU,EAAM,OACZ,EAAQ,IACV,EAAQ,GAEV,IAAW,EAAQ,EAAM,CACzB,EAAW,QAAQ,EAAM,EAE5B,CAAC,CAEI,EAAS,EAAkB,KAAK,YAAY,EAAU,CAM1D,MAJC,WAAY,EACN,IAAI,SAAS,EAAQ,EAAkB,CAGvC,IAAI,QAAQ,EAAmB,CAAE,KAAM,EAAQ,OAAQ,OAAQ,CAAC,MAEnE,CACN,OAAO,GAIX,MAAM,EAAwB,MAAO,EAAa,IAAyC,CACzF,IAAI,EACI,EAAK,gBAAgB,YAAc,CAAC,EAAK,KAAK,WAC5C,EAAK,gBAAgB,KAAO,CAAC,EAAK,KAAK,KACrC,EACZ,GAAI,CAEF,KAAW,MAAM,IAAI,QAAQ,EAAK,EAAK,CAAC,MAAM,EAAE,UAC1C,EAIR,OAAO,GAoBH,GAagD,CACpD,iBAAiB,GACf,EAAE,GAAK,CACT,SAAS,EAAmB,EAAgD,CAC1E,MAAO,KAAS,EAAK,IACZ,EAAK,EAAK,EAAK,CAAC,KAAK,GACrB,EAAM,SAGJ,EAAS,EAAU,EAAG,EAAM,SAAS,CAFnC,EAGT,CAIN,SAAS,EAAiB,EAA+C,CACvE,MAAO,IAAQ,MAAO,EAAK,IAGrB,CAFS,EAAK,MAEL,CAAC,EAAM,OACX,EAAK,EAAK,EAAK,CAOjB,EAAK,EAJe,EACzB,IAAI,QAAQ,EAAK,EAAK,CACtB,MAAM,EAAe,EAAK,EAAK,CAAE,EAAM,OACxC,CACmC,CAIxC,MAAO,CACL,cAAc,EAAQ,EAAS,EAAO,CACpC,IAAM,EAAc,EAAE,CAWtB,OAVI,EAAQ,2BACV,EAAM,OAAS,EAAQ,yBACvB,OAAO,EAAQ,0BAEb,EAAQ,6BACV,EAAM,SAAW,EAAQ,2BACzB,OAAO,EAAQ,4BAEjB,EAAY,KAAK,EAAiB,EAAM,CAAC,CACzC,EAAY,KAAK,EAAmB,EAAM,CAAC,CACpC,EAAO,YAAY,EAAY,EAExC,OAAQ,CACN,SAAS,EAAmD,CAC1D,OAAO,KAAK,QAAQ,CAAE,yBAA0B,EAAU,CAAC,EAE7D,WAAW,EAAqD,CAC9D,OAAO,KAAK,QAAQ,CAAE,2BAA4B,EAAY,CAAC,EAElE,CACD,SAAU,CACR,SAAS,EAA8B,CAErC,MADA,MAAK,aAAa,SAAW,EACtB,MAEV,CACF"}