@gjsify/fetch 0.0.2
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/README.md +9 -0
- package/lib/cjs/body.js +284 -0
- package/lib/cjs/errors/abort-error.js +28 -0
- package/lib/cjs/errors/base.js +36 -0
- package/lib/cjs/errors/fetch-error.js +40 -0
- package/lib/cjs/headers.js +231 -0
- package/lib/cjs/index.js +246 -0
- package/lib/cjs/request.js +306 -0
- package/lib/cjs/response.js +162 -0
- package/lib/cjs/types/index.js +17 -0
- package/lib/cjs/types/system-error.js +16 -0
- package/lib/cjs/utils/blob-from.js +124 -0
- package/lib/cjs/utils/get-search.js +30 -0
- package/lib/cjs/utils/is-redirect.js +26 -0
- package/lib/cjs/utils/is.js +47 -0
- package/lib/cjs/utils/multipart-parser.js +372 -0
- package/lib/cjs/utils/referrer.js +172 -0
- package/lib/esm/body.js +255 -0
- package/lib/esm/errors/abort-error.js +9 -0
- package/lib/esm/errors/base.js +17 -0
- package/lib/esm/errors/fetch-error.js +21 -0
- package/lib/esm/headers.js +202 -0
- package/lib/esm/index.js +224 -0
- package/lib/esm/request.js +281 -0
- package/lib/esm/response.js +133 -0
- package/lib/esm/types/index.js +1 -0
- package/lib/esm/types/system-error.js +1 -0
- package/lib/esm/utils/blob-from.js +101 -0
- package/lib/esm/utils/get-search.js +11 -0
- package/lib/esm/utils/is-redirect.js +7 -0
- package/lib/esm/utils/is.js +28 -0
- package/lib/esm/utils/multipart-parser.js +353 -0
- package/lib/esm/utils/referrer.js +153 -0
- package/package.json +53 -0
- package/src/body.ts +415 -0
- package/src/errors/abort-error.ts +10 -0
- package/src/errors/base.ts +20 -0
- package/src/errors/fetch-error.ts +26 -0
- package/src/headers.ts +279 -0
- package/src/index.spec.ts +13 -0
- package/src/index.ts +367 -0
- package/src/request.ts +396 -0
- package/src/response.ts +197 -0
- package/src/test.mts +6 -0
- package/src/types/index.ts +1 -0
- package/src/types/system-error.ts +11 -0
- package/src/utils/blob-from.ts +168 -0
- package/src/utils/get-search.ts +9 -0
- package/src/utils/is-redirect.ts +11 -0
- package/src/utils/is.ts +88 -0
- package/src/utils/multipart-parser.ts +448 -0
- package/src/utils/referrer.ts +350 -0
- package/test.gjs.js +34758 -0
- package/test.gjs.mjs +53177 -0
- package/test.node.js +1226 -0
- package/test.node.mjs +6294 -0
- package/tsconfig.json +19 -0
- package/tsconfig.types.json +8 -0
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@gjsify/fetch",
|
|
3
|
+
"version": "0.0.2",
|
|
4
|
+
"description": "Web and Node.js fetch module for Gjs",
|
|
5
|
+
"main": "lib/cjs/index.js",
|
|
6
|
+
"module": "lib/esm/index.js",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": {
|
|
11
|
+
"types": "./lib/types/index.d.ts",
|
|
12
|
+
"default": "./lib/esm/index.js"
|
|
13
|
+
},
|
|
14
|
+
"require": {
|
|
15
|
+
"types": "./lib/types/index.d.ts",
|
|
16
|
+
"default": "./lib/cjs/index.js"
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"scripts": {
|
|
21
|
+
"clear": "rm -rf lib tsconfig.tsbuildinfo tsconfig.types.tsbuildinfo || exit 0",
|
|
22
|
+
"print:name": "echo '@gjsify/fetch'",
|
|
23
|
+
"build": "yarn print:name && yarn build:gjsify",
|
|
24
|
+
"build:gjsify": "gjsify build --library 'src/**/*.{ts,js}' --exclude 'src/**/*.spec.{mts,ts}' 'src/test.{mts,ts}'",
|
|
25
|
+
"build:types": "tsc --project tsconfig.types.json",
|
|
26
|
+
"build:test": "yarn build:test:gjs && yarn build:test:node",
|
|
27
|
+
"build:test:gjs": "gjsify build src/test.mts --app gjs --outfile test.gjs.mjs",
|
|
28
|
+
"build:test:node": "gjsify build src/test.mts --app node --outfile test.node.mjs",
|
|
29
|
+
"test": "yarn print:name && yarn build:gjsify && yarn build:test && yarn test:node && yarn test:gjs",
|
|
30
|
+
"test:gjs": "gjs -m test.gjs.mjs",
|
|
31
|
+
"test:node": "node test.node.mjs"
|
|
32
|
+
},
|
|
33
|
+
"keywords": [
|
|
34
|
+
"gjs",
|
|
35
|
+
"node",
|
|
36
|
+
"fetch"
|
|
37
|
+
],
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@gjsify/cli": "^0.0.2",
|
|
40
|
+
"@gjsify/http": "^0.0.2",
|
|
41
|
+
"@gjsify/unit": "^0.0.2",
|
|
42
|
+
"@types/node": "^20.3.1"
|
|
43
|
+
},
|
|
44
|
+
"dependencies": {
|
|
45
|
+
"@gjsify/deno-runtime": "^0.0.2",
|
|
46
|
+
"@gjsify/gio-2.0": "^0.0.2",
|
|
47
|
+
"@gjsify/soup-3.0": "^0.0.2",
|
|
48
|
+
"@types/node-fetch": "^2.6.4",
|
|
49
|
+
"data-uri-to-buffer": "^5.0.1",
|
|
50
|
+
"formdata-polyfill": "^4.0.10",
|
|
51
|
+
"node-fetch": "^3.3.1"
|
|
52
|
+
}
|
|
53
|
+
}
|
package/src/body.ts
ADDED
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
|
|
2
|
+
/**
|
|
3
|
+
* Body.js
|
|
4
|
+
*
|
|
5
|
+
* Body interface provides common methods for Request and Response
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { URLSearchParams } from '@gjsify/deno-runtime/ext/url/00_url';
|
|
9
|
+
import { Blob } from "@gjsify/deno-runtime/ext/web/09_file";
|
|
10
|
+
|
|
11
|
+
import { PassThrough, pipeline as pipelineCb, Readable, Stream, Writable } from 'stream';
|
|
12
|
+
import { ReadableStream as StreamWebReadableStream } from "stream/web";
|
|
13
|
+
import { types, deprecate, promisify } from 'util';
|
|
14
|
+
import { Buffer } from 'buffer';
|
|
15
|
+
|
|
16
|
+
import { FormData, formDataToBlob } from 'formdata-polyfill/esm.min.js';
|
|
17
|
+
|
|
18
|
+
import { FetchError } from './errors/fetch-error.js';
|
|
19
|
+
import { FetchBaseError } from './errors/base.js';
|
|
20
|
+
import { isBlob, isURLSearchParameters } from './utils/is.js';
|
|
21
|
+
|
|
22
|
+
import type { Request } from './request.js';
|
|
23
|
+
import type { Response } from './response.js';
|
|
24
|
+
|
|
25
|
+
const pipeline = promisify(pipelineCb);
|
|
26
|
+
const INTERNALS = Symbol('Body internals');
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Body mixin
|
|
30
|
+
*
|
|
31
|
+
* Ref: https://fetch.spec.whatwg.org/#body
|
|
32
|
+
*
|
|
33
|
+
* @param body Readable stream
|
|
34
|
+
* @param opts Response options
|
|
35
|
+
*/
|
|
36
|
+
export default class Body implements globalThis.Body {
|
|
37
|
+
|
|
38
|
+
[INTERNALS]: {
|
|
39
|
+
body: null | Buffer | Readable | Blob;
|
|
40
|
+
stream: Readable | null;
|
|
41
|
+
boundary: string;
|
|
42
|
+
disturbed: boolean,
|
|
43
|
+
error: null | FetchBaseError;
|
|
44
|
+
} = {
|
|
45
|
+
body: null,
|
|
46
|
+
stream: null,
|
|
47
|
+
boundary: '',
|
|
48
|
+
disturbed: false,
|
|
49
|
+
error: null,
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
size = 0;
|
|
53
|
+
|
|
54
|
+
constructor(body: BodyInit | Readable | Blob | Buffer, options: ResponseInit & { size?: number } = { size: 0 }) {
|
|
55
|
+
this.size = options.size || 0;
|
|
56
|
+
if (body === null) {
|
|
57
|
+
// Body is undefined or null
|
|
58
|
+
this[INTERNALS].body = null;
|
|
59
|
+
} else if (isURLSearchParameters(body)) {
|
|
60
|
+
// Body is a URLSearchParams
|
|
61
|
+
this[INTERNALS].body = Buffer.from(body.toString())
|
|
62
|
+
} else if (isBlob(body)) {
|
|
63
|
+
// Body is blob
|
|
64
|
+
} else if (Buffer.isBuffer(body)) {
|
|
65
|
+
// Body is Buffer
|
|
66
|
+
} else if (types.isAnyArrayBuffer(body)) {
|
|
67
|
+
// Body is ArrayBuffer
|
|
68
|
+
this[INTERNALS].body = Buffer.from(body);
|
|
69
|
+
} else if (ArrayBuffer.isView(body)) {
|
|
70
|
+
// Body is ArrayBufferView
|
|
71
|
+
this[INTERNALS].body = Buffer.from(body.buffer, body.byteOffset, body.byteLength);
|
|
72
|
+
} else if (body instanceof Readable) {
|
|
73
|
+
// Body is stream
|
|
74
|
+
this[INTERNALS].body = body;
|
|
75
|
+
} else if (body instanceof ReadableStream || body instanceof StreamWebReadableStream) {
|
|
76
|
+
// Body is web stream
|
|
77
|
+
this[INTERNALS].body = Readable.fromWeb(body as StreamWebReadableStream); // TODO check compatibility between ReadableStream (from lib.dom.d.ts) and StreamWebReadableStream (from stream/web)
|
|
78
|
+
} else if (body instanceof FormData) {
|
|
79
|
+
// Body is FormData
|
|
80
|
+
this[INTERNALS].body = formDataToBlob(body) as Blob & globalThis.Blob;
|
|
81
|
+
this[INTERNALS].boundary = this[INTERNALS].body.type.split('=')[1];
|
|
82
|
+
} else if (typeof body === 'string'){
|
|
83
|
+
// None of the above
|
|
84
|
+
// coerce to string then buffer
|
|
85
|
+
this[INTERNALS].body = Buffer.from(body);
|
|
86
|
+
} else if (body instanceof URLSearchParams){
|
|
87
|
+
// None of the above
|
|
88
|
+
// coerce to string then buffer
|
|
89
|
+
this[INTERNALS].body = Buffer.from(body.toString());
|
|
90
|
+
} else {
|
|
91
|
+
console.warn(`Unknown body type "${typeof body}", try to parse the body to string!`);
|
|
92
|
+
this[INTERNALS].body = Readable.from(typeof (body as any).toString === 'function' ? (body as any).toString() : body as any); // TODO
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// ´this[INTERNALS].stream = body;
|
|
96
|
+
|
|
97
|
+
if (Buffer.isBuffer(body)) {
|
|
98
|
+
this[INTERNALS].stream = Readable.from(body);
|
|
99
|
+
} else if (isBlob(body)) {
|
|
100
|
+
// @ts-ignore
|
|
101
|
+
this[INTERNALS].stream = Readable.from(body.stream());
|
|
102
|
+
} else if (body instanceof Readable) {
|
|
103
|
+
this[INTERNALS].stream = body;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (body instanceof Stream) {
|
|
107
|
+
body.on('error', error_ => {
|
|
108
|
+
const error = error_ instanceof FetchBaseError ?
|
|
109
|
+
error_ :
|
|
110
|
+
new FetchError(`Invalid response body while trying to fetch ${(this as any).url}: ${error_.message}`, 'system', error_ as any);
|
|
111
|
+
this[INTERNALS].error = error;
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
get body(): ReadableStream<Uint8Array> {
|
|
117
|
+
// @ts-ignore
|
|
118
|
+
return Readable.toWeb(this[INTERNALS].stream);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
get _stream() {
|
|
122
|
+
return this[INTERNALS].stream;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
get bodyUsed() {
|
|
126
|
+
return this[INTERNALS].disturbed;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Decode response as ArrayBuffer
|
|
131
|
+
*
|
|
132
|
+
* @return Promise
|
|
133
|
+
*/
|
|
134
|
+
async arrayBuffer() {
|
|
135
|
+
const {buffer, byteOffset, byteLength} = await consumeBody(this);
|
|
136
|
+
return buffer.slice(byteOffset, byteOffset + byteLength);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
async formData() {
|
|
140
|
+
const ct = (this as unknown as Request).headers?.get('content-type');
|
|
141
|
+
|
|
142
|
+
if (ct.startsWith('application/x-www-form-urlencoded')) {
|
|
143
|
+
const formData = new FormData();
|
|
144
|
+
const parameters = new URLSearchParams(await this.text());
|
|
145
|
+
|
|
146
|
+
for (const [name, value] of parameters) {
|
|
147
|
+
formData.append(name, value);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return formData;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const {toFormData} = await import('./utils/multipart-parser.js');
|
|
154
|
+
return toFormData(this.body, ct);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Return raw response as Blob
|
|
159
|
+
*
|
|
160
|
+
* @return Promise
|
|
161
|
+
*/
|
|
162
|
+
// @ts-ignore
|
|
163
|
+
async blob() {
|
|
164
|
+
const ct = ((this as unknown as Request).headers?.get('content-type')) || (this[INTERNALS].body && (this[INTERNALS].body as Blob).type) || '';
|
|
165
|
+
const buf = await this.arrayBuffer();
|
|
166
|
+
|
|
167
|
+
return new Blob([buf], {
|
|
168
|
+
type: ct
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Decode response as json
|
|
174
|
+
*
|
|
175
|
+
* @return Promise
|
|
176
|
+
*/
|
|
177
|
+
async json() {
|
|
178
|
+
const text = await this.text();
|
|
179
|
+
return JSON.parse(text);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Decode response as text
|
|
184
|
+
*
|
|
185
|
+
* @return Promise
|
|
186
|
+
*/
|
|
187
|
+
async text() {
|
|
188
|
+
const buffer = await consumeBody(this);
|
|
189
|
+
return new TextDecoder().decode(buffer);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// In browsers, all properties are enumerable.
|
|
194
|
+
Object.defineProperties(Body.prototype, {
|
|
195
|
+
body: {enumerable: true},
|
|
196
|
+
bodyUsed: {enumerable: true},
|
|
197
|
+
arrayBuffer: {enumerable: true},
|
|
198
|
+
blob: {enumerable: true},
|
|
199
|
+
json: {enumerable: true},
|
|
200
|
+
text: {enumerable: true},
|
|
201
|
+
data: {get: deprecate(() => {},
|
|
202
|
+
'data doesn\'t exist, use json(), text(), arrayBuffer(), or body instead',
|
|
203
|
+
'https://github.com/node-fetch/node-fetch/issues/1000 (response)')}
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Consume and convert an entire Body to a Buffer.
|
|
208
|
+
*
|
|
209
|
+
* Ref: https://fetch.spec.whatwg.org/#concept-body-consume-body
|
|
210
|
+
*
|
|
211
|
+
* @return Promise
|
|
212
|
+
*/
|
|
213
|
+
async function consumeBody(data: Body & Partial<Request>) {
|
|
214
|
+
if (data[INTERNALS].disturbed) {
|
|
215
|
+
throw new TypeError(`body used already for: ${data.url}`);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
data[INTERNALS].disturbed = true;
|
|
219
|
+
|
|
220
|
+
if (data[INTERNALS].error) {
|
|
221
|
+
throw data[INTERNALS].error;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const { _stream: body } = data;
|
|
225
|
+
|
|
226
|
+
// Body is null
|
|
227
|
+
if (body === null) {
|
|
228
|
+
return Buffer.alloc(0);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/* c8 ignore next 3 */
|
|
232
|
+
if (!(body instanceof Stream)) {
|
|
233
|
+
return Buffer.alloc(0);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Body is stream
|
|
237
|
+
// get ready to actually consume the body
|
|
238
|
+
const accum = [];
|
|
239
|
+
let accumBytes = 0;
|
|
240
|
+
|
|
241
|
+
try {
|
|
242
|
+
for await (const chunk of body) {
|
|
243
|
+
if (data.size > 0 && accumBytes + chunk.length > data.size) {
|
|
244
|
+
const error = new FetchError(`content size at ${data.url} over limit: ${data.size}`, 'max-size');
|
|
245
|
+
body.destroy(error);
|
|
246
|
+
throw error;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
accumBytes += chunk.length;
|
|
250
|
+
accum.push(chunk);
|
|
251
|
+
}
|
|
252
|
+
} catch (error) {
|
|
253
|
+
const error_ = error instanceof FetchBaseError ? error : new FetchError(`Invalid response body while trying to fetch ${data.url}: ${error.message}`, 'system', error);
|
|
254
|
+
throw error_;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (body.readableEnded === true || (body as any)._readableState.ended === true) {
|
|
258
|
+
try {
|
|
259
|
+
if (accum.every(c => typeof c === 'string')) {
|
|
260
|
+
return Buffer.from(accum.join(''));
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
return Buffer.concat(accum, accumBytes);
|
|
264
|
+
} catch (error) {
|
|
265
|
+
throw new FetchError(`Could not create Buffer from response body for ${data.url}: ${error.message}`, 'system', error);
|
|
266
|
+
}
|
|
267
|
+
} else {
|
|
268
|
+
throw new FetchError(`Premature close of server response while trying to fetch ${data.url}`);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Clone body given Res/Req instance
|
|
274
|
+
*
|
|
275
|
+
* @param Mixed instance Response or Request instance
|
|
276
|
+
* @param highWaterMark highWaterMark for both PassThrough body streams
|
|
277
|
+
* @return Mixed
|
|
278
|
+
*/
|
|
279
|
+
export const clone = <T extends Request | Response>(instance: T, highWaterMark?: number) => {
|
|
280
|
+
let p1: PassThrough;
|
|
281
|
+
let p2: PassThrough;
|
|
282
|
+
let {body} = instance[INTERNALS];
|
|
283
|
+
|
|
284
|
+
// Don't allow cloning a used body
|
|
285
|
+
if (instance.bodyUsed) {
|
|
286
|
+
throw new Error('cannot clone body after it is used');
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// Check that body is a stream and not form-data object
|
|
290
|
+
// note: we can't clone the form-data object without having it as a dependency
|
|
291
|
+
if ((body instanceof Stream) && (typeof (body as any).getBoundary !== 'function')) {
|
|
292
|
+
// Tee instance body
|
|
293
|
+
p1 = new PassThrough({highWaterMark});
|
|
294
|
+
p2 = new PassThrough({highWaterMark});
|
|
295
|
+
body.pipe(p1);
|
|
296
|
+
body.pipe(p2);
|
|
297
|
+
// Set instance body to teed body and return the other teed body
|
|
298
|
+
instance[INTERNALS].stream = p1;
|
|
299
|
+
body = p2;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
return body;
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
// const getNonSpecFormDataBoundary = deprecate(
|
|
306
|
+
// (body) => body.getBoundary(),
|
|
307
|
+
// 'form-data doesn\'t follow the spec and requires special treatment. Use alternative package',
|
|
308
|
+
// 'https://github.com/node-fetch/node-fetch/issues/1167'
|
|
309
|
+
// );
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Performs the operation "extract a `Content-Type` value from |object|" as
|
|
313
|
+
* specified in the specification:
|
|
314
|
+
* https://fetch.spec.whatwg.org/#concept-bodyinit-extract
|
|
315
|
+
*
|
|
316
|
+
* This function assumes that instance.body is present.
|
|
317
|
+
*
|
|
318
|
+
* @param body Any options.body input
|
|
319
|
+
*/
|
|
320
|
+
export const extractContentType = (body: BodyInit | string | ArrayBuffer | Readable | Blob | ArrayBufferView | Buffer | FormData | globalThis.ReadableStream<any> | null, request: Request | Response): string | null => {
|
|
321
|
+
// Body is null or undefined
|
|
322
|
+
if (body === null) {
|
|
323
|
+
return null;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// Body is string
|
|
327
|
+
if (typeof body === 'string') {
|
|
328
|
+
return 'text/plain;charset=UTF-8';
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// Body is a URLSearchParams
|
|
332
|
+
if (isURLSearchParameters(body)) {
|
|
333
|
+
return 'application/x-www-form-urlencoded;charset=UTF-8';
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// Body is blob
|
|
337
|
+
if (isBlob(body)) {
|
|
338
|
+
return (body as Blob & globalThis.Blob).type || null;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// Body is a Buffer (Buffer, ArrayBuffer or ArrayBufferView)
|
|
342
|
+
if (Buffer.isBuffer(body) || types.isAnyArrayBuffer(body) || ArrayBuffer.isView(body)) {
|
|
343
|
+
return null;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
if (body instanceof FormData) {
|
|
347
|
+
return `multipart/form-data; boundary=${request[INTERNALS].boundary}`;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// Detect form data input from form-data module
|
|
351
|
+
// if (body && typeof body.getBoundary === 'function') {
|
|
352
|
+
// return `multipart/form-data;boundary=${getNonSpecFormDataBoundary(body)}`;
|
|
353
|
+
// }
|
|
354
|
+
|
|
355
|
+
// Body is stream - can't really do much about this
|
|
356
|
+
if (body instanceof Stream) {
|
|
357
|
+
return null;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// Body constructor defaults other things to string
|
|
361
|
+
return 'text/plain;charset=UTF-8';
|
|
362
|
+
};
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* The Fetch Standard treats this as if "total bytes" is a property on the body.
|
|
366
|
+
* For us, we have to explicitly get it with a function.
|
|
367
|
+
*
|
|
368
|
+
* ref: https://fetch.spec.whatwg.org/#concept-body-total-bytes
|
|
369
|
+
*
|
|
370
|
+
* @param request Request object with the body property.
|
|
371
|
+
*/
|
|
372
|
+
export const getTotalBytes = (request: Request): number | null => {
|
|
373
|
+
const { body } = request[INTERNALS];
|
|
374
|
+
|
|
375
|
+
// Body is null or undefined
|
|
376
|
+
if (body === null) {
|
|
377
|
+
return 0;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// Body is Blob
|
|
381
|
+
if (isBlob(body)) {
|
|
382
|
+
return (body as Blob).size;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// Body is Buffer
|
|
386
|
+
if (Buffer.isBuffer(body)) {
|
|
387
|
+
return body.length;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// Detect form data input from form-data module
|
|
391
|
+
if (body && typeof (body as any).getLengthSync === 'function') {
|
|
392
|
+
const anyBody = body as any;
|
|
393
|
+
return anyBody.hasKnownLength && anyBody.hasKnownLength() ? anyBody.getLengthSync() : null;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// Body is stream
|
|
397
|
+
return null;
|
|
398
|
+
};
|
|
399
|
+
|
|
400
|
+
/**
|
|
401
|
+
* Write a Body to a Node.js WritableStream (e.g. http.Request) object.
|
|
402
|
+
*
|
|
403
|
+
* @param dest The stream to write to.
|
|
404
|
+
* @param obj.body Body object from the Body instance.
|
|
405
|
+
*/
|
|
406
|
+
export const writeToStream = async (dest: Writable, {body}): Promise<void> => {
|
|
407
|
+
if (body === null) {
|
|
408
|
+
// Body is null
|
|
409
|
+
dest.end();
|
|
410
|
+
} else {
|
|
411
|
+
// Body is stream
|
|
412
|
+
await pipeline(body, dest);
|
|
413
|
+
}
|
|
414
|
+
};
|
|
415
|
+
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export class FetchBaseError extends Error {
|
|
2
|
+
|
|
3
|
+
type?: 'aborted' | string;
|
|
4
|
+
|
|
5
|
+
constructor(message: string, type?: string) {
|
|
6
|
+
super(message);
|
|
7
|
+
// Hide custom error implementation details from end-users
|
|
8
|
+
Error.captureStackTrace(this, this.constructor);
|
|
9
|
+
|
|
10
|
+
this.type = type;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
get name() {
|
|
14
|
+
return this.constructor.name;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
get [Symbol.toStringTag]() {
|
|
18
|
+
return this.constructor.name;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { FetchBaseError } from './base.js';
|
|
2
|
+
import type { SystemError } from '../types/index.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* FetchError interface for operational errors
|
|
6
|
+
*/
|
|
7
|
+
export class FetchError extends FetchBaseError {
|
|
8
|
+
code: string;
|
|
9
|
+
errno: string;
|
|
10
|
+
erroredSysCall: string;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @param message Error message for human
|
|
14
|
+
* @param type Error type for machine
|
|
15
|
+
* @param systemError For Node.js system error
|
|
16
|
+
*/
|
|
17
|
+
constructor(message: string, type?: string, systemError?: SystemError) {
|
|
18
|
+
super(message, type);
|
|
19
|
+
// When err.type is `system`, err.erroredSysCall contains system error and err.code contains system error code
|
|
20
|
+
if (systemError) {
|
|
21
|
+
// eslint-disable-next-line no-multi-assign
|
|
22
|
+
this.code = this.errno = systemError.code;
|
|
23
|
+
this.erroredSysCall = systemError.syscall;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|