@webqit/webflo 1.0.19 → 1.0.21

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 +7 -4
  2. package/src/config-pi/runtime/Client.js +50 -46
  3. package/src/config-pi/runtime/Server.js +77 -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 +8 -84
  7. package/src/runtime-pi/WebfloCookieStorage.js +28 -12
  8. package/src/runtime-pi/WebfloRouter.js +2 -2
  9. package/src/runtime-pi/WebfloRuntime.js +9 -4
  10. package/src/runtime-pi/WebfloStorage.js +91 -34
  11. package/src/runtime-pi/client/Capabilities.js +211 -0
  12. package/src/runtime-pi/client/CookieStorage.js +3 -3
  13. package/src/runtime-pi/client/SessionStorage.js +8 -25
  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 +6 -4
  22. package/src/runtime-pi/client/worker/SessionStorage.js +3 -7
  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 +6 -4
  26. package/src/runtime-pi/server/SessionStorage.js +17 -19
  27. package/src/runtime-pi/server/WebfloServer.js +66 -12
  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,6 +1,7 @@
1
1
  import { WebfloClient } from './WebfloClient.js';
2
2
  import { Context } from './Context.js';
3
3
  import { Workport } from './Workport.js';
4
+ import { Capabilities } from './Capabilities.js';
4
5
 
5
6
  const { Observer } = webqit;
6
7
 
@@ -10,6 +11,8 @@ export class WebfloRootClient1 extends WebfloClient {
10
11
 
11
12
  static get Workport() { return Workport; }
12
13
 
14
+ static get Capabilities() { return Capabilities; }
15
+
13
16
  static create(host, cx = {}) {
14
17
  return new this(host, this.Context.create(cx));
15
18
  }
@@ -17,6 +20,15 @@ export class WebfloRootClient1 extends WebfloClient {
17
20
  #cx;
18
21
  get cx() { return this.#cx; }
19
22
 
23
+ #network;
24
+ get network() { return this.#network; }
25
+
26
+ #workport;
27
+ get workport() { return this.#workport; }
28
+
29
+ #capabilities;
30
+ get capabilities() { return this.#capabilities; }
31
+
20
32
  constructor(host, cx) {
21
33
  if (!(host instanceof Document)) {
22
34
  throw new Error('Argument #1 must be a Document instance');
@@ -26,11 +38,42 @@ export class WebfloRootClient1 extends WebfloClient {
26
38
  throw new Error('Argument #2 must be a Webflo Context instance');
27
39
  }
28
40
  this.#cx = cx;
41
+ this.#network = { status: window.navigator.onLine };
29
42
  }
30
43
 
31
- initialize() {
32
- // Main initializations
33
- let undoControl = super.initialize();
44
+ async initialize() {
45
+ // --------
46
+ // INITIALIZATIONS
47
+ const cleanups = [await super.initialize()];
48
+ // --------
49
+ // Service Worker && Capabilities
50
+ if (this.cx.params.capabilities?.service_worker?.filename) {
51
+ const { service_worker: { filename, ...restServiceWorkerParams } = {} } = this.cx.params.capabilities;
52
+ this.#workport = await this.constructor.Workport.initialize(null, (this.cx.params.public_base_url || '') + filename, restServiceWorkerParams);
53
+ cleanups.push(() => this.#workport.close());
54
+ }
55
+ this.#capabilities = await this.constructor.Capabilities.initialize({ ...this.cx.params.capabilities, env: this.cx.params.env });
56
+ cleanups.push(() => this.#capabilities.close());
57
+ // --------
58
+ // Bind network status handlers
59
+ const onlineHandler = () => Observer.set(this.network, 'status', window.navigator.onLine);
60
+ window.addEventListener('online', onlineHandler);
61
+ window.addEventListener('offline', onlineHandler);
62
+ cleanups.push(() => {
63
+ window.removeEventListener('online', onlineHandler);
64
+ window.removeEventListener('offline', onlineHandler);
65
+ });
66
+ // --------
67
+ // Window opener pinging
68
+ let beforeunloadCleanup;
69
+ if (window.opener) {
70
+ const beforeunloadHandler = () => window.opener.postMessage('close');
71
+ window.addEventListener('beforeunload', beforeunloadHandler);
72
+ cleanups.push(() => {
73
+ window.removeEventListener('beforeunload', beforeunloadHandler);
74
+ });
75
+ }
76
+ // --------
34
77
  // Bind global prompt handlers
35
78
  const promptsHandler = (e) => {
36
79
  e.stopPropagation();
@@ -49,9 +92,10 @@ export class WebfloRootClient1 extends WebfloClient {
49
92
  });
50
93
  }, 10);
51
94
  };
52
- this.backgroundMessaging.handleMessages('confirm', promptsHandler);
53
- this.backgroundMessaging.handleMessages('prompt', promptsHandler);
54
- // Respond to background activity request at pageload
95
+ cleanups.push(this.backgroundMessaging.handleMessages('confirm', promptsHandler));
96
+ cleanups.push(this.backgroundMessaging.handleMessages('prompt', promptsHandler));
97
+ // --------
98
+ // HYDRATION
55
99
  const scope = {};
56
100
  if (scope.backgroundMessagingMeta = document.querySelector('meta[name="X-Background-Messaging"]')) {
57
101
  scope.backgroundMessaging = this.$createBackgroundMessagingFrom(scope.backgroundMessagingMeta.content);
@@ -66,32 +110,9 @@ export class WebfloRootClient1 extends WebfloClient {
66
110
  });
67
111
  } catch(e) {}
68
112
  }
69
- // Service Worker && COMM
70
- if (this.cx.params.service_worker?.filename) {
71
- const { public_base_url: base, service_worker: { filename, ...restServiceWorkerParams } } = this.cx.params;
72
- const { vapid_key_env, push_registration_url_env, ..._restServiceWorkerParams } = restServiceWorkerParams;
73
- const swParams = {
74
- ..._restServiceWorkerParams,
75
- VAPID_PUBLIC_KEY: this.cx.params.env[vapid_key_env],
76
- PUSH_REGISTRATION_PUBLIC_URL: this.cx.params.env[push_registration_url_env],
77
- startMessages: true
78
- };
79
- this.workport = new this.constructor.Workport;
80
- this.workport.registerServiceWorker(base + filename, swParams);
81
- }
82
- if (window.opener) {
83
- // Window opener pinging
84
- const $undoControl = undoControl;
85
- const beforeunloadHandler = () => {
86
- window.opener.postMessage('close');
87
- };
88
- window.addEventListener('beforeunload', beforeunloadHandler);
89
- undoControl = () => {
90
- window.removeEventListener('beforeunload', beforeunloadHandler);
91
- $undoControl();
92
- };
93
- }
94
- return undoControl
113
+ // --------
114
+ // CLEAN UP
115
+ return () => cleanups.forEach((c) => c());
95
116
  }
96
117
 
97
118
  /**
@@ -102,7 +123,7 @@ export class WebfloRootClient1 extends WebfloClient {
102
123
 
103
124
  control() {
104
125
  // IMPORTANT: we're calling super.controlClassic()
105
- const undoControl = super.controlClassic((newHref) => {
126
+ const cleanupSuper = super.controlClassic((newHref) => {
106
127
  try {
107
128
  // Save current scroll position
108
129
  window.history.replaceState({
@@ -134,7 +155,7 @@ export class WebfloRootClient1 extends WebfloClient {
134
155
  window.addEventListener('popstate', popstateHandler);
135
156
  return () => {
136
157
  this.host.removeEventListener('popstate', popstateHandler);
137
- undoControl();
158
+ cleanupSuper();
138
159
  };
139
160
  }
140
161
 
@@ -9,14 +9,14 @@ export class WebfloRootClient2 extends WebfloRootClient1 {
9
9
  let navigationOrigins = [];
10
10
  // Capture all link-clicks
11
11
  const clickHandler = (e) => {
12
- if (!this._canIntercept(e)) return;
12
+ if (!this._canIntercept(e) || e.defaultPrevented) return;
13
13
  let anchorEl = e.target.closest('a');
14
14
  if (!anchorEl || !anchorEl.href || anchorEl.target) return;
15
15
  navigationOrigins = [anchorEl, null, anchorEl.closest('[navigationcontext]')];
16
16
  };
17
17
  // Capture all form-submits
18
18
  const submitHandler = (e) => {
19
- if (!this._canIntercept(e)) return;
19
+ if (!this._canIntercept(e) || e.defaultPrevented) return;
20
20
  navigationOrigins = [e.submitter, e.target.closest('form'), e.target.closest('[navigationcontext]')];
21
21
  };
22
22
  // Handle navigation event which happens after the above
@@ -50,9 +50,9 @@ export class WebfloSubClient extends WebfloClient {
50
50
  this.location = newValue;
51
51
  }
52
52
 
53
- connectedCallback() {
53
+ async connectedCallback() {
54
54
  this.#superRuntime = (this.parentNode?.closest(embedTagNames) || document).webfloRuntime;
55
- this.#webfloControllerUninitialize = WebfloSubClient.create(this, this.#superRuntime).initialize();
55
+ this.#webfloControllerUninitialize = await WebfloSubClient.create(this, this.#superRuntime).initialize();
56
56
  }
57
57
 
58
58
  disconnectedCallback() {
@@ -70,8 +70,12 @@ export class WebfloSubClient extends WebfloClient {
70
70
 
71
71
  get cx() { return this.#superRuntime.cx; }
72
72
 
73
+ get network() { return this.#superRuntime.network; }
74
+
73
75
  get workport() { return this.#superRuntime.workport; }
74
76
 
77
+ get capabilities() { return this.#superRuntime.capabilities; }
78
+
75
79
  get withViewTransitions() { return this.host.hasAttribute('viewtransitions'); }
76
80
 
77
81
  constructor(host, superRuntime) {
@@ -85,11 +89,11 @@ export class WebfloSubClient extends WebfloClient {
85
89
  this.#superRuntime = superRuntime;
86
90
  }
87
91
 
88
- initialize() {
92
+ async initialize() {
89
93
  if (this.host.location.origin !== window.location.origin) {
90
94
  throw new Error(`Webflo embeddable origin violation in "${window.location}"`);
91
95
  }
92
- const uncontrols = super.initialize();
96
+ const cleanupSuper = await super.initialize();
93
97
  this.backgroundMessaging.setParent(this.#superRuntime.backgroundMessaging);
94
98
  if (this.host.getAttribute('location')) {
95
99
  this.navigate(this.location.href);
@@ -98,7 +102,7 @@ export class WebfloSubClient extends WebfloClient {
98
102
  if (this.backgroundMessaging.parentNode === this.#superRuntime.backgroundMessaging) {
99
103
  this.backgroundMessaging.setParent(null);
100
104
  }
101
- uncontrols();
105
+ cleanupSuper();
102
106
  };
103
107
  }
104
108
 
@@ -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,12 @@ export class CookieStorage extends WebfloCookieStorage {
8
8
  );
9
9
  }
10
10
 
11
- commit(response) {
12
- for (const cookieStr of this.render()) {
13
- response.headers.append('Set-Cookie', cookieStr);
11
+ async commit(response = null) {
12
+ if (response) {
13
+ for (const cookieStr of await this.render()) {
14
+ response.headers.append('Set-Cookie', cookieStr);
15
+ }
14
16
  }
15
- super.commit();
17
+ await super.commit();
16
18
  }
17
19
  }
@@ -2,14 +2,10 @@ import { WebfloStorage } from '../../WebfloStorage.js';
2
2
 
3
3
  export class SessionStorage extends WebfloStorage {
4
4
  static create(request) {
5
- return new this(request);
5
+ return new this({}, null, request);
6
6
  }
7
7
 
8
- constructor(request) {
9
- super(request, true);
10
- }
11
-
12
- async commit(response) {
13
- super.commit();
8
+ async commit(response = null) {
9
+ await super.commit();
14
10
  }
15
11
  }