@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 +1 -3
- package/cjs/fetch.js +17 -110
- package/cjs/fetchCurl.js +81 -0
- package/cjs/fetchNodeHttp.js +104 -0
- package/cjs/utils.js +12 -1
- package/esm/Request.js +1 -3
- package/esm/fetch.js +17 -110
- package/esm/fetchCurl.js +77 -0
- package/esm/fetchNodeHttp.js +100 -0
- package/esm/utils.js +10 -0
- package/package.json +1 -1
- package/typings/Request.d.cts +5 -5
- package/typings/Request.d.ts +5 -5
- package/typings/fetchCurl.d.cts +3 -0
- package/typings/fetchCurl.d.ts +3 -0
- package/typings/fetchNodeHttp.d.cts +3 -0
- package/typings/fetchNodeHttp.d.ts +3 -0
- package/typings/utils.d.cts +1 -0
- package/typings/utils.d.ts +1 -0
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
|
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
|
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.
|
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
|
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
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
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;
|
package/cjs/fetchCurl.js
ADDED
@@ -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
|
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
|
-
|
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.
|
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
|
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
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
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
|
}
|
package/esm/fetchCurl.js
ADDED
@@ -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
package/typings/Request.d.cts
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
/// <reference types="node" />
|
2
|
-
import
|
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) =>
|
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
|
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
|
-
|
31
|
-
clone(): PonyfillRequest;
|
31
|
+
clone(): PonyfillRequest<TJSON>;
|
32
32
|
}
|
33
33
|
export {};
|
package/typings/Request.d.ts
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
/// <reference types="node" />
|
2
|
-
import
|
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) =>
|
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
|
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
|
-
|
31
|
-
clone(): PonyfillRequest;
|
31
|
+
clone(): PonyfillRequest<TJSON>;
|
32
32
|
}
|
33
33
|
export {};
|
package/typings/utils.d.cts
CHANGED
@@ -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[];
|
package/typings/utils.d.ts
CHANGED
@@ -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[];
|