@webqit/webflo 1.0.18 → 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 +4 -3
  9. package/src/runtime-pi/WebfloRuntime.js +27 -19
  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 +17 -25
  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 +32 -13
  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
package/package.json CHANGED
@@ -12,7 +12,7 @@
12
12
  "vanila-javascript"
13
13
  ],
14
14
  "homepage": "https://webqit.io/tooling/webflo",
15
- "version": "1.0.18",
15
+ "version": "1.0.20",
16
16
  "license": "MIT",
17
17
  "repository": {
18
18
  "type": "git",
@@ -36,7 +36,7 @@
36
36
  },
37
37
  "dependencies": {
38
38
  "@octokit/webhooks": "^7.15.1",
39
- "@webqit/backpack": "^0.1.6",
39
+ "@webqit/backpack": "^0.1.8",
40
40
  "@webqit/observer": "^2.0.7",
41
41
  "@webqit/oohtml-ssr": "^2.1.1",
42
42
  "@webqit/util": "^0.8.11",
@@ -46,7 +46,8 @@
46
46
  "mime-types": "^2.1.33",
47
47
  "simple-git": "^2.20.1",
48
48
  "stream-slice": "^0.1.2",
49
- "urlpattern-polyfill": "^4.0.3"
49
+ "urlpattern-polyfill": "^4.0.3",
50
+ "web-push": "^3.6.7"
50
51
  },
51
52
  "devDependencies": {
52
53
  "chai": "^4.3.6",
@@ -19,31 +19,34 @@ export default class Client extends Dotfile {
19
19
  // Defaults merger
20
20
  withDefaults(config) {
21
21
  return this.merge({
22
+ spa_routing: true,
22
23
  bundle_filename: 'bundle.js',
23
24
  public_base_url: '/',
24
- spa_routing: true,
25
- service_worker: {
26
- filename: 'worker.js',
27
- scope: '/',
28
- support_push: false,
29
- vapid_key_env: 'VAPID_PUBLIC_KEY',
30
- push_registration_url_env: 'PUSH_REGISTRATION_PUBLIC_URL',
25
+ copy_public_variables: true,
26
+ capabilities: {
27
+ service_worker: true,
28
+ webpush: false,
29
+ custom_install: false,
30
+ exposed: ['display-mode', 'notifications'],
31
+ app_vapid_public_key_variable: 'APP_VAPID_PUBLIC_KEY',
32
+ app_public_webhook_url_variable: 'APP_PUBLIC_WEBHOOK_URL',
31
33
  },
32
- bundle_public_env: false,
33
34
  }, config, 'patch');
34
35
  }
35
36
 
36
37
  // Questions generator
37
38
  getSchema(config, choices = {}) {
38
- // Choices
39
- const CHOICES = this.merge({
40
- webqit_dependencies: [
41
- {value: 'externalize', title: 'Externalize'},
42
- {value: 'internalize', title: 'Internalize'},
43
- ],
44
- }, choices, 'patch');
45
39
  // Questions
46
40
  return [
41
+ {
42
+ name: 'spa_routing',
43
+ type: 'toggle',
44
+ message: '[spa_routing]: Enable Single Page Routing Mode',
45
+ active: 'YES',
46
+ inactive: 'NO',
47
+ initial: config.spa_routing,
48
+ validation: ['important'],
49
+ },
47
50
  {
48
51
  name: 'bundle_filename',
49
52
  type: 'text',
@@ -58,59 +61,60 @@ export default class Client extends Dotfile {
58
61
  validation: ['important'],
59
62
  },
60
63
  {
61
- name: 'bundle_public_env',
64
+ name: 'copy_public_variables',
62
65
  type: 'toggle',
63
- message: '[spa_routing]: Enable Single Page Routing Mode',
66
+ message: '[copy_public_variables]: Bundle public ENV variables?',
64
67
  active: 'YES',
65
68
  inactive: 'NO',
66
- initial: config.spa_routing,
69
+ initial: config.copy_public_variables,
67
70
  validation: ['important'],
68
71
  },
69
72
  {
70
- name: 'service_worker',
73
+ name: 'capabilities',
71
74
  controls: {
72
- name: 'service_worker',
75
+ name: 'capabilities',
73
76
  },
74
- initial: config.service_worker,
77
+ initial: config.capabilities,
75
78
  schema: [
76
79
  {
77
- name: 'filename',
78
- type: 'text',
79
- message: 'Specify the Service Worker filename',
80
+ name: 'service_worker',
81
+ type: 'toggle',
82
+ message: 'Enable service worker?',
83
+ active: 'YES',
84
+ inactive: 'NO',
80
85
  },
81
86
  {
82
- name: 'scope',
83
- type: 'text',
84
- message: 'Specify the Service Worker scope',
87
+ name: 'webpush',
88
+ type: 'toggle',
89
+ message: 'Support push-notifications?',
90
+ active: 'YES',
91
+ inactive: 'NO',
85
92
  },
86
93
  {
87
- name: 'support_push',
94
+ name: 'custom_install',
88
95
  type: 'toggle',
89
- message: 'Support push-notifications?',
96
+ message: 'Enable custom PWA install prompt?',
90
97
  active: 'YES',
91
98
  inactive: 'NO',
92
99
  },
93
100
  {
94
- name: 'vapid_key_env',
95
- type: (prev, answers) => answers.support_push ? 'text' : null,
96
- message: 'Enter the VAPID KEY env id for push notification subscription',
101
+ name: 'exposed',
102
+ type: 'list',
103
+ message: 'Specify features exposed on capabilities.exposed',
104
+ initial: (config.exposed || []).join(', '),
97
105
  },
98
106
  {
99
- name: 'push_registration_url_env',
100
- type: (prev, answers) => answers.support_push ? 'text' : null,
101
- message: 'Enter the URL for push notification subscription',
107
+ name: 'app_vapid_public_key_variable',
108
+ type: (prev, answers) => !answers.webpush ? null : 'text',
109
+ message: 'Enter the environment variable name for APP_VAPID_PUBLIC_KEY if not as written',
102
110
  },
103
- ],
104
- },
105
- {
106
- name: 'bundle_public_env',
107
- type: 'toggle',
108
- message: '[bundle_public_env]: Bundle public ENV variables?',
109
- active: 'YES',
110
- inactive: 'NO',
111
- initial: config.bundle_public_env,
112
- validation: ['important'],
113
- },
111
+ {
112
+ name: 'app_public_webhook_url_variable',
113
+ type: 'text',
114
+ message: 'Enter the environment variable name for APP_PUBLIC_WEBHOOK_URL if not as written',
115
+ },
116
+ ]
117
+ }
114
118
  ];
115
119
  }
116
120
  }
@@ -29,7 +29,12 @@ export default class Server extends Dotfile {
29
29
  force: false,
30
30
  },
31
31
  force_www: '',
32
- oohtml_support: 'full',
32
+ session_key_variable: 'APP_SESSION_KEY',
33
+ capabilities: {
34
+ webpush: false,
35
+ app_vapid_public_key_variable: 'APP_VAPID_PUBLIC_KEY',
36
+ app_vapid_private_key_variable: 'APP_VAPID_PRIVATE_KEY'
37
+ },
33
38
  }, config, 'patch');
34
39
  }
35
40
 
@@ -42,13 +47,6 @@ export default class Server extends Dotfile {
42
47
  {value: 'add',},
43
48
  {value: 'remove',},
44
49
  ],
45
- oohtml_support: [
46
- {value: 'full', title: 'full'},
47
- {value: 'namespacing', title: 'namespacing'},
48
- {value: 'scripting', title: 'scripting'},
49
- {value: 'templating', title: 'templating'},
50
- {value: 'none', title: 'none'},
51
- ],
52
50
  }, choices, 'patch');
53
51
  // Questions
54
52
  return [
@@ -63,6 +61,7 @@ export default class Server extends Dotfile {
63
61
  name: 'domains',
64
62
  type: 'list',
65
63
  message: '[domains]: Enter a list of allowed domains if necessary (comma-separated)',
64
+ initial: (config.domains || []).join(', '),
66
65
  validation: ['important'],
67
66
  },
68
67
  {
@@ -72,6 +71,12 @@ export default class Server extends Dotfile {
72
71
  choices: CHOICES.force_www,
73
72
  initial: this.indexOfInitial(CHOICES.force_www, config.force_www),
74
73
  },
74
+ {
75
+ name: 'session_key_variable',
76
+ type: 'text',
77
+ message: 'Enter the environment variable name for APP_SESSION_KEY if not as written',
78
+ initial: config.session_key_variable,
79
+ },
75
80
  {
76
81
  name: 'https',
77
82
  controls: {
@@ -113,12 +118,30 @@ export default class Server extends Dotfile {
113
118
  ],
114
119
  },
115
120
  {
116
- name: 'oohtml_support',
117
- type: 'select',
118
- message: '[oohtml_support]: Specify OOHTML support level',
119
- choices: CHOICES.oohtml_support,
120
- initial: this.indexOfInitial(CHOICES.oohtml_support, config.oohtml_support),
121
- validation: ['important'],
121
+ name: 'capabilities',
122
+ controls: {
123
+ name: 'capabilities',
124
+ },
125
+ initial: config.capabilities,
126
+ schema: [
127
+ {
128
+ name: 'webpush',
129
+ type: 'toggle',
130
+ message: 'Support push-notifications?',
131
+ active: 'YES',
132
+ inactive: 'NO',
133
+ },
134
+ {
135
+ name: 'app_vapid_public_key_variable',
136
+ type: (prev, answers) => !answers.webpush ? null : 'text',
137
+ message: 'Enter the environment variable name for APP_VAPID_PUBLIC_KEY if not as written',
138
+ },
139
+ {
140
+ name: 'app_vapid_private_key_variable',
141
+ type: (prev, answers) => !answers.webpush ? null : 'text',
142
+ message: 'Enter the environment variable name for APP_VAPID_PRIVATE_KEY if not as written',
143
+ },
144
+ ]
122
145
  },
123
146
  ];
124
147
  }
@@ -21,14 +21,15 @@ export default class Worker extends Dotfile {
21
21
  // Defaults merger
22
22
  withDefaults(config) {
23
23
  return this.merge({
24
+ filename: 'worker.js',
25
+ scope: '/',
26
+ skip_waiting: true,
24
27
  cache_name: 'cache_v0',
25
28
  default_fetching_strategy: 'network-first',
26
29
  network_first_urls: [],
27
30
  cache_first_urls: [],
28
31
  network_only_urls: [],
29
32
  cache_only_urls: [],
30
- skip_waiting: true,
31
- bundle_public_env: false,
32
33
  }, config, 'patch');
33
34
  }
34
35
 
@@ -49,6 +50,24 @@ export default class Worker extends Dotfile {
49
50
  }, choices, 'patch');
50
51
  // Questions
51
52
  return [
53
+ {
54
+ name: 'filename',
55
+ type: 'text',
56
+ message: 'Specify the Service Worker filename',
57
+ },
58
+ {
59
+ name: 'scope',
60
+ type: 'text',
61
+ message: 'Specify the Service Worker scope',
62
+ },
63
+ {
64
+ name: 'skip_waiting',
65
+ type: 'toggle',
66
+ message: 'Choose whether to skip the "waiting" state for updated Service Workers',
67
+ active: 'YES',
68
+ inactive: 'NO',
69
+ initial: config.skip_waiting,
70
+ },
52
71
  {
53
72
  name: 'cache_name',
54
73
  type: 'text',
@@ -86,24 +105,7 @@ export default class Worker extends Dotfile {
86
105
  type: (prev, answers) => answers.default_fetching_strategy === 'cache-only' ? null : 'list',
87
106
  message: 'Specify URLs for a "cache-only" fetching strategy (comma-separated, globe supported)',
88
107
  initial: (config.cache_only_urls || []).join(', '),
89
- },
90
- {
91
- name: 'skip_waiting',
92
- type: 'toggle',
93
- message: 'Choose whether to skip the "waiting" state for updated Service Workers',
94
- active: 'YES',
95
- inactive: 'NO',
96
- initial: config.skip_waiting,
97
- },
98
- {
99
- name: 'bundle_public_env',
100
- type: 'toggle',
101
- message: '[bundle_public_env]: Bundle public ENV variables?',
102
- active: 'YES',
103
- inactive: 'NO',
104
- initial: config.bundle_public_env,
105
- validation: ['important'],
106
- },
108
+ }
107
109
  ];
108
110
  }
109
111
  }
@@ -31,6 +31,8 @@ export class HttpEvent {
31
31
 
32
32
  get client() { return this.#init.client; }
33
33
 
34
+ get sdk() { return this.#init.sdk; }
35
+
34
36
  #requestCloneCallback;
35
37
  set onRequestClone(callback) {
36
38
  this.#requestCloneCallback = callback;
@@ -59,7 +61,7 @@ export class HttpEvent {
59
61
  }
60
62
  }
61
63
 
62
- #response = null;
64
+ #response = undefined;
63
65
  get response() { return this.#response; }
64
66
 
65
67
  async respondWith(response) {
@@ -92,7 +94,7 @@ export class HttpEvent {
92
94
  const messageID = (0 | Math.random() * 9e6).toString(36);
93
95
  const $url = new URL(url, this.request.url);
94
96
  $url.searchParams.set('redirect-message', messageID);
95
- this.session.set(`redirect-message:${messageID}`, data);
97
+ await this.session.set(`redirect-message:${messageID}`, data);
96
98
  await this.redirect($url, ...args);
97
99
  }
98
100
 
@@ -114,7 +116,7 @@ export class HttpEvent {
114
116
  if (endOfStream) {
115
117
  res(response);
116
118
  } else {
117
- await this.client.postMessage(response, { messageType: 'response' });
119
+ await this.respondWith(response);
118
120
  }
119
121
  });
120
122
  poll(typeof maxClock === 'number' && maxClock > 0 ? --maxClock : maxClock);
@@ -122,20 +124,32 @@ export class HttpEvent {
122
124
  poll(maxClock);
123
125
  };
124
126
  // Life cycle management
125
- this.client.on('connected', () => {
126
- state.connected = true;
127
- start();
128
- });
129
- this.client.on('empty', () => {
130
- state.connected = false;
131
- });
132
- this.client.handleMessages('navigation', (e) => {
133
- if (!crossNavigation
134
- || (crossNavigation === -1 && e.data.pathname === this.url.pathname)
135
- || (typeof crossNavigation === 'function' && !crossNavigation(e.data))) {
136
- state.navigatedAway = true;
137
- }
138
- });
127
+ if (this.#response === undefined) {
128
+ callback(async (response, endOfStream = false) => {
129
+ if (endOfStream) {
130
+ state.earlyTermination = true;
131
+ res(response);
132
+ } else {
133
+ await this.respondWith(response);
134
+ }
135
+ });
136
+ }
137
+ if (!state.earlyTermination) {
138
+ this.client.on('connected', () => {
139
+ state.connected = true;
140
+ start();
141
+ });
142
+ this.client.on('empty', () => {
143
+ state.connected = false;
144
+ });
145
+ this.client.handleMessages('navigation', (e) => {
146
+ if (!crossNavigation
147
+ || (crossNavigation === -1 && e.data.pathname === this.url.pathname)
148
+ || (typeof crossNavigation === 'function' && !crossNavigation(e.data))) {
149
+ state.navigatedAway = true;
150
+ }
151
+ });
152
+ }
139
153
  setTimeout(() => {
140
154
  if (!state.connected) {
141
155
  res();
@@ -154,8 +168,9 @@ export class HttpEvent {
154
168
  request = !_isEmpty(init) ? new Request(url, init) : url;
155
169
  } else {
156
170
  url = new xURL(url, this.#url.origin);
157
- init = await Request.copy(this.request, init);
158
- request = new Request(url, { ...init, referrer: this.request.url });
171
+ const { url: _, ...$init } = await Request.copy(this.request, init);
172
+ init = $init;
173
+ request = Request.create(url, { ...init, referrer: this.request.url });
159
174
  }
160
175
  return new HttpEvent(this, { ...this.#init, request });
161
176
  }
@@ -19,21 +19,13 @@ export class HttpUser extends WebfloStorage {
19
19
  }
20
20
 
21
21
  get #dict() {
22
- if (!this.#session.has('user')) {
23
- this.#session.set('user', {});
24
- }
25
- return this.#session.get('user');
22
+ return this.#session.get('user') || {};
26
23
  }
27
24
 
28
25
  [ Symbol.iterator ]() { return this.entries()[ Symbol.iterator ](); }
29
26
 
30
27
  get size() { return Object.keys(this.#dict).length; }
31
28
 
32
- set(key, value) {
33
- Reflect.set(this.#dict, key, value);
34
- return this;
35
- }
36
-
37
29
  get(key) {
38
30
  return Reflect.get(this.#dict, key);
39
31
  }
@@ -42,10 +34,6 @@ export class HttpUser extends WebfloStorage {
42
34
  return Reflect.has(this.#dict, key);
43
35
  }
44
36
 
45
- delete(key) {
46
- return Reflect.deleteProperty(this.#dict, key);
47
- }
48
-
49
37
  keys() {
50
38
  return Object.keys(this.#dict);
51
39
  }
@@ -58,24 +46,46 @@ export class HttpUser extends WebfloStorage {
58
46
  return Object.entries(this.#dict);
59
47
  }
60
48
 
61
- clear() {
62
- for (const key of this.keys()) {
63
- Reflect.deleteProperty(this.#dict, key);
49
+ forEach(callback) {
50
+ this.entries().forEach(callback);
51
+ }
52
+
53
+ async set(key, value) {
54
+ if (!this.#session.has('user')) {
55
+ await this.#session.set('user', {});
64
56
  }
57
+ Reflect.set(this.#dict, key, value);
58
+ await this.emit(key, value);
59
+ return this;
65
60
  }
66
61
 
67
- forEach(callback) {
68
- this.entries().forEach(callback);
62
+ async delete(key) {
63
+ if (!this.#session.has('user')) {
64
+ await this.#session.set('user', {});
65
+ }
66
+ Reflect.deleteProperty(this.#dict, key);
67
+ await this.emit(key);
68
+ return this;
69
69
  }
70
70
 
71
- json(arg = null) {
71
+ async clear() {
72
+ for (const key of this.keys()) {
73
+ Reflect.deleteProperty(this.#dict, key);
74
+ }
75
+ await this.emit();
76
+ return this;
77
+ }
78
+
79
+ async json(arg = null) {
72
80
  if (!arguments.length || typeof arg === 'boolean') {
73
81
  return {...this.#dict};
74
82
  }
75
83
  if (!_isObject(arg)) {
76
84
  throw new Error(`Argument must be a valid JSON object`);
77
85
  }
78
- Object.assign(this.#dict, arg);
86
+ return await Promise.all(Object.entries(arg).map(([key, value]) => {
87
+ return this.set(key, value);
88
+ }));
79
89
  }
80
90
 
81
91
  isSignedIn() {
@@ -90,22 +100,11 @@ export class HttpUser extends WebfloStorage {
90
100
  }
91
101
 
92
102
  async signOut() {
93
- const handler = this.getReverseHandlers().get('id')?.[0];
94
- let response;
95
- if (typeof handler === 'string') {
96
- response = new Response(null, { status: 302, headers: {
97
- Location: url
98
- }});
99
- }
100
- if (typeof handler === 'function') {
101
- response = await handler(this);
102
- }
103
- this.clear();
104
- return response;
103
+ await this.clear();
105
104
  }
106
105
 
107
- confirm(data, callback, options = {}) {
108
- return new Promise((resolve) => {
106
+ async confirm(data, callback, options = {}) {
107
+ return await new Promise((resolve) => {
109
108
  this.#client.postRequest(
110
109
  data,
111
110
  (event) => resolve(callback ? callback(event) : event),
@@ -114,8 +113,8 @@ export class HttpUser extends WebfloStorage {
114
113
  });
115
114
  }
116
115
 
117
- prompt(data, callback, options = {}) {
118
- return new Promise((resolve) => {
116
+ async prompt(data, callback, options = {}) {
117
+ return await new Promise((resolve) => {
119
118
  this.#client.postRequest(
120
119
  data,
121
120
  (event) => resolve(callback ? callback(event) : event),
@@ -9,19 +9,19 @@ export class WebfloCookieStorage extends WebfloStorage {
9
9
  this.saveOriginals();
10
10
  }
11
11
 
12
- render() {
13
- return this.getAdded().map((key) => renderCookieObj({ name: key, ...this.get(key, true) })).concat(
14
- this.getDeleted().map((key) => renderCookieObj({ name: key, value: '', maxAge: 0 }))
15
- );
16
- }
17
-
18
- set(key, value) {
12
+ async set(key, value) {
19
13
  if (!_isObject(value)) { value = { name: key, value }; }
20
- return super.set(key, value);
14
+ return await super.set(key, value);
21
15
  }
22
16
 
23
17
  get(key, withDetail = false) {
24
18
  if (!withDetail) return super.get(key)?.value;
25
19
  return super.get(key);
26
20
  }
21
+
22
+ render() {
23
+ return this.getAdded().map((key) => renderCookieObj({ name: key, ...this.get(key, true) })).concat(
24
+ this.getDeleted().map((key) => renderCookieObj({ name: key, value: '', maxAge: 0 }))
25
+ );
26
+ }
27
27
  }
@@ -8,7 +8,7 @@ export class WebfloRouter {
8
8
  this.path = _isArray(path) ? path : (path + '').split('/').filter(a => a);
9
9
  }
10
10
 
11
- async route(method, event, arg, _default, remoteFetch = null) {
11
+ async route(method, event, arg = null, _default = null, remoteFetch = null, requestLifecycle = null) {
12
12
 
13
13
  const $this = this;
14
14
  const $runtime = this.cx.runtime;
@@ -58,7 +58,7 @@ export class WebfloRouter {
58
58
  nextTick.event = await thisTick.event.with(newDestination, _request, requestInit);
59
59
  } else {
60
60
  nextTick.event = await thisTick.event.with(newDestination, requestInit);
61
- }
61
+ }
62
62
  nextTick.source = thisTick.destination.join('/');
63
63
  nextTick.destination = newDestination.split('?').shift().split('/').map(a => a.trim()).filter(a => a);
64
64
  nextTick.trail = _args[1].startsWith('/') ? [] : thisTick.trail.reduce((_commonRoot, _seg, i) => _commonRoot.length === i && _seg === nextTick.destination[i] ? _commonRoot.concat(_seg) : _commonRoot, []);
@@ -74,9 +74,10 @@ export class WebfloRouter {
74
74
  _next.stepname = nextPathname[0];
75
75
  // -------------
76
76
  return new Promise(async (res) => {
77
- thisTick.event.onRespondWith = (response) => {
77
+ thisTick.event.onRespondWith = async (response) => {
78
78
  thisTick.event.onRespondWith = null;
79
79
  res(response);
80
+ await requestLifecycle.responsePromise;
80
81
  };
81
82
  const $returnValue = Promise.resolve(handler.call(thisContext, thisTick.event, thisTick.arg, _next/*next*/, remoteFetch));
82
83
  // This should listen first before waitUntil's listener
@@ -2,27 +2,35 @@ import { _isObject } from '@webqit/util/js/index.js';
2
2
 
3
3
  export class WebfloRuntime {
4
4
 
5
- async dispatch(httpEvent, context, crossLayerFetch) {
6
- // Exec routing
5
+ async setup(httpEvent) {
7
6
  const router = new this.constructor.Router(this.cx, httpEvent.url.pathname);
8
- const route = async () => {
9
- return await router.route([httpEvent.request.method, 'default'], httpEvent, context, async (event) => {
10
- return crossLayerFetch(event);
11
- }, (...args) => this.remoteFetch(...args));
12
- };
13
- try {
14
- // Route for response
15
- return await (this.cx.middlewares || []).concat(route).reverse().reduce((next, fn) => {
16
- return () => fn.call(this.cx, httpEvent, router, next);
17
- }, null)();
18
-
19
- } catch (e) {
20
- console.error(e);
21
- return new Response(null, { status: 500, statusText: e.message });
22
- }
7
+ return await router.route(['SETUP'], httpEvent);
23
8
  }
24
9
 
25
- async normalizeResponse(httpEvent, response, forceCommit = false) {
10
+ async dispatch(httpEvent, context, crossLayerFetch) {
11
+ const requestLifecycle = {};
12
+ requestLifecycle.responsePromise = new Promise(async (res) => {
13
+ // Exec routing
14
+ const router = new this.constructor.Router(this.cx, httpEvent.url.pathname);
15
+ const route = async () => {
16
+ return await router.route([httpEvent.request.method, 'default'], httpEvent, context, async (event) => {
17
+ return crossLayerFetch(event);
18
+ }, (...args) => this.remoteFetch(...args), requestLifecycle);
19
+ };
20
+ try {
21
+ // Route for response
22
+ res(await (this.cx.middlewares || []).concat(route).reverse().reduce((next, fn) => {
23
+ return () => fn.call(this.cx, httpEvent, router, next);
24
+ }, null)());
25
+ } catch (e) {
26
+ console.error(e);
27
+ res(new Response(null, { status: 500, statusText: e.message }));
28
+ }
29
+ });
30
+ return await requestLifecycle.responsePromise;
31
+ }
32
+
33
+ async normalizeResponse(httpEvent, response) {
26
34
  // Normalize response
27
35
  if (!(response instanceof Response)) {
28
36
  response = typeof response === 'undefined'
@@ -31,7 +39,7 @@ export class WebfloRuntime {
31
39
  }
32
40
  // Commit data
33
41
  for (const storage of [httpEvent.cookies, httpEvent.session, httpEvent.storage]) {
34
- await storage?.commit?.(response, forceCommit);
42
+ await storage?.commit?.(response);
35
43
  }
36
44
  return response;
37
45
  }