@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/cjs/fetch.js CHANGED
@@ -3,11 +3,11 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.fetchPonyfill = void 0;
4
4
  const fs_1 = require("fs");
5
5
  const url_1 = require("url");
6
- const Blob_js_1 = require("./Blob.js");
7
6
  const fetchCurl_js_1 = require("./fetchCurl.js");
8
7
  const fetchNodeHttp_js_1 = require("./fetchNodeHttp.js");
9
8
  const Request_js_1 = require("./Request.js");
10
9
  const Response_js_1 = require("./Response.js");
10
+ const utils_js_1 = require("./utils.js");
11
11
  const BASE64_SUFFIX = ';base64';
12
12
  function getResponseForFile(url) {
13
13
  const path = (0, url_1.fileURLToPath)(url);
@@ -15,15 +15,17 @@ function getResponseForFile(url) {
15
15
  return new Response_js_1.PonyfillResponse(readable);
16
16
  }
17
17
  function getResponseForDataUri(url) {
18
- const [mimeType = 'text/plain', ...datas] = url.split(',');
18
+ const [mimeType = 'text/plain', ...datas] = url.substring(5).split(',');
19
19
  const data = decodeURIComponent(datas.join(','));
20
20
  if (mimeType.endsWith(BASE64_SUFFIX)) {
21
21
  const buffer = Buffer.from(data, 'base64url');
22
22
  const realMimeType = mimeType.slice(0, -BASE64_SUFFIX.length);
23
- const file = new Blob_js_1.PonyfillBlob([buffer], { type: realMimeType });
24
- return new Response_js_1.PonyfillResponse(file, {
23
+ return new Response_js_1.PonyfillResponse(buffer, {
25
24
  status: 200,
26
25
  statusText: 'OK',
26
+ headers: {
27
+ 'content-type': realMimeType,
28
+ },
27
29
  });
28
30
  }
29
31
  return new Response_js_1.PonyfillResponse(data, {
@@ -34,19 +36,22 @@ function getResponseForDataUri(url) {
34
36
  },
35
37
  });
36
38
  }
37
- async function fetchPonyfill(info, init) {
38
- if (typeof info === 'string' || 'href' in info) {
39
+ function isURL(obj) {
40
+ return obj != null && obj.href != null;
41
+ }
42
+ function fetchPonyfill(info, init) {
43
+ if (typeof info === 'string' || isURL(info)) {
39
44
  const ponyfillRequest = new Request_js_1.PonyfillRequest(info, init);
40
45
  return fetchPonyfill(ponyfillRequest);
41
46
  }
42
47
  const fetchRequest = info;
43
48
  if (fetchRequest.url.startsWith('data:')) {
44
49
  const response = getResponseForDataUri(fetchRequest.url);
45
- return Promise.resolve(response);
50
+ return (0, utils_js_1.fakePromise)(response);
46
51
  }
47
52
  if (fetchRequest.url.startsWith('file:')) {
48
53
  const response = getResponseForFile(fetchRequest.url);
49
- return Promise.resolve(response);
54
+ return (0, utils_js_1.fakePromise)(response);
50
55
  }
51
56
  if (globalThis.libcurl) {
52
57
  return (0, fetchCurl_js_1.fetchCurl)(fetchRequest);
package/cjs/fetchCurl.js CHANGED
@@ -1,79 +1,106 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.fetchCurl = void 0;
4
- /* eslint-disable @typescript-eslint/no-this-alias */
5
- const stream_1 = require("stream");
6
- const Headers_js_1 = require("./Headers.js");
4
+ const node_stream_1 = require("node:stream");
5
+ const AbortError_js_1 = require("./AbortError.js");
7
6
  const Response_js_1 = require("./Response.js");
8
7
  const utils_js_1 = require("./utils.js");
9
- async function fetchCurl(fetchRequest) {
10
- const nodeReadable = (fetchRequest.body != null
11
- ? 'pipe' in fetchRequest.body
12
- ? fetchRequest.body
13
- : stream_1.Readable.from(fetchRequest.body)
14
- : null);
8
+ function fetchCurl(fetchRequest) {
9
+ const { Curl, CurlCode, CurlFeature, CurlPause, CurlProgressFunc } = globalThis['libcurl'];
10
+ const curlHandle = new Curl();
11
+ curlHandle.enable(CurlFeature.NoDataParsing);
12
+ curlHandle.setOpt('URL', fetchRequest.url);
13
+ curlHandle.setOpt('SSL_VERIFYPEER', false);
14
+ curlHandle.enable(CurlFeature.StreamResponse);
15
+ curlHandle.setStreamProgressCallback(function () {
16
+ return fetchRequest['_signal']?.aborted
17
+ ? process.env.DEBUG
18
+ ? CurlProgressFunc.Continue
19
+ : 1
20
+ : 0;
21
+ });
22
+ if (fetchRequest['bodyType'] === 'String') {
23
+ curlHandle.setOpt('POSTFIELDS', fetchRequest['bodyInit']);
24
+ }
25
+ else {
26
+ const nodeReadable = (fetchRequest.body != null
27
+ ? (0, utils_js_1.isNodeReadable)(fetchRequest.body)
28
+ ? fetchRequest.body
29
+ : node_stream_1.Readable.from(fetchRequest.body)
30
+ : null);
31
+ if (nodeReadable) {
32
+ curlHandle.setOpt('UPLOAD', true);
33
+ curlHandle.setUploadStream(nodeReadable);
34
+ }
35
+ }
36
+ if (process.env.DEBUG) {
37
+ curlHandle.setOpt('VERBOSE', true);
38
+ }
39
+ curlHandle.setOpt('TRANSFER_ENCODING', false);
40
+ curlHandle.setOpt('HTTP_TRANSFER_DECODING', true);
41
+ curlHandle.setOpt('FOLLOWLOCATION', fetchRequest.redirect === 'follow');
42
+ curlHandle.setOpt('MAXREDIRS', 20);
43
+ curlHandle.setOpt('ACCEPT_ENCODING', '');
44
+ curlHandle.setOpt('CUSTOMREQUEST', fetchRequest.method);
15
45
  const headersSerializer = fetchRequest.headersSerializer || utils_js_1.defaultHeadersSerializer;
16
46
  let size;
17
- const curlyHeaders = headersSerializer(fetchRequest.headers, value => {
47
+ const curlHeaders = headersSerializer(fetchRequest.headers, value => {
18
48
  size = Number(value);
19
49
  });
20
- let easyNativeBinding;
21
- const curlyOptions = {
22
- // we want the unparsed binary response to be returned as a stream to us
23
- curlyStreamResponse: true,
24
- curlyResponseBodyParser: false,
25
- curlyProgressCallback() {
26
- if (easyNativeBinding == null) {
27
- easyNativeBinding = this;
28
- }
29
- return fetchRequest.signal.aborted ? 1 : 0;
30
- },
31
- upload: nodeReadable != null,
32
- transferEncoding: false,
33
- httpTransferDecoding: true,
34
- followLocation: fetchRequest.redirect === 'follow',
35
- maxRedirs: 20,
36
- acceptEncoding: '',
37
- curlyStreamUpload: nodeReadable,
38
- // this will just make libcurl use their own progress function (which is pretty neat)
39
- // curlyProgressCallback() { return CurlProgressFunc.Continue },
40
- // verbose: true,
41
- httpHeader: curlyHeaders,
42
- customRequest: fetchRequest.method,
43
- };
44
50
  if (size != null) {
45
- curlyOptions.inFileSize = size;
51
+ curlHandle.setOpt('INFILESIZE', size);
46
52
  }
47
- const { curly, CurlCode, CurlPause } = globalThis['libcurl'];
48
- fetchRequest.signal.onabort = () => {
49
- if (easyNativeBinding != null) {
50
- easyNativeBinding.pause(CurlPause.Recv);
53
+ curlHandle.setOpt('HTTPHEADER', curlHeaders);
54
+ curlHandle.enable(CurlFeature.NoHeaderParsing);
55
+ return new Promise(function promiseResolver(resolve, reject) {
56
+ let streamResolved = false;
57
+ if (fetchRequest['_signal']) {
58
+ fetchRequest['_signal'].onabort = () => {
59
+ if (streamResolved) {
60
+ curlHandle.pause(CurlPause.Recv);
61
+ }
62
+ else {
63
+ reject(new AbortError_js_1.PonyfillAbortError());
64
+ curlHandle.close();
65
+ }
66
+ };
51
67
  }
52
- };
53
- const curlyResult = await curly(fetchRequest.url, curlyOptions);
54
- const responseHeaders = new Headers_js_1.PonyfillHeaders();
55
- curlyResult.headers.forEach(headerInfo => {
56
- for (const key in headerInfo) {
57
- if (key === 'location' || (key === 'Location' && fetchRequest.redirect === 'error')) {
58
- throw new Error('redirects are not allowed');
68
+ curlHandle.once('end', function endListener() {
69
+ curlHandle.close();
70
+ });
71
+ curlHandle.once('error', function errorListener(error) {
72
+ if (error.isCurlError && error.code === CurlCode.CURLE_ABORTED_BY_CALLBACK) {
73
+ // this is expected
59
74
  }
60
- if (key !== 'result') {
61
- responseHeaders.append(key, headerInfo[key]);
75
+ else {
76
+ // this is unexpected
77
+ reject(error);
62
78
  }
63
- }
64
- });
65
- curlyResult.data.on('error', (err) => {
66
- if (err.isCurlError && err.code === CurlCode.CURLE_ABORTED_BY_CALLBACK) {
67
- // this is expected
68
- }
69
- else {
70
- throw err;
71
- }
72
- });
73
- return new Response_js_1.PonyfillResponse(curlyResult.data, {
74
- status: curlyResult.statusCode,
75
- headers: responseHeaders,
76
- url: fetchRequest.url,
79
+ curlHandle.close();
80
+ });
81
+ curlHandle.once('stream', function streamListener(stream, status, headersBuf) {
82
+ const headersFlat = headersBuf
83
+ .toString('utf8')
84
+ .split(/\r?\n|\r/g)
85
+ .filter(headerFilter => {
86
+ if (headerFilter && !headerFilter.startsWith('HTTP/')) {
87
+ if (fetchRequest.redirect === 'error' &&
88
+ (headerFilter.includes('location') || headerFilter.includes('Location'))) {
89
+ reject(new Error('redirect is not allowed'));
90
+ }
91
+ return true;
92
+ }
93
+ return false;
94
+ });
95
+ const headersInit = headersFlat.map(headerFlat => headerFlat.split(/:\s(.+)/).slice(0, 2));
96
+ resolve(new Response_js_1.PonyfillResponse(stream, {
97
+ status,
98
+ headers: headersInit,
99
+ url: fetchRequest.url,
100
+ }));
101
+ streamResolved = true;
102
+ });
103
+ curlHandle.perform();
77
104
  });
78
105
  }
79
106
  exports.fetchCurl = fetchCurl;
@@ -5,7 +5,6 @@ const http_1 = require("http");
5
5
  const https_1 = require("https");
6
6
  const stream_1 = require("stream");
7
7
  const zlib_1 = require("zlib");
8
- const AbortError_js_1 = require("./AbortError.js");
9
8
  const Request_js_1 = require("./Request.js");
10
9
  const Response_js_1 = require("./Response.js");
11
10
  const URL_js_1 = require("./URL.js");
@@ -24,7 +23,7 @@ function fetchNodeHttp(fetchRequest) {
24
23
  try {
25
24
  const requestFn = getRequestFnForProtocol(fetchRequest.url);
26
25
  const nodeReadable = (fetchRequest.body != null
27
- ? 'pipe' in fetchRequest.body
26
+ ? (0, utils_js_1.isNodeReadable)(fetchRequest.body)
28
27
  ? fetchRequest.body
29
28
  : stream_1.Readable.from(fetchRequest.body)
30
29
  : null);
@@ -33,19 +32,9 @@ function fetchNodeHttp(fetchRequest) {
33
32
  const nodeRequest = requestFn(fetchRequest.url, {
34
33
  method: fetchRequest.method,
35
34
  headers: nodeHeaders,
36
- signal: fetchRequest.signal,
35
+ signal: fetchRequest['_signal'] ?? undefined,
37
36
  agent: fetchRequest.agent,
38
37
  });
39
- // TODO: will be removed after v16 reaches EOL
40
- fetchRequest.signal?.addEventListener('abort', () => {
41
- if (!nodeRequest.aborted) {
42
- nodeRequest.abort();
43
- }
44
- });
45
- // TODO: will be removed after v16 reaches EOL
46
- nodeRequest.once('abort', (reason) => {
47
- reject(new AbortError_js_1.PonyfillAbortError(reason));
48
- });
49
38
  nodeRequest.once('response', nodeResponse => {
50
39
  let responseBody = nodeResponse;
51
40
  const contentEncoding = nodeResponse.headers['content-encoding'];
package/cjs/utils.js CHANGED
@@ -1,8 +1,11 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.defaultHeadersSerializer = exports.uint8ArrayToArrayBuffer = exports.getHeadersObj = void 0;
3
+ exports.isNodeReadable = exports.isArrayBufferView = exports.fakePromise = exports.defaultHeadersSerializer = exports.getHeadersObj = void 0;
4
+ function isHeadersInstance(obj) {
5
+ return obj?.forEach != null;
6
+ }
4
7
  function getHeadersObj(headers) {
5
- if (headers == null || !('forEach' in headers)) {
8
+ if (headers == null || !isHeadersInstance(headers)) {
6
9
  return headers;
7
10
  }
8
11
  const obj = {};
@@ -12,10 +15,6 @@ function getHeadersObj(headers) {
12
15
  return obj;
13
16
  }
14
17
  exports.getHeadersObj = getHeadersObj;
15
- function uint8ArrayToArrayBuffer(uint8array) {
16
- return uint8array.buffer.slice(uint8array.byteOffset, uint8array.byteOffset + uint8array.byteLength);
17
- }
18
- exports.uint8ArrayToArrayBuffer = uint8ArrayToArrayBuffer;
19
18
  function defaultHeadersSerializer(headers, onContentLength) {
20
19
  const headerArray = [];
21
20
  headers.forEach((value, key) => {
@@ -27,3 +26,48 @@ function defaultHeadersSerializer(headers, onContentLength) {
27
26
  return headerArray;
28
27
  }
29
28
  exports.defaultHeadersSerializer = defaultHeadersSerializer;
29
+ function isPromise(val) {
30
+ return val?.then != null;
31
+ }
32
+ function fakePromise(value) {
33
+ if (isPromise(value)) {
34
+ return value;
35
+ }
36
+ // Write a fake promise to avoid the promise constructor
37
+ // being called with `new Promise` in the browser.
38
+ return {
39
+ then(resolve) {
40
+ if (resolve) {
41
+ const callbackResult = resolve(value);
42
+ if (isPromise(callbackResult)) {
43
+ return callbackResult;
44
+ }
45
+ return fakePromise(callbackResult);
46
+ }
47
+ return this;
48
+ },
49
+ catch() {
50
+ return this;
51
+ },
52
+ finally(cb) {
53
+ if (cb) {
54
+ const callbackResult = cb();
55
+ if (isPromise(callbackResult)) {
56
+ return callbackResult.then(() => value);
57
+ }
58
+ return fakePromise(value);
59
+ }
60
+ return this;
61
+ },
62
+ [Symbol.toStringTag]: 'Promise',
63
+ };
64
+ }
65
+ exports.fakePromise = fakePromise;
66
+ function isArrayBufferView(obj) {
67
+ return obj != null && obj.buffer != null && obj.byteLength != null && obj.byteOffset != null;
68
+ }
69
+ exports.isArrayBufferView = isArrayBufferView;
70
+ function isNodeReadable(obj) {
71
+ return obj != null && obj.pipe != null;
72
+ }
73
+ exports.isNodeReadable = isNodeReadable;
package/esm/Blob.js CHANGED
@@ -1,5 +1,6 @@
1
+ /* eslint-disable @typescript-eslint/no-unsafe-declaration-merging */
1
2
  import { PonyfillReadableStream } from './ReadableStream.js';
2
- import { uint8ArrayToArrayBuffer } from './utils.js';
3
+ import { fakePromise, isArrayBufferView } from './utils.js';
3
4
  function getBlobPartAsBuffer(blobPart) {
4
5
  if (typeof blobPart === 'string') {
5
6
  return Buffer.from(blobPart);
@@ -7,10 +8,7 @@ function getBlobPartAsBuffer(blobPart) {
7
8
  else if (Buffer.isBuffer(blobPart)) {
8
9
  return blobPart;
9
10
  }
10
- else if (blobPart instanceof Uint8Array) {
11
- return Buffer.from(blobPart);
12
- }
13
- else if ('buffer' in blobPart) {
11
+ else if (isArrayBufferView(blobPart)) {
14
12
  return Buffer.from(blobPart.buffer, blobPart.byteOffset, blobPart.byteLength);
15
13
  }
16
14
  else {
@@ -18,93 +16,115 @@ function getBlobPartAsBuffer(blobPart) {
18
16
  }
19
17
  }
20
18
  function isBlob(obj) {
21
- return obj != null && typeof obj === 'object' && obj.arrayBuffer != null;
19
+ return obj != null && obj.arrayBuffer != null;
22
20
  }
23
21
  // Will be removed after v14 reaches EOL
24
22
  // Needed because v14 doesn't have .stream() implemented
25
23
  export class PonyfillBlob {
26
24
  constructor(blobParts, options) {
27
25
  this.blobParts = blobParts;
26
+ this._size = null;
28
27
  this.type = options?.type || 'application/octet-stream';
29
28
  this.encoding = options?.encoding || 'utf8';
29
+ this._size = options?.size || null;
30
+ if (blobParts.length === 1 && isBlob(blobParts[0])) {
31
+ return blobParts[0];
32
+ }
30
33
  }
31
- async buffer() {
32
- const bufferChunks = [];
33
- for (const blobPart of this.blobParts) {
34
+ arrayBuffer() {
35
+ if (this.blobParts.length === 1) {
36
+ const blobPart = this.blobParts[0];
34
37
  if (isBlob(blobPart)) {
35
- const arrayBuf = await blobPart.arrayBuffer();
36
- const buf = Buffer.from(arrayBuf, undefined, blobPart.size);
37
- bufferChunks.push(buf);
38
+ return blobPart.arrayBuffer();
39
+ }
40
+ return fakePromise(getBlobPartAsBuffer(blobPart));
41
+ }
42
+ const jobs = [];
43
+ const bufferChunks = this.blobParts.map((blobPart, i) => {
44
+ if (isBlob(blobPart)) {
45
+ jobs.push(blobPart.arrayBuffer().then(arrayBuf => {
46
+ bufferChunks[i] = Buffer.from(arrayBuf, undefined, blobPart.size);
47
+ }));
48
+ return undefined;
38
49
  }
39
50
  else {
40
- const buf = getBlobPartAsBuffer(blobPart);
41
- bufferChunks.push(buf);
51
+ return getBlobPartAsBuffer(blobPart);
42
52
  }
53
+ });
54
+ if (jobs.length > 0) {
55
+ return Promise.all(jobs).then(() => Buffer.concat(bufferChunks, this._size || undefined));
43
56
  }
44
- return Buffer.concat(bufferChunks);
57
+ return fakePromise(Buffer.concat(bufferChunks, this._size || undefined));
45
58
  }
46
- async arrayBuffer() {
47
- const buffer = await this.buffer();
48
- return uint8ArrayToArrayBuffer(buffer);
49
- }
50
- async text() {
51
- let text = '';
52
- for (const blobPart of this.blobParts) {
59
+ text() {
60
+ if (this.blobParts.length === 1) {
61
+ const blobPart = this.blobParts[0];
53
62
  if (typeof blobPart === 'string') {
54
- text += blobPart;
55
- }
56
- else if ('text' in blobPart) {
57
- text += await blobPart.text();
63
+ return fakePromise(blobPart);
58
64
  }
59
- else {
60
- const buf = getBlobPartAsBuffer(blobPart);
61
- text += buf.toString(this.encoding);
65
+ if (isBlob(blobPart)) {
66
+ return blobPart.text();
62
67
  }
68
+ const buf = getBlobPartAsBuffer(blobPart);
69
+ return fakePromise(buf.toString(this.encoding));
63
70
  }
64
- return text;
71
+ return this.arrayBuffer().then(buf => buf.toString(this.encoding));
65
72
  }
66
73
  get size() {
67
- let size = 0;
68
- for (const blobPart of this.blobParts) {
69
- if (typeof blobPart === 'string') {
70
- size += Buffer.byteLength(blobPart);
71
- }
72
- else if (isBlob(blobPart)) {
73
- size += blobPart.size;
74
- }
75
- else if ('length' in blobPart) {
76
- size += blobPart.length;
77
- }
78
- else if ('byteLength' in blobPart) {
79
- size += blobPart.byteLength;
74
+ if (this._size == null) {
75
+ this._size = 0;
76
+ for (const blobPart of this.blobParts) {
77
+ if (typeof blobPart === 'string') {
78
+ this._size += Buffer.byteLength(blobPart);
79
+ }
80
+ else if (isBlob(blobPart)) {
81
+ this._size += blobPart.size;
82
+ }
83
+ else if (isArrayBufferView(blobPart)) {
84
+ this._size += blobPart.byteLength;
85
+ }
80
86
  }
81
87
  }
82
- return size;
88
+ return this._size;
83
89
  }
84
90
  stream() {
85
- let partQueue = [];
91
+ if (this.blobParts.length === 1) {
92
+ const blobPart = this.blobParts[0];
93
+ if (isBlob(blobPart)) {
94
+ return blobPart.stream();
95
+ }
96
+ const buf = getBlobPartAsBuffer(blobPart);
97
+ return new PonyfillReadableStream({
98
+ start: controller => {
99
+ controller.enqueue(buf);
100
+ controller.close();
101
+ },
102
+ });
103
+ }
104
+ let blobPartIterator;
86
105
  return new PonyfillReadableStream({
87
106
  start: controller => {
88
- partQueue = [...this.blobParts];
89
- if (partQueue.length === 0) {
107
+ if (this.blobParts.length === 0) {
90
108
  controller.close();
109
+ return;
91
110
  }
111
+ blobPartIterator = this.blobParts[Symbol.iterator]();
92
112
  },
93
- pull: async (controller) => {
94
- const blobPart = partQueue.pop();
113
+ pull: controller => {
114
+ const { value: blobPart, done } = blobPartIterator.next();
115
+ if (done) {
116
+ controller.close();
117
+ return;
118
+ }
95
119
  if (blobPart) {
96
120
  if (isBlob(blobPart)) {
97
- const arrayBuffer = await blobPart.arrayBuffer();
98
- const buf = Buffer.from(arrayBuffer, undefined, blobPart.size);
99
- controller.enqueue(buf);
121
+ return blobPart.arrayBuffer().then(arrayBuffer => {
122
+ const buf = Buffer.from(arrayBuffer, undefined, blobPart.size);
123
+ controller.enqueue(buf);
124
+ });
100
125
  }
101
- else {
102
- const buf = getBlobPartAsBuffer(blobPart);
103
- controller.enqueue(buf);
104
- }
105
- }
106
- else {
107
- controller.close();
126
+ const buf = getBlobPartAsBuffer(blobPart);
127
+ controller.enqueue(buf);
108
128
  }
109
129
  },
110
130
  });