@webqit/webflo 0.20.26 → 0.20.28
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 +8 -5
- package/src/build-pi/index.js +6 -4
- package/src/init-pi/index.js +0 -1
- package/src/runtime-pi/{WebfloRuntime.js → AppRuntime.js} +57 -113
- package/src/runtime-pi/webflo-client/DeviceCapabilities.js +1 -1
- package/src/runtime-pi/webflo-client/WebfloClient.js +163 -103
- package/src/runtime-pi/webflo-client/{WebfloRootClient1.js → WebfloRootClientA.js} +39 -56
- package/src/runtime-pi/webflo-client/{WebfloRootClient2.js → WebfloRootClientB.js} +3 -3
- package/src/runtime-pi/webflo-client/WebfloSubClient.js +28 -15
- package/src/runtime-pi/webflo-client/index.js +3 -3
- package/src/runtime-pi/webflo-messaging/ClientPortMixin.js +13 -0
- package/src/runtime-pi/{webflo-server/messaging/ClientRequestRealtime.js → webflo-messaging/ClientRequestPort001.js} +13 -9
- package/src/runtime-pi/webflo-messaging/ClientRequestPort010.js +4 -0
- package/src/runtime-pi/webflo-messaging/ClientRequestPort100.js +17 -0
- package/src/runtime-pi/webflo-messaging/WebfloTenancy001.js +27 -0
- package/src/runtime-pi/webflo-messaging/WebfloTenant001.js +27 -0
- package/src/runtime-pi/webflo-routing/HttpCookies101.js +53 -0
- package/src/runtime-pi/webflo-routing/HttpCookies110.js +3 -0
- package/src/runtime-pi/webflo-routing/{HttpEvent.js → HttpEvent111.js} +95 -73
- package/src/runtime-pi/webflo-routing/HttpKeyvalInterface.js +120 -0
- package/src/runtime-pi/webflo-routing/HttpSession001.js +24 -0
- package/src/runtime-pi/webflo-routing/HttpSession110.js +3 -0
- package/src/runtime-pi/webflo-routing/{HttpThread.js → HttpThread111.js} +54 -13
- package/src/runtime-pi/webflo-routing/{HttpUser.js → HttpUser111.js} +10 -23
- package/src/runtime-pi/webflo-routing/KeyvalsFactory001.js +53 -0
- package/src/runtime-pi/webflo-routing/KeyvalsFactory110.js +48 -0
- package/src/runtime-pi/webflo-routing/KeyvalsFactoryInterface.js +56 -0
- package/src/runtime-pi/webflo-routing/{WebfloRouter.js → WebfloRouter111.js} +5 -6
- package/src/runtime-pi/webflo-server/WebfloServer.js +262 -269
- package/src/runtime-pi/webflo-worker/WebfloWorker.js +97 -44
- package/src/util.js +3 -2
- package/src/runtime-pi/apis.js +0 -9
- package/src/runtime-pi/webflo-client/ClientSideCookies.js +0 -18
- package/src/runtime-pi/webflo-fetch/LiveResponse.js +0 -476
- package/src/runtime-pi/webflo-fetch/index.js +0 -419
- package/src/runtime-pi/webflo-fetch/util.js +0 -28
- package/src/runtime-pi/webflo-messaging/WQBroadcastChannel.js +0 -10
- package/src/runtime-pi/webflo-messaging/WQMessageChannel.js +0 -26
- package/src/runtime-pi/webflo-messaging/WQMessageEvent.js +0 -87
- package/src/runtime-pi/webflo-messaging/WQMessagePort.js +0 -38
- package/src/runtime-pi/webflo-messaging/WQRelayPort.js +0 -47
- package/src/runtime-pi/webflo-messaging/WQSockPort.js +0 -111
- package/src/runtime-pi/webflo-messaging/WQStarPort.js +0 -112
- package/src/runtime-pi/webflo-messaging/wq-message-port.js +0 -413
- package/src/runtime-pi/webflo-routing/HttpCookies.js +0 -43
- package/src/runtime-pi/webflo-routing/HttpSession.js +0 -11
- package/src/runtime-pi/webflo-routing/HttpState.js +0 -182
- package/src/runtime-pi/webflo-server/ServerSideCookies.js +0 -22
- package/src/runtime-pi/webflo-server/ServerSideSession.js +0 -40
- package/src/runtime-pi/webflo-server/messaging/Client.js +0 -27
- package/src/runtime-pi/webflo-server/messaging/Clients.js +0 -25
- package/src/runtime-pi/webflo-url/Url.js +0 -156
- package/src/runtime-pi/webflo-url/index.js +0 -1
- package/src/runtime-pi/webflo-url/urlpattern.js +0 -38
- package/src/runtime-pi/webflo-url/util.js +0 -109
- package/src/runtime-pi/webflo-url/xURL.js +0 -94
- package/src/runtime-pi/webflo-worker/WorkerSideCookies.js +0 -21
|
@@ -1,20 +1,24 @@
|
|
|
1
1
|
import { _before, _toTitle } from '@webqit/util/str/index.js';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import '../webflo-
|
|
13
|
-
import '../
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
2
|
+
import { LiveResponse, RequestPlus } from '@webqit/fetch-plus';
|
|
3
|
+
import { StarPort } from '@webqit/port-plus';
|
|
4
|
+
import { HttpThread111 } from '../webflo-routing/HttpThread111.js';
|
|
5
|
+
import { HttpCookies101 } from '../webflo-routing/HttpCookies101.js';
|
|
6
|
+
import { HttpCookies110 } from '../webflo-routing/HttpCookies110.js';
|
|
7
|
+
import { HttpSession110 } from '../webflo-routing/HttpSession110.js';
|
|
8
|
+
import { HttpUser111 } from '../webflo-routing/HttpUser111.js';
|
|
9
|
+
import { HttpEvent111 } from '../webflo-routing/HttpEvent111.js';
|
|
10
|
+
import { WebfloRouter111 } from '../webflo-routing/WebfloRouter111.js';
|
|
11
|
+
import { KeyvalsFactory110 } from '../webflo-routing/KeyvalsFactory110.js';
|
|
12
|
+
import { ClientRequestPort100 } from '../webflo-messaging/ClientRequestPort100.js';
|
|
13
|
+
import { AppRuntime } from '../AppRuntime.js';
|
|
14
|
+
import { Observer } from '@webqit/observer';
|
|
15
|
+
import { URLPlus } from '@webqit/url-plus';
|
|
16
|
+
import { _meta } from '../../util.js';
|
|
17
|
+
|
|
18
|
+
export class WebfloClient extends AppRuntime {
|
|
19
|
+
|
|
20
|
+
#keyvals;
|
|
21
|
+
get keyvals() { return this.#keyvals; }
|
|
18
22
|
|
|
19
23
|
#host;
|
|
20
24
|
get host() { return this.#host; }
|
|
@@ -37,7 +41,7 @@ export class WebfloClient extends WebfloRuntime {
|
|
|
37
41
|
super(bootstrap);
|
|
38
42
|
this.#host = host;
|
|
39
43
|
Object.defineProperty(this.host, 'webfloRuntime', { get: () => this });
|
|
40
|
-
this.#location = new
|
|
44
|
+
this.#location = new URLPlus(this.host.location);
|
|
41
45
|
this.#navigator = {
|
|
42
46
|
requesting: null,
|
|
43
47
|
redirecting: null,
|
|
@@ -46,27 +50,35 @@ export class WebfloClient extends WebfloRuntime {
|
|
|
46
50
|
error: null,
|
|
47
51
|
};
|
|
48
52
|
this.#transition = {
|
|
49
|
-
from: new
|
|
50
|
-
to: new
|
|
53
|
+
from: new URLPlus(window.origin),
|
|
54
|
+
to: new URLPlus(this.host.location),
|
|
51
55
|
rel: 'unrelated',
|
|
52
56
|
phase: 0
|
|
53
57
|
};
|
|
54
|
-
this.#background = new
|
|
58
|
+
this.#background = new StarPort({ handshake: 1, autoClose: false });
|
|
55
59
|
}
|
|
56
60
|
|
|
57
61
|
async initialize() {
|
|
62
|
+
// ----------
|
|
63
|
+
// The keyvals API
|
|
64
|
+
this.#keyvals = new KeyvalsFactory110;
|
|
65
|
+
|
|
66
|
+
// ----------
|
|
67
|
+
// Call default-init
|
|
58
68
|
const instanceController = await super.initialize();
|
|
69
|
+
|
|
70
|
+
// ----------
|
|
59
71
|
// Bind prompt handlers
|
|
60
72
|
const promptsHandler = (e) => {
|
|
61
73
|
window.queueMicrotask(() => {
|
|
62
74
|
if (e.defaultPrevented) return;
|
|
63
75
|
if (e.type === 'confirm') {
|
|
64
76
|
if (e.data?.message) {
|
|
65
|
-
e.
|
|
77
|
+
e.respondWith(confirm(e.data.message));
|
|
66
78
|
}
|
|
67
79
|
} else if (e.type === 'prompt') {
|
|
68
80
|
if (e.data?.message) {
|
|
69
|
-
e.
|
|
81
|
+
e.respondWith(prompt(e.data.message));
|
|
70
82
|
}
|
|
71
83
|
} else if (e.type === 'alert') {
|
|
72
84
|
for (const item of [].concat(e.data)) {
|
|
@@ -80,9 +92,13 @@ export class WebfloClient extends WebfloRuntime {
|
|
|
80
92
|
this.background.addEventListener('confirm', promptsHandler, { signal: instanceController.signal });
|
|
81
93
|
this.background.addEventListener('prompt', promptsHandler, { signal: instanceController.signal });
|
|
82
94
|
this.background.addEventListener('alert', promptsHandler, { signal: instanceController.signal });
|
|
95
|
+
|
|
96
|
+
// ----------
|
|
97
|
+
// Call default-init
|
|
83
98
|
await this.setupCapabilities();
|
|
84
99
|
this.control();
|
|
85
100
|
await this.hydrate();
|
|
101
|
+
|
|
86
102
|
return instanceController;
|
|
87
103
|
}
|
|
88
104
|
|
|
@@ -94,7 +110,7 @@ export class WebfloClient extends WebfloRuntime {
|
|
|
94
110
|
const instanceController = super.control();
|
|
95
111
|
const setStates = (url, detail, method = 'GET') => {
|
|
96
112
|
Observer.set(this.navigator, {
|
|
97
|
-
requesting: new
|
|
113
|
+
requesting: new URL(url),
|
|
98
114
|
origins: detail.navigationOrigins || [],
|
|
99
115
|
method,
|
|
100
116
|
error: null
|
|
@@ -221,8 +237,6 @@ export class WebfloClient extends WebfloRuntime {
|
|
|
221
237
|
|
|
222
238
|
_canIntercept(e) { return !(e.metaKey || e.altKey || e.ctrlKey || e.shiftKey); }
|
|
223
239
|
|
|
224
|
-
#xRedirectCode = 200;
|
|
225
|
-
|
|
226
240
|
isHashChange(urlObj) { return _before(this.location.href, '#') === _before(urlObj.href, '#') && (this.location.href.includes('#') || urlObj.href.includes('#')); }
|
|
227
241
|
|
|
228
242
|
isSpaRoute(urlObj) {
|
|
@@ -239,12 +253,12 @@ export class WebfloClient extends WebfloRuntime {
|
|
|
239
253
|
}
|
|
240
254
|
|
|
241
255
|
#prevEvent;
|
|
242
|
-
|
|
256
|
+
createHttpEvent111(init, singleton = true) {
|
|
243
257
|
if (singleton && this.#prevEvent) {
|
|
244
258
|
// TODO
|
|
245
259
|
//this.#prevEvent.abort();
|
|
246
260
|
}
|
|
247
|
-
const httpEvent = super.
|
|
261
|
+
const httpEvent = super.createHttpEvent111(init);
|
|
248
262
|
this.$instanceController.signal.addEventListener('abort', () => httpEvent.abort(), { once: true });
|
|
249
263
|
return this.#prevEvent = httpEvent;
|
|
250
264
|
}
|
|
@@ -253,63 +267,100 @@ export class WebfloClient extends WebfloRuntime {
|
|
|
253
267
|
const request = super.createRequest(href, init);
|
|
254
268
|
request.headers.set('Accept', 'application/json');
|
|
255
269
|
request.headers.set('X-Redirect-Policy', 'manual-when-cross-spa');
|
|
256
|
-
request.headers.set('X-Redirect-Code', this.#xRedirectCode);
|
|
257
270
|
request.headers.set('X-Powered-By', '@webqit/webflo');
|
|
258
271
|
return request;
|
|
259
272
|
}
|
|
260
273
|
|
|
261
274
|
async navigate(url, init = {}, detail = {}) {
|
|
262
|
-
//
|
|
263
|
-
const scopeObj = {
|
|
275
|
+
// Scope object
|
|
276
|
+
const scopeObj = {
|
|
277
|
+
url,
|
|
278
|
+
init,
|
|
279
|
+
detail,
|
|
280
|
+
requestID: (0 | Math.random() * 9e6).toString(36),
|
|
281
|
+
tenantID: 'anon',
|
|
282
|
+
};
|
|
264
283
|
if (typeof scopeObj.url === 'string') {
|
|
265
|
-
scopeObj.url = new URL(scopeObj.url,
|
|
284
|
+
scopeObj.url = new URL(scopeObj.url, window.location.origin);
|
|
266
285
|
}
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
scopeObj.
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
286
|
+
|
|
287
|
+
// Request
|
|
288
|
+
scopeObj.request = scopeObj.init instanceof Request && scopeObj.init.url === scopeObj.url.href
|
|
289
|
+
? scopeObj.init
|
|
290
|
+
: this.createRequest(scopeObj.url, scopeObj.init);
|
|
291
|
+
RequestPlus.upgradeInPlace(scopeObj.request);
|
|
292
|
+
|
|
293
|
+
// Origins
|
|
294
|
+
const origins = [scopeObj.requestID];
|
|
295
|
+
|
|
296
|
+
// Thread
|
|
297
|
+
scopeObj.thread = HttpThread111.create({
|
|
298
|
+
context: {},
|
|
299
|
+
store: this.#keyvals.create({ path: ['thread', scopeObj.tenantID], origins }),
|
|
300
|
+
threadID: scopeObj.url.searchParams.get('_thread'),
|
|
277
301
|
realm: 1
|
|
278
302
|
});
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
303
|
+
|
|
304
|
+
// Cookies
|
|
305
|
+
if (typeof cookieStore === 'undefined') {
|
|
306
|
+
const entries = document.cookie.split(';').map((c) => c.split('=').map((s) => s.trim()));
|
|
307
|
+
const store = this.#keyvals.create({ type: 'inmemory', path: ['cookies', scopeObj.tenantID], origins });
|
|
308
|
+
entries.forEach(([key, value]) => store.set(key, { value }));
|
|
309
|
+
const initial = Object.fromEntries(entries);
|
|
310
|
+
scopeObj.cookies = HttpCookies101.create({
|
|
311
|
+
context: { handlersRegistry: this.#keyvals.getHandlers('cookies', true) },
|
|
312
|
+
store,
|
|
313
|
+
initial,
|
|
314
|
+
realm: 1
|
|
315
|
+
});
|
|
316
|
+
} else {
|
|
317
|
+
scopeObj.cookies = HttpCookies110.create({
|
|
318
|
+
context: { handlersRegistry: this.#keyvals.getHandlers('cookies', true) },
|
|
319
|
+
store: this.#keyvals.create({ type: 'cookiestore', path: ['cookies', scopeObj.tenantID], origins }),
|
|
320
|
+
realm: 1
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// Session
|
|
325
|
+
scopeObj.session = HttpSession110.create({
|
|
326
|
+
context: { handlersRegistry: this.#keyvals.getHandlers('session', true) },
|
|
327
|
+
store: this.#keyvals.create({ type: 'indexeddb', path: ['session', scopeObj.tenantID], origins }),
|
|
283
328
|
realm: 1
|
|
284
329
|
});
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
scopeObj.user =
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
thread: scopeObj.thread,
|
|
291
|
-
client: scopeObj.clientRequestRealtime,
|
|
330
|
+
|
|
331
|
+
// User
|
|
332
|
+
scopeObj.user = HttpUser111.create({
|
|
333
|
+
context: { handlersRegistry: this.#keyvals.getHandlers('user', true) },
|
|
334
|
+
store: this.#keyvals.create({ type: 'indexeddb', path: ['user', scopeObj.tenantID], origins }),
|
|
292
335
|
realm: 1
|
|
293
336
|
});
|
|
337
|
+
|
|
338
|
+
// UIState
|
|
294
339
|
if (window.webqit?.oohtml?.configs) {
|
|
295
340
|
const { BINDINGS_API: { api: bindingsConfig } = {}, } = window.webqit.oohtml.configs;
|
|
296
341
|
scopeObj.UIState = (this.host[bindingsConfig.bindings] || {}).state;
|
|
297
342
|
}
|
|
298
|
-
|
|
343
|
+
|
|
344
|
+
// ClientPort
|
|
345
|
+
scopeObj.clientRequestPort = new ClientRequestPort100({ handshake: 1, postAwaitsOpen: true });
|
|
346
|
+
|
|
347
|
+
// HttpEvent111
|
|
348
|
+
scopeObj.httpEvent = HttpEvent111.create({
|
|
349
|
+
detail: scopeObj.detail,
|
|
350
|
+
signal: init.signal,
|
|
299
351
|
request: scopeObj.request,
|
|
300
352
|
thread: scopeObj.thread,
|
|
301
|
-
client: scopeObj.clientRequestRealtime,
|
|
302
353
|
cookies: scopeObj.cookies,
|
|
303
354
|
session: scopeObj.session,
|
|
304
355
|
user: scopeObj.user,
|
|
305
|
-
|
|
306
|
-
signal: init.signal,
|
|
356
|
+
client: scopeObj.clientRequestPort.port1,
|
|
307
357
|
state: scopeObj.UIState,
|
|
308
358
|
realm: 1
|
|
309
359
|
}, true);
|
|
360
|
+
|
|
310
361
|
// Set pre-request states
|
|
311
362
|
Observer.set(this.navigator, {
|
|
312
|
-
requesting: new
|
|
363
|
+
requesting: new URL(scopeObj.url),
|
|
313
364
|
origins: scopeObj.detail.navigationOrigins || [],
|
|
314
365
|
method: scopeObj.request.method,
|
|
315
366
|
error: null
|
|
@@ -327,8 +378,8 @@ export class WebfloClient extends WebfloRuntime {
|
|
|
327
378
|
// !IMPORTANT: Posting to the group when empty will keep the event until next addition
|
|
328
379
|
// and we don't want that
|
|
329
380
|
if (this.#background.length) {
|
|
330
|
-
const url = { ...
|
|
331
|
-
this.#background.postMessage(url, {
|
|
381
|
+
const url = { ...URLPlus.copy(scopeObj.url), method: scopeObj.request.method };
|
|
382
|
+
this.#background.postMessage(url, { type: 'navigate' });
|
|
332
383
|
}
|
|
333
384
|
|
|
334
385
|
// Dispatch for response
|
|
@@ -341,35 +392,46 @@ export class WebfloClient extends WebfloRuntime {
|
|
|
341
392
|
}
|
|
342
393
|
return await this.remoteFetch(event.request);
|
|
343
394
|
},
|
|
344
|
-
clientPortB:
|
|
395
|
+
clientPortB: scopeObj.clientRequestPort.port2,
|
|
345
396
|
originalRequestInit: scopeObj.init
|
|
346
397
|
});
|
|
347
398
|
|
|
399
|
+
// Commit cookies
|
|
400
|
+
if (typeof cookieStore === 'undefined') {
|
|
401
|
+
for (const cookieStr of await scopeObj.cookies.render()) {
|
|
402
|
+
document.cookie = cookieStr;
|
|
403
|
+
}
|
|
404
|
+
await scopeObj.cookies._commit();
|
|
405
|
+
}
|
|
406
|
+
|
|
348
407
|
// Decode response
|
|
349
408
|
scopeObj.finalUrl = scopeObj.response.url || scopeObj.request.url;
|
|
350
409
|
if (scopeObj.response.redirected || scopeObj.detail.navigationType === 'rdr' || scopeObj.detail.isHoisted) {
|
|
351
410
|
const stateData = { ...(this.currentEntry()?.getState() || {}), redirected: true, };
|
|
352
411
|
await this.updateCurrentEntry({ state: stateData }, scopeObj.finalUrl);
|
|
353
412
|
}
|
|
354
|
-
|
|
413
|
+
|
|
355
414
|
// Transition UI
|
|
356
415
|
await this.transitionUI(async () => {
|
|
357
416
|
// Set post-request states
|
|
358
417
|
Observer.set(this.location, 'href', scopeObj.finalUrl);
|
|
359
418
|
scopeObj.resetStates();
|
|
419
|
+
|
|
360
420
|
// Error?
|
|
361
|
-
const statusCode =
|
|
421
|
+
const statusCode = scopeObj.response.status;
|
|
362
422
|
if ([404, 500].includes(statusCode)) {
|
|
363
423
|
const error = new Error(scopeObj.response.statusText, { code: statusCode });
|
|
364
424
|
Object.defineProperty(error, 'retry', { value: async () => await this.navigate(scopeObj.url, scopeObj.init, scopeObj.detail) });
|
|
365
425
|
Observer.set(this.navigator, 'error', error);
|
|
366
426
|
}
|
|
427
|
+
|
|
367
428
|
// Render response
|
|
368
429
|
await this.render(
|
|
369
430
|
scopeObj.httpEvent,
|
|
370
431
|
scopeObj.response,
|
|
371
432
|
!(['GET'].includes(scopeObj.request.method) || scopeObj.response.redirected || scopeObj.detail.navigationType === 'rdr')
|
|
372
433
|
);
|
|
434
|
+
|
|
373
435
|
await this.applyPostRenderState(scopeObj.httpEvent);
|
|
374
436
|
}, scopeObj.finalUrl, scopeObj.detail);
|
|
375
437
|
}
|
|
@@ -378,36 +440,38 @@ export class WebfloClient extends WebfloRuntime {
|
|
|
378
440
|
let response = await super.dispatchNavigationEvent({ httpEvent, crossLayerFetch, clientPortB });
|
|
379
441
|
|
|
380
442
|
// Extract interactive. mode handling
|
|
381
|
-
const handleInteractiveMode =
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
443
|
+
const handleInteractiveMode = () => {
|
|
444
|
+
return new Promise(async (resolve) => {
|
|
445
|
+
// Must come as first thing
|
|
446
|
+
const backgroundPort = LiveResponse.getPort(response);
|
|
447
|
+
this.background.addPort(backgroundPort);
|
|
448
|
+
|
|
449
|
+
const liveResponse = response instanceof LiveResponse
|
|
450
|
+
? response
|
|
451
|
+
: LiveResponse.from(response);
|
|
452
|
+
|
|
453
|
+
liveResponse.addEventListener('replace', (e) => {
|
|
454
|
+
if (liveResponse.headers.get('Location')) {
|
|
455
|
+
this.processRedirect(liveResponse);
|
|
456
|
+
} else {
|
|
457
|
+
resolve(liveResponse);
|
|
458
|
+
}
|
|
459
|
+
}, { signal: httpEvent.signal });
|
|
460
|
+
});
|
|
392
461
|
};
|
|
393
462
|
|
|
394
463
|
// Await a response with an "Accepted" or redirect status
|
|
395
|
-
const statusCode =
|
|
396
|
-
if (statusCode === 202 && LiveResponse.
|
|
397
|
-
return
|
|
464
|
+
const statusCode = response.status;
|
|
465
|
+
if (statusCode === 202 && LiveResponse.hasPort(response)) {
|
|
466
|
+
return await handleInteractiveMode();
|
|
398
467
|
}
|
|
399
468
|
|
|
400
469
|
// Handle redirects
|
|
401
470
|
if (response.headers.get('Location')) {
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
if (redirectHandlingMode === 3/* keep-alive */
|
|
407
|
-
&& LiveResponse.hasBackground(response)) {
|
|
408
|
-
await handleInteractiveMode(resolve);
|
|
409
|
-
}
|
|
410
|
-
});
|
|
471
|
+
await this.processRedirect(response);
|
|
472
|
+
if (LiveResponse.hasPort(response)) {
|
|
473
|
+
return await handleInteractiveMode();
|
|
474
|
+
}
|
|
411
475
|
}
|
|
412
476
|
|
|
413
477
|
// Handle "retry" directives
|
|
@@ -430,7 +494,7 @@ export class WebfloClient extends WebfloRuntime {
|
|
|
430
494
|
}
|
|
431
495
|
|
|
432
496
|
// Obtain and connect clientPortB as first thing
|
|
433
|
-
if (LiveResponse.
|
|
497
|
+
if (LiveResponse.hasPort(response)) {
|
|
434
498
|
response = await handleInteractiveMode();
|
|
435
499
|
}
|
|
436
500
|
}
|
|
@@ -438,37 +502,33 @@ export class WebfloClient extends WebfloRuntime {
|
|
|
438
502
|
return response;
|
|
439
503
|
}
|
|
440
504
|
|
|
441
|
-
processRedirect(response) {
|
|
505
|
+
async processRedirect(response) {
|
|
442
506
|
// Normalize redirect
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
const responseMeta = _wq(response, 'meta');
|
|
447
|
-
responseMeta.set('status', xActualRedirectCode); // @NOTE 1
|
|
448
|
-
statusCode = xActualRedirectCode;
|
|
449
|
-
}
|
|
507
|
+
const statusCode = response.headers.has('X-Redirect-Code')
|
|
508
|
+
? parseInt(response.headers.get('X-Redirect-Code'))
|
|
509
|
+
: response.status;
|
|
450
510
|
|
|
451
511
|
// Trigger redirect
|
|
452
512
|
if ([302, 301].includes(statusCode)) {
|
|
453
513
|
const location = new URL(response.headers.get('Location'), this.location.origin);
|
|
454
514
|
if (this.isSpaRoute(location)) {
|
|
455
515
|
this.navigate(location, {}, { navigationType: 'rdr' });
|
|
456
|
-
return
|
|
516
|
+
return;
|
|
457
517
|
}
|
|
458
|
-
return this.redirect(location, response);
|
|
459
|
-
}
|
|
460
518
|
|
|
461
|
-
|
|
519
|
+
// External redirect
|
|
520
|
+
await this.redirect(location, response);
|
|
521
|
+
}
|
|
462
522
|
}
|
|
463
523
|
|
|
464
|
-
redirect(location) {
|
|
524
|
+
async redirect(location) {
|
|
465
525
|
window.location = location;
|
|
466
|
-
|
|
526
|
+
await new Promise(() => { });
|
|
467
527
|
}
|
|
468
528
|
|
|
469
529
|
async transitionUI(updateCallback, finalUrl, detail) {
|
|
470
530
|
// Set initial states
|
|
471
|
-
Observer.set(this.transition.from,
|
|
531
|
+
Observer.set(this.transition.from, URLPlus.copy(this.location));
|
|
472
532
|
Observer.set(this.transition.to, 'href', finalUrl);
|
|
473
533
|
const viewTransitionRel = this.transition.from.pathname === this.transition.to.pathname ? 'same' : (
|
|
474
534
|
`${this.transition.from.pathname}/`.startsWith(`${this.transition.to.pathname}/`) ? 'out' : (
|
|
@@ -496,7 +556,7 @@ export class WebfloClient extends WebfloRuntime {
|
|
|
496
556
|
}
|
|
497
557
|
|
|
498
558
|
async render(httpEvent, response, merge = false) {
|
|
499
|
-
const router = new
|
|
559
|
+
const router = new WebfloRouter111(this, this.location.pathname);
|
|
500
560
|
await router.route('render', httpEvent, async (httpEvent) => {
|
|
501
561
|
if (!window.webqit?.oohtml?.configs) return;
|
|
502
562
|
if (window.webqit?.dom) {
|
|
@@ -507,7 +567,7 @@ export class WebfloClient extends WebfloRuntime {
|
|
|
507
567
|
HTML_IMPORTS: { attr: modulesContextAttrs } = {},
|
|
508
568
|
} = window.webqit.oohtml.configs;
|
|
509
569
|
if (bindingsConfig) {
|
|
510
|
-
const $response = await LiveResponse.from(response);
|
|
570
|
+
const $response = await LiveResponse.from(response).readyStateChange('live');
|
|
511
571
|
this.host[bindingsConfig.bind]({
|
|
512
572
|
state: {},
|
|
513
573
|
data: $response.body,
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { Observer } from '@webqit/
|
|
2
|
-
import {
|
|
1
|
+
import { Observer } from '@webqit/observer';
|
|
2
|
+
import { LiveResponse } from '@webqit/fetch-plus';
|
|
3
|
+
import { HttpEvent111 } from '../webflo-routing/HttpEvent111.js';
|
|
3
4
|
import { ClientSideWorkport } from './ClientSideWorkport.js';
|
|
4
5
|
import { DeviceCapabilities } from './DeviceCapabilities.js';
|
|
5
|
-
import {
|
|
6
|
-
import { LiveResponse } from '../webflo-fetch/LiveResponse.js';
|
|
6
|
+
import { WebfloClient } from './WebfloClient.js';
|
|
7
7
|
import { WebfloHMR } from './webflo-devmode.js';
|
|
8
8
|
|
|
9
|
-
export class
|
|
9
|
+
export class WebfloRootClientA extends WebfloClient {
|
|
10
10
|
|
|
11
11
|
static get Workport() { return ClientSideWorkport; }
|
|
12
12
|
|
|
@@ -42,55 +42,38 @@ export class WebfloRootClient1 extends WebfloClient {
|
|
|
42
42
|
async initialize() {
|
|
43
43
|
// INITIALIZATIONS
|
|
44
44
|
const instanceController = await super.initialize();
|
|
45
|
+
|
|
45
46
|
// Bind network status handlers
|
|
46
47
|
const onlineHandler = () => Observer.set(this.network, 'status', window.navigator.onLine);
|
|
47
48
|
window.addEventListener('online', onlineHandler, { signal: instanceController.signal });
|
|
48
49
|
window.addEventListener('offline', onlineHandler, { signal: instanceController.signal });
|
|
50
|
+
|
|
49
51
|
// Window opener pinging
|
|
50
52
|
if (window.opener) {
|
|
51
53
|
const beforeunloadHandler = () => window.opener.postMessage('close');
|
|
52
54
|
window.addEventListener('beforeunload', beforeunloadHandler, { signal: instanceController.signal });
|
|
53
55
|
}
|
|
56
|
+
|
|
54
57
|
// Bind top-level User-Agent requests
|
|
55
|
-
this.background.
|
|
58
|
+
this.background.addRequestListener('query', async (e) => {
|
|
56
59
|
if (e.data?.query === 'push_registration') {
|
|
57
60
|
const pushManager = (await navigator.serviceWorker.getRegistration()).pushManager;
|
|
58
61
|
return await pushManager.getSubscription();
|
|
59
62
|
}
|
|
60
63
|
}, { signal: instanceController.signal });
|
|
61
|
-
|
|
62
|
-
this.background.handleRequests('storage:query', (e) => {
|
|
63
|
-
const { source, namespace, query, key, value } = e.data;
|
|
64
|
-
const storage = source === 'session' ? sessionStorage : (source === 'local' ? localStorage : null);
|
|
65
|
-
if (!storage) return;
|
|
66
|
-
const data = JSON.parse(storage.getItem(namespace) || (query === 'set' ? '{}' : 'null'));
|
|
67
|
-
switch (query) {
|
|
68
|
-
case 'has': return !!data && Reflect.has(data, key);
|
|
69
|
-
case 'get': return data && Reflect.get(data, key);
|
|
70
|
-
case 'set':
|
|
71
|
-
Reflect.set(data, key, value);
|
|
72
|
-
return storage.setItem(namespace, JSON.stringify(data))
|
|
73
|
-
case 'delete':
|
|
74
|
-
if (!data) return;
|
|
75
|
-
Reflect.deleteProperty(data, key);
|
|
76
|
-
return storage.setItem(namespace, JSON.stringify(data));
|
|
77
|
-
case 'clear': return storage.removeItem(namespace);
|
|
78
|
-
case 'keys': return data && Reflect.ownKeys(data) || [];
|
|
79
|
-
case 'values': return data && Object.values(data) || [];
|
|
80
|
-
case 'entries': return data && Object.entries(data) || [];
|
|
81
|
-
case 'size': return data && Object.keys(data).length || 0;
|
|
82
|
-
}
|
|
83
|
-
}, { signal: instanceController.signal });
|
|
64
|
+
|
|
84
65
|
return instanceController;
|
|
85
66
|
}
|
|
86
67
|
|
|
87
68
|
async setupCapabilities() {
|
|
88
69
|
const instanceController = await super.setupCapabilities();
|
|
89
|
-
// Service Worker && Capabilities
|
|
90
70
|
const cleanups = [];
|
|
71
|
+
|
|
72
|
+
// Service Worker && Capabilities
|
|
91
73
|
instanceController.signal.addEventListener('abort', () => cleanups.forEach((c) => c()), { once: true });
|
|
92
74
|
this.#capabilities = await this.constructor.DeviceCapabilities.initialize(this, this.config.CLIENT.capabilities);
|
|
93
75
|
cleanups.push(() => this.#capabilities.close());
|
|
76
|
+
|
|
94
77
|
if (this.config.CLIENT.capabilities?.service_worker) {
|
|
95
78
|
const { filename, ...restServiceWorkerParams } = this.config.WORKER;
|
|
96
79
|
this.constructor.Workport.initialize(null, filename, restServiceWorkerParams).then((workport) => {
|
|
@@ -98,33 +81,40 @@ export class WebfloRootClient1 extends WebfloClient {
|
|
|
98
81
|
cleanups.push(() => this.#workport.close());
|
|
99
82
|
});
|
|
100
83
|
}
|
|
84
|
+
|
|
101
85
|
return instanceController;
|
|
102
86
|
}
|
|
103
87
|
|
|
104
88
|
async hydrate() {
|
|
105
89
|
const instanceController = await super.hydrate();
|
|
106
90
|
const scopeObj = {};
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
91
|
+
|
|
92
|
+
try {
|
|
93
|
+
scopeObj.data = JSON.parse(this.host.querySelector(`script[rel="hydration"][type="application/json"]`)?.textContent?.trim() || 'null');
|
|
94
|
+
} catch (e) { }
|
|
95
|
+
scopeObj.response = new LiveResponse(scopeObj.data, { headers: { 'Content-Type': 'application/json' } });
|
|
96
|
+
|
|
97
|
+
for (const name of ['X-Message-Port', 'X-Webflo-Dev-Mode']) {
|
|
110
98
|
const metaElement = this.host.querySelector(`meta[name="${name}"]`);
|
|
111
99
|
if (!metaElement) continue;
|
|
112
100
|
scopeObj.response.headers.set(name, metaElement.content?.trim() || '');
|
|
113
101
|
}
|
|
114
|
-
|
|
115
|
-
if (
|
|
116
|
-
this.background.addPort(
|
|
102
|
+
|
|
103
|
+
if (scopeObj.response.port) {
|
|
104
|
+
this.background.addPort(scopeObj.response.port);
|
|
117
105
|
}
|
|
118
|
-
if (scopeObj.response.body || backgroundPort) {
|
|
119
106
|
|
|
120
|
-
|
|
107
|
+
if (scopeObj.response.body || scopeObj.response.port) {
|
|
108
|
+
const httpEvent = HttpEvent111.create({ request: this.createRequest(this.location.href) }, true);
|
|
121
109
|
await this.render(httpEvent, scopeObj.response);
|
|
122
110
|
} else {
|
|
123
111
|
await this.navigate(this.location.href);
|
|
124
112
|
}
|
|
113
|
+
|
|
125
114
|
if (scopeObj.response.headers.get('X-Webflo-Dev-Mode') === 'true') {
|
|
126
115
|
this.enterDevMode();
|
|
127
116
|
}
|
|
117
|
+
|
|
128
118
|
return instanceController;
|
|
129
119
|
}
|
|
130
120
|
|
|
@@ -142,15 +132,18 @@ export class WebfloRootClient1 extends WebfloClient {
|
|
|
142
132
|
const state = { ...(this.currentEntry()?.getState?.() || {}), scrollPosition };
|
|
143
133
|
window.history.replaceState(state, '', this.location.href);
|
|
144
134
|
} catch (e) { }
|
|
135
|
+
|
|
145
136
|
try { window.history.pushState({}, '', newHref); } catch (e) { }
|
|
146
137
|
};
|
|
147
138
|
const instanceController = super.controlClassic/*IMPORTANT*/(locationCallback);
|
|
139
|
+
|
|
148
140
|
// ONPOPSTATE
|
|
149
141
|
const popstateHandler = (e) => {
|
|
150
142
|
if (this.isHashChange(location)) {
|
|
151
143
|
Observer.set(this.location, 'href', location.href);
|
|
152
144
|
return;
|
|
153
145
|
}
|
|
146
|
+
|
|
154
147
|
// Navigation details
|
|
155
148
|
const detail = {
|
|
156
149
|
navigationType: 'traverse',
|
|
@@ -159,29 +152,23 @@ export class WebfloRootClient1 extends WebfloClient {
|
|
|
159
152
|
source: this.currentEntry(),
|
|
160
153
|
userInitiated: true,
|
|
161
154
|
};
|
|
155
|
+
|
|
162
156
|
// Traversal?
|
|
163
157
|
// Push
|
|
164
158
|
this.navigate(location.href, {}, detail);
|
|
165
159
|
};
|
|
166
160
|
window.addEventListener('popstate', popstateHandler, { signal: instanceController.signal });
|
|
161
|
+
|
|
167
162
|
return instanceController;
|
|
168
163
|
}
|
|
169
164
|
|
|
170
|
-
reload() {
|
|
171
|
-
return window.history.reload();
|
|
172
|
-
}
|
|
165
|
+
reload() { return window.history.reload(); }
|
|
173
166
|
|
|
174
|
-
back() {
|
|
175
|
-
return window.history.back();
|
|
176
|
-
}
|
|
167
|
+
back() { return window.history.back(); }
|
|
177
168
|
|
|
178
|
-
forward() {
|
|
179
|
-
return window.history.forward();
|
|
180
|
-
}
|
|
169
|
+
forward() { return window.history.forward(); }
|
|
181
170
|
|
|
182
|
-
traverseTo(...args) {
|
|
183
|
-
return window.history.go(...args);
|
|
184
|
-
}
|
|
171
|
+
traverseTo(...args) { return window.history.go(...args); }
|
|
185
172
|
|
|
186
173
|
async push(url, state = {}) {
|
|
187
174
|
if (typeof url === 'string' && url.startsWith('&')) { url = this.location.href.split('#')[0] + (this.location.href.includes('?') ? url : url.replace('&', '?')); }
|
|
@@ -190,13 +177,9 @@ export class WebfloRootClient1 extends WebfloClient {
|
|
|
190
177
|
Observer.set(this.location, 'href', url.href);
|
|
191
178
|
}
|
|
192
179
|
|
|
193
|
-
entries() {
|
|
194
|
-
return window.history;
|
|
195
|
-
}
|
|
180
|
+
entries() { return window.history; }
|
|
196
181
|
|
|
197
|
-
currentEntry() {
|
|
198
|
-
return this._asEntry(history.state);
|
|
199
|
-
}
|
|
182
|
+
currentEntry() { return this._asEntry(history.state); }
|
|
200
183
|
|
|
201
184
|
async updateCurrentEntry(params, url = null) {
|
|
202
185
|
window.history.replaceState(params.state, '', url);
|