@webqit/webflo 0.20.4-next.2 → 0.20.4-next.4

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 (56) hide show
  1. package/package.json +13 -34
  2. package/site/docs/concepts/realtime.md +45 -44
  3. package/site/docs/getting-started.md +40 -40
  4. package/src/{Context.js → CLIContext.js} +9 -8
  5. package/src/build-pi/esbuild-plugin-uselive-transform.js +42 -0
  6. package/src/{runtime-pi/webflo-client/webflo-codegen.js → build-pi/index.js} +148 -142
  7. package/src/index.js +3 -1
  8. package/src/init-pi/index.js +7 -4
  9. package/src/init-pi/templates/pwa/.gitignore +6 -0
  10. package/src/init-pi/templates/pwa/.webqit/webflo/client.json +15 -0
  11. package/src/init-pi/templates/pwa/.webqit/webflo/layout.json +7 -0
  12. package/src/init-pi/templates/pwa/package.json +2 -2
  13. package/src/init-pi/templates/pwa/public/manifest.json +2 -2
  14. package/src/init-pi/templates/web/.gitignore +6 -0
  15. package/src/init-pi/templates/web/.webqit/webflo/client.json +12 -0
  16. package/src/init-pi/templates/web/.webqit/webflo/layout.json +7 -0
  17. package/src/init-pi/templates/web/package.json +2 -2
  18. package/src/runtime-pi/AppBootstrap.js +38 -0
  19. package/src/runtime-pi/WebfloRuntime.js +68 -56
  20. package/src/runtime-pi/apis.js +9 -0
  21. package/src/runtime-pi/index.js +2 -4
  22. package/src/runtime-pi/webflo-client/DeviceCapabilities.js +1 -1
  23. package/src/runtime-pi/webflo-client/WebfloClient.js +33 -36
  24. package/src/runtime-pi/webflo-client/WebfloRootClient1.js +23 -17
  25. package/src/runtime-pi/webflo-client/WebfloRootClient2.js +1 -1
  26. package/src/runtime-pi/webflo-client/WebfloSubClient.js +14 -14
  27. package/src/runtime-pi/webflo-client/bootstrap.js +38 -0
  28. package/src/runtime-pi/webflo-client/index.js +2 -8
  29. package/src/runtime-pi/webflo-client/webflo-devmode.js +3 -3
  30. package/src/runtime-pi/webflo-fetch/LiveResponse.js +154 -116
  31. package/src/runtime-pi/webflo-fetch/index.js +436 -5
  32. package/src/runtime-pi/webflo-messaging/wq-message-port.js +1 -1
  33. package/src/runtime-pi/webflo-routing/HttpCookies.js +1 -1
  34. package/src/runtime-pi/webflo-routing/HttpEvent.js +12 -11
  35. package/src/runtime-pi/webflo-routing/HttpUser.js +7 -7
  36. package/src/runtime-pi/webflo-routing/WebfloRouter.js +12 -7
  37. package/src/runtime-pi/webflo-server/ServerSideCookies.js +3 -1
  38. package/src/runtime-pi/webflo-server/ServerSideSession.js +2 -1
  39. package/src/runtime-pi/webflo-server/WebfloServer.js +138 -200
  40. package/src/runtime-pi/webflo-server/bootstrap.js +59 -0
  41. package/src/runtime-pi/webflo-server/index.js +2 -6
  42. package/src/runtime-pi/webflo-server/webflo-devmode.js +24 -31
  43. package/src/runtime-pi/webflo-url/Url.js +1 -1
  44. package/src/runtime-pi/webflo-url/xURL.js +1 -1
  45. package/src/runtime-pi/webflo-worker/WebfloWorker.js +11 -15
  46. package/src/runtime-pi/webflo-worker/WorkerSideCookies.js +2 -1
  47. package/src/runtime-pi/webflo-worker/bootstrap.js +39 -0
  48. package/src/runtime-pi/webflo-worker/index.js +3 -7
  49. package/src/webflo-cli.js +1 -2
  50. package/src/runtime-pi/webflo-fetch/cookies.js +0 -10
  51. package/src/runtime-pi/webflo-fetch/fetch.js +0 -16
  52. package/src/runtime-pi/webflo-fetch/formdata.js +0 -54
  53. package/src/runtime-pi/webflo-fetch/headers.js +0 -151
  54. package/src/runtime-pi/webflo-fetch/message.js +0 -49
  55. package/src/runtime-pi/webflo-fetch/request.js +0 -62
  56. package/src/runtime-pi/webflo-fetch/response.js +0 -110
@@ -1,5 +1,7 @@
1
- import { Context } from '../Context.js';
2
1
  import { WebfloRouter } from './webflo-routing/WebfloRouter.js';
2
+ import { response as responseShim, headers as headersShim } from './webflo-fetch/index.js';
3
+ import { LiveResponse } from './webflo-fetch/LiveResponse.js';
4
+ import { AppBootstrap } from './AppBootstrap.js';
3
5
  import { _wq } from '../util.js';
4
6
 
5
7
  export class WebfloRuntime {
@@ -7,55 +9,65 @@ export class WebfloRuntime {
7
9
  #instanceController = new AbortController;
8
10
  get $instanceController() { return this.#instanceController; }
9
11
 
10
- static get Context() { return Context; }
11
-
12
12
  static get Router() { return WebfloRouter; }
13
13
 
14
- #cx;
15
- get cx() { return this.#cx; }
14
+ static create(bootstrap) { return new this(bootstrap); }
16
15
 
17
- #config;
18
- get config() { return this.#config; }
16
+ #bootstrap;
19
17
 
20
- #routes;
21
- get routes() { return this.#routes; }
18
+ get bootstrap() { return this.#bootstrap; }
19
+ get cx() { return this.bootstrap.cx; }
20
+ get config() { return this.bootstrap.config; }
21
+ get routes() { return this.bootstrap.routes; }
22
22
 
23
- constructor(cx) {
24
- if (!(cx instanceof this.constructor.Context)) {
25
- throw new Error('Argument #1 must be a Webflo Context instance');
26
- }
27
- this.#cx = cx;
28
- this.#config = this.#cx.config;
29
- this.#routes = this.#cx.routes;
23
+ constructor(bootstrap) {
24
+ this.#bootstrap = new AppBootstrap(bootstrap);
30
25
  }
31
26
 
32
27
  env(key) {
33
28
  const { ENV } = this.config;
34
29
  return key in ENV.mappings
35
- ? ENV.data[ENV.mappings[key]]
36
- : ENV.data[key];
30
+ ? ENV.data?.[ENV.mappings[key]]
31
+ : ENV.data?.[key];
37
32
  }
38
33
 
39
34
  async initialize() {
40
- // Do init work
35
+ if (this.bootstrap.init.SETUP) {
36
+ await this.bootstrap.init.SETUP(this);
37
+ }
38
+ await this.initCreateStorage();
39
+ return this.#instanceController;
40
+ }
41
+
42
+ async initCreateStorage() {
43
+ if (!this.bootstrap.init.createStorage) {
44
+ const inmemSessionRegistry = new Map;
45
+ this.bootstrap.init.createStorage = (namespace) => {
46
+ if (!inmemSessionRegistry.has(namespace)) {
47
+ inmemSessionRegistry.set(namespace, new Map);
48
+ }
49
+ return inmemSessionRegistry.get(namespace);
50
+ };
51
+ }
41
52
  return this.#instanceController;
42
53
  }
43
54
 
44
55
  async setupCapabilities() {
45
- // Do init work
46
56
  return this.#instanceController;
47
57
  }
48
58
 
49
59
  async hydrate() {
50
- // Do hydration work
51
60
  return this.#instanceController;
52
61
  }
53
62
 
54
63
  control() {
55
- // Do control work
56
64
  return this.#instanceController;
57
65
  }
58
66
 
67
+ createStorage(namespace, ttl) {
68
+ return this.bootstrap.init.createStorage(namespace, ttl);
69
+ }
70
+
59
71
  createRequest(href, init = {}) {
60
72
  return new Request(href, init);
61
73
  }
@@ -68,16 +80,16 @@ export class WebfloRuntime {
68
80
  return this.constructor.HttpSession.create({ store, request, ...rest });
69
81
  }
70
82
 
71
- createHttpUser({ store, request, session, realtime, ...rest }) {
72
- return this.constructor.HttpUser.create({ store, request, session, realtime, ...rest });
83
+ createHttpUser({ store, request, session, client, ...rest }) {
84
+ return this.constructor.HttpUser.create({ store, request, session, client, ...rest });
73
85
  }
74
86
 
75
- createHttpEvent({ request, cookies, session, user, realtime, sdk, detail, signal, state, ...rest }) {
76
- return this.constructor.HttpEvent.create(null, { request, cookies, session, user, realtime, sdk, detail, signal, state, ...rest });
87
+ createHttpEvent({ request, cookies, session, user, client, detail, signal, state, ...rest }) {
88
+ return this.constructor.HttpEvent.create(null, { request, cookies, session, user, client, detail, signal, state, ...rest });
77
89
  }
78
90
 
79
- async dispatchNavigationEvent({ httpEvent, crossLayerFetch, responseRealtime }) {
80
- const { flags: FLAGS } = this.cx;
91
+ async dispatchNavigationEvent({ httpEvent, crossLayerFetch, clientPortB }) {
92
+ const { flags: FLAGS, logger: LOGGER } = this.cx;
81
93
  // Resolve rid before dispatching
82
94
  if (httpEvent.request.method === 'GET' && httpEvent.url.query['_rid']) {
83
95
  const requestMeta = _wq(httpEvent.request, 'meta');
@@ -87,11 +99,11 @@ export class WebfloRuntime {
87
99
  }
88
100
  // Dispatch event
89
101
  const router = new this.constructor.Router(this, httpEvent.url.pathname);
90
- await router.route(['SETUP'], httpEvent);
102
+ await router.route(['SETUP'], httpEvent.extend(false));
91
103
  // Do proper routing for respone
92
104
  const response = await new Promise(async (resolve) => {
93
105
  let autoLiveResponse, response;
94
- httpEvent.realtime.wqLifecycle.messaging.then(() => {
106
+ httpEvent.client.wqLifecycle.messaging.then(() => {
95
107
  autoLiveResponse = new LiveResponse(null, { status: 202, statusText: 'Accepted', done: false });
96
108
  resolve(autoLiveResponse);
97
109
  });
@@ -100,19 +112,20 @@ export class WebfloRuntime {
100
112
  const remoteFetch = (...args) => this.remoteFetch(...args);
101
113
  return await router.route(routeMethods, httpEvent, crossLayerFetch, remoteFetch);
102
114
  };
103
- const fullRoutingPipeline = (this.cx.middlewares || []).concat(route);
115
+ const fullRoutingPipeline = this.bootstrap.middlewares.concat(route);
104
116
  try {
105
117
  response = await fullRoutingPipeline.reverse().reduce((next, fn) => {
106
- return () => fn.call(this.cx, httpEvent, router, next);
118
+ return () => fn.call(this.cx, httpEvent, next);
107
119
  }, null)()/*immediately calling the first*/;
108
120
  } catch (e) {
109
121
  console.error(e);
110
122
  response = new Response(null, { status: 500, statusText: e.message });
111
123
  }
112
- if (!(response instanceof LiveResponse) && !(response instanceof Response)) {
113
- response = LiveResponse.test(response) === 'Default'
114
- ? Response.from(response)
115
- : await LiveResponse.from(response, { responsesOK: true });
124
+ if (!/Response/.test(LiveResponse.test(response))) {
125
+ const isLifecyleComplete = httpEvent.lifeCycleComplete();
126
+ response = LiveResponse.test(response) !== 'Default' || !isLifecyleComplete
127
+ ? await LiveResponse.from(response, { done: isLifecyleComplete })
128
+ : responseShim.from.value(response);
116
129
  }
117
130
  // Any "carry" data?
118
131
  //await this.handleCarries(httpEvent, response);
@@ -123,15 +136,14 @@ export class WebfloRuntime {
123
136
  resolve(response);
124
137
  }
125
138
  });
139
+
126
140
  // Commit data in the exact order. Reason: in how they depend on each other
127
141
  for (const storage of [httpEvent.user, httpEvent.session, httpEvent.cookies]) {
128
142
  await storage?.commit?.(response, FLAGS['dev']);
129
143
  }
130
- if (response instanceof LiveResponse && response.whileLive()) {
144
+ // Wait for any whileLive promises to resolve
145
+ if (LiveResponse.test(response) === 'LiveResponse' && response.whileLive()) {
131
146
  httpEvent.waitUntil(response.whileLive(true));
132
- } else {
133
- httpEvent.waitUntil(Promise.resolve());
134
- await null; // We need the above resolved before we move on
135
147
  }
136
148
 
137
149
  // Send the X-Background-Messaging-Port header
@@ -139,42 +151,42 @@ export class WebfloRuntime {
139
151
  if (!httpEvent.lifeCycleComplete()) {
140
152
  if (this.isClientSide) {
141
153
  const responseMeta = _wq(response, 'meta');
142
- responseMeta.set('wqRealtime', responseRealtime);
154
+ responseMeta.set('background_port', clientPortB);
143
155
  } else {
144
- const upstreamBackgroundMessagingPort = response.headers.get('X-Background-Messaging-Port');
145
- response.headers.set('X-Background-Messaging-Port', responseRealtime);
156
+ const upstreamBackgroundPort = response.headers.get('X-Background-Messaging-Port');
157
+ response.headers.set('X-Background-Messaging-Port', clientPortB);
146
158
  }
147
159
 
148
160
  // On navigation:
149
- // Abort httpEvent.realtime and httpEvent itself
150
- httpEvent.realtime.addEventListener('navigate', (e) => {
161
+ // Abort httpEvent.client and httpEvent itself
162
+ httpEvent.client.addEventListener('navigate', (e) => {
151
163
  setTimeout(() => { // Allow for global handlers to see the events
152
164
  if (e.defaultPrevented) {
153
- console.log(`Client Messaging Port on ${httpEvent.request.url} not auto-closed on user navigation.`);
165
+ LOGGER.log(`Client Messaging Port on ${httpEvent.request.url} not auto-closed on user navigation.`);
154
166
  } else {
155
- httpEvent.realtime.close();
167
+ httpEvent.client.close();
156
168
  httpEvent.abort();
157
169
  }
158
170
  }, 0);
159
171
  });
160
172
  // On close:
161
173
  // Abort httpEvent itself
162
- httpEvent.realtime.wqLifecycle.close.then(() => {
174
+ httpEvent.client.wqLifecycle.close.then(() => {
163
175
  httpEvent.abort();
164
176
  });
165
177
 
166
178
  // On ROOT event complete:
167
- // Close httpEvent.realtime
179
+ // Close httpEvent.client
168
180
  httpEvent.lifeCycleComplete(true).then(() => {
169
- httpEvent.realtime.close();
181
+ httpEvent.client.close();
170
182
  });
171
183
  }
172
184
 
173
- if (!this.isClientSide && response instanceof LiveResponse) {
185
+ if (!this.isClientSide && LiveResponse.test(response) === 'LiveResponse') {
174
186
  // Must convert to Response on the server-side before returning
175
- return response.toResponse({ clientRequestRealtime: httpEvent.realtime });
187
+ return await response.toResponse({ client: httpEvent.client });
176
188
  }
177
-
189
+
178
190
  return response;
179
191
  }
180
192
 
@@ -210,8 +222,8 @@ export class WebfloRuntime {
210
222
  const flashResponses = requestMeta.get('carries')?.map((c) => c.response).filter((r) => r);
211
223
  if (flashResponses?.length) {
212
224
  httpEvent.waitUntil(new Promise((resolve) => {
213
- httpEvent.realtime.wqLifecycle.open.then(() => {
214
- httpEvent.realtime.postMessage(flashResponses, { wqEventOptions: { type: 'flash' } });
225
+ httpEvent.client.wqLifecycle.open.then(() => {
226
+ httpEvent.client.postMessage(flashResponses, { wqEventOptions: { type: 'flash' } });
215
227
  resolve();
216
228
  }, { once: true });
217
229
  }));
@@ -315,7 +327,7 @@ export class WebfloRuntime {
315
327
 
316
328
  createStreamingResponse(httpEvent, readStream, stats) {
317
329
  let response;
318
- const requestRange = httpEvent.request.headers.get('Range', true); // Parses the Range header
330
+ const requestRange = headersShim.get.value.call(httpEvent.request.headers, 'Range', true); // Parses the Range header
319
331
  if (requestRange.length) {
320
332
  const streams = requestRange.reduce((streams, range) => {
321
333
  if (!streams) return;
@@ -0,0 +1,9 @@
1
+ import { LiveResponse } from './webflo-fetch/LiveResponse.js';
2
+ import { Observer } from '@webqit/use-live';
3
+ import { shim } from './webflo-fetch/index.js';
4
+
5
+ export {
6
+ LiveResponse,
7
+ Observer,
8
+ shim,
9
+ }
@@ -1,7 +1,5 @@
1
- import * as server from './webflo-server/index.js';
2
- import * as client from './webflo-client/webflo-codegen.js';
1
+ import { start } from './webflo-server/bootstrap.js';
3
2
 
4
3
  export {
5
- server,
6
- client
4
+ start,
7
5
  }
@@ -1,4 +1,4 @@
1
- import { Observer } from '@webqit/quantum-js';
1
+ import { Observer } from '@webqit/use-live';
2
2
 
3
3
  export class DeviceCapabilities {
4
4
 
@@ -1,8 +1,10 @@
1
1
  import { _before, _toTitle } from '@webqit/util/str/index.js';
2
2
  import { _isObject } from '@webqit/util/js/index.js';
3
- import { Observer } from '@webqit/quantum-js';
3
+ import { Observer } from '@webqit/use-live';
4
4
  import { WebfloRuntime } from '../WebfloRuntime.js';
5
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';
6
8
  import { WQStarPort } from '../webflo-messaging/WQStarPort.js';
7
9
  import { ClientSideCookies } from './ClientSideCookies.js';
8
10
  import { HttpSession } from '../webflo-routing/HttpSession.js';
@@ -38,13 +40,10 @@ export class WebfloClient extends WebfloRuntime {
38
40
  #background;
39
41
  get background() { return this.#background; }
40
42
 
41
- #sdk = {};
42
- get sdk() { return this.#sdk; }
43
-
44
43
  get isClientSide() { return true; }
45
44
 
46
- constructor(cx, host) {
47
- super(cx);
45
+ constructor(bootstrap, host) {
46
+ super(bootstrap);
48
47
  this.#host = host;
49
48
  Object.defineProperty(this.host, 'webfloRuntime', { get: () => this });
50
49
  this.#location = new Url/*NOT URL*/(this.host.location);
@@ -88,12 +87,6 @@ export class WebfloClient extends WebfloRuntime {
88
87
  return instanceController;
89
88
  }
90
89
 
91
- async setupCapabilities() {
92
- const instanceController = await super.setupCapabilities();
93
- this.#sdk.Observer = Observer;
94
- return instanceController;
95
- }
96
-
97
90
  controlSuper() {
98
91
  return super.control();
99
92
  }
@@ -241,7 +234,7 @@ export class WebfloClient extends WebfloRuntime {
241
234
  a = a.split('/').filter(s => s);
242
235
  return a.reduce((prev, s, i) => prev && (s === b[i] || [s, b[i]].includes('-')), true);
243
236
  };
244
- return match(this.routes.$root) && this.routes.$sparoots.reduce((prev, subroot) => {
237
+ return match(this.bootstrap.$root) && this.bootstrap.$sparoots.reduce((prev, subroot) => {
245
238
  return prev && !match(subroot);
246
239
  }, true);
247
240
  }
@@ -277,15 +270,15 @@ export class WebfloClient extends WebfloRuntime {
277
270
  request: scopeObj.request
278
271
  });
279
272
  scopeObj.session = this.createHttpSession({
280
- store: this.#sdk.storage?.('session'),
273
+ store: this.createStorage('session'),
281
274
  request: scopeObj.request
282
275
  });
283
276
  const wqMessageChannel = new WQMessageChannel;
284
277
  scopeObj.clientRequestRealtime = wqMessageChannel.port1;
285
278
  scopeObj.user = this.createHttpUser({
286
- store: this.#sdk.storage?.('user'),
279
+ store: this.createStorage('user'),
287
280
  request: scopeObj.request,
288
- realtime: scopeObj.clientRequestRealtime,
281
+ client: scopeObj.clientRequestRealtime,
289
282
  session: scopeObj.session,
290
283
  });
291
284
  if (window.webqit?.oohtml?.configs) {
@@ -294,11 +287,10 @@ export class WebfloClient extends WebfloRuntime {
294
287
  }
295
288
  scopeObj.httpEvent = this.createHttpEvent({
296
289
  request: scopeObj.request,
297
- realtime: scopeObj.clientRequestRealtime,
290
+ client: scopeObj.clientRequestRealtime,
298
291
  cookies: scopeObj.cookies,
299
292
  session: scopeObj.session,
300
293
  user: scopeObj.user,
301
- sdk: this.#sdk,
302
294
  detail: scopeObj.detail,
303
295
  signal: init.signal,
304
296
  state: scopeObj.UIState,
@@ -335,7 +327,7 @@ export class WebfloClient extends WebfloRuntime {
335
327
  }
336
328
  return await this.remoteFetch(event.request);
337
329
  },
338
- responseRealtime: wqMessageChannel.port2,
330
+ clientPortB: wqMessageChannel.port2,
339
331
  originalRequestInit: scopeObj.init
340
332
  });
341
333
  // Decode response
@@ -357,8 +349,9 @@ export class WebfloClient extends WebfloRuntime {
357
349
  Observer.set(this.location, 'href', scopeObj.finalUrl);
358
350
  scopeObj.resetStates();
359
351
  // Error?
360
- if ([404, 500].includes(scopeObj.response.status)) {
361
- const error = new Error(scopeObj.response.statusText, { code: scopeObj.response.status });
352
+ const statusCode = responseShim.prototype.status.get.call(scopeObj.response);
353
+ if ([404, 500].includes(statusCode)) {
354
+ const error = new Error(scopeObj.response.statusText, { code: statusCode });
362
355
  Object.defineProperty(error, 'retry', { value: async () => await this.navigate(scopeObj.url, scopeObj.init, scopeObj.detail) });
363
356
  Observer.set(this.navigator, 'error', error);
364
357
  }
@@ -372,16 +365,18 @@ export class WebfloClient extends WebfloRuntime {
372
365
  });
373
366
  }
374
367
 
375
- async dispatchNavigationEvent({ httpEvent, crossLayerFetch, responseRealtime, originalRequestInit, processObj = {} }) {
376
- const response = await super.dispatchNavigationEvent({ httpEvent, crossLayerFetch, responseRealtime });
377
- // Obtain and connect responseRealtime as first thing
378
- if (response.isLive()) {
379
- this.background.addPort(response.wqRealtime);
368
+ async dispatchNavigationEvent({ httpEvent, crossLayerFetch, clientPortB, originalRequestInit, processObj = {} }) {
369
+ const response = await super.dispatchNavigationEvent({ httpEvent, crossLayerFetch, clientPortB });
370
+ // Obtain and connect clientPortB as first thing
371
+ const backgroundPort = LiveResponse.getBackground(response);
372
+ if (backgroundPort) {
373
+ this.background.addPort(backgroundPort);
380
374
  }
381
375
  // Await a response with an "Accepted" or redirect status
382
- if (response.status === 202 || (response.headers.get('Location') && this.processRedirect(response))) {
376
+ const statusCode = responseShim.prototype.status.get.call(response);
377
+ if (statusCode === 202 || (response.headers.get('Location') && this.processRedirect(response))) {
383
378
  return new Promise(async (resolve) => {
384
- if (response.isLive()) {
379
+ if (LiveResponse.hasBackground(response)) {
385
380
  const liveResponse = await LiveResponse.from(response);
386
381
  liveResponse.addEventListener('replace', () => resolve(liveResponse), { once: true, signal: httpEvent.signal });
387
382
  } // Never resolves otherwise
@@ -398,7 +393,7 @@ export class WebfloClient extends WebfloRuntime {
398
393
  if (!processObj.recurseController.signal.aborted) {
399
394
  await new Promise((res) => setTimeout(res, parseInt(response.headers.get('Retry-After')) * 1000));
400
395
  const eventClone = httpEvent.cloneWith({ request: this.createRequest(httpEvent.url, originalRequestInit) });
401
- return await this.dispatchNavigationEvent({ httpEvent: eventClone, crossLayerFetch, responseRealtime, originalRequestInit, processObj });
396
+ return await this.dispatchNavigationEvent({ httpEvent: eventClone, crossLayerFetch, clientPortB, originalRequestInit, processObj });
402
397
  }
403
398
  } else if (processObj.recurseController) {
404
399
  // Abort the signal. This is the end of the loop
@@ -409,27 +404,29 @@ export class WebfloClient extends WebfloRuntime {
409
404
 
410
405
  processRedirect(response) {
411
406
  // Normalize redirect
407
+ let statusCode = responseShim.prototype.status.get.call(response);
412
408
  const xActualRedirectCode = parseInt(response.headers.get('X-Redirect-Code'));
413
- if (xActualRedirectCode && response.status === this.#xRedirectCode) {
409
+ if (xActualRedirectCode && statusCode === this.#xRedirectCode) {
414
410
  const responseMeta = _wq(response, 'meta');
415
411
  responseMeta.set('status', xActualRedirectCode); // @NOTE 1
412
+ statusCode = xActualRedirectCode;
416
413
  }
417
414
  // Trigger redirect
418
- if ([302, 301].includes(response.status)) {
415
+ if ([302, 301].includes(statusCode)) {
419
416
  const location = new URL(response.headers.get('Location'), this.location.origin);
420
417
  if (this.isSpaRoute(location)) {
421
418
  this.navigate(location, {}, { navigationType: 'rdr' });
422
419
  } else {
423
- this.redirect(location, response.wqRealtime);
420
+ this.redirect(location, LiveResponse.getBackground(response));
424
421
  }
425
422
  return true;
426
423
  }
427
424
  }
428
425
 
429
- redirect(location, responseRealtime) {
430
- if (responseRealtime) {
426
+ redirect(location, responseBackground) {
427
+ if (responseBackground) {
431
428
  // Redundant as this is a window reload anyways
432
- responseRealtime.close();
429
+ responseBackground.close();
433
430
  }
434
431
  window.location = location;
435
432
  }
@@ -470,7 +467,7 @@ export class WebfloClient extends WebfloRuntime {
470
467
  navigator: this.navigator,
471
468
  location: this.location,
472
469
  network: this.network, // request, redirect, error, status, remote
473
- capabilities: this.deviceCapabilities,
470
+ capabilities: this.capabilities,
474
471
  transition: this.transition,
475
472
  }, { diff: true, merge });
476
473
  $response.addEventListener('replace', (e) => {
@@ -1,7 +1,9 @@
1
- import { Observer } from '@webqit/quantum-js';
1
+ import { Observer } from '@webqit/use-live';
2
2
  import { WebfloClient } from './WebfloClient.js';
3
3
  import { ClientSideWorkport } from './ClientSideWorkport.js';
4
4
  import { DeviceCapabilities } from './DeviceCapabilities.js';
5
+ import { response as responseShim } from '../webflo-fetch/index.js';
6
+ import { LiveResponse } from '../webflo-fetch/LiveResponse.js';
5
7
  import { WebfloHMR } from './webflo-devmode.js';
6
8
 
7
9
  export class WebfloRootClient1 extends WebfloClient {
@@ -10,8 +12,8 @@ export class WebfloRootClient1 extends WebfloClient {
10
12
 
11
13
  static get DeviceCapabilities() { return DeviceCapabilities; }
12
14
 
13
- static create(cx, host) {
14
- return new this(this.Context.create(cx), host);
15
+ static create(bootstrap, host) {
16
+ return new this(bootstrap, host);
15
17
  }
16
18
 
17
19
  #network;
@@ -20,8 +22,8 @@ export class WebfloRootClient1 extends WebfloClient {
20
22
  #workport;
21
23
  get workport() { return this.#workport; }
22
24
 
23
- #deviceCapabilities;
24
- get deviceCapabilities() { return this.#deviceCapabilities; }
25
+ #capabilities;
26
+ get capabilities() { return this.#capabilities; }
25
27
 
26
28
  #hmr;
27
29
 
@@ -29,11 +31,11 @@ export class WebfloRootClient1 extends WebfloClient {
29
31
  return document.querySelector('meta[name="webflo:viewtransitions"]')?.value;
30
32
  }
31
33
 
32
- constructor(cx, host) {
34
+ constructor(bootstrap, host) {
33
35
  if (!(host instanceof Document)) {
34
36
  throw new Error('Argument #1 must be a Document instance');
35
37
  }
36
- super(cx, host);
38
+ super(bootstrap, host);
37
39
  this.#network = { status: window.navigator.onLine };
38
40
  }
39
41
 
@@ -87,12 +89,14 @@ export class WebfloRootClient1 extends WebfloClient {
87
89
  // Service Worker && Capabilities
88
90
  const cleanups = [];
89
91
  instanceController.signal.addEventListener('abort', () => cleanups.forEach((c) => c()), { once: true });
90
- this.#deviceCapabilities = await this.constructor.DeviceCapabilities.initialize(this, this.config.CLIENT.capabilities);
91
- cleanups.push(() => this.#deviceCapabilities.close());
92
- if (this.config.CLIENT.capabilities?.service_worker?.filename) {
93
- const { service_worker: { filename, ...restServiceWorkerParams } = {} } = this.config.CLIENT.capabilities;
94
- this.#workport = await this.constructor.Workport.initialize(null, (this.config.CLIENT.public_base_url || '') + filename, restServiceWorkerParams);
95
- cleanups.push(() => this.#workport.close());
92
+ this.#capabilities = await this.constructor.DeviceCapabilities.initialize(this, this.config.CLIENT.capabilities);
93
+ cleanups.push(() => this.#capabilities.close());
94
+ if (this.config.CLIENT.capabilities?.service_worker) {
95
+ const { filename, ...restServiceWorkerParams } = this.config.WORKER;
96
+ this.constructor.Workport.initialize(null, filename, restServiceWorkerParams).then((workport) => {
97
+ this.#workport = workport;
98
+ cleanups.push(() => this.#workport.close());
99
+ });
96
100
  }
97
101
  return instanceController;
98
102
  }
@@ -101,16 +105,18 @@ export class WebfloRootClient1 extends WebfloClient {
101
105
  const instanceController = await super.hydrate();
102
106
  const scopeObj = {};
103
107
  scopeObj.data = this.host.querySelector(`script[rel="hydration"][type="application/json"]`)?.textContent?.trim() || null;
104
- scopeObj.response = new Response.from(scopeObj.data, { headers: { 'Content-Type': 'application/json' } });
108
+ scopeObj.response = responseShim.from.value(scopeObj.data, { headers: { 'Content-Type': 'application/json' } });
105
109
  for (const name of ['X-Background-Messaging-Port', 'X-Live-Response-Message-ID', 'X-Webflo-Dev-Mode']) {
106
110
  const metaElement = this.host.querySelector(`meta[name="${name}"]`);
107
111
  if (!metaElement) continue;
108
112
  scopeObj.response.headers.set(name, metaElement.content?.trim() || '');
109
113
  }
110
- if (scopeObj.response.isLive()) {
111
- this.background.addPort(scopeObj.response.wqRealtime);
114
+ const backgroundPort = LiveResponse.getBackground(scopeObj.response);
115
+ if (backgroundPort) {
116
+ this.background.addPort(backgroundPort);
112
117
  }
113
- if (scopeObj.response.body || scopeObj.response.isLive()) {
118
+ if (scopeObj.response.body || backgroundPort) {
119
+
114
120
  const httpEvent = this.createHttpEvent({ request: this.createRequest(this.location.href) }, true);
115
121
  await this.render(httpEvent, scopeObj.response);
116
122
  } else {
@@ -1,4 +1,4 @@
1
- import { Observer } from '@webqit/quantum-js';
1
+ import { Observer } from '@webqit/use-live';
2
2
  import { WebfloRootClient1 } from './WebfloRootClient1.js';
3
3
 
4
4
  export class WebfloRootClient2 extends WebfloRootClient1 {
@@ -1,4 +1,4 @@
1
- import { Observer } from '@webqit/quantum-js';
1
+ import { Observer } from '@webqit/use-live';
2
2
  import { WebfloClient } from './WebfloClient.js';
3
3
  import { defineElement } from './webflo-embedded.js';
4
4
  import { Url } from '../webflo-url/Url.js';
@@ -21,7 +21,7 @@ export class WebfloSubClient extends WebfloClient {
21
21
 
22
22
  get workport() { return this.#superRuntime.workport; }
23
23
 
24
- get deviceCapabilities() { return this.#superRuntime.deviceCapabilities; }
24
+ get capabilities() { return this.#superRuntime.capabilities; }
25
25
 
26
26
  get withViewTransitions() { return this.host.hasAttribute('viewtransitions'); }
27
27
 
@@ -32,7 +32,7 @@ export class WebfloSubClient extends WebfloClient {
32
32
  if (!(host instanceof HTMLElement)) {
33
33
  throw new Error('Argument #1 must be a HTMLElement instance');
34
34
  }
35
- super(superRuntime.cx, host);
35
+ super(superRuntime.bootstrap, host);
36
36
  this.#superRuntime = superRuntime;
37
37
  }
38
38
 
@@ -65,19 +65,19 @@ export class WebfloSubClient extends WebfloClient {
65
65
  return super.controlClassic/*IMPORTANT*/(locationCallback);
66
66
  }
67
67
 
68
- reload(params) {}
68
+ reload(params) { }
69
69
 
70
- back() {}
70
+ back() { }
71
71
 
72
- forward() {}
72
+ forward() { }
73
73
 
74
- traverseTo(...args) {}
74
+ traverseTo(...args) { }
75
75
 
76
- async push(url, state = {}) {}
76
+ async push(url, state = {}) { }
77
77
 
78
- entries() {}
78
+ entries() { }
79
79
 
80
- currentEntry() {}
80
+ currentEntry() { }
81
81
 
82
82
  async updateCurrentEntry(params, url = null) {
83
83
  this.host.reflectLocation(url);
@@ -90,16 +90,16 @@ export class WebfloSubClient extends WebfloClient {
90
90
  (this.host.querySelector('[autofocus]') || this.host).focus();
91
91
  }
92
92
 
93
- redirect(location, responseRealtime = null) {
93
+ redirect(location, responseBackground = null) {
94
94
  location = typeof location === 'string' ? new URL(location, this.location.origin) : location;
95
95
  const width = Math.min(800, window.innerWidth);
96
96
  const height = Math.min(600, window.innerHeight);
97
97
  const left = (window.outerWidth - width) / 2;
98
98
  const top = (window.outerHeight - height) / 2;
99
99
  const popup = window.open(location, '_blank', `popup=true,width=${width},height=${height},left=${left},top=${top}`);
100
- if (responseRealtime) {
100
+ if (responseBackground) {
101
101
  Observer.set(this.navigator, 'redirecting', new Url/*NOT URL*/(location), { diff: true });
102
- responseRealtime.addEventListener('close', (e) => {
102
+ responseBackground.addEventListener('close', (e) => {
103
103
  window.removeEventListener('message', windowMessageHandler);
104
104
  Observer.set(this.navigator, 'redirecting', null);
105
105
  popup.postMessage('timeout:5');
@@ -109,7 +109,7 @@ export class WebfloSubClient extends WebfloClient {
109
109
  }, { once: true });
110
110
  const windowMessageHandler = (e) => {
111
111
  if (e.source === popup && e.data === 'close') {
112
- responseRealtime.close();
112
+ responseBackground.close();
113
113
  }
114
114
  };
115
115
  window.addEventListener('message', windowMessageHandler);