@webqit/webflo 1.0.19 → 1.0.20

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 (32) hide show
  1. package/package.json +4 -3
  2. package/src/config-pi/runtime/Client.js +50 -46
  3. package/src/config-pi/runtime/Server.js +37 -14
  4. package/src/config-pi/runtime/client/Worker.js +22 -20
  5. package/src/runtime-pi/HttpEvent.js +34 -19
  6. package/src/runtime-pi/HttpUser.js +35 -36
  7. package/src/runtime-pi/WebfloCookieStorage.js +8 -8
  8. package/src/runtime-pi/WebfloRouter.js +2 -2
  9. package/src/runtime-pi/WebfloRuntime.js +7 -2
  10. package/src/runtime-pi/WebfloStorage.js +47 -16
  11. package/src/runtime-pi/client/Capabilities.js +211 -0
  12. package/src/runtime-pi/client/CookieStorage.js +2 -2
  13. package/src/runtime-pi/client/SessionStorage.js +2 -2
  14. package/src/runtime-pi/client/WebfloClient.js +15 -23
  15. package/src/runtime-pi/client/WebfloRootClient1.js +55 -34
  16. package/src/runtime-pi/client/WebfloRootClient2.js +2 -2
  17. package/src/runtime-pi/client/WebfloSubClient.js +9 -5
  18. package/src/runtime-pi/client/Workport.js +64 -91
  19. package/src/runtime-pi/client/generate.js +25 -16
  20. package/src/runtime-pi/client/index.js +3 -2
  21. package/src/runtime-pi/client/worker/CookieStorage.js +2 -2
  22. package/src/runtime-pi/client/worker/SessionStorage.js +1 -1
  23. package/src/runtime-pi/client/worker/WebfloWorker.js +70 -56
  24. package/src/runtime-pi/client/worker/index.js +3 -2
  25. package/src/runtime-pi/server/CookieStorage.js +2 -2
  26. package/src/runtime-pi/server/SessionStorage.js +3 -3
  27. package/src/runtime-pi/server/WebfloServer.js +26 -11
  28. package/src/runtime-pi/server/index.js +1 -0
  29. package/src/runtime-pi/util-http.js +15 -2
  30. package/src/services-pi/index.js +2 -0
  31. package/src/services-pi/push/index.js +23 -0
  32. package/src/static-pi/index.js +1 -1
@@ -1,118 +1,91 @@
1
- export class Workport {
1
+ import { _isObject } from '@webqit/util/js/index.js';
2
+ import { WebfloMessagingAPI } from '../WebfloMessagingAPI.js';
3
+ import { WebfloMessageEvent } from '../WebfloMessageEvent.js';
2
4
 
3
- #swFile;
4
- #swParams = {};
5
+ export class Workport extends WebfloMessagingAPI {
5
6
 
6
- #swReady;
7
- get swReady() { return this.#swReady; }
8
- #swRegistration;
7
+ #registration;
8
+ get registration() { return this.#registration; }
9
9
 
10
- async registerServiceWorker(file, params = {}) {
11
- if (this.#swRegistration) {
12
- throw new Error('Service worker already registered');
13
- }
14
- this.#swFile = file;
15
- this.#swParams = params;
16
- this.#swReady = navigator.serviceWorker ? navigator.serviceWorker.ready : new Promise(() => {});
17
- this.#swRegistration = await navigator.serviceWorker.register(this.#swFile, { scope: this.#swParams.scope || '/' });
10
+ #params;
11
+ get params() { return this.#params; }
12
+
13
+ #ready;
14
+ get ready() { return this.#ready; }
15
+
16
+ #active;
17
+ get active() { return this.#active; }
18
+
19
+ static async initialize(parentNode, file, params = {}) {
20
+ const registration = (await navigator.serviceWorker.getRegistration())
21
+ || (await navigator.serviceWorker.register(file, { scope: '/', ...params }));
22
+ return new this(parentNode, registration, params);
23
+ }
24
+
25
+ #messageHandler;
26
+ constructor(parentNode, registration, params = {}) {
27
+ super(parentNode, params);
28
+ this.#registration = registration;
29
+ this.#params = params;
30
+ this.#ready = navigator.serviceWorker ? navigator.serviceWorker.ready : new Promise(() => {});
18
31
  // Helper that updates instance's state
19
32
  const stateChange = (target) => {
20
33
  // target.state can be any of: "parsed", "installing", "installed", "activating", "activated", "redundant"
21
34
  if (target.state === 'redundant') {
22
- //this.remove(target);
23
35
  } else if (target.state === 'activated') {
24
- //this.add(target);
36
+ const existing = this.#active;
37
+ this.#active = target;
38
+ if (!existing) {
39
+ this.$emit('connected');
40
+ }
25
41
  }
26
42
  }
27
43
  // We're always installing at first for a new service worker.
28
44
  // An existing service would immediately be active
29
- const worker = this.#swRegistration.active || this.#swRegistration.waiting || this.#swRegistration.installing;
30
- if (worker) {
31
- stateChange(worker);
32
- worker.addEventListener('statechange', (e) => stateChange(e.target));
45
+ const initial = this.#registration.active || this.#registration.waiting || this.#registration.installing;
46
+ if (initial) {
47
+ stateChange(initial);
48
+ initial.addEventListener('statechange', (e) => stateChange(e.target));
33
49
  // "updatefound" event - a new worker that will control
34
50
  // this page is installing somewhere
35
- this.#swRegistration.addEventListener('updatefound', () => {
51
+ this.#registration.addEventListener('updatefound', () => {
36
52
  // If updatefound is fired, it means that there's
37
53
  // a new service worker being installed.
38
- stateChange(this.#swRegistration.installing);
39
- this.#swRegistration.installing.addEventListener('statechange', (e) => stateChange(e.target));
54
+ stateChange(this.#registration.installing);
55
+ this.#registration.installing.addEventListener('statechange', (e) => stateChange(e.target));
40
56
  });
41
57
  }
42
- }
43
-
44
- async requestNotificationPermission() {
45
- return await new Promise(async (resolve, reject) => {
46
- const permissionResult = Notification.requestPermission(resolve);
47
- if (permissionResult) {
48
- permissionResult.then(resolve, reject);
58
+ this.#messageHandler = async (event) => {
59
+ if (!_isObject(event.data) || !['messageType', 'message'].every((k) => k in event.data)) {
60
+ return;
49
61
  }
50
- });
62
+ this.dispatchEvent(new WorkerMessageEvent(
63
+ this,
64
+ event.data.messageType,
65
+ event.data.message,
66
+ event.ports
67
+ ));
68
+ };
69
+ navigator.serviceWorker.addEventListener('message', this.#messageHandler);
51
70
  }
52
71
 
53
- async showNotification(title, params = {}) {
54
- await this.#swReady;
55
- if (this.#swRegistration) {
56
- return (await this.#swRegistration).showNotification(title, params);
57
- }
58
- return new Notification(title, params);
59
- }
60
-
61
- async pushSubscription(autoPrompt = true) {
62
- if (!this.#swRegistration) {
63
- throw new Error(`Service worker not registered`);
64
- }
65
- await this.#swReady;
66
- const pushManager = (await this.#swRegistration).pushManager;
67
- let subscription = await pushManager.getSubscription();
68
- if (!subscription && autoPrompt && this.#swParams.VAPID_PUBLIC_KEY) {
69
- subscription = await pushManager.subscribe({
70
- userVisibleOnly: true,
71
- applicationServerKey: urlBase64ToUint8Array(this.#swParams.VAPID_PUBLIC_KEY),
72
- });
73
- if (this.#swParams.PUSH_REGISTRATION_PUBLIC_URL) {
74
- await fetch(this.#swParams.PUSH_REGISTRATION_PUBLIC_URL, {
75
- method: 'POST',
76
- headers: { 'Content-Type': 'application/json', },
77
- body: JSON.stringify(subscription)
78
- });
72
+ postMessage(message, transferOrOptions = []) {
73
+ this.on('connected', () => {
74
+ if (Array.isArray(transferOrOptions)) {
75
+ transferOrOptions = { transfer: transferOrOptions };
79
76
  }
80
- }
81
- return subscription;
77
+ const { messageType = 'message', ...options } = transferOrOptions;
78
+ return this.#active.postMessage({
79
+ messageType,
80
+ message
81
+ }, options);
82
+ });
83
+ super.postMessage(message, transferOrOptions);
82
84
  }
83
85
 
84
- async pushUnsubscribe() {
85
- if (!this.#swRegistration) {
86
- throw new Error(`Service worker not registered`);
87
- }
88
- await this.#swReady;
89
- const pushManager = (await this.#swRegistration).pushManager;
90
- const subscription = await pushManager.getSubscription();
91
- if (subscription) {
92
- subscription.unsubscribe();
93
- if (subscription && this.#swParams.PUSH_REGISTRATION_PUBLIC_URL) {
94
- await fetch(this.#swParams.PUSH_REGISTRATION_PUBLIC_URL, {
95
- method: 'DELETE',
96
- headers: { 'Content-Type': 'application/json', },
97
- body: JSON.stringify(subscription)
98
- });
99
- }
100
- }
86
+ close() {
87
+ navigator.serviceWorker.removeEventListener('message', this.#messageHandler);
101
88
  }
102
89
  }
103
90
 
104
- // Public base64 to Uint
105
- function urlBase64ToUint8Array(base64String) {
106
- var padding = '='.repeat((4 - base64String.length % 4) % 4);
107
- var base64 = (base64String + padding)
108
- .replace(/\-/g, '+')
109
- .replace(/_/g, '/');
110
-
111
- var rawData = window.atob(base64);
112
- var outputArray = new Uint8Array(rawData.length);
113
-
114
- for (var i = 0; i < rawData.length; ++i) {
115
- outputArray[i] = rawData.charCodeAt(i);
116
- }
117
- return outputArray;
118
- }
91
+ export class WorkerMessageEvent extends WebfloMessageEvent {}
@@ -25,13 +25,14 @@ export async function generate() {
25
25
  if (!cx.config.deployment?.Layout) {
26
26
  throw new Error(`The Client configurator "config.deployment.Layout" is required in context.`);
27
27
  }
28
+ const env = {};
28
29
  const clientConfig = await (new cx.config.runtime.Client(cx)).read();
29
- clientConfig.env = {};
30
- if (clientConfig.service_worker?.filename && !cx.config.runtime.client?.Worker) {
30
+ clientConfig.env = env;
31
+ if (clientConfig.capabilities?.service_worker && !cx.config.runtime.client?.Worker) {
31
32
  throw new Error(`The Service Worker configurator "config.runtime.client.Worker" is required in context.`);
32
33
  }
33
34
  const workerConfig = await (new cx.config.runtime.client.Worker(cx)).read();
34
- workerConfig.env = {};
35
+ workerConfig.env = env;
35
36
  // -----------
36
37
  if (!cx.config.deployment?.Layout) {
37
38
  throw new Error(`The Layout configurator "config.deployment.Layout" is required in context.`);
@@ -42,20 +43,15 @@ export async function generate() {
42
43
  const dirClient = Path.resolve(cx.CWD || '', layoutConfig.CLIENT_DIR);
43
44
  const dirWorker = Path.resolve(cx.CWD || '', layoutConfig.WORKER_DIR);
44
45
  const dirSelf = Path.dirname(Url.fileURLToPath(import.meta.url)).replace(/\\/g, '/');
45
- if (clientConfig.bundle_public_env || workerConfig.bundle_public_env) {
46
+ if (clientConfig.copy_public_variables) {
46
47
  if (!cx.config.deployment?.Env) {
47
48
  throw new Error(`The Layout configurator "config.deployment.Env" is required in context to bundle public env.`);
48
49
  }
49
50
  const envConfig = await (new cx.config.deployment.Env(cx)).read();
50
- const env = { ...envConfig.entries, ...process.env };
51
- for (const key in env) {
51
+ const $env = { ...envConfig.entries, ...process.env };
52
+ for (const key in $env) {
52
53
  if (!key.includes('PUBLIC_') && !key.includes('_PUBLIC')) continue;
53
- if (clientConfig.bundle_public_env) {
54
- clientConfig.env[key] = env[key];
55
- }
56
- if (workerConfig.bundle_public_env) {
57
- workerConfig.env[key] = env[key];
58
- }
54
+ env[key] = $env[key];
59
55
  }
60
56
  }
61
57
  // -----------
@@ -114,7 +110,14 @@ export async function generate() {
114
110
  gen.code.push(`const { start } = webqit.Webflo`);
115
111
  // ------------------
116
112
  // Bundle
117
- declareStart.call(cx, gen, dirClient, dirPublic, clientConfig, spaRouting);
113
+ const paramsObj = structuredClone(clientConfig);
114
+ if (paramsObj.capabilities?.service_worker) {
115
+ paramsObj.capabilities.service_worker = {
116
+ filename: workerConfig.filename,
117
+ scope: workerConfig.scope
118
+ };
119
+ }
120
+ declareStart.call(cx, gen, dirClient, dirPublic, paramsObj, spaRouting);
118
121
  await bundle.call(cx, gen, Path.join(dirPublic, outfileMain), true/* asModule */);
119
122
  // ------------------
120
123
  // Embed/unembed
@@ -192,8 +195,14 @@ export async function generate() {
192
195
  }
193
196
  }
194
197
  }
195
- declareStart.call(cx, gen, dirWorker, dirPublic, workerConfig, workerRouting);
196
- await bundle.call(cx, gen, Path.join(dirPublic, workerroot, clientConfig.service_worker.filename));
198
+ const paramsObj = structuredClone(workerConfig);
199
+ if (clientConfig.capabilities?.webpush) {
200
+ paramsObj.capabilities = {
201
+ webpush: true
202
+ };
203
+ }
204
+ declareStart.call(cx, gen, dirWorker, dirPublic, paramsObj, workerRouting);
205
+ await bundle.call(cx, gen, Path.join(dirPublic, workerroot, workerConfig.filename));
197
206
  // ------------------
198
207
  // Recurse
199
208
  workerGraphCallback && workerGraphCallback(workerroot, subworkerroots);
@@ -214,7 +223,7 @@ export async function generate() {
214
223
  await generateClient();
215
224
  Fs.existsSync(sparootsFile) && Fs.unlinkSync(sparootsFile);
216
225
  }
217
- if (clientConfig.service_worker.filename) {
226
+ if (clientConfig.capabilities?.service_worker) {
218
227
  await generateWorker('/');
219
228
  }
220
229
  }
@@ -2,11 +2,12 @@ import { WebfloRootClient1 } from './WebfloRootClient1.js';
2
2
  import { WebfloRootClient2 } from './WebfloRootClient2.js';
3
3
  import { WebfloSubClient } from './WebfloSubClient.js';
4
4
 
5
- export function start() {
5
+ export async function start() {
6
6
  const WebfloRootClient = window.navigation ? WebfloRootClient2 : WebfloRootClient1;
7
7
  const instance = WebfloRootClient.create(document, this || {});
8
- instance.initialize();
8
+ await instance.initialize();
9
9
  WebfloSubClient.defineElement();
10
+ return instance;
10
11
  }
11
12
 
12
13
  export { WebfloSubClient } from './WebfloSubClient.js';
@@ -8,10 +8,10 @@ export class CookieStorage extends WebfloCookieStorage {
8
8
  );
9
9
  }
10
10
 
11
- commit(response) {
11
+ async commit(response) {
12
12
  for (const cookieStr of this.render()) {
13
13
  response.headers.append('Set-Cookie', cookieStr);
14
14
  }
15
- super.commit();
15
+ await super.commit();
16
16
  }
17
17
  }
@@ -10,6 +10,6 @@ export class SessionStorage extends WebfloStorage {
10
10
  }
11
11
 
12
12
  async commit(response) {
13
- super.commit();
13
+ await super.commit();
14
14
  }
15
15
  }
@@ -25,13 +25,13 @@ export class WebfloWorker extends WebfloRuntime {
25
25
 
26
26
  static get SessionStorage() { return SessionStorage; }
27
27
 
28
- static get HttpUser() { return HttpUser; }
28
+ static get HttpUser() { return HttpUser; }
29
29
 
30
30
  static get Workport() { return Workport; }
31
31
 
32
32
  static create(cx) {
33
- return new this(this.Context.create(cx));
34
- }
33
+ return new this(this.Context.create(cx));
34
+ }
35
35
 
36
36
  #cx;
37
37
  get cx() { return this.#cx; }
@@ -44,7 +44,7 @@ export class WebfloWorker extends WebfloRuntime {
44
44
  this.#cx = cx;
45
45
  }
46
46
 
47
- initialize() {
47
+ async initialize() {
48
48
  // ONINSTALL
49
49
  const installHandler = (event) => {
50
50
  if (this.cx.params.skip_waiting) self.skipWaiting();
@@ -53,7 +53,7 @@ export class WebfloWorker extends WebfloRuntime {
53
53
  // Add files to cache
54
54
  event.waitUntil(self.caches.open(this.cx.params.cache_name).then(async cache => {
55
55
  if (this.cx.logger) { this.cx.logger.log('[ServiceWorker] Pre-caching resources.'); }
56
- for (const urls of [ 'cache_first_urls', 'cache_only_urls' ]) {
56
+ for (const urls of ['cache_first_urls', 'cache_only_urls']) {
57
57
  const _urls = (this.cx.params[urls] || []).map(c => c.trim()).filter(c => c && !pattern(c, self.origin).isPattern());
58
58
  await cache.addAll(_urls);
59
59
  }
@@ -74,7 +74,7 @@ export class WebfloWorker extends WebfloRuntime {
74
74
  return self.caches.delete(key);
75
75
  }
76
76
  }));
77
- })
77
+ })
78
78
  }
79
79
  resolve();
80
80
  }));
@@ -82,11 +82,11 @@ export class WebfloWorker extends WebfloRuntime {
82
82
  self.addEventListener('install', installHandler);
83
83
  self.addEventListener('activate', activateHandler);
84
84
  const uncontrols = this.control();
85
- return () => {
86
- self.removeEventListener('install', installHandler);
87
- self.removeEventListener('activate', activateHandler);
85
+ return () => {
86
+ self.removeEventListener('install', installHandler);
87
+ self.removeEventListener('activate', activateHandler);
88
88
  uncontrols();
89
- };
89
+ };
90
90
  }
91
91
 
92
92
  control() {
@@ -112,42 +112,54 @@ export class WebfloWorker extends WebfloRuntime {
112
112
  event.respondWith(this.navigate(event.request.url, event.request, { event }));
113
113
  }
114
114
  };
115
+ const webpushHandler = (event) => {
116
+ if (!(self.Notification && self.Notification.permission === 'granted')) return;
117
+ let data;
118
+ try {
119
+ data = event.data?.json() ?? {};
120
+ } catch(e) { return; }
121
+ const { type, title, ...params } = data;
122
+ if (type !== 'notification') return;
123
+ self.registration.showNotification(title, params);
124
+ };
115
125
  self.addEventListener('fetch', fetchHandler);
116
- return () => {
117
- self.removeEventListener('fetch', fetchHandler);
118
- };
126
+ self.addEventListener('push', webpushHandler);
127
+ return () => {
128
+ self.removeEventListener('fetch', fetchHandler);
129
+ self.removeEventListener('push', webpushHandler);
130
+ };
119
131
  }
120
132
 
121
- createRequest(href, init = {}) {
133
+ createRequest(href, init = {}) {
122
134
  if (init instanceof Request && init.url === (href.href || href)) {
123
135
  return init;
124
136
  }
125
137
  return new Request(href, init);
126
- }
138
+ }
127
139
 
128
140
  async navigate(url, init = {}, detail = {}) {
129
141
  // Resolve inputs
130
- const scope = { url, init, detail };
142
+ const scope = { url, init, detail };
131
143
  if (typeof scope.url === 'string') {
132
144
  scope.url = new URL(scope.url, self.location.origin);
133
145
  }
134
146
  // ---------------
135
- // Event lifecycle
136
- scope.eventLifecyclePromises = new Set;
137
- scope.eventLifecycleHooks = {
138
- waitUntil: (promise) => {
139
- promise = Promise.resolve(promise);
140
- scope.eventLifecyclePromises.add(promise);
141
- scope.eventLifecyclePromises.dirty = true;
142
- promise.then(() => scope.eventLifecyclePromises.delete(promise));
143
- },
144
- respondWith: async (response) => {
145
- if (scope.eventLifecyclePromises.dirty && !scope.eventLifecyclePromises.size) {
146
- throw new Error('Final response already sent');
147
- }
148
- return await this.execPush(scope.clientMessaging, response);
149
- },
150
- };
147
+ // Event lifecycle
148
+ scope.eventLifecyclePromises = new Set;
149
+ scope.eventLifecycleHooks = {
150
+ waitUntil: (promise) => {
151
+ promise = Promise.resolve(promise);
152
+ scope.eventLifecyclePromises.add(promise);
153
+ scope.eventLifecyclePromises.dirty = true;
154
+ promise.then(() => scope.eventLifecyclePromises.delete(promise));
155
+ },
156
+ respondWith: async (response) => {
157
+ if (scope.eventLifecyclePromises.dirty && !scope.eventLifecyclePromises.size) {
158
+ throw new Error('Final response already sent');
159
+ }
160
+ return await this.execPush(scope.clientMessaging, response);
161
+ },
162
+ };
151
163
  // Create and route request
152
164
  scope.request = this.createRequest(scope.url, scope.init);
153
165
  scope.cookies = this.constructor.CookieStorage.create(scope.request);
@@ -165,13 +177,15 @@ export class WebfloWorker extends WebfloRuntime {
165
177
  cookies: scope.cookies,
166
178
  session: scope.session,
167
179
  user: scope.user,
168
- client: scope.clientMessaging
180
+ client: scope.clientMessaging,
181
+ sdk: {}
169
182
  });
183
+ await this.setup(scope.httpEvent);
170
184
  // Restore session before dispatching
171
- if (scope.request.method === 'GET'
172
- && (scope.redirectMessageID = scope.httpEvent.url.query['redirect-message'])
173
- && (scope.redirectMessage = scope.session.get(`redirect-message:${scope.redirectMessageID}`))) {
174
- scope.session.delete(`redirect-message:${scope.redirectMessageID}`);
185
+ if (scope.request.method === 'GET'
186
+ && (scope.redirectMessageID = scope.httpEvent.url.query['redirect-message'])
187
+ && (scope.redirectMessage = scope.session.get(`redirect-message:${scope.redirectMessageID}`))) {
188
+ await scope.session.delete(`redirect-message:${scope.redirectMessageID}`);
175
189
  }
176
190
  // Dispatch for response
177
191
  scope.response = await this.dispatch(scope.httpEvent, {}, async (event) => {
@@ -182,30 +196,30 @@ export class WebfloWorker extends WebfloRuntime {
182
196
  return await this.remoteFetch(event.request);
183
197
  });
184
198
  // ---------------
185
- // Response processing
186
- scope.hasBackgroundActivity = scope.clientMessaging.isMessaging() || scope.eventLifecyclePromises.size || (scope.redirectMessage && !(scope.response instanceof Response && scope.response.headers.get('Location')));
187
- scope.response = await this.normalizeResponse(scope.httpEvent, scope.response, scope.hasBackgroundActivity);
199
+ // Response processing
200
+ scope.response = await this.normalizeResponse(scope.httpEvent, scope.response);
201
+ scope.hasBackgroundActivity = scope.clientMessaging.isMessaging() || scope.eventLifecyclePromises.size || (scope.redirectMessage && !scope.response.headers.get('Location'));
188
202
  if (scope.hasBackgroundActivity) {
189
203
  scope.response.headers.set('X-Background-Messaging', `ch:${scope.clientMessaging.port.name}`);
190
- }
204
+ }
191
205
  if (scope.response.headers.get('Location')) {
192
- if (scope.redirectMessage) {
193
- scope.session.set(`redirect-message:${scope.redirectMessageID}`, scope.redirectMessage);
194
- }
206
+ if (scope.redirectMessage) {
207
+ scope.session.set(`redirect-message:${scope.redirectMessageID}`, scope.redirectMessage);
208
+ }
195
209
  } else {
196
210
  if (scope.redirectMessage) {
197
- scope.eventLifecycleHooks.respondWith(scope.redirectMessage);
198
- }
211
+ scope.eventLifecycleHooks.respondWith(scope.redirectMessage);
212
+ }
199
213
  }
200
- Promise.all([...scope.eventLifecyclePromises]).then(() => {
201
- if (scope.clientMessaging.isMessaging()) {
202
- scope.clientMessaging.on('connected', () => {
203
- setTimeout(() => {
204
- scope.clientMessaging.close();
205
- }, 100);
206
- });
207
- } else scope.clientMessaging.close();
208
- });
214
+ Promise.all([...scope.eventLifecyclePromises]).then(() => {
215
+ if (scope.clientMessaging.isMessaging()) {
216
+ scope.clientMessaging.on('connected', () => {
217
+ setTimeout(() => {
218
+ scope.clientMessaging.close();
219
+ }, 100);
220
+ });
221
+ } else scope.clientMessaging.close();
222
+ });
209
223
  return scope.response;
210
224
  }
211
225
 
@@ -287,7 +301,7 @@ export class WebfloWorker extends WebfloRuntime {
287
301
 
288
302
  async getRequestCache(request) {
289
303
  const cacheName = request.headers.get('Accept') === 'application/json'
290
- ? this.cx.params.cache_name + '_json'
304
+ ? this.cx.params.cache_name + '_json'
291
305
  : this.cx.params.cache_name;
292
306
  return self.caches.open(cacheName);
293
307
  }
@@ -1,8 +1,9 @@
1
1
  import { WebfloWorker } from './WebfloWorker.js';
2
2
 
3
- export function start() {
3
+ export async function start() {
4
4
  const instance = WebfloWorker.create(this || {});
5
- instance.initialize();
5
+ await instance.initialize();
6
+ return instance;
6
7
  }
7
8
 
8
9
  export {
@@ -8,10 +8,10 @@ export class CookieStorage extends WebfloCookieStorage {
8
8
  );
9
9
  }
10
10
 
11
- commit(response) {
11
+ async commit(response) {
12
12
  for (const cookieStr of this.render()) {
13
13
  response.headers.append('Set-Cookie', cookieStr);
14
14
  }
15
- super.commit();
15
+ await super.commit();
16
16
  }
17
17
  }
@@ -44,10 +44,10 @@ export class SessionStorage extends WebfloStorage {
44
44
  return this.#sessionID;
45
45
  }
46
46
 
47
- commit(response, force = false) {
47
+ async commit(response, force = false) {
48
48
  if (response.headers.get('Set-Cookie', true).find((c) => c.name === '__sessid')) return;
49
- if (!force && !this.getAdded().length && !this.getDeleted().length) return;
49
+ //if (!force && !this.getAdded().length && !this.getDeleted().length) return;
50
50
  response.headers.append('Set-Cookie', `__sessid=${this.#sessionID}; Path=/; Secure; HttpOnly; SameSite=Lax; Max-Age=31536000`);
51
- super.commit();
51
+ await super.commit();
52
52
  }
53
53
  }