@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/lib/esm/body.js
ADDED
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import { URLSearchParams } from "@gjsify/deno-runtime/ext/url/00_url";
|
|
2
|
+
import { Blob } from "@gjsify/deno-runtime/ext/web/09_file";
|
|
3
|
+
import { PassThrough, pipeline as pipelineCb, Readable, Stream } from "stream";
|
|
4
|
+
import { ReadableStream as StreamWebReadableStream } from "stream/web";
|
|
5
|
+
import { types, deprecate, promisify } from "util";
|
|
6
|
+
import { Buffer } from "buffer";
|
|
7
|
+
import { FormData, formDataToBlob } from "formdata-polyfill/esm.min.js";
|
|
8
|
+
import { FetchError } from "./errors/fetch-error.js";
|
|
9
|
+
import { FetchBaseError } from "./errors/base.js";
|
|
10
|
+
import { isBlob, isURLSearchParameters } from "./utils/is.js";
|
|
11
|
+
const pipeline = promisify(pipelineCb);
|
|
12
|
+
const INTERNALS = Symbol("Body internals");
|
|
13
|
+
class Body {
|
|
14
|
+
[INTERNALS] = {
|
|
15
|
+
body: null,
|
|
16
|
+
stream: null,
|
|
17
|
+
boundary: "",
|
|
18
|
+
disturbed: false,
|
|
19
|
+
error: null
|
|
20
|
+
};
|
|
21
|
+
size = 0;
|
|
22
|
+
constructor(body, options = { size: 0 }) {
|
|
23
|
+
this.size = options.size || 0;
|
|
24
|
+
if (body === null) {
|
|
25
|
+
this[INTERNALS].body = null;
|
|
26
|
+
} else if (isURLSearchParameters(body)) {
|
|
27
|
+
this[INTERNALS].body = Buffer.from(body.toString());
|
|
28
|
+
} else if (isBlob(body)) {
|
|
29
|
+
} else if (Buffer.isBuffer(body)) {
|
|
30
|
+
} else if (types.isAnyArrayBuffer(body)) {
|
|
31
|
+
this[INTERNALS].body = Buffer.from(body);
|
|
32
|
+
} else if (ArrayBuffer.isView(body)) {
|
|
33
|
+
this[INTERNALS].body = Buffer.from(body.buffer, body.byteOffset, body.byteLength);
|
|
34
|
+
} else if (body instanceof Readable) {
|
|
35
|
+
this[INTERNALS].body = body;
|
|
36
|
+
} else if (body instanceof ReadableStream || body instanceof StreamWebReadableStream) {
|
|
37
|
+
this[INTERNALS].body = Readable.fromWeb(body);
|
|
38
|
+
} else if (body instanceof FormData) {
|
|
39
|
+
this[INTERNALS].body = formDataToBlob(body);
|
|
40
|
+
this[INTERNALS].boundary = this[INTERNALS].body.type.split("=")[1];
|
|
41
|
+
} else if (typeof body === "string") {
|
|
42
|
+
this[INTERNALS].body = Buffer.from(body);
|
|
43
|
+
} else if (body instanceof URLSearchParams) {
|
|
44
|
+
this[INTERNALS].body = Buffer.from(body.toString());
|
|
45
|
+
} else {
|
|
46
|
+
console.warn(`Unknown body type "${typeof body}", try to parse the body to string!`);
|
|
47
|
+
this[INTERNALS].body = Readable.from(typeof body.toString === "function" ? body.toString() : body);
|
|
48
|
+
}
|
|
49
|
+
if (Buffer.isBuffer(body)) {
|
|
50
|
+
this[INTERNALS].stream = Readable.from(body);
|
|
51
|
+
} else if (isBlob(body)) {
|
|
52
|
+
this[INTERNALS].stream = Readable.from(body.stream());
|
|
53
|
+
} else if (body instanceof Readable) {
|
|
54
|
+
this[INTERNALS].stream = body;
|
|
55
|
+
}
|
|
56
|
+
if (body instanceof Stream) {
|
|
57
|
+
body.on("error", (error_) => {
|
|
58
|
+
const error = error_ instanceof FetchBaseError ? error_ : new FetchError(`Invalid response body while trying to fetch ${this.url}: ${error_.message}`, "system", error_);
|
|
59
|
+
this[INTERNALS].error = error;
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
get body() {
|
|
64
|
+
return Readable.toWeb(this[INTERNALS].stream);
|
|
65
|
+
}
|
|
66
|
+
get _stream() {
|
|
67
|
+
return this[INTERNALS].stream;
|
|
68
|
+
}
|
|
69
|
+
get bodyUsed() {
|
|
70
|
+
return this[INTERNALS].disturbed;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Decode response as ArrayBuffer
|
|
74
|
+
*
|
|
75
|
+
* @return Promise
|
|
76
|
+
*/
|
|
77
|
+
async arrayBuffer() {
|
|
78
|
+
const { buffer, byteOffset, byteLength } = await consumeBody(this);
|
|
79
|
+
return buffer.slice(byteOffset, byteOffset + byteLength);
|
|
80
|
+
}
|
|
81
|
+
async formData() {
|
|
82
|
+
const ct = this.headers?.get("content-type");
|
|
83
|
+
if (ct.startsWith("application/x-www-form-urlencoded")) {
|
|
84
|
+
const formData = new FormData();
|
|
85
|
+
const parameters = new URLSearchParams(await this.text());
|
|
86
|
+
for (const [name, value] of parameters) {
|
|
87
|
+
formData.append(name, value);
|
|
88
|
+
}
|
|
89
|
+
return formData;
|
|
90
|
+
}
|
|
91
|
+
const { toFormData } = await import("./utils/multipart-parser.js");
|
|
92
|
+
return toFormData(this.body, ct);
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Return raw response as Blob
|
|
96
|
+
*
|
|
97
|
+
* @return Promise
|
|
98
|
+
*/
|
|
99
|
+
// @ts-ignore
|
|
100
|
+
async blob() {
|
|
101
|
+
const ct = this.headers?.get("content-type") || this[INTERNALS].body && this[INTERNALS].body.type || "";
|
|
102
|
+
const buf = await this.arrayBuffer();
|
|
103
|
+
return new Blob([buf], {
|
|
104
|
+
type: ct
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Decode response as json
|
|
109
|
+
*
|
|
110
|
+
* @return Promise
|
|
111
|
+
*/
|
|
112
|
+
async json() {
|
|
113
|
+
const text = await this.text();
|
|
114
|
+
return JSON.parse(text);
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Decode response as text
|
|
118
|
+
*
|
|
119
|
+
* @return Promise
|
|
120
|
+
*/
|
|
121
|
+
async text() {
|
|
122
|
+
const buffer = await consumeBody(this);
|
|
123
|
+
return new TextDecoder().decode(buffer);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
Object.defineProperties(Body.prototype, {
|
|
127
|
+
body: { enumerable: true },
|
|
128
|
+
bodyUsed: { enumerable: true },
|
|
129
|
+
arrayBuffer: { enumerable: true },
|
|
130
|
+
blob: { enumerable: true },
|
|
131
|
+
json: { enumerable: true },
|
|
132
|
+
text: { enumerable: true },
|
|
133
|
+
data: { get: deprecate(
|
|
134
|
+
() => {
|
|
135
|
+
},
|
|
136
|
+
"data doesn't exist, use json(), text(), arrayBuffer(), or body instead",
|
|
137
|
+
"https://github.com/node-fetch/node-fetch/issues/1000 (response)"
|
|
138
|
+
) }
|
|
139
|
+
});
|
|
140
|
+
async function consumeBody(data) {
|
|
141
|
+
if (data[INTERNALS].disturbed) {
|
|
142
|
+
throw new TypeError(`body used already for: ${data.url}`);
|
|
143
|
+
}
|
|
144
|
+
data[INTERNALS].disturbed = true;
|
|
145
|
+
if (data[INTERNALS].error) {
|
|
146
|
+
throw data[INTERNALS].error;
|
|
147
|
+
}
|
|
148
|
+
const { _stream: body } = data;
|
|
149
|
+
if (body === null) {
|
|
150
|
+
return Buffer.alloc(0);
|
|
151
|
+
}
|
|
152
|
+
if (!(body instanceof Stream)) {
|
|
153
|
+
return Buffer.alloc(0);
|
|
154
|
+
}
|
|
155
|
+
const accum = [];
|
|
156
|
+
let accumBytes = 0;
|
|
157
|
+
try {
|
|
158
|
+
for await (const chunk of body) {
|
|
159
|
+
if (data.size > 0 && accumBytes + chunk.length > data.size) {
|
|
160
|
+
const error = new FetchError(`content size at ${data.url} over limit: ${data.size}`, "max-size");
|
|
161
|
+
body.destroy(error);
|
|
162
|
+
throw error;
|
|
163
|
+
}
|
|
164
|
+
accumBytes += chunk.length;
|
|
165
|
+
accum.push(chunk);
|
|
166
|
+
}
|
|
167
|
+
} catch (error) {
|
|
168
|
+
const error_ = error instanceof FetchBaseError ? error : new FetchError(`Invalid response body while trying to fetch ${data.url}: ${error.message}`, "system", error);
|
|
169
|
+
throw error_;
|
|
170
|
+
}
|
|
171
|
+
if (body.readableEnded === true || body._readableState.ended === true) {
|
|
172
|
+
try {
|
|
173
|
+
if (accum.every((c) => typeof c === "string")) {
|
|
174
|
+
return Buffer.from(accum.join(""));
|
|
175
|
+
}
|
|
176
|
+
return Buffer.concat(accum, accumBytes);
|
|
177
|
+
} catch (error) {
|
|
178
|
+
throw new FetchError(`Could not create Buffer from response body for ${data.url}: ${error.message}`, "system", error);
|
|
179
|
+
}
|
|
180
|
+
} else {
|
|
181
|
+
throw new FetchError(`Premature close of server response while trying to fetch ${data.url}`);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
const clone = (instance, highWaterMark) => {
|
|
185
|
+
let p1;
|
|
186
|
+
let p2;
|
|
187
|
+
let { body } = instance[INTERNALS];
|
|
188
|
+
if (instance.bodyUsed) {
|
|
189
|
+
throw new Error("cannot clone body after it is used");
|
|
190
|
+
}
|
|
191
|
+
if (body instanceof Stream && typeof body.getBoundary !== "function") {
|
|
192
|
+
p1 = new PassThrough({ highWaterMark });
|
|
193
|
+
p2 = new PassThrough({ highWaterMark });
|
|
194
|
+
body.pipe(p1);
|
|
195
|
+
body.pipe(p2);
|
|
196
|
+
instance[INTERNALS].stream = p1;
|
|
197
|
+
body = p2;
|
|
198
|
+
}
|
|
199
|
+
return body;
|
|
200
|
+
};
|
|
201
|
+
const extractContentType = (body, request) => {
|
|
202
|
+
if (body === null) {
|
|
203
|
+
return null;
|
|
204
|
+
}
|
|
205
|
+
if (typeof body === "string") {
|
|
206
|
+
return "text/plain;charset=UTF-8";
|
|
207
|
+
}
|
|
208
|
+
if (isURLSearchParameters(body)) {
|
|
209
|
+
return "application/x-www-form-urlencoded;charset=UTF-8";
|
|
210
|
+
}
|
|
211
|
+
if (isBlob(body)) {
|
|
212
|
+
return body.type || null;
|
|
213
|
+
}
|
|
214
|
+
if (Buffer.isBuffer(body) || types.isAnyArrayBuffer(body) || ArrayBuffer.isView(body)) {
|
|
215
|
+
return null;
|
|
216
|
+
}
|
|
217
|
+
if (body instanceof FormData) {
|
|
218
|
+
return `multipart/form-data; boundary=${request[INTERNALS].boundary}`;
|
|
219
|
+
}
|
|
220
|
+
if (body instanceof Stream) {
|
|
221
|
+
return null;
|
|
222
|
+
}
|
|
223
|
+
return "text/plain;charset=UTF-8";
|
|
224
|
+
};
|
|
225
|
+
const getTotalBytes = (request) => {
|
|
226
|
+
const { body } = request[INTERNALS];
|
|
227
|
+
if (body === null) {
|
|
228
|
+
return 0;
|
|
229
|
+
}
|
|
230
|
+
if (isBlob(body)) {
|
|
231
|
+
return body.size;
|
|
232
|
+
}
|
|
233
|
+
if (Buffer.isBuffer(body)) {
|
|
234
|
+
return body.length;
|
|
235
|
+
}
|
|
236
|
+
if (body && typeof body.getLengthSync === "function") {
|
|
237
|
+
const anyBody = body;
|
|
238
|
+
return anyBody.hasKnownLength && anyBody.hasKnownLength() ? anyBody.getLengthSync() : null;
|
|
239
|
+
}
|
|
240
|
+
return null;
|
|
241
|
+
};
|
|
242
|
+
const writeToStream = async (dest, { body }) => {
|
|
243
|
+
if (body === null) {
|
|
244
|
+
dest.end();
|
|
245
|
+
} else {
|
|
246
|
+
await pipeline(body, dest);
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
export {
|
|
250
|
+
clone,
|
|
251
|
+
Body as default,
|
|
252
|
+
extractContentType,
|
|
253
|
+
getTotalBytes,
|
|
254
|
+
writeToStream
|
|
255
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
class FetchBaseError extends Error {
|
|
2
|
+
type;
|
|
3
|
+
constructor(message, type) {
|
|
4
|
+
super(message);
|
|
5
|
+
Error.captureStackTrace(this, this.constructor);
|
|
6
|
+
this.type = type;
|
|
7
|
+
}
|
|
8
|
+
get name() {
|
|
9
|
+
return this.constructor.name;
|
|
10
|
+
}
|
|
11
|
+
get [Symbol.toStringTag]() {
|
|
12
|
+
return this.constructor.name;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
export {
|
|
16
|
+
FetchBaseError
|
|
17
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { FetchBaseError } from "./base.js";
|
|
2
|
+
class FetchError extends FetchBaseError {
|
|
3
|
+
code;
|
|
4
|
+
errno;
|
|
5
|
+
erroredSysCall;
|
|
6
|
+
/**
|
|
7
|
+
* @param message Error message for human
|
|
8
|
+
* @param type Error type for machine
|
|
9
|
+
* @param systemError For Node.js system error
|
|
10
|
+
*/
|
|
11
|
+
constructor(message, type, systemError) {
|
|
12
|
+
super(message, type);
|
|
13
|
+
if (systemError) {
|
|
14
|
+
this.code = this.errno = systemError.code;
|
|
15
|
+
this.erroredSysCall = systemError.syscall;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
export {
|
|
20
|
+
FetchError
|
|
21
|
+
};
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import Soup from "@girs/soup-3.0";
|
|
2
|
+
import { URLSearchParams } from "@gjsify/deno-runtime/ext/url/00_url";
|
|
3
|
+
import { types } from "util";
|
|
4
|
+
import * as http from "http";
|
|
5
|
+
const validateHeaderName = http.validateHeaderName;
|
|
6
|
+
const validateHeaderValue = http.validateHeaderValue;
|
|
7
|
+
class Headers extends URLSearchParams {
|
|
8
|
+
/**
|
|
9
|
+
* Headers class
|
|
10
|
+
*
|
|
11
|
+
* @constructor
|
|
12
|
+
* @param init Response headers
|
|
13
|
+
*/
|
|
14
|
+
constructor(init) {
|
|
15
|
+
let result = [];
|
|
16
|
+
if (init instanceof Headers) {
|
|
17
|
+
const raw = init.raw();
|
|
18
|
+
for (const [name, values] of Object.entries(raw)) {
|
|
19
|
+
result.push(...values.map((value) => [name, value]));
|
|
20
|
+
}
|
|
21
|
+
} else if (init == null) {
|
|
22
|
+
} else if (typeof init === "object" && !types.isBoxedPrimitive(init)) {
|
|
23
|
+
const method = init[Symbol.iterator];
|
|
24
|
+
if (method == null) {
|
|
25
|
+
result.push(...Object.entries(init));
|
|
26
|
+
} else {
|
|
27
|
+
if (typeof method !== "function") {
|
|
28
|
+
throw new TypeError("Header pairs must be iterable");
|
|
29
|
+
}
|
|
30
|
+
result = [...init].map((pair) => {
|
|
31
|
+
if (typeof pair !== "object" || types.isBoxedPrimitive(pair)) {
|
|
32
|
+
throw new TypeError("Each header pair must be an iterable object");
|
|
33
|
+
}
|
|
34
|
+
return [...pair];
|
|
35
|
+
}).map((pair) => {
|
|
36
|
+
if (pair.length !== 2) {
|
|
37
|
+
throw new TypeError("Each header pair must be a name/value tuple");
|
|
38
|
+
}
|
|
39
|
+
return [...pair];
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
} else {
|
|
43
|
+
throw new TypeError("Failed to construct 'Headers': The provided value is not of type '(sequence<sequence<ByteString>> or record<ByteString, ByteString>)");
|
|
44
|
+
}
|
|
45
|
+
result = result.length > 0 ? result.map(([name, value]) => {
|
|
46
|
+
validateHeaderName(name);
|
|
47
|
+
validateHeaderValue(name, String(value));
|
|
48
|
+
return [String(name).toLowerCase(), String(value)];
|
|
49
|
+
}) : void 0;
|
|
50
|
+
super(result);
|
|
51
|
+
return new Proxy(this, {
|
|
52
|
+
get(target, p, receiver) {
|
|
53
|
+
switch (p) {
|
|
54
|
+
case "append":
|
|
55
|
+
case "set":
|
|
56
|
+
return (name, value) => {
|
|
57
|
+
validateHeaderName(name);
|
|
58
|
+
validateHeaderValue(name, String(value));
|
|
59
|
+
return URLSearchParams.prototype[p].call(
|
|
60
|
+
target,
|
|
61
|
+
String(name).toLowerCase(),
|
|
62
|
+
String(value)
|
|
63
|
+
);
|
|
64
|
+
};
|
|
65
|
+
case "delete":
|
|
66
|
+
case "has":
|
|
67
|
+
case "getAll":
|
|
68
|
+
return (name) => {
|
|
69
|
+
validateHeaderName(name);
|
|
70
|
+
return URLSearchParams.prototype[p].call(
|
|
71
|
+
target,
|
|
72
|
+
String(name).toLowerCase()
|
|
73
|
+
);
|
|
74
|
+
};
|
|
75
|
+
case "keys":
|
|
76
|
+
return () => {
|
|
77
|
+
target.sort();
|
|
78
|
+
return new Set(URLSearchParams.prototype.keys.call(target)).keys();
|
|
79
|
+
};
|
|
80
|
+
default:
|
|
81
|
+
return Reflect.get(target, p, receiver);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
get [Symbol.toStringTag]() {
|
|
87
|
+
return this.constructor.name;
|
|
88
|
+
}
|
|
89
|
+
toString() {
|
|
90
|
+
return Object.prototype.toString.call(this);
|
|
91
|
+
}
|
|
92
|
+
_appendToSoupMessage(message, type = Soup.MessageHeadersType.REQUEST) {
|
|
93
|
+
const soupHeaders = message ? message.get_request_headers() : new Soup.MessageHeaders(type);
|
|
94
|
+
for (const header in this.entries()) {
|
|
95
|
+
soupHeaders.append(header, this.get(header));
|
|
96
|
+
}
|
|
97
|
+
return soupHeaders;
|
|
98
|
+
}
|
|
99
|
+
static _newFromSoupMessage(message, type = Soup.MessageHeadersType.RESPONSE) {
|
|
100
|
+
let soupHeaders;
|
|
101
|
+
const headers = new Headers();
|
|
102
|
+
if (type === Soup.MessageHeadersType.RESPONSE) {
|
|
103
|
+
soupHeaders = message.get_response_headers();
|
|
104
|
+
} else if (type === Soup.MessageHeadersType.REQUEST) {
|
|
105
|
+
soupHeaders = message.get_request_headers();
|
|
106
|
+
} else {
|
|
107
|
+
for (const header in message.get_request_headers()) {
|
|
108
|
+
headers.append(header, soupHeaders[header]);
|
|
109
|
+
}
|
|
110
|
+
soupHeaders = message.get_response_headers();
|
|
111
|
+
}
|
|
112
|
+
for (const header in soupHeaders) {
|
|
113
|
+
headers.append(header, soupHeaders[header]);
|
|
114
|
+
}
|
|
115
|
+
return headers;
|
|
116
|
+
}
|
|
117
|
+
get(name) {
|
|
118
|
+
const values = this.getAll(name);
|
|
119
|
+
if (values.length === 0) {
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
let value = values.join(", ");
|
|
123
|
+
if (/^content-encoding$/i.test(name)) {
|
|
124
|
+
value = value.toLowerCase();
|
|
125
|
+
}
|
|
126
|
+
return value;
|
|
127
|
+
}
|
|
128
|
+
forEach(callback, thisArg = void 0) {
|
|
129
|
+
for (const name of this.keys()) {
|
|
130
|
+
Reflect.apply(callback, thisArg, [this.get(name), name, this]);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
*values() {
|
|
134
|
+
for (const name of this.keys()) {
|
|
135
|
+
yield this.get(name);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
*
|
|
140
|
+
*/
|
|
141
|
+
*entries() {
|
|
142
|
+
for (const name of this.keys()) {
|
|
143
|
+
yield [name, this.get(name)];
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
[Symbol.iterator]() {
|
|
147
|
+
return this.entries();
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Node-fetch non-spec method
|
|
151
|
+
* returning all headers and their values as array
|
|
152
|
+
*/
|
|
153
|
+
raw() {
|
|
154
|
+
return [...this.keys()].reduce((result, key) => {
|
|
155
|
+
result[key] = this.getAll(key);
|
|
156
|
+
return result;
|
|
157
|
+
}, {});
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* For better console.log(headers) and also to convert Headers into Node.js Request compatible format
|
|
161
|
+
*/
|
|
162
|
+
[Symbol.for("nodejs.util.inspect.custom")]() {
|
|
163
|
+
return [...this.keys()].reduce((result, key) => {
|
|
164
|
+
const values = this.getAll(key);
|
|
165
|
+
if (key === "host") {
|
|
166
|
+
result[key] = values[0];
|
|
167
|
+
} else {
|
|
168
|
+
result[key] = values.length > 1 ? values : values[0];
|
|
169
|
+
}
|
|
170
|
+
return result;
|
|
171
|
+
}, {});
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
Object.defineProperties(
|
|
175
|
+
Headers.prototype,
|
|
176
|
+
["get", "entries", "forEach", "values"].reduce((result, property) => {
|
|
177
|
+
result[property] = { enumerable: true };
|
|
178
|
+
return result;
|
|
179
|
+
}, {})
|
|
180
|
+
);
|
|
181
|
+
function fromRawHeaders(headers = []) {
|
|
182
|
+
return new Headers(
|
|
183
|
+
headers.reduce((result, value, index, array) => {
|
|
184
|
+
if (index % 2 === 0) {
|
|
185
|
+
result.push(array.slice(index, index + 2));
|
|
186
|
+
}
|
|
187
|
+
return result;
|
|
188
|
+
}, []).filter(([name, value]) => {
|
|
189
|
+
try {
|
|
190
|
+
validateHeaderName(name);
|
|
191
|
+
validateHeaderValue(name, String(value));
|
|
192
|
+
return true;
|
|
193
|
+
} catch {
|
|
194
|
+
return false;
|
|
195
|
+
}
|
|
196
|
+
})
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
export {
|
|
200
|
+
Headers as default,
|
|
201
|
+
fromRawHeaders
|
|
202
|
+
};
|