@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
@@ -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 = await 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,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
  }
@@ -1,7 +1,7 @@
1
1
  import { WebfloStorage } from '../WebfloStorage.js';
2
2
  import crypto from 'crypto';
3
3
 
4
- const sessionStorage = new Map;
4
+ const inmemSessionRegistry = new Map;
5
5
  export class SessionStorage extends WebfloStorage {
6
6
 
7
7
  static create(request, params = {}) {
@@ -26,28 +26,26 @@ export class SessionStorage extends WebfloStorage {
26
26
  sessionID = crypto.randomUUID();
27
27
  }
28
28
  }
29
- if (sessionStorage.has(sessionID)) {
30
- return sessionStorage.get(sessionID);
31
- }
32
- const instance = new this(request, sessionID);
33
- sessionStorage.set(sessionID, instance);
34
- return instance;
35
- }
36
-
37
- constructor(request, sessionID) {
38
- super(request, true);
39
- this.#sessionID = sessionID;
29
+ return new this(params.registry || inmemSessionRegistry, sessionID, request);
40
30
  }
41
31
 
42
32
  #sessionID;
43
- get sessionID() {
44
- return this.#sessionID;
33
+ get sessionID() { return this.#sessionID; }
34
+
35
+ constructor(reqistry, sessionID, request) {
36
+ super(
37
+ reqistry,
38
+ `session:${sessionID}`,
39
+ request,
40
+ true
41
+ );
42
+ this.#sessionID = sessionID;
45
43
  }
46
44
 
47
- commit(response, force = false) {
48
- if (response.headers.get('Set-Cookie', true).find((c) => c.name === '__sessid')) return;
49
- if (!force && !this.getAdded().length && !this.getDeleted().length) return;
50
- response.headers.append('Set-Cookie', `__sessid=${this.#sessionID}; Path=/; Secure; HttpOnly; SameSite=Lax; Max-Age=31536000`);
51
- super.commit();
45
+ async commit(response = null) {
46
+ if (response && !response.headers.get('Set-Cookie', true).find((c) => c.name === '__sessid')) {
47
+ response.headers.append('Set-Cookie', `__sessid=${this.#sessionID}; Path=/; Secure; HttpOnly; SameSite=Lax; Max-Age=31536000`);
48
+ }
49
+ await super.commit();
52
50
  }
53
51
  }
@@ -54,6 +54,9 @@ export class WebfloServer extends WebfloRuntime {
54
54
  // Typically for access by Router
55
55
  get cx() { return this.#cx; }
56
56
 
57
+ #sdk = {};
58
+ get sdk() { return this.#sdk; }
59
+
57
60
  constructor(cx) {
58
61
  super();
59
62
  if (!(cx instanceof this.constructor.Context)) {
@@ -99,6 +102,46 @@ export class WebfloServer extends WebfloRuntime {
99
102
  }));
100
103
  }
101
104
  // ---------------
105
+ if (this.#cx.server.capabilities?.database) {
106
+ if (this.#cx.server.capabilities.database_dialect !== 'postgres') {
107
+ throw new Error(`Only postgres supported for now for database dialect`);
108
+ }
109
+ if (this.#cx.env.entries[this.#cx.server.capabilities.database_url_variable]) {
110
+ const { SQLClient } = await import('@linked-db/linked-ql/sql');
111
+ const { default: pg } = await import('pg');
112
+ // Obtain pg client
113
+ const pgClient = new pg.Pool({
114
+ connectionString: this.#cx.env.entries[this.#cx.server.capabilities.database_url_variable],
115
+ database: 'postgres',
116
+ });
117
+ // Connect
118
+ await pgClient.connect();
119
+ this.#sdk.db = new SQLClient(pgClient, { dialect: 'postgres' });
120
+ } else {
121
+ //const { ODBClient } = await import('@linked-db/linked-ql/odb');
122
+ //this.#sdk.db = new ODBClient({ dialect: 'postgres' });
123
+ }
124
+ }
125
+ if (this.#cx.server.capabilities?.redis && this.#cx.env.entries[this.#cx.server.capabilities.redis_url_variable]) {
126
+ const { Redis } = await import('ioredis');
127
+ this.#sdk.redis = !this.#cx.env.entries[this.#cx.server.capabilities.redis_url_variable]
128
+ ? new Redis : new Redis(this.#cx.env.entries[this.#cx.server.capabilities.redis_url_variable], {
129
+ tls: { rejectUnauthorized: false }, // Required for Upstash
130
+ });
131
+ }
132
+ if (this.#cx.server.capabilities?.webpush) {
133
+ const { default: webpush } = await import('web-push');
134
+ this.#sdk.webpush = webpush;
135
+ if (this.#cx.env.entries[this.#cx.server.capabilities.vapid_public_key_variable]
136
+ && this.#cx.env.entries[this.#cx.server.capabilities.vapid_private_key_variable]) {
137
+ webpush.setVapidDetails(
138
+ this.#cx.server.capabilities.vapid_subject,
139
+ this.#cx.env.entries[this.#cx.server.capabilities.vapid_public_key_variable],
140
+ this.#cx.env.entries[this.#cx.server.capabilities.vapid_private_key_variable]
141
+ );
142
+ }
143
+ }
144
+ // ---------------
102
145
  this.control();
103
146
  if (this.#cx.logger) {
104
147
  if (this.#servers.size) {
@@ -196,7 +239,7 @@ export class WebfloServer extends WebfloRuntime {
196
239
  if (!scope.error) {
197
240
  if (!hosts.includes(scope.url.hostname) && !hosts.includes('*')) {
198
241
  scope.error = 'Unrecognized host';
199
- } else if (scope.url.protocol === 'ws:' && this.#cx.server.https.force) {
242
+ } else if (scope.url.protocol === 'ws:' && this.#cx.server.https.port && this.#cx.server.https.force) {
200
243
  scope.error = `Only secure connections allowed (wss:)`;
201
244
  } else if (scope.url.hostname.startsWith('www.') && this.#cx.server.force_www === 'remove') {
202
245
  scope.error = `Connections not allowed over the www subdomain`;
@@ -208,7 +251,7 @@ export class WebfloServer extends WebfloRuntime {
208
251
  // Level 3 validation
209
252
  // and actual processing
210
253
  scope.request = this.createRequest(scope.url.href, requestInit);
211
- scope.session = this.constructor.SessionStorage.create(scope.request, { secret: this.#cx.env.entries.SESSION_KEY });
254
+ scope.session = this.constructor.SessionStorage.create(scope.request, { secret: this.#cx.env.entries[this.#cx.server.session_key_variable] });
212
255
  if (!scope.error) {
213
256
  if (!(scope.clientMessagingRegistry = this.#globalMessagingRegistry.get(scope.session.sessionID))) {
214
257
  scope.error = `Lost or invalid clientID`;
@@ -257,7 +300,7 @@ export class WebfloServer extends WebfloRuntime {
257
300
  if (!hosts.includes(scope.url.hostname) && !hosts.includes('*')) {
258
301
  scope.exit = { status: 500 };
259
302
  scope.exitMessage = 'Unrecognized host';
260
- } else if (scope.url.protocol === 'http:' && this.#cx.server.https.force) {
303
+ } else if (scope.url.protocol === 'http:' && this.#cx.server.https.port && this.#cx.server.https.force) {
261
304
  scope.exit = {
262
305
  status: 302,
263
306
  headers: { Location: (scope.url.protocol = 'https:', scope.url.href) }
@@ -289,7 +332,9 @@ export class WebfloServer extends WebfloRuntime {
289
332
  if (!scope.response) {
290
333
  scope.response = await this.navigate(fullUrl, requestInit, {
291
334
  request: nodeRequest,
292
- response: nodeResponse
335
+ response: nodeResponse,
336
+ ipAddress: nodeRequest.headers['x-forwarded-for']?.split(',')[0]
337
+ || nodeRequest.socket.remoteAddress
293
338
  });
294
339
  }
295
340
  // -----------------
@@ -491,7 +536,8 @@ export class WebfloServer extends WebfloRuntime {
491
536
  },
492
537
  respondWith: async (response, isRedirectMessage = false) => {
493
538
  if (!isRedirectMessage && scope.eventLifecyclePromises.dirty && !scope.eventLifecyclePromises.size) {
494
- throw new Error('Final response already sent');
539
+ console.error('Final response already sent');
540
+ return;
495
541
  }
496
542
  return await this.execPush(scope.clientMessaging, response);
497
543
  },
@@ -503,7 +549,13 @@ export class WebfloServer extends WebfloRuntime {
503
549
  : [];
504
550
  scope.request = this.createRequest(scope.url.href, scope.init, scope.autoHeaders.filter((header) => header.type === 'request'));
505
551
  scope.cookies = this.constructor.CookieStorage.create(scope.request);
506
- scope.session = this.constructor.SessionStorage.create(scope.request, { secret: this.#cx.env.entries.SESSION_KEY });
552
+ scope.session = this.constructor.SessionStorage.create(scope.request, {
553
+ secret: this.#cx.env.entries[this.#cx.server.session_key_variable],
554
+ registry: this.#sdk.redis && {
555
+ get: async (key) => { return await this.#sdk.redis.hgetall(key) },
556
+ set: async (key, value) => { return await this.#sdk.redis.hset(key, value) },
557
+ },
558
+ });
507
559
  const sessionID = scope.session.sessionID;
508
560
  if (!this.#globalMessagingRegistry.has(sessionID)) {
509
561
  this.#globalMessagingRegistry.set(sessionID, new ClientMessagingRegistry(this, sessionID));
@@ -521,13 +573,15 @@ export class WebfloServer extends WebfloRuntime {
521
573
  cookies: scope.cookies,
522
574
  session: scope.session,
523
575
  user: scope.user,
524
- client: scope.clientMessaging
576
+ client: scope.clientMessaging,
577
+ sdk: this.#sdk
525
578
  });
579
+ await this.setup(scope.httpEvent);
526
580
  // Restore session before dispatching
527
581
  if (scope.request.method === 'GET'
528
582
  && (scope.redirectMessageID = scope.httpEvent.url.query['redirect-message'])
529
- && (scope.redirectMessage = scope.session.get(`redirect-message:${scope.redirectMessageID}`))) {
530
- scope.session.delete(`redirect-message:${scope.redirectMessageID}`);
583
+ && (scope.redirectMessage = await scope.session.get(`redirect-message:${scope.redirectMessageID}`))) {
584
+ await scope.session.delete(`redirect-message:${scope.redirectMessageID}`);
531
585
  }
532
586
  // Dispatch for response
533
587
  scope.response = await this.dispatch(scope.httpEvent, {}, async (event) => {
@@ -535,15 +589,15 @@ export class WebfloServer extends WebfloRuntime {
535
589
  });
536
590
  // ---------------
537
591
  // Response processing
538
- scope.hasBackgroundActivity = scope.clientMessaging.isMessaging() || scope.eventLifecyclePromises.size || (scope.redirectMessage && !(scope.response instanceof Response && scope.response.headers.get('Location')));
539
- scope.response = await this.normalizeResponse(scope.httpEvent, scope.response, scope.hasBackgroundActivity);
592
+ scope.response = await this.normalizeResponse(scope.httpEvent, scope.response);
593
+ scope.hasBackgroundActivity = scope.clientMessaging.isMessaging() || scope.eventLifecyclePromises.size || (scope.redirectMessage && !scope.response.headers.get('Location'));
540
594
  if (scope.hasBackgroundActivity) {
541
595
  scope.response.headers.set('X-Background-Messaging', `ws:${scope.clientMessaging.portID}`);
542
596
  }
543
597
  // Reponse handlers
544
598
  if (scope.response.headers.get('Location')) {
545
599
  if (scope.redirectMessage) {
546
- scope.session.set(`redirect-message:${scope.redirectMessageID}`, scope.redirectMessage);
600
+ await scope.session.set(`redirect-message:${scope.redirectMessageID}`, scope.redirectMessage);
547
601
  }
548
602
  this.writeRedirectHeaders(scope.httpEvent, scope.response);
549
603
  } else {
@@ -3,6 +3,7 @@ import { WebfloServer } from './WebfloServer.js';
3
3
  export async function start() {
4
4
  const instance = WebfloServer.create(this || {});
5
5
  await instance.initialize();
6
+ return instance;
6
7
  }
7
8
 
8
9
  export {
@@ -47,6 +47,9 @@ export function renderHttpMessageInit(httpMessageInit) {
47
47
  }
48
48
 
49
49
  export async function parseHttpMessage(httpMessage) {
50
+ if (httpMessage.meta && 'body' in httpMessage.meta && httpMessage.meta.type === 'json') {
51
+ return httpMessage.meta.body;
52
+ }
50
53
  let result;
51
54
  const contentType = httpMessage.headers.get('Content-Type') || '';
52
55
  if (contentType === 'application/x-www-form-urlencoded' || contentType.startsWith('multipart/form-data')) {
@@ -132,9 +135,19 @@ Object.defineProperties(Request, {
132
135
  value: async function (request, init = {}) {
133
136
  const requestInit = [
134
137
  'method', 'headers', 'mode', 'credentials', 'cache', 'redirect', 'referrer', 'integrity',
135
- ].reduce((init, prop) => ({ [prop]: request[prop], ...init }), {});
138
+ ].reduce(($init, prop) => (
139
+ { ...$init, [prop]: prop in init ? init[prop] : (prop === 'headers' ? new Headers(request[prop]) : request[prop]) }
140
+ ), {});
136
141
  if (!['GET', 'HEAD'].includes(init.method?.toUpperCase() || request.method)) {
137
- requestInit.body = await request.clone().arrayBuffer();
142
+ if ('body' in init) {
143
+ requestInit.body = init.body
144
+ if (!('headers' in init)) {
145
+ requestInit.headers.delete('Content-Type');
146
+ requestInit.headers.delete('Content-Length');
147
+ }
148
+ } else {
149
+ requestInit.body = await request.clone().arrayBuffer();
150
+ }
138
151
  }
139
152
  if (requestInit.mode === 'navigate') {
140
153
  requestInit.mode = 'cors';
@@ -3,7 +3,9 @@
3
3
  * @imports
4
4
  */
5
5
  import * as cert from './cert/index.js';
6
+ import * as push from './push/index.js';
6
7
 
7
8
  export {
8
9
  cert,
10
+ push,
9
11
  }
@@ -0,0 +1,23 @@
1
+
2
+ /**
3
+ * imports
4
+ */
5
+ import webpush from 'web-push';
6
+
7
+ /**
8
+ * @description
9
+ */
10
+ export const desc = {
11
+ generate: 'Generate a set of VAPID keys for push notifications.',
12
+ };
13
+
14
+ /**
15
+ * Reads SSL from file.
16
+ *
17
+ * @return object
18
+ */
19
+ export async function generate() {
20
+ const cx = this || {};
21
+ const vapidKeys = webpush.generateVAPIDKeys();
22
+ cx.logger.log(vapidKeys);
23
+ }
@@ -3,7 +3,7 @@
3
3
  * @imports
4
4
  import * as client from './client/index.js';
5
5
  import * as server from './server/index.js';
6
- */
6
+ */
7
7
 
8
8
  export {
9
9
  //client,