@whatwg-node/fetch 0.6.0 → 0.6.2-alpha-20230102102521-2e7c4e5
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 +25 -0
- package/dist/create-node-ponyfill.js +41 -306
- package/dist/global-ponyfill.js +0 -4
- package/package.json +4 -8
- package/tests/getFormDataMethod.spec.ts +52 -30
- package/dist/getFormDataMethod.js +0 -65
- package/dist/handle-file-request.js +0 -7
- package/dist/readableStreamToReadable.js +0 -26
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,30 @@
|
|
|
1
1
|
# @whatwg-node/fetch
|
|
2
2
|
|
|
3
|
+
## 0.6.2-alpha-20230102102521-2e7c4e5
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#154](https://github.com/ardatan/whatwg-node/pull/154) [`02b2895`](https://github.com/ardatan/whatwg-node/commit/02b289550751fd2a8317666ecad101037a4e6551) Thanks [@ardatan](https://github.com/ardatan)! - dependencies updates:
|
|
8
|
+
|
|
9
|
+
- Updated dependency [`web-streams-polyfill@^3.2.1` ↗︎](https://www.npmjs.com/package/web-streams-polyfill/v/3.2.1) (from `^3.2.0`, in `dependencies`)
|
|
10
|
+
- Added dependency [`@whatwg-node/node-fetch@0.0.0` ↗︎](https://www.npmjs.com/package/@whatwg-node/node-fetch/v/0.0.0) (to `dependencies`)
|
|
11
|
+
- Removed dependency [`abort-controller@^3.0.0` ↗︎](https://www.npmjs.com/package/abort-controller/v/3.0.0) (from `dependencies`)
|
|
12
|
+
- Removed dependency [`form-data-encoder@^1.7.1` ↗︎](https://www.npmjs.com/package/form-data-encoder/v/1.7.1) (from `dependencies`)
|
|
13
|
+
- Removed dependency [`formdata-node@^4.3.1` ↗︎](https://www.npmjs.com/package/formdata-node/v/4.3.1) (from `dependencies`)
|
|
14
|
+
- Removed dependency [`node-fetch@^2.6.7` ↗︎](https://www.npmjs.com/package/node-fetch/v/2.6.7) (from `dependencies`)
|
|
15
|
+
- Removed dependency [`undici@^5.12.0` ↗︎](https://www.npmjs.com/package/undici/v/5.12.0) (from `dependencies`)
|
|
16
|
+
|
|
17
|
+
- [#154](https://github.com/ardatan/whatwg-node/pull/154) [`02b2895`](https://github.com/ardatan/whatwg-node/commit/02b289550751fd2a8317666ecad101037a4e6551) Thanks [@ardatan](https://github.com/ardatan)! - New Fetch API implementation for Node
|
|
18
|
+
|
|
19
|
+
- Updated dependencies [[`02b2895`](https://github.com/ardatan/whatwg-node/commit/02b289550751fd2a8317666ecad101037a4e6551)]:
|
|
20
|
+
- @whatwg-node/node-fetch@0.0.1-alpha-20230102102521-2e7c4e5
|
|
21
|
+
|
|
22
|
+
## 0.6.1
|
|
23
|
+
|
|
24
|
+
### Patch Changes
|
|
25
|
+
|
|
26
|
+
- [`9752cca`](https://github.com/ardatan/whatwg-node/commit/9752cca54e7636114d87849ca9c7eb9be3d9dba8) Thanks [@ardatan](https://github.com/ardatan)! - Remove unnecessary ponyfill for the envs supporting Fetch by default
|
|
27
|
+
|
|
3
28
|
## 0.6.0
|
|
4
29
|
|
|
5
30
|
### Minor Changes
|
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
const http2 = require('http2')
|
|
2
|
-
const handleFileRequest = require("./handle-file-request");
|
|
3
|
-
const readableStreamToReadable = require("./readableStreamToReadable");
|
|
4
|
-
|
|
5
1
|
module.exports = function createNodePonyfill(opts = {}) {
|
|
6
2
|
|
|
7
3
|
// Bun already has a Fetch API
|
|
@@ -9,75 +5,64 @@ module.exports = function createNodePonyfill(opts = {}) {
|
|
|
9
5
|
return globalThis;
|
|
10
6
|
}
|
|
11
7
|
|
|
12
|
-
const
|
|
8
|
+
const newNodeFetch = require('@whatwg-node/node-fetch');
|
|
13
9
|
|
|
14
|
-
|
|
15
|
-
ponyfills.fetch = globalThis.fetch;
|
|
16
|
-
ponyfills.Headers = globalThis.Headers;
|
|
17
|
-
ponyfills.Request = globalThis.Request;
|
|
18
|
-
ponyfills.Response = globalThis.Response;
|
|
19
|
-
ponyfills.FormData = globalThis.FormData;
|
|
20
|
-
ponyfills.File = globalThis.File;
|
|
21
|
-
}
|
|
10
|
+
const ponyfills = {};
|
|
22
11
|
|
|
23
|
-
ponyfills.
|
|
24
|
-
ponyfills.
|
|
12
|
+
ponyfills.fetch = newNodeFetch.fetch;
|
|
13
|
+
ponyfills.Request = newNodeFetch.Request;
|
|
14
|
+
ponyfills.Response = newNodeFetch.Response;
|
|
15
|
+
ponyfills.Headers = newNodeFetch.Headers;
|
|
16
|
+
ponyfills.FormData = newNodeFetch.FormData;
|
|
17
|
+
ponyfills.AbortController = newNodeFetch.AbortController;
|
|
18
|
+
ponyfills.ReadableStream = newNodeFetch.ReadableStream;
|
|
25
19
|
ponyfills.WritableStream = globalThis.WritableStream;
|
|
26
20
|
ponyfills.TransformStream = globalThis.TransformStream;
|
|
27
|
-
ponyfills.Blob = globalThis.Blob;
|
|
28
|
-
ponyfills.crypto = globalThis.crypto;
|
|
29
|
-
ponyfills.URLPattern = globalThis.URLPattern;
|
|
30
|
-
|
|
31
|
-
if (!ponyfills.AbortController) {
|
|
32
|
-
const abortControllerModule = require("abort-controller");
|
|
33
|
-
ponyfills.AbortController =
|
|
34
|
-
abortControllerModule.default || abortControllerModule;
|
|
35
|
-
}
|
|
36
21
|
|
|
37
|
-
if (!ponyfills.
|
|
38
|
-
const bufferModule = require('buffer')
|
|
39
|
-
ponyfills.Blob = bufferModule.Blob;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
if (!ponyfills.Blob) {
|
|
43
|
-
const formDataModule = require("formdata-node");
|
|
44
|
-
ponyfills.Blob = formDataModule.Blob
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
if (!ponyfills.ReadableStream) {
|
|
22
|
+
if (!ponyfills.WritableStream) {
|
|
48
23
|
try {
|
|
49
24
|
const streamsWeb = require("stream/web");
|
|
50
|
-
|
|
51
|
-
ponyfills.ReadableStream = streamsWeb.ReadableStream;
|
|
52
25
|
ponyfills.WritableStream = streamsWeb.WritableStream;
|
|
53
26
|
ponyfills.TransformStream = streamsWeb.TransformStream;
|
|
54
27
|
} catch (e) {
|
|
55
28
|
const streamsWeb = require("web-streams-polyfill/ponyfill");
|
|
56
|
-
ponyfills.ReadableStream = streamsWeb.ReadableStream;
|
|
57
29
|
ponyfills.WritableStream = streamsWeb.WritableStream;
|
|
58
30
|
ponyfills.TransformStream = streamsWeb.TransformStream;
|
|
59
31
|
}
|
|
60
32
|
}
|
|
61
33
|
|
|
62
|
-
ponyfills.
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
ponyfills.
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
34
|
+
ponyfills.Blob = newNodeFetch.Blob;
|
|
35
|
+
ponyfills.File = newNodeFetch.File;
|
|
36
|
+
ponyfills.crypto = globalThis.crypto;
|
|
37
|
+
ponyfills.btoa = newNodeFetch.btoa;
|
|
38
|
+
ponyfills.TextEncoder = newNodeFetch.TextEncoder;
|
|
39
|
+
ponyfills.TextDecoder = newNodeFetch.TextDecoder;
|
|
40
|
+
ponyfills.AbortError = newNodeFetch.AbortError;
|
|
41
|
+
ponyfills.AbortSignal = newNodeFetch.AbortSignal;
|
|
42
|
+
|
|
43
|
+
if (opts.formDataLimits) {
|
|
44
|
+
ponyfills.Body = class Body extends newNodeFetch.Body {
|
|
45
|
+
constructor(body, userOpts) {
|
|
46
|
+
super(body, {
|
|
47
|
+
formDataLimits: opts.formDataLimits,
|
|
48
|
+
...userOpts,
|
|
49
|
+
});
|
|
73
50
|
}
|
|
74
51
|
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
52
|
+
ponyfills.Request = class Request extends newNodeFetch.Request {
|
|
53
|
+
constructor(input, userOpts) {
|
|
54
|
+
super(input, {
|
|
55
|
+
formDataLimits: opts.formDataLimits,
|
|
56
|
+
...userOpts,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
ponyfills.Response = class Response extends newNodeFetch.Response {
|
|
61
|
+
constructor(body, userOpts) {
|
|
62
|
+
super(body, {
|
|
63
|
+
formDataLimits: opts.formDataLimits,
|
|
64
|
+
...userOpts,
|
|
65
|
+
});
|
|
81
66
|
}
|
|
82
67
|
}
|
|
83
68
|
}
|
|
@@ -91,261 +76,11 @@ module.exports = function createNodePonyfill(opts = {}) {
|
|
|
91
76
|
const cryptoPonyfill = require('@peculiar/webcrypto');
|
|
92
77
|
ponyfills.crypto = new cryptoPonyfill.Crypto();
|
|
93
78
|
}
|
|
94
|
-
|
|
79
|
+
|
|
95
80
|
if (!ponyfills.URLPattern) {
|
|
96
81
|
const urlPatternModule = require('urlpattern-polyfill');
|
|
97
82
|
ponyfills.URLPattern = urlPatternModule.URLPattern;
|
|
98
83
|
}
|
|
99
84
|
|
|
100
|
-
// If any of classes of Fetch API is missing, we need to ponyfill them.
|
|
101
|
-
if (!ponyfills.fetch ||
|
|
102
|
-
!ponyfills.Request ||
|
|
103
|
-
!ponyfills.Headers ||
|
|
104
|
-
!ponyfills.Response ||
|
|
105
|
-
!ponyfills.FormData ||
|
|
106
|
-
!ponyfills.File ||
|
|
107
|
-
opts.useNodeFetch) {
|
|
108
|
-
|
|
109
|
-
const [
|
|
110
|
-
nodeMajorStr,
|
|
111
|
-
nodeMinorStr
|
|
112
|
-
] = process.versions.node.split('.');
|
|
113
|
-
|
|
114
|
-
const nodeMajor = parseInt(nodeMajorStr);
|
|
115
|
-
const nodeMinor = parseInt(nodeMinorStr);
|
|
116
|
-
const getFormDataMethod = require('./getFormDataMethod');
|
|
117
|
-
|
|
118
|
-
if (!opts.useNodeFetch && (nodeMajor > 16 || (nodeMajor === 16 && nodeMinor >= 5))) {
|
|
119
|
-
const undici = require("undici");
|
|
120
|
-
|
|
121
|
-
if (!ponyfills.Headers) {
|
|
122
|
-
ponyfills.Headers = undici.Headers;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
const streams = require("stream");
|
|
126
|
-
|
|
127
|
-
const OriginalRequest = ponyfills.Request || undici.Request;
|
|
128
|
-
|
|
129
|
-
class Request extends OriginalRequest {
|
|
130
|
-
constructor(requestOrUrl, options) {
|
|
131
|
-
if (typeof requestOrUrl === "string" || requestOrUrl instanceof URL) {
|
|
132
|
-
if (options != null && typeof options === "object" && !options.duplex) {
|
|
133
|
-
options.duplex = 'half';
|
|
134
|
-
}
|
|
135
|
-
super(requestOrUrl, options);
|
|
136
|
-
const contentType = this.headers.get("content-type");
|
|
137
|
-
if (contentType && contentType.startsWith("multipart/form-data")) {
|
|
138
|
-
this.headers.set("content-type", contentType.split(', ')[0]);
|
|
139
|
-
}
|
|
140
|
-
} else {
|
|
141
|
-
super(requestOrUrl);
|
|
142
|
-
}
|
|
143
|
-
this.formData = getFormDataMethod(undici.File, opts.formDataLimits);
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
ponyfills.Request = Request;
|
|
148
|
-
|
|
149
|
-
const originalFetch = ponyfills.fetch || undici.fetch;
|
|
150
|
-
|
|
151
|
-
const fetch = function (requestOrUrl, options) {
|
|
152
|
-
if (typeof requestOrUrl === "string" || requestOrUrl instanceof URL) {
|
|
153
|
-
if (options != null && typeof options === "object" && !options.duplex) {
|
|
154
|
-
options.duplex = 'half';
|
|
155
|
-
}
|
|
156
|
-
// We cannot use our ctor because it leaks on Node 18's global fetch
|
|
157
|
-
return originalFetch(requestOrUrl, options);
|
|
158
|
-
}
|
|
159
|
-
if (requestOrUrl.url.startsWith('file:')) {
|
|
160
|
-
return handleFileRequest(requestOrUrl.url, ponyfills.Response);
|
|
161
|
-
}
|
|
162
|
-
return originalFetch(requestOrUrl);
|
|
163
|
-
};
|
|
164
|
-
|
|
165
|
-
ponyfills.fetch = fetch;
|
|
166
|
-
|
|
167
|
-
if (!ponyfills.Response) {
|
|
168
|
-
ponyfills.Response = undici.Response;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
if (!ponyfills.FormData) {
|
|
172
|
-
ponyfills.FormData = undici.FormData;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
if (!ponyfills.File) {
|
|
176
|
-
ponyfills.File = undici.File
|
|
177
|
-
}
|
|
178
|
-
} else {
|
|
179
|
-
const nodeFetch = require("node-fetch");
|
|
180
|
-
const realFetch = ponyfills.fetch || nodeFetch.default || nodeFetch;
|
|
181
|
-
if (!ponyfills.Headers) {
|
|
182
|
-
ponyfills.Headers = nodeFetch.Headers;
|
|
183
|
-
// Sveltekit
|
|
184
|
-
if (globalThis.Headers && nodeMajor < 18) {
|
|
185
|
-
Object.defineProperty(globalThis.Headers, Symbol.hasInstance, {
|
|
186
|
-
value(obj) {
|
|
187
|
-
return obj && obj.get && obj.set && obj.delete && obj.has && obj.append;
|
|
188
|
-
},
|
|
189
|
-
configurable: true,
|
|
190
|
-
})
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
const formDataEncoderModule = require("form-data-encoder");
|
|
194
|
-
const streams = require("stream");
|
|
195
|
-
const formDataModule = require("formdata-node");
|
|
196
|
-
if (!ponyfills.FormData) {
|
|
197
|
-
ponyfills.FormData = formDataModule.FormData
|
|
198
|
-
}
|
|
199
|
-
if (!ponyfills.File) {
|
|
200
|
-
ponyfills.File = formDataModule.File
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
const OriginalRequest = ponyfills.Request || nodeFetch.Request;
|
|
204
|
-
|
|
205
|
-
class Request extends OriginalRequest {
|
|
206
|
-
constructor(requestOrUrl, options) {
|
|
207
|
-
if (typeof requestOrUrl === "string" || requestOrUrl instanceof URL) {
|
|
208
|
-
// Support schemaless URIs on the server for parity with the browser.
|
|
209
|
-
// Ex: //github.com/ -> https://github.com/
|
|
210
|
-
if (/^\/\//.test(requestOrUrl.toString())) {
|
|
211
|
-
requestOrUrl = "https:" + requestOrUrl.toString();
|
|
212
|
-
}
|
|
213
|
-
let method = (options || {}).method;
|
|
214
|
-
const headers = {};
|
|
215
|
-
if ('headers' in (options || {})) {
|
|
216
|
-
let isHttp2 = false;
|
|
217
|
-
for (const [key, value] of Object.entries(options.headers)) {
|
|
218
|
-
if (key.startsWith(':')) {
|
|
219
|
-
// omit http2 headers
|
|
220
|
-
isHttp2 = true;
|
|
221
|
-
} else {
|
|
222
|
-
headers[key] = value
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
if (isHttp2) {
|
|
226
|
-
// translate http2 if applicable
|
|
227
|
-
method = options.headers[http2.constants.HTTP2_HEADER_METHOD];
|
|
228
|
-
const scheme = options.headers[http2.constants.HTTP2_HEADER_SCHEME];
|
|
229
|
-
const authority = options.headers[http2.constants.HTTP2_HEADER_AUTHORITY];
|
|
230
|
-
const path = options.headers[http2.constants.HTTP2_HEADER_PATH];
|
|
231
|
-
headers.host = authority;
|
|
232
|
-
requestOrUrl = `${scheme}://${authority}${path}`
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
const fixedOptions = {
|
|
236
|
-
...options,
|
|
237
|
-
method,
|
|
238
|
-
headers,
|
|
239
|
-
};
|
|
240
|
-
fixedOptions.headers = new ponyfills.Headers(fixedOptions.headers || {});
|
|
241
|
-
fixedOptions.headers.set('Connection', 'keep-alive');
|
|
242
|
-
if (fixedOptions.body != null) {
|
|
243
|
-
if (fixedOptions.body[Symbol.toStringTag] === 'FormData') {
|
|
244
|
-
const encoder = new formDataEncoderModule.FormDataEncoder(fixedOptions.body)
|
|
245
|
-
for (const headerKey in encoder.headers) {
|
|
246
|
-
fixedOptions.headers.set(headerKey, encoder.headers[headerKey])
|
|
247
|
-
}
|
|
248
|
-
fixedOptions.body = streams.Readable.from(encoder);
|
|
249
|
-
} else if (fixedOptions.body[Symbol.toStringTag] === 'ReadableStream') {
|
|
250
|
-
fixedOptions.body = readableStreamToReadable(fixedOptions.body);
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
super(requestOrUrl, fixedOptions);
|
|
254
|
-
} else {
|
|
255
|
-
super(requestOrUrl);
|
|
256
|
-
}
|
|
257
|
-
this.formData = getFormDataMethod(formDataModule.File, opts.formDataLimits);
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
ponyfills.Request = Request;
|
|
261
|
-
const fetch = function (requestOrUrl, options) {
|
|
262
|
-
if (typeof requestOrUrl === "string" || requestOrUrl instanceof URL) {
|
|
263
|
-
return fetch(new Request(requestOrUrl, options));
|
|
264
|
-
}
|
|
265
|
-
if (requestOrUrl.url.startsWith('file:')) {
|
|
266
|
-
return handleFileRequest(requestOrUrl.url, ponyfills.Response);
|
|
267
|
-
}
|
|
268
|
-
const abortCtrl = new ponyfills.AbortController();
|
|
269
|
-
|
|
270
|
-
return realFetch(requestOrUrl, {
|
|
271
|
-
...options,
|
|
272
|
-
signal: abortCtrl.signal
|
|
273
|
-
}).then(res => {
|
|
274
|
-
return new Proxy(res, {
|
|
275
|
-
get(target, prop, receiver) {
|
|
276
|
-
if (prop === 'body') {
|
|
277
|
-
return new Proxy(res.body, {
|
|
278
|
-
get(target, prop, receiver) {
|
|
279
|
-
if (prop === Symbol.asyncIterator) {
|
|
280
|
-
return () => {
|
|
281
|
-
const originalAsyncIterator = target[Symbol.asyncIterator]();
|
|
282
|
-
return {
|
|
283
|
-
next() {
|
|
284
|
-
return originalAsyncIterator.next();
|
|
285
|
-
},
|
|
286
|
-
return() {
|
|
287
|
-
abortCtrl.abort();
|
|
288
|
-
return originalAsyncIterator.return();
|
|
289
|
-
},
|
|
290
|
-
throw(error) {
|
|
291
|
-
abortCtrl.abort(error);
|
|
292
|
-
return originalAsyncIterator.throw(error);
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
return Reflect.get(target, prop, receiver);
|
|
298
|
-
}
|
|
299
|
-
})
|
|
300
|
-
}
|
|
301
|
-
return Reflect.get(target, prop, receiver);
|
|
302
|
-
}
|
|
303
|
-
})
|
|
304
|
-
});
|
|
305
|
-
};
|
|
306
|
-
|
|
307
|
-
ponyfills.fetch = fetch;
|
|
308
|
-
|
|
309
|
-
const OriginalResponse = ponyfills.Response || nodeFetch.Response;
|
|
310
|
-
ponyfills.Response = function Response(body, init) {
|
|
311
|
-
if (body != null && body[Symbol.toStringTag] === 'ReadableStream') {
|
|
312
|
-
const actualBody = readableStreamToReadable(body);
|
|
313
|
-
// Polyfill ReadableStream is not working well with node-fetch's Response
|
|
314
|
-
return new OriginalResponse(actualBody, init);
|
|
315
|
-
}
|
|
316
|
-
return new OriginalResponse(body, init);
|
|
317
|
-
};
|
|
318
|
-
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
if (!ponyfills.Response.redirect) {
|
|
323
|
-
ponyfills.Response.redirect = function (url, status = 302) {
|
|
324
|
-
return new ponyfills.Response(null, {
|
|
325
|
-
status,
|
|
326
|
-
headers: {
|
|
327
|
-
Location: url,
|
|
328
|
-
},
|
|
329
|
-
});
|
|
330
|
-
};
|
|
331
|
-
}
|
|
332
|
-
if (!ponyfills.Response.json) {
|
|
333
|
-
ponyfills.Response.json = function (data, init = {}) {
|
|
334
|
-
return new ponyfills.Response(JSON.stringify(data), {
|
|
335
|
-
...init,
|
|
336
|
-
headers: {
|
|
337
|
-
"Content-Type": "application/json",
|
|
338
|
-
...init.headers,
|
|
339
|
-
},
|
|
340
|
-
});
|
|
341
|
-
};
|
|
342
|
-
}
|
|
343
|
-
if (!ponyfills.Response.error) {
|
|
344
|
-
ponyfills.Response.error = function () {
|
|
345
|
-
return new ponyfills.Response(null, {
|
|
346
|
-
status: 500,
|
|
347
|
-
});
|
|
348
|
-
};
|
|
349
|
-
}
|
|
350
85
|
return ponyfills;
|
|
351
86
|
}
|
package/dist/global-ponyfill.js
CHANGED
|
@@ -14,8 +14,4 @@ module.exports.btoa = globalThis.btoa;
|
|
|
14
14
|
module.exports.TextEncoder = globalThis.TextEncoder;
|
|
15
15
|
module.exports.TextDecoder = globalThis.TextDecoder;
|
|
16
16
|
module.exports.URLPattern = globalThis.URLPattern;
|
|
17
|
-
if (!module.exports.URLPattern) {
|
|
18
|
-
const urlPatternModule = require('urlpattern-polyfill');
|
|
19
|
-
module.exports.URLPattern = urlPatternModule.URLPattern;
|
|
20
|
-
}
|
|
21
17
|
module.exports.createFetch = () => globalThis;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@whatwg-node/fetch",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.2-alpha-20230102102521-2e7c4e5",
|
|
4
4
|
"description": "Cross Platform Smart Fetch Ponyfill",
|
|
5
5
|
"author": "Arda TANRIKULU <ardatanrikulu@gmail.com>",
|
|
6
6
|
"repository": {
|
|
@@ -19,14 +19,10 @@
|
|
|
19
19
|
},
|
|
20
20
|
"dependencies": {
|
|
21
21
|
"@peculiar/webcrypto": "^1.4.0",
|
|
22
|
-
"
|
|
22
|
+
"@whatwg-node/node-fetch": "0.0.1-alpha-20230102102521-2e7c4e5",
|
|
23
23
|
"busboy": "^1.6.0",
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
"node-fetch": "^2.6.7",
|
|
27
|
-
"undici": "^5.12.0",
|
|
28
|
-
"web-streams-polyfill": "^3.2.0",
|
|
29
|
-
"urlpattern-polyfill": "^6.0.2"
|
|
24
|
+
"urlpattern-polyfill": "^6.0.2",
|
|
25
|
+
"web-streams-polyfill": "^3.2.1"
|
|
30
26
|
},
|
|
31
27
|
"publishConfig": {
|
|
32
28
|
"access": "public"
|
|
@@ -1,36 +1,58 @@
|
|
|
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
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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,
|
|
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!');
|
|
33
|
+
});
|
|
34
|
+
it('should handle file limits', async () => {
|
|
35
|
+
const limitedFetchAPI = fetchAPI.createFetch({
|
|
36
|
+
formDataLimits: {
|
|
37
|
+
fileSize: 1,
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
const formData = new limitedFetchAPI.FormData();
|
|
41
|
+
const greetingsFile = new limitedFetchAPI.File(['Hello world!'], 'greetings.txt', { type: 'text/plain' });
|
|
42
|
+
formData.append('greetings', greetingsFile);
|
|
43
|
+
const proxyRequest = new limitedFetchAPI.Request('http://localhost:8080', {
|
|
44
|
+
method: 'POST',
|
|
45
|
+
body: formData,
|
|
46
|
+
});
|
|
47
|
+
const formDataInText = await proxyRequest.text();
|
|
48
|
+
const contentType = proxyRequest.headers.get('content-type')!;
|
|
49
|
+
const requestWillParse = new limitedFetchAPI.Request('http://localhost:8080', {
|
|
50
|
+
method: 'POST',
|
|
51
|
+
body: formDataInText,
|
|
52
|
+
headers: {
|
|
53
|
+
'content-type': contentType,
|
|
54
|
+
},
|
|
34
55
|
});
|
|
56
|
+
await expect(() => requestWillParse.formData()).rejects.toThrowError('File size limit exceeded: 1 bytes');
|
|
35
57
|
});
|
|
36
58
|
});
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
const busboy = require('busboy');
|
|
2
|
-
const { resolve } = require('path');
|
|
3
|
-
const streams = require("stream");
|
|
4
|
-
|
|
5
|
-
module.exports = function getFormDataMethod(File, limits) {
|
|
6
|
-
|
|
7
|
-
return function formData() {
|
|
8
|
-
if (this.body == null) {
|
|
9
|
-
return null;
|
|
10
|
-
}
|
|
11
|
-
const contentType = this.headers.get('Content-Type');
|
|
12
|
-
const nodeReadable = this.body.on ? this.body : streams.Readable.from(this.body);
|
|
13
|
-
const bb = busboy({
|
|
14
|
-
headers: {
|
|
15
|
-
'content-type': contentType
|
|
16
|
-
},
|
|
17
|
-
limits,
|
|
18
|
-
defParamCharset: 'utf-8'
|
|
19
|
-
});
|
|
20
|
-
return new Promise((resolve, reject) => {
|
|
21
|
-
const formData = new Map();
|
|
22
|
-
bb.on('field', (name, value, { nameTruncated, valueTruncated }) => {
|
|
23
|
-
if (nameTruncated) {
|
|
24
|
-
reject(new Error(`Field name size exceeded: ${limits.fieldNameSize} bytes`));
|
|
25
|
-
}
|
|
26
|
-
if (valueTruncated) {
|
|
27
|
-
reject(new Error(`Field value size exceeded: ${limits.fieldSize} bytes`));
|
|
28
|
-
}
|
|
29
|
-
formData.set(name, value)
|
|
30
|
-
})
|
|
31
|
-
bb.on('fieldsLimit', () => {
|
|
32
|
-
reject(new Error(`Fields limit exceeded: ${limits.fields}`));
|
|
33
|
-
})
|
|
34
|
-
bb.on('file', (name, fileStream, { filename, mimeType }) => {
|
|
35
|
-
const chunks = [];
|
|
36
|
-
fileStream.on('limit', () => {
|
|
37
|
-
reject(new Error(`File size limit exceeded: ${limits.fileSize} bytes`));
|
|
38
|
-
})
|
|
39
|
-
fileStream.on('data', (chunk) => {
|
|
40
|
-
chunks.push(Buffer.from(chunk));
|
|
41
|
-
})
|
|
42
|
-
fileStream.on('close', () => {
|
|
43
|
-
if (fileStream.truncated) {
|
|
44
|
-
reject(new Error(`File size limit exceeded: ${limits.fileSize} bytes`));
|
|
45
|
-
}
|
|
46
|
-
const file = new File(chunks, filename, { type: mimeType });
|
|
47
|
-
formData.set(name, file);
|
|
48
|
-
});
|
|
49
|
-
})
|
|
50
|
-
bb.on('filesLimit', () => {
|
|
51
|
-
reject(new Error(`Files limit exceeded: ${limits.files}`));
|
|
52
|
-
})
|
|
53
|
-
bb.on('partsLimit', () => {
|
|
54
|
-
reject(new Error(`Parts limit exceeded: ${limits.parts}`));
|
|
55
|
-
})
|
|
56
|
-
bb.on('close', () => {
|
|
57
|
-
resolve(formData);
|
|
58
|
-
});
|
|
59
|
-
bb.on('error', err => {
|
|
60
|
-
reject(err);
|
|
61
|
-
})
|
|
62
|
-
nodeReadable.pipe(bb);
|
|
63
|
-
})
|
|
64
|
-
}
|
|
65
|
-
}
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
const streams = require('stream');
|
|
2
|
-
|
|
3
|
-
module.exports = function readableStreamToReadable(readableStream) {
|
|
4
|
-
const reader = readableStream.getReader();
|
|
5
|
-
return new streams.Readable({
|
|
6
|
-
read() {
|
|
7
|
-
reader.read().then(({ done, value }) => {
|
|
8
|
-
if (done) {
|
|
9
|
-
this.push(null);
|
|
10
|
-
} else {
|
|
11
|
-
this.push(value);
|
|
12
|
-
}
|
|
13
|
-
})
|
|
14
|
-
},
|
|
15
|
-
async destroy(err, callback) {
|
|
16
|
-
try {
|
|
17
|
-
reader.cancel();
|
|
18
|
-
reader.releaseLock();
|
|
19
|
-
await readableStream.cancel();
|
|
20
|
-
callback();
|
|
21
|
-
} catch (error) {
|
|
22
|
-
callback(error);
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
})
|
|
26
|
-
}
|