@webqit/webflo 0.20.25 → 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.
Files changed (57) hide show
  1. package/package.json +8 -5
  2. package/src/build-pi/index.js +7 -5
  3. package/src/init-pi/index.js +0 -1
  4. package/src/runtime-pi/{WebfloRuntime.js → AppRuntime.js} +57 -113
  5. package/src/runtime-pi/webflo-client/DeviceCapabilities.js +1 -1
  6. package/src/runtime-pi/webflo-client/WebfloClient.js +163 -95
  7. package/src/runtime-pi/webflo-client/{WebfloRootClient1.js → WebfloRootClientA.js} +39 -56
  8. package/src/runtime-pi/webflo-client/{WebfloRootClient2.js → WebfloRootClientB.js} +3 -3
  9. package/src/runtime-pi/webflo-client/WebfloSubClient.js +28 -15
  10. package/src/runtime-pi/webflo-client/index.js +3 -3
  11. package/src/runtime-pi/webflo-messaging/ClientPortMixin.js +13 -0
  12. package/src/runtime-pi/{webflo-server/messaging/ClientRequestRealtime.js → webflo-messaging/ClientRequestPort001.js} +13 -9
  13. package/src/runtime-pi/webflo-messaging/ClientRequestPort010.js +4 -0
  14. package/src/runtime-pi/webflo-messaging/ClientRequestPort100.js +17 -0
  15. package/src/runtime-pi/webflo-messaging/WebfloTenancy001.js +27 -0
  16. package/src/runtime-pi/webflo-messaging/WebfloTenant001.js +27 -0
  17. package/src/runtime-pi/webflo-routing/HttpCookies101.js +53 -0
  18. package/src/runtime-pi/webflo-routing/HttpCookies110.js +3 -0
  19. package/src/runtime-pi/webflo-routing/{HttpEvent.js → HttpEvent111.js} +95 -73
  20. package/src/runtime-pi/webflo-routing/HttpKeyvalInterface.js +120 -0
  21. package/src/runtime-pi/webflo-routing/HttpSession001.js +24 -0
  22. package/src/runtime-pi/webflo-routing/HttpSession110.js +3 -0
  23. package/src/runtime-pi/webflo-routing/{HttpThread.js → HttpThread111.js} +54 -13
  24. package/src/runtime-pi/webflo-routing/{HttpUser.js → HttpUser111.js} +10 -23
  25. package/src/runtime-pi/webflo-routing/KeyvalsFactory001.js +53 -0
  26. package/src/runtime-pi/webflo-routing/KeyvalsFactory110.js +48 -0
  27. package/src/runtime-pi/webflo-routing/KeyvalsFactoryInterface.js +56 -0
  28. package/src/runtime-pi/webflo-routing/{WebfloRouter.js → WebfloRouter111.js} +5 -6
  29. package/src/runtime-pi/webflo-server/WebfloServer.js +262 -266
  30. package/src/runtime-pi/webflo-worker/WebfloWorker.js +97 -44
  31. package/src/util.js +3 -2
  32. package/src/runtime-pi/apis.js +0 -9
  33. package/src/runtime-pi/webflo-client/ClientSideCookies.js +0 -18
  34. package/src/runtime-pi/webflo-fetch/LiveResponse.js +0 -476
  35. package/src/runtime-pi/webflo-fetch/index.js +0 -419
  36. package/src/runtime-pi/webflo-fetch/util.js +0 -28
  37. package/src/runtime-pi/webflo-messaging/WQBroadcastChannel.js +0 -10
  38. package/src/runtime-pi/webflo-messaging/WQMessageChannel.js +0 -26
  39. package/src/runtime-pi/webflo-messaging/WQMessageEvent.js +0 -87
  40. package/src/runtime-pi/webflo-messaging/WQMessagePort.js +0 -38
  41. package/src/runtime-pi/webflo-messaging/WQRelayPort.js +0 -47
  42. package/src/runtime-pi/webflo-messaging/WQSockPort.js +0 -111
  43. package/src/runtime-pi/webflo-messaging/WQStarPort.js +0 -112
  44. package/src/runtime-pi/webflo-messaging/wq-message-port.js +0 -413
  45. package/src/runtime-pi/webflo-routing/HttpCookies.js +0 -43
  46. package/src/runtime-pi/webflo-routing/HttpSession.js +0 -11
  47. package/src/runtime-pi/webflo-routing/HttpState.js +0 -182
  48. package/src/runtime-pi/webflo-server/ServerSideCookies.js +0 -22
  49. package/src/runtime-pi/webflo-server/ServerSideSession.js +0 -40
  50. package/src/runtime-pi/webflo-server/messaging/Client.js +0 -27
  51. package/src/runtime-pi/webflo-server/messaging/Clients.js +0 -25
  52. package/src/runtime-pi/webflo-url/Url.js +0 -156
  53. package/src/runtime-pi/webflo-url/index.js +0 -1
  54. package/src/runtime-pi/webflo-url/urlpattern.js +0 -38
  55. package/src/runtime-pi/webflo-url/util.js +0 -109
  56. package/src/runtime-pi/webflo-url/xURL.js +0 -94
  57. 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 { _isObject } from '@webqit/util/js/index.js';
3
- import { Observer } from '@webqit/use-live';
4
- import { WebfloRuntime } from '../WebfloRuntime.js';
5
- import { WQMessageChannel } from '../webflo-messaging/WQMessageChannel.js';
6
- import { response as responseShim } from '../webflo-fetch/index.js';
7
- import { LiveResponse } from '../webflo-fetch/LiveResponse.js';
8
- import { WQStarPort } from '../webflo-messaging/WQStarPort.js';
9
- import { ClientSideCookies } from './ClientSideCookies.js';
10
- import { Url } from '../webflo-url/Url.js';
11
- import { _wq } from '../../util.js';
12
- import '../webflo-fetch/index.js';
13
- import '../webflo-url/index.js';
14
-
15
- export class WebfloClient extends WebfloRuntime {
16
-
17
- static get HttpCookies() { return ClientSideCookies; }
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 Url/*NOT URL*/(this.host.location);
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 Url/*NOT URL*/({}),
50
- to: new Url/*NOT URL*/(this.host.location),
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 WQStarPort;
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.wqRespondWith(confirm(e.data.message));
77
+ e.respondWith(confirm(e.data.message));
66
78
  }
67
79
  } else if (e.type === 'prompt') {
68
80
  if (e.data?.message) {
69
- e.wqRespondWith(prompt(e.data.message));
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 Url/*NOT URL*/(url),
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
- createHttpEvent(init, singleton = true) {
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.createHttpEvent(init);
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
- // Resolve inputs
263
- const scopeObj = { url, init, detail };
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, self.location.origin);
287
+ scopeObj.url = new URL(scopeObj.url, window.location.origin);
266
288
  }
267
- // Create and route request
268
- scopeObj.request = this.createRequest(scopeObj.url, scopeObj.init);
269
- scopeObj.thread = this.createHttpThread({
270
- store: this.createStorage('thread'),
271
- threadId: scopeObj.url.searchParams.get('_thread'),
272
- realm: 1
273
- });
274
- scopeObj.cookies = this.createHttpCookies({
275
- request: scopeObj.request,
276
- thread: scopeObj.thread,
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
- scopeObj.session = this.createHttpSession({
280
- store: this.createStorage('session'),
281
- request: scopeObj.request,
282
- thread: scopeObj.thread,
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
- const wqMessageChannel = new WQMessageChannel;
286
- scopeObj.clientRequestRealtime = wqMessageChannel.port1;
287
- scopeObj.user = this.createHttpUser({
288
- store: this.createStorage('user'),
289
- request: scopeObj.request,
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
- scopeObj.httpEvent = this.createHttpEvent({
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
- detail: scopeObj.detail,
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 Url/*NOT URL*/(scopeObj.url),
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 = { ...Url.copy(scopeObj.url), method: scopeObj.request.method };
331
- this.#background.postMessage(url, { wqEventOptions: { type: 'navigate' } });
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: wqMessageChannel.port2,
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 = responseShim.prototype.status.get.call(scopeObj.response);
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 = async (resolve) => {
382
- const liveResponse = await LiveResponse.from(response);
383
- this.background.addPort(liveResponse.background);
384
- liveResponse.addEventListener('replace', () => {
385
- if (liveResponse.headers.get('Location')) {
386
- this.processRedirect(liveResponse);
387
- } else {
388
- resolve?.(liveResponse);
389
- }
390
- }, { signal: httpEvent.signal });
391
- return liveResponse;
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 = responseShim.prototype.status.get.call(response);
396
- if (statusCode === 202 && LiveResponse.hasBackground(response)) {
397
- return new Promise(handleInteractiveMode);
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
- // Never resolves...
403
- return new Promise(async (resolve) => {
404
- const redirectHandlingMode = this.processRedirect(response);
405
- // ...except processRedirect() says keep-alive
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.hasBackground(response)) {
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 = responseShim.prototype.status.get.call(response);
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 = _wq(response, 'meta');
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 1;
524
+ return;
457
525
  }
458
- return this.redirect(location, response);
459
- }
460
526
 
461
- return 0; // No actual redirect
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
- return 2; // Window reload
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, Url.copy(this.location));
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 this.constructor.Router(this, this.location.pathname);
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/use-live';
2
- import { WebfloClient } from './WebfloClient.js';
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 { response as responseShim } from '../webflo-fetch/index.js';
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 WebfloRootClient1 extends WebfloClient {
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.handleRequests('ua:query', async (e) => {
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
- // Bind top-level Storage requests
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
- scopeObj.data = this.host.querySelector(`script[rel="hydration"][type="application/json"]`)?.textContent?.trim() || null;
108
- scopeObj.response = responseShim.from.value(scopeObj.data, { headers: { 'Content-Type': 'application/json' } });
109
- for (const name of ['X-Background-Messaging-Port', 'X-Live-Response-Message-ID', 'X-Webflo-Dev-Mode']) {
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
- const backgroundPort = LiveResponse.getBackground(scopeObj.response);
115
- if (backgroundPort) {
116
- this.background.addPort(backgroundPort);
102
+
103
+ if (scopeObj.response.port) {
104
+ this.background.addPort(scopeObj.response.port);
117
105
  }
118
- if (scopeObj.response.body || backgroundPort) {
119
106
 
120
- const httpEvent = this.createHttpEvent({ request: this.createRequest(this.location.href) }, true);
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/use-live';
2
- import { WebfloRootClient1 } from './WebfloRootClient1.js';
1
+ import { Observer } from '@webqit/observer';
2
+ import { WebfloRootClientA } from './WebfloRootClientA.js';
3
3
 
4
- export class WebfloRootClient2 extends WebfloRootClient1 {
4
+ export class WebfloRootClientB extends WebfloRootClientA {
5
5
 
6
6
  control() {
7
7
  const instanceController = super.controlSuper/*IMPORTANT*/();