@webqit/webflo 0.11.61-0 → 1.0.0
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/.gitignore +7 -7
- package/LICENSE +20 -20
- package/README.md +2079 -2074
- package/docker/Dockerfile +42 -42
- package/docker/README.md +91 -91
- package/docker/package.json +2 -2
- package/package.json +80 -81
- package/src/{Context.js → AbstractContext.js} +71 -79
- package/src/config-pi/deployment/Env.js +68 -68
- package/src/config-pi/deployment/Layout.js +63 -63
- package/src/config-pi/deployment/Origins.js +139 -139
- package/src/config-pi/deployment/Proxy.js +74 -74
- package/src/config-pi/deployment/index.js +17 -17
- package/src/config-pi/index.js +15 -15
- package/src/config-pi/runtime/Client.js +116 -98
- package/src/config-pi/runtime/Server.js +125 -125
- package/src/config-pi/runtime/client/Worker.js +109 -134
- package/src/config-pi/runtime/client/index.js +11 -11
- package/src/config-pi/runtime/index.js +17 -17
- package/src/config-pi/runtime/server/Headers.js +74 -74
- package/src/config-pi/runtime/server/Redirects.js +69 -69
- package/src/config-pi/runtime/server/index.js +13 -13
- package/src/config-pi/static/Manifest.js +319 -319
- package/src/config-pi/static/Ssg.js +49 -49
- package/src/config-pi/static/index.js +13 -13
- package/src/deployment-pi/index.js +10 -10
- package/src/deployment-pi/origins/index.js +216 -216
- package/src/index.js +11 -19
- package/src/runtime-pi/HttpEvent.js +126 -106
- package/src/runtime-pi/HttpUser.js +126 -0
- package/src/runtime-pi/MessagingOverBroadcast.js +9 -0
- package/src/runtime-pi/MessagingOverChannel.js +85 -0
- package/src/runtime-pi/MessagingOverSocket.js +106 -0
- package/src/runtime-pi/MultiportMessagingAPI.js +81 -0
- package/src/runtime-pi/WebfloCookieStorage.js +27 -0
- package/src/runtime-pi/WebfloEventTarget.js +39 -0
- package/src/runtime-pi/WebfloMessageEvent.js +58 -0
- package/src/runtime-pi/WebfloMessagingAPI.js +69 -0
- package/src/runtime-pi/{Router.js → WebfloRouter.js} +99 -130
- package/src/runtime-pi/WebfloRuntime.js +52 -0
- package/src/runtime-pi/WebfloStorage.js +109 -0
- package/src/runtime-pi/client/ClientMessaging.js +5 -0
- package/src/runtime-pi/client/Context.js +3 -7
- package/src/runtime-pi/client/CookieStorage.js +17 -0
- package/src/runtime-pi/client/Router.js +38 -48
- package/src/runtime-pi/client/SessionStorage.js +33 -0
- package/src/runtime-pi/client/Url.js +156 -205
- package/src/runtime-pi/client/WebfloClient.js +544 -0
- package/src/runtime-pi/client/WebfloRootClient1.js +179 -0
- package/src/runtime-pi/client/WebfloRootClient2.js +109 -0
- package/src/runtime-pi/client/WebfloSubClient.js +165 -0
- package/src/runtime-pi/client/Workport.js +118 -178
- package/src/runtime-pi/client/generate.js +480 -471
- package/src/runtime-pi/client/index.js +16 -21
- package/src/runtime-pi/client/worker/ClientMessaging.js +5 -0
- package/src/runtime-pi/client/worker/Context.js +3 -7
- package/src/runtime-pi/client/worker/CookieStorage.js +17 -0
- package/src/runtime-pi/client/worker/SessionStorage.js +13 -0
- package/src/runtime-pi/client/worker/WebfloWorker.js +294 -0
- package/src/runtime-pi/client/worker/Workport.js +17 -85
- package/src/runtime-pi/client/worker/index.js +10 -21
- package/src/runtime-pi/index.js +6 -13
- package/src/runtime-pi/server/ClientMessaging.js +18 -0
- package/src/runtime-pi/server/ClientMessagingRegistry.js +57 -0
- package/src/runtime-pi/server/Context.js +11 -15
- package/src/runtime-pi/server/CookieStorage.js +17 -0
- package/src/runtime-pi/server/Router.js +93 -159
- package/src/runtime-pi/server/SessionStorage.js +53 -0
- package/src/runtime-pi/server/WebfloServer.js +755 -0
- package/src/runtime-pi/server/index.js +10 -21
- package/src/runtime-pi/util-http.js +322 -86
- package/src/runtime-pi/util-url.js +146 -146
- package/src/runtime-pi/xURL.js +108 -105
- package/src/runtime-pi/xfetch.js +22 -22
- package/src/services-pi/cert/http-auth-hook.js +22 -22
- package/src/services-pi/cert/http-cleanup-hook.js +22 -22
- package/src/services-pi/cert/index.js +79 -79
- package/src/services-pi/index.js +8 -8
- package/src/static-pi/index.js +10 -10
- package/src/webflo.js +30 -30
- package/test/index.test.js +26 -26
- package/test/site/package.json +9 -9
- package/test/site/public/bundle.html +5 -5
- package/test/site/public/bundle.html.json +3 -3
- package/test/site/public/bundle.js +2 -2
- package/test/site/public/bundle.webflo.js +15 -15
- package/test/site/public/index.html +29 -29
- package/test/site/public/index1.html +34 -34
- package/test/site/public/page-2/bundle.html +4 -4
- package/test/site/public/page-2/bundle.js +2 -2
- package/test/site/public/page-2/index.html +45 -45
- package/test/site/public/page-2/main.html +2 -2
- package/test/site/public/page-4/subpage/bundle.js +2 -2
- package/test/site/public/page-4/subpage/index.html +30 -30
- package/test/site/public/sparoots.json +4 -4
- package/test/site/public/worker.js +3 -3
- package/test/site/server/index.js +15 -15
- package/src/runtime-pi/Application.js +0 -29
- package/src/runtime-pi/Cookies.js +0 -82
- package/src/runtime-pi/Runtime.js +0 -21
- package/src/runtime-pi/client/Application.js +0 -100
- package/src/runtime-pi/client/Runtime.js +0 -332
- package/src/runtime-pi/client/createStorage.js +0 -57
- package/src/runtime-pi/client/oohtml/full.js +0 -7
- package/src/runtime-pi/client/oohtml/namespacing.js +0 -7
- package/src/runtime-pi/client/oohtml/scripting.js +0 -8
- package/src/runtime-pi/client/oohtml/templating.js +0 -8
- package/src/runtime-pi/client/worker/Application.js +0 -44
- package/src/runtime-pi/client/worker/Runtime.js +0 -269
- package/src/runtime-pi/server/Application.js +0 -116
- package/src/runtime-pi/server/Runtime.js +0 -557
- package/src/runtime-pi/xFormData.js +0 -24
- package/src/runtime-pi/xHeaders.js +0 -146
- package/src/runtime-pi/xRequest.js +0 -46
- package/src/runtime-pi/xRequestHeaders.js +0 -109
- package/src/runtime-pi/xResponse.js +0 -33
- package/src/runtime-pi/xResponseHeaders.js +0 -117
- package/src/runtime-pi/xxHttpMessage.js +0 -102
|
@@ -1,21 +1,16 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
export
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* @APIS
|
|
20
|
-
*/
|
|
21
|
-
export * as APIS from './Runtime.js';
|
|
1
|
+
import { WebfloRootClient1 } from './WebfloRootClient1.js';
|
|
2
|
+
import { WebfloRootClient2 } from './WebfloRootClient2.js';
|
|
3
|
+
import { WebfloSubClient } from './WebfloSubClient.js';
|
|
4
|
+
|
|
5
|
+
export function start() {
|
|
6
|
+
const Controller = window.navigation ? WebfloRootClient2 : WebfloRootClient1;
|
|
7
|
+
const instance = Controller.create(document, this || {});
|
|
8
|
+
instance.initialize();
|
|
9
|
+
WebfloSubClient.defineElement();
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export { WebfloSubClient } from './WebfloSubClient.js';
|
|
13
|
+
export {
|
|
14
|
+
WebfloRootClient1,
|
|
15
|
+
WebfloRootClient2
|
|
16
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { WebfloCookieStorage } from '../../WebfloCookieStorage.js';
|
|
2
|
+
|
|
3
|
+
export class CookieStorage extends WebfloCookieStorage {
|
|
4
|
+
static create(request) {
|
|
5
|
+
return new this(
|
|
6
|
+
request,
|
|
7
|
+
request.headers.get('Cookie', true).map((c) => [c.name, c])
|
|
8
|
+
);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
commit(response) {
|
|
12
|
+
for (const cookieStr of this.render()) {
|
|
13
|
+
response.headers.append('Set-Cookie', cookieStr);
|
|
14
|
+
}
|
|
15
|
+
super.commit();
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
import { _any } from '@webqit/util/arr/index.js';
|
|
2
|
+
import { _isObject } from '@webqit/util/js/index.js';
|
|
3
|
+
import { pattern } from '../../util-url.js';
|
|
4
|
+
import { WebfloRuntime } from '../../WebfloRuntime.js';
|
|
5
|
+
import { ClientMessaging } from './ClientMessaging.js';
|
|
6
|
+
import { CookieStorage } from './CookieStorage.js';
|
|
7
|
+
import { SessionStorage } from './SessionStorage.js';
|
|
8
|
+
import { HttpEvent } from '../../HttpEvent.js';
|
|
9
|
+
import { HttpUser } from '../../HttpUser.js';
|
|
10
|
+
import { Workport } from './Workport.js';
|
|
11
|
+
import { Context } from './Context.js';
|
|
12
|
+
import { Router } from '../Router.js';
|
|
13
|
+
import xfetch from '../../xfetch.js';
|
|
14
|
+
import '../../util-http.js';
|
|
15
|
+
|
|
16
|
+
export class WebfloWorker extends WebfloRuntime {
|
|
17
|
+
|
|
18
|
+
static get Context() { return Context; }
|
|
19
|
+
|
|
20
|
+
static get Router() { return Router; }
|
|
21
|
+
|
|
22
|
+
static get HttpEvent() { return HttpEvent; }
|
|
23
|
+
|
|
24
|
+
static get CookieStorage() { return CookieStorage; }
|
|
25
|
+
|
|
26
|
+
static get SessionStorage() { return SessionStorage; }
|
|
27
|
+
|
|
28
|
+
static get HttpUser() { return HttpUser; }
|
|
29
|
+
|
|
30
|
+
static get Workport() { return Workport; }
|
|
31
|
+
|
|
32
|
+
static create(cx) {
|
|
33
|
+
return new this(this.Context.create(cx));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
#cx;
|
|
37
|
+
get cx() { return this.#cx; }
|
|
38
|
+
|
|
39
|
+
constructor(cx) {
|
|
40
|
+
super();
|
|
41
|
+
if (!(cx instanceof this.constructor.Context)) {
|
|
42
|
+
throw new Error('Argument #1 must be a Webflo Context instance');
|
|
43
|
+
}
|
|
44
|
+
this.#cx = cx;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
initialize() {
|
|
48
|
+
// ONINSTALL
|
|
49
|
+
const installHandler = (event) => {
|
|
50
|
+
if (this.cx.params.skip_waiting) self.skipWaiting();
|
|
51
|
+
// Manage CACHE
|
|
52
|
+
if (this.cx.params.cache_name && (this.cx.params.cache_only_urls || []).length) {
|
|
53
|
+
// Add files to cache
|
|
54
|
+
event.waitUntil(self.caches.open(this.cx.params.cache_name).then(async cache => {
|
|
55
|
+
if (this.cx.logger) { this.cx.logger.log('[ServiceWorker] Pre-caching resources.'); }
|
|
56
|
+
for (const urls of [ 'cache_first_urls', 'cache_only_urls' ]) {
|
|
57
|
+
const _urls = (this.cx.params[urls] || []).map(c => c.trim()).filter(c => c && !pattern(c, self.origin).isPattern());
|
|
58
|
+
await cache.addAll(_urls);
|
|
59
|
+
}
|
|
60
|
+
}));
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
// ONACTIVATE
|
|
64
|
+
const activateHandler = (event) => {
|
|
65
|
+
event.waitUntil(new Promise(async resolve => {
|
|
66
|
+
if (this.cx.params.skip_waiting) { await self.clients.claim(); }
|
|
67
|
+
// Manage CACHE
|
|
68
|
+
if (this.cx.params.cache_name) {
|
|
69
|
+
// Clear outdated CACHES
|
|
70
|
+
await self.caches.keys().then(keyList => {
|
|
71
|
+
return Promise.all(keyList.map(key => {
|
|
72
|
+
if (key !== this.cx.params.cache_name && key !== this.cx.params.cache_name + '_json') {
|
|
73
|
+
if (this.cx.logger) { this.cx.logger.log('[ServiceWorker] Removing old cache:', key); }
|
|
74
|
+
return self.caches.delete(key);
|
|
75
|
+
}
|
|
76
|
+
}));
|
|
77
|
+
})
|
|
78
|
+
}
|
|
79
|
+
resolve();
|
|
80
|
+
}));
|
|
81
|
+
};
|
|
82
|
+
self.addEventListener('install', installHandler);
|
|
83
|
+
self.addEventListener('activate', activateHandler);
|
|
84
|
+
const uncontrols = this.control();
|
|
85
|
+
return () => {
|
|
86
|
+
self.removeEventListener('install', installHandler);
|
|
87
|
+
self.removeEventListener('activate', activateHandler);
|
|
88
|
+
uncontrols();
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
control() {
|
|
93
|
+
// ONFETCH
|
|
94
|
+
const fetchHandler = (event) => {
|
|
95
|
+
// URL schemes that might arrive here but not supported; e.g.: chrome-extension://
|
|
96
|
+
if (!event.request.url.startsWith('http')) return;
|
|
97
|
+
// Handle external requests
|
|
98
|
+
if (!event.request.url.startsWith(self.origin)) {
|
|
99
|
+
return event.respondWith(this.remoteFetch(event.request));
|
|
100
|
+
}
|
|
101
|
+
if (event.request.mode === 'navigate' || event.request.cache === 'force-cache'/* && event.request.mode === 'navigate' - even webflo client init call also comes with that... needs investigation */) {
|
|
102
|
+
// Now, the following is key:
|
|
103
|
+
// The browser likes to use "force-cache" for "navigate" requests, when, e.g: re-entering your site with the back button
|
|
104
|
+
// Problem here, force-cache forces out JSON not HTML as per webflo's design.
|
|
105
|
+
// So, we detect this scenerio and avoid it.
|
|
106
|
+
event.respondWith((async (event) => {
|
|
107
|
+
const { url, ...requestInit } = await Request.copy(event.request);
|
|
108
|
+
requestInit.cache = 'default';
|
|
109
|
+
return await this.navigate(url, requestInit, { event });
|
|
110
|
+
})(event));
|
|
111
|
+
} else {
|
|
112
|
+
event.respondWith(this.navigate(event.request.url, event.request, { event }));
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
self.addEventListener('fetch', fetchHandler);
|
|
116
|
+
return () => {
|
|
117
|
+
self.removeEventListener('fetch', fetchHandler);
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
createRequest(href, init = {}) {
|
|
122
|
+
return new Request(href, init);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async navigate(url, init = {}, detail = {}) {
|
|
126
|
+
// Resolve inputs
|
|
127
|
+
const scope = { url, init, detail };
|
|
128
|
+
if (typeof scope.url === 'string') {
|
|
129
|
+
scope.url = new URL(scope.url, self.location.origin);
|
|
130
|
+
}
|
|
131
|
+
scope.response = await new Promise(async (resolveResponse) => {
|
|
132
|
+
scope.handleRespondWith = async (response) => {
|
|
133
|
+
if (scope.finalResponseSeen) {
|
|
134
|
+
throw new Error('Final response already sent');
|
|
135
|
+
}
|
|
136
|
+
if (scope.initialResponseSeen) {
|
|
137
|
+
return await this.execPush(scope.clientMessaging, response);
|
|
138
|
+
}
|
|
139
|
+
resolveResponse(response);
|
|
140
|
+
};
|
|
141
|
+
// Create and route request
|
|
142
|
+
scope.request = this.createRequest(scope.url, scope.init);
|
|
143
|
+
scope.cookies = this.constructor.CookieStorage.create(scope.request);
|
|
144
|
+
scope.session = this.constructor.SessionStorage.create(scope.request, { secret: this.cx.env.entries.SESSION_KEY });
|
|
145
|
+
const portID = crypto.randomUUID();
|
|
146
|
+
scope.clientMessaging = new ClientMessaging(this, portID, { isPrimary: true });
|
|
147
|
+
scope.user = this.constructor.HttpUser.create(
|
|
148
|
+
scope.request,
|
|
149
|
+
scope.session,
|
|
150
|
+
scope.clientMessaging
|
|
151
|
+
);
|
|
152
|
+
scope.httpEvent = this.constructor.HttpEvent.create(scope.handleRespondWith, {
|
|
153
|
+
request: scope.request,
|
|
154
|
+
detail: scope.detail,
|
|
155
|
+
cookies: scope.cookies,
|
|
156
|
+
session: scope.session,
|
|
157
|
+
user: scope.user,
|
|
158
|
+
client: scope.clientMessaging
|
|
159
|
+
});
|
|
160
|
+
// Restore session before dispatching
|
|
161
|
+
if (scope.request.method === 'GET'
|
|
162
|
+
&& (scope.redirectMessageID = scope.httpEvent.url.query['redirect-message'])
|
|
163
|
+
&& (scope.redirectMessage = scope.session.get(`redirect-message:${scope.redirectMessageID}`))) {
|
|
164
|
+
scope.session.delete(`redirect-message:${scope.redirectMessageID}`);
|
|
165
|
+
}
|
|
166
|
+
// Dispatch for response
|
|
167
|
+
scope.$response = await this.dispatch(scope.httpEvent, {}, async (event) => {
|
|
168
|
+
// Was this nexted()? Tell the next layer we're in JSON mode by default
|
|
169
|
+
if (event !== scope.httpEvent && !event.request.headers.has('Accept')) {
|
|
170
|
+
event.request.headers.set('Accept', 'application/json');
|
|
171
|
+
}
|
|
172
|
+
return await this.remoteFetch(event.request);
|
|
173
|
+
});
|
|
174
|
+
// Final reponse!!!
|
|
175
|
+
scope.finalResponseSeen = true;
|
|
176
|
+
if (scope.initialResponseSeen) {
|
|
177
|
+
// Send via background port
|
|
178
|
+
if (typeof scope.$response !== 'undefined') {
|
|
179
|
+
await this.execPush(scope.clientMessaging, scope.$response);
|
|
180
|
+
}
|
|
181
|
+
scope.clientMessaging.close();
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
// Send normally
|
|
185
|
+
resolveResponse(scope.$response);
|
|
186
|
+
});
|
|
187
|
+
scope.initialResponseSeen = true;
|
|
188
|
+
scope.hasBackgroundActivity = !scope.finalResponseSeen || (scope.redirectMessage && !(scope.response instanceof Response && scope.response.headers.get('Location')));
|
|
189
|
+
scope.response = await this.normalizeResponse(scope.httpEvent, scope.response, scope.hasBackgroundActivity);
|
|
190
|
+
if (scope.hasBackgroundActivity) {
|
|
191
|
+
scope.response.headers.set('X-Background-Messaging', `ch:${scope.clientMessaging.port.name}`);
|
|
192
|
+
}
|
|
193
|
+
if (scope.response instanceof Response && scope.response.headers.get('Location')) {
|
|
194
|
+
if (scope.redirectMessage) {
|
|
195
|
+
scope.session.set(`redirect-message:${scope.redirectMessageID}`, scope.redirectMessage);
|
|
196
|
+
}
|
|
197
|
+
} else {
|
|
198
|
+
if (scope.redirectMessage) {
|
|
199
|
+
setTimeout(() => {
|
|
200
|
+
this.execPush(scope.clientMessaging, scope.redirectMessage);
|
|
201
|
+
if (scope.finalResponseSeen) {
|
|
202
|
+
scope.clientMessaging.close();
|
|
203
|
+
}
|
|
204
|
+
}, 500);
|
|
205
|
+
} else if (scope.finalResponseSeen) {
|
|
206
|
+
scope.clientMessaging.close();
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
return scope.response;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
async remoteFetch(request, ...args) {
|
|
213
|
+
if (arguments.length > 1) {
|
|
214
|
+
request = this.createRequest(request, ...args);
|
|
215
|
+
}
|
|
216
|
+
const scope = {};
|
|
217
|
+
const matchUrl = (patterns, url) => _any((patterns || []).map(p => p.trim()).filter(p => p), p => pattern(p, self.origin).test(url));
|
|
218
|
+
if (matchUrl(this.cx.params.cache_only_urls, request.url)) {
|
|
219
|
+
scope.strategy = 'cache-only';
|
|
220
|
+
scope.response = this.cacheFetch(request, { networkFallback: false, cacheRefresh: false });
|
|
221
|
+
} else if (matchUrl(this.cx.params.network_only_urls, request.url)) {
|
|
222
|
+
scope.strategy = 'network-only';
|
|
223
|
+
scope.response = this.networkFetch(request, { cacheFallback: false, cacheRefresh: false });
|
|
224
|
+
} else if (matchUrl(this.cx.params.cache_first_urls, request.url)) {
|
|
225
|
+
scope.strategy = 'cache-first';
|
|
226
|
+
scope.response = this.cacheFetch(request, { networkFallback: true, cacheRefresh: true });
|
|
227
|
+
} else if (matchUrl(this.cx.params.network_first_urls, request.url) || !this.cx.params.default_fetching_strategy) {
|
|
228
|
+
scope.strategy = 'network-first';
|
|
229
|
+
scope.response = this.networkFetch(request, { cacheFallback: true, cacheRefresh: true });
|
|
230
|
+
} else {
|
|
231
|
+
scope.strategy = this.cx.params.default_fetching_strategy;
|
|
232
|
+
switch (this.cx.params.default_fetching_strategy) {
|
|
233
|
+
case 'cache-only':
|
|
234
|
+
scope.response = this.cacheFetch(request, { networkFallback: false, cacheRefresh: false });
|
|
235
|
+
break;
|
|
236
|
+
case 'network-only':
|
|
237
|
+
scope.response = this.networkFetch(request, { cacheFallback: false, cacheRefresh: false });
|
|
238
|
+
break;
|
|
239
|
+
case 'cache-first':
|
|
240
|
+
scope.response = this.cacheFetch(request, { networkFallback: true, cacheRefresh: true });
|
|
241
|
+
break;
|
|
242
|
+
case 'network-first':
|
|
243
|
+
scope.response = this.networkFetch(request, { cacheFallback: true, cacheRefresh: true });
|
|
244
|
+
break;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
return await scope.response;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
async networkFetch(request, params = {}) {
|
|
251
|
+
if (!params.cacheFallback) {
|
|
252
|
+
return xfetch(request);
|
|
253
|
+
}
|
|
254
|
+
return xfetch(request).then((response) => {
|
|
255
|
+
if (params.cacheRefresh) this.refreshCache(request, response);
|
|
256
|
+
return response;
|
|
257
|
+
}).catch((e) => this.getRequestCache(request).then(cache => {
|
|
258
|
+
return cache.match(request);
|
|
259
|
+
}));
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
async cacheFetch(request, params = {}) {
|
|
263
|
+
return this.getRequestCache(request).then(cache => cache.match(request).then((response) => {
|
|
264
|
+
// Nothing cache, use network
|
|
265
|
+
if (!response && params.networkFallback) return this.networkFetch(request, { ...params, cacheFallback: false });
|
|
266
|
+
// Note: fetch, but for refreshing purposes only... not the returned response
|
|
267
|
+
if (response && params.cacheRefresh) this.networkFetch(request, { ...params, justRefreshing: true });
|
|
268
|
+
return response;
|
|
269
|
+
}));
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
async refreshCache(request, response) {
|
|
273
|
+
// Check if we received a valid response
|
|
274
|
+
if (request.method !== 'GET' || !response || response.status !== 200 || (response.type !== 'basic' && response.type !== 'cors')) {
|
|
275
|
+
return response;
|
|
276
|
+
}
|
|
277
|
+
// IMPORTANT: Clone the response. A response is a stream
|
|
278
|
+
// and because we want the browser to consume the response
|
|
279
|
+
// as well as the cache consuming the response, we need
|
|
280
|
+
// to clone it so we have two streams.
|
|
281
|
+
var responseToCache = response.clone();
|
|
282
|
+
this.getRequestCache(request).then(cache => {
|
|
283
|
+
cache.put(request, responseToCache);
|
|
284
|
+
});
|
|
285
|
+
return response;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
async getRequestCache(request) {
|
|
289
|
+
const cacheName = request.headers.get('Accept') === 'application/json'
|
|
290
|
+
? this.cx.params.cache_name + '_json'
|
|
291
|
+
: this.cx.params.cache_name;
|
|
292
|
+
return self.caches.open(cacheName);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
@@ -1,86 +1,18 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
let responsePort = evt.ports[0];
|
|
19
|
-
if (responsePort) {
|
|
20
|
-
if (response instanceof Promise) {
|
|
21
|
-
response.then(data => {
|
|
22
|
-
responsePort.postMessage(data);
|
|
23
|
-
});
|
|
24
|
-
} else {
|
|
25
|
-
responsePort.postMessage(response);
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
});
|
|
29
|
-
return this.post;
|
|
30
|
-
},
|
|
31
|
-
request: (message, client = this.client) => {
|
|
32
|
-
if (!client) throw new Error(`No client for this operation.`);
|
|
33
|
-
return new Promise(res => {
|
|
34
|
-
let messageChannel = new MessageChannel();
|
|
35
|
-
client.postMessage(message, [ messageChannel.port2 ]);
|
|
36
|
-
messageChannel.port1.onmessage = e => res(e.data);
|
|
37
|
-
});
|
|
38
|
-
},
|
|
39
|
-
channel(channelId) {
|
|
40
|
-
if (!this.channels.has(channelId)) { this.channels.set(channelId, new BroadcastChannel(channel)); }
|
|
41
|
-
let channel = this.channels.get(channelId);
|
|
42
|
-
return {
|
|
43
|
-
broadcast: message => channel.postMessage(message),
|
|
44
|
-
listen: callback => channel.addEventListener('message', callback),
|
|
45
|
-
};
|
|
46
|
-
},
|
|
47
|
-
channels: new Map,
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
// --------
|
|
51
|
-
// Notifications
|
|
52
|
-
// --------
|
|
53
|
-
this.notifications = {
|
|
54
|
-
fire: (title, params = {}) => {
|
|
55
|
-
return new Promise((res, rej) => {
|
|
56
|
-
if (!(self.Notification && self.Notification.permission === 'granted')) {
|
|
57
|
-
return rej(self.Notification && self.Notification.permission);
|
|
58
|
-
}
|
|
59
|
-
notification.addEventListener('error', rej);
|
|
60
|
-
let notification = new self.Notification(title, params);
|
|
61
|
-
notification.addEventListener('click', res);
|
|
62
|
-
notification.addEventListener('close', res);
|
|
63
|
-
});
|
|
64
|
-
},
|
|
65
|
-
handle: callback => {
|
|
66
|
-
self.addEventListener('notificationclick', callback);
|
|
67
|
-
return this.notifications;
|
|
68
|
-
},
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
// --------
|
|
72
|
-
// Push Notifications
|
|
73
|
-
// --------
|
|
74
|
-
this.push = {
|
|
75
|
-
listen: callback => {
|
|
76
|
-
self.addEventListener('push', callback);
|
|
77
|
-
return this.post;
|
|
78
|
-
},
|
|
79
|
-
};
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
setCurrentClient(client) {
|
|
83
|
-
this.client = client;
|
|
84
|
-
}
|
|
85
|
-
|
|
1
|
+
export class Workport {
|
|
2
|
+
|
|
3
|
+
showNotification(title, params = {}) {
|
|
4
|
+
return self.registration.showNotification(title, params);
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
handleNotificationClick(callback) {
|
|
8
|
+
const handler = (e) => e.waitUntil(callback(e));
|
|
9
|
+
self.addEventListener('notificationclick', handler);
|
|
10
|
+
return () => self.removeEventListener('notificationclick', handler);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
handlePush(callback) {
|
|
14
|
+
const handler = (e) => e.waitUntil(callback(e));
|
|
15
|
+
self.addEventListener('push', handler);
|
|
16
|
+
return () => self.removeEventListener('notificationclick', handler);
|
|
17
|
+
}
|
|
86
18
|
}
|
|
@@ -1,21 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
*/
|
|
12
|
-
export async function start(applicationInstance = null) {
|
|
13
|
-
const cx = this || {};
|
|
14
|
-
const defaultApplicationInstance = _cx => new Application(_cx);
|
|
15
|
-
return new Runtime(Context.create(cx), applicationInstance || defaultApplicationInstance);
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* @APIS
|
|
20
|
-
*/
|
|
21
|
-
export * as APIS from './Runtime.js';
|
|
1
|
+
import { WebfloWorker } from './WebfloWorker.js';
|
|
2
|
+
|
|
3
|
+
export function start() {
|
|
4
|
+
const instance = WebfloWorker.create(this || {});
|
|
5
|
+
instance.initialize();
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export {
|
|
9
|
+
WebfloWorker
|
|
10
|
+
}
|
package/src/runtime-pi/index.js
CHANGED
|
@@ -1,14 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* @exports
|
|
10
|
-
*/
|
|
11
|
-
export {
|
|
12
|
-
server,
|
|
13
|
-
client,
|
|
1
|
+
import * as server from './server/index.js';
|
|
2
|
+
import * as client from './client/generate.js';
|
|
3
|
+
|
|
4
|
+
export {
|
|
5
|
+
server,
|
|
6
|
+
client
|
|
14
7
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { MultiportMessagingAPI } from '../MultiportMessagingAPI.js';
|
|
2
|
+
|
|
3
|
+
export class ClientMessaging extends MultiportMessagingAPI {
|
|
4
|
+
|
|
5
|
+
get runtime() { return this.parentNode.parentNode; }
|
|
6
|
+
|
|
7
|
+
#portID;
|
|
8
|
+
get portID() { return this.#portID; }
|
|
9
|
+
|
|
10
|
+
constructor(parentNode/*ClientMessagingRegistry*/, portID, params = {}) {
|
|
11
|
+
super(parentNode, params);
|
|
12
|
+
this.#portID = portID;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
createBroadcastChannel(name) {
|
|
16
|
+
return this.parentNode.createBroadcastChannel(name);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { ClientMessaging } from './ClientMessaging.js';
|
|
2
|
+
import crypto from 'crypto';
|
|
3
|
+
|
|
4
|
+
export class ClientMessagingRegistry extends Map {
|
|
5
|
+
|
|
6
|
+
#parentNode;
|
|
7
|
+
get parentNode() { return this.#parentNode; }
|
|
8
|
+
|
|
9
|
+
#sessionID;
|
|
10
|
+
get sessionID() { return this.#sessionID; }
|
|
11
|
+
|
|
12
|
+
#params;
|
|
13
|
+
get params() { return this.#params; }
|
|
14
|
+
|
|
15
|
+
#channels = new Map;
|
|
16
|
+
|
|
17
|
+
constructor(parentNode/*WebfloServer*/, sessionID, params = {}) {
|
|
18
|
+
super();
|
|
19
|
+
this.#parentNode = parentNode;
|
|
20
|
+
this.#sessionID = sessionID;
|
|
21
|
+
this.#params = params;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
createPort() {
|
|
25
|
+
const portID = crypto.randomUUID();
|
|
26
|
+
const portInstance = new ClientMessaging(this, portID, this.#params);
|
|
27
|
+
this.set(portID, portInstance);
|
|
28
|
+
portInstance.on('empty', () => {
|
|
29
|
+
this.delete(portID);
|
|
30
|
+
});
|
|
31
|
+
setTimeout(() => {
|
|
32
|
+
if (portInstance.ports.size || !this.has(portID)) return;
|
|
33
|
+
this.delete(portID);
|
|
34
|
+
}, 30000/*30sec*/);
|
|
35
|
+
return portInstance;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
createBroadcastChannel(name) {
|
|
39
|
+
if (!this.#channels.has(name)) {
|
|
40
|
+
this.#channels.set(name, new BroadcastChannel(this, name));
|
|
41
|
+
}
|
|
42
|
+
return this.#channels.get(name);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export class BroadcastChannel extends EventTarget {
|
|
47
|
+
#manager;
|
|
48
|
+
#name;
|
|
49
|
+
constructor(manager, name) {
|
|
50
|
+
super();
|
|
51
|
+
this.#manager = manager;
|
|
52
|
+
this.#name = name;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
postMessage() {
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -1,16 +1,12 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
set env(value) {
|
|
14
|
-
this.dict.env = value;
|
|
15
|
-
}
|
|
1
|
+
import { AbstractContext } from '../../AbstractContext.js';
|
|
2
|
+
|
|
3
|
+
export class Context extends AbstractContext {
|
|
4
|
+
// env
|
|
5
|
+
get env() {
|
|
6
|
+
return this.dict.env || {};
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
set env(value) {
|
|
10
|
+
this.dict.env = value;
|
|
11
|
+
}
|
|
16
12
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { WebfloCookieStorage } from '../WebfloCookieStorage.js';
|
|
2
|
+
|
|
3
|
+
export class CookieStorage extends WebfloCookieStorage {
|
|
4
|
+
static create(request) {
|
|
5
|
+
return new this(
|
|
6
|
+
request,
|
|
7
|
+
request.headers.get('Cookie', true).map((c) => [c.name, c])
|
|
8
|
+
);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
commit(response) {
|
|
12
|
+
for (const cookieStr of this.render()) {
|
|
13
|
+
response.headers.append('Set-Cookie', cookieStr);
|
|
14
|
+
}
|
|
15
|
+
super.commit();
|
|
16
|
+
}
|
|
17
|
+
}
|