@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.
Files changed (118) hide show
  1. package/.gitignore +7 -7
  2. package/LICENSE +20 -20
  3. package/README.md +2079 -2074
  4. package/docker/Dockerfile +42 -42
  5. package/docker/README.md +91 -91
  6. package/docker/package.json +2 -2
  7. package/package.json +80 -81
  8. package/src/{Context.js → AbstractContext.js} +71 -79
  9. package/src/config-pi/deployment/Env.js +68 -68
  10. package/src/config-pi/deployment/Layout.js +63 -63
  11. package/src/config-pi/deployment/Origins.js +139 -139
  12. package/src/config-pi/deployment/Proxy.js +74 -74
  13. package/src/config-pi/deployment/index.js +17 -17
  14. package/src/config-pi/index.js +15 -15
  15. package/src/config-pi/runtime/Client.js +116 -98
  16. package/src/config-pi/runtime/Server.js +125 -125
  17. package/src/config-pi/runtime/client/Worker.js +109 -134
  18. package/src/config-pi/runtime/client/index.js +11 -11
  19. package/src/config-pi/runtime/index.js +17 -17
  20. package/src/config-pi/runtime/server/Headers.js +74 -74
  21. package/src/config-pi/runtime/server/Redirects.js +69 -69
  22. package/src/config-pi/runtime/server/index.js +13 -13
  23. package/src/config-pi/static/Manifest.js +319 -319
  24. package/src/config-pi/static/Ssg.js +49 -49
  25. package/src/config-pi/static/index.js +13 -13
  26. package/src/deployment-pi/index.js +10 -10
  27. package/src/deployment-pi/origins/index.js +216 -216
  28. package/src/index.js +11 -19
  29. package/src/runtime-pi/HttpEvent.js +126 -106
  30. package/src/runtime-pi/HttpUser.js +126 -0
  31. package/src/runtime-pi/MessagingOverBroadcast.js +9 -0
  32. package/src/runtime-pi/MessagingOverChannel.js +85 -0
  33. package/src/runtime-pi/MessagingOverSocket.js +106 -0
  34. package/src/runtime-pi/MultiportMessagingAPI.js +81 -0
  35. package/src/runtime-pi/WebfloCookieStorage.js +27 -0
  36. package/src/runtime-pi/WebfloEventTarget.js +39 -0
  37. package/src/runtime-pi/WebfloMessageEvent.js +58 -0
  38. package/src/runtime-pi/WebfloMessagingAPI.js +69 -0
  39. package/src/runtime-pi/{Router.js → WebfloRouter.js} +99 -130
  40. package/src/runtime-pi/WebfloRuntime.js +52 -0
  41. package/src/runtime-pi/WebfloStorage.js +109 -0
  42. package/src/runtime-pi/client/ClientMessaging.js +5 -0
  43. package/src/runtime-pi/client/Context.js +3 -7
  44. package/src/runtime-pi/client/CookieStorage.js +17 -0
  45. package/src/runtime-pi/client/Router.js +38 -48
  46. package/src/runtime-pi/client/SessionStorage.js +33 -0
  47. package/src/runtime-pi/client/Url.js +156 -205
  48. package/src/runtime-pi/client/WebfloClient.js +544 -0
  49. package/src/runtime-pi/client/WebfloRootClient1.js +179 -0
  50. package/src/runtime-pi/client/WebfloRootClient2.js +109 -0
  51. package/src/runtime-pi/client/WebfloSubClient.js +165 -0
  52. package/src/runtime-pi/client/Workport.js +118 -178
  53. package/src/runtime-pi/client/generate.js +480 -471
  54. package/src/runtime-pi/client/index.js +16 -21
  55. package/src/runtime-pi/client/worker/ClientMessaging.js +5 -0
  56. package/src/runtime-pi/client/worker/Context.js +3 -7
  57. package/src/runtime-pi/client/worker/CookieStorage.js +17 -0
  58. package/src/runtime-pi/client/worker/SessionStorage.js +13 -0
  59. package/src/runtime-pi/client/worker/WebfloWorker.js +294 -0
  60. package/src/runtime-pi/client/worker/Workport.js +17 -85
  61. package/src/runtime-pi/client/worker/index.js +10 -21
  62. package/src/runtime-pi/index.js +6 -13
  63. package/src/runtime-pi/server/ClientMessaging.js +18 -0
  64. package/src/runtime-pi/server/ClientMessagingRegistry.js +57 -0
  65. package/src/runtime-pi/server/Context.js +11 -15
  66. package/src/runtime-pi/server/CookieStorage.js +17 -0
  67. package/src/runtime-pi/server/Router.js +93 -159
  68. package/src/runtime-pi/server/SessionStorage.js +53 -0
  69. package/src/runtime-pi/server/WebfloServer.js +755 -0
  70. package/src/runtime-pi/server/index.js +10 -21
  71. package/src/runtime-pi/util-http.js +322 -86
  72. package/src/runtime-pi/util-url.js +146 -146
  73. package/src/runtime-pi/xURL.js +108 -105
  74. package/src/runtime-pi/xfetch.js +22 -22
  75. package/src/services-pi/cert/http-auth-hook.js +22 -22
  76. package/src/services-pi/cert/http-cleanup-hook.js +22 -22
  77. package/src/services-pi/cert/index.js +79 -79
  78. package/src/services-pi/index.js +8 -8
  79. package/src/static-pi/index.js +10 -10
  80. package/src/webflo.js +30 -30
  81. package/test/index.test.js +26 -26
  82. package/test/site/package.json +9 -9
  83. package/test/site/public/bundle.html +5 -5
  84. package/test/site/public/bundle.html.json +3 -3
  85. package/test/site/public/bundle.js +2 -2
  86. package/test/site/public/bundle.webflo.js +15 -15
  87. package/test/site/public/index.html +29 -29
  88. package/test/site/public/index1.html +34 -34
  89. package/test/site/public/page-2/bundle.html +4 -4
  90. package/test/site/public/page-2/bundle.js +2 -2
  91. package/test/site/public/page-2/index.html +45 -45
  92. package/test/site/public/page-2/main.html +2 -2
  93. package/test/site/public/page-4/subpage/bundle.js +2 -2
  94. package/test/site/public/page-4/subpage/index.html +30 -30
  95. package/test/site/public/sparoots.json +4 -4
  96. package/test/site/public/worker.js +3 -3
  97. package/test/site/server/index.js +15 -15
  98. package/src/runtime-pi/Application.js +0 -29
  99. package/src/runtime-pi/Cookies.js +0 -82
  100. package/src/runtime-pi/Runtime.js +0 -21
  101. package/src/runtime-pi/client/Application.js +0 -100
  102. package/src/runtime-pi/client/Runtime.js +0 -332
  103. package/src/runtime-pi/client/createStorage.js +0 -57
  104. package/src/runtime-pi/client/oohtml/full.js +0 -7
  105. package/src/runtime-pi/client/oohtml/namespacing.js +0 -7
  106. package/src/runtime-pi/client/oohtml/scripting.js +0 -8
  107. package/src/runtime-pi/client/oohtml/templating.js +0 -8
  108. package/src/runtime-pi/client/worker/Application.js +0 -44
  109. package/src/runtime-pi/client/worker/Runtime.js +0 -269
  110. package/src/runtime-pi/server/Application.js +0 -116
  111. package/src/runtime-pi/server/Runtime.js +0 -557
  112. package/src/runtime-pi/xFormData.js +0 -24
  113. package/src/runtime-pi/xHeaders.js +0 -146
  114. package/src/runtime-pi/xRequest.js +0 -46
  115. package/src/runtime-pi/xRequestHeaders.js +0 -109
  116. package/src/runtime-pi/xResponse.js +0 -33
  117. package/src/runtime-pi/xResponseHeaders.js +0 -117
  118. package/src/runtime-pi/xxHttpMessage.js +0 -102
@@ -1,21 +1,16 @@
1
-
2
- /**
3
- * @imports
4
- */
5
- import Context from './Context.js';
6
- import Application from './Application.js';
7
- import Runtime from './Runtime.js';
8
-
9
- /**
10
- * @start
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 { 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,5 @@
1
+ import { MessagingOverBroadcast } from '../../MessagingOverBroadcast.js';
2
+
3
+ export class ClientMessaging extends MessagingOverBroadcast {
4
+ get runtime() { return this.parentNode; }
5
+ }
@@ -1,7 +1,3 @@
1
-
2
- /**
3
- * @imports
4
- */
5
- import _Contex from '../Context.js';
6
-
7
- export default class Context extends _Contex {}
1
+ import { Context as AbstractContext } from '../Context.js';
2
+
3
+ export class Context extends AbstractContext {}
@@ -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,13 @@
1
+ import { WebfloStorage } from '../../WebfloStorage.js';
2
+
3
+ export class SessionStorage extends WebfloStorage {
4
+ #type;
5
+
6
+ static async create(request) {
7
+ return new this;
8
+ }
9
+
10
+ async commit(response) {
11
+ super.commit();
12
+ }
13
+ }
@@ -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
- 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
- (client || self).addEventListener('message', evt => {
16
- this.client = evt.source;
17
- const response = callback(evt);
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
- * @imports
4
- */
5
- import Context from './Context.js';
6
- import Application from './Application.js';
7
- import Runtime from './Runtime.js';
8
-
9
- /**
10
- * @start
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
+ }
@@ -1,14 +1,7 @@
1
-
2
- /**
3
- * @imports
4
- */
5
- import * as server from './server/index.js';
6
- import * as client from './client/generate.js';
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
- * @imports
4
- */
5
- import _Contex from '../../Context.js';
6
-
7
- export default class Context extends _Contex {
8
- // env
9
- get env() {
10
- return this.dict.env || {};
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
+ }