@whatwg-node/node-fetch 0.5.0-alpha-20230710180612-32e574f → 0.5.0-alpha-20231025080723-49677d8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/esm/fetchCurl.js CHANGED
@@ -1,75 +1,102 @@
1
- /* eslint-disable @typescript-eslint/no-this-alias */
2
- import { Readable } from 'stream';
3
- import { PonyfillHeaders } from './Headers.js';
1
+ import { Readable } from 'node:stream';
2
+ import { PonyfillAbortError } from './AbortError.js';
4
3
  import { PonyfillResponse } from './Response.js';
5
- import { defaultHeadersSerializer } from './utils.js';
6
- export async function fetchCurl(fetchRequest) {
7
- const nodeReadable = (fetchRequest.body != null
8
- ? 'pipe' in fetchRequest.body
9
- ? fetchRequest.body
10
- : Readable.from(fetchRequest.body)
11
- : null);
4
+ import { defaultHeadersSerializer, isNodeReadable } from './utils.js';
5
+ export function fetchCurl(fetchRequest) {
6
+ const { Curl, CurlCode, CurlFeature, CurlPause, CurlProgressFunc } = globalThis['libcurl'];
7
+ const curlHandle = new Curl();
8
+ curlHandle.enable(CurlFeature.NoDataParsing);
9
+ curlHandle.setOpt('URL', fetchRequest.url);
10
+ curlHandle.setOpt('SSL_VERIFYPEER', false);
11
+ curlHandle.enable(CurlFeature.StreamResponse);
12
+ curlHandle.setStreamProgressCallback(function () {
13
+ return fetchRequest['_signal']?.aborted
14
+ ? process.env.DEBUG
15
+ ? CurlProgressFunc.Continue
16
+ : 1
17
+ : 0;
18
+ });
19
+ if (fetchRequest['bodyType'] === 'String') {
20
+ curlHandle.setOpt('POSTFIELDS', fetchRequest['bodyInit']);
21
+ }
22
+ else {
23
+ const nodeReadable = (fetchRequest.body != null
24
+ ? isNodeReadable(fetchRequest.body)
25
+ ? fetchRequest.body
26
+ : Readable.from(fetchRequest.body)
27
+ : null);
28
+ if (nodeReadable) {
29
+ curlHandle.setOpt('UPLOAD', true);
30
+ curlHandle.setUploadStream(nodeReadable);
31
+ }
32
+ }
33
+ if (process.env.DEBUG) {
34
+ curlHandle.setOpt('VERBOSE', true);
35
+ }
36
+ curlHandle.setOpt('TRANSFER_ENCODING', false);
37
+ curlHandle.setOpt('HTTP_TRANSFER_DECODING', true);
38
+ curlHandle.setOpt('FOLLOWLOCATION', fetchRequest.redirect === 'follow');
39
+ curlHandle.setOpt('MAXREDIRS', 20);
40
+ curlHandle.setOpt('ACCEPT_ENCODING', '');
41
+ curlHandle.setOpt('CUSTOMREQUEST', fetchRequest.method);
12
42
  const headersSerializer = fetchRequest.headersSerializer || defaultHeadersSerializer;
13
43
  let size;
14
- const curlyHeaders = headersSerializer(fetchRequest.headers, value => {
44
+ const curlHeaders = headersSerializer(fetchRequest.headers, value => {
15
45
  size = Number(value);
16
46
  });
17
- let easyNativeBinding;
18
- const curlyOptions = {
19
- // we want the unparsed binary response to be returned as a stream to us
20
- curlyStreamResponse: true,
21
- curlyResponseBodyParser: false,
22
- curlyProgressCallback() {
23
- if (easyNativeBinding == null) {
24
- easyNativeBinding = this;
25
- }
26
- return fetchRequest.signal.aborted ? 1 : 0;
27
- },
28
- upload: nodeReadable != null,
29
- transferEncoding: false,
30
- httpTransferDecoding: true,
31
- followLocation: fetchRequest.redirect === 'follow',
32
- maxRedirs: 20,
33
- acceptEncoding: '',
34
- curlyStreamUpload: nodeReadable,
35
- // this will just make libcurl use their own progress function (which is pretty neat)
36
- // curlyProgressCallback() { return CurlProgressFunc.Continue },
37
- // verbose: true,
38
- httpHeader: curlyHeaders,
39
- customRequest: fetchRequest.method,
40
- };
41
47
  if (size != null) {
42
- curlyOptions.inFileSize = size;
48
+ curlHandle.setOpt('INFILESIZE', size);
43
49
  }
44
- const { curly, CurlCode, CurlPause } = globalThis['libcurl'];
45
- fetchRequest.signal.onabort = () => {
46
- if (easyNativeBinding != null) {
47
- easyNativeBinding.pause(CurlPause.Recv);
50
+ curlHandle.setOpt('HTTPHEADER', curlHeaders);
51
+ curlHandle.enable(CurlFeature.NoHeaderParsing);
52
+ return new Promise(function promiseResolver(resolve, reject) {
53
+ let streamResolved = false;
54
+ if (fetchRequest['_signal']) {
55
+ fetchRequest['_signal'].onabort = () => {
56
+ if (streamResolved) {
57
+ curlHandle.pause(CurlPause.Recv);
58
+ }
59
+ else {
60
+ reject(new PonyfillAbortError());
61
+ curlHandle.close();
62
+ }
63
+ };
48
64
  }
49
- };
50
- const curlyResult = await curly(fetchRequest.url, curlyOptions);
51
- const responseHeaders = new PonyfillHeaders();
52
- curlyResult.headers.forEach(headerInfo => {
53
- for (const key in headerInfo) {
54
- if (key === 'location' || (key === 'Location' && fetchRequest.redirect === 'error')) {
55
- throw new Error('redirects are not allowed');
65
+ curlHandle.once('end', function endListener() {
66
+ curlHandle.close();
67
+ });
68
+ curlHandle.once('error', function errorListener(error) {
69
+ if (error.isCurlError && error.code === CurlCode.CURLE_ABORTED_BY_CALLBACK) {
70
+ // this is expected
56
71
  }
57
- if (key !== 'result') {
58
- responseHeaders.append(key, headerInfo[key]);
72
+ else {
73
+ // this is unexpected
74
+ reject(error);
59
75
  }
60
- }
61
- });
62
- curlyResult.data.on('error', (err) => {
63
- if (err.isCurlError && err.code === CurlCode.CURLE_ABORTED_BY_CALLBACK) {
64
- // this is expected
65
- }
66
- else {
67
- throw err;
68
- }
69
- });
70
- return new PonyfillResponse(curlyResult.data, {
71
- status: curlyResult.statusCode,
72
- headers: responseHeaders,
73
- url: fetchRequest.url,
76
+ curlHandle.close();
77
+ });
78
+ curlHandle.once('stream', function streamListener(stream, status, headersBuf) {
79
+ const headersFlat = headersBuf
80
+ .toString('utf8')
81
+ .split(/\r?\n|\r/g)
82
+ .filter(headerFilter => {
83
+ if (headerFilter && !headerFilter.startsWith('HTTP/')) {
84
+ if (fetchRequest.redirect === 'error' &&
85
+ (headerFilter.includes('location') || headerFilter.includes('Location'))) {
86
+ reject(new Error('redirect is not allowed'));
87
+ }
88
+ return true;
89
+ }
90
+ return false;
91
+ });
92
+ const headersInit = headersFlat.map(headerFlat => headerFlat.split(/:\s(.+)/).slice(0, 2));
93
+ resolve(new PonyfillResponse(stream, {
94
+ status,
95
+ headers: headersInit,
96
+ url: fetchRequest.url,
97
+ }));
98
+ streamResolved = true;
99
+ });
100
+ curlHandle.perform();
74
101
  });
75
102
  }
@@ -2,11 +2,10 @@ import { request as httpRequest } from 'http';
2
2
  import { request as httpsRequest } from 'https';
3
3
  import { Readable } from 'stream';
4
4
  import { createBrotliDecompress, createGunzip, createInflate } from 'zlib';
5
- import { PonyfillAbortError } from './AbortError.js';
6
5
  import { PonyfillRequest } from './Request.js';
7
6
  import { PonyfillResponse } from './Response.js';
8
7
  import { PonyfillURL } from './URL.js';
9
- import { getHeadersObj } from './utils.js';
8
+ import { getHeadersObj, isNodeReadable } from './utils.js';
10
9
  function getRequestFnForProtocol(url) {
11
10
  if (url.startsWith('http:')) {
12
11
  return httpRequest;
@@ -21,7 +20,7 @@ export function fetchNodeHttp(fetchRequest) {
21
20
  try {
22
21
  const requestFn = getRequestFnForProtocol(fetchRequest.url);
23
22
  const nodeReadable = (fetchRequest.body != null
24
- ? 'pipe' in fetchRequest.body
23
+ ? isNodeReadable(fetchRequest.body)
25
24
  ? fetchRequest.body
26
25
  : Readable.from(fetchRequest.body)
27
26
  : null);
@@ -30,19 +29,9 @@ export function fetchNodeHttp(fetchRequest) {
30
29
  const nodeRequest = requestFn(fetchRequest.url, {
31
30
  method: fetchRequest.method,
32
31
  headers: nodeHeaders,
33
- signal: fetchRequest.signal,
32
+ signal: fetchRequest['_signal'] ?? undefined,
34
33
  agent: fetchRequest.agent,
35
34
  });
36
- // TODO: will be removed after v16 reaches EOL
37
- fetchRequest.signal?.addEventListener('abort', () => {
38
- if (!nodeRequest.aborted) {
39
- nodeRequest.abort();
40
- }
41
- });
42
- // TODO: will be removed after v16 reaches EOL
43
- nodeRequest.once('abort', (reason) => {
44
- reject(new PonyfillAbortError(reason));
45
- });
46
35
  nodeRequest.once('response', nodeResponse => {
47
36
  let responseBody = nodeResponse;
48
37
  const contentEncoding = nodeResponse.headers['content-encoding'];
package/esm/utils.js CHANGED
@@ -1,5 +1,8 @@
1
+ function isHeadersInstance(obj) {
2
+ return obj?.forEach != null;
3
+ }
1
4
  export function getHeadersObj(headers) {
2
- if (headers == null || !('forEach' in headers)) {
5
+ if (headers == null || !isHeadersInstance(headers)) {
3
6
  return headers;
4
7
  }
5
8
  const obj = {};
@@ -8,9 +11,6 @@ export function getHeadersObj(headers) {
8
11
  });
9
12
  return obj;
10
13
  }
11
- export function uint8ArrayToArrayBuffer(uint8array) {
12
- return uint8array.buffer.slice(uint8array.byteOffset, uint8array.byteOffset + uint8array.byteLength);
13
- }
14
14
  export function defaultHeadersSerializer(headers, onContentLength) {
15
15
  const headerArray = [];
16
16
  headers.forEach((value, key) => {
@@ -21,3 +21,45 @@ export function defaultHeadersSerializer(headers, onContentLength) {
21
21
  });
22
22
  return headerArray;
23
23
  }
24
+ function isPromise(val) {
25
+ return val?.then != null;
26
+ }
27
+ export function fakePromise(value) {
28
+ if (isPromise(value)) {
29
+ return value;
30
+ }
31
+ // Write a fake promise to avoid the promise constructor
32
+ // being called with `new Promise` in the browser.
33
+ return {
34
+ then(resolve) {
35
+ if (resolve) {
36
+ const callbackResult = resolve(value);
37
+ if (isPromise(callbackResult)) {
38
+ return callbackResult;
39
+ }
40
+ return fakePromise(callbackResult);
41
+ }
42
+ return this;
43
+ },
44
+ catch() {
45
+ return this;
46
+ },
47
+ finally(cb) {
48
+ if (cb) {
49
+ const callbackResult = cb();
50
+ if (isPromise(callbackResult)) {
51
+ return callbackResult.then(() => value);
52
+ }
53
+ return fakePromise(value);
54
+ }
55
+ return this;
56
+ },
57
+ [Symbol.toStringTag]: 'Promise',
58
+ };
59
+ }
60
+ export function isArrayBufferView(obj) {
61
+ return obj != null && obj.buffer != null && obj.byteLength != null && obj.byteOffset != null;
62
+ }
63
+ export function isNodeReadable(obj) {
64
+ return obj != null && obj.pipe != null;
65
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@whatwg-node/node-fetch",
3
- "version": "0.5.0-alpha-20230710180612-32e574f",
3
+ "version": "0.5.0-alpha-20231025080723-49677d8",
4
4
  "description": "Fetch API implementation for Node",
5
5
  "sideEffects": false,
6
6
  "dependencies": {
@@ -10,14 +10,18 @@ interface BlobOptions {
10
10
  * is performed.
11
11
  */
12
12
  type?: string | undefined;
13
+ /**
14
+ * The size of the Blob object in bytes.
15
+ */
16
+ size?: number | null;
13
17
  }
14
18
  export declare class PonyfillBlob implements Blob {
15
19
  private blobParts;
16
20
  type: string;
17
21
  private encoding;
22
+ private _size;
18
23
  constructor(blobParts: BlobPart[], options?: BlobOptions);
19
- buffer(): Promise<Buffer>;
20
- arrayBuffer(): Promise<ArrayBuffer>;
24
+ arrayBuffer(): Promise<Buffer>;
21
25
  text(): Promise<string>;
22
26
  get size(): number;
23
27
  stream(): any;
package/typings/Blob.d.ts CHANGED
@@ -10,14 +10,18 @@ interface BlobOptions {
10
10
  * is performed.
11
11
  */
12
12
  type?: string | undefined;
13
+ /**
14
+ * The size of the Blob object in bytes.
15
+ */
16
+ size?: number | null;
13
17
  }
14
18
  export declare class PonyfillBlob implements Blob {
15
19
  private blobParts;
16
20
  type: string;
17
21
  private encoding;
22
+ private _size;
18
23
  constructor(blobParts: BlobPart[], options?: BlobOptions);
19
- buffer(): Promise<Buffer>;
20
- arrayBuffer(): Promise<ArrayBuffer>;
24
+ arrayBuffer(): Promise<Buffer>;
21
25
  text(): Promise<string>;
22
26
  get size(): number;
23
27
  stream(): any;
@@ -27,15 +27,15 @@ export declare class PonyfillBody<TJSON = any> implements Body {
27
27
  private bodyType?;
28
28
  private _bodyFactory;
29
29
  private _generatedBody;
30
+ private _buffer?;
30
31
  private generateBody;
31
32
  get body(): PonyfillReadableStream<Uint8Array> | null;
32
- arrayBuffer(): Promise<ArrayBuffer>;
33
33
  _collectChunksFromReadable(): Promise<Uint8Array[]>;
34
34
  blob(): Promise<PonyfillBlob>;
35
35
  formData(opts?: {
36
36
  formDataLimits: FormDataLimits;
37
37
  }): Promise<PonyfillFormData>;
38
- buffer(): Promise<Buffer>;
38
+ arrayBuffer(): Promise<Buffer>;
39
39
  json(): Promise<TJSON>;
40
40
  text(): Promise<string>;
41
41
  }
package/typings/Body.d.ts CHANGED
@@ -27,15 +27,15 @@ export declare class PonyfillBody<TJSON = any> implements Body {
27
27
  private bodyType?;
28
28
  private _bodyFactory;
29
29
  private _generatedBody;
30
+ private _buffer?;
30
31
  private generateBody;
31
32
  get body(): PonyfillReadableStream<Uint8Array> | null;
32
- arrayBuffer(): Promise<ArrayBuffer>;
33
33
  _collectChunksFromReadable(): Promise<Uint8Array[]>;
34
34
  blob(): Promise<PonyfillBlob>;
35
35
  formData(opts?: {
36
36
  formDataLimits: FormDataLimits;
37
37
  }): Promise<PonyfillFormData>;
38
- buffer(): Promise<Buffer>;
38
+ arrayBuffer(): Promise<Buffer>;
39
39
  json(): Promise<TJSON>;
40
40
  text(): Promise<string>;
41
41
  }
@@ -2,12 +2,14 @@ import { PonyfillBlob } from './Blob.cjs';
2
2
  import { PonyfillReadableStream } from './ReadableStream.cjs';
3
3
  export declare class PonyfillFormData implements FormData {
4
4
  private map;
5
- append(name: string, value: PonyfillBlob | string, fileName?: string): void;
5
+ append(name: string, value: string): void;
6
+ append(name: string, value: PonyfillBlob, fileName?: string): void;
6
7
  delete(name: string): void;
7
8
  get(name: string): FormDataEntryValue | null;
8
9
  getAll(name: string): FormDataEntryValue[];
9
10
  has(name: string): boolean;
10
- set(name: string, value: PonyfillBlob | string, fileName?: string): void;
11
+ set(name: string, value: string): void;
12
+ set(name: string, value: PonyfillBlob, fileName?: string): void;
11
13
  [Symbol.iterator](): IterableIterator<[string, FormDataEntryValue]>;
12
14
  entries(): IterableIterator<[string, FormDataEntryValue]>;
13
15
  keys(): IterableIterator<string>;
@@ -2,12 +2,14 @@ import { PonyfillBlob } from './Blob.js';
2
2
  import { PonyfillReadableStream } from './ReadableStream.js';
3
3
  export declare class PonyfillFormData implements FormData {
4
4
  private map;
5
- append(name: string, value: PonyfillBlob | string, fileName?: string): void;
5
+ append(name: string, value: string): void;
6
+ append(name: string, value: PonyfillBlob, fileName?: string): void;
6
7
  delete(name: string): void;
7
8
  get(name: string): FormDataEntryValue | null;
8
9
  getAll(name: string): FormDataEntryValue[];
9
10
  has(name: string): boolean;
10
- set(name: string, value: PonyfillBlob | string, fileName?: string): void;
11
+ set(name: string, value: string): void;
12
+ set(name: string, value: PonyfillBlob, fileName?: string): void;
11
13
  [Symbol.iterator](): IterableIterator<[string, FormDataEntryValue]>;
12
14
  entries(): IterableIterator<[string, FormDataEntryValue]>;
13
15
  keys(): IterableIterator<string>;
@@ -9,7 +9,7 @@ export declare class PonyfillTextDecoder implements TextDecoder {
9
9
  encoding: BufferEncoding;
10
10
  fatal: boolean;
11
11
  ignoreBOM: boolean;
12
- constructor(encoding: BufferEncoding, options: TextDecoderOptions);
13
- decode(input: Uint8Array): string;
12
+ constructor(encoding?: BufferEncoding, options?: TextDecoderOptions);
13
+ decode(input: BufferSource): string;
14
14
  }
15
15
  export declare function PonyfillBtoa(input: string): string;
@@ -9,7 +9,7 @@ export declare class PonyfillTextDecoder implements TextDecoder {
9
9
  encoding: BufferEncoding;
10
10
  fatal: boolean;
11
11
  ignoreBOM: boolean;
12
- constructor(encoding: BufferEncoding, options: TextDecoderOptions);
13
- decode(input: Uint8Array): string;
12
+ constructor(encoding?: BufferEncoding, options?: TextDecoderOptions);
13
+ decode(input: BufferSource): string;
14
14
  }
15
15
  export declare function PonyfillBtoa(input: string): string;
@@ -1,3 +1,7 @@
1
+ /// <reference types="node" />
2
+ import { Readable } from 'node:stream';
1
3
  export declare function getHeadersObj(headers: Headers): Record<string, string>;
2
- export declare function uint8ArrayToArrayBuffer(uint8array: Uint8Array): ArrayBuffer;
3
4
  export declare function defaultHeadersSerializer(headers: Headers, onContentLength?: (value: string) => void): string[];
5
+ export declare function fakePromise<T>(value: T): Promise<T>;
6
+ export declare function isArrayBufferView(obj: any): obj is ArrayBufferView;
7
+ export declare function isNodeReadable(obj: any): obj is Readable;
@@ -1,3 +1,7 @@
1
+ /// <reference types="node" />
2
+ import { Readable } from 'node:stream';
1
3
  export declare function getHeadersObj(headers: Headers): Record<string, string>;
2
- export declare function uint8ArrayToArrayBuffer(uint8array: Uint8Array): ArrayBuffer;
3
4
  export declare function defaultHeadersSerializer(headers: Headers, onContentLength?: (value: string) => void): string[];
5
+ export declare function fakePromise<T>(value: T): Promise<T>;
6
+ export declare function isArrayBufferView(obj: any): obj is ArrayBufferView;
7
+ export declare function isNodeReadable(obj: any): obj is Readable;