@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.
- package/package.json +13 -34
- package/site/docs/concepts/realtime.md +45 -44
- package/site/docs/getting-started.md +40 -40
- package/src/{Context.js → CLIContext.js} +9 -8
- package/src/build-pi/esbuild-plugin-uselive-transform.js +42 -0
- package/src/{runtime-pi/webflo-client/webflo-codegen.js → build-pi/index.js} +148 -142
- package/src/index.js +3 -1
- package/src/init-pi/index.js +7 -4
- package/src/init-pi/templates/pwa/.gitignore +6 -0
- package/src/init-pi/templates/pwa/.webqit/webflo/client.json +15 -0
- package/src/init-pi/templates/pwa/.webqit/webflo/layout.json +7 -0
- package/src/init-pi/templates/pwa/package.json +2 -2
- package/src/init-pi/templates/pwa/public/manifest.json +2 -2
- package/src/init-pi/templates/web/.gitignore +6 -0
- package/src/init-pi/templates/web/.webqit/webflo/client.json +12 -0
- package/src/init-pi/templates/web/.webqit/webflo/layout.json +7 -0
- package/src/init-pi/templates/web/package.json +2 -2
- package/src/runtime-pi/AppBootstrap.js +38 -0
- package/src/runtime-pi/WebfloRuntime.js +68 -56
- package/src/runtime-pi/apis.js +9 -0
- package/src/runtime-pi/index.js +2 -4
- package/src/runtime-pi/webflo-client/DeviceCapabilities.js +1 -1
- package/src/runtime-pi/webflo-client/WebfloClient.js +33 -36
- package/src/runtime-pi/webflo-client/WebfloRootClient1.js +23 -17
- package/src/runtime-pi/webflo-client/WebfloRootClient2.js +1 -1
- package/src/runtime-pi/webflo-client/WebfloSubClient.js +14 -14
- package/src/runtime-pi/webflo-client/bootstrap.js +38 -0
- package/src/runtime-pi/webflo-client/index.js +2 -8
- package/src/runtime-pi/webflo-client/webflo-devmode.js +3 -3
- package/src/runtime-pi/webflo-fetch/LiveResponse.js +154 -116
- package/src/runtime-pi/webflo-fetch/index.js +436 -5
- package/src/runtime-pi/webflo-messaging/wq-message-port.js +1 -1
- package/src/runtime-pi/webflo-routing/HttpCookies.js +1 -1
- package/src/runtime-pi/webflo-routing/HttpEvent.js +12 -11
- package/src/runtime-pi/webflo-routing/HttpUser.js +7 -7
- package/src/runtime-pi/webflo-routing/WebfloRouter.js +12 -7
- package/src/runtime-pi/webflo-server/ServerSideCookies.js +3 -1
- package/src/runtime-pi/webflo-server/ServerSideSession.js +2 -1
- package/src/runtime-pi/webflo-server/WebfloServer.js +138 -200
- package/src/runtime-pi/webflo-server/bootstrap.js +59 -0
- package/src/runtime-pi/webflo-server/index.js +2 -6
- package/src/runtime-pi/webflo-server/webflo-devmode.js +24 -31
- package/src/runtime-pi/webflo-url/Url.js +1 -1
- package/src/runtime-pi/webflo-url/xURL.js +1 -1
- package/src/runtime-pi/webflo-worker/WebfloWorker.js +11 -15
- package/src/runtime-pi/webflo-worker/WorkerSideCookies.js +2 -1
- package/src/runtime-pi/webflo-worker/bootstrap.js +39 -0
- package/src/runtime-pi/webflo-worker/index.js +3 -7
- package/src/webflo-cli.js +1 -2
- package/src/runtime-pi/webflo-fetch/cookies.js +0 -10
- package/src/runtime-pi/webflo-fetch/fetch.js +0 -16
- package/src/runtime-pi/webflo-fetch/formdata.js +0 -54
- package/src/runtime-pi/webflo-fetch/headers.js +0 -151
- package/src/runtime-pi/webflo-fetch/message.js +0 -49
- package/src/runtime-pi/webflo-fetch/request.js +0 -62
- 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
|
-
|
|
15
|
-
get cx() { return this.#cx; }
|
|
14
|
+
static create(bootstrap) { return new this(bootstrap); }
|
|
16
15
|
|
|
17
|
-
#
|
|
18
|
-
get config() { return this.#config; }
|
|
16
|
+
#bootstrap;
|
|
19
17
|
|
|
20
|
-
|
|
21
|
-
get
|
|
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(
|
|
24
|
-
|
|
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
|
-
|
|
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,
|
|
72
|
-
return this.constructor.HttpUser.create({ store, request, session,
|
|
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,
|
|
76
|
-
return this.constructor.HttpEvent.create(null, { request, cookies, session, user,
|
|
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,
|
|
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.
|
|
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 =
|
|
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,
|
|
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 (
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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
|
-
|
|
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('
|
|
154
|
+
responseMeta.set('background_port', clientPortB);
|
|
143
155
|
} else {
|
|
144
|
-
const
|
|
145
|
-
response.headers.set('X-Background-Messaging-Port',
|
|
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.
|
|
150
|
-
httpEvent.
|
|
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
|
-
|
|
165
|
+
LOGGER.log(`Client Messaging Port on ${httpEvent.request.url} not auto-closed on user navigation.`);
|
|
154
166
|
} else {
|
|
155
|
-
httpEvent.
|
|
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.
|
|
174
|
+
httpEvent.client.wqLifecycle.close.then(() => {
|
|
163
175
|
httpEvent.abort();
|
|
164
176
|
});
|
|
165
177
|
|
|
166
178
|
// On ROOT event complete:
|
|
167
|
-
// Close httpEvent.
|
|
179
|
+
// Close httpEvent.client
|
|
168
180
|
httpEvent.lifeCycleComplete(true).then(() => {
|
|
169
|
-
httpEvent.
|
|
181
|
+
httpEvent.client.close();
|
|
170
182
|
});
|
|
171
183
|
}
|
|
172
184
|
|
|
173
|
-
if (!this.isClientSide && response
|
|
185
|
+
if (!this.isClientSide && LiveResponse.test(response) === 'LiveResponse') {
|
|
174
186
|
// Must convert to Response on the server-side before returning
|
|
175
|
-
return response.toResponse({
|
|
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.
|
|
214
|
-
httpEvent.
|
|
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
|
|
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;
|
package/src/runtime-pi/index.js
CHANGED
|
@@ -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/
|
|
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(
|
|
47
|
-
super(
|
|
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.
|
|
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
|
|
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
|
|
279
|
+
store: this.createStorage('user'),
|
|
287
280
|
request: scopeObj.request,
|
|
288
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
361
|
-
|
|
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,
|
|
376
|
-
const response = await super.dispatchNavigationEvent({ httpEvent, crossLayerFetch,
|
|
377
|
-
// Obtain and connect
|
|
378
|
-
|
|
379
|
-
|
|
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
|
-
|
|
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 (
|
|
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,
|
|
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 &&
|
|
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(
|
|
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
|
|
420
|
+
this.redirect(location, LiveResponse.getBackground(response));
|
|
424
421
|
}
|
|
425
422
|
return true;
|
|
426
423
|
}
|
|
427
424
|
}
|
|
428
425
|
|
|
429
|
-
redirect(location,
|
|
430
|
-
if (
|
|
426
|
+
redirect(location, responseBackground) {
|
|
427
|
+
if (responseBackground) {
|
|
431
428
|
// Redundant as this is a window reload anyways
|
|
432
|
-
|
|
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.
|
|
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/
|
|
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(
|
|
14
|
-
return new this(
|
|
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
|
-
#
|
|
24
|
-
get
|
|
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(
|
|
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(
|
|
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.#
|
|
91
|
-
cleanups.push(() => this.#
|
|
92
|
-
if (this.config.CLIENT.capabilities?.service_worker
|
|
93
|
-
const {
|
|
94
|
-
this
|
|
95
|
-
|
|
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 =
|
|
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
|
-
|
|
111
|
-
|
|
114
|
+
const backgroundPort = LiveResponse.getBackground(scopeObj.response);
|
|
115
|
+
if (backgroundPort) {
|
|
116
|
+
this.background.addPort(backgroundPort);
|
|
112
117
|
}
|
|
113
|
-
if (scopeObj.response.body ||
|
|
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/
|
|
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
|
|
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.
|
|
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,
|
|
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 (
|
|
100
|
+
if (responseBackground) {
|
|
101
101
|
Observer.set(this.navigator, 'redirecting', new Url/*NOT URL*/(location), { diff: true });
|
|
102
|
-
|
|
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
|
-
|
|
112
|
+
responseBackground.close();
|
|
113
113
|
}
|
|
114
114
|
};
|
|
115
115
|
window.addEventListener('message', windowMessageHandler);
|