@webqit/webflo 0.20.26 → 0.20.27
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 -95
- 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 -266
- 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
|
|
@@ -239,12 +255,12 @@ export class WebfloClient extends WebfloRuntime {
|
|
|
239
255
|
}
|
|
240
256
|
|
|
241
257
|
#prevEvent;
|
|
242
|
-
|
|
258
|
+
createHttpEvent111(init, singleton = true) {
|
|
243
259
|
if (singleton && this.#prevEvent) {
|
|
244
260
|
// TODO
|
|
245
261
|
//this.#prevEvent.abort();
|
|
246
262
|
}
|
|
247
|
-
const httpEvent = super.
|
|
263
|
+
const httpEvent = super.createHttpEvent111(init);
|
|
248
264
|
this.$instanceController.signal.addEventListener('abort', () => httpEvent.abort(), { once: true });
|
|
249
265
|
return this.#prevEvent = httpEvent;
|
|
250
266
|
}
|
|
@@ -259,57 +275,95 @@ export class WebfloClient extends WebfloRuntime {
|
|
|
259
275
|
}
|
|
260
276
|
|
|
261
277
|
async navigate(url, init = {}, detail = {}) {
|
|
262
|
-
//
|
|
263
|
-
const scopeObj = {
|
|
278
|
+
// Scope object
|
|
279
|
+
const scopeObj = {
|
|
280
|
+
url,
|
|
281
|
+
init,
|
|
282
|
+
detail,
|
|
283
|
+
requestID: (0 | Math.random() * 9e6).toString(36),
|
|
284
|
+
tenantID: 'anon',
|
|
285
|
+
};
|
|
264
286
|
if (typeof scopeObj.url === 'string') {
|
|
265
|
-
scopeObj.url = new URL(scopeObj.url,
|
|
287
|
+
scopeObj.url = new URL(scopeObj.url, window.location.origin);
|
|
266
288
|
}
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
scopeObj.
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
289
|
+
|
|
290
|
+
// Request
|
|
291
|
+
scopeObj.request = scopeObj.init instanceof Request && scopeObj.init.url === scopeObj.url.href
|
|
292
|
+
? scopeObj.init
|
|
293
|
+
: this.createRequest(scopeObj.url, scopeObj.init);
|
|
294
|
+
RequestPlus.upgradeInPlace(scopeObj.request);
|
|
295
|
+
|
|
296
|
+
// Origins
|
|
297
|
+
const origins = [scopeObj.requestID];
|
|
298
|
+
|
|
299
|
+
// Thread
|
|
300
|
+
scopeObj.thread = HttpThread111.create({
|
|
301
|
+
context: {},
|
|
302
|
+
store: this.#keyvals.create({ path: ['thread', scopeObj.tenantID], origins }),
|
|
303
|
+
threadID: scopeObj.url.searchParams.get('_thread'),
|
|
277
304
|
realm: 1
|
|
278
305
|
});
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
306
|
+
|
|
307
|
+
// Cookies
|
|
308
|
+
if (typeof cookieStore === 'undefined') {
|
|
309
|
+
const entries = document.cookie.split(';').map((c) => c.split('=').map((s) => s.trim()));
|
|
310
|
+
const store = this.#keyvals.create({ type: 'inmemory', path: ['cookies', scopeObj.tenantID], origins });
|
|
311
|
+
entries.forEach(([key, value]) => store.set(key, { value }));
|
|
312
|
+
const initial = Object.fromEntries(entries);
|
|
313
|
+
scopeObj.cookies = HttpCookies101.create({
|
|
314
|
+
context: { handlersRegistry: this.#keyvals.getHandlers('cookies', true) },
|
|
315
|
+
store,
|
|
316
|
+
initial,
|
|
317
|
+
realm: 1
|
|
318
|
+
});
|
|
319
|
+
} else {
|
|
320
|
+
scopeObj.cookies = HttpCookies110.create({
|
|
321
|
+
context: { handlersRegistry: this.#keyvals.getHandlers('cookies', true) },
|
|
322
|
+
store: this.#keyvals.create({ type: 'cookiestore', path: ['cookies', scopeObj.tenantID], origins }),
|
|
323
|
+
realm: 1
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// Session
|
|
328
|
+
scopeObj.session = HttpSession110.create({
|
|
329
|
+
context: { handlersRegistry: this.#keyvals.getHandlers('session', true) },
|
|
330
|
+
store: this.#keyvals.create({ type: 'indexeddb', path: ['session', scopeObj.tenantID], origins }),
|
|
283
331
|
realm: 1
|
|
284
332
|
});
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
scopeObj.user =
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
thread: scopeObj.thread,
|
|
291
|
-
client: scopeObj.clientRequestRealtime,
|
|
333
|
+
|
|
334
|
+
// User
|
|
335
|
+
scopeObj.user = HttpUser111.create({
|
|
336
|
+
context: { handlersRegistry: this.#keyvals.getHandlers('user', true) },
|
|
337
|
+
store: this.#keyvals.create({ type: 'indexeddb', path: ['user', scopeObj.tenantID], origins }),
|
|
292
338
|
realm: 1
|
|
293
339
|
});
|
|
340
|
+
|
|
341
|
+
// UIState
|
|
294
342
|
if (window.webqit?.oohtml?.configs) {
|
|
295
343
|
const { BINDINGS_API: { api: bindingsConfig } = {}, } = window.webqit.oohtml.configs;
|
|
296
344
|
scopeObj.UIState = (this.host[bindingsConfig.bindings] || {}).state;
|
|
297
345
|
}
|
|
298
|
-
|
|
346
|
+
|
|
347
|
+
// ClientPort
|
|
348
|
+
scopeObj.clientRequestPort = new ClientRequestPort100({ handshake: 1, postAwaitsOpen: true });
|
|
349
|
+
|
|
350
|
+
// HttpEvent111
|
|
351
|
+
scopeObj.httpEvent = HttpEvent111.create({
|
|
352
|
+
detail: scopeObj.detail,
|
|
353
|
+
signal: init.signal,
|
|
299
354
|
request: scopeObj.request,
|
|
300
355
|
thread: scopeObj.thread,
|
|
301
|
-
client: scopeObj.clientRequestRealtime,
|
|
302
356
|
cookies: scopeObj.cookies,
|
|
303
357
|
session: scopeObj.session,
|
|
304
358
|
user: scopeObj.user,
|
|
305
|
-
|
|
306
|
-
signal: init.signal,
|
|
359
|
+
client: scopeObj.clientRequestPort.port1,
|
|
307
360
|
state: scopeObj.UIState,
|
|
308
361
|
realm: 1
|
|
309
362
|
}, true);
|
|
363
|
+
|
|
310
364
|
// Set pre-request states
|
|
311
365
|
Observer.set(this.navigator, {
|
|
312
|
-
requesting: new
|
|
366
|
+
requesting: new URL(scopeObj.url),
|
|
313
367
|
origins: scopeObj.detail.navigationOrigins || [],
|
|
314
368
|
method: scopeObj.request.method,
|
|
315
369
|
error: null
|
|
@@ -327,8 +381,8 @@ export class WebfloClient extends WebfloRuntime {
|
|
|
327
381
|
// !IMPORTANT: Posting to the group when empty will keep the event until next addition
|
|
328
382
|
// and we don't want that
|
|
329
383
|
if (this.#background.length) {
|
|
330
|
-
const url = { ...
|
|
331
|
-
this.#background.postMessage(url, {
|
|
384
|
+
const url = { ...URLPlus.copy(scopeObj.url), method: scopeObj.request.method };
|
|
385
|
+
this.#background.postMessage(url, { type: 'navigate' });
|
|
332
386
|
}
|
|
333
387
|
|
|
334
388
|
// Dispatch for response
|
|
@@ -341,35 +395,46 @@ export class WebfloClient extends WebfloRuntime {
|
|
|
341
395
|
}
|
|
342
396
|
return await this.remoteFetch(event.request);
|
|
343
397
|
},
|
|
344
|
-
clientPortB:
|
|
398
|
+
clientPortB: scopeObj.clientRequestPort.port2,
|
|
345
399
|
originalRequestInit: scopeObj.init
|
|
346
400
|
});
|
|
347
401
|
|
|
402
|
+
// Commit cookies
|
|
403
|
+
if (typeof cookieStore === 'undefined') {
|
|
404
|
+
for (const cookieStr of await scopeObj.cookies.render()) {
|
|
405
|
+
document.cookie = cookieStr;
|
|
406
|
+
}
|
|
407
|
+
await scopeObj.cookies._commit();
|
|
408
|
+
}
|
|
409
|
+
|
|
348
410
|
// Decode response
|
|
349
411
|
scopeObj.finalUrl = scopeObj.response.url || scopeObj.request.url;
|
|
350
412
|
if (scopeObj.response.redirected || scopeObj.detail.navigationType === 'rdr' || scopeObj.detail.isHoisted) {
|
|
351
413
|
const stateData = { ...(this.currentEntry()?.getState() || {}), redirected: true, };
|
|
352
414
|
await this.updateCurrentEntry({ state: stateData }, scopeObj.finalUrl);
|
|
353
415
|
}
|
|
354
|
-
|
|
416
|
+
|
|
355
417
|
// Transition UI
|
|
356
418
|
await this.transitionUI(async () => {
|
|
357
419
|
// Set post-request states
|
|
358
420
|
Observer.set(this.location, 'href', scopeObj.finalUrl);
|
|
359
421
|
scopeObj.resetStates();
|
|
422
|
+
|
|
360
423
|
// Error?
|
|
361
|
-
const statusCode =
|
|
424
|
+
const statusCode = scopeObj.response.status;
|
|
362
425
|
if ([404, 500].includes(statusCode)) {
|
|
363
426
|
const error = new Error(scopeObj.response.statusText, { code: statusCode });
|
|
364
427
|
Object.defineProperty(error, 'retry', { value: async () => await this.navigate(scopeObj.url, scopeObj.init, scopeObj.detail) });
|
|
365
428
|
Observer.set(this.navigator, 'error', error);
|
|
366
429
|
}
|
|
430
|
+
|
|
367
431
|
// Render response
|
|
368
432
|
await this.render(
|
|
369
433
|
scopeObj.httpEvent,
|
|
370
434
|
scopeObj.response,
|
|
371
435
|
!(['GET'].includes(scopeObj.request.method) || scopeObj.response.redirected || scopeObj.detail.navigationType === 'rdr')
|
|
372
436
|
);
|
|
437
|
+
|
|
373
438
|
await this.applyPostRenderState(scopeObj.httpEvent);
|
|
374
439
|
}, scopeObj.finalUrl, scopeObj.detail);
|
|
375
440
|
}
|
|
@@ -378,36 +443,38 @@ export class WebfloClient extends WebfloRuntime {
|
|
|
378
443
|
let response = await super.dispatchNavigationEvent({ httpEvent, crossLayerFetch, clientPortB });
|
|
379
444
|
|
|
380
445
|
// Extract interactive. mode handling
|
|
381
|
-
const handleInteractiveMode =
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
446
|
+
const handleInteractiveMode = () => {
|
|
447
|
+
return new Promise(async (resolve) => {
|
|
448
|
+
// Must come as first thing
|
|
449
|
+
const backgroundPort = LiveResponse.getPort(response);
|
|
450
|
+
this.background.addPort(backgroundPort);
|
|
451
|
+
|
|
452
|
+
const liveResponse = response instanceof LiveResponse
|
|
453
|
+
? response
|
|
454
|
+
: LiveResponse.from(response);
|
|
455
|
+
|
|
456
|
+
liveResponse.addEventListener('replace', (e) => {
|
|
457
|
+
if (liveResponse.headers.get('Location')) {
|
|
458
|
+
this.processRedirect(liveResponse);
|
|
459
|
+
} else {
|
|
460
|
+
resolve(liveResponse);
|
|
461
|
+
}
|
|
462
|
+
}, { signal: httpEvent.signal });
|
|
463
|
+
});
|
|
392
464
|
};
|
|
393
465
|
|
|
394
466
|
// Await a response with an "Accepted" or redirect status
|
|
395
|
-
const statusCode =
|
|
396
|
-
if (statusCode === 202 && LiveResponse.
|
|
397
|
-
return
|
|
467
|
+
const statusCode = response.status;
|
|
468
|
+
if (statusCode === 202 && LiveResponse.hasPort(response)) {
|
|
469
|
+
return await handleInteractiveMode();
|
|
398
470
|
}
|
|
399
471
|
|
|
400
472
|
// Handle redirects
|
|
401
473
|
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
|
-
});
|
|
474
|
+
await this.processRedirect(response);
|
|
475
|
+
if (LiveResponse.hasPort(response)) {
|
|
476
|
+
return await handleInteractiveMode();
|
|
477
|
+
}
|
|
411
478
|
}
|
|
412
479
|
|
|
413
480
|
// Handle "retry" directives
|
|
@@ -430,7 +497,7 @@ export class WebfloClient extends WebfloRuntime {
|
|
|
430
497
|
}
|
|
431
498
|
|
|
432
499
|
// Obtain and connect clientPortB as first thing
|
|
433
|
-
if (LiveResponse.
|
|
500
|
+
if (LiveResponse.hasPort(response)) {
|
|
434
501
|
response = await handleInteractiveMode();
|
|
435
502
|
}
|
|
436
503
|
}
|
|
@@ -438,12 +505,13 @@ export class WebfloClient extends WebfloRuntime {
|
|
|
438
505
|
return response;
|
|
439
506
|
}
|
|
440
507
|
|
|
441
|
-
processRedirect(response) {
|
|
508
|
+
async processRedirect(response) {
|
|
442
509
|
// Normalize redirect
|
|
443
|
-
let statusCode =
|
|
510
|
+
let statusCode = response.status;
|
|
444
511
|
const xActualRedirectCode = parseInt(response.headers.get('X-Redirect-Code'));
|
|
512
|
+
|
|
445
513
|
if (xActualRedirectCode && statusCode === this.#xRedirectCode) {
|
|
446
|
-
const responseMeta =
|
|
514
|
+
const responseMeta = _meta(response);
|
|
447
515
|
responseMeta.set('status', xActualRedirectCode); // @NOTE 1
|
|
448
516
|
statusCode = xActualRedirectCode;
|
|
449
517
|
}
|
|
@@ -453,22 +521,22 @@ export class WebfloClient extends WebfloRuntime {
|
|
|
453
521
|
const location = new URL(response.headers.get('Location'), this.location.origin);
|
|
454
522
|
if (this.isSpaRoute(location)) {
|
|
455
523
|
this.navigate(location, {}, { navigationType: 'rdr' });
|
|
456
|
-
return
|
|
524
|
+
return;
|
|
457
525
|
}
|
|
458
|
-
return this.redirect(location, response);
|
|
459
|
-
}
|
|
460
526
|
|
|
461
|
-
|
|
527
|
+
// External redirect
|
|
528
|
+
await this.redirect(location, response);
|
|
529
|
+
}
|
|
462
530
|
}
|
|
463
531
|
|
|
464
|
-
redirect(location) {
|
|
532
|
+
async redirect(location) {
|
|
465
533
|
window.location = location;
|
|
466
|
-
|
|
534
|
+
await new Promise(() => { });
|
|
467
535
|
}
|
|
468
536
|
|
|
469
537
|
async transitionUI(updateCallback, finalUrl, detail) {
|
|
470
538
|
// Set initial states
|
|
471
|
-
Observer.set(this.transition.from,
|
|
539
|
+
Observer.set(this.transition.from, URLPlus.copy(this.location));
|
|
472
540
|
Observer.set(this.transition.to, 'href', finalUrl);
|
|
473
541
|
const viewTransitionRel = this.transition.from.pathname === this.transition.to.pathname ? 'same' : (
|
|
474
542
|
`${this.transition.from.pathname}/`.startsWith(`${this.transition.to.pathname}/`) ? 'out' : (
|
|
@@ -496,7 +564,7 @@ export class WebfloClient extends WebfloRuntime {
|
|
|
496
564
|
}
|
|
497
565
|
|
|
498
566
|
async render(httpEvent, response, merge = false) {
|
|
499
|
-
const router = new
|
|
567
|
+
const router = new WebfloRouter111(this, this.location.pathname);
|
|
500
568
|
await router.route('render', httpEvent, async (httpEvent) => {
|
|
501
569
|
if (!window.webqit?.oohtml?.configs) return;
|
|
502
570
|
if (window.webqit?.dom) {
|
|
@@ -507,7 +575,7 @@ export class WebfloClient extends WebfloRuntime {
|
|
|
507
575
|
HTML_IMPORTS: { attr: modulesContextAttrs } = {},
|
|
508
576
|
} = window.webqit.oohtml.configs;
|
|
509
577
|
if (bindingsConfig) {
|
|
510
|
-
const $response = await LiveResponse.from(response);
|
|
578
|
+
const $response = await LiveResponse.from(response).readyStateChange('live');
|
|
511
579
|
this.host[bindingsConfig.bind]({
|
|
512
580
|
state: {},
|
|
513
581
|
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);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { Observer } from '@webqit/
|
|
2
|
-
import {
|
|
1
|
+
import { Observer } from '@webqit/observer';
|
|
2
|
+
import { WebfloRootClientA } from './WebfloRootClientA.js';
|
|
3
3
|
|
|
4
|
-
export class
|
|
4
|
+
export class WebfloRootClientB extends WebfloRootClientA {
|
|
5
5
|
|
|
6
6
|
control() {
|
|
7
7
|
const instanceController = super.controlSuper/*IMPORTANT*/();
|