@whatwg-node/node-fetch 0.5.0-alpha-20230710154907-66260af → 0.5.0-alpha-20230710180612-32e574f
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 -2
- package/cjs/fetch.js +10 -101
- package/cjs/fetchCurl.js +79 -0
- package/cjs/fetchNodeHttp.js +104 -0
- package/esm/Request.js +1 -2
- package/esm/fetch.js +10 -78
- package/esm/fetchCurl.js +75 -0
- package/esm/fetchNodeHttp.js +100 -0
- package/package.json +1 -2
- package/typings/Request.d.cts +6 -2
- package/typings/Request.d.ts +6 -2
- 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/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,7 +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
|
44
|
+
this.headersSerializer = requestInit?.headersSerializer;
|
46
45
|
this.url = url || '';
|
47
46
|
this.destination = 'document';
|
48
47
|
this.priority = 'auto';
|
package/cjs/fetch.js
CHANGED
@@ -1,46 +1,21 @@
|
|
1
1
|
"use strict";
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
3
|
-
if (k2 === undefined) k2 = k;
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
7
|
-
}
|
8
|
-
Object.defineProperty(o, k2, desc);
|
9
|
-
}) : (function(o, m, k, k2) {
|
10
|
-
if (k2 === undefined) k2 = k;
|
11
|
-
o[k2] = m[k];
|
12
|
-
}));
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
15
|
-
}) : function(o, v) {
|
16
|
-
o["default"] = v;
|
17
|
-
});
|
18
|
-
var __importStar = (this && this.__importStar) || function (mod) {
|
19
|
-
if (mod && mod.__esModule) return mod;
|
20
|
-
var result = {};
|
21
|
-
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
22
|
-
__setModuleDefault(result, mod);
|
23
|
-
return result;
|
24
|
-
};
|
25
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
26
3
|
exports.fetchPonyfill = void 0;
|
27
|
-
/* eslint-disable @typescript-eslint/no-this-alias */
|
28
4
|
const fs_1 = require("fs");
|
29
|
-
const stream_1 = require("stream");
|
30
5
|
const url_1 = require("url");
|
31
6
|
const Blob_js_1 = require("./Blob.js");
|
32
|
-
const
|
7
|
+
const fetchCurl_js_1 = require("./fetchCurl.js");
|
8
|
+
const fetchNodeHttp_js_1 = require("./fetchNodeHttp.js");
|
33
9
|
const Request_js_1 = require("./Request.js");
|
34
10
|
const Response_js_1 = require("./Response.js");
|
35
|
-
const
|
36
|
-
const utils_js_1 = require("./utils.js");
|
11
|
+
const BASE64_SUFFIX = ';base64';
|
37
12
|
function getResponseForFile(url) {
|
38
13
|
const path = (0, url_1.fileURLToPath)(url);
|
39
14
|
const readable = (0, fs_1.createReadStream)(path);
|
40
15
|
return new Response_js_1.PonyfillResponse(readable);
|
41
16
|
}
|
42
17
|
function getResponseForDataUri(url) {
|
43
|
-
const [mimeType = 'text/plain', ...datas] = url.
|
18
|
+
const [mimeType = 'text/plain', ...datas] = url.split(',');
|
44
19
|
const data = decodeURIComponent(datas.join(','));
|
45
20
|
if (mimeType.endsWith(BASE64_SUFFIX)) {
|
46
21
|
const buffer = Buffer.from(data, 'base64url');
|
@@ -59,89 +34,23 @@ function getResponseForDataUri(url) {
|
|
59
34
|
},
|
60
35
|
});
|
61
36
|
}
|
62
|
-
const BASE64_SUFFIX = ';base64';
|
63
37
|
async function fetchPonyfill(info, init) {
|
64
38
|
if (typeof info === 'string' || 'href' in info) {
|
65
39
|
const ponyfillRequest = new Request_js_1.PonyfillRequest(info, init);
|
66
40
|
return fetchPonyfill(ponyfillRequest);
|
67
41
|
}
|
68
42
|
const fetchRequest = info;
|
69
|
-
|
70
|
-
|
71
|
-
const response = getResponseForDataUri(url);
|
43
|
+
if (fetchRequest.url.startsWith('data:')) {
|
44
|
+
const response = getResponseForDataUri(fetchRequest.url);
|
72
45
|
return Promise.resolve(response);
|
73
46
|
}
|
74
|
-
if (url.
|
47
|
+
if (fetchRequest.url.startsWith('file:')) {
|
75
48
|
const response = getResponseForFile(fetchRequest.url);
|
76
49
|
return Promise.resolve(response);
|
77
50
|
}
|
78
|
-
|
79
|
-
|
80
|
-
? fetchRequest.body
|
81
|
-
: stream_1.Readable.from(fetchRequest.body)
|
82
|
-
: null);
|
83
|
-
const headersSerializer = fetchRequest.headersSerializer || utils_js_1.defaultHeadersSerializer;
|
84
|
-
let size;
|
85
|
-
const curlyHeaders = headersSerializer(fetchRequest.headers, value => {
|
86
|
-
size = Number(value);
|
87
|
-
});
|
88
|
-
let easyNativeBinding;
|
89
|
-
const curlyOptions = {
|
90
|
-
// we want the unparsed binary response to be returned as a stream to us
|
91
|
-
curlyStreamResponse: true,
|
92
|
-
curlyResponseBodyParser: false,
|
93
|
-
curlyProgressCallback() {
|
94
|
-
if (easyNativeBinding == null) {
|
95
|
-
easyNativeBinding = this;
|
96
|
-
}
|
97
|
-
return fetchRequest.signal.aborted ? 1 : 0;
|
98
|
-
},
|
99
|
-
upload: nodeReadable != null,
|
100
|
-
transferEncoding: false,
|
101
|
-
httpTransferDecoding: true,
|
102
|
-
followLocation: fetchRequest.redirect === 'follow',
|
103
|
-
maxRedirs: 20,
|
104
|
-
acceptEncoding: '',
|
105
|
-
curlyStreamUpload: nodeReadable,
|
106
|
-
// this will just make libcurl use their own progress function (which is pretty neat)
|
107
|
-
// curlyProgressCallback() { return CurlProgressFunc.Continue },
|
108
|
-
// verbose: true,
|
109
|
-
httpHeader: curlyHeaders,
|
110
|
-
customRequest: fetchRequest.method,
|
111
|
-
};
|
112
|
-
if (size != null) {
|
113
|
-
curlyOptions.inFileSize = size;
|
51
|
+
if (globalThis.libcurl) {
|
52
|
+
return (0, fetchCurl_js_1.fetchCurl)(fetchRequest);
|
114
53
|
}
|
115
|
-
|
116
|
-
fetchRequest.signal.onabort = () => {
|
117
|
-
if (easyNativeBinding != null) {
|
118
|
-
easyNativeBinding.pause(CurlPause.Recv);
|
119
|
-
}
|
120
|
-
};
|
121
|
-
const curlyResult = await curly(fetchRequest.url, curlyOptions);
|
122
|
-
const responseHeaders = new Headers_js_1.PonyfillHeaders();
|
123
|
-
curlyResult.headers.forEach(headerInfo => {
|
124
|
-
for (const key in headerInfo) {
|
125
|
-
if (key === 'location' || (key === 'Location' && fetchRequest.redirect === 'error')) {
|
126
|
-
throw new Error('redirects are not allowed');
|
127
|
-
}
|
128
|
-
if (key !== 'result') {
|
129
|
-
responseHeaders.append(key, headerInfo[key]);
|
130
|
-
}
|
131
|
-
}
|
132
|
-
});
|
133
|
-
curlyResult.data.on('error', (err) => {
|
134
|
-
if (err.isCurlError && err.code === CurlCode.CURLE_ABORTED_BY_CALLBACK) {
|
135
|
-
// this is expected
|
136
|
-
}
|
137
|
-
else {
|
138
|
-
throw err;
|
139
|
-
}
|
140
|
-
});
|
141
|
-
return new Response_js_1.PonyfillResponse(curlyResult.data, {
|
142
|
-
status: curlyResult.statusCode,
|
143
|
-
headers: responseHeaders,
|
144
|
-
url: info.url,
|
145
|
-
});
|
54
|
+
return (0, fetchNodeHttp_js_1.fetchNodeHttp)(fetchRequest);
|
146
55
|
}
|
147
56
|
exports.fetchPonyfill = fetchPonyfill;
|
package/cjs/fetchCurl.js
ADDED
@@ -0,0 +1,79 @@
|
|
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
|
+
fetchRequest.signal.onabort = () => {
|
49
|
+
if (easyNativeBinding != null) {
|
50
|
+
easyNativeBinding.pause(CurlPause.Recv);
|
51
|
+
}
|
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');
|
59
|
+
}
|
60
|
+
if (key !== 'result') {
|
61
|
+
responseHeaders.append(key, headerInfo[key]);
|
62
|
+
}
|
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,
|
77
|
+
});
|
78
|
+
}
|
79
|
+
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,
|
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/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 { defaultHeadersSerializer } from './utils.js';
|
4
3
|
function isRequest(input) {
|
5
4
|
return input[Symbol.toStringTag] === 'Request';
|
6
5
|
}
|
@@ -39,7 +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
|
41
|
+
this.headersSerializer = requestInit?.headersSerializer;
|
43
42
|
this.url = url || '';
|
44
43
|
this.destination = 'document';
|
45
44
|
this.priority = 'auto';
|
package/esm/fetch.js
CHANGED
@@ -1,20 +1,18 @@
|
|
1
|
-
/* eslint-disable @typescript-eslint/no-this-alias */
|
2
1
|
import { createReadStream } from 'fs';
|
3
|
-
import { Readable } from 'stream';
|
4
2
|
import { fileURLToPath } from 'url';
|
5
3
|
import { PonyfillBlob } from './Blob.js';
|
6
|
-
import {
|
4
|
+
import { fetchCurl } from './fetchCurl.js';
|
5
|
+
import { fetchNodeHttp } from './fetchNodeHttp.js';
|
7
6
|
import { PonyfillRequest } from './Request.js';
|
8
7
|
import { PonyfillResponse } from './Response.js';
|
9
|
-
|
10
|
-
import { defaultHeadersSerializer } from './utils.js';
|
8
|
+
const BASE64_SUFFIX = ';base64';
|
11
9
|
function getResponseForFile(url) {
|
12
10
|
const path = fileURLToPath(url);
|
13
11
|
const readable = createReadStream(path);
|
14
12
|
return new PonyfillResponse(readable);
|
15
13
|
}
|
16
14
|
function getResponseForDataUri(url) {
|
17
|
-
const [mimeType = 'text/plain', ...datas] = url.
|
15
|
+
const [mimeType = 'text/plain', ...datas] = url.split(',');
|
18
16
|
const data = decodeURIComponent(datas.join(','));
|
19
17
|
if (mimeType.endsWith(BASE64_SUFFIX)) {
|
20
18
|
const buffer = Buffer.from(data, 'base64url');
|
@@ -33,88 +31,22 @@ function getResponseForDataUri(url) {
|
|
33
31
|
},
|
34
32
|
});
|
35
33
|
}
|
36
|
-
const BASE64_SUFFIX = ';base64';
|
37
34
|
export async function fetchPonyfill(info, init) {
|
38
35
|
if (typeof info === 'string' || 'href' in info) {
|
39
36
|
const ponyfillRequest = new PonyfillRequest(info, init);
|
40
37
|
return fetchPonyfill(ponyfillRequest);
|
41
38
|
}
|
42
39
|
const fetchRequest = info;
|
43
|
-
|
44
|
-
|
45
|
-
const response = getResponseForDataUri(url);
|
40
|
+
if (fetchRequest.url.startsWith('data:')) {
|
41
|
+
const response = getResponseForDataUri(fetchRequest.url);
|
46
42
|
return Promise.resolve(response);
|
47
43
|
}
|
48
|
-
if (url.
|
44
|
+
if (fetchRequest.url.startsWith('file:')) {
|
49
45
|
const response = getResponseForFile(fetchRequest.url);
|
50
46
|
return Promise.resolve(response);
|
51
47
|
}
|
52
|
-
|
53
|
-
|
54
|
-
? fetchRequest.body
|
55
|
-
: Readable.from(fetchRequest.body)
|
56
|
-
: null);
|
57
|
-
const headersSerializer = fetchRequest.headersSerializer || defaultHeadersSerializer;
|
58
|
-
let size;
|
59
|
-
const curlyHeaders = headersSerializer(fetchRequest.headers, value => {
|
60
|
-
size = Number(value);
|
61
|
-
});
|
62
|
-
let easyNativeBinding;
|
63
|
-
const curlyOptions = {
|
64
|
-
// we want the unparsed binary response to be returned as a stream to us
|
65
|
-
curlyStreamResponse: true,
|
66
|
-
curlyResponseBodyParser: false,
|
67
|
-
curlyProgressCallback() {
|
68
|
-
if (easyNativeBinding == null) {
|
69
|
-
easyNativeBinding = this;
|
70
|
-
}
|
71
|
-
return fetchRequest.signal.aborted ? 1 : 0;
|
72
|
-
},
|
73
|
-
upload: nodeReadable != null,
|
74
|
-
transferEncoding: false,
|
75
|
-
httpTransferDecoding: true,
|
76
|
-
followLocation: fetchRequest.redirect === 'follow',
|
77
|
-
maxRedirs: 20,
|
78
|
-
acceptEncoding: '',
|
79
|
-
curlyStreamUpload: nodeReadable,
|
80
|
-
// this will just make libcurl use their own progress function (which is pretty neat)
|
81
|
-
// curlyProgressCallback() { return CurlProgressFunc.Continue },
|
82
|
-
// verbose: true,
|
83
|
-
httpHeader: curlyHeaders,
|
84
|
-
customRequest: fetchRequest.method,
|
85
|
-
};
|
86
|
-
if (size != null) {
|
87
|
-
curlyOptions.inFileSize = size;
|
48
|
+
if (globalThis.libcurl) {
|
49
|
+
return fetchCurl(fetchRequest);
|
88
50
|
}
|
89
|
-
|
90
|
-
fetchRequest.signal.onabort = () => {
|
91
|
-
if (easyNativeBinding != null) {
|
92
|
-
easyNativeBinding.pause(CurlPause.Recv);
|
93
|
-
}
|
94
|
-
};
|
95
|
-
const curlyResult = await curly(fetchRequest.url, curlyOptions);
|
96
|
-
const responseHeaders = new PonyfillHeaders();
|
97
|
-
curlyResult.headers.forEach(headerInfo => {
|
98
|
-
for (const key in headerInfo) {
|
99
|
-
if (key === 'location' || (key === 'Location' && fetchRequest.redirect === 'error')) {
|
100
|
-
throw new Error('redirects are not allowed');
|
101
|
-
}
|
102
|
-
if (key !== 'result') {
|
103
|
-
responseHeaders.append(key, headerInfo[key]);
|
104
|
-
}
|
105
|
-
}
|
106
|
-
});
|
107
|
-
curlyResult.data.on('error', (err) => {
|
108
|
-
if (err.isCurlError && err.code === CurlCode.CURLE_ABORTED_BY_CALLBACK) {
|
109
|
-
// this is expected
|
110
|
-
}
|
111
|
-
else {
|
112
|
-
throw err;
|
113
|
-
}
|
114
|
-
});
|
115
|
-
return new PonyfillResponse(curlyResult.data, {
|
116
|
-
status: curlyResult.statusCode,
|
117
|
-
headers: responseHeaders,
|
118
|
-
url: info.url,
|
119
|
-
});
|
51
|
+
return fetchNodeHttp(fetchRequest);
|
120
52
|
}
|
package/esm/fetchCurl.js
ADDED
@@ -0,0 +1,75 @@
|
|
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
|
+
fetchRequest.signal.onabort = () => {
|
46
|
+
if (easyNativeBinding != null) {
|
47
|
+
easyNativeBinding.pause(CurlPause.Recv);
|
48
|
+
}
|
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');
|
56
|
+
}
|
57
|
+
if (key !== 'result') {
|
58
|
+
responseHeaders.append(key, headerInfo[key]);
|
59
|
+
}
|
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,
|
74
|
+
});
|
75
|
+
}
|
@@ -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,
|
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/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@whatwg-node/node-fetch",
|
3
|
-
"version": "0.5.0-alpha-
|
3
|
+
"version": "0.5.0-alpha-20230710180612-32e574f",
|
4
4
|
"description": "Fetch API implementation for Node",
|
5
5
|
"sideEffects": false,
|
6
6
|
"dependencies": {
|
@@ -8,7 +8,6 @@
|
|
8
8
|
"busboy": "^1.6.0",
|
9
9
|
"fast-querystring": "^1.1.1",
|
10
10
|
"fast-url-parser": "^1.1.3",
|
11
|
-
"node-libcurl": "^3.0.0",
|
12
11
|
"tslib": "^2.3.1"
|
13
12
|
},
|
14
13
|
"repository": {
|
package/typings/Request.d.cts
CHANGED
@@ -1,14 +1,17 @@
|
|
1
|
+
/// <reference types="node" />
|
2
|
+
import { Agent } from 'http';
|
1
3
|
import { BodyPonyfillInit, PonyfillBody, PonyfillBodyOptions } from './Body.cjs';
|
2
4
|
import { PonyfillHeadersInit } from './Headers.cjs';
|
3
5
|
export type RequestPonyfillInit = PonyfillBodyOptions & Omit<RequestInit, 'body' | 'headers'> & {
|
4
6
|
body?: BodyPonyfillInit | null;
|
5
7
|
headers?: PonyfillHeadersInit;
|
6
8
|
headersSerializer?: HeadersSerializer;
|
9
|
+
agent?: Agent;
|
7
10
|
};
|
8
11
|
type HeadersSerializer = (headers: Headers, onContentLength?: (contentLength: string) => void) => string[];
|
9
12
|
export declare class PonyfillRequest<TJSON = any> extends PonyfillBody<TJSON> implements Request {
|
10
13
|
constructor(input: RequestInfo | URL, options?: RequestPonyfillInit);
|
11
|
-
headersSerializer
|
14
|
+
headersSerializer?: HeadersSerializer;
|
12
15
|
cache: RequestCache;
|
13
16
|
credentials: RequestCredentials;
|
14
17
|
destination: RequestDestination;
|
@@ -22,8 +25,9 @@ export declare class PonyfillRequest<TJSON = any> extends PonyfillBody<TJSON> im
|
|
22
25
|
referrer: string;
|
23
26
|
referrerPolicy: ReferrerPolicy;
|
24
27
|
url: string;
|
28
|
+
agent?: Agent;
|
25
29
|
private _signal;
|
26
30
|
get signal(): AbortSignal;
|
27
|
-
clone(): PonyfillRequest
|
31
|
+
clone(): PonyfillRequest<TJSON>;
|
28
32
|
}
|
29
33
|
export {};
|
package/typings/Request.d.ts
CHANGED
@@ -1,14 +1,17 @@
|
|
1
|
+
/// <reference types="node" />
|
2
|
+
import { Agent } from 'http';
|
1
3
|
import { BodyPonyfillInit, PonyfillBody, PonyfillBodyOptions } from './Body.js';
|
2
4
|
import { PonyfillHeadersInit } from './Headers.js';
|
3
5
|
export type RequestPonyfillInit = PonyfillBodyOptions & Omit<RequestInit, 'body' | 'headers'> & {
|
4
6
|
body?: BodyPonyfillInit | null;
|
5
7
|
headers?: PonyfillHeadersInit;
|
6
8
|
headersSerializer?: HeadersSerializer;
|
9
|
+
agent?: Agent;
|
7
10
|
};
|
8
11
|
type HeadersSerializer = (headers: Headers, onContentLength?: (contentLength: string) => void) => string[];
|
9
12
|
export declare class PonyfillRequest<TJSON = any> extends PonyfillBody<TJSON> implements Request {
|
10
13
|
constructor(input: RequestInfo | URL, options?: RequestPonyfillInit);
|
11
|
-
headersSerializer
|
14
|
+
headersSerializer?: HeadersSerializer;
|
12
15
|
cache: RequestCache;
|
13
16
|
credentials: RequestCredentials;
|
14
17
|
destination: RequestDestination;
|
@@ -22,8 +25,9 @@ export declare class PonyfillRequest<TJSON = any> extends PonyfillBody<TJSON> im
|
|
22
25
|
referrer: string;
|
23
26
|
referrerPolicy: ReferrerPolicy;
|
24
27
|
url: string;
|
28
|
+
agent?: Agent;
|
25
29
|
private _signal;
|
26
30
|
get signal(): AbortSignal;
|
27
|
-
clone(): PonyfillRequest
|
31
|
+
clone(): PonyfillRequest<TJSON>;
|
28
32
|
}
|
29
33
|
export {};
|