@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.
Files changed (57) hide show
  1. package/package.json +8 -5
  2. package/src/build-pi/index.js +6 -4
  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 -103
  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 -269
  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
@@ -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
- createHttpEvent(init, singleton = true) {
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.createHttpEvent(init);
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
- // Resolve inputs
263
- const scopeObj = { url, init, detail };
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, self.location.origin);
284
+ scopeObj.url = new URL(scopeObj.url, window.location.origin);
266
285
  }
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,
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
- scopeObj.session = this.createHttpSession({
280
- store: this.createStorage('session'),
281
- request: scopeObj.request,
282
- thread: scopeObj.thread,
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
- 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,
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
- scopeObj.httpEvent = this.createHttpEvent({
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
- detail: scopeObj.detail,
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 Url/*NOT URL*/(scopeObj.url),
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 = { ...Url.copy(scopeObj.url), method: scopeObj.request.method };
331
- this.#background.postMessage(url, { wqEventOptions: { type: 'navigate' } });
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: wqMessageChannel.port2,
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 = responseShim.prototype.status.get.call(scopeObj.response);
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 = 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;
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 = responseShim.prototype.status.get.call(response);
396
- if (statusCode === 202 && LiveResponse.hasBackground(response)) {
397
- return new Promise(handleInteractiveMode);
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
- // 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
- });
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.hasBackground(response)) {
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
- let statusCode = responseShim.prototype.status.get.call(response);
444
- const xActualRedirectCode = parseInt(response.headers.get('X-Redirect-Code'));
445
- if (xActualRedirectCode && statusCode === this.#xRedirectCode) {
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 1;
516
+ return;
457
517
  }
458
- return this.redirect(location, response);
459
- }
460
518
 
461
- return 0; // No actual redirect
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
- return 2; // Window reload
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, Url.copy(this.location));
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 this.constructor.Router(this, this.location.pathname);
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/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);