@whatwg-node/node-fetch 0.4.7 → 0.4.8-alpha-20230714134016-d696bc4

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/Request.js CHANGED
@@ -3,7 +3,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.PonyfillRequest = void 0;
4
4
  const Body_js_1 = require("./Body.js");
5
5
  const Headers_js_1 = require("./Headers.js");
6
- const utils_js_1 = require("./utils.js");
7
6
  function isRequest(input) {
8
7
  return input[Symbol.toStringTag] === 'Request';
9
8
  }
@@ -42,8 +41,7 @@ class PonyfillRequest extends Body_js_1.PonyfillBody {
42
41
  this.referrer = requestInit?.referrer || 'about:client';
43
42
  this.referrerPolicy = requestInit?.referrerPolicy || 'no-referrer';
44
43
  this._signal = requestInit?.signal;
45
- this.headersSerializer = requestInit?.headersSerializer || utils_js_1.getHeadersObj;
46
- this.agent = requestInit?.agent;
44
+ this.headersSerializer = requestInit?.headersSerializer;
47
45
  this.url = url || '';
48
46
  this.destination = 'document';
49
47
  this.priority = 'auto';
package/cjs/fetch.js CHANGED
@@ -2,24 +2,20 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.fetchPonyfill = void 0;
4
4
  const fs_1 = require("fs");
5
- const http_1 = require("http");
6
- const https_1 = require("https");
7
- const stream_1 = require("stream");
8
5
  const url_1 = require("url");
9
- const zlib_1 = require("zlib");
10
- const AbortError_js_1 = require("./AbortError.js");
11
6
  const Blob_js_1 = require("./Blob.js");
7
+ const fetchCurl_js_1 = require("./fetchCurl.js");
8
+ const fetchNodeHttp_js_1 = require("./fetchNodeHttp.js");
12
9
  const Request_js_1 = require("./Request.js");
13
10
  const Response_js_1 = require("./Response.js");
14
- const URL_js_1 = require("./URL.js");
15
- const utils_js_1 = require("./utils.js");
11
+ const BASE64_SUFFIX = ';base64';
16
12
  function getResponseForFile(url) {
17
13
  const path = (0, url_1.fileURLToPath)(url);
18
14
  const readable = (0, fs_1.createReadStream)(path);
19
15
  return new Response_js_1.PonyfillResponse(readable);
20
16
  }
21
17
  function getResponseForDataUri(url) {
22
- const [mimeType = 'text/plain', ...datas] = url.pathname.split(',');
18
+ const [mimeType = 'text/plain', ...datas] = url.split(',');
23
19
  const data = decodeURIComponent(datas.join(','));
24
20
  if (mimeType.endsWith(BASE64_SUFFIX)) {
25
21
  const buffer = Buffer.from(data, 'base64url');
@@ -38,112 +34,23 @@ function getResponseForDataUri(url) {
38
34
  },
39
35
  });
40
36
  }
41
- function getRequestFnForProtocol(protocol) {
42
- switch (protocol) {
43
- case 'http:':
44
- return http_1.request;
45
- case 'https:':
46
- return https_1.request;
47
- }
48
- throw new Error(`Unsupported protocol: ${protocol}`);
49
- }
50
- const BASE64_SUFFIX = ';base64';
51
- function fetchPonyfill(info, init) {
37
+ async function fetchPonyfill(info, init) {
52
38
  if (typeof info === 'string' || 'href' in info) {
53
39
  const ponyfillRequest = new Request_js_1.PonyfillRequest(info, init);
54
40
  return fetchPonyfill(ponyfillRequest);
55
41
  }
56
42
  const fetchRequest = info;
57
- return new Promise((resolve, reject) => {
58
- try {
59
- const url = new URL_js_1.PonyfillURL(fetchRequest.url, 'http://localhost');
60
- if (url.protocol === 'data:') {
61
- const response = getResponseForDataUri(url);
62
- resolve(response);
63
- return;
64
- }
65
- if (url.protocol === 'file:') {
66
- const response = getResponseForFile(fetchRequest.url);
67
- resolve(response);
68
- return;
69
- }
70
- const requestFn = getRequestFnForProtocol(url.protocol);
71
- const nodeReadable = (fetchRequest.body != null
72
- ? 'pipe' in fetchRequest.body
73
- ? fetchRequest.body
74
- : stream_1.Readable.from(fetchRequest.body)
75
- : null);
76
- const headersSerializer = fetchRequest.headersSerializer || utils_js_1.getHeadersObj;
77
- const nodeHeaders = headersSerializer(fetchRequest.headers);
78
- const nodeRequest = requestFn(fetchRequest.url, {
79
- method: fetchRequest.method,
80
- headers: nodeHeaders,
81
- signal: fetchRequest.signal,
82
- agent: fetchRequest.agent,
83
- });
84
- // TODO: will be removed after v16 reaches EOL
85
- fetchRequest.signal?.addEventListener('abort', () => {
86
- if (!nodeRequest.aborted) {
87
- nodeRequest.abort();
88
- }
89
- });
90
- // TODO: will be removed after v16 reaches EOL
91
- nodeRequest.once('abort', (reason) => {
92
- reject(new AbortError_js_1.PonyfillAbortError(reason));
93
- });
94
- nodeRequest.once('response', nodeResponse => {
95
- let responseBody = nodeResponse;
96
- const contentEncoding = nodeResponse.headers['content-encoding'];
97
- switch (contentEncoding) {
98
- case 'x-gzip':
99
- case 'gzip':
100
- responseBody = nodeResponse.pipe((0, zlib_1.createGunzip)());
101
- break;
102
- case 'x-deflate':
103
- case 'deflate':
104
- responseBody = nodeResponse.pipe((0, zlib_1.createInflate)());
105
- break;
106
- case 'br':
107
- responseBody = nodeResponse.pipe((0, zlib_1.createBrotliDecompress)());
108
- break;
109
- }
110
- if (nodeResponse.headers.location) {
111
- if (fetchRequest.redirect === 'error') {
112
- const redirectError = new Error('Redirects are not allowed');
113
- reject(redirectError);
114
- nodeResponse.resume();
115
- return;
116
- }
117
- if (fetchRequest.redirect === 'follow') {
118
- const redirectedUrl = new URL_js_1.PonyfillURL(nodeResponse.headers.location, url);
119
- const redirectResponse$ = fetchPonyfill(redirectedUrl, info);
120
- resolve(redirectResponse$.then(redirectResponse => {
121
- redirectResponse.redirected = true;
122
- return redirectResponse;
123
- }));
124
- nodeResponse.resume();
125
- return;
126
- }
127
- }
128
- const ponyfillResponse = new Response_js_1.PonyfillResponse(responseBody, {
129
- status: nodeResponse.statusCode,
130
- statusText: nodeResponse.statusMessage,
131
- headers: nodeResponse.headers,
132
- url: info.url,
133
- });
134
- resolve(ponyfillResponse);
135
- });
136
- nodeRequest.once('error', reject);
137
- if (nodeReadable) {
138
- nodeReadable.pipe(nodeRequest);
139
- }
140
- else {
141
- nodeRequest.end();
142
- }
143
- }
144
- catch (e) {
145
- reject(e);
146
- }
147
- });
43
+ if (fetchRequest.url.startsWith('data:')) {
44
+ const response = getResponseForDataUri(fetchRequest.url);
45
+ return Promise.resolve(response);
46
+ }
47
+ if (fetchRequest.url.startsWith('file:')) {
48
+ const response = getResponseForFile(fetchRequest.url);
49
+ return Promise.resolve(response);
50
+ }
51
+ if (globalThis.libcurl) {
52
+ return (0, fetchCurl_js_1.fetchCurl)(fetchRequest);
53
+ }
54
+ return (0, fetchNodeHttp_js_1.fetchNodeHttp)(fetchRequest);
148
55
  }
149
56
  exports.fetchPonyfill = fetchPonyfill;
@@ -0,0 +1,81 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
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");
7
+ const Response_js_1 = require("./Response.js");
8
+ 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);
15
+ const headersSerializer = fetchRequest.headersSerializer || utils_js_1.defaultHeadersSerializer;
16
+ let size;
17
+ const curlyHeaders = headersSerializer(fetchRequest.headers, value => {
18
+ size = Number(value);
19
+ });
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
+ if (size != null) {
45
+ curlyOptions.inFileSize = size;
46
+ }
47
+ const { curly, CurlCode, CurlPause } = globalThis['libcurl'];
48
+ if (fetchRequest['_signal']) {
49
+ fetchRequest['_signal'].onabort = () => {
50
+ if (easyNativeBinding != null) {
51
+ easyNativeBinding.pause(CurlPause.Recv);
52
+ }
53
+ };
54
+ }
55
+ const curlyResult = await curly(fetchRequest.url, curlyOptions);
56
+ const responseHeaders = new Headers_js_1.PonyfillHeaders();
57
+ curlyResult.headers.forEach(headerInfo => {
58
+ for (const key in headerInfo) {
59
+ if (key === 'location' || (key === 'Location' && fetchRequest.redirect === 'error')) {
60
+ throw new Error('redirects are not allowed');
61
+ }
62
+ if (key !== 'result') {
63
+ responseHeaders.append(key, headerInfo[key]);
64
+ }
65
+ }
66
+ });
67
+ curlyResult.data.on('error', (err) => {
68
+ if (err.isCurlError && err.code === CurlCode.CURLE_ABORTED_BY_CALLBACK) {
69
+ // this is expected
70
+ }
71
+ else {
72
+ throw err;
73
+ }
74
+ });
75
+ return new Response_js_1.PonyfillResponse(curlyResult.data, {
76
+ status: curlyResult.statusCode,
77
+ headers: responseHeaders,
78
+ url: fetchRequest.url,
79
+ });
80
+ }
81
+ exports.fetchCurl = fetchCurl;
@@ -0,0 +1,104 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.fetchNodeHttp = void 0;
4
+ const http_1 = require("http");
5
+ const https_1 = require("https");
6
+ const stream_1 = require("stream");
7
+ const zlib_1 = require("zlib");
8
+ const AbortError_js_1 = require("./AbortError.js");
9
+ const Request_js_1 = require("./Request.js");
10
+ const Response_js_1 = require("./Response.js");
11
+ const URL_js_1 = require("./URL.js");
12
+ const utils_js_1 = require("./utils.js");
13
+ function getRequestFnForProtocol(url) {
14
+ if (url.startsWith('http:')) {
15
+ return http_1.request;
16
+ }
17
+ else if (url.startsWith('https:')) {
18
+ return https_1.request;
19
+ }
20
+ throw new Error(`Unsupported protocol: ${url.split(':')[0] || url}`);
21
+ }
22
+ function fetchNodeHttp(fetchRequest) {
23
+ return new Promise((resolve, reject) => {
24
+ try {
25
+ const requestFn = getRequestFnForProtocol(fetchRequest.url);
26
+ const nodeReadable = (fetchRequest.body != null
27
+ ? 'pipe' in fetchRequest.body
28
+ ? fetchRequest.body
29
+ : stream_1.Readable.from(fetchRequest.body)
30
+ : null);
31
+ const headersSerializer = fetchRequest.headersSerializer || utils_js_1.getHeadersObj;
32
+ const nodeHeaders = headersSerializer(fetchRequest.headers);
33
+ const nodeRequest = requestFn(fetchRequest.url, {
34
+ method: fetchRequest.method,
35
+ headers: nodeHeaders,
36
+ signal: fetchRequest['_signal'] ?? undefined,
37
+ agent: fetchRequest.agent,
38
+ });
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
+ nodeRequest.once('response', nodeResponse => {
50
+ let responseBody = nodeResponse;
51
+ const contentEncoding = nodeResponse.headers['content-encoding'];
52
+ switch (contentEncoding) {
53
+ case 'x-gzip':
54
+ case 'gzip':
55
+ responseBody = nodeResponse.pipe((0, zlib_1.createGunzip)());
56
+ break;
57
+ case 'x-deflate':
58
+ case 'deflate':
59
+ responseBody = nodeResponse.pipe((0, zlib_1.createInflate)());
60
+ break;
61
+ case 'br':
62
+ responseBody = nodeResponse.pipe((0, zlib_1.createBrotliDecompress)());
63
+ break;
64
+ }
65
+ if (nodeResponse.headers.location) {
66
+ if (fetchRequest.redirect === 'error') {
67
+ const redirectError = new Error('Redirects are not allowed');
68
+ reject(redirectError);
69
+ nodeResponse.resume();
70
+ return;
71
+ }
72
+ if (fetchRequest.redirect === 'follow') {
73
+ const redirectedUrl = new URL_js_1.PonyfillURL(nodeResponse.headers.location, fetchRequest.url);
74
+ const redirectResponse$ = fetchNodeHttp(new Request_js_1.PonyfillRequest(redirectedUrl, fetchRequest));
75
+ resolve(redirectResponse$.then(redirectResponse => {
76
+ redirectResponse.redirected = true;
77
+ return redirectResponse;
78
+ }));
79
+ nodeResponse.resume();
80
+ return;
81
+ }
82
+ }
83
+ const ponyfillResponse = new Response_js_1.PonyfillResponse(responseBody, {
84
+ status: nodeResponse.statusCode,
85
+ statusText: nodeResponse.statusMessage,
86
+ headers: nodeResponse.headers,
87
+ url: fetchRequest.url,
88
+ });
89
+ resolve(ponyfillResponse);
90
+ });
91
+ nodeRequest.once('error', reject);
92
+ if (nodeReadable) {
93
+ nodeReadable.pipe(nodeRequest);
94
+ }
95
+ else {
96
+ nodeRequest.end();
97
+ }
98
+ }
99
+ catch (e) {
100
+ reject(e);
101
+ }
102
+ });
103
+ }
104
+ exports.fetchNodeHttp = fetchNodeHttp;
package/cjs/utils.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.uint8ArrayToArrayBuffer = exports.getHeadersObj = void 0;
3
+ exports.defaultHeadersSerializer = exports.uint8ArrayToArrayBuffer = exports.getHeadersObj = void 0;
4
4
  function getHeadersObj(headers) {
5
5
  if (headers == null || !('forEach' in headers)) {
6
6
  return headers;
@@ -16,3 +16,14 @@ function uint8ArrayToArrayBuffer(uint8array) {
16
16
  return uint8array.buffer.slice(uint8array.byteOffset, uint8array.byteOffset + uint8array.byteLength);
17
17
  }
18
18
  exports.uint8ArrayToArrayBuffer = uint8ArrayToArrayBuffer;
19
+ function defaultHeadersSerializer(headers, onContentLength) {
20
+ const headerArray = [];
21
+ headers.forEach((value, key) => {
22
+ if (onContentLength && key === 'content-length') {
23
+ onContentLength(value);
24
+ }
25
+ headerArray.push(`${key}: ${value}`);
26
+ });
27
+ return headerArray;
28
+ }
29
+ exports.defaultHeadersSerializer = defaultHeadersSerializer;
package/esm/Request.js CHANGED
@@ -1,6 +1,5 @@
1
1
  import { PonyfillBody } from './Body.js';
2
2
  import { isHeadersLike, PonyfillHeaders } from './Headers.js';
3
- import { getHeadersObj } from './utils.js';
4
3
  function isRequest(input) {
5
4
  return input[Symbol.toStringTag] === 'Request';
6
5
  }
@@ -39,8 +38,7 @@ export class PonyfillRequest extends PonyfillBody {
39
38
  this.referrer = requestInit?.referrer || 'about:client';
40
39
  this.referrerPolicy = requestInit?.referrerPolicy || 'no-referrer';
41
40
  this._signal = requestInit?.signal;
42
- this.headersSerializer = requestInit?.headersSerializer || getHeadersObj;
43
- this.agent = requestInit?.agent;
41
+ this.headersSerializer = requestInit?.headersSerializer;
44
42
  this.url = url || '';
45
43
  this.destination = 'document';
46
44
  this.priority = 'auto';
package/esm/fetch.js CHANGED
@@ -1,22 +1,18 @@
1
1
  import { createReadStream } from 'fs';
2
- import { request as httpRequest } from 'http';
3
- import { request as httpsRequest } from 'https';
4
- import { Readable } from 'stream';
5
2
  import { fileURLToPath } from 'url';
6
- import { createBrotliDecompress, createGunzip, createInflate } from 'zlib';
7
- import { PonyfillAbortError } from './AbortError.js';
8
3
  import { PonyfillBlob } from './Blob.js';
4
+ import { fetchCurl } from './fetchCurl.js';
5
+ import { fetchNodeHttp } from './fetchNodeHttp.js';
9
6
  import { PonyfillRequest } from './Request.js';
10
7
  import { PonyfillResponse } from './Response.js';
11
- import { PonyfillURL } from './URL.js';
12
- import { getHeadersObj } from './utils.js';
8
+ const BASE64_SUFFIX = ';base64';
13
9
  function getResponseForFile(url) {
14
10
  const path = fileURLToPath(url);
15
11
  const readable = createReadStream(path);
16
12
  return new PonyfillResponse(readable);
17
13
  }
18
14
  function getResponseForDataUri(url) {
19
- const [mimeType = 'text/plain', ...datas] = url.pathname.split(',');
15
+ const [mimeType = 'text/plain', ...datas] = url.split(',');
20
16
  const data = decodeURIComponent(datas.join(','));
21
17
  if (mimeType.endsWith(BASE64_SUFFIX)) {
22
18
  const buffer = Buffer.from(data, 'base64url');
@@ -35,111 +31,22 @@ function getResponseForDataUri(url) {
35
31
  },
36
32
  });
37
33
  }
38
- function getRequestFnForProtocol(protocol) {
39
- switch (protocol) {
40
- case 'http:':
41
- return httpRequest;
42
- case 'https:':
43
- return httpsRequest;
44
- }
45
- throw new Error(`Unsupported protocol: ${protocol}`);
46
- }
47
- const BASE64_SUFFIX = ';base64';
48
- export function fetchPonyfill(info, init) {
34
+ export async function fetchPonyfill(info, init) {
49
35
  if (typeof info === 'string' || 'href' in info) {
50
36
  const ponyfillRequest = new PonyfillRequest(info, init);
51
37
  return fetchPonyfill(ponyfillRequest);
52
38
  }
53
39
  const fetchRequest = info;
54
- return new Promise((resolve, reject) => {
55
- try {
56
- const url = new PonyfillURL(fetchRequest.url, 'http://localhost');
57
- if (url.protocol === 'data:') {
58
- const response = getResponseForDataUri(url);
59
- resolve(response);
60
- return;
61
- }
62
- if (url.protocol === 'file:') {
63
- const response = getResponseForFile(fetchRequest.url);
64
- resolve(response);
65
- return;
66
- }
67
- const requestFn = getRequestFnForProtocol(url.protocol);
68
- const nodeReadable = (fetchRequest.body != null
69
- ? 'pipe' in fetchRequest.body
70
- ? fetchRequest.body
71
- : Readable.from(fetchRequest.body)
72
- : null);
73
- const headersSerializer = fetchRequest.headersSerializer || getHeadersObj;
74
- const nodeHeaders = headersSerializer(fetchRequest.headers);
75
- const nodeRequest = requestFn(fetchRequest.url, {
76
- method: fetchRequest.method,
77
- headers: nodeHeaders,
78
- signal: fetchRequest.signal,
79
- agent: fetchRequest.agent,
80
- });
81
- // TODO: will be removed after v16 reaches EOL
82
- fetchRequest.signal?.addEventListener('abort', () => {
83
- if (!nodeRequest.aborted) {
84
- nodeRequest.abort();
85
- }
86
- });
87
- // TODO: will be removed after v16 reaches EOL
88
- nodeRequest.once('abort', (reason) => {
89
- reject(new PonyfillAbortError(reason));
90
- });
91
- nodeRequest.once('response', nodeResponse => {
92
- let responseBody = nodeResponse;
93
- const contentEncoding = nodeResponse.headers['content-encoding'];
94
- switch (contentEncoding) {
95
- case 'x-gzip':
96
- case 'gzip':
97
- responseBody = nodeResponse.pipe(createGunzip());
98
- break;
99
- case 'x-deflate':
100
- case 'deflate':
101
- responseBody = nodeResponse.pipe(createInflate());
102
- break;
103
- case 'br':
104
- responseBody = nodeResponse.pipe(createBrotliDecompress());
105
- break;
106
- }
107
- if (nodeResponse.headers.location) {
108
- if (fetchRequest.redirect === 'error') {
109
- const redirectError = new Error('Redirects are not allowed');
110
- reject(redirectError);
111
- nodeResponse.resume();
112
- return;
113
- }
114
- if (fetchRequest.redirect === 'follow') {
115
- const redirectedUrl = new PonyfillURL(nodeResponse.headers.location, url);
116
- const redirectResponse$ = fetchPonyfill(redirectedUrl, info);
117
- resolve(redirectResponse$.then(redirectResponse => {
118
- redirectResponse.redirected = true;
119
- return redirectResponse;
120
- }));
121
- nodeResponse.resume();
122
- return;
123
- }
124
- }
125
- const ponyfillResponse = new PonyfillResponse(responseBody, {
126
- status: nodeResponse.statusCode,
127
- statusText: nodeResponse.statusMessage,
128
- headers: nodeResponse.headers,
129
- url: info.url,
130
- });
131
- resolve(ponyfillResponse);
132
- });
133
- nodeRequest.once('error', reject);
134
- if (nodeReadable) {
135
- nodeReadable.pipe(nodeRequest);
136
- }
137
- else {
138
- nodeRequest.end();
139
- }
140
- }
141
- catch (e) {
142
- reject(e);
143
- }
144
- });
40
+ if (fetchRequest.url.startsWith('data:')) {
41
+ const response = getResponseForDataUri(fetchRequest.url);
42
+ return Promise.resolve(response);
43
+ }
44
+ if (fetchRequest.url.startsWith('file:')) {
45
+ const response = getResponseForFile(fetchRequest.url);
46
+ return Promise.resolve(response);
47
+ }
48
+ if (globalThis.libcurl) {
49
+ return fetchCurl(fetchRequest);
50
+ }
51
+ return fetchNodeHttp(fetchRequest);
145
52
  }
@@ -0,0 +1,77 @@
1
+ /* eslint-disable @typescript-eslint/no-this-alias */
2
+ import { Readable } from 'stream';
3
+ import { PonyfillHeaders } from './Headers.js';
4
+ 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);
12
+ const headersSerializer = fetchRequest.headersSerializer || defaultHeadersSerializer;
13
+ let size;
14
+ const curlyHeaders = headersSerializer(fetchRequest.headers, value => {
15
+ size = Number(value);
16
+ });
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
+ if (size != null) {
42
+ curlyOptions.inFileSize = size;
43
+ }
44
+ const { curly, CurlCode, CurlPause } = globalThis['libcurl'];
45
+ if (fetchRequest['_signal']) {
46
+ fetchRequest['_signal'].onabort = () => {
47
+ if (easyNativeBinding != null) {
48
+ easyNativeBinding.pause(CurlPause.Recv);
49
+ }
50
+ };
51
+ }
52
+ const curlyResult = await curly(fetchRequest.url, curlyOptions);
53
+ const responseHeaders = new PonyfillHeaders();
54
+ curlyResult.headers.forEach(headerInfo => {
55
+ for (const key in headerInfo) {
56
+ if (key === 'location' || (key === 'Location' && fetchRequest.redirect === 'error')) {
57
+ throw new Error('redirects are not allowed');
58
+ }
59
+ if (key !== 'result') {
60
+ responseHeaders.append(key, headerInfo[key]);
61
+ }
62
+ }
63
+ });
64
+ curlyResult.data.on('error', (err) => {
65
+ if (err.isCurlError && err.code === CurlCode.CURLE_ABORTED_BY_CALLBACK) {
66
+ // this is expected
67
+ }
68
+ else {
69
+ throw err;
70
+ }
71
+ });
72
+ return new PonyfillResponse(curlyResult.data, {
73
+ status: curlyResult.statusCode,
74
+ headers: responseHeaders,
75
+ url: fetchRequest.url,
76
+ });
77
+ }
@@ -0,0 +1,100 @@
1
+ import { request as httpRequest } from 'http';
2
+ import { request as httpsRequest } from 'https';
3
+ import { Readable } from 'stream';
4
+ import { createBrotliDecompress, createGunzip, createInflate } from 'zlib';
5
+ import { PonyfillAbortError } from './AbortError.js';
6
+ import { PonyfillRequest } from './Request.js';
7
+ import { PonyfillResponse } from './Response.js';
8
+ import { PonyfillURL } from './URL.js';
9
+ import { getHeadersObj } from './utils.js';
10
+ function getRequestFnForProtocol(url) {
11
+ if (url.startsWith('http:')) {
12
+ return httpRequest;
13
+ }
14
+ else if (url.startsWith('https:')) {
15
+ return httpsRequest;
16
+ }
17
+ throw new Error(`Unsupported protocol: ${url.split(':')[0] || url}`);
18
+ }
19
+ export function fetchNodeHttp(fetchRequest) {
20
+ return new Promise((resolve, reject) => {
21
+ try {
22
+ const requestFn = getRequestFnForProtocol(fetchRequest.url);
23
+ const nodeReadable = (fetchRequest.body != null
24
+ ? 'pipe' in fetchRequest.body
25
+ ? fetchRequest.body
26
+ : Readable.from(fetchRequest.body)
27
+ : null);
28
+ const headersSerializer = fetchRequest.headersSerializer || getHeadersObj;
29
+ const nodeHeaders = headersSerializer(fetchRequest.headers);
30
+ const nodeRequest = requestFn(fetchRequest.url, {
31
+ method: fetchRequest.method,
32
+ headers: nodeHeaders,
33
+ signal: fetchRequest['_signal'] ?? undefined,
34
+ agent: fetchRequest.agent,
35
+ });
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
+ nodeRequest.once('response', nodeResponse => {
47
+ let responseBody = nodeResponse;
48
+ const contentEncoding = nodeResponse.headers['content-encoding'];
49
+ switch (contentEncoding) {
50
+ case 'x-gzip':
51
+ case 'gzip':
52
+ responseBody = nodeResponse.pipe(createGunzip());
53
+ break;
54
+ case 'x-deflate':
55
+ case 'deflate':
56
+ responseBody = nodeResponse.pipe(createInflate());
57
+ break;
58
+ case 'br':
59
+ responseBody = nodeResponse.pipe(createBrotliDecompress());
60
+ break;
61
+ }
62
+ if (nodeResponse.headers.location) {
63
+ if (fetchRequest.redirect === 'error') {
64
+ const redirectError = new Error('Redirects are not allowed');
65
+ reject(redirectError);
66
+ nodeResponse.resume();
67
+ return;
68
+ }
69
+ if (fetchRequest.redirect === 'follow') {
70
+ const redirectedUrl = new PonyfillURL(nodeResponse.headers.location, fetchRequest.url);
71
+ const redirectResponse$ = fetchNodeHttp(new PonyfillRequest(redirectedUrl, fetchRequest));
72
+ resolve(redirectResponse$.then(redirectResponse => {
73
+ redirectResponse.redirected = true;
74
+ return redirectResponse;
75
+ }));
76
+ nodeResponse.resume();
77
+ return;
78
+ }
79
+ }
80
+ const ponyfillResponse = new PonyfillResponse(responseBody, {
81
+ status: nodeResponse.statusCode,
82
+ statusText: nodeResponse.statusMessage,
83
+ headers: nodeResponse.headers,
84
+ url: fetchRequest.url,
85
+ });
86
+ resolve(ponyfillResponse);
87
+ });
88
+ nodeRequest.once('error', reject);
89
+ if (nodeReadable) {
90
+ nodeReadable.pipe(nodeRequest);
91
+ }
92
+ else {
93
+ nodeRequest.end();
94
+ }
95
+ }
96
+ catch (e) {
97
+ reject(e);
98
+ }
99
+ });
100
+ }
package/esm/utils.js CHANGED
@@ -11,3 +11,13 @@ export function getHeadersObj(headers) {
11
11
  export function uint8ArrayToArrayBuffer(uint8array) {
12
12
  return uint8array.buffer.slice(uint8array.byteOffset, uint8array.byteOffset + uint8array.byteLength);
13
13
  }
14
+ export function defaultHeadersSerializer(headers, onContentLength) {
15
+ const headerArray = [];
16
+ headers.forEach((value, key) => {
17
+ if (onContentLength && key === 'content-length') {
18
+ onContentLength(value);
19
+ }
20
+ headerArray.push(`${key}: ${value}`);
21
+ });
22
+ return headerArray;
23
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@whatwg-node/node-fetch",
3
- "version": "0.4.7",
3
+ "version": "0.4.8-alpha-20230714134016-d696bc4",
4
4
  "description": "Fetch API implementation for Node",
5
5
  "sideEffects": false,
6
6
  "dependencies": {
@@ -1,5 +1,5 @@
1
1
  /// <reference types="node" />
2
- import type { Agent } from 'http';
2
+ import { Agent } from 'http';
3
3
  import { BodyPonyfillInit, PonyfillBody, PonyfillBodyOptions } from './Body.cjs';
4
4
  import { PonyfillHeadersInit } from './Headers.cjs';
5
5
  export type RequestPonyfillInit = PonyfillBodyOptions & Omit<RequestInit, 'body' | 'headers'> & {
@@ -8,10 +8,10 @@ export type RequestPonyfillInit = PonyfillBodyOptions & Omit<RequestInit, 'body'
8
8
  headersSerializer?: HeadersSerializer;
9
9
  agent?: Agent;
10
10
  };
11
- type HeadersSerializer = (headers: Headers) => Record<string, string>;
11
+ type HeadersSerializer = (headers: Headers, onContentLength?: (contentLength: string) => void) => string[];
12
12
  export declare class PonyfillRequest<TJSON = any> extends PonyfillBody<TJSON> implements Request {
13
13
  constructor(input: RequestInfo | URL, options?: RequestPonyfillInit);
14
- headersSerializer: HeadersSerializer;
14
+ headersSerializer?: HeadersSerializer;
15
15
  cache: RequestCache;
16
16
  credentials: RequestCredentials;
17
17
  destination: RequestDestination;
@@ -25,9 +25,9 @@ export declare class PonyfillRequest<TJSON = any> extends PonyfillBody<TJSON> im
25
25
  referrer: string;
26
26
  referrerPolicy: ReferrerPolicy;
27
27
  url: string;
28
+ agent?: Agent;
28
29
  private _signal;
29
30
  get signal(): AbortSignal;
30
- agent?: Agent;
31
- clone(): PonyfillRequest;
31
+ clone(): PonyfillRequest<TJSON>;
32
32
  }
33
33
  export {};
@@ -1,5 +1,5 @@
1
1
  /// <reference types="node" />
2
- import type { Agent } from 'http';
2
+ import { Agent } from 'http';
3
3
  import { BodyPonyfillInit, PonyfillBody, PonyfillBodyOptions } from './Body.js';
4
4
  import { PonyfillHeadersInit } from './Headers.js';
5
5
  export type RequestPonyfillInit = PonyfillBodyOptions & Omit<RequestInit, 'body' | 'headers'> & {
@@ -8,10 +8,10 @@ export type RequestPonyfillInit = PonyfillBodyOptions & Omit<RequestInit, 'body'
8
8
  headersSerializer?: HeadersSerializer;
9
9
  agent?: Agent;
10
10
  };
11
- type HeadersSerializer = (headers: Headers) => Record<string, string>;
11
+ type HeadersSerializer = (headers: Headers, onContentLength?: (contentLength: string) => void) => string[];
12
12
  export declare class PonyfillRequest<TJSON = any> extends PonyfillBody<TJSON> implements Request {
13
13
  constructor(input: RequestInfo | URL, options?: RequestPonyfillInit);
14
- headersSerializer: HeadersSerializer;
14
+ headersSerializer?: HeadersSerializer;
15
15
  cache: RequestCache;
16
16
  credentials: RequestCredentials;
17
17
  destination: RequestDestination;
@@ -25,9 +25,9 @@ export declare class PonyfillRequest<TJSON = any> extends PonyfillBody<TJSON> im
25
25
  referrer: string;
26
26
  referrerPolicy: ReferrerPolicy;
27
27
  url: string;
28
+ agent?: Agent;
28
29
  private _signal;
29
30
  get signal(): AbortSignal;
30
- agent?: Agent;
31
- clone(): PonyfillRequest;
31
+ clone(): PonyfillRequest<TJSON>;
32
32
  }
33
33
  export {};
@@ -0,0 +1,3 @@
1
+ import { PonyfillRequest } from './Request.cjs';
2
+ import { PonyfillResponse } from './Response.cjs';
3
+ export declare function fetchCurl<TResponseJSON = any, TRequestJSON = any>(fetchRequest: PonyfillRequest<TRequestJSON>): Promise<PonyfillResponse<TResponseJSON>>;
@@ -0,0 +1,3 @@
1
+ import { PonyfillRequest } from './Request.js';
2
+ import { PonyfillResponse } from './Response.js';
3
+ export declare function fetchCurl<TResponseJSON = any, TRequestJSON = any>(fetchRequest: PonyfillRequest<TRequestJSON>): Promise<PonyfillResponse<TResponseJSON>>;
@@ -0,0 +1,3 @@
1
+ import { PonyfillRequest } from './Request.cjs';
2
+ import { PonyfillResponse } from './Response.cjs';
3
+ export declare function fetchNodeHttp<TResponseJSON = any, TRequestJSON = any>(fetchRequest: PonyfillRequest<TRequestJSON>): Promise<PonyfillResponse<TResponseJSON>>;
@@ -0,0 +1,3 @@
1
+ import { PonyfillRequest } from './Request.js';
2
+ import { PonyfillResponse } from './Response.js';
3
+ export declare function fetchNodeHttp<TResponseJSON = any, TRequestJSON = any>(fetchRequest: PonyfillRequest<TRequestJSON>): Promise<PonyfillResponse<TResponseJSON>>;
@@ -1,2 +1,3 @@
1
1
  export declare function getHeadersObj(headers: Headers): Record<string, string>;
2
2
  export declare function uint8ArrayToArrayBuffer(uint8array: Uint8Array): ArrayBuffer;
3
+ export declare function defaultHeadersSerializer(headers: Headers, onContentLength?: (value: string) => void): string[];
@@ -1,2 +1,3 @@
1
1
  export declare function getHeadersObj(headers: Headers): Record<string, string>;
2
2
  export declare function uint8ArrayToArrayBuffer(uint8array: Uint8Array): ArrayBuffer;
3
+ export declare function defaultHeadersSerializer(headers: Headers, onContentLength?: (value: string) => void): string[];