Mirror: A Node.js fetch shim using built-in Request, Response, and Headers (but without native fetch)
1import { PassThrough, Transform, TransformCallback } from 'node:stream';
2import * as zlib from 'node:zlib';
3
4/** @see https://www.rfc-editor.org/rfc/rfc9112.html#section-7.2 */
5type Encoding = 'gzip' | 'x-gzip' | 'deflate' | 'x-deflate' | 'br' | {};
6
7/** @see https://github.com/nodejs/undici/pull/2650 */
8class InflateStream extends Transform {
9 _opts?: zlib.ZlibOptions;
10 _inflate?: Transform;
11
12 constructor(opts?: zlib.ZlibOptions) {
13 super();
14 this._opts = opts;
15 }
16
17 _transform(
18 chunk: Buffer,
19 encoding: BufferEncoding,
20 callback: TransformCallback
21 ) {
22 if (!this._inflate) {
23 if (chunk.length === 0) {
24 callback();
25 return;
26 }
27 this._inflate =
28 (chunk[0] & 0x0f) === 0x08
29 ? zlib.createInflate(this._opts)
30 : zlib.createInflateRaw(this._opts);
31 this._inflate.on('data', this.push.bind(this));
32 this._inflate.on('end', () => this.push(null));
33 this._inflate.on('error', err => this.destroy(err));
34 }
35 this._inflate.write(chunk, encoding, callback);
36 }
37
38 _final(callback: TransformCallback) {
39 if (this._inflate) {
40 this._inflate.end();
41 this._inflate = undefined;
42 }
43 callback();
44 }
45}
46
47export const createContentDecoder = (encoding: Encoding | {}) => {
48 // See: https://github.com/nodejs/undici/blob/008187b/lib/web/fetch/index.js#L2138-L2160
49 switch (encoding) {
50 case 'br':
51 return zlib.createBrotliDecompress({
52 flush: zlib.constants.BROTLI_OPERATION_FLUSH,
53 finishFlush: zlib.constants.BROTLI_OPERATION_FLUSH,
54 });
55 case 'gzip':
56 case 'x-gzip':
57 return zlib.createGunzip({
58 flush: zlib.constants.Z_SYNC_FLUSH,
59 finishFlush: zlib.constants.Z_SYNC_FLUSH,
60 });
61 case 'deflate':
62 case 'x-deflate':
63 return new InflateStream({
64 flush: zlib.constants.Z_SYNC_FLUSH,
65 finishFlush: zlib.constants.Z_SYNC_FLUSH,
66 });
67 default:
68 return new PassThrough();
69 }
70};