@whatwg-node/fetch 0.5.2 → 0.5.4-alpha-20221225155445-dfcb269
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/CHANGELOG.md +12 -0
- package/dist/create-node-ponyfill.js +20 -266
- package/dist/getFormDataMethod.js +0 -1
- package/dist/readableStreamToReadable.js +3 -2
- package/package.json +2 -8
- package/tests/getFormDataMethod.spec.ts +28 -30
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @whatwg-node/fetch
|
|
2
2
|
|
|
3
|
+
## 0.5.4-alpha-20221225155445-dfcb269
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#154](https://github.com/ardatan/whatwg-node/pull/154) [`8324adc`](https://github.com/ardatan/whatwg-node/commit/8324adcc1383eb16fd6d0a6417842e0b8d9b82ba) Thanks [@ardatan](https://github.com/ardatan)! - New Fetch API implementation for Node
|
|
8
|
+
|
|
9
|
+
## 0.5.3
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- [`188ac01`](https://github.com/ardatan/whatwg-node/commit/188ac01dab264ed483dbc3b897e6958b49085922) Thanks [@ardatan](https://github.com/ardatan)! - Fix destroy for Node 18
|
|
14
|
+
|
|
3
15
|
## 0.5.2
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
|
@@ -8,77 +8,28 @@ module.exports = function createNodePonyfill(opts = {}) {
|
|
|
8
8
|
return globalThis;
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
if (!opts.useNodeFetch) {
|
|
14
|
-
ponyfills.fetch = globalThis.fetch;
|
|
15
|
-
ponyfills.Headers = globalThis.Headers;
|
|
16
|
-
ponyfills.Request = globalThis.Request;
|
|
17
|
-
ponyfills.Response = globalThis.Response;
|
|
18
|
-
ponyfills.FormData = globalThis.FormData;
|
|
19
|
-
ponyfills.File = globalThis.File;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
ponyfills.AbortController = globalThis.AbortController;
|
|
23
|
-
ponyfills.ReadableStream = globalThis.ReadableStream;
|
|
24
|
-
ponyfills.WritableStream = globalThis.WritableStream;
|
|
25
|
-
ponyfills.TransformStream = globalThis.TransformStream;
|
|
26
|
-
ponyfills.Blob = globalThis.Blob;
|
|
27
|
-
ponyfills.crypto = globalThis.crypto;
|
|
28
|
-
|
|
29
|
-
if (!ponyfills.AbortController) {
|
|
30
|
-
const abortControllerModule = require("abort-controller");
|
|
31
|
-
ponyfills.AbortController =
|
|
32
|
-
abortControllerModule.default || abortControllerModule;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
if (!ponyfills.Blob) {
|
|
36
|
-
const bufferModule = require('buffer')
|
|
37
|
-
ponyfills.Blob = bufferModule.Blob;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
if (!ponyfills.Blob) {
|
|
41
|
-
const formDataModule = require("formdata-node");
|
|
42
|
-
ponyfills.Blob = formDataModule.Blob
|
|
43
|
-
}
|
|
11
|
+
const newNodeFetch = require('@whatwg-node/node-fetch');
|
|
44
12
|
|
|
45
|
-
|
|
46
|
-
try {
|
|
47
|
-
const streamsWeb = require("stream/web");
|
|
48
|
-
|
|
49
|
-
ponyfills.ReadableStream = streamsWeb.ReadableStream;
|
|
50
|
-
ponyfills.WritableStream = streamsWeb.WritableStream;
|
|
51
|
-
ponyfills.TransformStream = streamsWeb.TransformStream;
|
|
52
|
-
} catch (e) {
|
|
53
|
-
const streamsWeb = require("web-streams-polyfill/ponyfill");
|
|
54
|
-
ponyfills.ReadableStream = streamsWeb.ReadableStream;
|
|
55
|
-
ponyfills.WritableStream = streamsWeb.WritableStream;
|
|
56
|
-
ponyfills.TransformStream = streamsWeb.TransformStream;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
ponyfills.btoa = globalThis.btoa
|
|
61
|
-
if (!ponyfills.btoa) {
|
|
62
|
-
ponyfills.btoa = function btoa(data) {
|
|
63
|
-
return Buffer.from(data, 'binary').toString('base64');
|
|
64
|
-
};
|
|
65
|
-
}
|
|
13
|
+
const ponyfills = {};
|
|
66
14
|
|
|
67
|
-
ponyfills.
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
ponyfills.
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
15
|
+
ponyfills.AbortController = newNodeFetch.AbortController;
|
|
16
|
+
ponyfills.AbortError = newNodeFetch.AbortError;
|
|
17
|
+
ponyfills.AbortSignal = newNodeFetch.AbortSignal;
|
|
18
|
+
ponyfills.Blob = newNodeFetch.Blob;
|
|
19
|
+
ponyfills.Body = newNodeFetch.Body;
|
|
20
|
+
ponyfills.fetch = newNodeFetch.fetch;
|
|
21
|
+
ponyfills.File = newNodeFetch.File;
|
|
22
|
+
ponyfills.FormData = newNodeFetch.FormData;
|
|
23
|
+
ponyfills.Headers = newNodeFetch.Headers;
|
|
24
|
+
ponyfills.ReadableStream = newNodeFetch.ReadableStream;
|
|
25
|
+
ponyfills.Request = newNodeFetch.Request;
|
|
26
|
+
ponyfills.Response = newNodeFetch.Response;
|
|
27
|
+
ponyfills.TextEncoder = newNodeFetch.TextEncoder;
|
|
28
|
+
ponyfills.TextDecoder = newNodeFetch.TextDecoder;
|
|
29
|
+
ponyfills.btoa = newNodeFetch.btoa;
|
|
30
|
+
|
|
31
|
+
const getFormDataMethod = require("./getFormDataMethod");
|
|
32
|
+
ponyfills.Body.prototype.formData = getFormDataMethod(ponyfills.File, opts.formDataLimits)
|
|
82
33
|
|
|
83
34
|
if (!ponyfills.crypto) {
|
|
84
35
|
const cryptoModule = require("crypto");
|
|
@@ -90,203 +41,6 @@ module.exports = function createNodePonyfill(opts = {}) {
|
|
|
90
41
|
ponyfills.crypto = new cryptoPonyfill.Crypto();
|
|
91
42
|
}
|
|
92
43
|
|
|
93
|
-
// If any of classes of Fetch API is missing, we need to ponyfill them.
|
|
94
|
-
if (!ponyfills.fetch ||
|
|
95
|
-
!ponyfills.Request ||
|
|
96
|
-
!ponyfills.Headers ||
|
|
97
|
-
!ponyfills.Response ||
|
|
98
|
-
!ponyfills.FormData ||
|
|
99
|
-
!ponyfills.File ||
|
|
100
|
-
opts.useNodeFetch) {
|
|
101
|
-
|
|
102
|
-
const [
|
|
103
|
-
nodeMajorStr,
|
|
104
|
-
nodeMinorStr
|
|
105
|
-
] = process.versions.node.split('.');
|
|
106
|
-
|
|
107
|
-
const nodeMajor = parseInt(nodeMajorStr);
|
|
108
|
-
const nodeMinor = parseInt(nodeMinorStr);
|
|
109
|
-
const getFormDataMethod = require('./getFormDataMethod');
|
|
110
|
-
|
|
111
|
-
if (!opts.useNodeFetch && (nodeMajor > 16 || (nodeMajor === 16 && nodeMinor >= 5))) {
|
|
112
|
-
const undici = require("undici");
|
|
113
|
-
|
|
114
|
-
if (!ponyfills.Headers) {
|
|
115
|
-
ponyfills.Headers = undici.Headers;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
const streams = require("stream");
|
|
119
|
-
|
|
120
|
-
const OriginalRequest = ponyfills.Request || undici.Request;
|
|
121
|
-
|
|
122
|
-
class Request extends OriginalRequest {
|
|
123
|
-
constructor(requestOrUrl, options) {
|
|
124
|
-
if (typeof requestOrUrl === "string" || requestOrUrl instanceof URL) {
|
|
125
|
-
if (options != null && typeof options === "object" && !options.duplex) {
|
|
126
|
-
options.duplex = 'half';
|
|
127
|
-
}
|
|
128
|
-
super(requestOrUrl, options);
|
|
129
|
-
const contentType = this.headers.get("content-type");
|
|
130
|
-
if (contentType && contentType.startsWith("multipart/form-data")) {
|
|
131
|
-
this.headers.set("content-type", contentType.split(', ')[0]);
|
|
132
|
-
}
|
|
133
|
-
} else {
|
|
134
|
-
super(requestOrUrl);
|
|
135
|
-
}
|
|
136
|
-
this.formData = getFormDataMethod(undici.File, opts.formDataLimits);
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
ponyfills.Request = Request;
|
|
141
|
-
|
|
142
|
-
const originalFetch = ponyfills.fetch || undici.fetch;
|
|
143
|
-
|
|
144
|
-
const fetch = function (requestOrUrl, options) {
|
|
145
|
-
if (typeof requestOrUrl === "string" || requestOrUrl instanceof URL) {
|
|
146
|
-
if (options != null && typeof options === "object" && !options.duplex) {
|
|
147
|
-
options.duplex = 'half';
|
|
148
|
-
}
|
|
149
|
-
// We cannot use our ctor because it leaks on Node 18's global fetch
|
|
150
|
-
return originalFetch(requestOrUrl, options);
|
|
151
|
-
}
|
|
152
|
-
if (requestOrUrl.url.startsWith('file:')) {
|
|
153
|
-
return handleFileRequest(requestOrUrl.url, ponyfills.Response);
|
|
154
|
-
}
|
|
155
|
-
return originalFetch(requestOrUrl);
|
|
156
|
-
};
|
|
157
|
-
|
|
158
|
-
ponyfills.fetch = fetch;
|
|
159
|
-
|
|
160
|
-
if (!ponyfills.Response) {
|
|
161
|
-
ponyfills.Response = undici.Response;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
if (!ponyfills.FormData) {
|
|
165
|
-
ponyfills.FormData = undici.FormData;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
if (!ponyfills.File) {
|
|
169
|
-
ponyfills.File = undici.File
|
|
170
|
-
}
|
|
171
|
-
} else {
|
|
172
|
-
const nodeFetch = require("node-fetch");
|
|
173
|
-
const realFetch = ponyfills.fetch || nodeFetch.default || nodeFetch;
|
|
174
|
-
if (!ponyfills.Headers) {
|
|
175
|
-
ponyfills.Headers = nodeFetch.Headers;
|
|
176
|
-
// Sveltekit
|
|
177
|
-
if (globalThis.Headers && nodeMajor < 18) {
|
|
178
|
-
Object.defineProperty(globalThis.Headers, Symbol.hasInstance, {
|
|
179
|
-
value(obj) {
|
|
180
|
-
return obj && obj.get && obj.set && obj.delete && obj.has && obj.append;
|
|
181
|
-
},
|
|
182
|
-
configurable: true,
|
|
183
|
-
})
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
const formDataEncoderModule = require("form-data-encoder");
|
|
187
|
-
const streams = require("stream");
|
|
188
|
-
const formDataModule = require("formdata-node");
|
|
189
|
-
if (!ponyfills.FormData) {
|
|
190
|
-
ponyfills.FormData = formDataModule.FormData
|
|
191
|
-
}
|
|
192
|
-
if (!ponyfills.File) {
|
|
193
|
-
ponyfills.File = formDataModule.File
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
const OriginalRequest = ponyfills.Request || nodeFetch.Request;
|
|
197
|
-
|
|
198
|
-
class Request extends OriginalRequest {
|
|
199
|
-
constructor(requestOrUrl, options) {
|
|
200
|
-
if (typeof requestOrUrl === "string" || requestOrUrl instanceof URL) {
|
|
201
|
-
// Support schemaless URIs on the server for parity with the browser.
|
|
202
|
-
// Ex: //github.com/ -> https://github.com/
|
|
203
|
-
if (/^\/\//.test(requestOrUrl.toString())) {
|
|
204
|
-
requestOrUrl = "https:" + requestOrUrl.toString();
|
|
205
|
-
}
|
|
206
|
-
const fixedOptions = {
|
|
207
|
-
...options
|
|
208
|
-
};
|
|
209
|
-
fixedOptions.headers = new ponyfills.Headers(fixedOptions.headers || {});
|
|
210
|
-
fixedOptions.headers.set('Connection', 'keep-alive');
|
|
211
|
-
if (fixedOptions.body != null) {
|
|
212
|
-
if (fixedOptions.body[Symbol.toStringTag] === 'FormData') {
|
|
213
|
-
const encoder = new formDataEncoderModule.FormDataEncoder(fixedOptions.body)
|
|
214
|
-
for (const headerKey in encoder.headers) {
|
|
215
|
-
fixedOptions.headers.set(headerKey, encoder.headers[headerKey])
|
|
216
|
-
}
|
|
217
|
-
fixedOptions.body = streams.Readable.from(encoder);
|
|
218
|
-
} else if (fixedOptions.body[Symbol.toStringTag] === 'ReadableStream') {
|
|
219
|
-
fixedOptions.body = readableStreamToReadable(fixedOptions.body);
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
super(requestOrUrl, fixedOptions);
|
|
223
|
-
} else {
|
|
224
|
-
super(requestOrUrl);
|
|
225
|
-
}
|
|
226
|
-
this.formData = getFormDataMethod(formDataModule.File, opts.formDataLimits);
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
ponyfills.Request = Request;
|
|
230
|
-
const fetch = function (requestOrUrl, options) {
|
|
231
|
-
if (typeof requestOrUrl === "string" || requestOrUrl instanceof URL) {
|
|
232
|
-
return fetch(new Request(requestOrUrl, options));
|
|
233
|
-
}
|
|
234
|
-
if (requestOrUrl.url.startsWith('file:')) {
|
|
235
|
-
return handleFileRequest(requestOrUrl.url, ponyfills.Response);
|
|
236
|
-
}
|
|
237
|
-
const abortCtrl = new ponyfills.AbortController();
|
|
238
|
-
|
|
239
|
-
return realFetch(requestOrUrl, {
|
|
240
|
-
...options,
|
|
241
|
-
signal: abortCtrl.signal
|
|
242
|
-
}).then(res => {
|
|
243
|
-
return new Proxy(res, {
|
|
244
|
-
get(target, prop, receiver) {
|
|
245
|
-
if (prop === 'body') {
|
|
246
|
-
return new Proxy(res.body, {
|
|
247
|
-
get(target, prop, receiver) {
|
|
248
|
-
if (prop === Symbol.asyncIterator) {
|
|
249
|
-
return () => {
|
|
250
|
-
const originalAsyncIterator = target[Symbol.asyncIterator]();
|
|
251
|
-
return {
|
|
252
|
-
next() {
|
|
253
|
-
return originalAsyncIterator.next();
|
|
254
|
-
},
|
|
255
|
-
return() {
|
|
256
|
-
abortCtrl.abort();
|
|
257
|
-
return originalAsyncIterator.return();
|
|
258
|
-
},
|
|
259
|
-
throw(error) {
|
|
260
|
-
abortCtrl.abort(error);
|
|
261
|
-
return originalAsyncIterator.throw(error);
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
return Reflect.get(target, prop, receiver);
|
|
267
|
-
}
|
|
268
|
-
})
|
|
269
|
-
}
|
|
270
|
-
return Reflect.get(target, prop, receiver);
|
|
271
|
-
}
|
|
272
|
-
})
|
|
273
|
-
});
|
|
274
|
-
};
|
|
275
|
-
|
|
276
|
-
ponyfills.fetch = fetch;
|
|
277
|
-
|
|
278
|
-
const OriginalResponse = ponyfills.Response || nodeFetch.Response;
|
|
279
|
-
ponyfills.Response = function Response(body, init) {
|
|
280
|
-
if (body != null && body[Symbol.toStringTag] === 'ReadableStream') {
|
|
281
|
-
const actualBody = readableStreamToReadable(body);
|
|
282
|
-
// Polyfill ReadableStream is not working well with node-fetch's Response
|
|
283
|
-
return new OriginalResponse(actualBody, init);
|
|
284
|
-
}
|
|
285
|
-
return new OriginalResponse(body, init);
|
|
286
|
-
};
|
|
287
|
-
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
44
|
|
|
291
45
|
if (!ponyfills.Response.redirect) {
|
|
292
46
|
ponyfills.Response.redirect = function (url, status = 302) {
|
|
@@ -12,13 +12,14 @@ module.exports = function readableStreamToReadable(readableStream) {
|
|
|
12
12
|
}
|
|
13
13
|
})
|
|
14
14
|
},
|
|
15
|
-
async destroy() {
|
|
15
|
+
async destroy(err, callback) {
|
|
16
16
|
try {
|
|
17
17
|
reader.cancel();
|
|
18
18
|
reader.releaseLock();
|
|
19
19
|
await readableStream.cancel();
|
|
20
|
+
callback();
|
|
20
21
|
} catch (error) {
|
|
21
|
-
|
|
22
|
+
callback(error);
|
|
22
23
|
}
|
|
23
24
|
}
|
|
24
25
|
})
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@whatwg-node/fetch",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.4-alpha-20221225155445-dfcb269",
|
|
4
4
|
"description": "Cross Platform Smart Fetch Ponyfill",
|
|
5
5
|
"author": "Arda TANRIKULU <ardatanrikulu@gmail.com>",
|
|
6
6
|
"repository": {
|
|
@@ -19,13 +19,7 @@
|
|
|
19
19
|
},
|
|
20
20
|
"dependencies": {
|
|
21
21
|
"@peculiar/webcrypto": "^1.4.0",
|
|
22
|
-
"
|
|
23
|
-
"busboy": "^1.6.0",
|
|
24
|
-
"form-data-encoder": "^1.7.1",
|
|
25
|
-
"formdata-node": "^4.3.1",
|
|
26
|
-
"node-fetch": "^2.6.7",
|
|
27
|
-
"undici": "^5.12.0",
|
|
28
|
-
"web-streams-polyfill": "^3.2.0"
|
|
22
|
+
"busboy": "^1.6.0"
|
|
29
23
|
},
|
|
30
24
|
"publishConfig": {
|
|
31
25
|
"access": "public"
|
|
@@ -1,36 +1,34 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as fetchAPI from '@whatwg-node/fetch'
|
|
2
2
|
|
|
3
3
|
describe('getFormDataMethod', () => {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
body: formData,
|
|
12
|
-
});
|
|
13
|
-
const formdata = await request.formData();
|
|
14
|
-
expect(formdata.get('greetings')).toBe('Hello world!');
|
|
15
|
-
expect(formdata.get('bye')).toBe('Goodbye world!');
|
|
4
|
+
it('should parse fields correctly', async () => {
|
|
5
|
+
const formData = new fetchAPI.FormData();
|
|
6
|
+
formData.append('greetings', 'Hello world!');
|
|
7
|
+
formData.append('bye', 'Goodbye world!');
|
|
8
|
+
const request = new fetchAPI.Request('http://localhost:8080', {
|
|
9
|
+
method: 'POST',
|
|
10
|
+
body: formData,
|
|
16
11
|
});
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
expect(receivedGreetingsText).toBe('Hello world!');
|
|
31
|
-
const receivedByeFile = formdata.get('bye') as File;
|
|
32
|
-
const receivedByeText = await receivedByeFile.text();
|
|
33
|
-
expect(receivedByeText).toBe('Goodbye world!');
|
|
12
|
+
const formdata = await request.formData();
|
|
13
|
+
expect(formdata.get('greetings')).toBe('Hello world!');
|
|
14
|
+
expect(formdata.get('bye')).toBe('Goodbye world!');
|
|
15
|
+
});
|
|
16
|
+
it('should parse and receive text files correctly', async () => {
|
|
17
|
+
const formData = new fetchAPI.FormData();
|
|
18
|
+
const greetingsFile = new fetchAPI.File(['Hello world!'], 'greetings.txt', { type: 'text/plain' });
|
|
19
|
+
const byeFile = new fetchAPI.File(['Goodbye world!'], 'bye.txt', { type: 'text/plain' });
|
|
20
|
+
formData.append('greetings', greetingsFile);
|
|
21
|
+
formData.append('bye', byeFile);
|
|
22
|
+
const request = new fetchAPI.Request('http://localhost:8080', {
|
|
23
|
+
method: 'POST',
|
|
24
|
+
body: formData,
|
|
34
25
|
});
|
|
26
|
+
const formdata = await request.formData();
|
|
27
|
+
const receivedGreetingsFile = formdata.get('greetings') as File;
|
|
28
|
+
const receivedGreetingsText = await receivedGreetingsFile.text();
|
|
29
|
+
expect(receivedGreetingsText).toBe('Hello world!');
|
|
30
|
+
const receivedByeFile = formdata.get('bye') as File;
|
|
31
|
+
const receivedByeText = await receivedByeFile.text();
|
|
32
|
+
expect(receivedByeText).toBe('Goodbye world!');
|
|
35
33
|
});
|
|
36
34
|
});
|