@webqit/fetch-plus 0.1.2 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2050 -2
- package/dist/main.js +1 -1
- package/dist/main.js.map +4 -4
- package/package.json +8 -8
- package/src/FormDataPlus.js +23 -15
- package/src/HeadersPlus.js +128 -56
- package/src/LiveResponse.js +233 -155
- package/src/RequestPlus.js +9 -7
- package/src/ResponsePlus.js +6 -6
- package/src/index.js +0 -1
- package/src/messageParserMixin.js +217 -0
- package/test/1.basic.test.js +314 -0
- package/test/2.LiveResponse.test.js +261 -0
- package/test/3.LiveResponse.integration.test.js +459 -0
- package/src/URLSearchParamsPlus.js +0 -80
- package/src/core.js +0 -172
- package/test/basic.test.js +0 -0
package/src/LiveResponse.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { _isObject, _isTypeObject } from '@webqit/util/js/index.js';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { Observer, ListenerRegistry, Descriptor } from '@webqit/observer';
|
|
3
|
+
import { BroadcastChannelPlus, WebSocketPort, MessagePortPlus } from '@webqit/port-plus';
|
|
4
|
+
import { isTypeStream, _meta, _wq } from './messageParserMixin.js';
|
|
4
5
|
import { ResponsePlus } from './ResponsePlus.js';
|
|
5
|
-
import { HeadersPlus } from './HeadersPlus.js';
|
|
6
6
|
|
|
7
7
|
export class LiveResponse extends EventTarget {
|
|
8
8
|
|
|
9
9
|
static get xHeaderName() {
|
|
10
|
-
return 'X-
|
|
10
|
+
return 'X-Message-Port';
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
static test(unknown) {
|
|
@@ -27,33 +27,41 @@ export class LiveResponse extends EventTarget {
|
|
|
27
27
|
return 'Default';
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
static
|
|
30
|
+
static hasPort(respone) {
|
|
31
31
|
return !!respone.headers?.get?.(this.xHeaderName)?.trim();
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
static
|
|
34
|
+
static getPort(respone, autoStart = false) {
|
|
35
35
|
if (!/Response/.test(this.test(respone))) {
|
|
36
36
|
return;
|
|
37
37
|
}
|
|
38
38
|
const responseMeta = _meta(respone);
|
|
39
39
|
|
|
40
|
-
if (!responseMeta.has('
|
|
40
|
+
if (!responseMeta.has('port')) {
|
|
41
41
|
const value = respone.headers.get(this.xHeaderName)?.trim();
|
|
42
42
|
if (!value) return;
|
|
43
43
|
|
|
44
|
-
const [
|
|
45
|
-
if (!
|
|
46
|
-
throw new Error(`Unknown
|
|
44
|
+
const [, scheme, portID] = /^(socket|channel):\/\/(.*)$/.exec(value) || [];
|
|
45
|
+
if (!scheme || !portID) {
|
|
46
|
+
throw new Error(`Unknown port messaging protocol: ${value}`);
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
const
|
|
50
|
-
? new BroadcastChannelPlus(portID, { autoStart
|
|
51
|
-
: new WebSocketPort(portID, { autoStart
|
|
49
|
+
const port = scheme === 'channel'
|
|
50
|
+
? new BroadcastChannelPlus(portID, { autoStart, postAwaitsOpen: true, clientServerMode: 'client' })
|
|
51
|
+
: new WebSocketPort(portID, { autoStart, naturalOpen: false, postAwaitsOpen: true });
|
|
52
52
|
|
|
53
|
-
responseMeta.set('
|
|
53
|
+
responseMeta.set('port', port);
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
return responseMeta.get('
|
|
56
|
+
return responseMeta.get('port');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
static attachPort(respone, port) {
|
|
60
|
+
if (!(port instanceof MessagePortPlus)) {
|
|
61
|
+
throw new Error('Client must be a MessagePortPlus interface');
|
|
62
|
+
}
|
|
63
|
+
const responseMeta = _meta(respone);
|
|
64
|
+
responseMeta.set('port', port);
|
|
57
65
|
}
|
|
58
66
|
|
|
59
67
|
static from(data, ...args) {
|
|
@@ -67,9 +75,18 @@ export class LiveResponse extends EventTarget {
|
|
|
67
75
|
|
|
68
76
|
[Symbol.toStringTag] = 'LiveResponse';
|
|
69
77
|
|
|
78
|
+
#listenersRegistry;
|
|
79
|
+
|
|
70
80
|
constructor(body, ...args) {
|
|
71
81
|
super();
|
|
72
|
-
this.#
|
|
82
|
+
this.#listenersRegistry = ListenerRegistry.getInstance(this, true);
|
|
83
|
+
|
|
84
|
+
const readyStateInternals = getReadyStateInternals.call(this);
|
|
85
|
+
const frame = readyStateInternals.now;
|
|
86
|
+
|
|
87
|
+
this.#replaceWith(frame, body, ...args).catch((e) => {
|
|
88
|
+
frame.reject(e);
|
|
89
|
+
});
|
|
73
90
|
}
|
|
74
91
|
|
|
75
92
|
/* Level 1 props */
|
|
@@ -77,9 +94,12 @@ export class LiveResponse extends EventTarget {
|
|
|
77
94
|
#body = null;
|
|
78
95
|
get body() { return this.#body; }
|
|
79
96
|
|
|
80
|
-
|
|
97
|
+
#concurrent = false;
|
|
98
|
+
get concurrent() { return this.#concurrent; }
|
|
81
99
|
|
|
82
|
-
|
|
100
|
+
get bodyUsed() { return true; }
|
|
101
|
+
|
|
102
|
+
#headers = new Headers;
|
|
83
103
|
get headers() { return this.#headers; }
|
|
84
104
|
|
|
85
105
|
#status = 200;
|
|
@@ -99,28 +119,21 @@ export class LiveResponse extends EventTarget {
|
|
|
99
119
|
#url = null;
|
|
100
120
|
get url() { return this.#url; }
|
|
101
121
|
|
|
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.`); }
|
|
122
|
+
get ok() { return !!(this.#status >= 200 && this.#status < 299); }
|
|
113
123
|
|
|
114
|
-
async
|
|
124
|
+
async now() {
|
|
125
|
+
const readyStateInternals = getReadyStateInternals.call(this);
|
|
126
|
+
return readyStateInternals.now.promise;
|
|
127
|
+
}
|
|
115
128
|
|
|
116
129
|
/* Level 3 props */
|
|
117
130
|
|
|
118
|
-
get
|
|
131
|
+
get port() { return this.constructor.getPort(this, true); }
|
|
119
132
|
|
|
120
133
|
// Lifecycle
|
|
121
134
|
|
|
122
135
|
#abortController = new AbortController;
|
|
123
|
-
|
|
136
|
+
#concurrencyAbortController = new AbortController;
|
|
124
137
|
|
|
125
138
|
get readyState() {
|
|
126
139
|
const readyStateInternals = getReadyStateInternals.call(this);
|
|
@@ -129,16 +142,20 @@ export class LiveResponse extends EventTarget {
|
|
|
129
142
|
}
|
|
130
143
|
|
|
131
144
|
readyStateChange(query) {
|
|
132
|
-
if (!['live', 'done'].includes(query)) {
|
|
145
|
+
if (!['live', 'now', 'done'].includes(query)) {
|
|
133
146
|
throw new Error(`Invalid readyState query "${query}"`);
|
|
134
147
|
}
|
|
135
148
|
const readyStateInternals = getReadyStateInternals.call(this);
|
|
136
149
|
return readyStateInternals[query].promise;
|
|
137
150
|
}
|
|
138
151
|
|
|
139
|
-
disconnect() {
|
|
152
|
+
disconnect(dispose = false) {
|
|
140
153
|
this.#abortController.abort();
|
|
141
154
|
this.#abortController = new AbortController;
|
|
155
|
+
if (dispose) {
|
|
156
|
+
this.#concurrencyAbortController.abort();
|
|
157
|
+
this.#concurrencyAbortController = new AbortController;
|
|
158
|
+
}
|
|
142
159
|
}
|
|
143
160
|
|
|
144
161
|
#currentFramePromise;
|
|
@@ -161,6 +178,7 @@ export class LiveResponse extends EventTarget {
|
|
|
161
178
|
readyStateInternals.done.reject(e);
|
|
162
179
|
}
|
|
163
180
|
});
|
|
181
|
+
return promise;
|
|
164
182
|
}
|
|
165
183
|
|
|
166
184
|
async replaceWith(body, ...args) {
|
|
@@ -168,65 +186,101 @@ export class LiveResponse extends EventTarget {
|
|
|
168
186
|
throw new Error('Response already done.');
|
|
169
187
|
}
|
|
170
188
|
this.disconnect(); // Disconnect from existing source if any
|
|
171
|
-
await this.#replaceWith(body, ...args);
|
|
189
|
+
await this.#replaceWith(null, body, ...args);
|
|
172
190
|
}
|
|
173
191
|
|
|
174
|
-
async #replaceWith(body, ...args) {
|
|
192
|
+
async #replaceWith(__frame, body, ...args) {
|
|
193
|
+
const readyStateInternals = getReadyStateInternals.call(this);
|
|
194
|
+
const frame = __frame || readyStateInternals.now.refresh();
|
|
195
|
+
|
|
196
|
+
// ----------- Promise input
|
|
197
|
+
|
|
175
198
|
if (body instanceof Promise) {
|
|
176
|
-
this.#extendLifecycle(
|
|
177
|
-
return await new Promise((resolve, reject) => {
|
|
178
|
-
let aborted = false;
|
|
199
|
+
return this.#extendLifecycle(new Promise((resolve, reject) => {
|
|
179
200
|
this.#abortController.signal.addEventListener('abort', () => {
|
|
180
|
-
aborted = true
|
|
201
|
+
frame.aborted = true;
|
|
181
202
|
resolve();
|
|
182
203
|
});
|
|
204
|
+
|
|
183
205
|
body.then(async (resolveData) => {
|
|
184
|
-
|
|
185
|
-
await this.#replaceWith(resolveData, ...args);
|
|
206
|
+
await this.#replaceWith(frame, resolveData, ...args);
|
|
186
207
|
resolve();
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
});
|
|
208
|
+
}).catch((e) => reject(e));
|
|
209
|
+
}));
|
|
190
210
|
}
|
|
191
211
|
|
|
192
212
|
// ----------- Formatters
|
|
193
213
|
|
|
194
|
-
const directReplaceWith = (
|
|
195
|
-
|
|
214
|
+
const directReplaceWith = (__frame, responseFrame) => {
|
|
215
|
+
responseFrame = Object.freeze({
|
|
216
|
+
...responseFrame,
|
|
217
|
+
ok: !!(responseFrame.status >= 200 && responseFrame.status < 299),
|
|
218
|
+
bodyUsed: true,
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
if (__frame?.aborted) {
|
|
222
|
+
__frame.resolve(responseFrame);
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
196
225
|
|
|
197
|
-
|
|
198
|
-
|
|
226
|
+
const frame = __frame || readyStateInternals.now.refresh();
|
|
227
|
+
|
|
228
|
+
const $body = responseFrame.body;
|
|
229
|
+
|
|
230
|
+
this.#status = responseFrame.status;
|
|
231
|
+
this.#statusText = responseFrame.statusText;
|
|
199
232
|
|
|
200
233
|
for (const [name] of [/*IMPORTANT*/...this.#headers.entries()]) { // for some reason, some entries not produced when not spread
|
|
201
234
|
this.#headers.delete(name);
|
|
202
235
|
}
|
|
203
|
-
for (const [name, value] of
|
|
236
|
+
for (const [name, value] of responseFrame.headers.entries()) {
|
|
204
237
|
this.#headers.append(name, value);
|
|
205
238
|
}
|
|
206
239
|
|
|
207
|
-
this.#type =
|
|
208
|
-
this.#redirected =
|
|
209
|
-
this.#url =
|
|
240
|
+
this.#type = responseFrame.type;
|
|
241
|
+
this.#redirected = responseFrame.redirected;
|
|
242
|
+
this.#url = responseFrame.url;
|
|
210
243
|
|
|
244
|
+
const bodyOld = this.#body;
|
|
211
245
|
this.#body = $body;
|
|
246
|
+
this.#concurrent = !!responseFrame.concurrent;
|
|
212
247
|
|
|
213
|
-
|
|
214
|
-
|
|
248
|
+
if (!this.#concurrent) {
|
|
249
|
+
this.#concurrencyAbortController.abort();
|
|
250
|
+
this.#concurrencyAbortController = new AbortController;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const descriptor = new Descriptor(this, {
|
|
254
|
+
type: 'set',
|
|
255
|
+
key: 'body',
|
|
256
|
+
value: $body,
|
|
257
|
+
oldValue: bodyOld,
|
|
258
|
+
isUpdate: true,
|
|
259
|
+
related: [],
|
|
260
|
+
operation: 'set',
|
|
261
|
+
detail: null,
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
// Must come first so that observers below here see this state
|
|
215
265
|
|
|
216
|
-
const readyStateInternals = getReadyStateInternals.call(this);
|
|
217
266
|
readyStateInternals.live.state = true;
|
|
218
|
-
readyStateInternals.live.resolve();
|
|
267
|
+
readyStateInternals.live.resolve(this);
|
|
219
268
|
|
|
220
|
-
|
|
269
|
+
// May trigger "done" ready state
|
|
270
|
+
frame.resolve(responseFrame);
|
|
271
|
+
|
|
272
|
+
// Must come after all property assignments above because it fires events
|
|
273
|
+
this.#listenersRegistry.emit([descriptor]);
|
|
274
|
+
this.dispatchEvent(new ReplaceEvent(responseFrame));
|
|
221
275
|
};
|
|
222
276
|
|
|
223
|
-
const wrapReplaceWith =
|
|
224
|
-
directReplaceWith({
|
|
277
|
+
const wrapReplaceWith = (frame, body, options) => {
|
|
278
|
+
directReplaceWith(frame, {
|
|
225
279
|
body,
|
|
226
280
|
status: 200,
|
|
227
281
|
statusText: '',
|
|
228
|
-
headers: new Headers,
|
|
229
282
|
...options,
|
|
283
|
+
headers: options.headers instanceof Headers ? options.headers : new Headers(options.headers || {}),
|
|
230
284
|
type: 'basic',
|
|
231
285
|
redirected: false,
|
|
232
286
|
url: null
|
|
@@ -235,21 +289,22 @@ export class LiveResponse extends EventTarget {
|
|
|
235
289
|
|
|
236
290
|
// ----------- "Response" handler
|
|
237
291
|
|
|
238
|
-
const execReplaceWithResponse = async (response, options) => {
|
|
292
|
+
const execReplaceWithResponse = async (frame, response, options) => {
|
|
239
293
|
let body, jsonSuccess = false;
|
|
240
294
|
try {
|
|
241
295
|
body = response instanceof Response
|
|
242
|
-
? await ResponsePlus.prototype.
|
|
243
|
-
: response.body;
|
|
296
|
+
? await ResponsePlus.prototype.any.call(response, { to: 'json' })
|
|
297
|
+
: (await response.readyStateChange('live')).body;
|
|
244
298
|
jsonSuccess = true;
|
|
245
299
|
} catch (e) {
|
|
246
|
-
body = response
|
|
300
|
+
body = await ResponsePlus.prototype.any.call(response);
|
|
247
301
|
}
|
|
248
|
-
directReplaceWith({
|
|
302
|
+
directReplaceWith(frame, {
|
|
249
303
|
body,
|
|
250
304
|
status: response.status,
|
|
251
305
|
statusText: response.statusText,
|
|
252
306
|
headers: response.headers,
|
|
307
|
+
concurrent: response.concurrent, // for response === LiveResponse
|
|
253
308
|
...options,
|
|
254
309
|
type: response.type,
|
|
255
310
|
redirected: response.redirected,
|
|
@@ -257,54 +312,61 @@ export class LiveResponse extends EventTarget {
|
|
|
257
312
|
});
|
|
258
313
|
|
|
259
314
|
if (this.constructor.test(response) === 'LiveResponse') {
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
}
|
|
263
|
-
|
|
315
|
+
const replaceHandler = () => {
|
|
316
|
+
wrapReplaceWith(null, response.body, response);
|
|
317
|
+
};
|
|
318
|
+
response.addEventListener('replace', replaceHandler, { signal: this.#abortController.signal });
|
|
319
|
+
await response.readyStateChange('done');
|
|
320
|
+
response.removeEventListener('replace', replaceHandler);
|
|
321
|
+
return response;
|
|
264
322
|
}
|
|
265
323
|
|
|
266
|
-
if (this.
|
|
267
|
-
const
|
|
324
|
+
if (this.constructor.hasPort(response)) {
|
|
325
|
+
const port = this.constructor.getPort(response);
|
|
326
|
+
port.start();
|
|
327
|
+
|
|
268
328
|
// Bind to upstream mutations
|
|
269
|
-
let undoInitialProjectMutations;
|
|
270
329
|
if (jsonSuccess) {
|
|
271
|
-
|
|
330
|
+
port.projectMutations({
|
|
272
331
|
from: 'initial_response',
|
|
273
332
|
to: body,
|
|
274
|
-
signal: this.#
|
|
333
|
+
signal: this.#concurrencyAbortController.signal
|
|
275
334
|
});
|
|
276
335
|
}
|
|
336
|
+
|
|
277
337
|
// Bind to replacements
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
338
|
+
return new Promise((resolve) => {
|
|
339
|
+
const replaceHandler = (e) => {
|
|
340
|
+
const { body, ...options } = e.data;
|
|
341
|
+
wrapReplaceWith(null, body, { ...options });
|
|
342
|
+
};
|
|
343
|
+
port.addEventListener('response.replace', replaceHandler, { signal: this.#abortController.signal });
|
|
344
|
+
port.addEventListener('response.done', () => {
|
|
345
|
+
port.removeEventListener('response.replace', replaceHandler);
|
|
346
|
+
resolve(this);
|
|
347
|
+
}, { once: true });
|
|
348
|
+
port.readyStateChange('close').then(resolve);
|
|
349
|
+
});
|
|
286
350
|
}
|
|
287
|
-
|
|
288
|
-
return Promise.resolve();
|
|
289
351
|
};
|
|
290
352
|
|
|
291
353
|
// ----------- "Generator" handler
|
|
292
354
|
|
|
293
|
-
const execReplaceWithGenerator = async (gen, options) => {
|
|
355
|
+
const execReplaceWithGenerator = async (frame, gen, options) => {
|
|
294
356
|
const firstFrame = await gen.next();
|
|
295
357
|
const firstValue = await firstFrame.value;
|
|
296
358
|
|
|
297
|
-
await this.#replaceWith(firstValue, { done: firstFrame.done, ...options });
|
|
359
|
+
await this.#replaceWith(frame, firstValue, { done: firstFrame.done, ...options });
|
|
298
360
|
// this is the first time options has a chance to be applied
|
|
299
361
|
|
|
300
|
-
let
|
|
362
|
+
let generatorFrame = firstFrame;
|
|
301
363
|
let value = firstValue;
|
|
302
364
|
|
|
303
|
-
while (!
|
|
304
|
-
|
|
305
|
-
value = await
|
|
365
|
+
while (!generatorFrame.done && !this.#abortController.signal.aborted) {
|
|
366
|
+
generatorFrame = await gen.next();
|
|
367
|
+
value = await generatorFrame.value;
|
|
306
368
|
if (!this.#abortController.signal.aborted) {
|
|
307
|
-
await this.#replaceWith(value, { done: options.done === false ? false :
|
|
369
|
+
await this.#replaceWith(null, value, { concurrent: options.concurrent, done: options.done === false ? false : generatorFrame.done });
|
|
308
370
|
// top-level false need to be respected: means keep instance alive even when done
|
|
309
371
|
}
|
|
310
372
|
}
|
|
@@ -312,14 +374,14 @@ export class LiveResponse extends EventTarget {
|
|
|
312
374
|
|
|
313
375
|
// ----------- "LiveProgramHandle" handler
|
|
314
376
|
|
|
315
|
-
const execReplaceWithLiveProgramHandle = async (liveProgramHandle, options) => {
|
|
316
|
-
await this.#replaceWith(liveProgramHandle.value, options);
|
|
377
|
+
const execReplaceWithLiveProgramHandle = async (frame, liveProgramHandle, options) => {
|
|
378
|
+
await this.#replaceWith(frame, liveProgramHandle.value, options);
|
|
317
379
|
// this is the first time options has a chance to be applied
|
|
318
380
|
|
|
319
381
|
Observer.observe(
|
|
320
382
|
liveProgramHandle,
|
|
321
383
|
'value',
|
|
322
|
-
(e) => this.#replaceWith(e.value, { done: false }),
|
|
384
|
+
(e) => this.#replaceWith(null, e.value, { concurrent: options.concurrent, done: false }),
|
|
323
385
|
// we're never done unless explicitly aborted
|
|
324
386
|
{ signal: this.#abortController.signal }
|
|
325
387
|
);
|
|
@@ -329,110 +391,109 @@ export class LiveResponse extends EventTarget {
|
|
|
329
391
|
|
|
330
392
|
// ----------- Procesing time
|
|
331
393
|
|
|
332
|
-
const
|
|
333
|
-
const
|
|
394
|
+
const frameClosure = typeof args[0]/* !ORDER 1 */ === 'function' ? args.shift() : null;
|
|
395
|
+
const frameOptions = _isObject(args[0]/* !ORDER 2 */) ? { ...args.shift() } : {};
|
|
334
396
|
|
|
335
|
-
if ('status' in
|
|
336
|
-
|
|
337
|
-
if (
|
|
338
|
-
throw new Error(`The status provided (${
|
|
397
|
+
if ('status' in frameOptions) {
|
|
398
|
+
frameOptions.status = parseInt(frameOptions.status);
|
|
399
|
+
if (frameOptions.status < 200 || frameOptions.status > 599) {
|
|
400
|
+
throw new Error(`The status provided (${frameOptions.status}) is outside the range [200, 599].`);
|
|
339
401
|
}
|
|
340
402
|
}
|
|
341
|
-
if ('statusText' in
|
|
342
|
-
|
|
403
|
+
if ('statusText' in frameOptions) {
|
|
404
|
+
frameOptions.statusText = String(frameOptions.statusText);
|
|
343
405
|
}
|
|
344
|
-
if (
|
|
345
|
-
|
|
406
|
+
if (frameOptions.headers && !(frameOptions.headers instanceof Headers)) {
|
|
407
|
+
frameOptions.headers = new Headers(frameOptions.headers);
|
|
408
|
+
}
|
|
409
|
+
if ('concurrent' in frameOptions) {
|
|
410
|
+
frameOptions.concurrent = Boolean(frameOptions.concurrent);
|
|
346
411
|
}
|
|
347
412
|
|
|
348
413
|
// ----------- Dispatch time
|
|
349
414
|
|
|
350
|
-
let donePromise;
|
|
351
|
-
|
|
352
415
|
if (/Response/.test(this.constructor.test(body))) {
|
|
353
416
|
if (frameClosure) {
|
|
354
417
|
throw new Error(`frameClosure is not supported for responses.`);
|
|
355
418
|
}
|
|
356
|
-
donePromise =
|
|
419
|
+
frame.donePromise = execReplaceWithResponse(frame, body, frameOptions);
|
|
357
420
|
} else if (this.constructor.test(body) === 'Generator') {
|
|
358
421
|
if (frameClosure) {
|
|
359
422
|
throw new Error(`frameClosure is not supported for generators.`);
|
|
360
423
|
}
|
|
361
|
-
donePromise =
|
|
424
|
+
frame.donePromise = execReplaceWithGenerator(frame, body, frameOptions);
|
|
362
425
|
} else if (this.constructor.test(body) === 'LiveProgramHandle') {
|
|
363
426
|
if (frameClosure) {
|
|
364
427
|
throw new Error(`frameClosure is not supported for live program handles.`);
|
|
365
428
|
}
|
|
366
|
-
donePromise =
|
|
429
|
+
frame.donePromise = execReplaceWithLiveProgramHandle(frame, body, frameOptions);
|
|
367
430
|
} else {
|
|
368
|
-
donePromise = wrapReplaceWith(body,
|
|
431
|
+
frame.donePromise = Promise.resolve(wrapReplaceWith(frame, body, frameOptions));
|
|
369
432
|
if (frameClosure) {
|
|
370
433
|
const reactiveProxy = _isTypeObject(body) && !isTypeStream(body)
|
|
371
434
|
? Observer.proxy(body, { chainable: true, membrane: body })
|
|
372
435
|
: body;
|
|
373
|
-
donePromise = Promise.resolve(frameClosure.call(this, reactiveProxy));
|
|
436
|
+
frame.donePromise = Promise.resolve(frameClosure.call(this, reactiveProxy, this.#concurrencyAbortController.signal));
|
|
374
437
|
}
|
|
375
438
|
}
|
|
376
439
|
|
|
377
440
|
// Lifecycle time
|
|
378
441
|
|
|
379
|
-
this.#extendLifecycle(
|
|
380
|
-
|
|
442
|
+
this.#extendLifecycle(frameOptions.done === false ? new Promise(() => { }) : frame.donePromise);
|
|
443
|
+
|
|
381
444
|
return await new Promise((resolve, reject) => {
|
|
382
|
-
this.#abortController.signal.addEventListener('abort', resolve);
|
|
383
|
-
donePromise.then(() => resolve());
|
|
384
|
-
donePromise.catch((e) => reject(e));
|
|
445
|
+
this.#abortController.signal.addEventListener('abort', () => resolve(false));
|
|
446
|
+
frame.donePromise.then(() => resolve(true)).catch(reject);
|
|
385
447
|
});
|
|
386
448
|
}
|
|
387
449
|
|
|
388
450
|
// ----------- Conversions
|
|
389
451
|
|
|
390
|
-
toResponse({
|
|
452
|
+
toResponse({ port: clientPort, signal: abortSignal } = {}) {
|
|
391
453
|
if (clientPort && !(clientPort instanceof MessagePortPlus)) {
|
|
392
454
|
throw new Error('Client must be a MessagePortPlus interface');
|
|
393
455
|
}
|
|
394
456
|
|
|
395
457
|
const response = ResponsePlus.from(this.body, {
|
|
396
|
-
status: this
|
|
397
|
-
statusText: this
|
|
398
|
-
headers: this
|
|
458
|
+
status: this.#status,
|
|
459
|
+
statusText: this.#statusText,
|
|
460
|
+
headers: this.#headers,
|
|
399
461
|
});
|
|
400
462
|
|
|
401
463
|
const responseMeta = _meta(this);
|
|
402
464
|
_wq(response).set('meta', responseMeta);
|
|
403
465
|
|
|
404
|
-
if (clientPort
|
|
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
|
-
}
|
|
466
|
+
if (!clientPort) return response;
|
|
413
467
|
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
468
|
+
if (_isTypeObject(this.#body) && !isTypeStream(this.#body)) {
|
|
469
|
+
clientPort.projectMutations({
|
|
470
|
+
from: this.#body,
|
|
471
|
+
to: 'initial_response',
|
|
472
|
+
signal: AbortSignal.any([this.#concurrencyAbortController.signal].concat(abortSignal || []))/* stop observing mutations on body when we abort */
|
|
473
|
+
});
|
|
474
|
+
}
|
|
417
475
|
|
|
418
|
-
|
|
476
|
+
const replaceHandler = () => {
|
|
477
|
+
const headers = Object.fromEntries([...this.headers.entries()]);
|
|
419
478
|
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
479
|
+
if (headers?.['set-cookie']) {
|
|
480
|
+
delete headers['set-cookie'];
|
|
481
|
+
console.warn('Warning: The "set-cookie" header is not supported for security reasons and has been removed from the response.');
|
|
482
|
+
}
|
|
424
483
|
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
484
|
+
clientPort.postMessage({
|
|
485
|
+
body: this.#body,
|
|
486
|
+
status: this.#status,
|
|
487
|
+
statusText: this.#statusText,
|
|
488
|
+
headers,
|
|
489
|
+
concurrent: this.#concurrent,
|
|
490
|
+
}, { type: 'response.replace', live: true/*gracefully ignored if not an object*/, signal: AbortSignal.any([this.#concurrencyAbortController.signal].concat(abortSignal || []))/* stop observing mutations on body a new body takes effect */ });
|
|
491
|
+
};
|
|
433
492
|
|
|
434
|
-
|
|
435
|
-
|
|
493
|
+
this.addEventListener('replace', replaceHandler, { signal: abortSignal/* stop listening when we abort */ });
|
|
494
|
+
this.readyStateChange('done').then(() => {
|
|
495
|
+
clientPort.postMessage(null, { type: 'response.done' });
|
|
496
|
+
});
|
|
436
497
|
|
|
437
498
|
return response;
|
|
438
499
|
}
|
|
@@ -448,8 +509,8 @@ export class LiveResponse extends EventTarget {
|
|
|
448
509
|
|
|
449
510
|
toLiveProgramHandle({ signal: abortSignal } = {}) {
|
|
450
511
|
const handle = new LiveProgramHandleX;
|
|
451
|
-
|
|
452
|
-
const replaceHandler = () => Observer.defineProperty(handle, 'value', { value: this.body, enumerable:
|
|
512
|
+
|
|
513
|
+
const replaceHandler = () => Observer.defineProperty(handle, 'value', { value: this.body, enumerable: false, configurable: true });
|
|
453
514
|
this.addEventListener('replace', replaceHandler, { signal: abortSignal });
|
|
454
515
|
replaceHandler();
|
|
455
516
|
|
|
@@ -460,7 +521,7 @@ export class LiveResponse extends EventTarget {
|
|
|
460
521
|
const clone = new this.constructor();
|
|
461
522
|
|
|
462
523
|
const responseMeta = _meta(this);
|
|
463
|
-
_wq(clone).set('meta', responseMeta);
|
|
524
|
+
_wq(clone).set('meta', new Map(responseMeta));
|
|
464
525
|
|
|
465
526
|
clone.replaceWith(this, init);
|
|
466
527
|
return clone;
|
|
@@ -477,18 +538,35 @@ export function getReadyStateInternals() {
|
|
|
477
538
|
const portPlusMeta = _meta(this);
|
|
478
539
|
if (!portPlusMeta.has('readystate_registry')) {
|
|
479
540
|
const $ref = (o) => {
|
|
480
|
-
o.promise = new Promise((res, rej) => (o.resolve =
|
|
541
|
+
o.promise = new Promise((res, rej) => (o.resolve = res, o.reject = rej));
|
|
481
542
|
return o;
|
|
482
543
|
};
|
|
483
|
-
|
|
544
|
+
const states = {
|
|
484
545
|
live: $ref({}),
|
|
485
546
|
done: $ref({}),
|
|
486
|
-
}
|
|
547
|
+
};
|
|
548
|
+
(function refresh() {
|
|
549
|
+
states.now = $ref({});
|
|
550
|
+
states.now.refresh = refresh;
|
|
551
|
+
return states.now;
|
|
552
|
+
})();
|
|
553
|
+
portPlusMeta.set('readystate_registry', states);
|
|
487
554
|
}
|
|
488
555
|
return portPlusMeta.get('readystate_registry');
|
|
489
556
|
}
|
|
490
557
|
|
|
491
|
-
class
|
|
558
|
+
export class ReplaceEvent extends Event {
|
|
559
|
+
|
|
560
|
+
#data;
|
|
561
|
+
get data() { return this.#data; }
|
|
562
|
+
|
|
563
|
+
constructor(responseFrame) {
|
|
564
|
+
super('replace');
|
|
565
|
+
this.#data = responseFrame;
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
export class LiveProgramHandleX {
|
|
492
570
|
[Symbol.toStringTag] = 'LiveProgramHandle';
|
|
493
571
|
abort() { }
|
|
494
572
|
}
|