@webqit/webflo 0.10.5 → 0.11.2
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/README.md +1082 -323
- package/package.json +2 -2
- package/src/config-pi/runtime/Client.js +7 -10
- package/src/config-pi/runtime/client/Worker.js +30 -12
- package/src/runtime-pi/Router.js +1 -1
- package/src/runtime-pi/client/Runtime.js +98 -49
- package/src/runtime-pi/client/RuntimeClient.js +12 -40
- package/src/runtime-pi/client/Workport.js +163 -0
- package/src/runtime-pi/client/generate.js +71 -37
- package/src/runtime-pi/client/worker/Worker.js +57 -23
- package/src/runtime-pi/client/worker/Workport.js +80 -0
- package/src/runtime-pi/server/Runtime.js +22 -8
- package/src/runtime-pi/server/RuntimeClient.js +6 -6
- package/src/runtime-pi/util.js +2 -2
- package/test/site/package.json +9 -0
- package/test/site/public/bundle.html +3 -0
- package/test/site/public/bundle.html.json +1 -0
- package/test/site/public/bundle.js +1 -1
- package/test/site/public/bundle.js.gz +0 -0
- package/test/site/public/bundle.webflo.js +8 -8
- package/test/site/public/bundle.webflo.js.gz +0 -0
- package/test/site/public/index.html +5 -5
- package/test/site/public/index1.html +35 -0
- package/test/site/public/page-2/bundle.js +1 -1
- package/test/site/public/page-2/bundle.js.gz +0 -0
- package/test/site/public/page-2/index.html +3 -4
- package/test/site/public/page-3/logo-130x130.png +0 -0
- package/test/site/public/page-4/subpage/bundle.js +1 -1
- package/test/site/public/page-4/subpage/bundle.js.gz +0 -0
- package/test/site/public/sparoots.json +5 -0
- package/test/site/public/worker.js +1 -1
- package/test/site/public/worker.js.gz +0 -0
- package/test/site/server/index.js +14 -6
- package/docker/Dockerfile +0 -26
- package/docker/README.md +0 -77
- package/src/runtime-pi/client/WorkerComm.js +0 -102
|
@@ -42,28 +42,29 @@ export async function generate() {
|
|
|
42
42
|
const dirSelf = Path.dirname(Url.fileURLToPath(import.meta.url)).replace(/\\/g, '/');
|
|
43
43
|
// -----------
|
|
44
44
|
// Scan Subdocuments
|
|
45
|
-
const
|
|
46
|
-
let dir = Path.join(dirPublic,
|
|
47
|
-
return [ Fs.readdirSync(dir).reduce((
|
|
45
|
+
const scanSubroots = (sparoot, rootFileName) => {
|
|
46
|
+
let dir = Path.join(dirPublic, sparoot), passes = 0;
|
|
47
|
+
return [ Fs.readdirSync(dir).reduce((sparoots, f) => {
|
|
48
48
|
let resource = Path.join(dir, f);
|
|
49
49
|
if (Fs.statSync(resource).isDirectory()) {
|
|
50
|
-
let
|
|
51
|
-
if (Fs.existsSync(Path.join(resource,
|
|
52
|
-
return
|
|
50
|
+
let subsparoot = Path.join(sparoot, f);
|
|
51
|
+
if (Fs.existsSync(Path.join(resource, rootFileName))) {
|
|
52
|
+
return sparoots.concat(subsparoot);
|
|
53
53
|
}
|
|
54
54
|
passes ++;
|
|
55
|
-
return
|
|
55
|
+
return sparoots.concat(scanSubroots(subsparoot, rootFileName)[ 0 ]);
|
|
56
56
|
}
|
|
57
|
-
return
|
|
57
|
+
return sparoots;
|
|
58
58
|
}, []), passes ];
|
|
59
59
|
};
|
|
60
60
|
// -----------
|
|
61
61
|
// Generate client build
|
|
62
|
-
const generateClient = async function(
|
|
63
|
-
let [
|
|
64
|
-
|
|
65
|
-
let
|
|
66
|
-
let
|
|
62
|
+
const generateClient = async function(sparoot, spaGraphCallback = null) {
|
|
63
|
+
let [ subsparoots, targets ] = (sparoot && scanSubroots(sparoot, 'index.html')) || [ [], false ];
|
|
64
|
+
if (!sparoot) sparoot = '/';
|
|
65
|
+
let spaRouting = { root: sparoot, subroots: subsparoots, targets };
|
|
66
|
+
let codeSplitting = !!(sparoot !== '/' || subsparoots.length);
|
|
67
|
+
let outfileMain = Path.join(sparoot, clientConfig.bundle_filename),
|
|
67
68
|
outfileWebflo = _beforeLast(clientConfig.bundle_filename, '.js') + '.webflo.js';
|
|
68
69
|
let gen = { imports: {}, code: [], };
|
|
69
70
|
// ------------------
|
|
@@ -88,7 +89,7 @@ export async function generate() {
|
|
|
88
89
|
// ------------------
|
|
89
90
|
if (!codeSplitting) {
|
|
90
91
|
initWebflo(gen);
|
|
91
|
-
} else if (
|
|
92
|
+
} else if (sparoot === '/') {
|
|
92
93
|
if (cx.logger) {
|
|
93
94
|
cx.logger.log(cx.logger.style.keyword(`-----------------`));
|
|
94
95
|
cx.logger.log(`Base Build`);
|
|
@@ -100,22 +101,22 @@ export async function generate() {
|
|
|
100
101
|
// ------------------
|
|
101
102
|
if (cx.logger) {
|
|
102
103
|
cx.logger.log(cx.logger.style.keyword(`-----------------`));
|
|
103
|
-
cx.logger.log(`Client Build ` + cx.logger.style.comment(`(
|
|
104
|
+
cx.logger.log(`Client Build ` + cx.logger.style.comment(`(sparoot:${sparoot}; is-split:${codeSplitting})`));
|
|
104
105
|
cx.logger.log(cx.logger.style.keyword(`-----------------`));
|
|
105
106
|
}
|
|
106
107
|
gen.code.push(`const { start } = WebQit.Webflo`);
|
|
107
108
|
// ------------------
|
|
108
109
|
// Bundle
|
|
109
|
-
declareStart.call(cx, gen, dirClient, dirPublic, clientConfig,
|
|
110
|
+
declareStart.call(cx, gen, dirClient, dirPublic, clientConfig, spaRouting);
|
|
110
111
|
await bundle.call(cx, gen, Path.join(dirPublic, outfileMain), true/* asModule */);
|
|
111
112
|
// ------------------
|
|
112
113
|
// Embed/unembed
|
|
113
|
-
let targetDocumentFile = Path.join(dirPublic,
|
|
114
|
+
let targetDocumentFile = Path.join(dirPublic, sparoot, 'index.html'),
|
|
114
115
|
outfileWebfloPublic = Path.join(clientConfig.public_base_url, outfileWebflo),
|
|
115
116
|
outfileMainPublic = Path.join(clientConfig.public_base_url, outfileMain),
|
|
116
117
|
embedList = [],
|
|
117
118
|
unembedList = [];
|
|
118
|
-
if (cx.flags['auto-
|
|
119
|
+
if (cx.flags['auto-embed']) {
|
|
119
120
|
if (codeSplitting) {
|
|
120
121
|
embedList.push(outfileWebfloPublic);
|
|
121
122
|
} else {
|
|
@@ -128,21 +129,23 @@ export async function generate() {
|
|
|
128
129
|
handleEmbeds(targetDocumentFile, embedList, unembedList);
|
|
129
130
|
// ------------------
|
|
130
131
|
// Recurse
|
|
132
|
+
spaGraphCallback && spaGraphCallback(sparoot, subsparoots);
|
|
131
133
|
if (cx.flags.recursive) {
|
|
132
|
-
while (
|
|
133
|
-
await generateClient(
|
|
134
|
+
while (subsparoots.length) {
|
|
135
|
+
await generateClient(subsparoots.shift(), spaGraphCallback);
|
|
134
136
|
}
|
|
135
137
|
}
|
|
136
138
|
};
|
|
137
139
|
// -----------
|
|
138
140
|
// Generate worker build
|
|
139
|
-
const generateWorker = async function(
|
|
140
|
-
let
|
|
141
|
-
|
|
141
|
+
const generateWorker = async function(workerroot, workerGraphCallbak = null) {
|
|
142
|
+
let [ subworkerroots, targets ] = workerroot && scanSubroots(workerroot, 'workerroot') || [ [], false ];
|
|
143
|
+
if (!workerroot) workerroot = '/';
|
|
144
|
+
let workerRouting = { root: workerroot, subroots: subworkerroots, targets };
|
|
142
145
|
let gen = { imports: {}, code: [], };
|
|
143
146
|
if (cx.logger) {
|
|
144
147
|
cx.logger.log(cx.logger.style.comment(`-----------------`));
|
|
145
|
-
cx.logger.log(`Worker Build -
|
|
148
|
+
cx.logger.log(`Worker Build - workerroot:${workerroot}`);
|
|
146
149
|
cx.logger.log(cx.logger.style.comment(`-----------------`));
|
|
147
150
|
}
|
|
148
151
|
// ------------------
|
|
@@ -152,22 +155,53 @@ export async function generate() {
|
|
|
152
155
|
// ------------------
|
|
153
156
|
// Bundle
|
|
154
157
|
if (workerConfig.cache_only_urls.length) {
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
158
|
+
// Separate URLs from patterns
|
|
159
|
+
let [ urls, patterns ] = workerConfig.cache_only_urls.reduce(([ urls, patterns ], url) => {
|
|
160
|
+
let patternInstance = urlPattern(url, 'http://localhost'),
|
|
161
|
+
isPattern = patternInstance.isPattern();
|
|
162
|
+
if (isPattern && (patternInstance.pattern.pattern.hostname !== 'localhost' || patternInstance.pattern.pattern.port)) {
|
|
163
|
+
throw new Error(`Pattern URLs must have no origin part. Recieved "${url}".`);
|
|
164
|
+
}
|
|
165
|
+
return isPattern ? [ urls, patterns.concat(patternInstance) ] : [ urls.concat(url), patterns ];
|
|
166
|
+
}, [ [], [] ]);
|
|
167
|
+
// Resolve patterns
|
|
168
|
+
if (patterns.length) {
|
|
169
|
+
// List all files
|
|
170
|
+
let scan = dir => Fs.readdirSync(dir).reduce((result, f) => {
|
|
171
|
+
let resource = Path.join(dir, f);
|
|
172
|
+
return result.concat(Fs.statSync(resource).isDirectory() ? scan(resource) : '/' + Path.relative(dirPublic, resource));
|
|
173
|
+
}, []);
|
|
174
|
+
let files = scan(dirPublic);
|
|
175
|
+
// Resolve patterns from files
|
|
176
|
+
workerConfig.cache_only_urls = patterns.reduce((all, pattern) => {
|
|
177
|
+
let matchedFiles = files.filter(file => pattern.test(file, 'http://localhost'));
|
|
178
|
+
if (matchedFiles.length) return all.concat(matchedFiles);
|
|
179
|
+
throw new Error(`The pattern "${pattern.pattern.pattern.pathname}" didn't match any files.`);
|
|
180
|
+
}, urls);
|
|
181
|
+
}
|
|
159
182
|
}
|
|
160
|
-
declareStart.call(cx, gen, dirWorker, dirPublic, workerConfig,
|
|
161
|
-
await bundle.call(cx, gen, Path.join(dirPublic,
|
|
183
|
+
declareStart.call(cx, gen, dirWorker, dirPublic, workerConfig, workerRouting);
|
|
184
|
+
await bundle.call(cx, gen, Path.join(dirPublic, workerroot, clientConfig.worker_filename));
|
|
185
|
+
// ------------------
|
|
186
|
+
// Recurse
|
|
187
|
+
workerGraphCallbak && workerGraphCallbak(workerroot, subworkerroots);
|
|
162
188
|
if (cx.flags.recursive) {
|
|
163
|
-
while (
|
|
164
|
-
await generateWorker(
|
|
189
|
+
while (subworkerroots.length) {
|
|
190
|
+
await generateWorker(subworkerroots.shift());
|
|
165
191
|
}
|
|
166
192
|
}
|
|
167
193
|
};
|
|
168
194
|
// -----------
|
|
169
195
|
// Generate now...
|
|
170
|
-
|
|
196
|
+
let sparootsFile = Path.join(dirPublic, 'sparoots.json');
|
|
197
|
+
if (clientConfig.spa_routing !== false) {
|
|
198
|
+
const sparoots = [];
|
|
199
|
+
await generateClient('/', root => sparoots.push(root));
|
|
200
|
+
Fs.writeFileSync(sparootsFile, JSON.stringify(sparoots, null, 4));
|
|
201
|
+
} else {
|
|
202
|
+
await generateClient();
|
|
203
|
+
Fs.existsSync(sparootsFile) && Fs.unlinkSync(sparootsFile);
|
|
204
|
+
}
|
|
171
205
|
if (clientConfig.service_worker_support) {
|
|
172
206
|
await generateWorker('/');
|
|
173
207
|
}
|
|
@@ -215,8 +249,8 @@ function declareStart(gen, routesDir, targetDir, paramsObj, routing) {
|
|
|
215
249
|
*/
|
|
216
250
|
function declareRoutesObj(gen, routesDir, targetDir, varName, routing) {
|
|
217
251
|
const cx = this || {};
|
|
218
|
-
let _routesDir = Path.join(routesDir, routing.
|
|
219
|
-
_targetDir = Path.join(targetDir, routing.
|
|
252
|
+
let _routesDir = Path.join(routesDir, routing.root),
|
|
253
|
+
_targetDir = Path.join(targetDir, routing.root);
|
|
220
254
|
cx.logger && cx.logger.log(cx.logger.style.keyword(`> `) + `Declaring routes...`);
|
|
221
255
|
// ----------------
|
|
222
256
|
// Directory walker
|
|
@@ -225,7 +259,7 @@ function declareRoutesObj(gen, routesDir, targetDir, varName, routing) {
|
|
|
225
259
|
let resource = Path.join(dir, f);
|
|
226
260
|
let namespace = _beforeLast('/' + Path.relative(routesDir, resource), '/index.js') || '/';
|
|
227
261
|
if (Fs.statSync(resource).isDirectory()) {
|
|
228
|
-
if (routing.
|
|
262
|
+
if (routing.subroots.includes(namespace)) return;
|
|
229
263
|
walk(resource, callback);
|
|
230
264
|
} else {
|
|
231
265
|
let relativePath = Path.relative(_targetDir, resource);
|
|
@@ -368,7 +402,7 @@ async function bundle(gen, outfile, asModule = false) {
|
|
|
368
402
|
}
|
|
369
403
|
// Remove moduleFile build
|
|
370
404
|
Fs.unlinkSync(bundlingConfig.entryPoints[0]);
|
|
371
|
-
removals.forEach(file => Fs.unlinkSync(file));
|
|
405
|
+
removals.forEach(file => Fs.existsSync(file) && Fs.unlinkSync(file));
|
|
372
406
|
if (waiting) waiting.stop();
|
|
373
407
|
// ----------------
|
|
374
408
|
// Stats
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import { _any } from '@webqit/util/arr/index.js';
|
|
6
6
|
import { HttpEvent, Request, Response, Observer } from '../Runtime.js';
|
|
7
7
|
import { urlPattern } from '../../util.js';
|
|
8
|
+
import Workport from './Workport.js';
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* ---------------------------
|
|
@@ -71,11 +72,13 @@ export default class Worker {
|
|
|
71
72
|
});
|
|
72
73
|
|
|
73
74
|
// -------------
|
|
74
|
-
// ONFETCH
|
|
75
|
+
// ONFETCH
|
|
75
76
|
self.addEventListener('fetch', event => {
|
|
76
77
|
// URL schemes that might arrive here but not supported; e.g.: chrome-extension://
|
|
77
78
|
if (!event.request.url.startsWith('http')) return;
|
|
78
79
|
event.respondWith((async (req, evt) => {
|
|
80
|
+
let requestingClient = await self.clients.get(event.clientId);
|
|
81
|
+
this.workport.setCurrentClient(requestingClient);
|
|
79
82
|
const requestInit = [
|
|
80
83
|
'method', 'headers', 'mode', 'credentials', 'cache', 'redirect', 'referrer', 'integrity',
|
|
81
84
|
].reduce((init, prop) => ({ [prop]: req[prop], ...init }), {});
|
|
@@ -93,6 +96,33 @@ export default class Worker {
|
|
|
93
96
|
})(event.request, event));
|
|
94
97
|
});
|
|
95
98
|
|
|
99
|
+
// -------------
|
|
100
|
+
// Workport
|
|
101
|
+
let workport = new Workport();
|
|
102
|
+
Observer.set(this, 'workport', workport);
|
|
103
|
+
workport.messaging.listen(async evt => {
|
|
104
|
+
let responsePort = evt.ports[0];
|
|
105
|
+
let client = this.clients.get('*');
|
|
106
|
+
let response = client.alert && await client.alert(evt);
|
|
107
|
+
if (responsePort) {
|
|
108
|
+
if (response instanceof Promise) {
|
|
109
|
+
response.then(data => {
|
|
110
|
+
responsePort.postMessage(data);
|
|
111
|
+
});
|
|
112
|
+
} else {
|
|
113
|
+
responsePort.postMessage(response);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
workport.notifications.listen(async evt => {
|
|
118
|
+
let client = this.clients.get('*');
|
|
119
|
+
client.alert && await client.alert(evt);
|
|
120
|
+
});
|
|
121
|
+
workport.push.listen(async evt => {
|
|
122
|
+
let client = this.clients.get('*');
|
|
123
|
+
client.alert && await client.alert(evt);
|
|
124
|
+
});
|
|
125
|
+
|
|
96
126
|
// ---------------
|
|
97
127
|
Observer.set(this, 'location', {});
|
|
98
128
|
Observer.set(this, 'network', {});
|
|
@@ -163,22 +193,26 @@ export default class Worker {
|
|
|
163
193
|
}
|
|
164
194
|
const matchUrl = (patterns, url) => _any((patterns || []).map(p => p.trim()).filter(p => p), p => urlPattern(p, self.origin).test(url));
|
|
165
195
|
const execFetch = () => {
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
196
|
+
// network_first_urls
|
|
197
|
+
if (!this.cx.params.default_fetching_strategy || this.cx.params.default_fetching_strategy === 'network-first' || matchUrl(this.cx.params.network_first_urls, request.url)) {
|
|
198
|
+
Observer.set(this.network, 'strategy', 'network-first');
|
|
199
|
+
return this.networkFetch(request, { cacheFallback: true, cacheRefresh: true });
|
|
200
|
+
}
|
|
201
|
+
// cache_first_urls
|
|
202
|
+
if (this.cx.params.default_fetching_strategy === 'cache-first' || matchUrl(this.cx.params.cache_first_urls, request.url)) {
|
|
203
|
+
Observer.set(this.network, 'strategy', 'cache-first');
|
|
204
|
+
return this.cacheFetch(request, { networkFallback: true, cacheRefresh: true });
|
|
169
205
|
}
|
|
170
206
|
// network_only_urls
|
|
171
|
-
if (matchUrl(this.cx.params.network_only_urls, request.url)) {
|
|
207
|
+
if (this.cx.params.default_fetching_strategy === 'network-only' || matchUrl(this.cx.params.network_only_urls, request.url)) {
|
|
172
208
|
Observer.set(this.network, 'strategy', 'network-only');
|
|
173
209
|
return this.networkFetch(request, { cacheFallback: false, cacheRefresh: false });
|
|
174
210
|
}
|
|
175
|
-
//
|
|
176
|
-
if (matchUrl(this.cx.params.
|
|
177
|
-
Observer.set(this.network, 'strategy', 'cache-
|
|
178
|
-
return this.cacheFetch(request, { networkFallback:
|
|
211
|
+
// cache_only_urls
|
|
212
|
+
if (this.cx.params.default_fetching_strategy === 'cache-only' || matchUrl(this.cx.params.cache_only_urls, request.url)) {
|
|
213
|
+
Observer.set(this.network, 'strategy', 'cache-only');
|
|
214
|
+
return this.cacheFetch(request, { networkFallback: false, cacheRefresh: false });
|
|
179
215
|
}
|
|
180
|
-
Observer.set(this.network, 'strategy', 'network-first');
|
|
181
|
-
return this.networkFetch(request, { cacheFallback: true, cacheRefresh: true });
|
|
182
216
|
};
|
|
183
217
|
let response = execFetch(request);
|
|
184
218
|
// This catch() is NOT intended to handle failure of the fetch
|
|
@@ -187,18 +221,6 @@ export default class Worker {
|
|
|
187
221
|
return response.then(_response => Response.compat(_response));
|
|
188
222
|
}
|
|
189
223
|
|
|
190
|
-
// Caching strategy: cache_first
|
|
191
|
-
cacheFetch(request, params = {}) {
|
|
192
|
-
return this.getRequestCache(request).then(cache => cache.match(request).then(response => {
|
|
193
|
-
// Nothing cache, use network
|
|
194
|
-
if (!response && params.networkFallback) return this.networkFetch(request, { ...params, cacheFallback: false });
|
|
195
|
-
// Note: fetch, but for refreshing purposes only... not the returned response
|
|
196
|
-
if (response && params.cacheRefresh) this.networkFetch(request, { ...params, justRefreshing: true });
|
|
197
|
-
Observer.set(this.network, 'cache', true);
|
|
198
|
-
return response;
|
|
199
|
-
}));
|
|
200
|
-
}
|
|
201
|
-
|
|
202
224
|
// Caching strategy: network_first
|
|
203
225
|
networkFetch(request, params = {}) {
|
|
204
226
|
if (!params.cacheFallback) {
|
|
@@ -215,6 +237,18 @@ export default class Worker {
|
|
|
215
237
|
}));
|
|
216
238
|
}
|
|
217
239
|
|
|
240
|
+
// Caching strategy: cache_first
|
|
241
|
+
cacheFetch(request, params = {}) {
|
|
242
|
+
return this.getRequestCache(request).then(cache => cache.match(request).then(response => {
|
|
243
|
+
// Nothing cache, use network
|
|
244
|
+
if (!response && params.networkFallback) return this.networkFetch(request, { ...params, cacheFallback: false });
|
|
245
|
+
// Note: fetch, but for refreshing purposes only... not the returned response
|
|
246
|
+
if (response && params.cacheRefresh) this.networkFetch(request, { ...params, justRefreshing: true });
|
|
247
|
+
Observer.set(this.network, 'cache', true);
|
|
248
|
+
return response;
|
|
249
|
+
}));
|
|
250
|
+
}
|
|
251
|
+
|
|
218
252
|
// Caches response
|
|
219
253
|
refreshCache(request, response) {
|
|
220
254
|
// Check if we received a valid response
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
|
|
2
|
+
export default class Workport {
|
|
3
|
+
|
|
4
|
+
constructor() {
|
|
5
|
+
// --------
|
|
6
|
+
// Post messaging
|
|
7
|
+
// --------
|
|
8
|
+
this.messaging = {
|
|
9
|
+
post: (message, client = this.client) => {
|
|
10
|
+
if (!client) throw new Error(`No client for this operation.`);
|
|
11
|
+
client.postMessage(message);
|
|
12
|
+
return this.post;
|
|
13
|
+
},
|
|
14
|
+
listen: (callback, client = this.client) => {
|
|
15
|
+
if (!client) {
|
|
16
|
+
self.addEventListener('message', evt => {
|
|
17
|
+
this.client = evt.source;
|
|
18
|
+
callback(evt);
|
|
19
|
+
});
|
|
20
|
+
return this.post;
|
|
21
|
+
}
|
|
22
|
+
client.addEventListener('message', callback);
|
|
23
|
+
return this.post;
|
|
24
|
+
},
|
|
25
|
+
request: (message, client = this.client) => {
|
|
26
|
+
if (!client) throw new Error(`No client for this operation.`);
|
|
27
|
+
return new Promise(res => {
|
|
28
|
+
let messageChannel = new MessageChannel();
|
|
29
|
+
client.postMessage(message, [ messageChannel.port2 ]);
|
|
30
|
+
messageChannel.port1.onmessage = e => res(e.data);
|
|
31
|
+
});
|
|
32
|
+
},
|
|
33
|
+
channel(channelId) {
|
|
34
|
+
if (!this.channels.has(channelId)) { this.channels.set(channelId, new BroadcastChannel(channel)); }
|
|
35
|
+
let channel = this.channels.get(channelId);
|
|
36
|
+
return {
|
|
37
|
+
broadcast: message => channel.postMessage(message),
|
|
38
|
+
listen: callback => channel.addEventListener('message', callback),
|
|
39
|
+
};
|
|
40
|
+
},
|
|
41
|
+
channels: new Map,
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
// --------
|
|
45
|
+
// Notifications
|
|
46
|
+
// --------
|
|
47
|
+
this.notifications = {
|
|
48
|
+
fire: (title, params = {}) => {
|
|
49
|
+
return new Promise((res, rej) => {
|
|
50
|
+
if (!(self.Notification && self.Notification.permission === 'granted')) {
|
|
51
|
+
return rej(self.Notification && self.Notification.permission);
|
|
52
|
+
}
|
|
53
|
+
notification.addEventListener('error', rej);
|
|
54
|
+
let notification = new self.Notification(title, params);
|
|
55
|
+
notification.addEventListener('click', res);
|
|
56
|
+
notification.addEventListener('close', res);
|
|
57
|
+
});
|
|
58
|
+
},
|
|
59
|
+
listen: callback => {
|
|
60
|
+
self.addEventListener('notificationclick', callback);
|
|
61
|
+
return this.notifications;
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
// --------
|
|
66
|
+
// Push Notifications
|
|
67
|
+
// --------
|
|
68
|
+
this.push = {
|
|
69
|
+
listen: callback => {
|
|
70
|
+
self.addEventListener('push', callback);
|
|
71
|
+
return this.post;
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
setCurrentClient(client) {
|
|
77
|
+
this.client = client;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
}
|
|
@@ -307,19 +307,25 @@ export default class Runtime {
|
|
|
307
307
|
return this.getSession(_context, httpEvent, id, options, callback);
|
|
308
308
|
});
|
|
309
309
|
// Response
|
|
310
|
-
let client = this.clients.get('*');
|
|
310
|
+
let client = this.clients.get('*'), response, finalResponse;
|
|
311
311
|
if (this.cx.server.shared) {
|
|
312
312
|
client = this.clients.get(url.hostname);
|
|
313
313
|
}
|
|
314
|
-
|
|
315
|
-
|
|
314
|
+
try {
|
|
315
|
+
response = await client.handle(httpEvent, ( ...args ) => this.remoteFetch( ...args ));
|
|
316
|
+
finalResponse = await this.handleResponse(_context, httpEvent, response, autoHeaders.filter(header => header.type === 'response'));
|
|
317
|
+
} catch(e) {
|
|
318
|
+
finalResponse = new Response(null, { status: 500, statusText: e.message });
|
|
319
|
+
console.error(e);
|
|
320
|
+
}
|
|
316
321
|
// Logging
|
|
317
322
|
if (this.cx.logger) {
|
|
318
323
|
let log = this.generateLog(httpEvent, finalResponse);
|
|
319
324
|
this.cx.logger.log(log);
|
|
320
325
|
}
|
|
326
|
+
// ------------
|
|
321
327
|
// Return value
|
|
322
|
-
|
|
328
|
+
return finalResponse;
|
|
323
329
|
}
|
|
324
330
|
|
|
325
331
|
// Generates request object
|
|
@@ -378,7 +384,7 @@ export default class Runtime {
|
|
|
378
384
|
}
|
|
379
385
|
|
|
380
386
|
// Handles response object
|
|
381
|
-
async handleResponse(e, response, autoHeaders = []) {
|
|
387
|
+
async handleResponse(cx, e, response, autoHeaders = []) {
|
|
382
388
|
if (!(response instanceof Response)) { response = new Response(response); }
|
|
383
389
|
Observer.set(this.network, 'remote', false);
|
|
384
390
|
Observer.set(this.network, 'error', null);
|
|
@@ -401,14 +407,22 @@ export default class Runtime {
|
|
|
401
407
|
if (response.headers.redirect) {
|
|
402
408
|
let xRedirectPolicy = e.request.headers.get('X-Redirect-Policy');
|
|
403
409
|
let xRedirectCode = e.request.headers.get('X-Redirect-Code') || 300;
|
|
404
|
-
let
|
|
405
|
-
|
|
406
|
-
|
|
410
|
+
let destinationUrl = new whatwag.URL(response.headers.location, e.url.origin);
|
|
411
|
+
let isSameOriginRedirect = destinationUrl.origin === e.url.origin;
|
|
412
|
+
let isSameSpaRedirect, sparootsFile = Path.join(cx.CWD, cx.layout.PUBLIC_DIR, 'sparoots.json');
|
|
413
|
+
if (isSameOriginRedirect && xRedirectPolicy === 'manual-when-cross-spa' && Fs.existsSync(sparootsFile)) {
|
|
414
|
+
// Longest-first sorting
|
|
415
|
+
let sparoots = _arrFrom(JSON.parse(Fs.readFileSync(sparootsFile))).sort((a, b) => a.length > b.length ? -1 : 1);
|
|
416
|
+
let matchRoot = path => sparoots.reduce((prev, root) => prev || (`${path}/`.startsWith(`${root}/`) && root), null);
|
|
417
|
+
isSameSpaRedirect = matchRoot(destinationUrl.pathname) === matchRoot(e.url.pathname);
|
|
418
|
+
}
|
|
419
|
+
if (xRedirectPolicy === 'manual' || (!isSameOriginRedirect && xRedirectPolicy === 'manual-when-cross-origin') || (!isSameSpaRedirect && xRedirectPolicy === 'manual-when-cross-spa')) {
|
|
407
420
|
response.headers.json({
|
|
408
421
|
'X-Redirect-Code': response.status,
|
|
409
422
|
'Access-Control-Allow-Origin': '*',
|
|
410
423
|
'Cache-Control': 'no-store',
|
|
411
424
|
});
|
|
425
|
+
response.attrs.status = xRedirectCode;
|
|
412
426
|
}
|
|
413
427
|
return response;
|
|
414
428
|
}
|
|
@@ -79,10 +79,10 @@ export default class RuntimeClient {
|
|
|
79
79
|
pathnameSplit.pop();
|
|
80
80
|
}
|
|
81
81
|
const instanceParams = QueryString.stringify({
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
82
|
+
file: renderFile,
|
|
83
|
+
url: httpEvent.url.href,
|
|
84
|
+
root: this.cx.CWD,
|
|
85
|
+
oohtml_level: this.cx.server.oohtml_support,
|
|
86
86
|
});
|
|
87
87
|
const { window } = await import('@webqit/oohtml-ssr/instance.js?' + instanceParams);
|
|
88
88
|
// --------
|
|
@@ -99,10 +99,10 @@ export default class RuntimeClient {
|
|
|
99
99
|
env: 'server',
|
|
100
100
|
}, { update: true });
|
|
101
101
|
}
|
|
102
|
-
window.document.setState({
|
|
102
|
+
window.document.setState({ data, url: httpEvent.url }, { update: 'merge' });
|
|
103
103
|
}
|
|
104
104
|
if (window.document.templates) {
|
|
105
|
-
window.document.body.setAttribute('template', '
|
|
105
|
+
window.document.body.setAttribute('template', 'routes/' + httpEvent.url.pathname.split('/').filter(a => a).map(a => a + '+-').join('/'));
|
|
106
106
|
}
|
|
107
107
|
await new Promise(res => setTimeout(res, 10));
|
|
108
108
|
return window;
|
package/src/runtime-pi/util.js
CHANGED
|
@@ -135,9 +135,9 @@ export const path = {
|
|
|
135
135
|
export const urlPattern = (pattern, baseUrl = null) => ({
|
|
136
136
|
pattern: new URLPattern(pattern, baseUrl),
|
|
137
137
|
isPattern() {
|
|
138
|
-
return Object.keys(this.pattern.keys).some(compName => this.pattern.keys[compName].length);
|
|
138
|
+
return Object.keys(this.pattern.keys || {}).some(compName => this.pattern.keys[compName].length);
|
|
139
139
|
},
|
|
140
|
-
test(...args) { this.pattern.test(...args) },
|
|
140
|
+
test(...args) { return this.pattern.test(...args) },
|
|
141
141
|
exec(...args) {
|
|
142
142
|
let components = this.pattern.exec(...args);
|
|
143
143
|
if (!components) return;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"title": "Webflo Test",
|
|
3
|
+
"type": "module",
|
|
4
|
+
"scripts": {
|
|
5
|
+
"generate": "webflo generate::client --recursive --compression --auto-embed",
|
|
6
|
+
"bundle": "cd public && oohtml bundle --recursive --auto-embed=routes",
|
|
7
|
+
"build": "npm run generate && npm run bundle"
|
|
8
|
+
}
|
|
9
|
+
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/** @webqit/webflo */
|
|
2
|
-
var{start:e}=WebQit.Webflo,
|
|
2
|
+
var{start:e}=WebQit.Webflo,r={},o={bundle_filename:"bundle.js",public_base_url:"/",spa_routing:!0,oohtml_support:"full",service_worker_support:!0,worker_scope:"/",worker_filename:"worker.js",routing:{root:"/",subroots:["/page-2","/page-4/subpage"],targets:2}};e.call({layout:r,params:o});
|
|
Binary file
|