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

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 (43) hide show
  1. package/package.json +5 -20
  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-livejs-transform.js +35 -0
  6. package/src/{runtime-pi/webflo-client/webflo-codegen.js → build-pi/index.js} +145 -141
  7. package/src/index.js +3 -1
  8. package/src/init-pi/index.js +6 -3
  9. package/src/init-pi/templates/pwa/package.json +2 -2
  10. package/src/init-pi/templates/web/package.json +2 -2
  11. package/src/runtime-pi/AppBootstrap.js +38 -0
  12. package/src/runtime-pi/WebfloRuntime.js +50 -47
  13. package/src/runtime-pi/apis.js +9 -0
  14. package/src/runtime-pi/index.js +2 -4
  15. package/src/runtime-pi/webflo-client/WebfloClient.js +31 -35
  16. package/src/runtime-pi/webflo-client/WebfloRootClient1.js +16 -14
  17. package/src/runtime-pi/webflo-client/WebfloSubClient.js +13 -13
  18. package/src/runtime-pi/webflo-client/bootstrap.js +37 -0
  19. package/src/runtime-pi/webflo-client/index.js +2 -8
  20. package/src/runtime-pi/webflo-client/webflo-devmode.js +3 -3
  21. package/src/runtime-pi/webflo-fetch/LiveResponse.js +127 -96
  22. package/src/runtime-pi/webflo-fetch/index.js +435 -5
  23. package/src/runtime-pi/webflo-routing/HttpCookies.js +1 -1
  24. package/src/runtime-pi/webflo-routing/HttpEvent.js +5 -6
  25. package/src/runtime-pi/webflo-routing/HttpUser.js +7 -7
  26. package/src/runtime-pi/webflo-server/ServerSideCookies.js +3 -1
  27. package/src/runtime-pi/webflo-server/ServerSideSession.js +2 -1
  28. package/src/runtime-pi/webflo-server/WebfloServer.js +98 -195
  29. package/src/runtime-pi/webflo-server/bootstrap.js +59 -0
  30. package/src/runtime-pi/webflo-server/index.js +2 -6
  31. package/src/runtime-pi/webflo-server/webflo-devmode.js +13 -24
  32. package/src/runtime-pi/webflo-worker/WebfloWorker.js +11 -15
  33. package/src/runtime-pi/webflo-worker/WorkerSideCookies.js +2 -1
  34. package/src/runtime-pi/webflo-worker/bootstrap.js +38 -0
  35. package/src/runtime-pi/webflo-worker/index.js +3 -7
  36. package/src/webflo-cli.js +1 -2
  37. package/src/runtime-pi/webflo-fetch/cookies.js +0 -10
  38. package/src/runtime-pi/webflo-fetch/fetch.js +0 -16
  39. package/src/runtime-pi/webflo-fetch/formdata.js +0 -54
  40. package/src/runtime-pi/webflo-fetch/headers.js +0 -151
  41. package/src/runtime-pi/webflo-fetch/message.js +0 -49
  42. package/src/runtime-pi/webflo-fetch/request.js +0 -62
  43. package/src/runtime-pi/webflo-fetch/response.js +0 -110
@@ -1,5 +1,6 @@
1
- import { Context } from '../Context.js';
2
1
  import { WebfloRouter } from './webflo-routing/WebfloRouter.js';
2
+ import { LiveResponse, response as responseShim, headers as headersShim } from './webflo-fetch/index.js';
3
+ import { AppBootstrap } from './AppBootstrap.js';
3
4
  import { _wq } from '../util.js';
4
5
 
5
6
  export class WebfloRuntime {
@@ -7,55 +8,57 @@ export class WebfloRuntime {
7
8
  #instanceController = new AbortController;
8
9
  get $instanceController() { return this.#instanceController; }
9
10
 
10
- static get Context() { return Context; }
11
-
12
11
  static get Router() { return WebfloRouter; }
13
12
 
14
- #cx;
15
- get cx() { return this.#cx; }
13
+ static create(bootstrap) { return new this(bootstrap); }
16
14
 
17
- #config;
18
- get config() { return this.#config; }
15
+ #bootstrap;
19
16
 
20
- #routes;
21
- get routes() { return this.#routes; }
17
+ get bootstrap() { return this.#bootstrap; }
18
+ get cx() { return this.bootstrap.cx; }
19
+ get config() { return this.bootstrap.config; }
20
+ get routes() { return this.bootstrap.routes; }
22
21
 
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;
22
+ constructor(bootstrap) {
23
+ this.#bootstrap = new AppBootstrap(bootstrap);
30
24
  }
31
25
 
32
26
  env(key) {
33
27
  const { ENV } = this.config;
34
28
  return key in ENV.mappings
35
- ? ENV.data[ENV.mappings[key]]
36
- : ENV.data[key];
29
+ ? ENV.data?.[ENV.mappings[key]]
30
+ : ENV.data?.[key];
37
31
  }
38
32
 
39
33
  async initialize() {
40
- // Do init work
34
+ if (!this.bootstrap.init.createStorage) {
35
+ const inmemSessionRegistry = new Map;
36
+ this.bootstrap.init.createStorage = (namespace) => {
37
+ if (!inmemSessionRegistry.has(namespace)) {
38
+ inmemSessionRegistry.set(namespace, new Map);
39
+ }
40
+ return inmemSessionRegistry.get(namespace);
41
+ };
42
+ }
41
43
  return this.#instanceController;
42
44
  }
43
45
 
44
46
  async setupCapabilities() {
45
- // Do init work
46
47
  return this.#instanceController;
47
48
  }
48
49
 
49
50
  async hydrate() {
50
- // Do hydration work
51
51
  return this.#instanceController;
52
52
  }
53
53
 
54
54
  control() {
55
- // Do control work
56
55
  return this.#instanceController;
57
56
  }
58
57
 
58
+ async createStorage(namespace, ttl) {
59
+ return await this.bootstrap.init.createStorage(namespace, ttl);
60
+ }
61
+
59
62
  createRequest(href, init = {}) {
60
63
  return new Request(href, init);
61
64
  }
@@ -68,16 +71,16 @@ export class WebfloRuntime {
68
71
  return this.constructor.HttpSession.create({ store, request, ...rest });
69
72
  }
70
73
 
71
- createHttpUser({ store, request, session, realtime, ...rest }) {
72
- return this.constructor.HttpUser.create({ store, request, session, realtime, ...rest });
74
+ createHttpUser({ store, request, session, client, ...rest }) {
75
+ return this.constructor.HttpUser.create({ store, request, session, client, ...rest });
73
76
  }
74
77
 
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 });
78
+ createHttpEvent({ request, cookies, session, user, client, detail, signal, state, ...rest }) {
79
+ return this.constructor.HttpEvent.create(null, { request, cookies, session, user, client, detail, signal, state, ...rest });
77
80
  }
78
81
 
79
- async dispatchNavigationEvent({ httpEvent, crossLayerFetch, responseRealtime }) {
80
- const { flags: FLAGS } = this.cx;
82
+ async dispatchNavigationEvent({ httpEvent, crossLayerFetch, clientPortB }) {
83
+ const { flags: FLAGS, logger: LOGGER } = this.cx;
81
84
  // Resolve rid before dispatching
82
85
  if (httpEvent.request.method === 'GET' && httpEvent.url.query['_rid']) {
83
86
  const requestMeta = _wq(httpEvent.request, 'meta');
@@ -91,7 +94,7 @@ export class WebfloRuntime {
91
94
  // Do proper routing for respone
92
95
  const response = await new Promise(async (resolve) => {
93
96
  let autoLiveResponse, response;
94
- httpEvent.realtime.wqLifecycle.messaging.then(() => {
97
+ httpEvent.client.wqLifecycle.messaging.then(() => {
95
98
  autoLiveResponse = new LiveResponse(null, { status: 202, statusText: 'Accepted', done: false });
96
99
  resolve(autoLiveResponse);
97
100
  });
@@ -100,10 +103,10 @@ export class WebfloRuntime {
100
103
  const remoteFetch = (...args) => this.remoteFetch(...args);
101
104
  return await router.route(routeMethods, httpEvent, crossLayerFetch, remoteFetch);
102
105
  };
103
- const fullRoutingPipeline = (this.cx.middlewares || []).concat(route);
106
+ const fullRoutingPipeline = this.bootstrap.middlewares.concat(route);
104
107
  try {
105
108
  response = await fullRoutingPipeline.reverse().reduce((next, fn) => {
106
- return () => fn.call(this.cx, httpEvent, router, next);
109
+ return () => fn.call(this.cx, httpEvent, next);
107
110
  }, null)()/*immediately calling the first*/;
108
111
  } catch (e) {
109
112
  console.error(e);
@@ -111,7 +114,7 @@ export class WebfloRuntime {
111
114
  }
112
115
  if (!(response instanceof LiveResponse) && !(response instanceof Response)) {
113
116
  response = LiveResponse.test(response) === 'Default'
114
- ? Response.from(response)
117
+ ? responseShim.from.value(response)
115
118
  : await LiveResponse.from(response, { responsesOK: true });
116
119
  }
117
120
  // Any "carry" data?
@@ -139,42 +142,42 @@ export class WebfloRuntime {
139
142
  if (!httpEvent.lifeCycleComplete()) {
140
143
  if (this.isClientSide) {
141
144
  const responseMeta = _wq(response, 'meta');
142
- responseMeta.set('wqRealtime', responseRealtime);
145
+ responseMeta.set('background_port', clientPortB);
143
146
  } else {
144
- const upstreamBackgroundMessagingPort = response.headers.get('X-Background-Messaging-Port');
145
- response.headers.set('X-Background-Messaging-Port', responseRealtime);
147
+ const upstreamBackgroundPort = response.headers.get('X-Background-Messaging-Port');
148
+ response.headers.set('X-Background-Messaging-Port', clientPortB);
146
149
  }
147
150
 
148
151
  // On navigation:
149
- // Abort httpEvent.realtime and httpEvent itself
150
- httpEvent.realtime.addEventListener('navigate', (e) => {
152
+ // Abort httpEvent.client and httpEvent itself
153
+ httpEvent.client.addEventListener('navigate', (e) => {
151
154
  setTimeout(() => { // Allow for global handlers to see the events
152
155
  if (e.defaultPrevented) {
153
- console.log(`Client Messaging Port on ${httpEvent.request.url} not auto-closed on user navigation.`);
156
+ LOGGER.log(`Client Messaging Port on ${httpEvent.request.url} not auto-closed on user navigation.`);
154
157
  } else {
155
- httpEvent.realtime.close();
158
+ httpEvent.client.close();
156
159
  httpEvent.abort();
157
160
  }
158
161
  }, 0);
159
162
  });
160
163
  // On close:
161
164
  // Abort httpEvent itself
162
- httpEvent.realtime.wqLifecycle.close.then(() => {
165
+ httpEvent.client.wqLifecycle.close.then(() => {
163
166
  httpEvent.abort();
164
167
  });
165
168
 
166
169
  // On ROOT event complete:
167
- // Close httpEvent.realtime
170
+ // Close httpEvent.client
168
171
  httpEvent.lifeCycleComplete(true).then(() => {
169
- httpEvent.realtime.close();
172
+ httpEvent.client.close();
170
173
  });
171
174
  }
172
175
 
173
176
  if (!this.isClientSide && response instanceof LiveResponse) {
174
177
  // Must convert to Response on the server-side before returning
175
- return response.toResponse({ clientRequestRealtime: httpEvent.realtime });
178
+ return response.toResponse({ client: httpEvent.client });
176
179
  }
177
-
180
+
178
181
  return response;
179
182
  }
180
183
 
@@ -210,8 +213,8 @@ export class WebfloRuntime {
210
213
  const flashResponses = requestMeta.get('carries')?.map((c) => c.response).filter((r) => r);
211
214
  if (flashResponses?.length) {
212
215
  httpEvent.waitUntil(new Promise((resolve) => {
213
- httpEvent.realtime.wqLifecycle.open.then(() => {
214
- httpEvent.realtime.postMessage(flashResponses, { wqEventOptions: { type: 'flash' } });
216
+ httpEvent.client.wqLifecycle.open.then(() => {
217
+ httpEvent.client.postMessage(flashResponses, { wqEventOptions: { type: 'flash' } });
215
218
  resolve();
216
219
  }, { once: true });
217
220
  }));
@@ -315,7 +318,7 @@ export class WebfloRuntime {
315
318
 
316
319
  createStreamingResponse(httpEvent, readStream, stats) {
317
320
  let response;
318
- const requestRange = httpEvent.request.headers.get('Range', true); // Parses the Range header
321
+ const requestRange = headersShim.get.value.call(httpEvent.request.headers, 'Range', true); // Parses the Range header
319
322
  if (requestRange.length) {
320
323
  const streams = requestRange.reduce((streams, range) => {
321
324
  if (!streams) return;
@@ -0,0 +1,9 @@
1
+ import { LiveResponse } from './webflo-fetch/LiveResponse.js';
2
+ import { Observer } from '@webqit/quantum-js';
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
  }
@@ -3,6 +3,7 @@ import { _isObject } from '@webqit/util/js/index.js';
3
3
  import { Observer } from '@webqit/quantum-js';
4
4
  import { WebfloRuntime } from '../WebfloRuntime.js';
5
5
  import { WQMessageChannel } from '../webflo-messaging/WQMessageChannel.js';
6
+ import { LiveResponse, response as responseShim } from '../webflo-fetch/index.js';
6
7
  import { WQStarPort } from '../webflo-messaging/WQStarPort.js';
7
8
  import { ClientSideCookies } from './ClientSideCookies.js';
8
9
  import { HttpSession } from '../webflo-routing/HttpSession.js';
@@ -38,13 +39,10 @@ export class WebfloClient extends WebfloRuntime {
38
39
  #background;
39
40
  get background() { return this.#background; }
40
41
 
41
- #sdk = {};
42
- get sdk() { return this.#sdk; }
43
-
44
42
  get isClientSide() { return true; }
45
43
 
46
- constructor(cx, host) {
47
- super(cx);
44
+ constructor(bootstrap, host) {
45
+ super(bootstrap);
48
46
  this.#host = host;
49
47
  Object.defineProperty(this.host, 'webfloRuntime', { get: () => this });
50
48
  this.#location = new Url/*NOT URL*/(this.host.location);
@@ -88,12 +86,6 @@ export class WebfloClient extends WebfloRuntime {
88
86
  return instanceController;
89
87
  }
90
88
 
91
- async setupCapabilities() {
92
- const instanceController = await super.setupCapabilities();
93
- this.#sdk.Observer = Observer;
94
- return instanceController;
95
- }
96
-
97
89
  controlSuper() {
98
90
  return super.control();
99
91
  }
@@ -241,7 +233,7 @@ export class WebfloClient extends WebfloRuntime {
241
233
  a = a.split('/').filter(s => s);
242
234
  return a.reduce((prev, s, i) => prev && (s === b[i] || [s, b[i]].includes('-')), true);
243
235
  };
244
- return match(this.routes.$root) && this.routes.$sparoots.reduce((prev, subroot) => {
236
+ return match(this.bootstrap.$root) && this.bootstrap.$sparoots.reduce((prev, subroot) => {
245
237
  return prev && !match(subroot);
246
238
  }, true);
247
239
  }
@@ -277,15 +269,15 @@ export class WebfloClient extends WebfloRuntime {
277
269
  request: scopeObj.request
278
270
  });
279
271
  scopeObj.session = this.createHttpSession({
280
- store: this.#sdk.storage?.('session'),
272
+ store: this.createStorage('session'),
281
273
  request: scopeObj.request
282
274
  });
283
275
  const wqMessageChannel = new WQMessageChannel;
284
276
  scopeObj.clientRequestRealtime = wqMessageChannel.port1;
285
277
  scopeObj.user = this.createHttpUser({
286
- store: this.#sdk.storage?.('user'),
278
+ store: this.createStorage('user'),
287
279
  request: scopeObj.request,
288
- realtime: scopeObj.clientRequestRealtime,
280
+ client: scopeObj.clientRequestRealtime,
289
281
  session: scopeObj.session,
290
282
  });
291
283
  if (window.webqit?.oohtml?.configs) {
@@ -294,11 +286,10 @@ export class WebfloClient extends WebfloRuntime {
294
286
  }
295
287
  scopeObj.httpEvent = this.createHttpEvent({
296
288
  request: scopeObj.request,
297
- realtime: scopeObj.clientRequestRealtime,
289
+ client: scopeObj.clientRequestRealtime,
298
290
  cookies: scopeObj.cookies,
299
291
  session: scopeObj.session,
300
292
  user: scopeObj.user,
301
- sdk: this.#sdk,
302
293
  detail: scopeObj.detail,
303
294
  signal: init.signal,
304
295
  state: scopeObj.UIState,
@@ -335,7 +326,7 @@ export class WebfloClient extends WebfloRuntime {
335
326
  }
336
327
  return await this.remoteFetch(event.request);
337
328
  },
338
- responseRealtime: wqMessageChannel.port2,
329
+ clientPortB: wqMessageChannel.port2,
339
330
  originalRequestInit: scopeObj.init
340
331
  });
341
332
  // Decode response
@@ -357,8 +348,9 @@ export class WebfloClient extends WebfloRuntime {
357
348
  Observer.set(this.location, 'href', scopeObj.finalUrl);
358
349
  scopeObj.resetStates();
359
350
  // Error?
360
- if ([404, 500].includes(scopeObj.response.status)) {
361
- const error = new Error(scopeObj.response.statusText, { code: scopeObj.response.status });
351
+ const statusCode = responseShim.prototype.status.get.call(scopeObj.response);
352
+ if ([404, 500].includes(statusCode)) {
353
+ const error = new Error(scopeObj.response.statusText, { code: statusCode });
362
354
  Object.defineProperty(error, 'retry', { value: async () => await this.navigate(scopeObj.url, scopeObj.init, scopeObj.detail) });
363
355
  Observer.set(this.navigator, 'error', error);
364
356
  }
@@ -372,16 +364,18 @@ export class WebfloClient extends WebfloRuntime {
372
364
  });
373
365
  }
374
366
 
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);
367
+ async dispatchNavigationEvent({ httpEvent, crossLayerFetch, clientPortB, originalRequestInit, processObj = {} }) {
368
+ const response = await super.dispatchNavigationEvent({ httpEvent, crossLayerFetch, clientPortB });
369
+ // Obtain and connect clientPortB as first thing
370
+ const backgroundPort = LiveResponse.getBackground(response);
371
+ if (backgroundPort) {
372
+ this.background.addPort(backgroundPort);
380
373
  }
381
374
  // Await a response with an "Accepted" or redirect status
382
- if (response.status === 202 || (response.headers.get('Location') && this.processRedirect(response))) {
375
+ const statusCode = responseShim.prototype.status.get.call(response);
376
+ if (statusCode === 202 || (response.headers.get('Location') && this.processRedirect(response))) {
383
377
  return new Promise(async (resolve) => {
384
- if (response.isLive()) {
378
+ if (LiveResponse.hasBackground(response)) {
385
379
  const liveResponse = await LiveResponse.from(response);
386
380
  liveResponse.addEventListener('replace', () => resolve(liveResponse), { once: true, signal: httpEvent.signal });
387
381
  } // Never resolves otherwise
@@ -398,7 +392,7 @@ export class WebfloClient extends WebfloRuntime {
398
392
  if (!processObj.recurseController.signal.aborted) {
399
393
  await new Promise((res) => setTimeout(res, parseInt(response.headers.get('Retry-After')) * 1000));
400
394
  const eventClone = httpEvent.cloneWith({ request: this.createRequest(httpEvent.url, originalRequestInit) });
401
- return await this.dispatchNavigationEvent({ httpEvent: eventClone, crossLayerFetch, responseRealtime, originalRequestInit, processObj });
395
+ return await this.dispatchNavigationEvent({ httpEvent: eventClone, crossLayerFetch, clientPortB, originalRequestInit, processObj });
402
396
  }
403
397
  } else if (processObj.recurseController) {
404
398
  // Abort the signal. This is the end of the loop
@@ -409,27 +403,29 @@ export class WebfloClient extends WebfloRuntime {
409
403
 
410
404
  processRedirect(response) {
411
405
  // Normalize redirect
406
+ let statusCode = responseShim.prototype.status.get.call(response);
412
407
  const xActualRedirectCode = parseInt(response.headers.get('X-Redirect-Code'));
413
- if (xActualRedirectCode && response.status === this.#xRedirectCode) {
408
+ if (xActualRedirectCode && statusCode === this.#xRedirectCode) {
414
409
  const responseMeta = _wq(response, 'meta');
415
410
  responseMeta.set('status', xActualRedirectCode); // @NOTE 1
411
+ statusCode = xActualRedirectCode;
416
412
  }
417
413
  // Trigger redirect
418
- if ([302, 301].includes(response.status)) {
414
+ if ([302, 301].includes(statusCode)) {
419
415
  const location = new URL(response.headers.get('Location'), this.location.origin);
420
416
  if (this.isSpaRoute(location)) {
421
417
  this.navigate(location, {}, { navigationType: 'rdr' });
422
418
  } else {
423
- this.redirect(location, response.wqRealtime);
419
+ this.redirect(location, LiveResponse.getBackground(response));
424
420
  }
425
421
  return true;
426
422
  }
427
423
  }
428
424
 
429
- redirect(location, responseRealtime) {
430
- if (responseRealtime) {
425
+ redirect(location, responseBackground) {
426
+ if (responseBackground) {
431
427
  // Redundant as this is a window reload anyways
432
- responseRealtime.close();
428
+ responseBackground.close();
433
429
  }
434
430
  window.location = location;
435
431
  }
@@ -470,7 +466,7 @@ export class WebfloClient extends WebfloRuntime {
470
466
  navigator: this.navigator,
471
467
  location: this.location,
472
468
  network: this.network, // request, redirect, error, status, remote
473
- capabilities: this.deviceCapabilities,
469
+ capabilities: this.capabilities,
474
470
  transition: this.transition,
475
471
  }, { diff: true, merge });
476
472
  $response.addEventListener('replace', (e) => {
@@ -2,6 +2,7 @@ import { Observer } from '@webqit/quantum-js';
2
2
  import { WebfloClient } from './WebfloClient.js';
3
3
  import { ClientSideWorkport } from './ClientSideWorkport.js';
4
4
  import { DeviceCapabilities } from './DeviceCapabilities.js';
5
+ import { LiveResponse, response as responseShim } from '../webflo-fetch/index.js';
5
6
  import { WebfloHMR } from './webflo-devmode.js';
6
7
 
7
8
  export class WebfloRootClient1 extends WebfloClient {
@@ -10,8 +11,8 @@ export class WebfloRootClient1 extends WebfloClient {
10
11
 
11
12
  static get DeviceCapabilities() { return DeviceCapabilities; }
12
13
 
13
- static create(cx, host) {
14
- return new this(this.Context.create(cx), host);
14
+ static create(bootstrap, host) {
15
+ return new this(bootstrap, host);
15
16
  }
16
17
 
17
18
  #network;
@@ -20,8 +21,8 @@ export class WebfloRootClient1 extends WebfloClient {
20
21
  #workport;
21
22
  get workport() { return this.#workport; }
22
23
 
23
- #deviceCapabilities;
24
- get deviceCapabilities() { return this.#deviceCapabilities; }
24
+ #capabilities;
25
+ get capabilities() { return this.#capabilities; }
25
26
 
26
27
  #hmr;
27
28
 
@@ -29,11 +30,11 @@ export class WebfloRootClient1 extends WebfloClient {
29
30
  return document.querySelector('meta[name="webflo:viewtransitions"]')?.value;
30
31
  }
31
32
 
32
- constructor(cx, host) {
33
+ constructor(bootstrap, host) {
33
34
  if (!(host instanceof Document)) {
34
35
  throw new Error('Argument #1 must be a Document instance');
35
36
  }
36
- super(cx, host);
37
+ super(bootstrap, host);
37
38
  this.#network = { status: window.navigator.onLine };
38
39
  }
39
40
 
@@ -87,10 +88,10 @@ export class WebfloRootClient1 extends WebfloClient {
87
88
  // Service Worker && Capabilities
88
89
  const cleanups = [];
89
90
  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;
91
+ this.#capabilities = await this.constructor.DeviceCapabilities.initialize(this, this.config.CLIENT.capabilities);
92
+ cleanups.push(() => this.#capabilities.close());
93
+ if (this.config.CLIENT.capabilities?.service_worker) {
94
+ const { filename, ...restServiceWorkerParams } = this.config.WORKER;
94
95
  this.#workport = await this.constructor.Workport.initialize(null, (this.config.CLIENT.public_base_url || '') + filename, restServiceWorkerParams);
95
96
  cleanups.push(() => this.#workport.close());
96
97
  }
@@ -101,16 +102,17 @@ export class WebfloRootClient1 extends WebfloClient {
101
102
  const instanceController = await super.hydrate();
102
103
  const scopeObj = {};
103
104
  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' } });
105
+ scopeObj.response = responseShim.from.value(scopeObj.data, { headers: { 'Content-Type': 'application/json' } });
105
106
  for (const name of ['X-Background-Messaging-Port', 'X-Live-Response-Message-ID', 'X-Webflo-Dev-Mode']) {
106
107
  const metaElement = this.host.querySelector(`meta[name="${name}"]`);
107
108
  if (!metaElement) continue;
108
109
  scopeObj.response.headers.set(name, metaElement.content?.trim() || '');
109
110
  }
110
- if (scopeObj.response.isLive()) {
111
- this.background.addPort(scopeObj.response.wqRealtime);
111
+ const backgroundPort = LiveResponse.getBackground(scopeObj.response);
112
+ if (backgroundPort) {
113
+ this.background.addPort(backgroundPort);
112
114
  }
113
- if (scopeObj.response.body || scopeObj.response.isLive()) {
115
+ if (scopeObj.response.body || backgroundPort) {
114
116
  const httpEvent = this.createHttpEvent({ request: this.createRequest(this.location.href) }, true);
115
117
  await this.render(httpEvent, scopeObj.response);
116
118
  } else {
@@ -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);
@@ -0,0 +1,37 @@
1
+ import Fs from 'fs';
2
+ import Path from 'path';
3
+ import {
4
+ readLayoutConfig,
5
+ readEnvConfig,
6
+ readClientConfig,
7
+ readWorkerConfig,
8
+ scanRoots,
9
+ scanRouteHandlers,
10
+ } from '../../deployment-pi/util.js';
11
+
12
+ export async function bootstrap(cx, offset = '') {
13
+ const $init = Fs.existsSync('./init.client.js')
14
+ ? Path.resolve('./init.client.js')
15
+ : null;
16
+ const config = {
17
+ LAYOUT: await readLayoutConfig(cx),
18
+ ENV: await readEnvConfig(cx),
19
+ CLIENT: await readClientConfig(cx),
20
+ WORKER: await readWorkerConfig(cx),
21
+ };
22
+ if (config.CLIENT.copy_public_variables) {
23
+ const publicEnvPattern = /(?:^|_)PUBLIC(?:_|$)/;
24
+ for (const key in process.env) {
25
+ if (publicEnvPattern.test(key)) {
26
+ config.ENV.data[key] = process.env[key];
27
+ }
28
+ }
29
+ }
30
+ const routes = {};
31
+ const $roots = Fs.existsSync(config.LAYOUT.PUBLIC_DIR) ? scanRoots(config.LAYOUT.PUBLIC_DIR, 'index.html') : [];
32
+ scanRouteHandlers(config.LAYOUT, 'client', (file, route) => {
33
+ routes[route] = file;
34
+ }, offset, $roots);
35
+ const outdir = Path.join(config.LAYOUT.PUBLIC_DIR, offset);
36
+ return { $init, config, routes, $roots, $sparoots: $roots, outdir, offset };
37
+ }
@@ -2,16 +2,10 @@ import { WebfloRootClient1 } from './WebfloRootClient1.js';
2
2
  import { WebfloRootClient2 } from './WebfloRootClient2.js';
3
3
  import { WebfloSubClient } from './WebfloSubClient.js';
4
4
 
5
- export async function start() {
5
+ export async function start(bootstrap) {
6
6
  const WebfloRootClient = window.navigation ? WebfloRootClient2 : WebfloRootClient1;
7
- const instance = WebfloRootClient.create(this || {}, document);
7
+ const instance = WebfloRootClient.create(bootstrap, document);
8
8
  await instance.initialize();
9
9
  WebfloSubClient.defineElement();
10
10
  return instance;
11
11
  }
12
-
13
- export {
14
- WebfloRootClient1,
15
- WebfloRootClient2,
16
- WebfloSubClient
17
- }
@@ -45,7 +45,7 @@ export class WebfloHMR {
45
45
  if (event.actionableEffect === 'unlink') {
46
46
  delete this.#app.routes[event.affectedRoute];
47
47
  } else {
48
- this.#app.routes[event.affectedRoute] = `/@dev?src=${event.affectedHandler}&t=${Date.now()}`;
48
+ this.#app.routes[event.affectedRoute] = `/@hmr?src=${event.affectedHandler}&t=${Date.now()}`;
49
49
  }
50
50
  statuses.routesAffected.add(event.affectedRoute);
51
51
  } else if (event.realm === 'worker') {
@@ -203,7 +203,7 @@ export class WebfloHMR {
203
203
  }
204
204
  const $url = node.$url;
205
205
  const url = encodeURIComponent($url.href.replace(`${$url.origin}/`, '')); // preserving origin query strings
206
- const urlRewrite = `/@dev?src=${url}&t=${Date.now()}`;
206
+ const urlRewrite = `/@hmr?src=${url}&t=${Date.now()}`;
207
207
  if (node.matches(this.#selectors.remoteStyleSheet)) {
208
208
  node.setAttribute('href', urlRewrite);
209
209
  return 1;
@@ -216,7 +216,7 @@ export class WebfloHMR {
216
216
  }
217
217
 
218
218
  async loadHTMLModule(url) {
219
- const urlRewrite = `/@dev?src=${url}?t=${Date.now()}`;
219
+ const urlRewrite = `/@hmr?src=${url}?t=${Date.now()}`;
220
220
  const fileContents = await fetch(urlRewrite).then((res) => res.text()).catch(() => null);
221
221
  if (fileContents === null) return null;
222
222
  const temp = document.createElement('template');