@sveltejs/kit 1.0.0-next.43 → 1.0.0-next.430
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 +12 -9
- package/package.json +95 -63
- package/src/cli.js +112 -0
- package/src/core/adapt/builder.js +207 -0
- package/src/core/adapt/index.js +19 -0
- package/src/core/config/index.js +86 -0
- package/src/core/config/options.js +488 -0
- package/src/core/config/types.d.ts +1 -0
- package/src/core/constants.js +5 -0
- package/src/core/env.js +97 -0
- package/src/core/generate_manifest/index.js +99 -0
- package/src/core/prerender/crawl.js +194 -0
- package/src/core/prerender/prerender.js +378 -0
- package/src/core/prerender/queue.js +80 -0
- package/src/core/sync/create_manifest_data/index.js +506 -0
- package/src/core/sync/create_manifest_data/types.d.ts +40 -0
- package/src/core/sync/sync.js +59 -0
- package/src/core/sync/utils.js +44 -0
- package/src/core/sync/write_ambient.js +27 -0
- package/src/core/sync/write_client_manifest.js +82 -0
- package/src/core/sync/write_matchers.js +25 -0
- package/src/core/sync/write_root.js +91 -0
- package/src/core/sync/write_tsconfig.js +195 -0
- package/src/core/sync/write_types.js +775 -0
- package/src/core/utils.js +70 -0
- package/src/hooks.js +26 -0
- package/src/index/index.js +45 -0
- package/src/index/private.js +33 -0
- package/src/node/index.js +145 -0
- package/src/node/polyfills.js +40 -0
- package/src/runtime/app/env.js +11 -0
- package/src/runtime/app/navigation.js +22 -0
- package/src/runtime/app/paths.js +1 -0
- package/src/runtime/app/stores.js +102 -0
- package/src/runtime/client/ambient.d.ts +17 -0
- package/src/runtime/client/client.js +1289 -0
- package/src/runtime/client/fetcher.js +60 -0
- package/src/runtime/client/parse.js +36 -0
- package/src/runtime/client/singletons.js +21 -0
- package/src/runtime/client/start.js +46 -0
- package/src/runtime/client/types.d.ts +105 -0
- package/src/runtime/client/utils.js +113 -0
- package/src/runtime/components/error.svelte +16 -0
- package/{assets → src/runtime}/components/layout.svelte +0 -0
- package/src/runtime/env/dynamic/private.js +1 -0
- package/src/runtime/env/dynamic/public.js +1 -0
- package/src/runtime/env-private.js +7 -0
- package/src/runtime/env-public.js +7 -0
- package/src/runtime/env.js +6 -0
- package/src/runtime/hash.js +16 -0
- package/src/runtime/paths.js +11 -0
- package/src/runtime/server/endpoint.js +58 -0
- package/src/runtime/server/index.js +448 -0
- package/src/runtime/server/page/cookie.js +25 -0
- package/src/runtime/server/page/crypto.js +239 -0
- package/src/runtime/server/page/csp.js +249 -0
- package/src/runtime/server/page/fetch.js +266 -0
- package/src/runtime/server/page/index.js +416 -0
- package/src/runtime/server/page/load_data.js +135 -0
- package/src/runtime/server/page/render.js +362 -0
- package/src/runtime/server/page/respond_with_error.js +94 -0
- package/src/runtime/server/page/types.d.ts +44 -0
- package/src/runtime/server/utils.js +116 -0
- package/src/utils/error.js +22 -0
- package/src/utils/escape.js +104 -0
- package/src/utils/filesystem.js +108 -0
- package/src/utils/http.js +55 -0
- package/src/utils/misc.js +1 -0
- package/src/utils/routing.js +108 -0
- package/src/utils/url.js +97 -0
- package/src/vite/build/build_server.js +337 -0
- package/src/vite/build/build_service_worker.js +90 -0
- package/src/vite/build/utils.js +160 -0
- package/src/vite/dev/index.js +551 -0
- package/src/vite/index.js +574 -0
- package/src/vite/preview/index.js +186 -0
- package/src/vite/types.d.ts +3 -0
- package/src/vite/utils.js +345 -0
- package/svelte-kit.js +1 -1
- package/types/ambient.d.ts +357 -0
- package/types/index.d.ts +343 -0
- package/types/internal.d.ts +308 -0
- package/types/private.d.ts +209 -0
- package/CHANGELOG.md +0 -431
- package/assets/components/error.svelte +0 -13
- package/assets/runtime/app/env.js +0 -5
- package/assets/runtime/app/navigation.js +0 -41
- package/assets/runtime/app/paths.js +0 -1
- package/assets/runtime/app/stores.js +0 -93
- package/assets/runtime/chunks/utils.js +0 -19
- package/assets/runtime/internal/singletons.js +0 -23
- package/assets/runtime/internal/start.js +0 -770
- package/assets/runtime/paths.js +0 -12
- package/dist/.DS_Store +0 -0
- package/dist/chunks/index.js +0 -3521
- package/dist/chunks/index2.js +0 -587
- package/dist/chunks/index3.js +0 -246
- package/dist/chunks/index4.js +0 -538
- package/dist/chunks/index5.js +0 -761
- package/dist/chunks/index6.js +0 -322
- package/dist/chunks/standard.js +0 -99
- package/dist/chunks/utils.js +0 -83
- package/dist/cli.js +0 -546
- package/dist/ssr.js +0 -2581
package/dist/ssr.js
DELETED
|
@@ -1,2581 +0,0 @@
|
|
|
1
|
-
import { randomBytes, createHash } from 'crypto';
|
|
2
|
-
import http from 'http';
|
|
3
|
-
import https from 'https';
|
|
4
|
-
import zlib from 'zlib';
|
|
5
|
-
import Stream, { PassThrough, pipeline } from 'stream';
|
|
6
|
-
import { types } from 'util';
|
|
7
|
-
import { format, parse, resolve, URLSearchParams as URLSearchParams$1 } from 'url';
|
|
8
|
-
|
|
9
|
-
var chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_$';
|
|
10
|
-
var unsafeChars = /[<>\b\f\n\r\t\0\u2028\u2029]/g;
|
|
11
|
-
var reserved = /^(?:do|if|in|for|int|let|new|try|var|byte|case|char|else|enum|goto|long|this|void|with|await|break|catch|class|const|final|float|short|super|throw|while|yield|delete|double|export|import|native|return|switch|throws|typeof|boolean|default|extends|finally|package|private|abstract|continue|debugger|function|volatile|interface|protected|transient|implements|instanceof|synchronized)$/;
|
|
12
|
-
var escaped = {
|
|
13
|
-
'<': '\\u003C',
|
|
14
|
-
'>': '\\u003E',
|
|
15
|
-
'/': '\\u002F',
|
|
16
|
-
'\\': '\\\\',
|
|
17
|
-
'\b': '\\b',
|
|
18
|
-
'\f': '\\f',
|
|
19
|
-
'\n': '\\n',
|
|
20
|
-
'\r': '\\r',
|
|
21
|
-
'\t': '\\t',
|
|
22
|
-
'\0': '\\0',
|
|
23
|
-
'\u2028': '\\u2028',
|
|
24
|
-
'\u2029': '\\u2029'
|
|
25
|
-
};
|
|
26
|
-
var objectProtoOwnPropertyNames = Object.getOwnPropertyNames(Object.prototype).sort().join('\0');
|
|
27
|
-
function devalue(value) {
|
|
28
|
-
var counts = new Map();
|
|
29
|
-
function walk(thing) {
|
|
30
|
-
if (typeof thing === 'function') {
|
|
31
|
-
throw new Error("Cannot stringify a function");
|
|
32
|
-
}
|
|
33
|
-
if (counts.has(thing)) {
|
|
34
|
-
counts.set(thing, counts.get(thing) + 1);
|
|
35
|
-
return;
|
|
36
|
-
}
|
|
37
|
-
counts.set(thing, 1);
|
|
38
|
-
if (!isPrimitive(thing)) {
|
|
39
|
-
var type = getType(thing);
|
|
40
|
-
switch (type) {
|
|
41
|
-
case 'Number':
|
|
42
|
-
case 'String':
|
|
43
|
-
case 'Boolean':
|
|
44
|
-
case 'Date':
|
|
45
|
-
case 'RegExp':
|
|
46
|
-
return;
|
|
47
|
-
case 'Array':
|
|
48
|
-
thing.forEach(walk);
|
|
49
|
-
break;
|
|
50
|
-
case 'Set':
|
|
51
|
-
case 'Map':
|
|
52
|
-
Array.from(thing).forEach(walk);
|
|
53
|
-
break;
|
|
54
|
-
default:
|
|
55
|
-
var proto = Object.getPrototypeOf(thing);
|
|
56
|
-
if (proto !== Object.prototype &&
|
|
57
|
-
proto !== null &&
|
|
58
|
-
Object.getOwnPropertyNames(proto).sort().join('\0') !== objectProtoOwnPropertyNames) {
|
|
59
|
-
throw new Error("Cannot stringify arbitrary non-POJOs");
|
|
60
|
-
}
|
|
61
|
-
if (Object.getOwnPropertySymbols(thing).length > 0) {
|
|
62
|
-
throw new Error("Cannot stringify POJOs with symbolic keys");
|
|
63
|
-
}
|
|
64
|
-
Object.keys(thing).forEach(function (key) { return walk(thing[key]); });
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
walk(value);
|
|
69
|
-
var names = new Map();
|
|
70
|
-
Array.from(counts)
|
|
71
|
-
.filter(function (entry) { return entry[1] > 1; })
|
|
72
|
-
.sort(function (a, b) { return b[1] - a[1]; })
|
|
73
|
-
.forEach(function (entry, i) {
|
|
74
|
-
names.set(entry[0], getName(i));
|
|
75
|
-
});
|
|
76
|
-
function stringify(thing) {
|
|
77
|
-
if (names.has(thing)) {
|
|
78
|
-
return names.get(thing);
|
|
79
|
-
}
|
|
80
|
-
if (isPrimitive(thing)) {
|
|
81
|
-
return stringifyPrimitive(thing);
|
|
82
|
-
}
|
|
83
|
-
var type = getType(thing);
|
|
84
|
-
switch (type) {
|
|
85
|
-
case 'Number':
|
|
86
|
-
case 'String':
|
|
87
|
-
case 'Boolean':
|
|
88
|
-
return "Object(" + stringify(thing.valueOf()) + ")";
|
|
89
|
-
case 'RegExp':
|
|
90
|
-
return "new RegExp(" + stringifyString(thing.source) + ", \"" + thing.flags + "\")";
|
|
91
|
-
case 'Date':
|
|
92
|
-
return "new Date(" + thing.getTime() + ")";
|
|
93
|
-
case 'Array':
|
|
94
|
-
var members = thing.map(function (v, i) { return i in thing ? stringify(v) : ''; });
|
|
95
|
-
var tail = thing.length === 0 || (thing.length - 1 in thing) ? '' : ',';
|
|
96
|
-
return "[" + members.join(',') + tail + "]";
|
|
97
|
-
case 'Set':
|
|
98
|
-
case 'Map':
|
|
99
|
-
return "new " + type + "([" + Array.from(thing).map(stringify).join(',') + "])";
|
|
100
|
-
default:
|
|
101
|
-
var obj = "{" + Object.keys(thing).map(function (key) { return safeKey(key) + ":" + stringify(thing[key]); }).join(',') + "}";
|
|
102
|
-
var proto = Object.getPrototypeOf(thing);
|
|
103
|
-
if (proto === null) {
|
|
104
|
-
return Object.keys(thing).length > 0
|
|
105
|
-
? "Object.assign(Object.create(null)," + obj + ")"
|
|
106
|
-
: "Object.create(null)";
|
|
107
|
-
}
|
|
108
|
-
return obj;
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
var str = stringify(value);
|
|
112
|
-
if (names.size) {
|
|
113
|
-
var params_1 = [];
|
|
114
|
-
var statements_1 = [];
|
|
115
|
-
var values_1 = [];
|
|
116
|
-
names.forEach(function (name, thing) {
|
|
117
|
-
params_1.push(name);
|
|
118
|
-
if (isPrimitive(thing)) {
|
|
119
|
-
values_1.push(stringifyPrimitive(thing));
|
|
120
|
-
return;
|
|
121
|
-
}
|
|
122
|
-
var type = getType(thing);
|
|
123
|
-
switch (type) {
|
|
124
|
-
case 'Number':
|
|
125
|
-
case 'String':
|
|
126
|
-
case 'Boolean':
|
|
127
|
-
values_1.push("Object(" + stringify(thing.valueOf()) + ")");
|
|
128
|
-
break;
|
|
129
|
-
case 'RegExp':
|
|
130
|
-
values_1.push(thing.toString());
|
|
131
|
-
break;
|
|
132
|
-
case 'Date':
|
|
133
|
-
values_1.push("new Date(" + thing.getTime() + ")");
|
|
134
|
-
break;
|
|
135
|
-
case 'Array':
|
|
136
|
-
values_1.push("Array(" + thing.length + ")");
|
|
137
|
-
thing.forEach(function (v, i) {
|
|
138
|
-
statements_1.push(name + "[" + i + "]=" + stringify(v));
|
|
139
|
-
});
|
|
140
|
-
break;
|
|
141
|
-
case 'Set':
|
|
142
|
-
values_1.push("new Set");
|
|
143
|
-
statements_1.push(name + "." + Array.from(thing).map(function (v) { return "add(" + stringify(v) + ")"; }).join('.'));
|
|
144
|
-
break;
|
|
145
|
-
case 'Map':
|
|
146
|
-
values_1.push("new Map");
|
|
147
|
-
statements_1.push(name + "." + Array.from(thing).map(function (_a) {
|
|
148
|
-
var k = _a[0], v = _a[1];
|
|
149
|
-
return "set(" + stringify(k) + ", " + stringify(v) + ")";
|
|
150
|
-
}).join('.'));
|
|
151
|
-
break;
|
|
152
|
-
default:
|
|
153
|
-
values_1.push(Object.getPrototypeOf(thing) === null ? 'Object.create(null)' : '{}');
|
|
154
|
-
Object.keys(thing).forEach(function (key) {
|
|
155
|
-
statements_1.push("" + name + safeProp(key) + "=" + stringify(thing[key]));
|
|
156
|
-
});
|
|
157
|
-
}
|
|
158
|
-
});
|
|
159
|
-
statements_1.push("return " + str);
|
|
160
|
-
return "(function(" + params_1.join(',') + "){" + statements_1.join(';') + "}(" + values_1.join(',') + "))";
|
|
161
|
-
}
|
|
162
|
-
else {
|
|
163
|
-
return str;
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
function getName(num) {
|
|
167
|
-
var name = '';
|
|
168
|
-
do {
|
|
169
|
-
name = chars[num % chars.length] + name;
|
|
170
|
-
num = ~~(num / chars.length) - 1;
|
|
171
|
-
} while (num >= 0);
|
|
172
|
-
return reserved.test(name) ? name + "_" : name;
|
|
173
|
-
}
|
|
174
|
-
function isPrimitive(thing) {
|
|
175
|
-
return Object(thing) !== thing;
|
|
176
|
-
}
|
|
177
|
-
function stringifyPrimitive(thing) {
|
|
178
|
-
if (typeof thing === 'string')
|
|
179
|
-
return stringifyString(thing);
|
|
180
|
-
if (thing === void 0)
|
|
181
|
-
return 'void 0';
|
|
182
|
-
if (thing === 0 && 1 / thing < 0)
|
|
183
|
-
return '-0';
|
|
184
|
-
var str = String(thing);
|
|
185
|
-
if (typeof thing === 'number')
|
|
186
|
-
return str.replace(/^(-)?0\./, '$1.');
|
|
187
|
-
return str;
|
|
188
|
-
}
|
|
189
|
-
function getType(thing) {
|
|
190
|
-
return Object.prototype.toString.call(thing).slice(8, -1);
|
|
191
|
-
}
|
|
192
|
-
function escapeUnsafeChar(c) {
|
|
193
|
-
return escaped[c] || c;
|
|
194
|
-
}
|
|
195
|
-
function escapeUnsafeChars(str) {
|
|
196
|
-
return str.replace(unsafeChars, escapeUnsafeChar);
|
|
197
|
-
}
|
|
198
|
-
function safeKey(key) {
|
|
199
|
-
return /^[_$a-zA-Z][_$a-zA-Z0-9]*$/.test(key) ? key : escapeUnsafeChars(JSON.stringify(key));
|
|
200
|
-
}
|
|
201
|
-
function safeProp(key) {
|
|
202
|
-
return /^[_$a-zA-Z][_$a-zA-Z0-9]*$/.test(key) ? "." + key : "[" + escapeUnsafeChars(JSON.stringify(key)) + "]";
|
|
203
|
-
}
|
|
204
|
-
function stringifyString(str) {
|
|
205
|
-
var result = '"';
|
|
206
|
-
for (var i = 0; i < str.length; i += 1) {
|
|
207
|
-
var char = str.charAt(i);
|
|
208
|
-
var code = char.charCodeAt(0);
|
|
209
|
-
if (char === '"') {
|
|
210
|
-
result += '\\"';
|
|
211
|
-
}
|
|
212
|
-
else if (char in escaped) {
|
|
213
|
-
result += escaped[char];
|
|
214
|
-
}
|
|
215
|
-
else if (code >= 0xd800 && code <= 0xdfff) {
|
|
216
|
-
var next = str.charCodeAt(i + 1);
|
|
217
|
-
// If this is the beginning of a [high, low] surrogate pair,
|
|
218
|
-
// add the next two characters, otherwise escape
|
|
219
|
-
if (code <= 0xdbff && (next >= 0xdc00 && next <= 0xdfff)) {
|
|
220
|
-
result += char + str[++i];
|
|
221
|
-
}
|
|
222
|
-
else {
|
|
223
|
-
result += "\\u" + code.toString(16).toUpperCase();
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
else {
|
|
227
|
-
result += char;
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
result += '"';
|
|
231
|
-
return result;
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
/**
|
|
235
|
-
* Returns a `Buffer` instance from the given data URI `uri`.
|
|
236
|
-
*
|
|
237
|
-
* @param {String} uri Data URI to turn into a Buffer instance
|
|
238
|
-
* @return {Buffer} Buffer instance from Data URI
|
|
239
|
-
* @api public
|
|
240
|
-
*/
|
|
241
|
-
function dataUriToBuffer(uri) {
|
|
242
|
-
if (!/^data:/i.test(uri)) {
|
|
243
|
-
throw new TypeError('`uri` does not appear to be a Data URI (must begin with "data:")');
|
|
244
|
-
}
|
|
245
|
-
// strip newlines
|
|
246
|
-
uri = uri.replace(/\r?\n/g, '');
|
|
247
|
-
// split the URI up into the "metadata" and the "data" portions
|
|
248
|
-
const firstComma = uri.indexOf(',');
|
|
249
|
-
if (firstComma === -1 || firstComma <= 4) {
|
|
250
|
-
throw new TypeError('malformed data: URI');
|
|
251
|
-
}
|
|
252
|
-
// remove the "data:" scheme and parse the metadata
|
|
253
|
-
const meta = uri.substring(5, firstComma).split(';');
|
|
254
|
-
let charset = '';
|
|
255
|
-
let base64 = false;
|
|
256
|
-
const type = meta[0] || 'text/plain';
|
|
257
|
-
let typeFull = type;
|
|
258
|
-
for (let i = 1; i < meta.length; i++) {
|
|
259
|
-
if (meta[i] === 'base64') {
|
|
260
|
-
base64 = true;
|
|
261
|
-
}
|
|
262
|
-
else {
|
|
263
|
-
typeFull += `;${meta[i]}`;
|
|
264
|
-
if (meta[i].indexOf('charset=') === 0) {
|
|
265
|
-
charset = meta[i].substring(8);
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
// defaults to US-ASCII only if type is not provided
|
|
270
|
-
if (!meta[0] && !charset.length) {
|
|
271
|
-
typeFull += ';charset=US-ASCII';
|
|
272
|
-
charset = 'US-ASCII';
|
|
273
|
-
}
|
|
274
|
-
// get the encoded data portion and decode URI-encoded chars
|
|
275
|
-
const encoding = base64 ? 'base64' : 'ascii';
|
|
276
|
-
const data = unescape(uri.substring(firstComma + 1));
|
|
277
|
-
const buffer = Buffer.from(data, encoding);
|
|
278
|
-
// set `.type` and `.typeFull` properties to MIME type
|
|
279
|
-
buffer.type = type;
|
|
280
|
-
buffer.typeFull = typeFull;
|
|
281
|
-
// set the `.charset` property
|
|
282
|
-
buffer.charset = charset;
|
|
283
|
-
return buffer;
|
|
284
|
-
}
|
|
285
|
-
var src = dataUriToBuffer;
|
|
286
|
-
|
|
287
|
-
const {Readable} = Stream;
|
|
288
|
-
|
|
289
|
-
/**
|
|
290
|
-
* @type {WeakMap<Blob, {type: string, size: number, parts: (Blob | Buffer)[] }>}
|
|
291
|
-
*/
|
|
292
|
-
const wm = new WeakMap();
|
|
293
|
-
|
|
294
|
-
async function * read(parts) {
|
|
295
|
-
for (const part of parts) {
|
|
296
|
-
if ('stream' in part) {
|
|
297
|
-
yield * part.stream();
|
|
298
|
-
} else {
|
|
299
|
-
yield part;
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
class Blob {
|
|
305
|
-
/**
|
|
306
|
-
* The Blob() constructor returns a new Blob object. The content
|
|
307
|
-
* of the blob consists of the concatenation of the values given
|
|
308
|
-
* in the parameter array.
|
|
309
|
-
*
|
|
310
|
-
* @param {(ArrayBufferLike | ArrayBufferView | Blob | Buffer | string)[]} blobParts
|
|
311
|
-
* @param {{ type?: string }} [options]
|
|
312
|
-
*/
|
|
313
|
-
constructor(blobParts = [], options = {type: ''}) {
|
|
314
|
-
let size = 0;
|
|
315
|
-
|
|
316
|
-
const parts = blobParts.map(element => {
|
|
317
|
-
let buffer;
|
|
318
|
-
if (element instanceof Buffer) {
|
|
319
|
-
buffer = element;
|
|
320
|
-
} else if (ArrayBuffer.isView(element)) {
|
|
321
|
-
buffer = Buffer.from(element.buffer, element.byteOffset, element.byteLength);
|
|
322
|
-
} else if (element instanceof ArrayBuffer) {
|
|
323
|
-
buffer = Buffer.from(element);
|
|
324
|
-
} else if (element instanceof Blob) {
|
|
325
|
-
buffer = element;
|
|
326
|
-
} else {
|
|
327
|
-
buffer = Buffer.from(typeof element === 'string' ? element : String(element));
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
size += buffer.length || buffer.size || 0;
|
|
331
|
-
return buffer;
|
|
332
|
-
});
|
|
333
|
-
|
|
334
|
-
const type = options.type === undefined ? '' : String(options.type).toLowerCase();
|
|
335
|
-
|
|
336
|
-
wm.set(this, {
|
|
337
|
-
type: /[^\u0020-\u007E]/.test(type) ? '' : type,
|
|
338
|
-
size,
|
|
339
|
-
parts
|
|
340
|
-
});
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
/**
|
|
344
|
-
* The Blob interface's size property returns the
|
|
345
|
-
* size of the Blob in bytes.
|
|
346
|
-
*/
|
|
347
|
-
get size() {
|
|
348
|
-
return wm.get(this).size;
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
/**
|
|
352
|
-
* The type property of a Blob object returns the MIME type of the file.
|
|
353
|
-
*/
|
|
354
|
-
get type() {
|
|
355
|
-
return wm.get(this).type;
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
/**
|
|
359
|
-
* The text() method in the Blob interface returns a Promise
|
|
360
|
-
* that resolves with a string containing the contents of
|
|
361
|
-
* the blob, interpreted as UTF-8.
|
|
362
|
-
*
|
|
363
|
-
* @return {Promise<string>}
|
|
364
|
-
*/
|
|
365
|
-
async text() {
|
|
366
|
-
return Buffer.from(await this.arrayBuffer()).toString();
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
/**
|
|
370
|
-
* The arrayBuffer() method in the Blob interface returns a
|
|
371
|
-
* Promise that resolves with the contents of the blob as
|
|
372
|
-
* binary data contained in an ArrayBuffer.
|
|
373
|
-
*
|
|
374
|
-
* @return {Promise<ArrayBuffer>}
|
|
375
|
-
*/
|
|
376
|
-
async arrayBuffer() {
|
|
377
|
-
const data = new Uint8Array(this.size);
|
|
378
|
-
let offset = 0;
|
|
379
|
-
for await (const chunk of this.stream()) {
|
|
380
|
-
data.set(chunk, offset);
|
|
381
|
-
offset += chunk.length;
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
return data.buffer;
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
/**
|
|
388
|
-
* The Blob interface's stream() method is difference from native
|
|
389
|
-
* and uses node streams instead of whatwg streams.
|
|
390
|
-
*
|
|
391
|
-
* @returns {Readable} Node readable stream
|
|
392
|
-
*/
|
|
393
|
-
stream() {
|
|
394
|
-
return Readable.from(read(wm.get(this).parts));
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
/**
|
|
398
|
-
* The Blob interface's slice() method creates and returns a
|
|
399
|
-
* new Blob object which contains data from a subset of the
|
|
400
|
-
* blob on which it's called.
|
|
401
|
-
*
|
|
402
|
-
* @param {number} [start]
|
|
403
|
-
* @param {number} [end]
|
|
404
|
-
* @param {string} [type]
|
|
405
|
-
*/
|
|
406
|
-
slice(start = 0, end = this.size, type = '') {
|
|
407
|
-
const {size} = this;
|
|
408
|
-
|
|
409
|
-
let relativeStart = start < 0 ? Math.max(size + start, 0) : Math.min(start, size);
|
|
410
|
-
let relativeEnd = end < 0 ? Math.max(size + end, 0) : Math.min(end, size);
|
|
411
|
-
|
|
412
|
-
const span = Math.max(relativeEnd - relativeStart, 0);
|
|
413
|
-
const parts = wm.get(this).parts.values();
|
|
414
|
-
const blobParts = [];
|
|
415
|
-
let added = 0;
|
|
416
|
-
|
|
417
|
-
for (const part of parts) {
|
|
418
|
-
const size = ArrayBuffer.isView(part) ? part.byteLength : part.size;
|
|
419
|
-
if (relativeStart && size <= relativeStart) {
|
|
420
|
-
// Skip the beginning and change the relative
|
|
421
|
-
// start & end position as we skip the unwanted parts
|
|
422
|
-
relativeStart -= size;
|
|
423
|
-
relativeEnd -= size;
|
|
424
|
-
} else {
|
|
425
|
-
const chunk = part.slice(relativeStart, Math.min(size, relativeEnd));
|
|
426
|
-
blobParts.push(chunk);
|
|
427
|
-
added += ArrayBuffer.isView(chunk) ? chunk.byteLength : chunk.size;
|
|
428
|
-
relativeStart = 0; // All next sequental parts should start at 0
|
|
429
|
-
|
|
430
|
-
// don't add the overflow to new blobParts
|
|
431
|
-
if (added >= span) {
|
|
432
|
-
break;
|
|
433
|
-
}
|
|
434
|
-
}
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
const blob = new Blob([], {type});
|
|
438
|
-
Object.assign(wm.get(blob), {size: span, parts: blobParts});
|
|
439
|
-
|
|
440
|
-
return blob;
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
get [Symbol.toStringTag]() {
|
|
444
|
-
return 'Blob';
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
static [Symbol.hasInstance](object) {
|
|
448
|
-
return (
|
|
449
|
-
typeof object === 'object' &&
|
|
450
|
-
typeof object.stream === 'function' &&
|
|
451
|
-
object.stream.length === 0 &&
|
|
452
|
-
typeof object.constructor === 'function' &&
|
|
453
|
-
/^(Blob|File)$/.test(object[Symbol.toStringTag])
|
|
454
|
-
);
|
|
455
|
-
}
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
Object.defineProperties(Blob.prototype, {
|
|
459
|
-
size: {enumerable: true},
|
|
460
|
-
type: {enumerable: true},
|
|
461
|
-
slice: {enumerable: true}
|
|
462
|
-
});
|
|
463
|
-
|
|
464
|
-
var fetchBlob = Blob;
|
|
465
|
-
|
|
466
|
-
class FetchBaseError extends Error {
|
|
467
|
-
constructor(message, type) {
|
|
468
|
-
super(message);
|
|
469
|
-
// Hide custom error implementation details from end-users
|
|
470
|
-
Error.captureStackTrace(this, this.constructor);
|
|
471
|
-
|
|
472
|
-
this.type = type;
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
get name() {
|
|
476
|
-
return this.constructor.name;
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
get [Symbol.toStringTag]() {
|
|
480
|
-
return this.constructor.name;
|
|
481
|
-
}
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
/**
|
|
485
|
-
* @typedef {{ address?: string, code: string, dest?: string, errno: number, info?: object, message: string, path?: string, port?: number, syscall: string}} SystemError
|
|
486
|
-
*/
|
|
487
|
-
|
|
488
|
-
/**
|
|
489
|
-
* FetchError interface for operational errors
|
|
490
|
-
*/
|
|
491
|
-
class FetchError extends FetchBaseError {
|
|
492
|
-
/**
|
|
493
|
-
* @param {string} message - Error message for human
|
|
494
|
-
* @param {string} [type] - Error type for machine
|
|
495
|
-
* @param {SystemError} [systemError] - For Node.js system error
|
|
496
|
-
*/
|
|
497
|
-
constructor(message, type, systemError) {
|
|
498
|
-
super(message, type);
|
|
499
|
-
// When err.type is `system`, err.erroredSysCall contains system error and err.code contains system error code
|
|
500
|
-
if (systemError) {
|
|
501
|
-
// eslint-disable-next-line no-multi-assign
|
|
502
|
-
this.code = this.errno = systemError.code;
|
|
503
|
-
this.erroredSysCall = systemError.syscall;
|
|
504
|
-
}
|
|
505
|
-
}
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
/**
|
|
509
|
-
* Is.js
|
|
510
|
-
*
|
|
511
|
-
* Object type checks.
|
|
512
|
-
*/
|
|
513
|
-
|
|
514
|
-
const NAME = Symbol.toStringTag;
|
|
515
|
-
|
|
516
|
-
/**
|
|
517
|
-
* Check if `obj` is a URLSearchParams object
|
|
518
|
-
* ref: https://github.com/node-fetch/node-fetch/issues/296#issuecomment-307598143
|
|
519
|
-
*
|
|
520
|
-
* @param {*} obj
|
|
521
|
-
* @return {boolean}
|
|
522
|
-
*/
|
|
523
|
-
const isURLSearchParameters = object => {
|
|
524
|
-
return (
|
|
525
|
-
typeof object === 'object' &&
|
|
526
|
-
typeof object.append === 'function' &&
|
|
527
|
-
typeof object.delete === 'function' &&
|
|
528
|
-
typeof object.get === 'function' &&
|
|
529
|
-
typeof object.getAll === 'function' &&
|
|
530
|
-
typeof object.has === 'function' &&
|
|
531
|
-
typeof object.set === 'function' &&
|
|
532
|
-
typeof object.sort === 'function' &&
|
|
533
|
-
object[NAME] === 'URLSearchParams'
|
|
534
|
-
);
|
|
535
|
-
};
|
|
536
|
-
|
|
537
|
-
/**
|
|
538
|
-
* Check if `object` is a W3C `Blob` object (which `File` inherits from)
|
|
539
|
-
*
|
|
540
|
-
* @param {*} obj
|
|
541
|
-
* @return {boolean}
|
|
542
|
-
*/
|
|
543
|
-
const isBlob = object => {
|
|
544
|
-
return (
|
|
545
|
-
typeof object === 'object' &&
|
|
546
|
-
typeof object.arrayBuffer === 'function' &&
|
|
547
|
-
typeof object.type === 'string' &&
|
|
548
|
-
typeof object.stream === 'function' &&
|
|
549
|
-
typeof object.constructor === 'function' &&
|
|
550
|
-
/^(Blob|File)$/.test(object[NAME])
|
|
551
|
-
);
|
|
552
|
-
};
|
|
553
|
-
|
|
554
|
-
/**
|
|
555
|
-
* Check if `obj` is a spec-compliant `FormData` object
|
|
556
|
-
*
|
|
557
|
-
* @param {*} object
|
|
558
|
-
* @return {boolean}
|
|
559
|
-
*/
|
|
560
|
-
function isFormData(object) {
|
|
561
|
-
return (
|
|
562
|
-
typeof object === 'object' &&
|
|
563
|
-
typeof object.append === 'function' &&
|
|
564
|
-
typeof object.set === 'function' &&
|
|
565
|
-
typeof object.get === 'function' &&
|
|
566
|
-
typeof object.getAll === 'function' &&
|
|
567
|
-
typeof object.delete === 'function' &&
|
|
568
|
-
typeof object.keys === 'function' &&
|
|
569
|
-
typeof object.values === 'function' &&
|
|
570
|
-
typeof object.entries === 'function' &&
|
|
571
|
-
typeof object.constructor === 'function' &&
|
|
572
|
-
object[NAME] === 'FormData'
|
|
573
|
-
);
|
|
574
|
-
}
|
|
575
|
-
|
|
576
|
-
/**
|
|
577
|
-
* Check if `obj` is an instance of AbortSignal.
|
|
578
|
-
*
|
|
579
|
-
* @param {*} obj
|
|
580
|
-
* @return {boolean}
|
|
581
|
-
*/
|
|
582
|
-
const isAbortSignal = object => {
|
|
583
|
-
return (
|
|
584
|
-
typeof object === 'object' &&
|
|
585
|
-
object[NAME] === 'AbortSignal'
|
|
586
|
-
);
|
|
587
|
-
};
|
|
588
|
-
|
|
589
|
-
const carriage = '\r\n';
|
|
590
|
-
const dashes = '-'.repeat(2);
|
|
591
|
-
const carriageLength = Buffer.byteLength(carriage);
|
|
592
|
-
|
|
593
|
-
/**
|
|
594
|
-
* @param {string} boundary
|
|
595
|
-
*/
|
|
596
|
-
const getFooter = boundary => `${dashes}${boundary}${dashes}${carriage.repeat(2)}`;
|
|
597
|
-
|
|
598
|
-
/**
|
|
599
|
-
* @param {string} boundary
|
|
600
|
-
* @param {string} name
|
|
601
|
-
* @param {*} field
|
|
602
|
-
*
|
|
603
|
-
* @return {string}
|
|
604
|
-
*/
|
|
605
|
-
function getHeader(boundary, name, field) {
|
|
606
|
-
let header = '';
|
|
607
|
-
|
|
608
|
-
header += `${dashes}${boundary}${carriage}`;
|
|
609
|
-
header += `Content-Disposition: form-data; name="${name}"`;
|
|
610
|
-
|
|
611
|
-
if (isBlob(field)) {
|
|
612
|
-
header += `; filename="${field.name}"${carriage}`;
|
|
613
|
-
header += `Content-Type: ${field.type || 'application/octet-stream'}`;
|
|
614
|
-
}
|
|
615
|
-
|
|
616
|
-
return `${header}${carriage.repeat(2)}`;
|
|
617
|
-
}
|
|
618
|
-
|
|
619
|
-
/**
|
|
620
|
-
* @return {string}
|
|
621
|
-
*/
|
|
622
|
-
const getBoundary = () => randomBytes(8).toString('hex');
|
|
623
|
-
|
|
624
|
-
/**
|
|
625
|
-
* @param {FormData} form
|
|
626
|
-
* @param {string} boundary
|
|
627
|
-
*/
|
|
628
|
-
async function * formDataIterator(form, boundary) {
|
|
629
|
-
for (const [name, value] of form) {
|
|
630
|
-
yield getHeader(boundary, name, value);
|
|
631
|
-
|
|
632
|
-
if (isBlob(value)) {
|
|
633
|
-
yield * value.stream();
|
|
634
|
-
} else {
|
|
635
|
-
yield value;
|
|
636
|
-
}
|
|
637
|
-
|
|
638
|
-
yield carriage;
|
|
639
|
-
}
|
|
640
|
-
|
|
641
|
-
yield getFooter(boundary);
|
|
642
|
-
}
|
|
643
|
-
|
|
644
|
-
/**
|
|
645
|
-
* @param {FormData} form
|
|
646
|
-
* @param {string} boundary
|
|
647
|
-
*/
|
|
648
|
-
function getFormDataLength(form, boundary) {
|
|
649
|
-
let length = 0;
|
|
650
|
-
|
|
651
|
-
for (const [name, value] of form) {
|
|
652
|
-
length += Buffer.byteLength(getHeader(boundary, name, value));
|
|
653
|
-
|
|
654
|
-
if (isBlob(value)) {
|
|
655
|
-
length += value.size;
|
|
656
|
-
} else {
|
|
657
|
-
length += Buffer.byteLength(String(value));
|
|
658
|
-
}
|
|
659
|
-
|
|
660
|
-
length += carriageLength;
|
|
661
|
-
}
|
|
662
|
-
|
|
663
|
-
length += Buffer.byteLength(getFooter(boundary));
|
|
664
|
-
|
|
665
|
-
return length;
|
|
666
|
-
}
|
|
667
|
-
|
|
668
|
-
const INTERNALS$2 = Symbol('Body internals');
|
|
669
|
-
|
|
670
|
-
/**
|
|
671
|
-
* Body mixin
|
|
672
|
-
*
|
|
673
|
-
* Ref: https://fetch.spec.whatwg.org/#body
|
|
674
|
-
*
|
|
675
|
-
* @param Stream body Readable stream
|
|
676
|
-
* @param Object opts Response options
|
|
677
|
-
* @return Void
|
|
678
|
-
*/
|
|
679
|
-
class Body {
|
|
680
|
-
constructor(body, {
|
|
681
|
-
size = 0
|
|
682
|
-
} = {}) {
|
|
683
|
-
let boundary = null;
|
|
684
|
-
|
|
685
|
-
if (body === null) {
|
|
686
|
-
// Body is undefined or null
|
|
687
|
-
body = null;
|
|
688
|
-
} else if (isURLSearchParameters(body)) {
|
|
689
|
-
// Body is a URLSearchParams
|
|
690
|
-
body = Buffer.from(body.toString());
|
|
691
|
-
} else if (isBlob(body)) ; else if (Buffer.isBuffer(body)) ; else if (types.isAnyArrayBuffer(body)) {
|
|
692
|
-
// Body is ArrayBuffer
|
|
693
|
-
body = Buffer.from(body);
|
|
694
|
-
} else if (ArrayBuffer.isView(body)) {
|
|
695
|
-
// Body is ArrayBufferView
|
|
696
|
-
body = Buffer.from(body.buffer, body.byteOffset, body.byteLength);
|
|
697
|
-
} else if (body instanceof Stream) ; else if (isFormData(body)) {
|
|
698
|
-
// Body is an instance of formdata-node
|
|
699
|
-
boundary = `NodeFetchFormDataBoundary${getBoundary()}`;
|
|
700
|
-
body = Stream.Readable.from(formDataIterator(body, boundary));
|
|
701
|
-
} else {
|
|
702
|
-
// None of the above
|
|
703
|
-
// coerce to string then buffer
|
|
704
|
-
body = Buffer.from(String(body));
|
|
705
|
-
}
|
|
706
|
-
|
|
707
|
-
this[INTERNALS$2] = {
|
|
708
|
-
body,
|
|
709
|
-
boundary,
|
|
710
|
-
disturbed: false,
|
|
711
|
-
error: null
|
|
712
|
-
};
|
|
713
|
-
this.size = size;
|
|
714
|
-
|
|
715
|
-
if (body instanceof Stream) {
|
|
716
|
-
body.on('error', err => {
|
|
717
|
-
const error = err instanceof FetchBaseError ?
|
|
718
|
-
err :
|
|
719
|
-
new FetchError(`Invalid response body while trying to fetch ${this.url}: ${err.message}`, 'system', err);
|
|
720
|
-
this[INTERNALS$2].error = error;
|
|
721
|
-
});
|
|
722
|
-
}
|
|
723
|
-
}
|
|
724
|
-
|
|
725
|
-
get body() {
|
|
726
|
-
return this[INTERNALS$2].body;
|
|
727
|
-
}
|
|
728
|
-
|
|
729
|
-
get bodyUsed() {
|
|
730
|
-
return this[INTERNALS$2].disturbed;
|
|
731
|
-
}
|
|
732
|
-
|
|
733
|
-
/**
|
|
734
|
-
* Decode response as ArrayBuffer
|
|
735
|
-
*
|
|
736
|
-
* @return Promise
|
|
737
|
-
*/
|
|
738
|
-
async arrayBuffer() {
|
|
739
|
-
const {buffer, byteOffset, byteLength} = await consumeBody(this);
|
|
740
|
-
return buffer.slice(byteOffset, byteOffset + byteLength);
|
|
741
|
-
}
|
|
742
|
-
|
|
743
|
-
/**
|
|
744
|
-
* Return raw response as Blob
|
|
745
|
-
*
|
|
746
|
-
* @return Promise
|
|
747
|
-
*/
|
|
748
|
-
async blob() {
|
|
749
|
-
const ct = (this.headers && this.headers.get('content-type')) || (this[INTERNALS$2].body && this[INTERNALS$2].body.type) || '';
|
|
750
|
-
const buf = await this.buffer();
|
|
751
|
-
|
|
752
|
-
return new fetchBlob([buf], {
|
|
753
|
-
type: ct
|
|
754
|
-
});
|
|
755
|
-
}
|
|
756
|
-
|
|
757
|
-
/**
|
|
758
|
-
* Decode response as json
|
|
759
|
-
*
|
|
760
|
-
* @return Promise
|
|
761
|
-
*/
|
|
762
|
-
async json() {
|
|
763
|
-
const buffer = await consumeBody(this);
|
|
764
|
-
return JSON.parse(buffer.toString());
|
|
765
|
-
}
|
|
766
|
-
|
|
767
|
-
/**
|
|
768
|
-
* Decode response as text
|
|
769
|
-
*
|
|
770
|
-
* @return Promise
|
|
771
|
-
*/
|
|
772
|
-
async text() {
|
|
773
|
-
const buffer = await consumeBody(this);
|
|
774
|
-
return buffer.toString();
|
|
775
|
-
}
|
|
776
|
-
|
|
777
|
-
/**
|
|
778
|
-
* Decode response as buffer (non-spec api)
|
|
779
|
-
*
|
|
780
|
-
* @return Promise
|
|
781
|
-
*/
|
|
782
|
-
buffer() {
|
|
783
|
-
return consumeBody(this);
|
|
784
|
-
}
|
|
785
|
-
}
|
|
786
|
-
|
|
787
|
-
// In browsers, all properties are enumerable.
|
|
788
|
-
Object.defineProperties(Body.prototype, {
|
|
789
|
-
body: {enumerable: true},
|
|
790
|
-
bodyUsed: {enumerable: true},
|
|
791
|
-
arrayBuffer: {enumerable: true},
|
|
792
|
-
blob: {enumerable: true},
|
|
793
|
-
json: {enumerable: true},
|
|
794
|
-
text: {enumerable: true}
|
|
795
|
-
});
|
|
796
|
-
|
|
797
|
-
/**
|
|
798
|
-
* Consume and convert an entire Body to a Buffer.
|
|
799
|
-
*
|
|
800
|
-
* Ref: https://fetch.spec.whatwg.org/#concept-body-consume-body
|
|
801
|
-
*
|
|
802
|
-
* @return Promise
|
|
803
|
-
*/
|
|
804
|
-
async function consumeBody(data) {
|
|
805
|
-
if (data[INTERNALS$2].disturbed) {
|
|
806
|
-
throw new TypeError(`body used already for: ${data.url}`);
|
|
807
|
-
}
|
|
808
|
-
|
|
809
|
-
data[INTERNALS$2].disturbed = true;
|
|
810
|
-
|
|
811
|
-
if (data[INTERNALS$2].error) {
|
|
812
|
-
throw data[INTERNALS$2].error;
|
|
813
|
-
}
|
|
814
|
-
|
|
815
|
-
let {body} = data;
|
|
816
|
-
|
|
817
|
-
// Body is null
|
|
818
|
-
if (body === null) {
|
|
819
|
-
return Buffer.alloc(0);
|
|
820
|
-
}
|
|
821
|
-
|
|
822
|
-
// Body is blob
|
|
823
|
-
if (isBlob(body)) {
|
|
824
|
-
body = body.stream();
|
|
825
|
-
}
|
|
826
|
-
|
|
827
|
-
// Body is buffer
|
|
828
|
-
if (Buffer.isBuffer(body)) {
|
|
829
|
-
return body;
|
|
830
|
-
}
|
|
831
|
-
|
|
832
|
-
/* c8 ignore next 3 */
|
|
833
|
-
if (!(body instanceof Stream)) {
|
|
834
|
-
return Buffer.alloc(0);
|
|
835
|
-
}
|
|
836
|
-
|
|
837
|
-
// Body is stream
|
|
838
|
-
// get ready to actually consume the body
|
|
839
|
-
const accum = [];
|
|
840
|
-
let accumBytes = 0;
|
|
841
|
-
|
|
842
|
-
try {
|
|
843
|
-
for await (const chunk of body) {
|
|
844
|
-
if (data.size > 0 && accumBytes + chunk.length > data.size) {
|
|
845
|
-
const err = new FetchError(`content size at ${data.url} over limit: ${data.size}`, 'max-size');
|
|
846
|
-
body.destroy(err);
|
|
847
|
-
throw err;
|
|
848
|
-
}
|
|
849
|
-
|
|
850
|
-
accumBytes += chunk.length;
|
|
851
|
-
accum.push(chunk);
|
|
852
|
-
}
|
|
853
|
-
} catch (error) {
|
|
854
|
-
if (error instanceof FetchBaseError) {
|
|
855
|
-
throw error;
|
|
856
|
-
} else {
|
|
857
|
-
// Other errors, such as incorrect content-encoding
|
|
858
|
-
throw new FetchError(`Invalid response body while trying to fetch ${data.url}: ${error.message}`, 'system', error);
|
|
859
|
-
}
|
|
860
|
-
}
|
|
861
|
-
|
|
862
|
-
if (body.readableEnded === true || body._readableState.ended === true) {
|
|
863
|
-
try {
|
|
864
|
-
if (accum.every(c => typeof c === 'string')) {
|
|
865
|
-
return Buffer.from(accum.join(''));
|
|
866
|
-
}
|
|
867
|
-
|
|
868
|
-
return Buffer.concat(accum, accumBytes);
|
|
869
|
-
} catch (error) {
|
|
870
|
-
throw new FetchError(`Could not create Buffer from response body for ${data.url}: ${error.message}`, 'system', error);
|
|
871
|
-
}
|
|
872
|
-
} else {
|
|
873
|
-
throw new FetchError(`Premature close of server response while trying to fetch ${data.url}`);
|
|
874
|
-
}
|
|
875
|
-
}
|
|
876
|
-
|
|
877
|
-
/**
|
|
878
|
-
* Clone body given Res/Req instance
|
|
879
|
-
*
|
|
880
|
-
* @param Mixed instance Response or Request instance
|
|
881
|
-
* @param String highWaterMark highWaterMark for both PassThrough body streams
|
|
882
|
-
* @return Mixed
|
|
883
|
-
*/
|
|
884
|
-
const clone = (instance, highWaterMark) => {
|
|
885
|
-
let p1;
|
|
886
|
-
let p2;
|
|
887
|
-
let {body} = instance;
|
|
888
|
-
|
|
889
|
-
// Don't allow cloning a used body
|
|
890
|
-
if (instance.bodyUsed) {
|
|
891
|
-
throw new Error('cannot clone body after it is used');
|
|
892
|
-
}
|
|
893
|
-
|
|
894
|
-
// Check that body is a stream and not form-data object
|
|
895
|
-
// note: we can't clone the form-data object without having it as a dependency
|
|
896
|
-
if ((body instanceof Stream) && (typeof body.getBoundary !== 'function')) {
|
|
897
|
-
// Tee instance body
|
|
898
|
-
p1 = new PassThrough({highWaterMark});
|
|
899
|
-
p2 = new PassThrough({highWaterMark});
|
|
900
|
-
body.pipe(p1);
|
|
901
|
-
body.pipe(p2);
|
|
902
|
-
// Set instance body to teed body and return the other teed body
|
|
903
|
-
instance[INTERNALS$2].body = p1;
|
|
904
|
-
body = p2;
|
|
905
|
-
}
|
|
906
|
-
|
|
907
|
-
return body;
|
|
908
|
-
};
|
|
909
|
-
|
|
910
|
-
/**
|
|
911
|
-
* Performs the operation "extract a `Content-Type` value from |object|" as
|
|
912
|
-
* specified in the specification:
|
|
913
|
-
* https://fetch.spec.whatwg.org/#concept-bodyinit-extract
|
|
914
|
-
*
|
|
915
|
-
* This function assumes that instance.body is present.
|
|
916
|
-
*
|
|
917
|
-
* @param {any} body Any options.body input
|
|
918
|
-
* @returns {string | null}
|
|
919
|
-
*/
|
|
920
|
-
const extractContentType = (body, request) => {
|
|
921
|
-
// Body is null or undefined
|
|
922
|
-
if (body === null) {
|
|
923
|
-
return null;
|
|
924
|
-
}
|
|
925
|
-
|
|
926
|
-
// Body is string
|
|
927
|
-
if (typeof body === 'string') {
|
|
928
|
-
return 'text/plain;charset=UTF-8';
|
|
929
|
-
}
|
|
930
|
-
|
|
931
|
-
// Body is a URLSearchParams
|
|
932
|
-
if (isURLSearchParameters(body)) {
|
|
933
|
-
return 'application/x-www-form-urlencoded;charset=UTF-8';
|
|
934
|
-
}
|
|
935
|
-
|
|
936
|
-
// Body is blob
|
|
937
|
-
if (isBlob(body)) {
|
|
938
|
-
return body.type || null;
|
|
939
|
-
}
|
|
940
|
-
|
|
941
|
-
// Body is a Buffer (Buffer, ArrayBuffer or ArrayBufferView)
|
|
942
|
-
if (Buffer.isBuffer(body) || types.isAnyArrayBuffer(body) || ArrayBuffer.isView(body)) {
|
|
943
|
-
return null;
|
|
944
|
-
}
|
|
945
|
-
|
|
946
|
-
// Detect form data input from form-data module
|
|
947
|
-
if (body && typeof body.getBoundary === 'function') {
|
|
948
|
-
return `multipart/form-data;boundary=${body.getBoundary()}`;
|
|
949
|
-
}
|
|
950
|
-
|
|
951
|
-
if (isFormData(body)) {
|
|
952
|
-
return `multipart/form-data; boundary=${request[INTERNALS$2].boundary}`;
|
|
953
|
-
}
|
|
954
|
-
|
|
955
|
-
// Body is stream - can't really do much about this
|
|
956
|
-
if (body instanceof Stream) {
|
|
957
|
-
return null;
|
|
958
|
-
}
|
|
959
|
-
|
|
960
|
-
// Body constructor defaults other things to string
|
|
961
|
-
return 'text/plain;charset=UTF-8';
|
|
962
|
-
};
|
|
963
|
-
|
|
964
|
-
/**
|
|
965
|
-
* The Fetch Standard treats this as if "total bytes" is a property on the body.
|
|
966
|
-
* For us, we have to explicitly get it with a function.
|
|
967
|
-
*
|
|
968
|
-
* ref: https://fetch.spec.whatwg.org/#concept-body-total-bytes
|
|
969
|
-
*
|
|
970
|
-
* @param {any} obj.body Body object from the Body instance.
|
|
971
|
-
* @returns {number | null}
|
|
972
|
-
*/
|
|
973
|
-
const getTotalBytes = request => {
|
|
974
|
-
const {body} = request;
|
|
975
|
-
|
|
976
|
-
// Body is null or undefined
|
|
977
|
-
if (body === null) {
|
|
978
|
-
return 0;
|
|
979
|
-
}
|
|
980
|
-
|
|
981
|
-
// Body is Blob
|
|
982
|
-
if (isBlob(body)) {
|
|
983
|
-
return body.size;
|
|
984
|
-
}
|
|
985
|
-
|
|
986
|
-
// Body is Buffer
|
|
987
|
-
if (Buffer.isBuffer(body)) {
|
|
988
|
-
return body.length;
|
|
989
|
-
}
|
|
990
|
-
|
|
991
|
-
// Detect form data input from form-data module
|
|
992
|
-
if (body && typeof body.getLengthSync === 'function') {
|
|
993
|
-
return body.hasKnownLength && body.hasKnownLength() ? body.getLengthSync() : null;
|
|
994
|
-
}
|
|
995
|
-
|
|
996
|
-
// Body is a spec-compliant form-data
|
|
997
|
-
if (isFormData(body)) {
|
|
998
|
-
return getFormDataLength(request[INTERNALS$2].boundary);
|
|
999
|
-
}
|
|
1000
|
-
|
|
1001
|
-
// Body is stream
|
|
1002
|
-
return null;
|
|
1003
|
-
};
|
|
1004
|
-
|
|
1005
|
-
/**
|
|
1006
|
-
* Write a Body to a Node.js WritableStream (e.g. http.Request) object.
|
|
1007
|
-
*
|
|
1008
|
-
* @param {Stream.Writable} dest The stream to write to.
|
|
1009
|
-
* @param obj.body Body object from the Body instance.
|
|
1010
|
-
* @returns {void}
|
|
1011
|
-
*/
|
|
1012
|
-
const writeToStream = (dest, {body}) => {
|
|
1013
|
-
if (body === null) {
|
|
1014
|
-
// Body is null
|
|
1015
|
-
dest.end();
|
|
1016
|
-
} else if (isBlob(body)) {
|
|
1017
|
-
// Body is Blob
|
|
1018
|
-
body.stream().pipe(dest);
|
|
1019
|
-
} else if (Buffer.isBuffer(body)) {
|
|
1020
|
-
// Body is buffer
|
|
1021
|
-
dest.write(body);
|
|
1022
|
-
dest.end();
|
|
1023
|
-
} else {
|
|
1024
|
-
// Body is stream
|
|
1025
|
-
body.pipe(dest);
|
|
1026
|
-
}
|
|
1027
|
-
};
|
|
1028
|
-
|
|
1029
|
-
/**
|
|
1030
|
-
* Headers.js
|
|
1031
|
-
*
|
|
1032
|
-
* Headers class offers convenient helpers
|
|
1033
|
-
*/
|
|
1034
|
-
|
|
1035
|
-
const validateHeaderName = typeof http.validateHeaderName === 'function' ?
|
|
1036
|
-
http.validateHeaderName :
|
|
1037
|
-
name => {
|
|
1038
|
-
if (!/^[\^`\-\w!#$%&'*+.|~]+$/.test(name)) {
|
|
1039
|
-
const err = new TypeError(`Header name must be a valid HTTP token [${name}]`);
|
|
1040
|
-
Object.defineProperty(err, 'code', {value: 'ERR_INVALID_HTTP_TOKEN'});
|
|
1041
|
-
throw err;
|
|
1042
|
-
}
|
|
1043
|
-
};
|
|
1044
|
-
|
|
1045
|
-
const validateHeaderValue = typeof http.validateHeaderValue === 'function' ?
|
|
1046
|
-
http.validateHeaderValue :
|
|
1047
|
-
(name, value) => {
|
|
1048
|
-
if (/[^\t\u0020-\u007E\u0080-\u00FF]/.test(value)) {
|
|
1049
|
-
const err = new TypeError(`Invalid character in header content ["${name}"]`);
|
|
1050
|
-
Object.defineProperty(err, 'code', {value: 'ERR_INVALID_CHAR'});
|
|
1051
|
-
throw err;
|
|
1052
|
-
}
|
|
1053
|
-
};
|
|
1054
|
-
|
|
1055
|
-
/**
|
|
1056
|
-
* @typedef {Headers | Record<string, string> | Iterable<readonly [string, string]> | Iterable<Iterable<string>>} HeadersInit
|
|
1057
|
-
*/
|
|
1058
|
-
|
|
1059
|
-
/**
|
|
1060
|
-
* This Fetch API interface allows you to perform various actions on HTTP request and response headers.
|
|
1061
|
-
* These actions include retrieving, setting, adding to, and removing.
|
|
1062
|
-
* A Headers object has an associated header list, which is initially empty and consists of zero or more name and value pairs.
|
|
1063
|
-
* You can add to this using methods like append() (see Examples.)
|
|
1064
|
-
* In all methods of this interface, header names are matched by case-insensitive byte sequence.
|
|
1065
|
-
*
|
|
1066
|
-
*/
|
|
1067
|
-
class Headers extends URLSearchParams {
|
|
1068
|
-
/**
|
|
1069
|
-
* Headers class
|
|
1070
|
-
*
|
|
1071
|
-
* @constructor
|
|
1072
|
-
* @param {HeadersInit} [init] - Response headers
|
|
1073
|
-
*/
|
|
1074
|
-
constructor(init) {
|
|
1075
|
-
// Validate and normalize init object in [name, value(s)][]
|
|
1076
|
-
/** @type {string[][]} */
|
|
1077
|
-
let result = [];
|
|
1078
|
-
if (init instanceof Headers) {
|
|
1079
|
-
const raw = init.raw();
|
|
1080
|
-
for (const [name, values] of Object.entries(raw)) {
|
|
1081
|
-
result.push(...values.map(value => [name, value]));
|
|
1082
|
-
}
|
|
1083
|
-
} else if (init == null) ; else if (typeof init === 'object' && !types.isBoxedPrimitive(init)) {
|
|
1084
|
-
const method = init[Symbol.iterator];
|
|
1085
|
-
// eslint-disable-next-line no-eq-null, eqeqeq
|
|
1086
|
-
if (method == null) {
|
|
1087
|
-
// Record<ByteString, ByteString>
|
|
1088
|
-
result.push(...Object.entries(init));
|
|
1089
|
-
} else {
|
|
1090
|
-
if (typeof method !== 'function') {
|
|
1091
|
-
throw new TypeError('Header pairs must be iterable');
|
|
1092
|
-
}
|
|
1093
|
-
|
|
1094
|
-
// Sequence<sequence<ByteString>>
|
|
1095
|
-
// Note: per spec we have to first exhaust the lists then process them
|
|
1096
|
-
result = [...init]
|
|
1097
|
-
.map(pair => {
|
|
1098
|
-
if (
|
|
1099
|
-
typeof pair !== 'object' || types.isBoxedPrimitive(pair)
|
|
1100
|
-
) {
|
|
1101
|
-
throw new TypeError('Each header pair must be an iterable object');
|
|
1102
|
-
}
|
|
1103
|
-
|
|
1104
|
-
return [...pair];
|
|
1105
|
-
}).map(pair => {
|
|
1106
|
-
if (pair.length !== 2) {
|
|
1107
|
-
throw new TypeError('Each header pair must be a name/value tuple');
|
|
1108
|
-
}
|
|
1109
|
-
|
|
1110
|
-
return [...pair];
|
|
1111
|
-
});
|
|
1112
|
-
}
|
|
1113
|
-
} else {
|
|
1114
|
-
throw new TypeError('Failed to construct \'Headers\': The provided value is not of type \'(sequence<sequence<ByteString>> or record<ByteString, ByteString>)');
|
|
1115
|
-
}
|
|
1116
|
-
|
|
1117
|
-
// Validate and lowercase
|
|
1118
|
-
result =
|
|
1119
|
-
result.length > 0 ?
|
|
1120
|
-
result.map(([name, value]) => {
|
|
1121
|
-
validateHeaderName(name);
|
|
1122
|
-
validateHeaderValue(name, String(value));
|
|
1123
|
-
return [String(name).toLowerCase(), String(value)];
|
|
1124
|
-
}) :
|
|
1125
|
-
undefined;
|
|
1126
|
-
|
|
1127
|
-
super(result);
|
|
1128
|
-
|
|
1129
|
-
// Returning a Proxy that will lowercase key names, validate parameters and sort keys
|
|
1130
|
-
// eslint-disable-next-line no-constructor-return
|
|
1131
|
-
return new Proxy(this, {
|
|
1132
|
-
get(target, p, receiver) {
|
|
1133
|
-
switch (p) {
|
|
1134
|
-
case 'append':
|
|
1135
|
-
case 'set':
|
|
1136
|
-
return (name, value) => {
|
|
1137
|
-
validateHeaderName(name);
|
|
1138
|
-
validateHeaderValue(name, String(value));
|
|
1139
|
-
return URLSearchParams.prototype[p].call(
|
|
1140
|
-
receiver,
|
|
1141
|
-
String(name).toLowerCase(),
|
|
1142
|
-
String(value)
|
|
1143
|
-
);
|
|
1144
|
-
};
|
|
1145
|
-
|
|
1146
|
-
case 'delete':
|
|
1147
|
-
case 'has':
|
|
1148
|
-
case 'getAll':
|
|
1149
|
-
return name => {
|
|
1150
|
-
validateHeaderName(name);
|
|
1151
|
-
return URLSearchParams.prototype[p].call(
|
|
1152
|
-
receiver,
|
|
1153
|
-
String(name).toLowerCase()
|
|
1154
|
-
);
|
|
1155
|
-
};
|
|
1156
|
-
|
|
1157
|
-
case 'keys':
|
|
1158
|
-
return () => {
|
|
1159
|
-
target.sort();
|
|
1160
|
-
return new Set(URLSearchParams.prototype.keys.call(target)).keys();
|
|
1161
|
-
};
|
|
1162
|
-
|
|
1163
|
-
default:
|
|
1164
|
-
return Reflect.get(target, p, receiver);
|
|
1165
|
-
}
|
|
1166
|
-
}
|
|
1167
|
-
/* c8 ignore next */
|
|
1168
|
-
});
|
|
1169
|
-
}
|
|
1170
|
-
|
|
1171
|
-
get [Symbol.toStringTag]() {
|
|
1172
|
-
return this.constructor.name;
|
|
1173
|
-
}
|
|
1174
|
-
|
|
1175
|
-
toString() {
|
|
1176
|
-
return Object.prototype.toString.call(this);
|
|
1177
|
-
}
|
|
1178
|
-
|
|
1179
|
-
get(name) {
|
|
1180
|
-
const values = this.getAll(name);
|
|
1181
|
-
if (values.length === 0) {
|
|
1182
|
-
return null;
|
|
1183
|
-
}
|
|
1184
|
-
|
|
1185
|
-
let value = values.join(', ');
|
|
1186
|
-
if (/^content-encoding$/i.test(name)) {
|
|
1187
|
-
value = value.toLowerCase();
|
|
1188
|
-
}
|
|
1189
|
-
|
|
1190
|
-
return value;
|
|
1191
|
-
}
|
|
1192
|
-
|
|
1193
|
-
forEach(callback) {
|
|
1194
|
-
for (const name of this.keys()) {
|
|
1195
|
-
callback(this.get(name), name);
|
|
1196
|
-
}
|
|
1197
|
-
}
|
|
1198
|
-
|
|
1199
|
-
* values() {
|
|
1200
|
-
for (const name of this.keys()) {
|
|
1201
|
-
yield this.get(name);
|
|
1202
|
-
}
|
|
1203
|
-
}
|
|
1204
|
-
|
|
1205
|
-
/**
|
|
1206
|
-
* @type {() => IterableIterator<[string, string]>}
|
|
1207
|
-
*/
|
|
1208
|
-
* entries() {
|
|
1209
|
-
for (const name of this.keys()) {
|
|
1210
|
-
yield [name, this.get(name)];
|
|
1211
|
-
}
|
|
1212
|
-
}
|
|
1213
|
-
|
|
1214
|
-
[Symbol.iterator]() {
|
|
1215
|
-
return this.entries();
|
|
1216
|
-
}
|
|
1217
|
-
|
|
1218
|
-
/**
|
|
1219
|
-
* Node-fetch non-spec method
|
|
1220
|
-
* returning all headers and their values as array
|
|
1221
|
-
* @returns {Record<string, string[]>}
|
|
1222
|
-
*/
|
|
1223
|
-
raw() {
|
|
1224
|
-
return [...this.keys()].reduce((result, key) => {
|
|
1225
|
-
result[key] = this.getAll(key);
|
|
1226
|
-
return result;
|
|
1227
|
-
}, {});
|
|
1228
|
-
}
|
|
1229
|
-
|
|
1230
|
-
/**
|
|
1231
|
-
* For better console.log(headers) and also to convert Headers into Node.js Request compatible format
|
|
1232
|
-
*/
|
|
1233
|
-
[Symbol.for('nodejs.util.inspect.custom')]() {
|
|
1234
|
-
return [...this.keys()].reduce((result, key) => {
|
|
1235
|
-
const values = this.getAll(key);
|
|
1236
|
-
// Http.request() only supports string as Host header.
|
|
1237
|
-
// This hack makes specifying custom Host header possible.
|
|
1238
|
-
if (key === 'host') {
|
|
1239
|
-
result[key] = values[0];
|
|
1240
|
-
} else {
|
|
1241
|
-
result[key] = values.length > 1 ? values : values[0];
|
|
1242
|
-
}
|
|
1243
|
-
|
|
1244
|
-
return result;
|
|
1245
|
-
}, {});
|
|
1246
|
-
}
|
|
1247
|
-
}
|
|
1248
|
-
|
|
1249
|
-
/**
|
|
1250
|
-
* Re-shaping object for Web IDL tests
|
|
1251
|
-
* Only need to do it for overridden methods
|
|
1252
|
-
*/
|
|
1253
|
-
Object.defineProperties(
|
|
1254
|
-
Headers.prototype,
|
|
1255
|
-
['get', 'entries', 'forEach', 'values'].reduce((result, property) => {
|
|
1256
|
-
result[property] = {enumerable: true};
|
|
1257
|
-
return result;
|
|
1258
|
-
}, {})
|
|
1259
|
-
);
|
|
1260
|
-
|
|
1261
|
-
/**
|
|
1262
|
-
* Create a Headers object from an http.IncomingMessage.rawHeaders, ignoring those that do
|
|
1263
|
-
* not conform to HTTP grammar productions.
|
|
1264
|
-
* @param {import('http').IncomingMessage['rawHeaders']} headers
|
|
1265
|
-
*/
|
|
1266
|
-
function fromRawHeaders(headers = []) {
|
|
1267
|
-
return new Headers(
|
|
1268
|
-
headers
|
|
1269
|
-
// Split into pairs
|
|
1270
|
-
.reduce((result, value, index, array) => {
|
|
1271
|
-
if (index % 2 === 0) {
|
|
1272
|
-
result.push(array.slice(index, index + 2));
|
|
1273
|
-
}
|
|
1274
|
-
|
|
1275
|
-
return result;
|
|
1276
|
-
}, [])
|
|
1277
|
-
.filter(([name, value]) => {
|
|
1278
|
-
try {
|
|
1279
|
-
validateHeaderName(name);
|
|
1280
|
-
validateHeaderValue(name, String(value));
|
|
1281
|
-
return true;
|
|
1282
|
-
} catch {
|
|
1283
|
-
return false;
|
|
1284
|
-
}
|
|
1285
|
-
})
|
|
1286
|
-
|
|
1287
|
-
);
|
|
1288
|
-
}
|
|
1289
|
-
|
|
1290
|
-
const redirectStatus = new Set([301, 302, 303, 307, 308]);
|
|
1291
|
-
|
|
1292
|
-
/**
|
|
1293
|
-
* Redirect code matching
|
|
1294
|
-
*
|
|
1295
|
-
* @param {number} code - Status code
|
|
1296
|
-
* @return {boolean}
|
|
1297
|
-
*/
|
|
1298
|
-
const isRedirect = code => {
|
|
1299
|
-
return redirectStatus.has(code);
|
|
1300
|
-
};
|
|
1301
|
-
|
|
1302
|
-
/**
|
|
1303
|
-
* Response.js
|
|
1304
|
-
*
|
|
1305
|
-
* Response class provides content decoding
|
|
1306
|
-
*/
|
|
1307
|
-
|
|
1308
|
-
const INTERNALS$1 = Symbol('Response internals');
|
|
1309
|
-
|
|
1310
|
-
/**
|
|
1311
|
-
* Response class
|
|
1312
|
-
*
|
|
1313
|
-
* @param Stream body Readable stream
|
|
1314
|
-
* @param Object opts Response options
|
|
1315
|
-
* @return Void
|
|
1316
|
-
*/
|
|
1317
|
-
class Response extends Body {
|
|
1318
|
-
constructor(body = null, options = {}) {
|
|
1319
|
-
super(body, options);
|
|
1320
|
-
|
|
1321
|
-
const status = options.status || 200;
|
|
1322
|
-
const headers = new Headers(options.headers);
|
|
1323
|
-
|
|
1324
|
-
if (body !== null && !headers.has('Content-Type')) {
|
|
1325
|
-
const contentType = extractContentType(body);
|
|
1326
|
-
if (contentType) {
|
|
1327
|
-
headers.append('Content-Type', contentType);
|
|
1328
|
-
}
|
|
1329
|
-
}
|
|
1330
|
-
|
|
1331
|
-
this[INTERNALS$1] = {
|
|
1332
|
-
url: options.url,
|
|
1333
|
-
status,
|
|
1334
|
-
statusText: options.statusText || '',
|
|
1335
|
-
headers,
|
|
1336
|
-
counter: options.counter,
|
|
1337
|
-
highWaterMark: options.highWaterMark
|
|
1338
|
-
};
|
|
1339
|
-
}
|
|
1340
|
-
|
|
1341
|
-
get url() {
|
|
1342
|
-
return this[INTERNALS$1].url || '';
|
|
1343
|
-
}
|
|
1344
|
-
|
|
1345
|
-
get status() {
|
|
1346
|
-
return this[INTERNALS$1].status;
|
|
1347
|
-
}
|
|
1348
|
-
|
|
1349
|
-
/**
|
|
1350
|
-
* Convenience property representing if the request ended normally
|
|
1351
|
-
*/
|
|
1352
|
-
get ok() {
|
|
1353
|
-
return this[INTERNALS$1].status >= 200 && this[INTERNALS$1].status < 300;
|
|
1354
|
-
}
|
|
1355
|
-
|
|
1356
|
-
get redirected() {
|
|
1357
|
-
return this[INTERNALS$1].counter > 0;
|
|
1358
|
-
}
|
|
1359
|
-
|
|
1360
|
-
get statusText() {
|
|
1361
|
-
return this[INTERNALS$1].statusText;
|
|
1362
|
-
}
|
|
1363
|
-
|
|
1364
|
-
get headers() {
|
|
1365
|
-
return this[INTERNALS$1].headers;
|
|
1366
|
-
}
|
|
1367
|
-
|
|
1368
|
-
get highWaterMark() {
|
|
1369
|
-
return this[INTERNALS$1].highWaterMark;
|
|
1370
|
-
}
|
|
1371
|
-
|
|
1372
|
-
/**
|
|
1373
|
-
* Clone this response
|
|
1374
|
-
*
|
|
1375
|
-
* @return Response
|
|
1376
|
-
*/
|
|
1377
|
-
clone() {
|
|
1378
|
-
return new Response(clone(this, this.highWaterMark), {
|
|
1379
|
-
url: this.url,
|
|
1380
|
-
status: this.status,
|
|
1381
|
-
statusText: this.statusText,
|
|
1382
|
-
headers: this.headers,
|
|
1383
|
-
ok: this.ok,
|
|
1384
|
-
redirected: this.redirected,
|
|
1385
|
-
size: this.size
|
|
1386
|
-
});
|
|
1387
|
-
}
|
|
1388
|
-
|
|
1389
|
-
/**
|
|
1390
|
-
* @param {string} url The URL that the new response is to originate from.
|
|
1391
|
-
* @param {number} status An optional status code for the response (e.g., 302.)
|
|
1392
|
-
* @returns {Response} A Response object.
|
|
1393
|
-
*/
|
|
1394
|
-
static redirect(url, status = 302) {
|
|
1395
|
-
if (!isRedirect(status)) {
|
|
1396
|
-
throw new RangeError('Failed to execute "redirect" on "response": Invalid status code');
|
|
1397
|
-
}
|
|
1398
|
-
|
|
1399
|
-
return new Response(null, {
|
|
1400
|
-
headers: {
|
|
1401
|
-
location: new URL(url).toString()
|
|
1402
|
-
},
|
|
1403
|
-
status
|
|
1404
|
-
});
|
|
1405
|
-
}
|
|
1406
|
-
|
|
1407
|
-
get [Symbol.toStringTag]() {
|
|
1408
|
-
return 'Response';
|
|
1409
|
-
}
|
|
1410
|
-
}
|
|
1411
|
-
|
|
1412
|
-
Object.defineProperties(Response.prototype, {
|
|
1413
|
-
url: {enumerable: true},
|
|
1414
|
-
status: {enumerable: true},
|
|
1415
|
-
ok: {enumerable: true},
|
|
1416
|
-
redirected: {enumerable: true},
|
|
1417
|
-
statusText: {enumerable: true},
|
|
1418
|
-
headers: {enumerable: true},
|
|
1419
|
-
clone: {enumerable: true}
|
|
1420
|
-
});
|
|
1421
|
-
|
|
1422
|
-
const getSearch = parsedURL => {
|
|
1423
|
-
if (parsedURL.search) {
|
|
1424
|
-
return parsedURL.search;
|
|
1425
|
-
}
|
|
1426
|
-
|
|
1427
|
-
const lastOffset = parsedURL.href.length - 1;
|
|
1428
|
-
const hash = parsedURL.hash || (parsedURL.href[lastOffset] === '#' ? '#' : '');
|
|
1429
|
-
return parsedURL.href[lastOffset - hash.length] === '?' ? '?' : '';
|
|
1430
|
-
};
|
|
1431
|
-
|
|
1432
|
-
const INTERNALS = Symbol('Request internals');
|
|
1433
|
-
|
|
1434
|
-
/**
|
|
1435
|
-
* Check if `obj` is an instance of Request.
|
|
1436
|
-
*
|
|
1437
|
-
* @param {*} obj
|
|
1438
|
-
* @return {boolean}
|
|
1439
|
-
*/
|
|
1440
|
-
const isRequest = object => {
|
|
1441
|
-
return (
|
|
1442
|
-
typeof object === 'object' &&
|
|
1443
|
-
typeof object[INTERNALS] === 'object'
|
|
1444
|
-
);
|
|
1445
|
-
};
|
|
1446
|
-
|
|
1447
|
-
/**
|
|
1448
|
-
* Request class
|
|
1449
|
-
*
|
|
1450
|
-
* @param Mixed input Url or Request instance
|
|
1451
|
-
* @param Object init Custom options
|
|
1452
|
-
* @return Void
|
|
1453
|
-
*/
|
|
1454
|
-
class Request extends Body {
|
|
1455
|
-
constructor(input, init = {}) {
|
|
1456
|
-
let parsedURL;
|
|
1457
|
-
|
|
1458
|
-
// Normalize input and force URL to be encoded as UTF-8 (https://github.com/node-fetch/node-fetch/issues/245)
|
|
1459
|
-
if (isRequest(input)) {
|
|
1460
|
-
parsedURL = new URL(input.url);
|
|
1461
|
-
} else {
|
|
1462
|
-
parsedURL = new URL(input);
|
|
1463
|
-
input = {};
|
|
1464
|
-
}
|
|
1465
|
-
|
|
1466
|
-
let method = init.method || input.method || 'GET';
|
|
1467
|
-
method = method.toUpperCase();
|
|
1468
|
-
|
|
1469
|
-
// eslint-disable-next-line no-eq-null, eqeqeq
|
|
1470
|
-
if (((init.body != null || isRequest(input)) && input.body !== null) &&
|
|
1471
|
-
(method === 'GET' || method === 'HEAD')) {
|
|
1472
|
-
throw new TypeError('Request with GET/HEAD method cannot have body');
|
|
1473
|
-
}
|
|
1474
|
-
|
|
1475
|
-
const inputBody = init.body ?
|
|
1476
|
-
init.body :
|
|
1477
|
-
(isRequest(input) && input.body !== null ?
|
|
1478
|
-
clone(input) :
|
|
1479
|
-
null);
|
|
1480
|
-
|
|
1481
|
-
super(inputBody, {
|
|
1482
|
-
size: init.size || input.size || 0
|
|
1483
|
-
});
|
|
1484
|
-
|
|
1485
|
-
const headers = new Headers(init.headers || input.headers || {});
|
|
1486
|
-
|
|
1487
|
-
if (inputBody !== null && !headers.has('Content-Type')) {
|
|
1488
|
-
const contentType = extractContentType(inputBody, this);
|
|
1489
|
-
if (contentType) {
|
|
1490
|
-
headers.append('Content-Type', contentType);
|
|
1491
|
-
}
|
|
1492
|
-
}
|
|
1493
|
-
|
|
1494
|
-
let signal = isRequest(input) ?
|
|
1495
|
-
input.signal :
|
|
1496
|
-
null;
|
|
1497
|
-
if ('signal' in init) {
|
|
1498
|
-
signal = init.signal;
|
|
1499
|
-
}
|
|
1500
|
-
|
|
1501
|
-
if (signal !== null && !isAbortSignal(signal)) {
|
|
1502
|
-
throw new TypeError('Expected signal to be an instanceof AbortSignal');
|
|
1503
|
-
}
|
|
1504
|
-
|
|
1505
|
-
this[INTERNALS] = {
|
|
1506
|
-
method,
|
|
1507
|
-
redirect: init.redirect || input.redirect || 'follow',
|
|
1508
|
-
headers,
|
|
1509
|
-
parsedURL,
|
|
1510
|
-
signal
|
|
1511
|
-
};
|
|
1512
|
-
|
|
1513
|
-
// Node-fetch-only options
|
|
1514
|
-
this.follow = init.follow === undefined ? (input.follow === undefined ? 20 : input.follow) : init.follow;
|
|
1515
|
-
this.compress = init.compress === undefined ? (input.compress === undefined ? true : input.compress) : init.compress;
|
|
1516
|
-
this.counter = init.counter || input.counter || 0;
|
|
1517
|
-
this.agent = init.agent || input.agent;
|
|
1518
|
-
this.highWaterMark = init.highWaterMark || input.highWaterMark || 16384;
|
|
1519
|
-
this.insecureHTTPParser = init.insecureHTTPParser || input.insecureHTTPParser || false;
|
|
1520
|
-
}
|
|
1521
|
-
|
|
1522
|
-
get method() {
|
|
1523
|
-
return this[INTERNALS].method;
|
|
1524
|
-
}
|
|
1525
|
-
|
|
1526
|
-
get url() {
|
|
1527
|
-
return format(this[INTERNALS].parsedURL);
|
|
1528
|
-
}
|
|
1529
|
-
|
|
1530
|
-
get headers() {
|
|
1531
|
-
return this[INTERNALS].headers;
|
|
1532
|
-
}
|
|
1533
|
-
|
|
1534
|
-
get redirect() {
|
|
1535
|
-
return this[INTERNALS].redirect;
|
|
1536
|
-
}
|
|
1537
|
-
|
|
1538
|
-
get signal() {
|
|
1539
|
-
return this[INTERNALS].signal;
|
|
1540
|
-
}
|
|
1541
|
-
|
|
1542
|
-
/**
|
|
1543
|
-
* Clone this request
|
|
1544
|
-
*
|
|
1545
|
-
* @return Request
|
|
1546
|
-
*/
|
|
1547
|
-
clone() {
|
|
1548
|
-
return new Request(this);
|
|
1549
|
-
}
|
|
1550
|
-
|
|
1551
|
-
get [Symbol.toStringTag]() {
|
|
1552
|
-
return 'Request';
|
|
1553
|
-
}
|
|
1554
|
-
}
|
|
1555
|
-
|
|
1556
|
-
Object.defineProperties(Request.prototype, {
|
|
1557
|
-
method: {enumerable: true},
|
|
1558
|
-
url: {enumerable: true},
|
|
1559
|
-
headers: {enumerable: true},
|
|
1560
|
-
redirect: {enumerable: true},
|
|
1561
|
-
clone: {enumerable: true},
|
|
1562
|
-
signal: {enumerable: true}
|
|
1563
|
-
});
|
|
1564
|
-
|
|
1565
|
-
/**
|
|
1566
|
-
* Convert a Request to Node.js http request options.
|
|
1567
|
-
*
|
|
1568
|
-
* @param Request A Request instance
|
|
1569
|
-
* @return Object The options object to be passed to http.request
|
|
1570
|
-
*/
|
|
1571
|
-
const getNodeRequestOptions = request => {
|
|
1572
|
-
const {parsedURL} = request[INTERNALS];
|
|
1573
|
-
const headers = new Headers(request[INTERNALS].headers);
|
|
1574
|
-
|
|
1575
|
-
// Fetch step 1.3
|
|
1576
|
-
if (!headers.has('Accept')) {
|
|
1577
|
-
headers.set('Accept', '*/*');
|
|
1578
|
-
}
|
|
1579
|
-
|
|
1580
|
-
// HTTP-network-or-cache fetch steps 2.4-2.7
|
|
1581
|
-
let contentLengthValue = null;
|
|
1582
|
-
if (request.body === null && /^(post|put)$/i.test(request.method)) {
|
|
1583
|
-
contentLengthValue = '0';
|
|
1584
|
-
}
|
|
1585
|
-
|
|
1586
|
-
if (request.body !== null) {
|
|
1587
|
-
const totalBytes = getTotalBytes(request);
|
|
1588
|
-
// Set Content-Length if totalBytes is a number (that is not NaN)
|
|
1589
|
-
if (typeof totalBytes === 'number' && !Number.isNaN(totalBytes)) {
|
|
1590
|
-
contentLengthValue = String(totalBytes);
|
|
1591
|
-
}
|
|
1592
|
-
}
|
|
1593
|
-
|
|
1594
|
-
if (contentLengthValue) {
|
|
1595
|
-
headers.set('Content-Length', contentLengthValue);
|
|
1596
|
-
}
|
|
1597
|
-
|
|
1598
|
-
// HTTP-network-or-cache fetch step 2.11
|
|
1599
|
-
if (!headers.has('User-Agent')) {
|
|
1600
|
-
headers.set('User-Agent', 'node-fetch');
|
|
1601
|
-
}
|
|
1602
|
-
|
|
1603
|
-
// HTTP-network-or-cache fetch step 2.15
|
|
1604
|
-
if (request.compress && !headers.has('Accept-Encoding')) {
|
|
1605
|
-
headers.set('Accept-Encoding', 'gzip,deflate,br');
|
|
1606
|
-
}
|
|
1607
|
-
|
|
1608
|
-
let {agent} = request;
|
|
1609
|
-
if (typeof agent === 'function') {
|
|
1610
|
-
agent = agent(parsedURL);
|
|
1611
|
-
}
|
|
1612
|
-
|
|
1613
|
-
if (!headers.has('Connection') && !agent) {
|
|
1614
|
-
headers.set('Connection', 'close');
|
|
1615
|
-
}
|
|
1616
|
-
|
|
1617
|
-
// HTTP-network fetch step 4.2
|
|
1618
|
-
// chunked encoding is handled by Node.js
|
|
1619
|
-
|
|
1620
|
-
const search = getSearch(parsedURL);
|
|
1621
|
-
|
|
1622
|
-
// Manually spread the URL object instead of spread syntax
|
|
1623
|
-
const requestOptions = {
|
|
1624
|
-
path: parsedURL.pathname + search,
|
|
1625
|
-
pathname: parsedURL.pathname,
|
|
1626
|
-
hostname: parsedURL.hostname,
|
|
1627
|
-
protocol: parsedURL.protocol,
|
|
1628
|
-
port: parsedURL.port,
|
|
1629
|
-
hash: parsedURL.hash,
|
|
1630
|
-
search: parsedURL.search,
|
|
1631
|
-
query: parsedURL.query,
|
|
1632
|
-
href: parsedURL.href,
|
|
1633
|
-
method: request.method,
|
|
1634
|
-
headers: headers[Symbol.for('nodejs.util.inspect.custom')](),
|
|
1635
|
-
insecureHTTPParser: request.insecureHTTPParser,
|
|
1636
|
-
agent
|
|
1637
|
-
};
|
|
1638
|
-
|
|
1639
|
-
return requestOptions;
|
|
1640
|
-
};
|
|
1641
|
-
|
|
1642
|
-
/**
|
|
1643
|
-
* AbortError interface for cancelled requests
|
|
1644
|
-
*/
|
|
1645
|
-
class AbortError extends FetchBaseError {
|
|
1646
|
-
constructor(message, type = 'aborted') {
|
|
1647
|
-
super(message, type);
|
|
1648
|
-
}
|
|
1649
|
-
}
|
|
1650
|
-
|
|
1651
|
-
/**
|
|
1652
|
-
* Index.js
|
|
1653
|
-
*
|
|
1654
|
-
* a request API compatible with window.fetch
|
|
1655
|
-
*
|
|
1656
|
-
* All spec algorithm step numbers are based on https://fetch.spec.whatwg.org/commit-snapshots/ae716822cb3a61843226cd090eefc6589446c1d2/.
|
|
1657
|
-
*/
|
|
1658
|
-
|
|
1659
|
-
const supportedSchemas = new Set(['data:', 'http:', 'https:']);
|
|
1660
|
-
|
|
1661
|
-
/**
|
|
1662
|
-
* Fetch function
|
|
1663
|
-
*
|
|
1664
|
-
* @param {string | URL | import('./request').default} url - Absolute url or Request instance
|
|
1665
|
-
* @param {*} [options_] - Fetch options
|
|
1666
|
-
* @return {Promise<import('./response').default>}
|
|
1667
|
-
*/
|
|
1668
|
-
async function fetch(url, options_) {
|
|
1669
|
-
return new Promise((resolve, reject) => {
|
|
1670
|
-
// Build request object
|
|
1671
|
-
const request = new Request(url, options_);
|
|
1672
|
-
const options = getNodeRequestOptions(request);
|
|
1673
|
-
if (!supportedSchemas.has(options.protocol)) {
|
|
1674
|
-
throw new TypeError(`node-fetch cannot load ${url}. URL scheme "${options.protocol.replace(/:$/, '')}" is not supported.`);
|
|
1675
|
-
}
|
|
1676
|
-
|
|
1677
|
-
if (options.protocol === 'data:') {
|
|
1678
|
-
const data = src(request.url);
|
|
1679
|
-
const response = new Response(data, {headers: {'Content-Type': data.typeFull}});
|
|
1680
|
-
resolve(response);
|
|
1681
|
-
return;
|
|
1682
|
-
}
|
|
1683
|
-
|
|
1684
|
-
// Wrap http.request into fetch
|
|
1685
|
-
const send = (options.protocol === 'https:' ? https : http).request;
|
|
1686
|
-
const {signal} = request;
|
|
1687
|
-
let response = null;
|
|
1688
|
-
|
|
1689
|
-
const abort = () => {
|
|
1690
|
-
const error = new AbortError('The operation was aborted.');
|
|
1691
|
-
reject(error);
|
|
1692
|
-
if (request.body && request.body instanceof Stream.Readable) {
|
|
1693
|
-
request.body.destroy(error);
|
|
1694
|
-
}
|
|
1695
|
-
|
|
1696
|
-
if (!response || !response.body) {
|
|
1697
|
-
return;
|
|
1698
|
-
}
|
|
1699
|
-
|
|
1700
|
-
response.body.emit('error', error);
|
|
1701
|
-
};
|
|
1702
|
-
|
|
1703
|
-
if (signal && signal.aborted) {
|
|
1704
|
-
abort();
|
|
1705
|
-
return;
|
|
1706
|
-
}
|
|
1707
|
-
|
|
1708
|
-
const abortAndFinalize = () => {
|
|
1709
|
-
abort();
|
|
1710
|
-
finalize();
|
|
1711
|
-
};
|
|
1712
|
-
|
|
1713
|
-
// Send request
|
|
1714
|
-
const request_ = send(options);
|
|
1715
|
-
|
|
1716
|
-
if (signal) {
|
|
1717
|
-
signal.addEventListener('abort', abortAndFinalize);
|
|
1718
|
-
}
|
|
1719
|
-
|
|
1720
|
-
const finalize = () => {
|
|
1721
|
-
request_.abort();
|
|
1722
|
-
if (signal) {
|
|
1723
|
-
signal.removeEventListener('abort', abortAndFinalize);
|
|
1724
|
-
}
|
|
1725
|
-
};
|
|
1726
|
-
|
|
1727
|
-
request_.on('error', err => {
|
|
1728
|
-
reject(new FetchError(`request to ${request.url} failed, reason: ${err.message}`, 'system', err));
|
|
1729
|
-
finalize();
|
|
1730
|
-
});
|
|
1731
|
-
|
|
1732
|
-
request_.on('response', response_ => {
|
|
1733
|
-
request_.setTimeout(0);
|
|
1734
|
-
const headers = fromRawHeaders(response_.rawHeaders);
|
|
1735
|
-
|
|
1736
|
-
// HTTP fetch step 5
|
|
1737
|
-
if (isRedirect(response_.statusCode)) {
|
|
1738
|
-
// HTTP fetch step 5.2
|
|
1739
|
-
const location = headers.get('Location');
|
|
1740
|
-
|
|
1741
|
-
// HTTP fetch step 5.3
|
|
1742
|
-
const locationURL = location === null ? null : new URL(location, request.url);
|
|
1743
|
-
|
|
1744
|
-
// HTTP fetch step 5.5
|
|
1745
|
-
switch (request.redirect) {
|
|
1746
|
-
case 'error':
|
|
1747
|
-
reject(new FetchError(`uri requested responds with a redirect, redirect mode is set to error: ${request.url}`, 'no-redirect'));
|
|
1748
|
-
finalize();
|
|
1749
|
-
return;
|
|
1750
|
-
case 'manual':
|
|
1751
|
-
// Node-fetch-specific step: make manual redirect a bit easier to use by setting the Location header value to the resolved URL.
|
|
1752
|
-
if (locationURL !== null) {
|
|
1753
|
-
// Handle corrupted header
|
|
1754
|
-
try {
|
|
1755
|
-
headers.set('Location', locationURL);
|
|
1756
|
-
/* c8 ignore next 3 */
|
|
1757
|
-
} catch (error) {
|
|
1758
|
-
reject(error);
|
|
1759
|
-
}
|
|
1760
|
-
}
|
|
1761
|
-
|
|
1762
|
-
break;
|
|
1763
|
-
case 'follow': {
|
|
1764
|
-
// HTTP-redirect fetch step 2
|
|
1765
|
-
if (locationURL === null) {
|
|
1766
|
-
break;
|
|
1767
|
-
}
|
|
1768
|
-
|
|
1769
|
-
// HTTP-redirect fetch step 5
|
|
1770
|
-
if (request.counter >= request.follow) {
|
|
1771
|
-
reject(new FetchError(`maximum redirect reached at: ${request.url}`, 'max-redirect'));
|
|
1772
|
-
finalize();
|
|
1773
|
-
return;
|
|
1774
|
-
}
|
|
1775
|
-
|
|
1776
|
-
// HTTP-redirect fetch step 6 (counter increment)
|
|
1777
|
-
// Create a new Request object.
|
|
1778
|
-
const requestOptions = {
|
|
1779
|
-
headers: new Headers(request.headers),
|
|
1780
|
-
follow: request.follow,
|
|
1781
|
-
counter: request.counter + 1,
|
|
1782
|
-
agent: request.agent,
|
|
1783
|
-
compress: request.compress,
|
|
1784
|
-
method: request.method,
|
|
1785
|
-
body: request.body,
|
|
1786
|
-
signal: request.signal,
|
|
1787
|
-
size: request.size
|
|
1788
|
-
};
|
|
1789
|
-
|
|
1790
|
-
// HTTP-redirect fetch step 9
|
|
1791
|
-
if (response_.statusCode !== 303 && request.body && options_.body instanceof Stream.Readable) {
|
|
1792
|
-
reject(new FetchError('Cannot follow redirect with body being a readable stream', 'unsupported-redirect'));
|
|
1793
|
-
finalize();
|
|
1794
|
-
return;
|
|
1795
|
-
}
|
|
1796
|
-
|
|
1797
|
-
// HTTP-redirect fetch step 11
|
|
1798
|
-
if (response_.statusCode === 303 || ((response_.statusCode === 301 || response_.statusCode === 302) && request.method === 'POST')) {
|
|
1799
|
-
requestOptions.method = 'GET';
|
|
1800
|
-
requestOptions.body = undefined;
|
|
1801
|
-
requestOptions.headers.delete('content-length');
|
|
1802
|
-
}
|
|
1803
|
-
|
|
1804
|
-
// HTTP-redirect fetch step 15
|
|
1805
|
-
resolve(fetch(new Request(locationURL, requestOptions)));
|
|
1806
|
-
finalize();
|
|
1807
|
-
return;
|
|
1808
|
-
}
|
|
1809
|
-
// Do nothing
|
|
1810
|
-
}
|
|
1811
|
-
}
|
|
1812
|
-
|
|
1813
|
-
// Prepare response
|
|
1814
|
-
response_.once('end', () => {
|
|
1815
|
-
if (signal) {
|
|
1816
|
-
signal.removeEventListener('abort', abortAndFinalize);
|
|
1817
|
-
}
|
|
1818
|
-
});
|
|
1819
|
-
|
|
1820
|
-
let body = pipeline(response_, new PassThrough(), error => {
|
|
1821
|
-
reject(error);
|
|
1822
|
-
});
|
|
1823
|
-
// see https://github.com/nodejs/node/pull/29376
|
|
1824
|
-
if (process.version < 'v12.10') {
|
|
1825
|
-
response_.on('aborted', abortAndFinalize);
|
|
1826
|
-
}
|
|
1827
|
-
|
|
1828
|
-
const responseOptions = {
|
|
1829
|
-
url: request.url,
|
|
1830
|
-
status: response_.statusCode,
|
|
1831
|
-
statusText: response_.statusMessage,
|
|
1832
|
-
headers,
|
|
1833
|
-
size: request.size,
|
|
1834
|
-
counter: request.counter,
|
|
1835
|
-
highWaterMark: request.highWaterMark
|
|
1836
|
-
};
|
|
1837
|
-
|
|
1838
|
-
// HTTP-network fetch step 12.1.1.3
|
|
1839
|
-
const codings = headers.get('Content-Encoding');
|
|
1840
|
-
|
|
1841
|
-
// HTTP-network fetch step 12.1.1.4: handle content codings
|
|
1842
|
-
|
|
1843
|
-
// in following scenarios we ignore compression support
|
|
1844
|
-
// 1. compression support is disabled
|
|
1845
|
-
// 2. HEAD request
|
|
1846
|
-
// 3. no Content-Encoding header
|
|
1847
|
-
// 4. no content response (204)
|
|
1848
|
-
// 5. content not modified response (304)
|
|
1849
|
-
if (!request.compress || request.method === 'HEAD' || codings === null || response_.statusCode === 204 || response_.statusCode === 304) {
|
|
1850
|
-
response = new Response(body, responseOptions);
|
|
1851
|
-
resolve(response);
|
|
1852
|
-
return;
|
|
1853
|
-
}
|
|
1854
|
-
|
|
1855
|
-
// For Node v6+
|
|
1856
|
-
// Be less strict when decoding compressed responses, since sometimes
|
|
1857
|
-
// servers send slightly invalid responses that are still accepted
|
|
1858
|
-
// by common browsers.
|
|
1859
|
-
// Always using Z_SYNC_FLUSH is what cURL does.
|
|
1860
|
-
const zlibOptions = {
|
|
1861
|
-
flush: zlib.Z_SYNC_FLUSH,
|
|
1862
|
-
finishFlush: zlib.Z_SYNC_FLUSH
|
|
1863
|
-
};
|
|
1864
|
-
|
|
1865
|
-
// For gzip
|
|
1866
|
-
if (codings === 'gzip' || codings === 'x-gzip') {
|
|
1867
|
-
body = pipeline(body, zlib.createGunzip(zlibOptions), error => {
|
|
1868
|
-
reject(error);
|
|
1869
|
-
});
|
|
1870
|
-
response = new Response(body, responseOptions);
|
|
1871
|
-
resolve(response);
|
|
1872
|
-
return;
|
|
1873
|
-
}
|
|
1874
|
-
|
|
1875
|
-
// For deflate
|
|
1876
|
-
if (codings === 'deflate' || codings === 'x-deflate') {
|
|
1877
|
-
// Handle the infamous raw deflate response from old servers
|
|
1878
|
-
// a hack for old IIS and Apache servers
|
|
1879
|
-
const raw = pipeline(response_, new PassThrough(), error => {
|
|
1880
|
-
reject(error);
|
|
1881
|
-
});
|
|
1882
|
-
raw.once('data', chunk => {
|
|
1883
|
-
// See http://stackoverflow.com/questions/37519828
|
|
1884
|
-
if ((chunk[0] & 0x0F) === 0x08) {
|
|
1885
|
-
body = pipeline(body, zlib.createInflate(), error => {
|
|
1886
|
-
reject(error);
|
|
1887
|
-
});
|
|
1888
|
-
} else {
|
|
1889
|
-
body = pipeline(body, zlib.createInflateRaw(), error => {
|
|
1890
|
-
reject(error);
|
|
1891
|
-
});
|
|
1892
|
-
}
|
|
1893
|
-
|
|
1894
|
-
response = new Response(body, responseOptions);
|
|
1895
|
-
resolve(response);
|
|
1896
|
-
});
|
|
1897
|
-
return;
|
|
1898
|
-
}
|
|
1899
|
-
|
|
1900
|
-
// For br
|
|
1901
|
-
if (codings === 'br') {
|
|
1902
|
-
body = pipeline(body, zlib.createBrotliDecompress(), error => {
|
|
1903
|
-
reject(error);
|
|
1904
|
-
});
|
|
1905
|
-
response = new Response(body, responseOptions);
|
|
1906
|
-
resolve(response);
|
|
1907
|
-
return;
|
|
1908
|
-
}
|
|
1909
|
-
|
|
1910
|
-
// Otherwise, use response as-is
|
|
1911
|
-
response = new Response(body, responseOptions);
|
|
1912
|
-
resolve(response);
|
|
1913
|
-
});
|
|
1914
|
-
|
|
1915
|
-
writeToStream(request_, request);
|
|
1916
|
-
});
|
|
1917
|
-
}
|
|
1918
|
-
|
|
1919
|
-
function noop() { }
|
|
1920
|
-
function safe_not_equal(a, b) {
|
|
1921
|
-
return a != a ? b == b : a !== b || ((a && typeof a === 'object') || typeof a === 'function');
|
|
1922
|
-
}
|
|
1923
|
-
|
|
1924
|
-
console.log('>>> module');
|
|
1925
|
-
|
|
1926
|
-
const subscriber_queue = [];
|
|
1927
|
-
/**
|
|
1928
|
-
* Create a `Writable` store that allows both updating and reading by subscription.
|
|
1929
|
-
* @param {*=}value initial value
|
|
1930
|
-
* @param {StartStopNotifier=}start start and stop notifications for subscriptions
|
|
1931
|
-
*/
|
|
1932
|
-
function writable(value, start = noop) {
|
|
1933
|
-
let stop;
|
|
1934
|
-
const subscribers = [];
|
|
1935
|
-
function set(new_value) {
|
|
1936
|
-
if (safe_not_equal(value, new_value)) {
|
|
1937
|
-
value = new_value;
|
|
1938
|
-
if (stop) { // store is ready
|
|
1939
|
-
const run_queue = !subscriber_queue.length;
|
|
1940
|
-
for (let i = 0; i < subscribers.length; i += 1) {
|
|
1941
|
-
const s = subscribers[i];
|
|
1942
|
-
s[1]();
|
|
1943
|
-
subscriber_queue.push(s, value);
|
|
1944
|
-
}
|
|
1945
|
-
if (run_queue) {
|
|
1946
|
-
for (let i = 0; i < subscriber_queue.length; i += 2) {
|
|
1947
|
-
subscriber_queue[i][0](subscriber_queue[i + 1]);
|
|
1948
|
-
}
|
|
1949
|
-
subscriber_queue.length = 0;
|
|
1950
|
-
}
|
|
1951
|
-
}
|
|
1952
|
-
}
|
|
1953
|
-
}
|
|
1954
|
-
function update(fn) {
|
|
1955
|
-
set(fn(value));
|
|
1956
|
-
}
|
|
1957
|
-
function subscribe(run, invalidate = noop) {
|
|
1958
|
-
const subscriber = [run, invalidate];
|
|
1959
|
-
subscribers.push(subscriber);
|
|
1960
|
-
if (subscribers.length === 1) {
|
|
1961
|
-
stop = start(set) || noop;
|
|
1962
|
-
}
|
|
1963
|
-
run(value);
|
|
1964
|
-
return () => {
|
|
1965
|
-
const index = subscribers.indexOf(subscriber);
|
|
1966
|
-
if (index !== -1) {
|
|
1967
|
-
subscribers.splice(index, 1);
|
|
1968
|
-
}
|
|
1969
|
-
if (subscribers.length === 0) {
|
|
1970
|
-
stop();
|
|
1971
|
-
stop = null;
|
|
1972
|
-
}
|
|
1973
|
-
};
|
|
1974
|
-
}
|
|
1975
|
-
return { set, update, subscribe };
|
|
1976
|
-
}
|
|
1977
|
-
|
|
1978
|
-
/**
|
|
1979
|
-
* @param {import('../types').LoadResult} loaded
|
|
1980
|
-
* @returns {import('../types').LoadResult}
|
|
1981
|
-
*/
|
|
1982
|
-
function normalize(loaded) {
|
|
1983
|
-
// TODO should this behaviour be dev-only?
|
|
1984
|
-
|
|
1985
|
-
if (loaded.error) {
|
|
1986
|
-
const error = typeof loaded.error === 'string' ? new Error(loaded.error) : loaded.error;
|
|
1987
|
-
const status = loaded.status;
|
|
1988
|
-
|
|
1989
|
-
if (!(error instanceof Error)) {
|
|
1990
|
-
return {
|
|
1991
|
-
status: 500,
|
|
1992
|
-
error: new Error(
|
|
1993
|
-
`"error" property returned from load() must be a string or instance of Error, received type "${typeof error}"`
|
|
1994
|
-
)
|
|
1995
|
-
};
|
|
1996
|
-
}
|
|
1997
|
-
|
|
1998
|
-
if (!status || status < 400 || status > 599) {
|
|
1999
|
-
console.warn('"error" returned from load() without a valid status code — defaulting to 500');
|
|
2000
|
-
return { status: 500, error };
|
|
2001
|
-
}
|
|
2002
|
-
|
|
2003
|
-
return { status, error };
|
|
2004
|
-
}
|
|
2005
|
-
|
|
2006
|
-
if (loaded.redirect) {
|
|
2007
|
-
if (!loaded.status || Math.floor(loaded.status / 100) !== 3) {
|
|
2008
|
-
return {
|
|
2009
|
-
status: 500,
|
|
2010
|
-
error: new Error(
|
|
2011
|
-
'"redirect" property returned from load() must be accompanied by a 3xx status code'
|
|
2012
|
-
)
|
|
2013
|
-
};
|
|
2014
|
-
}
|
|
2015
|
-
|
|
2016
|
-
if (typeof loaded.redirect !== 'string') {
|
|
2017
|
-
return {
|
|
2018
|
-
status: 500,
|
|
2019
|
-
error: new Error('"redirect" property returned from load() must be a string')
|
|
2020
|
-
};
|
|
2021
|
-
}
|
|
2022
|
-
}
|
|
2023
|
-
|
|
2024
|
-
return loaded;
|
|
2025
|
-
}
|
|
2026
|
-
|
|
2027
|
-
/**
|
|
2028
|
-
* @param {{
|
|
2029
|
-
* request: import('../../types').Request;
|
|
2030
|
-
* options: import('../../types').RenderOptions;
|
|
2031
|
-
* $session: any;
|
|
2032
|
-
* route: import('../../types').Page;
|
|
2033
|
-
* status: number;
|
|
2034
|
-
* error: Error
|
|
2035
|
-
* }} opts
|
|
2036
|
-
* @returns {Promise<import('../../types').Response>}
|
|
2037
|
-
*/
|
|
2038
|
-
async function get_response({ request, options, $session, route, status = 200, error }) {
|
|
2039
|
-
const host = options.host || request.headers[options.host_header];
|
|
2040
|
-
|
|
2041
|
-
/** @type {Record<string, import('../../types').Response>} */
|
|
2042
|
-
const dependencies = {};
|
|
2043
|
-
|
|
2044
|
-
const serialized_session = try_serialize($session, (error) => {
|
|
2045
|
-
throw new Error(`Failed to serialize session data: ${error.message}`);
|
|
2046
|
-
});
|
|
2047
|
-
|
|
2048
|
-
/** @type {Array<{ url: string, payload: string }>} */
|
|
2049
|
-
const serialized_data = [];
|
|
2050
|
-
|
|
2051
|
-
const match = route && route.pattern.exec(request.path);
|
|
2052
|
-
const params = route && route.params(match);
|
|
2053
|
-
|
|
2054
|
-
const page = {
|
|
2055
|
-
host,
|
|
2056
|
-
path: request.path,
|
|
2057
|
-
query: request.query,
|
|
2058
|
-
params
|
|
2059
|
-
};
|
|
2060
|
-
|
|
2061
|
-
let uses_credentials = false;
|
|
2062
|
-
|
|
2063
|
-
/**
|
|
2064
|
-
* @param {string} url
|
|
2065
|
-
* @param {RequestInit} opts
|
|
2066
|
-
*/
|
|
2067
|
-
const fetcher = async (url, opts = {}) => {
|
|
2068
|
-
if (options.local && url.startsWith(options.paths.assets)) {
|
|
2069
|
-
// when running `start`, or prerendering, `assets` should be
|
|
2070
|
-
// config.kit.paths.assets, but we should still be able to fetch
|
|
2071
|
-
// assets directly from `static`
|
|
2072
|
-
url = url.replace(options.paths.assets, '');
|
|
2073
|
-
}
|
|
2074
|
-
|
|
2075
|
-
const parsed = parse(url);
|
|
2076
|
-
|
|
2077
|
-
if (opts.credentials !== 'omit') {
|
|
2078
|
-
uses_credentials = true;
|
|
2079
|
-
}
|
|
2080
|
-
|
|
2081
|
-
let response;
|
|
2082
|
-
|
|
2083
|
-
if (parsed.protocol) {
|
|
2084
|
-
// external fetch
|
|
2085
|
-
response = await fetch(parsed.href, opts);
|
|
2086
|
-
} else {
|
|
2087
|
-
// otherwise we're dealing with an internal fetch
|
|
2088
|
-
const resolved = resolve(request.path, parsed.pathname);
|
|
2089
|
-
|
|
2090
|
-
// is this a request for a static asset?
|
|
2091
|
-
const filename = resolved.slice(1);
|
|
2092
|
-
const filename_html = `${filename}/index.html`;
|
|
2093
|
-
const asset = options.manifest.assets.find(
|
|
2094
|
-
(d) => d.file === filename || d.file === filename_html
|
|
2095
|
-
);
|
|
2096
|
-
|
|
2097
|
-
if (asset) {
|
|
2098
|
-
if (options.get_static_file) {
|
|
2099
|
-
response = new Response(options.get_static_file(asset.file), {
|
|
2100
|
-
headers: {
|
|
2101
|
-
'content-type': asset.type
|
|
2102
|
-
}
|
|
2103
|
-
});
|
|
2104
|
-
} else {
|
|
2105
|
-
// TODO we need to know what protocol to use
|
|
2106
|
-
response = await fetch(`http://${page.host}/${asset.file}`, opts);
|
|
2107
|
-
}
|
|
2108
|
-
}
|
|
2109
|
-
|
|
2110
|
-
if (!response) {
|
|
2111
|
-
const rendered = await ssr(
|
|
2112
|
-
{
|
|
2113
|
-
host: request.host,
|
|
2114
|
-
method: opts.method || 'GET',
|
|
2115
|
-
headers: opts.headers || {}, // TODO inject credentials...
|
|
2116
|
-
path: resolved,
|
|
2117
|
-
body: opts.body,
|
|
2118
|
-
query: new URLSearchParams$1(parsed.query || '')
|
|
2119
|
-
},
|
|
2120
|
-
{
|
|
2121
|
-
...options,
|
|
2122
|
-
fetched: url
|
|
2123
|
-
}
|
|
2124
|
-
);
|
|
2125
|
-
|
|
2126
|
-
if (rendered) {
|
|
2127
|
-
// TODO this is primarily for the benefit of the static case,
|
|
2128
|
-
// but could it be used elsewhere?
|
|
2129
|
-
dependencies[resolved] = rendered;
|
|
2130
|
-
|
|
2131
|
-
response = new Response(rendered.body, {
|
|
2132
|
-
status: rendered.status,
|
|
2133
|
-
headers: rendered.headers
|
|
2134
|
-
});
|
|
2135
|
-
}
|
|
2136
|
-
}
|
|
2137
|
-
}
|
|
2138
|
-
|
|
2139
|
-
if (response) {
|
|
2140
|
-
const clone = response.clone();
|
|
2141
|
-
|
|
2142
|
-
/** @type {import('../../types').Headers} */
|
|
2143
|
-
const headers = {};
|
|
2144
|
-
clone.headers.forEach((value, key) => {
|
|
2145
|
-
if (key !== 'etag') headers[key] = value;
|
|
2146
|
-
});
|
|
2147
|
-
|
|
2148
|
-
const payload = JSON.stringify({
|
|
2149
|
-
status: clone.status,
|
|
2150
|
-
statusText: clone.statusText,
|
|
2151
|
-
headers,
|
|
2152
|
-
body: await clone.text() // TODO handle binary data
|
|
2153
|
-
});
|
|
2154
|
-
|
|
2155
|
-
// TODO i guess we need to sanitize/escape this... somehow?
|
|
2156
|
-
serialized_data.push({ url, payload });
|
|
2157
|
-
|
|
2158
|
-
return response;
|
|
2159
|
-
}
|
|
2160
|
-
|
|
2161
|
-
return new Response('Not found', {
|
|
2162
|
-
status: 404
|
|
2163
|
-
});
|
|
2164
|
-
};
|
|
2165
|
-
|
|
2166
|
-
const parts = error ? [options.manifest.layout] : [options.manifest.layout, ...route.parts];
|
|
2167
|
-
|
|
2168
|
-
const component_promises = parts.map((loader) => loader());
|
|
2169
|
-
const components = [];
|
|
2170
|
-
const props_promises = [];
|
|
2171
|
-
|
|
2172
|
-
let context = {};
|
|
2173
|
-
let maxage;
|
|
2174
|
-
|
|
2175
|
-
for (let i = 0; i < component_promises.length; i += 1) {
|
|
2176
|
-
let loaded;
|
|
2177
|
-
|
|
2178
|
-
try {
|
|
2179
|
-
const mod = await component_promises[i];
|
|
2180
|
-
components[i] = mod.default;
|
|
2181
|
-
|
|
2182
|
-
if (options.only_prerender && !mod.prerender) {
|
|
2183
|
-
return;
|
|
2184
|
-
}
|
|
2185
|
-
|
|
2186
|
-
if (mod.preload) {
|
|
2187
|
-
throw new Error(
|
|
2188
|
-
'preload has been deprecated in favour of load. Please consult the documentation: https://kit.svelte.dev/docs#load'
|
|
2189
|
-
);
|
|
2190
|
-
}
|
|
2191
|
-
|
|
2192
|
-
loaded =
|
|
2193
|
-
mod.load &&
|
|
2194
|
-
(await mod.load.call(null, {
|
|
2195
|
-
page,
|
|
2196
|
-
get session() {
|
|
2197
|
-
uses_credentials = true;
|
|
2198
|
-
return $session;
|
|
2199
|
-
},
|
|
2200
|
-
fetch: fetcher,
|
|
2201
|
-
context: { ...context }
|
|
2202
|
-
}));
|
|
2203
|
-
} catch (e) {
|
|
2204
|
-
// if load fails when we're already rendering the
|
|
2205
|
-
// error page, there's not a lot we can do
|
|
2206
|
-
if (error) throw e;
|
|
2207
|
-
|
|
2208
|
-
loaded = { error: e, status: 500 };
|
|
2209
|
-
}
|
|
2210
|
-
|
|
2211
|
-
if (loaded) {
|
|
2212
|
-
loaded = normalize(loaded);
|
|
2213
|
-
|
|
2214
|
-
// TODO there's some logic that's duplicated in the client runtime,
|
|
2215
|
-
// it would be nice to DRY it out if possible
|
|
2216
|
-
if (loaded.error) {
|
|
2217
|
-
return await get_response({
|
|
2218
|
-
request,
|
|
2219
|
-
options,
|
|
2220
|
-
$session,
|
|
2221
|
-
route,
|
|
2222
|
-
status: loaded.status,
|
|
2223
|
-
error: loaded.error
|
|
2224
|
-
});
|
|
2225
|
-
}
|
|
2226
|
-
|
|
2227
|
-
if (loaded.redirect) {
|
|
2228
|
-
return {
|
|
2229
|
-
status: loaded.status,
|
|
2230
|
-
headers: {
|
|
2231
|
-
location: loaded.redirect
|
|
2232
|
-
}
|
|
2233
|
-
};
|
|
2234
|
-
}
|
|
2235
|
-
|
|
2236
|
-
if (loaded.context) {
|
|
2237
|
-
context = {
|
|
2238
|
-
...context,
|
|
2239
|
-
...loaded.context
|
|
2240
|
-
};
|
|
2241
|
-
}
|
|
2242
|
-
|
|
2243
|
-
maxage = loaded.maxage || 0;
|
|
2244
|
-
|
|
2245
|
-
props_promises[i] = loaded.props;
|
|
2246
|
-
}
|
|
2247
|
-
}
|
|
2248
|
-
|
|
2249
|
-
const session = writable($session);
|
|
2250
|
-
let session_tracking_active = false;
|
|
2251
|
-
const unsubscribe = session.subscribe(() => {
|
|
2252
|
-
if (session_tracking_active) uses_credentials = true;
|
|
2253
|
-
});
|
|
2254
|
-
session_tracking_active = true;
|
|
2255
|
-
|
|
2256
|
-
if (error) {
|
|
2257
|
-
if (options.dev) {
|
|
2258
|
-
error.stack = await options.get_stack(error);
|
|
2259
|
-
} else {
|
|
2260
|
-
// remove error.stack in production
|
|
2261
|
-
error.stack = String(error);
|
|
2262
|
-
}
|
|
2263
|
-
}
|
|
2264
|
-
|
|
2265
|
-
/** @type {Record<string, any>} */
|
|
2266
|
-
const props = {
|
|
2267
|
-
status,
|
|
2268
|
-
error,
|
|
2269
|
-
stores: {
|
|
2270
|
-
page: writable(null),
|
|
2271
|
-
navigating: writable(null),
|
|
2272
|
-
session
|
|
2273
|
-
},
|
|
2274
|
-
page,
|
|
2275
|
-
components
|
|
2276
|
-
};
|
|
2277
|
-
|
|
2278
|
-
// leveln (instead of levels[n]) makes it easy to avoid
|
|
2279
|
-
// unnecessary updates for layout components
|
|
2280
|
-
for (let i = 0; i < props_promises.length; i += 1) {
|
|
2281
|
-
props[`props_${i}`] = await props_promises[i];
|
|
2282
|
-
}
|
|
2283
|
-
|
|
2284
|
-
let rendered;
|
|
2285
|
-
|
|
2286
|
-
try {
|
|
2287
|
-
rendered = options.root.render(props);
|
|
2288
|
-
} catch (e) {
|
|
2289
|
-
if (error) throw e;
|
|
2290
|
-
|
|
2291
|
-
return await get_response({
|
|
2292
|
-
request,
|
|
2293
|
-
options,
|
|
2294
|
-
$session,
|
|
2295
|
-
route,
|
|
2296
|
-
status: 500,
|
|
2297
|
-
error: e
|
|
2298
|
-
});
|
|
2299
|
-
}
|
|
2300
|
-
|
|
2301
|
-
unsubscribe();
|
|
2302
|
-
|
|
2303
|
-
// TODO all the `route &&` stuff is messy
|
|
2304
|
-
const js_deps = route ? route.js : [];
|
|
2305
|
-
const css_deps = route ? route.css : [];
|
|
2306
|
-
const style = route ? route.style : '';
|
|
2307
|
-
|
|
2308
|
-
const s = JSON.stringify;
|
|
2309
|
-
|
|
2310
|
-
// TODO strip the AMP stuff out of the build if not relevant
|
|
2311
|
-
const links = options.amp
|
|
2312
|
-
? `<style amp-custom>${
|
|
2313
|
-
style || (await Promise.all(css_deps.map((dep) => options.get_amp_css(dep)))).join('\n')
|
|
2314
|
-
}</style>`
|
|
2315
|
-
: [
|
|
2316
|
-
...js_deps.map((dep) => `<link rel="modulepreload" href="${dep}">`),
|
|
2317
|
-
...css_deps.map((dep) => `<link rel="stylesheet" href="${dep}">`)
|
|
2318
|
-
].join('\n\t\t\t');
|
|
2319
|
-
|
|
2320
|
-
const init = options.amp
|
|
2321
|
-
? `
|
|
2322
|
-
<style amp-boilerplate>body{-webkit-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-moz-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-ms-animation:-amp-start 8s steps(1,end) 0s 1 normal both;animation:-amp-start 8s steps(1,end) 0s 1 normal both}@-webkit-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-moz-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-ms-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-o-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}</style>
|
|
2323
|
-
<noscript><style amp-boilerplate>body{-webkit-animation:none;-moz-animation:none;-ms-animation:none;animation:none}</style></noscript>
|
|
2324
|
-
<script async src="https://cdn.ampproject.org/v0.js"></script>`
|
|
2325
|
-
: `
|
|
2326
|
-
<script type="module">
|
|
2327
|
-
import { start } from ${s(options.entry)};
|
|
2328
|
-
${options.start_global ? `window.${options.start_global} = () => ` : ''}start({
|
|
2329
|
-
target: ${options.target ? `document.querySelector(${s(options.target)})` : 'document.body'},
|
|
2330
|
-
host: ${host ? s(host) : 'location.host'},
|
|
2331
|
-
paths: ${s(options.paths)},
|
|
2332
|
-
status: ${status},
|
|
2333
|
-
error: ${serialize_error(error)},
|
|
2334
|
-
session: ${serialized_session}
|
|
2335
|
-
});
|
|
2336
|
-
</script>`;
|
|
2337
|
-
|
|
2338
|
-
const head = [
|
|
2339
|
-
rendered.head,
|
|
2340
|
-
options.amp ? '' : `<style data-svelte>${style}</style>`,
|
|
2341
|
-
links,
|
|
2342
|
-
init
|
|
2343
|
-
].join('\n\n');
|
|
2344
|
-
|
|
2345
|
-
const body = options.amp
|
|
2346
|
-
? rendered.html
|
|
2347
|
-
: `${rendered.html}
|
|
2348
|
-
|
|
2349
|
-
${serialized_data
|
|
2350
|
-
.map(({ url, payload }) => `<script type="svelte-data" url="${url}">${payload}</script>`)
|
|
2351
|
-
.join('\n\n\t\t\t')}
|
|
2352
|
-
`.replace(/^\t{2}/gm, '');
|
|
2353
|
-
|
|
2354
|
-
/** @type {import('../../types').Headers} */
|
|
2355
|
-
const headers = {
|
|
2356
|
-
'content-type': 'text/html'
|
|
2357
|
-
};
|
|
2358
|
-
|
|
2359
|
-
if (maxage) {
|
|
2360
|
-
headers['cache-control'] = `${uses_credentials ? 'private' : 'public'}, max-age=${maxage}`;
|
|
2361
|
-
}
|
|
2362
|
-
|
|
2363
|
-
return {
|
|
2364
|
-
status,
|
|
2365
|
-
headers,
|
|
2366
|
-
body: options.template({ head, body }),
|
|
2367
|
-
dependencies
|
|
2368
|
-
};
|
|
2369
|
-
}
|
|
2370
|
-
|
|
2371
|
-
/**
|
|
2372
|
-
* @param {import('../../types').Request} request
|
|
2373
|
-
* @param {any} context
|
|
2374
|
-
* @param {import('../../types').RenderOptions} options
|
|
2375
|
-
*/
|
|
2376
|
-
async function render_page(request, context, options) {
|
|
2377
|
-
const route = options.manifest.pages.find((route) => route.pattern.test(request.path));
|
|
2378
|
-
|
|
2379
|
-
const $session = await (options.setup.getSession && options.setup.getSession(context));
|
|
2380
|
-
|
|
2381
|
-
if (!route) {
|
|
2382
|
-
if (options.fetched) {
|
|
2383
|
-
// we came here because of a bad request in a `load` function.
|
|
2384
|
-
// rather than render the error page — which could lead to an
|
|
2385
|
-
// infinite loop, if the `load` belonged to the root layout,
|
|
2386
|
-
// we respond with a bare-bones 500
|
|
2387
|
-
throw new Error(`Bad request in load function: failed to fetch ${options.fetched}`);
|
|
2388
|
-
}
|
|
2389
|
-
|
|
2390
|
-
return await get_response({
|
|
2391
|
-
request,
|
|
2392
|
-
options,
|
|
2393
|
-
$session,
|
|
2394
|
-
route,
|
|
2395
|
-
status: 404,
|
|
2396
|
-
error: new Error(`Not found: ${request.path}`)
|
|
2397
|
-
});
|
|
2398
|
-
}
|
|
2399
|
-
|
|
2400
|
-
return await get_response({
|
|
2401
|
-
request,
|
|
2402
|
-
options,
|
|
2403
|
-
$session,
|
|
2404
|
-
route,
|
|
2405
|
-
status: 200,
|
|
2406
|
-
error: null
|
|
2407
|
-
});
|
|
2408
|
-
}
|
|
2409
|
-
|
|
2410
|
-
/**
|
|
2411
|
-
* @param {any} data
|
|
2412
|
-
* @param {(error: Error) => void} [fail]
|
|
2413
|
-
*/
|
|
2414
|
-
function try_serialize(data, fail) {
|
|
2415
|
-
try {
|
|
2416
|
-
return devalue(data);
|
|
2417
|
-
} catch (err) {
|
|
2418
|
-
if (fail) fail(err);
|
|
2419
|
-
return null;
|
|
2420
|
-
}
|
|
2421
|
-
}
|
|
2422
|
-
|
|
2423
|
-
// Ensure we return something truthy so the client will not re-render the page over the error
|
|
2424
|
-
|
|
2425
|
-
/** @param {Error} error */
|
|
2426
|
-
function serialize_error(error) {
|
|
2427
|
-
if (!error) return null;
|
|
2428
|
-
let serialized = try_serialize(error);
|
|
2429
|
-
if (!serialized) {
|
|
2430
|
-
const { name, message, stack } = error;
|
|
2431
|
-
serialized = try_serialize({ name, message, stack });
|
|
2432
|
-
}
|
|
2433
|
-
if (!serialized) {
|
|
2434
|
-
serialized = '{}';
|
|
2435
|
-
}
|
|
2436
|
-
return serialized;
|
|
2437
|
-
}
|
|
2438
|
-
|
|
2439
|
-
/**
|
|
2440
|
-
* @param {import('../../types').Request} request
|
|
2441
|
-
* @param {*} context // TODO
|
|
2442
|
-
* @param {import('../../types').RenderOptions} options
|
|
2443
|
-
* @returns {Promise<import('../../types').Response>}
|
|
2444
|
-
*/
|
|
2445
|
-
function render_route(request, context, options) {
|
|
2446
|
-
const route = options.manifest.endpoints.find((route) => route.pattern.test(request.path));
|
|
2447
|
-
if (!route) return null;
|
|
2448
|
-
|
|
2449
|
-
return route.load().then(async (mod) => {
|
|
2450
|
-
const handler = mod[request.method.toLowerCase().replace('delete', 'del')]; // 'delete' is a reserved word
|
|
2451
|
-
|
|
2452
|
-
if (handler) {
|
|
2453
|
-
const match = route.pattern.exec(request.path);
|
|
2454
|
-
const params = route.params(match);
|
|
2455
|
-
|
|
2456
|
-
const response = await handler(
|
|
2457
|
-
{
|
|
2458
|
-
host: options.host || request.headers[options.host_header || 'host'],
|
|
2459
|
-
path: request.path,
|
|
2460
|
-
headers: request.headers,
|
|
2461
|
-
query: request.query,
|
|
2462
|
-
body: request.body,
|
|
2463
|
-
params
|
|
2464
|
-
},
|
|
2465
|
-
context
|
|
2466
|
-
);
|
|
2467
|
-
|
|
2468
|
-
if (typeof response !== 'object' || response.body == null) {
|
|
2469
|
-
return {
|
|
2470
|
-
status: 500,
|
|
2471
|
-
body: `Invalid response from route ${request.path}; ${
|
|
2472
|
-
response.body == null ? 'body is missing' : `expected an object, got ${typeof response}`
|
|
2473
|
-
}`,
|
|
2474
|
-
headers: {}
|
|
2475
|
-
};
|
|
2476
|
-
}
|
|
2477
|
-
|
|
2478
|
-
let { status = 200, body, headers = {} } = response;
|
|
2479
|
-
|
|
2480
|
-
headers = lowercase_keys(headers);
|
|
2481
|
-
|
|
2482
|
-
if (
|
|
2483
|
-
(typeof body === 'object' && !('content-type' in headers)) ||
|
|
2484
|
-
headers['content-type'] === 'application/json'
|
|
2485
|
-
) {
|
|
2486
|
-
headers = { ...headers, 'content-type': 'application/json' };
|
|
2487
|
-
body = JSON.stringify(body);
|
|
2488
|
-
}
|
|
2489
|
-
|
|
2490
|
-
return { status, body, headers };
|
|
2491
|
-
} else {
|
|
2492
|
-
return {
|
|
2493
|
-
status: 501,
|
|
2494
|
-
body: `${request.method} is not implemented for ${request.path}`,
|
|
2495
|
-
headers: {}
|
|
2496
|
-
};
|
|
2497
|
-
}
|
|
2498
|
-
});
|
|
2499
|
-
}
|
|
2500
|
-
|
|
2501
|
-
/** @param {Record<string, string>} obj */
|
|
2502
|
-
function lowercase_keys(obj) {
|
|
2503
|
-
/** @type {Record<string, string>} */
|
|
2504
|
-
const clone = {};
|
|
2505
|
-
|
|
2506
|
-
for (const key in obj) {
|
|
2507
|
-
clone[key.toLowerCase()] = obj[key];
|
|
2508
|
-
}
|
|
2509
|
-
|
|
2510
|
-
return clone;
|
|
2511
|
-
}
|
|
2512
|
-
|
|
2513
|
-
/** @param {string} body */
|
|
2514
|
-
function md5(body) {
|
|
2515
|
-
return createHash('md5').update(body).digest('hex');
|
|
2516
|
-
}
|
|
2517
|
-
|
|
2518
|
-
/**
|
|
2519
|
-
* @param {import('../../types').Request} request
|
|
2520
|
-
* @param {import('../../types').RenderOptions} options
|
|
2521
|
-
*/
|
|
2522
|
-
async function ssr(request, options) {
|
|
2523
|
-
if (request.path.endsWith('/') && request.path !== '/') {
|
|
2524
|
-
const q = request.query.toString();
|
|
2525
|
-
|
|
2526
|
-
return {
|
|
2527
|
-
status: 301,
|
|
2528
|
-
headers: {
|
|
2529
|
-
location: request.path.slice(0, -1) + (q ? `?${q}` : '')
|
|
2530
|
-
}
|
|
2531
|
-
};
|
|
2532
|
-
}
|
|
2533
|
-
|
|
2534
|
-
const { context, headers = {} } =
|
|
2535
|
-
(await (options.setup.prepare && options.setup.prepare({ headers: request.headers }))) || {};
|
|
2536
|
-
|
|
2537
|
-
try {
|
|
2538
|
-
const response = await (render_route(request, context, options) ||
|
|
2539
|
-
render_page(request, context, options));
|
|
2540
|
-
|
|
2541
|
-
if (response) {
|
|
2542
|
-
// inject ETags for 200 responses
|
|
2543
|
-
if (response.status === 200) {
|
|
2544
|
-
if (!/(no-store|immutable)/.test(response.headers['cache-control'])) {
|
|
2545
|
-
const etag = `"${md5(response.body)}"`;
|
|
2546
|
-
|
|
2547
|
-
if (request.headers['if-none-match'] === etag) {
|
|
2548
|
-
return {
|
|
2549
|
-
status: 304,
|
|
2550
|
-
headers: {},
|
|
2551
|
-
body: null
|
|
2552
|
-
};
|
|
2553
|
-
}
|
|
2554
|
-
|
|
2555
|
-
response.headers['etag'] = etag;
|
|
2556
|
-
}
|
|
2557
|
-
}
|
|
2558
|
-
|
|
2559
|
-
return {
|
|
2560
|
-
status: response.status,
|
|
2561
|
-
headers: { ...headers, ...response.headers },
|
|
2562
|
-
body: response.body,
|
|
2563
|
-
dependencies: response.dependencies
|
|
2564
|
-
};
|
|
2565
|
-
}
|
|
2566
|
-
} catch (e) {
|
|
2567
|
-
if (e && e.stack) {
|
|
2568
|
-
e.stack = await options.get_stack(e);
|
|
2569
|
-
}
|
|
2570
|
-
|
|
2571
|
-
console.error((e && e.stack) || e);
|
|
2572
|
-
|
|
2573
|
-
return {
|
|
2574
|
-
status: 500,
|
|
2575
|
-
headers,
|
|
2576
|
-
body: options.dev ? e.stack : e.message
|
|
2577
|
-
};
|
|
2578
|
-
}
|
|
2579
|
-
}
|
|
2580
|
-
|
|
2581
|
-
export { ssr };
|