@webqit/fetch-plus 0.1.1
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/.github/FUNDING.yml +13 -0
- package/.github/workflows/publish.yml +48 -0
- package/.gitignore +6 -0
- package/LICENSE +21 -0
- package/README.md +3 -0
- package/dist/main.js +2 -0
- package/dist/main.js.map +7 -0
- package/package.json +54 -0
- package/src/FormDataPlus.js +57 -0
- package/src/HeadersPlus.js +152 -0
- package/src/LiveResponse.js +494 -0
- package/src/RequestPlus.js +80 -0
- package/src/ResponsePlus.js +56 -0
- package/src/URLSearchParamsPlus.js +80 -0
- package/src/core.js +172 -0
- package/src/fetchPlus.js +7 -0
- package/src/index.browser.js +4 -0
- package/src/index.js +8 -0
- package/test/basic.test.js +0 -0
|
@@ -0,0 +1,494 @@
|
|
|
1
|
+
import { _isObject, _isTypeObject } from '@webqit/util/js/index.js';
|
|
2
|
+
import { BroadcastChannelPlus, WebSocketPort, MessagePortPlus, Observer } from '@webqit/port-plus';
|
|
3
|
+
import { isTypeStream, _meta, _wq } from './core.js';
|
|
4
|
+
import { ResponsePlus } from './ResponsePlus.js';
|
|
5
|
+
import { HeadersPlus } from './HeadersPlus.js';
|
|
6
|
+
|
|
7
|
+
export class LiveResponse extends EventTarget {
|
|
8
|
+
|
|
9
|
+
static get xHeaderName() {
|
|
10
|
+
return 'X-Background-Messaging-Port';
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
static test(unknown) {
|
|
14
|
+
if (unknown instanceof LiveResponse
|
|
15
|
+
|| unknown?.[Symbol.toStringTag] === 'LiveResponse') {
|
|
16
|
+
return 'LiveResponse';
|
|
17
|
+
}
|
|
18
|
+
if (unknown?.[Symbol.toStringTag] === 'LiveProgramHandle') {
|
|
19
|
+
return 'LiveProgramHandle';
|
|
20
|
+
}
|
|
21
|
+
if (unknown instanceof Response) {
|
|
22
|
+
return 'Response';
|
|
23
|
+
}
|
|
24
|
+
if (isGenerator(unknown)) {
|
|
25
|
+
return 'Generator';
|
|
26
|
+
}
|
|
27
|
+
return 'Default';
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
static hasBackgroundPort(respone) {
|
|
31
|
+
return !!respone.headers?.get?.(this.xHeaderName)?.trim();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
static getBackgroundPort(respone) {
|
|
35
|
+
if (!/Response/.test(this.test(respone))) {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
const responseMeta = _meta(respone);
|
|
39
|
+
|
|
40
|
+
if (!responseMeta.has('background_port')) {
|
|
41
|
+
const value = respone.headers.get(this.xHeaderName)?.trim();
|
|
42
|
+
if (!value) return;
|
|
43
|
+
|
|
44
|
+
const [proto, portID] = value.split(':');
|
|
45
|
+
if (!['ws', 'br'].includes(proto)) {
|
|
46
|
+
throw new Error(`Unknown background messaging protocol: ${value}`);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const backgroundPort = proto === 'br'
|
|
50
|
+
? new BroadcastChannelPlus(portID, { autoStart: false, postAwaitsOpen: true, clientServerMode: 'client' })
|
|
51
|
+
: new WebSocketPort(portID, { autoStart: false, naturalOpen: false, postAwaitsOpen: true });
|
|
52
|
+
|
|
53
|
+
responseMeta.set('background_port', backgroundPort);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return responseMeta.get('background_port');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
static from(data, ...args) {
|
|
60
|
+
if (this.test(data) === 'LiveResponse') {
|
|
61
|
+
return data.clone(...args);
|
|
62
|
+
}
|
|
63
|
+
return new this(data, ...args);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/* INSTANCE */
|
|
67
|
+
|
|
68
|
+
[Symbol.toStringTag] = 'LiveResponse';
|
|
69
|
+
|
|
70
|
+
constructor(body, ...args) {
|
|
71
|
+
super();
|
|
72
|
+
this.#replaceWith(body, ...args);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/* Level 1 props */
|
|
76
|
+
|
|
77
|
+
#body = null;
|
|
78
|
+
get body() { return this.#body; }
|
|
79
|
+
|
|
80
|
+
get bodyUsed() { return false; }
|
|
81
|
+
|
|
82
|
+
#headers = new HeadersPlus;
|
|
83
|
+
get headers() { return this.#headers; }
|
|
84
|
+
|
|
85
|
+
#status = 200;
|
|
86
|
+
get status() { return this.#status; }
|
|
87
|
+
|
|
88
|
+
#statusText = '';
|
|
89
|
+
get statusText() { return this.#statusText; }
|
|
90
|
+
|
|
91
|
+
/* Level 2 props */
|
|
92
|
+
|
|
93
|
+
#type = 'basic';
|
|
94
|
+
get type() { return this.#type; }
|
|
95
|
+
|
|
96
|
+
#redirected = false;
|
|
97
|
+
get redirected() { return this.#redirected; }
|
|
98
|
+
|
|
99
|
+
#url = null;
|
|
100
|
+
get url() { return this.#url; }
|
|
101
|
+
|
|
102
|
+
get ok() { return this.#status >= 200 && this.#status < 299; }
|
|
103
|
+
|
|
104
|
+
async arrayBuffer() { throw new Error(`LiveResponse does not support the arrayBuffer() method.`); }
|
|
105
|
+
|
|
106
|
+
async formData() { throw new Error(`LiveResponse does not support the formData() method.`); }
|
|
107
|
+
|
|
108
|
+
async json() { throw new Error(`LiveResponse does not support the json() method.`); }
|
|
109
|
+
|
|
110
|
+
async text() { throw new Error(`LiveResponse does not support the text() method.`); }
|
|
111
|
+
|
|
112
|
+
async blob() { throw new Error(`LiveResponse does not support the blob() method.`); }
|
|
113
|
+
|
|
114
|
+
async bytes() { throw new Error(`LiveResponse does not support the bytes() method.`); }
|
|
115
|
+
|
|
116
|
+
/* Level 3 props */
|
|
117
|
+
|
|
118
|
+
get background() { return this.constructor.getBackgroundPort(this); }
|
|
119
|
+
|
|
120
|
+
// Lifecycle
|
|
121
|
+
|
|
122
|
+
#abortController = new AbortController;
|
|
123
|
+
get signal() { return this.#abortController.signal; }
|
|
124
|
+
|
|
125
|
+
get readyState() {
|
|
126
|
+
const readyStateInternals = getReadyStateInternals.call(this);
|
|
127
|
+
return readyStateInternals.done.state ? 'done'
|
|
128
|
+
: (readyStateInternals.live.state ? 'live' : 'waiting');
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
readyStateChange(query) {
|
|
132
|
+
if (!['live', 'done'].includes(query)) {
|
|
133
|
+
throw new Error(`Invalid readyState query "${query}"`);
|
|
134
|
+
}
|
|
135
|
+
const readyStateInternals = getReadyStateInternals.call(this);
|
|
136
|
+
return readyStateInternals[query].promise;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
disconnect() {
|
|
140
|
+
this.#abortController.abort();
|
|
141
|
+
this.#abortController = new AbortController;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
#currentFramePromise;
|
|
145
|
+
#extendLifecycle(promise) {
|
|
146
|
+
const readyStateInternals = getReadyStateInternals.call(this);
|
|
147
|
+
if (readyStateInternals.done.state) {
|
|
148
|
+
throw new Error('Response already done.');
|
|
149
|
+
}
|
|
150
|
+
this.#currentFramePromise = promise;
|
|
151
|
+
promise.then((value) => {
|
|
152
|
+
if (this.#currentFramePromise === promise) {
|
|
153
|
+
this.#currentFramePromise = null;
|
|
154
|
+
readyStateInternals.done.state = true;
|
|
155
|
+
readyStateInternals.done.resolve(value);
|
|
156
|
+
}
|
|
157
|
+
}).catch((e) => {
|
|
158
|
+
if (this.#currentFramePromise === promise) {
|
|
159
|
+
this.#currentFramePromise = null;
|
|
160
|
+
readyStateInternals.done.state = true;
|
|
161
|
+
readyStateInternals.done.reject(e);
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
async replaceWith(body, ...args) {
|
|
167
|
+
if (this.readyState === 'done') {
|
|
168
|
+
throw new Error('Response already done.');
|
|
169
|
+
}
|
|
170
|
+
this.disconnect(); // Disconnect from existing source if any
|
|
171
|
+
await this.#replaceWith(body, ...args);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
async #replaceWith(body, ...args) {
|
|
175
|
+
if (body instanceof Promise) {
|
|
176
|
+
this.#extendLifecycle(body);
|
|
177
|
+
return await new Promise((resolve, reject) => {
|
|
178
|
+
let aborted = false;
|
|
179
|
+
this.#abortController.signal.addEventListener('abort', () => {
|
|
180
|
+
aborted = true
|
|
181
|
+
resolve();
|
|
182
|
+
});
|
|
183
|
+
body.then(async (resolveData) => {
|
|
184
|
+
if (aborted) return;
|
|
185
|
+
await this.#replaceWith(resolveData, ...args);
|
|
186
|
+
resolve();
|
|
187
|
+
});
|
|
188
|
+
body.catch((e) => reject(e));
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// ----------- Formatters
|
|
193
|
+
|
|
194
|
+
const directReplaceWith = (responseLike) => {
|
|
195
|
+
const $body = responseLike.body;
|
|
196
|
+
|
|
197
|
+
this.#status = responseLike.status;
|
|
198
|
+
this.#statusText = responseLike.statusText;
|
|
199
|
+
|
|
200
|
+
for (const [name] of [/*IMPORTANT*/...this.#headers.entries()]) { // for some reason, some entries not produced when not spread
|
|
201
|
+
this.#headers.delete(name);
|
|
202
|
+
}
|
|
203
|
+
for (const [name, value] of responseLike.headers.entries()) {
|
|
204
|
+
this.#headers.append(name, value);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
this.#type = responseLike.type;
|
|
208
|
+
this.#redirected = responseLike.redirected;
|
|
209
|
+
this.#url = responseLike.url;
|
|
210
|
+
|
|
211
|
+
this.#body = $body;
|
|
212
|
+
|
|
213
|
+
// Must come after all property assignments above because it fires events
|
|
214
|
+
Observer.defineProperty(this, 'body', { get: () => this.#body, enumerable: true, configurable: true });
|
|
215
|
+
|
|
216
|
+
const readyStateInternals = getReadyStateInternals.call(this);
|
|
217
|
+
readyStateInternals.live.state = true;
|
|
218
|
+
readyStateInternals.live.resolve();
|
|
219
|
+
|
|
220
|
+
this.dispatchEvent(new Event('replace'));
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
const wrapReplaceWith = async (body, options) => {
|
|
224
|
+
directReplaceWith({
|
|
225
|
+
body,
|
|
226
|
+
status: 200,
|
|
227
|
+
statusText: '',
|
|
228
|
+
headers: new Headers,
|
|
229
|
+
...options,
|
|
230
|
+
type: 'basic',
|
|
231
|
+
redirected: false,
|
|
232
|
+
url: null
|
|
233
|
+
});
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
// ----------- "Response" handler
|
|
237
|
+
|
|
238
|
+
const execReplaceWithResponse = async (response, options) => {
|
|
239
|
+
let body, jsonSuccess = false;
|
|
240
|
+
try {
|
|
241
|
+
body = response instanceof Response
|
|
242
|
+
? await ResponsePlus.prototype.parse.call(response, { to: 'json' })
|
|
243
|
+
: response.body;
|
|
244
|
+
jsonSuccess = true;
|
|
245
|
+
} catch (e) {
|
|
246
|
+
body = response.body;
|
|
247
|
+
}
|
|
248
|
+
directReplaceWith({
|
|
249
|
+
body,
|
|
250
|
+
status: response.status,
|
|
251
|
+
statusText: response.statusText,
|
|
252
|
+
headers: response.headers,
|
|
253
|
+
...options,
|
|
254
|
+
type: response.type,
|
|
255
|
+
redirected: response.redirected,
|
|
256
|
+
url: response.url,
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
if (this.constructor.test(response) === 'LiveResponse') {
|
|
260
|
+
response.addEventListener('replace', () => {
|
|
261
|
+
directReplaceWith(response)
|
|
262
|
+
}, { signal: this.#abortController.signal });
|
|
263
|
+
return await response.readyStateChange('done');
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if (this.hasBackgroundPort(response)) {
|
|
267
|
+
const backgroundPort = this.constructor.getBackgroundPort(response);
|
|
268
|
+
// Bind to upstream mutations
|
|
269
|
+
let undoInitialProjectMutations;
|
|
270
|
+
if (jsonSuccess) {
|
|
271
|
+
undoInitialProjectMutations = donePromise.projectMutations({
|
|
272
|
+
from: 'initial_response',
|
|
273
|
+
to: body,
|
|
274
|
+
signal: this.#abortController.signal
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
// Bind to replacements
|
|
278
|
+
backgroundPort.addEventListener('response.replace', (e) => {
|
|
279
|
+
undoInitialProjectMutations?.();
|
|
280
|
+
undoInitialProjectMutations = null;
|
|
281
|
+
|
|
282
|
+
directReplaceWith(e.data);
|
|
283
|
+
}, { signal: this.#abortController.signal });
|
|
284
|
+
// Wait until done
|
|
285
|
+
return await backgroundPort.readyStateChange('close');
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
return Promise.resolve();
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
// ----------- "Generator" handler
|
|
292
|
+
|
|
293
|
+
const execReplaceWithGenerator = async (gen, options) => {
|
|
294
|
+
const firstFrame = await gen.next();
|
|
295
|
+
const firstValue = await firstFrame.value;
|
|
296
|
+
|
|
297
|
+
await this.#replaceWith(firstValue, { done: firstFrame.done, ...options });
|
|
298
|
+
// this is the first time options has a chance to be applied
|
|
299
|
+
|
|
300
|
+
let frame = firstFrame;
|
|
301
|
+
let value = firstValue;
|
|
302
|
+
|
|
303
|
+
while (!frame.done && !this.#abortController.signal.aborted) {
|
|
304
|
+
frame = await gen.next();
|
|
305
|
+
value = await frame.value;
|
|
306
|
+
if (!this.#abortController.signal.aborted) {
|
|
307
|
+
await this.#replaceWith(value, { done: options.done === false ? false : frame.done });
|
|
308
|
+
// top-level false need to be respected: means keep instance alive even when done
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
// ----------- "LiveProgramHandle" handler
|
|
314
|
+
|
|
315
|
+
const execReplaceWithLiveProgramHandle = async (liveProgramHandle, options) => {
|
|
316
|
+
await this.#replaceWith(liveProgramHandle.value, options);
|
|
317
|
+
// this is the first time options has a chance to be applied
|
|
318
|
+
|
|
319
|
+
Observer.observe(
|
|
320
|
+
liveProgramHandle,
|
|
321
|
+
'value',
|
|
322
|
+
(e) => this.#replaceWith(e.value, { done: false }),
|
|
323
|
+
// we're never done unless explicitly aborted
|
|
324
|
+
{ signal: this.#abortController.signal }
|
|
325
|
+
);
|
|
326
|
+
|
|
327
|
+
return new Promise(() => { });
|
|
328
|
+
};
|
|
329
|
+
|
|
330
|
+
// ----------- Procesing time
|
|
331
|
+
|
|
332
|
+
const options = _isObject(args[0]/* !ORDER 1 */) ? { ...args.shift() } : {};
|
|
333
|
+
const frameClosure = typeof args[0]/* !ORDER 2 */ === 'function' ? args.shift() : null;
|
|
334
|
+
|
|
335
|
+
if ('status' in options) {
|
|
336
|
+
options.status = parseInt(options.status);
|
|
337
|
+
if (options.status < 200 || options.status > 599) {
|
|
338
|
+
throw new Error(`The status provided (${options.status}) is outside the range [200, 599].`);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
if ('statusText' in options) {
|
|
342
|
+
options.statusText = String(options.statusText);
|
|
343
|
+
}
|
|
344
|
+
if (options.headers && !(options.headers instanceof Headers)) {
|
|
345
|
+
options.headers = new Headers(options.headers);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// ----------- Dispatch time
|
|
349
|
+
|
|
350
|
+
let donePromise;
|
|
351
|
+
|
|
352
|
+
if (/Response/.test(this.constructor.test(body))) {
|
|
353
|
+
if (frameClosure) {
|
|
354
|
+
throw new Error(`frameClosure is not supported for responses.`);
|
|
355
|
+
}
|
|
356
|
+
donePromise = await execReplaceWithResponse(body, options);
|
|
357
|
+
} else if (this.constructor.test(body) === 'Generator') {
|
|
358
|
+
if (frameClosure) {
|
|
359
|
+
throw new Error(`frameClosure is not supported for generators.`);
|
|
360
|
+
}
|
|
361
|
+
donePromise = await execReplaceWithGenerator(body, options);
|
|
362
|
+
} else if (this.constructor.test(body) === 'LiveProgramHandle') {
|
|
363
|
+
if (frameClosure) {
|
|
364
|
+
throw new Error(`frameClosure is not supported for live program handles.`);
|
|
365
|
+
}
|
|
366
|
+
donePromise = await execReplaceWithLiveProgramHandle(body, options);
|
|
367
|
+
} else {
|
|
368
|
+
donePromise = wrapReplaceWith(body, options);
|
|
369
|
+
if (frameClosure) {
|
|
370
|
+
const reactiveProxy = _isTypeObject(body) && !isTypeStream(body)
|
|
371
|
+
? Observer.proxy(body, { chainable: true, membrane: body })
|
|
372
|
+
: body;
|
|
373
|
+
donePromise = Promise.resolve(frameClosure.call(this, reactiveProxy));
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// Lifecycle time
|
|
378
|
+
|
|
379
|
+
this.#extendLifecycle(options.done === false ? new Promise(() => { }) : donePromise);
|
|
380
|
+
|
|
381
|
+
return await new Promise((resolve, reject) => {
|
|
382
|
+
this.#abortController.signal.addEventListener('abort', resolve);
|
|
383
|
+
donePromise.then(() => resolve());
|
|
384
|
+
donePromise.catch((e) => reject(e));
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// ----------- Conversions
|
|
389
|
+
|
|
390
|
+
toResponse({ client: clientPort, signal: abortSignal } = {}) {
|
|
391
|
+
if (clientPort && !(clientPort instanceof MessagePortPlus)) {
|
|
392
|
+
throw new Error('Client must be a MessagePortPlus interface');
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
const response = ResponsePlus.from(this.body, {
|
|
396
|
+
status: this.status,
|
|
397
|
+
statusText: this.statusText,
|
|
398
|
+
headers: this.headers,
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
const responseMeta = _meta(this);
|
|
402
|
+
_wq(response).set('meta', responseMeta);
|
|
403
|
+
|
|
404
|
+
if (clientPort && this.readyState === 'live') {
|
|
405
|
+
let undoInitialProjectMutations;
|
|
406
|
+
if (_isTypeObject(this.body) && !isTypeStream(this.body)) {
|
|
407
|
+
undoInitialProjectMutations = clientPort.projectMutations({
|
|
408
|
+
from: this.body,
|
|
409
|
+
to: 'initial_response',
|
|
410
|
+
signal: abortSignal/* stop observing mutations on body when we abort */
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
const replaceHandler = () => {
|
|
415
|
+
undoInitialProjectMutations?.();
|
|
416
|
+
undoInitialProjectMutations = null;
|
|
417
|
+
|
|
418
|
+
const headers = Object.fromEntries([...this.headers.entries()]);
|
|
419
|
+
|
|
420
|
+
if (headers?.['set-cookie']) {
|
|
421
|
+
delete headers['set-cookie'];
|
|
422
|
+
console.warn('Warning: The "set-cookie" header is not supported for security reasons and has been removed from the response.');
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
clientPort.postMessage({
|
|
426
|
+
body: this.body,
|
|
427
|
+
status: this.status,
|
|
428
|
+
statusText: this.statusText,
|
|
429
|
+
headers,
|
|
430
|
+
done: this.readyState === 'done',
|
|
431
|
+
}, { type: 'response.replace', live: true/*gracefully ignored if not an object*/, signal: this.#abortController.signal/* stop observing mutations on body a new body takes effect */ });
|
|
432
|
+
};
|
|
433
|
+
|
|
434
|
+
this.addEventListener('replace', replaceHandler, { signal: abortSignal/* stop listening when we abort */ });
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
return response;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
async * toGenerator({ signal: abortSignal } = {}) {
|
|
441
|
+
do {
|
|
442
|
+
yield this.body;
|
|
443
|
+
} while (await new Promise((resolve) => {
|
|
444
|
+
this.addEventListener('replace', () => resolve(true), { once: true, signal: abortSignal });
|
|
445
|
+
this.readyStateChange('done').then(() => resolve(false));
|
|
446
|
+
}));
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
toLiveProgramHandle({ signal: abortSignal } = {}) {
|
|
450
|
+
const handle = new LiveProgramHandleX;
|
|
451
|
+
|
|
452
|
+
const replaceHandler = () => Observer.defineProperty(handle, 'value', { value: this.body, enumerable: true, configurable: true });
|
|
453
|
+
this.addEventListener('replace', replaceHandler, { signal: abortSignal });
|
|
454
|
+
replaceHandler();
|
|
455
|
+
|
|
456
|
+
return handle;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
clone(init = {}) {
|
|
460
|
+
const clone = new this.constructor();
|
|
461
|
+
|
|
462
|
+
const responseMeta = _meta(this);
|
|
463
|
+
_wq(clone).set('meta', responseMeta);
|
|
464
|
+
|
|
465
|
+
clone.replaceWith(this, init);
|
|
466
|
+
return clone;
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
export const isGenerator = (obj) => {
|
|
471
|
+
return typeof obj?.next === 'function' &&
|
|
472
|
+
typeof obj?.throw === 'function' &&
|
|
473
|
+
typeof obj?.return === 'function';
|
|
474
|
+
};
|
|
475
|
+
|
|
476
|
+
export function getReadyStateInternals() {
|
|
477
|
+
const portPlusMeta = _meta(this);
|
|
478
|
+
if (!portPlusMeta.has('readystate_registry')) {
|
|
479
|
+
const $ref = (o) => {
|
|
480
|
+
o.promise = new Promise((res, rej) => (o.resolve = res, o.reject = rej));
|
|
481
|
+
return o;
|
|
482
|
+
};
|
|
483
|
+
portPlusMeta.set('readystate_registry', {
|
|
484
|
+
live: $ref({}),
|
|
485
|
+
done: $ref({}),
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
return portPlusMeta.get('readystate_registry');
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
class LiveProgramHandleX {
|
|
492
|
+
[Symbol.toStringTag] = 'LiveProgramHandle';
|
|
493
|
+
abort() { }
|
|
494
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { messageParserMixin, _meta, _wq } from './core.js';
|
|
2
|
+
import { HeadersPlus } from './HeadersPlus.js';
|
|
3
|
+
|
|
4
|
+
export class RequestPlus extends messageParserMixin(Request) {
|
|
5
|
+
|
|
6
|
+
constructor(url, init = {}) {
|
|
7
|
+
super(url, init);
|
|
8
|
+
HeadersPlus.upgradeInPlace(this.headers);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
static upgradeInPlace(request) {
|
|
12
|
+
Object.setPrototypeOf(request, RequestPlus.prototype);
|
|
13
|
+
HeadersPlus.upgradeInPlace(request.headers);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
static from(url, { memoize = false, ...init } = {}) {
|
|
17
|
+
if (url instanceof Request) return url;
|
|
18
|
+
|
|
19
|
+
let $type, $$body = init.body;
|
|
20
|
+
if ('body' in init) {
|
|
21
|
+
const { body, headers, $type: $$type } = super.from(init);
|
|
22
|
+
init = { ...init, body, headers };
|
|
23
|
+
$type = $$type;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const instance = new this.constructor(url, init);
|
|
27
|
+
|
|
28
|
+
if (memoize) {
|
|
29
|
+
const cache = _meta(instance, 'cache');
|
|
30
|
+
const typeMap = { json: 'json', FormData: 'formData', text: 'text', ArrayBuffer: 'arrayBuffer', Blob: 'blob', Bytes: 'bytes' };
|
|
31
|
+
cache.set(typeMap[$type] || 'original', $$body);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return instance;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
static async copy(request, init = {}) {
|
|
38
|
+
const attrs = ['method', 'headers', 'mode', 'credentials', 'cache', 'redirect', 'referrer', 'integrity'];
|
|
39
|
+
const requestInit = attrs.reduce(($init, prop) => (
|
|
40
|
+
{
|
|
41
|
+
...$init,
|
|
42
|
+
[prop]: prop in init
|
|
43
|
+
? init[prop]
|
|
44
|
+
: (prop === 'headers'
|
|
45
|
+
? new Headers(request[prop])
|
|
46
|
+
: request[prop])
|
|
47
|
+
}
|
|
48
|
+
), {});
|
|
49
|
+
if (!['GET', 'HEAD'].includes(init.method?.toUpperCase() || request.method)) {
|
|
50
|
+
if ('body' in init) {
|
|
51
|
+
requestInit.body = init.body
|
|
52
|
+
if (!('headers' in init)) {
|
|
53
|
+
requestInit.headers.delete('Content-Type');
|
|
54
|
+
requestInit.headers.delete('Content-Length');
|
|
55
|
+
}
|
|
56
|
+
} else {
|
|
57
|
+
requestInit.body = await request.clone().arrayBuffer();
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
if (requestInit.mode === 'navigate') {
|
|
61
|
+
requestInit.mode = 'cors';
|
|
62
|
+
}
|
|
63
|
+
return { url: request.url, ...requestInit };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
clone() {
|
|
67
|
+
const clone = super.clone();
|
|
68
|
+
RequestPlus.upgradeInPlace(clone);
|
|
69
|
+
|
|
70
|
+
const requestMeta = _meta(this);
|
|
71
|
+
_wq(clone).set('meta', new Map(requestMeta));
|
|
72
|
+
if (requestMeta.has('cache')) {
|
|
73
|
+
requestMeta.set('cache', new Map(requestMeta.get('cache')));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return clone;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { messageParserMixin, _meta, _wq } from './core.js';
|
|
2
|
+
import { HeadersPlus } from './HeadersPlus.js';
|
|
3
|
+
|
|
4
|
+
export class ResponsePlus extends messageParserMixin(Response) {
|
|
5
|
+
|
|
6
|
+
constructor(body, init = {}) {
|
|
7
|
+
super(body, init);
|
|
8
|
+
HeadersPlus.upgradeInPlace(this.headers);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
static upgradeInPlace(response) {
|
|
12
|
+
Object.setPrototypeOf(response, ResponsePlus.prototype);
|
|
13
|
+
HeadersPlus.upgradeInPlace(response.headers);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
static from(body, { memoize = false, ...init } = {}) {
|
|
17
|
+
if (body instanceof Response) return body;
|
|
18
|
+
|
|
19
|
+
let $type, $body = body;
|
|
20
|
+
if (body || body === 0) {
|
|
21
|
+
let headers;
|
|
22
|
+
({ body, headers, $type } = super.from({ body, headers: init.headers }));
|
|
23
|
+
init = { ...init, headers };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const instance = new this.constructor(body, init);
|
|
27
|
+
|
|
28
|
+
if (memoize) {
|
|
29
|
+
const cache = _meta(instance, 'cache');
|
|
30
|
+
const typeMap = { json: 'json', FormData: 'formData', text: 'text', ArrayBuffer: 'arrayBuffer', Blob: 'blob', Bytes: 'bytes' };
|
|
31
|
+
cache.set(typeMap[$type] || 'original', body);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return instance;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
get status() {
|
|
38
|
+
// Support framework-injected app-level 'status'
|
|
39
|
+
return _meta(this).get('status') ?? super.status;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
clone() {
|
|
43
|
+
const clone = super.clone();
|
|
44
|
+
ResponsePlus.upgradeInPlace(clone);
|
|
45
|
+
|
|
46
|
+
const responseMeta = _meta(this);
|
|
47
|
+
_wq(clone).set('meta', new Map(responseMeta));
|
|
48
|
+
if (responseMeta.has('cache')) {
|
|
49
|
+
responseMeta.set('cache', new Map(responseMeta.get('cache')));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return clone;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
|