@webqit/webflo 0.20.15 → 0.20.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/runtime-pi/WebfloRuntime.js +18 -14
- package/src/runtime-pi/webflo-client/WebfloClient.js +65 -34
- package/src/runtime-pi/webflo-client/WebfloSubClient.js +8 -4
- package/src/runtime-pi/webflo-fetch/LiveResponse.js +2 -1
- package/src/runtime-pi/webflo-fetch/index.js +0 -6
- package/src/runtime-pi/webflo-routing/HttpEvent.js +3 -4
- package/src/runtime-pi/webflo-routing/HttpState.js +1 -1
- package/src/runtime-pi/webflo-routing/HttpThread.js +6 -1
- package/src/runtime-pi/webflo-routing/WebfloRouter.js +2 -2
package/package.json
CHANGED
|
@@ -15,13 +15,13 @@ export class WebfloRuntime {
|
|
|
15
15
|
|
|
16
16
|
static get Router() { return WebfloRouter; }
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
static get HttpEvent() { return HttpEvent; }
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
static get HttpThread() { return HttpThread; }
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
static get HttpSession() { return HttpSession; }
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
static get HttpUser() { return HttpUser; }
|
|
25
25
|
|
|
26
26
|
static create(bootstrap) { return new this(bootstrap); }
|
|
27
27
|
|
|
@@ -109,7 +109,7 @@ export class WebfloRuntime {
|
|
|
109
109
|
|
|
110
110
|
// Dispatch event
|
|
111
111
|
const router = new this.constructor.Router(this, httpEvent.url.pathname);
|
|
112
|
-
await router.route(['SETUP'], httpEvent.
|
|
112
|
+
await router.route(['SETUP'], httpEvent.spawn());
|
|
113
113
|
|
|
114
114
|
// Do proper routing for respone
|
|
115
115
|
const response = await new Promise(async (resolve) => {
|
|
@@ -141,8 +141,8 @@ export class WebfloRuntime {
|
|
|
141
141
|
: responseShim.from.value(response);
|
|
142
142
|
}
|
|
143
143
|
|
|
144
|
-
// Any "
|
|
145
|
-
await this.
|
|
144
|
+
// Any "status" in thread?
|
|
145
|
+
await this.handleThreadStatus(httpEvent, response);
|
|
146
146
|
|
|
147
147
|
// Resolve now...
|
|
148
148
|
if (autoLiveResponse) {
|
|
@@ -192,8 +192,11 @@ export class WebfloRuntime {
|
|
|
192
192
|
|
|
193
193
|
// On ROOT event complete:
|
|
194
194
|
// Close httpEvent.client
|
|
195
|
-
httpEvent.lifeCycleComplete(true).then(() => {
|
|
195
|
+
httpEvent.lifeCycleComplete(true).then(async () => {
|
|
196
196
|
httpEvent.client.close();
|
|
197
|
+
if (!httpEvent.thread.extended) {
|
|
198
|
+
await httpEvent.thread.clear();
|
|
199
|
+
}
|
|
197
200
|
});
|
|
198
201
|
}
|
|
199
202
|
|
|
@@ -205,16 +208,17 @@ export class WebfloRuntime {
|
|
|
205
208
|
return response;
|
|
206
209
|
}
|
|
207
210
|
|
|
208
|
-
async
|
|
211
|
+
async handleThreadStatus(httpEvent, response) {
|
|
209
212
|
if (!response.headers.get('Location')) {
|
|
210
|
-
const status = await httpEvent.thread.consume('status');
|
|
211
|
-
|
|
212
|
-
if (!status) return;
|
|
213
|
+
const status = await httpEvent.thread.consume('status', true);
|
|
214
|
+
if (!status.length) return;
|
|
213
215
|
// Fire redirect message?
|
|
214
216
|
httpEvent.waitUntil(new Promise((resolve) => {
|
|
215
217
|
httpEvent.client.wqLifecycle.open.then(async () => {
|
|
216
|
-
|
|
217
|
-
|
|
218
|
+
setTimeout(() => {
|
|
219
|
+
httpEvent.client.postMessage(status, { wqEventOptions: { type: 'alert' } });
|
|
220
|
+
resolve();
|
|
221
|
+
}, 500); // half a sec
|
|
218
222
|
}, { once: true });
|
|
219
223
|
}));
|
|
220
224
|
}
|
|
@@ -58,20 +58,24 @@ export class WebfloClient extends WebfloRuntime {
|
|
|
58
58
|
const instanceController = await super.initialize();
|
|
59
59
|
// Bind prompt handlers
|
|
60
60
|
const promptsHandler = (e) => {
|
|
61
|
-
|
|
62
|
-
? e.data.message
|
|
63
|
-
: e.data;
|
|
64
|
-
const execPromp = () => {
|
|
61
|
+
window.queueMicrotask(() => {
|
|
65
62
|
if (e.defaultPrevented) return;
|
|
66
63
|
if (e.type === 'confirm') {
|
|
67
|
-
e.
|
|
64
|
+
if (e.data?.message) {
|
|
65
|
+
e.wqRespondWith(confirm(e.data.message));
|
|
66
|
+
}
|
|
68
67
|
} else if (e.type === 'prompt') {
|
|
69
|
-
e.
|
|
68
|
+
if (e.data?.message) {
|
|
69
|
+
e.wqRespondWith(prompt(e.data.message));
|
|
70
|
+
}
|
|
70
71
|
} else if (e.type === 'alert') {
|
|
71
|
-
|
|
72
|
+
for (const item of [].concat(e.data)) {
|
|
73
|
+
if (item?.message) {
|
|
74
|
+
alert(item.message);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
72
77
|
}
|
|
73
|
-
};
|
|
74
|
-
window.queueMicrotask(execPromp);
|
|
78
|
+
});
|
|
75
79
|
};
|
|
76
80
|
this.background.addEventListener('confirm', promptsHandler, { signal: instanceController.signal });
|
|
77
81
|
this.background.addEventListener('prompt', promptsHandler, { signal: instanceController.signal });
|
|
@@ -374,22 +378,41 @@ export class WebfloClient extends WebfloRuntime {
|
|
|
374
378
|
}
|
|
375
379
|
|
|
376
380
|
async dispatchNavigationEvent({ httpEvent, crossLayerFetch, clientPortB, originalRequestInit, processObj = {} }) {
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
381
|
+
let response = await super.dispatchNavigationEvent({ httpEvent, crossLayerFetch, clientPortB });
|
|
382
|
+
|
|
383
|
+
// Extract interactive. mode handling
|
|
384
|
+
const handleInteractiveMode = async (resolve) => {
|
|
385
|
+
const liveResponse = await LiveResponse.from(response);
|
|
386
|
+
this.background.addPort(liveResponse.background);
|
|
387
|
+
liveResponse.addEventListener('replace', () => {
|
|
388
|
+
if (liveResponse.headers.get('Location')) {
|
|
389
|
+
this.processRedirect(liveResponse);
|
|
390
|
+
} else {
|
|
391
|
+
resolve?.(liveResponse);
|
|
392
|
+
}
|
|
393
|
+
}, { signal: httpEvent.signal });
|
|
394
|
+
return liveResponse;
|
|
395
|
+
};
|
|
396
|
+
|
|
383
397
|
// Await a response with an "Accepted" or redirect status
|
|
384
398
|
const statusCode = responseShim.prototype.status.get.call(response);
|
|
385
|
-
if (statusCode === 202
|
|
399
|
+
if (statusCode === 202 && LiveResponse.hasBackground(response)) {
|
|
400
|
+
return new Promise(handleInteractiveMode);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// Handle redirects
|
|
404
|
+
if (response.headers.get('Location')) {
|
|
405
|
+
// Never resolves...
|
|
386
406
|
return new Promise(async (resolve) => {
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
407
|
+
const redirectHandlingMode = this.processRedirect(response);
|
|
408
|
+
// ...except processRedirect() says keep-alive
|
|
409
|
+
if (redirectHandlingMode === 3/* keep-alive */
|
|
410
|
+
&& LiveResponse.hasBackground(response)) {
|
|
411
|
+
await handleInteractiveMode(resolve);
|
|
412
|
+
}
|
|
391
413
|
});
|
|
392
414
|
}
|
|
415
|
+
|
|
393
416
|
// Handle "retry" directives
|
|
394
417
|
if (response.headers.has('Retry-After')) {
|
|
395
418
|
if (!processObj.recurseController) {
|
|
@@ -400,13 +423,21 @@ export class WebfloClient extends WebfloRuntime {
|
|
|
400
423
|
// Ensure a previous recursion hasn't aborted the process
|
|
401
424
|
if (!processObj.recurseController.signal.aborted) {
|
|
402
425
|
await new Promise((res) => setTimeout(res, parseInt(response.headers.get('Retry-After')) * 1000));
|
|
403
|
-
const eventClone = httpEvent.
|
|
426
|
+
const eventClone = httpEvent.clone({ request: this.createRequest(httpEvent.url, originalRequestInit) });
|
|
404
427
|
return await this.dispatchNavigationEvent({ httpEvent: eventClone, crossLayerFetch, clientPortB, originalRequestInit, processObj });
|
|
405
428
|
}
|
|
406
|
-
} else
|
|
407
|
-
|
|
408
|
-
|
|
429
|
+
} else {
|
|
430
|
+
if (processObj.recurseController) {
|
|
431
|
+
// Abort the signal. This is the end of the loop
|
|
432
|
+
processObj.recurseController.abort();
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
// Obtain and connect clientPortB as first thing
|
|
436
|
+
if (LiveResponse.hasBackground(response)) {
|
|
437
|
+
response = await handleInteractiveMode();
|
|
438
|
+
}
|
|
409
439
|
}
|
|
440
|
+
|
|
410
441
|
return response;
|
|
411
442
|
}
|
|
412
443
|
|
|
@@ -419,24 +450,23 @@ export class WebfloClient extends WebfloRuntime {
|
|
|
419
450
|
responseMeta.set('status', xActualRedirectCode); // @NOTE 1
|
|
420
451
|
statusCode = xActualRedirectCode;
|
|
421
452
|
}
|
|
453
|
+
|
|
422
454
|
// Trigger redirect
|
|
423
455
|
if ([302, 301].includes(statusCode)) {
|
|
424
456
|
const location = new URL(response.headers.get('Location'), this.location.origin);
|
|
425
457
|
if (this.isSpaRoute(location)) {
|
|
426
458
|
this.navigate(location, {}, { navigationType: 'rdr' });
|
|
427
|
-
|
|
428
|
-
this.redirect(location, LiveResponse.getBackground(response));
|
|
459
|
+
return 1;
|
|
429
460
|
}
|
|
430
|
-
return
|
|
461
|
+
return this.redirect(location, response);
|
|
431
462
|
}
|
|
463
|
+
|
|
464
|
+
return 0; // No actual redirect
|
|
432
465
|
}
|
|
433
466
|
|
|
434
|
-
redirect(location
|
|
435
|
-
if (responseBackground) {
|
|
436
|
-
// Redundant as this is a window reload anyways
|
|
437
|
-
responseBackground.close();
|
|
438
|
-
}
|
|
467
|
+
redirect(location) {
|
|
439
468
|
window.location = location;
|
|
469
|
+
return 2; // Window reload
|
|
440
470
|
}
|
|
441
471
|
|
|
442
472
|
async transitionUI(updateCallback) {
|
|
@@ -482,8 +512,9 @@ export class WebfloClient extends WebfloRuntime {
|
|
|
482
512
|
transition: this.transition,
|
|
483
513
|
}, { diff: true, merge });
|
|
484
514
|
$response.addEventListener('replace', (e) => {
|
|
485
|
-
if (
|
|
486
|
-
|
|
515
|
+
if (!$response.headers.get('Location')) {
|
|
516
|
+
this.host[bindingsConfig.bindings].data = $response.body;
|
|
517
|
+
}
|
|
487
518
|
});
|
|
488
519
|
}
|
|
489
520
|
if (modulesContextAttrs) {
|
|
@@ -90,16 +90,18 @@ export class WebfloSubClient extends WebfloClient {
|
|
|
90
90
|
(this.host.querySelector('[autofocus]') || this.host).focus();
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
-
redirect(location,
|
|
93
|
+
redirect(location, response = null) {
|
|
94
94
|
location = typeof location === 'string' ? new URL(location, this.location.origin) : location;
|
|
95
95
|
const width = Math.min(800, window.innerWidth);
|
|
96
96
|
const height = Math.min(600, window.innerHeight);
|
|
97
97
|
const left = (window.outerWidth - width) / 2;
|
|
98
98
|
const top = (window.outerHeight - height) / 2;
|
|
99
99
|
const popup = window.open(location, '_blank', `popup=true,width=${width},height=${height},left=${left},top=${top}`);
|
|
100
|
-
if (
|
|
100
|
+
if (response && LiveResponse.hasBackground(response)) {
|
|
101
101
|
Observer.set(this.navigator, 'redirecting', new Url/*NOT URL*/(location), { diff: true });
|
|
102
|
-
|
|
102
|
+
const backgroundPort = LiveResponse.getBackground(response);
|
|
103
|
+
backgroundPort.postMessage('keep-alive');
|
|
104
|
+
backgroundPort.addEventListener('close', (e) => {
|
|
103
105
|
window.removeEventListener('message', windowMessageHandler);
|
|
104
106
|
Observer.set(this.navigator, 'redirecting', null);
|
|
105
107
|
popup.postMessage('timeout:5');
|
|
@@ -109,10 +111,12 @@ export class WebfloSubClient extends WebfloClient {
|
|
|
109
111
|
}, { once: true });
|
|
110
112
|
const windowMessageHandler = (e) => {
|
|
111
113
|
if (e.source === popup && e.data === 'close') {
|
|
112
|
-
|
|
114
|
+
backgroundPort.close();
|
|
113
115
|
}
|
|
114
116
|
};
|
|
115
117
|
window.addEventListener('message', windowMessageHandler);
|
|
116
118
|
}
|
|
119
|
+
|
|
120
|
+
return 3; // keep-alive
|
|
117
121
|
}
|
|
118
122
|
}
|
|
@@ -50,7 +50,8 @@ export class LiveResponse extends EventTarget {
|
|
|
50
50
|
throw new Error('Argument must be a Response instance.');
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
|
|
53
|
+
let body = null;
|
|
54
|
+
try { body = await responseShim.prototype.parse.value.call(response); } catch(e) {}
|
|
54
55
|
|
|
55
56
|
// Instance
|
|
56
57
|
const instance = new this(body, {
|
|
@@ -87,11 +87,6 @@ export const request = {
|
|
|
87
87
|
}
|
|
88
88
|
},
|
|
89
89
|
prototype: {
|
|
90
|
-
carries: {
|
|
91
|
-
get: function () {
|
|
92
|
-
return _wq(this, 'meta').get('carries') || [];
|
|
93
|
-
}
|
|
94
|
-
},
|
|
95
90
|
parse: { value: async function () { return await parseHttpMessage(this); } },
|
|
96
91
|
clone: {
|
|
97
92
|
value: function (init = {}) {
|
|
@@ -149,7 +144,6 @@ export const response = {
|
|
|
149
144
|
: this.status);
|
|
150
145
|
}
|
|
151
146
|
},
|
|
152
|
-
carry: { get: function () { return _wq(this, 'meta').get('carry'); } },
|
|
153
147
|
parse: { value: async function () { return await parseHttpMessage(this); } },
|
|
154
148
|
clone: {
|
|
155
149
|
value: function (init = {}) {
|
|
@@ -132,7 +132,7 @@ export class HttpEvent {
|
|
|
132
132
|
}
|
|
133
133
|
//-----
|
|
134
134
|
const urlRewrite = new URL(url, this.request.url);
|
|
135
|
-
const newThread = this.thread.
|
|
135
|
+
const newThread = this.thread.spawn(urlRewrite.searchParams.get('_thread'));
|
|
136
136
|
urlRewrite.searchParams.set('_thread', newThread.threadID);
|
|
137
137
|
await newThread.append('back', this.request.url.replace(urlRewrite.origin, ''));
|
|
138
138
|
for (const [key, value] of Object.entries(data)) {
|
|
@@ -146,9 +146,8 @@ export class HttpEvent {
|
|
|
146
146
|
return this.constructor.create(this.#parentEvent, { ...this.#init, ...init });
|
|
147
147
|
}
|
|
148
148
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
return instance;
|
|
149
|
+
spawn(init = {}) {
|
|
150
|
+
return this.constructor.create(this/*Main difference from clone*/, { ...this.#init, ...(init || {}) });
|
|
152
151
|
}
|
|
153
152
|
|
|
154
153
|
abort() {
|
|
@@ -140,7 +140,7 @@ export class HttpState {
|
|
|
140
140
|
return new Promise(() => { });
|
|
141
141
|
}
|
|
142
142
|
const urlRewrite = new URL(handler.url, this.#request.url);
|
|
143
|
-
const newThread = this.#thread.
|
|
143
|
+
const newThread = this.#thread.spawn(urlRewrite.searchParams.get('_thread')/* show */);
|
|
144
144
|
urlRewrite.searchParams.set('_thread', newThread.threadID);
|
|
145
145
|
await newThread.append('back', this.#request.url.replace(urlRewrite.origin, ''));
|
|
146
146
|
if (handler.with) {
|
|
@@ -10,6 +10,7 @@ export class HttpThread {
|
|
|
10
10
|
#store;
|
|
11
11
|
#threadID;
|
|
12
12
|
#realm;
|
|
13
|
+
#extended = false;
|
|
13
14
|
|
|
14
15
|
get threadID() { return this.#threadID; }
|
|
15
16
|
|
|
@@ -19,7 +20,7 @@ export class HttpThread {
|
|
|
19
20
|
this.#realm = realm;
|
|
20
21
|
}
|
|
21
22
|
|
|
22
|
-
|
|
23
|
+
spawn(_threadID = null) {
|
|
23
24
|
return this.constructor.create({
|
|
24
25
|
store: this.#store,
|
|
25
26
|
threadID: _threadID,
|
|
@@ -78,4 +79,8 @@ export class HttpThread {
|
|
|
78
79
|
await this.#store.delete(this.#threadID);
|
|
79
80
|
return this;
|
|
80
81
|
}
|
|
82
|
+
|
|
83
|
+
get extended() { return this.#extended; }
|
|
84
|
+
|
|
85
|
+
extend(set = true) { this.#extended = !!set; }
|
|
81
86
|
}
|
|
@@ -132,7 +132,7 @@ export class WebfloRouter {
|
|
|
132
132
|
|
|
133
133
|
// Set context parameters
|
|
134
134
|
nextTick.method = request.method;
|
|
135
|
-
nextTick.event = await thisTick.event.
|
|
135
|
+
nextTick.event = await thisTick.event.spawn({ request });
|
|
136
136
|
nextTick.source = thisTick.destination.join('/');
|
|
137
137
|
nextTick.destination = url.pathname.split('/').map((a) => a.trim()).filter((a) => a);
|
|
138
138
|
nextTick.trail = !urlStr_isRelative ? [] : thisTick.trail.reduce((_commonRoot, _seg, i) => _commonRoot.length === i && _seg === nextTick.destination[i] ? _commonRoot.concat(_seg) : _commonRoot, []);
|
|
@@ -141,7 +141,7 @@ export class WebfloRouter {
|
|
|
141
141
|
if (isFetch) {
|
|
142
142
|
throw new Error('fetch() cannot be called without arguments!');
|
|
143
143
|
}
|
|
144
|
-
nextTick.event = thisTick.event.
|
|
144
|
+
nextTick.event = thisTick.event.spawn();
|
|
145
145
|
}
|
|
146
146
|
const result = await next(nextTick);
|
|
147
147
|
if (asResponse) {
|