@gjsify/fetch 0.4.0 → 0.4.3
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/package.json +61 -58
- package/src/body.ts +0 -448
- package/src/errors/abort-error.ts +0 -10
- package/src/errors/base.ts +0 -22
- package/src/errors/fetch-error.ts +0 -26
- package/src/headers.ts +0 -232
- package/src/index.spec.ts +0 -339
- package/src/index.ts +0 -316
- package/src/register/fetch.ts +0 -16
- package/src/register/xhr.ts +0 -20
- package/src/register.ts +0 -5
- package/src/request.ts +0 -423
- package/src/response.ts +0 -227
- package/src/test.browser.mts +0 -130
- package/src/test.mts +0 -9
- package/src/types/index.ts +0 -1
- package/src/types/system-error.ts +0 -11
- package/src/utils/blob-from.ts +0 -8
- package/src/utils/data-uri.ts +0 -29
- package/src/utils/get-search.ts +0 -9
- package/src/utils/is-redirect.ts +0 -11
- package/src/utils/is.ts +0 -88
- package/src/utils/multipart-parser.ts +0 -448
- package/src/utils/referrer.ts +0 -350
- package/src/utils/soup-helpers.ts +0 -37
- package/src/xhr.ts +0 -270
- package/tsconfig.json +0 -22
- package/tsconfig.tsbuildinfo +0 -1
package/package.json
CHANGED
|
@@ -1,63 +1,66 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
2
|
+
"name": "@gjsify/fetch",
|
|
3
|
+
"version": "0.4.3",
|
|
4
|
+
"description": "Web and Node.js fetch module for Gjs",
|
|
5
|
+
"module": "lib/esm/index.js",
|
|
6
|
+
"types": "lib/index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"lib"
|
|
9
|
+
],
|
|
10
|
+
"type": "module",
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"types": "./lib/index.d.ts",
|
|
14
|
+
"default": "./lib/esm/index.js"
|
|
15
|
+
},
|
|
16
|
+
"./register": {
|
|
17
|
+
"types": "./lib/register.d.ts",
|
|
18
|
+
"default": "./lib/esm/register.js"
|
|
19
|
+
},
|
|
20
|
+
"./register/fetch": {
|
|
21
|
+
"default": "./lib/esm/register/fetch.js"
|
|
22
|
+
},
|
|
23
|
+
"./register/xhr": {
|
|
24
|
+
"default": "./lib/esm/register/xhr.js"
|
|
25
|
+
}
|
|
12
26
|
},
|
|
13
|
-
"
|
|
14
|
-
|
|
15
|
-
|
|
27
|
+
"sideEffects": [
|
|
28
|
+
"./lib/esm/register.js",
|
|
29
|
+
"./lib/esm/register/*.js"
|
|
30
|
+
],
|
|
31
|
+
"scripts": {
|
|
32
|
+
"clear": "rm -rf lib tsconfig.tsbuildinfo tsconfig.types.tsbuildinfo test.gjs.mjs test.node.mjs || exit 0",
|
|
33
|
+
"check": "tsc --noEmit",
|
|
34
|
+
"build": "gjsify run build:gjsify && gjsify run build:types",
|
|
35
|
+
"build:gjsify": "gjsify build --library 'src/**/*.{ts,js}' --exclude 'src/**/*.spec.{mts,ts}' 'src/test.{mts,ts}'",
|
|
36
|
+
"build:types": "tsc",
|
|
37
|
+
"build:test": "gjsify run build:test:gjs && gjsify run build:test:node",
|
|
38
|
+
"build:test:gjs": "gjsify build src/test.mts --app gjs --outfile test.gjs.mjs",
|
|
39
|
+
"build:test:browser": "gjsify build src/test.browser.mts --app browser --outfile dist/test.browser.mjs",
|
|
40
|
+
"build:test:node": "gjsify build src/test.mts --app node --outfile test.node.mjs",
|
|
41
|
+
"test": "gjsify run build:gjsify && gjsify run build:test:node && gjsify run test:node",
|
|
42
|
+
"test:gjs": "gjsify run test.gjs.mjs",
|
|
43
|
+
"test:node": "node test.node.mjs"
|
|
16
44
|
},
|
|
17
|
-
"
|
|
18
|
-
|
|
45
|
+
"keywords": [
|
|
46
|
+
"gjs",
|
|
47
|
+
"node",
|
|
48
|
+
"fetch"
|
|
49
|
+
],
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"@gjsify/cli": "workspace:^",
|
|
52
|
+
"@gjsify/unit": "workspace:^",
|
|
53
|
+
"@types/node": "^25.6.2",
|
|
54
|
+
"typescript": "^6.0.3"
|
|
19
55
|
},
|
|
20
|
-
"
|
|
21
|
-
|
|
56
|
+
"dependencies": {
|
|
57
|
+
"@girs/gio-2.0": "2.88.0-4.0.0-rc.15",
|
|
58
|
+
"@girs/gjs": "4.0.0-rc.15",
|
|
59
|
+
"@girs/glib-2.0": "2.88.0-4.0.0-rc.15",
|
|
60
|
+
"@girs/soup-3.0": "3.6.6-4.0.0-rc.15",
|
|
61
|
+
"@gjsify/formdata": "workspace:^",
|
|
62
|
+
"@gjsify/http": "workspace:^",
|
|
63
|
+
"@gjsify/url": "workspace:^",
|
|
64
|
+
"@gjsify/utils": "workspace:^"
|
|
22
65
|
}
|
|
23
|
-
|
|
24
|
-
"sideEffects": [
|
|
25
|
-
"./lib/esm/register.js",
|
|
26
|
-
"./lib/esm/register/*.js"
|
|
27
|
-
],
|
|
28
|
-
"scripts": {
|
|
29
|
-
"clear": "rm -rf lib tsconfig.tsbuildinfo tsconfig.types.tsbuildinfo test.gjs.mjs test.node.mjs || exit 0",
|
|
30
|
-
"check": "tsc --noEmit",
|
|
31
|
-
"build": "yarn build:gjsify && yarn build:types",
|
|
32
|
-
"build:gjsify": "gjsify build --library 'src/**/*.{ts,js}' --exclude 'src/**/*.spec.{mts,ts}' 'src/test.{mts,ts}'",
|
|
33
|
-
"build:types": "tsc",
|
|
34
|
-
"build:test": "yarn build:test:gjs && yarn build:test:node",
|
|
35
|
-
"build:test:gjs": "gjsify build src/test.mts --app gjs --outfile test.gjs.mjs",
|
|
36
|
-
"build:test:browser": "gjsify build src/test.browser.mts --app browser --outfile dist/test.browser.mjs",
|
|
37
|
-
"build:test:node": "gjsify build src/test.mts --app node --outfile test.node.mjs",
|
|
38
|
-
"test": "yarn build:gjsify && yarn build:test:node && yarn test:node",
|
|
39
|
-
"test:gjs": "gjsify run test.gjs.mjs",
|
|
40
|
-
"test:node": "node test.node.mjs"
|
|
41
|
-
},
|
|
42
|
-
"keywords": [
|
|
43
|
-
"gjs",
|
|
44
|
-
"node",
|
|
45
|
-
"fetch"
|
|
46
|
-
],
|
|
47
|
-
"devDependencies": {
|
|
48
|
-
"@gjsify/cli": "^0.4.0",
|
|
49
|
-
"@gjsify/unit": "^0.4.0",
|
|
50
|
-
"@types/node": "^25.6.2",
|
|
51
|
-
"typescript": "^6.0.3"
|
|
52
|
-
},
|
|
53
|
-
"dependencies": {
|
|
54
|
-
"@girs/gio-2.0": "2.88.0-4.0.0-rc.15",
|
|
55
|
-
"@girs/gjs": "4.0.0-rc.15",
|
|
56
|
-
"@girs/glib-2.0": "2.88.0-4.0.0-rc.15",
|
|
57
|
-
"@girs/soup-3.0": "3.6.6-4.0.0-rc.15",
|
|
58
|
-
"@gjsify/formdata": "^0.4.0",
|
|
59
|
-
"@gjsify/http": "^0.4.0",
|
|
60
|
-
"@gjsify/url": "^0.4.0",
|
|
61
|
-
"@gjsify/utils": "^0.4.0"
|
|
62
|
-
}
|
|
63
|
-
}
|
|
66
|
+
}
|
package/src/body.ts
DELETED
|
@@ -1,448 +0,0 @@
|
|
|
1
|
-
// SPDX-License-Identifier: MIT
|
|
2
|
-
// Adapted from node-fetch (https://github.com/node-fetch/node-fetch/blob/main/src/body.js)
|
|
3
|
-
// Copyright (c) node-fetch contributors. MIT license.
|
|
4
|
-
// Modifications: Rewritten for GJS using libsoup 3.0 and @gjsify/url
|
|
5
|
-
|
|
6
|
-
import { URLSearchParams } from '@gjsify/url';
|
|
7
|
-
import { Blob } from './utils/blob-from.js';
|
|
8
|
-
|
|
9
|
-
import { PassThrough, pipeline as pipelineCb, Readable, Stream, Writable } from 'node:stream';
|
|
10
|
-
import { Buffer } from 'node:buffer';
|
|
11
|
-
|
|
12
|
-
import { FormData, formDataToBlob } from '@gjsify/formdata';
|
|
13
|
-
|
|
14
|
-
import { FetchError } from './errors/fetch-error.js';
|
|
15
|
-
import { FetchBaseError } from './errors/base.js';
|
|
16
|
-
import { isBlob, isURLSearchParameters } from './utils/is.js';
|
|
17
|
-
|
|
18
|
-
import type { Request } from './request.js';
|
|
19
|
-
import type { Response } from './response.js';
|
|
20
|
-
import type { SystemError } from './types/index.js';
|
|
21
|
-
|
|
22
|
-
const pipeline = (source: Readable, dest: Writable): Promise<void> =>
|
|
23
|
-
new Promise((resolve, reject) => {
|
|
24
|
-
pipelineCb(source, dest, (err) => {
|
|
25
|
-
if (err) reject(err);
|
|
26
|
-
else resolve();
|
|
27
|
-
});
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
const INTERNALS = Symbol('Body internals');
|
|
31
|
-
|
|
32
|
-
function isAnyArrayBuffer(val: unknown): val is ArrayBuffer {
|
|
33
|
-
return val instanceof ArrayBuffer ||
|
|
34
|
-
(typeof SharedArrayBuffer !== 'undefined' && val instanceof SharedArrayBuffer);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function isBoxedPrimitive(val: unknown): boolean {
|
|
38
|
-
return (
|
|
39
|
-
val instanceof String ||
|
|
40
|
-
val instanceof Number ||
|
|
41
|
-
val instanceof Boolean ||
|
|
42
|
-
(typeof Symbol !== 'undefined' && val instanceof Symbol) ||
|
|
43
|
-
(typeof BigInt !== 'undefined' && val instanceof (BigInt as unknown as typeof Number))
|
|
44
|
-
);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Body mixin
|
|
49
|
-
*
|
|
50
|
-
* Ref: https://fetch.spec.whatwg.org/#body
|
|
51
|
-
*
|
|
52
|
-
* @param body Readable stream
|
|
53
|
-
* @param opts Response options
|
|
54
|
-
*/
|
|
55
|
-
export default class Body {
|
|
56
|
-
|
|
57
|
-
[INTERNALS]: {
|
|
58
|
-
body: null | Buffer | Readable | Blob;
|
|
59
|
-
stream: Readable | null;
|
|
60
|
-
boundary: string;
|
|
61
|
-
disturbed: boolean,
|
|
62
|
-
error: null | FetchBaseError;
|
|
63
|
-
} = {
|
|
64
|
-
body: null,
|
|
65
|
-
stream: null,
|
|
66
|
-
boundary: '',
|
|
67
|
-
disturbed: false,
|
|
68
|
-
error: null,
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
size = 0;
|
|
72
|
-
|
|
73
|
-
constructor(body: BodyInit | Readable | Blob | Buffer | null, options: { size?: number; headers?: unknown } = { size: 0 }) {
|
|
74
|
-
this.size = options.size || 0;
|
|
75
|
-
if (body === null || body === undefined) {
|
|
76
|
-
// Body is undefined or null
|
|
77
|
-
this[INTERNALS].body = null;
|
|
78
|
-
} else if (isURLSearchParameters(body)) {
|
|
79
|
-
// Body is a URLSearchParams
|
|
80
|
-
this[INTERNALS].body = Buffer.from(body.toString())
|
|
81
|
-
} else if (isBlob(body)) {
|
|
82
|
-
// Body is blob
|
|
83
|
-
this[INTERNALS].body = body as Blob;
|
|
84
|
-
} else if (Buffer.isBuffer(body)) {
|
|
85
|
-
// Body is Buffer
|
|
86
|
-
this[INTERNALS].body = body;
|
|
87
|
-
} else if (isAnyArrayBuffer(body)) {
|
|
88
|
-
// Body is ArrayBuffer
|
|
89
|
-
this[INTERNALS].body = Buffer.from(body);
|
|
90
|
-
} else if (ArrayBuffer.isView(body)) {
|
|
91
|
-
// Body is ArrayBufferView
|
|
92
|
-
this[INTERNALS].body = Buffer.from(body.buffer, body.byteOffset, body.byteLength);
|
|
93
|
-
} else if (body instanceof Readable) {
|
|
94
|
-
// Body is Node.js stream
|
|
95
|
-
this[INTERNALS].body = body;
|
|
96
|
-
} else if (typeof ReadableStream !== 'undefined' && body instanceof ReadableStream) {
|
|
97
|
-
// Body is Web ReadableStream — convert to Node.js Readable
|
|
98
|
-
this[INTERNALS].body = readableStreamToReadable(body);
|
|
99
|
-
} else if (body instanceof FormData) {
|
|
100
|
-
// Body is FormData
|
|
101
|
-
const blob = formDataToBlob(body) as Blob & globalThis.Blob;
|
|
102
|
-
this[INTERNALS].body = blob;
|
|
103
|
-
this[INTERNALS].boundary = blob.type?.split('boundary=')?.[1] ?? '';
|
|
104
|
-
} else if (typeof body === 'string'){
|
|
105
|
-
// String body
|
|
106
|
-
this[INTERNALS].body = Buffer.from(body);
|
|
107
|
-
} else if (body instanceof URLSearchParams){
|
|
108
|
-
this[INTERNALS].body = Buffer.from(body.toString());
|
|
109
|
-
} else {
|
|
110
|
-
console.warn(`Unknown body type "${typeof body}", try to parse the body to string!`);
|
|
111
|
-
this[INTERNALS].body = Buffer.from(String(body));
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// Set up the internal stream
|
|
115
|
-
const b = this[INTERNALS].body;
|
|
116
|
-
if (Buffer.isBuffer(b)) {
|
|
117
|
-
this[INTERNALS].stream = Readable.from(b);
|
|
118
|
-
} else if (isBlob(b)) {
|
|
119
|
-
this[INTERNALS].stream = Readable.from(blobToAsyncIterable(b as Blob));
|
|
120
|
-
} else if (b instanceof Readable) {
|
|
121
|
-
this[INTERNALS].stream = b;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
if (b instanceof Stream) {
|
|
125
|
-
b.on('error', (error_: Error) => {
|
|
126
|
-
const error = error_ instanceof FetchBaseError
|
|
127
|
-
? error_
|
|
128
|
-
: new FetchError(`Invalid response body while trying to fetch ${(this as unknown as Request).url}: ${error_.message}`, 'system', error_ as unknown as SystemError);
|
|
129
|
-
this[INTERNALS].error = error;
|
|
130
|
-
});
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
get body(): ReadableStream<Uint8Array> | null {
|
|
135
|
-
const stream = this[INTERNALS].stream;
|
|
136
|
-
if (!stream) return null;
|
|
137
|
-
|
|
138
|
-
// If ReadableStream is available, wrap the Readable into one
|
|
139
|
-
if (typeof ReadableStream !== 'undefined') {
|
|
140
|
-
let closed = false;
|
|
141
|
-
return new ReadableStream<Uint8Array>({
|
|
142
|
-
start(controller) {
|
|
143
|
-
stream.on('data', (chunk: Buffer | Uint8Array) => {
|
|
144
|
-
if (closed) return;
|
|
145
|
-
try {
|
|
146
|
-
controller.enqueue(chunk instanceof Uint8Array ? chunk : new Uint8Array(chunk));
|
|
147
|
-
} catch { /* consumer cancelled — drop */ }
|
|
148
|
-
});
|
|
149
|
-
stream.on('end', () => {
|
|
150
|
-
if (closed) return;
|
|
151
|
-
closed = true;
|
|
152
|
-
// Defensive: consumer may have cancelled the stream already,
|
|
153
|
-
// in which case .close() throws TypeError. Don't surface that
|
|
154
|
-
// as an unhandled error in the nextTick queue.
|
|
155
|
-
try { controller.close(); } catch { /* already closed/cancelled */ }
|
|
156
|
-
});
|
|
157
|
-
stream.on('error', (err: Error) => {
|
|
158
|
-
if (closed) return;
|
|
159
|
-
closed = true;
|
|
160
|
-
try { controller.error(err); } catch { /* already closed/cancelled */ }
|
|
161
|
-
});
|
|
162
|
-
},
|
|
163
|
-
cancel() {
|
|
164
|
-
closed = true;
|
|
165
|
-
stream.destroy();
|
|
166
|
-
}
|
|
167
|
-
});
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
return null;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
get _stream() {
|
|
174
|
-
return this[INTERNALS].stream;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
/** Return the raw body buffer without consuming the stream (used by Request._send). */
|
|
178
|
-
get _rawBodyBuffer(): Buffer | null {
|
|
179
|
-
const b = this[INTERNALS].body;
|
|
180
|
-
if (b === null) return null;
|
|
181
|
-
if (Buffer.isBuffer(b)) return b;
|
|
182
|
-
if (b instanceof Uint8Array) return Buffer.from(b.buffer, b.byteOffset, b.byteLength);
|
|
183
|
-
return null;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
get bodyUsed() {
|
|
187
|
-
return this[INTERNALS].disturbed;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
/**
|
|
191
|
-
* Decode response as ArrayBuffer
|
|
192
|
-
*/
|
|
193
|
-
async arrayBuffer(): Promise<ArrayBuffer> {
|
|
194
|
-
const {buffer, byteOffset, byteLength} = await consumeBody(this);
|
|
195
|
-
return buffer.slice(byteOffset, byteOffset + byteLength) as ArrayBuffer;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
async formData(): Promise<FormData> {
|
|
199
|
-
const ct = (this as unknown as Request).headers?.get('content-type');
|
|
200
|
-
|
|
201
|
-
if (ct?.startsWith('application/x-www-form-urlencoded')) {
|
|
202
|
-
const formData = new FormData();
|
|
203
|
-
const parameters = new URLSearchParams(await this.text());
|
|
204
|
-
|
|
205
|
-
for (const [name, value] of parameters) {
|
|
206
|
-
formData.append(name, value);
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
return formData;
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
const {toFormData} = await import('./utils/multipart-parser.js');
|
|
213
|
-
return toFormData(this.body, ct);
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
/**
|
|
217
|
-
* Return raw response as Blob
|
|
218
|
-
*/
|
|
219
|
-
async blob(): Promise<Blob> {
|
|
220
|
-
const ct = ((this as unknown as Request).headers?.get('content-type')) || (this[INTERNALS].body && (this[INTERNALS].body as Blob).type) || '';
|
|
221
|
-
const buf = await this.arrayBuffer();
|
|
222
|
-
|
|
223
|
-
return new Blob([buf], {
|
|
224
|
-
type: ct
|
|
225
|
-
});
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
/**
|
|
229
|
-
* Decode response as json
|
|
230
|
-
*/
|
|
231
|
-
async json(): Promise<unknown> {
|
|
232
|
-
const text = await this.text();
|
|
233
|
-
return JSON.parse(text);
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
/**
|
|
237
|
-
* Decode response as text
|
|
238
|
-
*/
|
|
239
|
-
async text(): Promise<string> {
|
|
240
|
-
const buffer = await consumeBody(this);
|
|
241
|
-
return new TextDecoder().decode(buffer);
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
// In browsers, all properties are enumerable.
|
|
246
|
-
Object.defineProperties(Body.prototype, {
|
|
247
|
-
body: {enumerable: true},
|
|
248
|
-
bodyUsed: {enumerable: true},
|
|
249
|
-
arrayBuffer: {enumerable: true},
|
|
250
|
-
blob: {enumerable: true},
|
|
251
|
-
json: {enumerable: true},
|
|
252
|
-
text: {enumerable: true},
|
|
253
|
-
});
|
|
254
|
-
|
|
255
|
-
/**
|
|
256
|
-
* Consume and convert an entire Body to a Buffer.
|
|
257
|
-
*/
|
|
258
|
-
async function consumeBody(data: Body & { url?: string }): Promise<Buffer> {
|
|
259
|
-
if (data[INTERNALS].disturbed) {
|
|
260
|
-
throw new TypeError(`body used already for: ${data.url}`);
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
data[INTERNALS].disturbed = true;
|
|
264
|
-
|
|
265
|
-
if (data[INTERNALS].error) {
|
|
266
|
-
throw data[INTERNALS].error;
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
const { _stream: body } = data;
|
|
270
|
-
|
|
271
|
-
// Body is null
|
|
272
|
-
if (body === null) {
|
|
273
|
-
return Buffer.alloc(0);
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
if (!(body instanceof Stream)) {
|
|
277
|
-
return Buffer.alloc(0);
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
// Body is stream — consume it
|
|
281
|
-
const accum: (Buffer | Uint8Array)[] = [];
|
|
282
|
-
let accumBytes = 0;
|
|
283
|
-
|
|
284
|
-
try {
|
|
285
|
-
for await (const chunk of body) {
|
|
286
|
-
if (data.size > 0 && accumBytes + chunk.length > data.size) {
|
|
287
|
-
const error = new FetchError(`content size at ${data.url} over limit: ${data.size}`, 'max-size');
|
|
288
|
-
body.destroy(error);
|
|
289
|
-
throw error;
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
accumBytes += chunk.length;
|
|
293
|
-
accum.push(chunk);
|
|
294
|
-
}
|
|
295
|
-
} catch (error: unknown) {
|
|
296
|
-
const err = error instanceof Error ? error : new Error(String(error));
|
|
297
|
-
const error_ = error instanceof FetchBaseError ? error : new FetchError(`Invalid response body while trying to fetch ${data.url}: ${err.message}`, 'system', err as unknown as SystemError);
|
|
298
|
-
throw error_;
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
try {
|
|
302
|
-
if (accum.every(c => typeof c === 'string')) {
|
|
303
|
-
return Buffer.from((accum as unknown as string[]).join(''));
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
return Buffer.concat(accum as Buffer[], accumBytes);
|
|
307
|
-
} catch (error: unknown) {
|
|
308
|
-
const err = error instanceof Error ? error : new Error(String(error));
|
|
309
|
-
throw new FetchError(`Could not create Buffer from response body for ${data.url}: ${err.message}`, 'system', err as unknown as SystemError);
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
/**
|
|
314
|
-
* Clone body given Res/Req instance
|
|
315
|
-
*/
|
|
316
|
-
export const clone = <T extends Request | Response>(instance: T, highWaterMark?: number) => {
|
|
317
|
-
let p1: PassThrough;
|
|
318
|
-
let p2: PassThrough;
|
|
319
|
-
let {body} = instance[INTERNALS];
|
|
320
|
-
|
|
321
|
-
if (instance.bodyUsed) {
|
|
322
|
-
throw new Error('cannot clone body after it is used');
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
if ((body instanceof Stream) && (typeof (body as unknown as Record<string, unknown>).getBoundary !== 'function')) {
|
|
326
|
-
p1 = new PassThrough({highWaterMark});
|
|
327
|
-
p2 = new PassThrough({highWaterMark});
|
|
328
|
-
body.pipe(p1);
|
|
329
|
-
body.pipe(p2);
|
|
330
|
-
instance[INTERNALS].stream = p1;
|
|
331
|
-
body = p2;
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
return body;
|
|
335
|
-
};
|
|
336
|
-
|
|
337
|
-
/**
|
|
338
|
-
* Extract a Content-Type value from a body.
|
|
339
|
-
*/
|
|
340
|
-
export const extractContentType = (body: BodyInit | Readable | Blob | Buffer | null, request: Request | Response): string | null => {
|
|
341
|
-
if (body === null) {
|
|
342
|
-
return null;
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
if (typeof body === 'string') {
|
|
346
|
-
return 'text/plain;charset=UTF-8';
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
if (isURLSearchParameters(body)) {
|
|
350
|
-
return 'application/x-www-form-urlencoded;charset=UTF-8';
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
if (isBlob(body)) {
|
|
354
|
-
return (body as Blob & globalThis.Blob).type || null;
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
if (Buffer.isBuffer(body) || isAnyArrayBuffer(body) || ArrayBuffer.isView(body)) {
|
|
358
|
-
return null;
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
if (body instanceof FormData) {
|
|
362
|
-
return `multipart/form-data; boundary=${request[INTERNALS].boundary}`;
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
if (body instanceof Stream) {
|
|
366
|
-
return null;
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
return 'text/plain;charset=UTF-8';
|
|
370
|
-
};
|
|
371
|
-
|
|
372
|
-
/**
|
|
373
|
-
* Get total bytes of a body.
|
|
374
|
-
*/
|
|
375
|
-
export const getTotalBytes = (request: Request): number | null => {
|
|
376
|
-
const { body } = request[INTERNALS];
|
|
377
|
-
|
|
378
|
-
if (body === null) {
|
|
379
|
-
return 0;
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
if (isBlob(body)) {
|
|
383
|
-
return (body as Blob).size;
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
if (Buffer.isBuffer(body)) {
|
|
387
|
-
return body.length;
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
if (body && typeof (body as unknown as Record<string, unknown>).getLengthSync === 'function') {
|
|
391
|
-
const streamBody = body as unknown as { getLengthSync(): number; hasKnownLength?(): boolean };
|
|
392
|
-
return streamBody.hasKnownLength && streamBody.hasKnownLength() ? streamBody.getLengthSync() : null;
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
return null;
|
|
396
|
-
};
|
|
397
|
-
|
|
398
|
-
/**
|
|
399
|
-
* Write a Body to a Node.js WritableStream.
|
|
400
|
-
*/
|
|
401
|
-
export const writeToStream = async (dest: Writable, {body}: {body: Readable | null}): Promise<void> => {
|
|
402
|
-
if (body === null) {
|
|
403
|
-
dest.end();
|
|
404
|
-
} else {
|
|
405
|
-
await pipeline(body, dest);
|
|
406
|
-
}
|
|
407
|
-
};
|
|
408
|
-
|
|
409
|
-
/**
|
|
410
|
-
* Convert a Web ReadableStream to a Node.js Readable.
|
|
411
|
-
*/
|
|
412
|
-
function readableStreamToReadable(webStream: ReadableStream): Readable {
|
|
413
|
-
const reader = webStream.getReader();
|
|
414
|
-
return new Readable({
|
|
415
|
-
async read() {
|
|
416
|
-
try {
|
|
417
|
-
const { done, value } = await reader.read();
|
|
418
|
-
if (done) {
|
|
419
|
-
this.push(null);
|
|
420
|
-
} else {
|
|
421
|
-
this.push(Buffer.from(value));
|
|
422
|
-
}
|
|
423
|
-
} catch (err) {
|
|
424
|
-
this.destroy(err as Error);
|
|
425
|
-
}
|
|
426
|
-
},
|
|
427
|
-
destroy(_err, callback) {
|
|
428
|
-
reader.cancel().then(() => callback(null), callback);
|
|
429
|
-
}
|
|
430
|
-
});
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
/**
|
|
434
|
-
* Convert a Blob to an async iterable for Readable.from().
|
|
435
|
-
*/
|
|
436
|
-
async function* blobToAsyncIterable(blob: Blob): AsyncIterable<Uint8Array> {
|
|
437
|
-
if (typeof blob.stream === 'function') {
|
|
438
|
-
const reader = (blob.stream() as unknown as ReadableStream).getReader();
|
|
439
|
-
while (true) {
|
|
440
|
-
const { done, value } = await reader.read();
|
|
441
|
-
if (done) break;
|
|
442
|
-
yield value;
|
|
443
|
-
}
|
|
444
|
-
} else {
|
|
445
|
-
// Fallback: read the entire blob at once
|
|
446
|
-
yield new Uint8Array(await blob.arrayBuffer());
|
|
447
|
-
}
|
|
448
|
-
}
|
package/src/errors/base.ts
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
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
|
-
if (typeof Error.captureStackTrace === 'function') {
|
|
9
|
-
Error.captureStackTrace(this, this.constructor);
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
this.type = type;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
get name() {
|
|
16
|
-
return this.constructor.name;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
get [Symbol.toStringTag]() {
|
|
20
|
-
return this.constructor.name;
|
|
21
|
-
}
|
|
22
|
-
}
|
|
@@ -1,26 +0,0 @@
|
|
|
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
|
-
}
|