@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
@@ -4,6 +4,7 @@ import Path from 'path';
4
4
  import Http from 'http';
5
5
  import Https from 'https';
6
6
  import WebSocket from 'ws';
7
+ import webpush from 'web-push';
7
8
  import Mime from 'mime-types';
8
9
  import QueryString from 'querystring';
9
10
  import { _from as _arrFrom, _any } from '@webqit/util/arr/index.js';
@@ -196,7 +197,7 @@ export class WebfloServer extends WebfloRuntime {
196
197
  if (!scope.error) {
197
198
  if (!hosts.includes(scope.url.hostname) && !hosts.includes('*')) {
198
199
  scope.error = 'Unrecognized host';
199
- } else if (scope.url.protocol === 'ws:' && this.#cx.server.https.force) {
200
+ } else if (scope.url.protocol === 'ws:' && this.#cx.server.https.port && this.#cx.server.https.force) {
200
201
  scope.error = `Only secure connections allowed (wss:)`;
201
202
  } else if (scope.url.hostname.startsWith('www.') && this.#cx.server.force_www === 'remove') {
202
203
  scope.error = `Connections not allowed over the www subdomain`;
@@ -208,7 +209,7 @@ export class WebfloServer extends WebfloRuntime {
208
209
  // Level 3 validation
209
210
  // and actual processing
210
211
  scope.request = this.createRequest(scope.url.href, requestInit);
211
- scope.session = this.constructor.SessionStorage.create(scope.request, { secret: this.#cx.env.entries.SESSION_KEY });
212
+ scope.session = this.constructor.SessionStorage.create(scope.request, { secret: this.#cx.env.entries[this.#cx.server.session_key_variable] });
212
213
  if (!scope.error) {
213
214
  if (!(scope.clientMessagingRegistry = this.#globalMessagingRegistry.get(scope.session.sessionID))) {
214
215
  scope.error = `Lost or invalid clientID`;
@@ -257,7 +258,7 @@ export class WebfloServer extends WebfloRuntime {
257
258
  if (!hosts.includes(scope.url.hostname) && !hosts.includes('*')) {
258
259
  scope.exit = { status: 500 };
259
260
  scope.exitMessage = 'Unrecognized host';
260
- } else if (scope.url.protocol === 'http:' && this.#cx.server.https.force) {
261
+ } else if (scope.url.protocol === 'http:' && this.#cx.server.https.port && this.#cx.server.https.force) {
261
262
  scope.exit = {
262
263
  status: 302,
263
264
  headers: { Location: (scope.url.protocol = 'https:', scope.url.href) }
@@ -289,7 +290,9 @@ export class WebfloServer extends WebfloRuntime {
289
290
  if (!scope.response) {
290
291
  scope.response = await this.navigate(fullUrl, requestInit, {
291
292
  request: nodeRequest,
292
- response: nodeResponse
293
+ response: nodeResponse,
294
+ ipAddress: nodeRequest.headers['x-forwarded-for']?.split(',')[0]
295
+ || nodeRequest.socket.remoteAddress
293
296
  });
294
297
  }
295
298
  // -----------------
@@ -485,11 +488,14 @@ export class WebfloServer extends WebfloRuntime {
485
488
  promise = Promise.resolve(promise);
486
489
  scope.eventLifecyclePromises.add(promise);
487
490
  scope.eventLifecyclePromises.dirty = true;
488
- promise.then(() => scope.eventLifecyclePromises.delete(promise));
491
+ promise.then(() => {
492
+ scope.eventLifecyclePromises.delete(promise);
493
+ });
489
494
  },
490
495
  respondWith: async (response, isRedirectMessage = false) => {
491
496
  if (!isRedirectMessage && scope.eventLifecyclePromises.dirty && !scope.eventLifecyclePromises.size) {
492
- throw new Error('Final response already sent');
497
+ console.error('Final response already sent');
498
+ return;
493
499
  }
494
500
  return await this.execPush(scope.clientMessaging, response);
495
501
  },
@@ -501,7 +507,7 @@ export class WebfloServer extends WebfloRuntime {
501
507
  : [];
502
508
  scope.request = this.createRequest(scope.url.href, scope.init, scope.autoHeaders.filter((header) => header.type === 'request'));
503
509
  scope.cookies = this.constructor.CookieStorage.create(scope.request);
504
- scope.session = this.constructor.SessionStorage.create(scope.request, { secret: this.#cx.env.entries.SESSION_KEY });
510
+ scope.session = this.constructor.SessionStorage.create(scope.request, { secret: this.#cx.env.entries[this.#cx.server.session_key_variable] });
505
511
  const sessionID = scope.session.sessionID;
506
512
  if (!this.#globalMessagingRegistry.has(sessionID)) {
507
513
  this.#globalMessagingRegistry.set(sessionID, new ClientMessagingRegistry(this, sessionID));
@@ -519,13 +525,24 @@ export class WebfloServer extends WebfloRuntime {
519
525
  cookies: scope.cookies,
520
526
  session: scope.session,
521
527
  user: scope.user,
522
- client: scope.clientMessaging
528
+ client: scope.clientMessaging,
529
+ sdk: { ...(this.#cx.server.capabilities?.webpush ? { webpush } : {}) }
523
530
  });
531
+ if (this.#cx.server.capabilities?.webpush
532
+ && this.#cx.env.entries[this.#cx.server.capabilities.app_vapid_public_key_variable]
533
+ && this.#cx.env.entries[this.#cx.server.capabilities.app_vapid_private_key_variable]) {
534
+ webpush.setVapidDetails(
535
+ scope.url.origin.replace(/^http:/i, 'https:'),
536
+ this.#cx.env.entries[this.#cx.server.capabilities.app_vapid_public_key_variable],
537
+ this.#cx.env.entries[this.#cx.server.capabilities.app_vapid_private_key_variable]
538
+ );
539
+ }
540
+ await this.setup(scope.httpEvent);
524
541
  // Restore session before dispatching
525
542
  if (scope.request.method === 'GET'
526
543
  && (scope.redirectMessageID = scope.httpEvent.url.query['redirect-message'])
527
544
  && (scope.redirectMessage = scope.session.get(`redirect-message:${scope.redirectMessageID}`))) {
528
- scope.session.delete(`redirect-message:${scope.redirectMessageID}`);
545
+ await scope.session.delete(`redirect-message:${scope.redirectMessageID}`);
529
546
  }
530
547
  // Dispatch for response
531
548
  scope.response = await this.dispatch(scope.httpEvent, {}, async (event) => {
@@ -533,15 +550,15 @@ export class WebfloServer extends WebfloRuntime {
533
550
  });
534
551
  // ---------------
535
552
  // Response processing
536
- scope.hasBackgroundActivity = scope.eventLifecyclePromises.size || (scope.redirectMessage && !(scope.response instanceof Response && scope.response.headers.get('Location')));
537
- scope.response = await this.normalizeResponse(scope.httpEvent, scope.response, scope.hasBackgroundActivity);
553
+ scope.response = await this.normalizeResponse(scope.httpEvent, scope.response);
554
+ scope.hasBackgroundActivity = scope.clientMessaging.isMessaging() || scope.eventLifecyclePromises.size || (scope.redirectMessage && !scope.response.headers.get('Location'));
538
555
  if (scope.hasBackgroundActivity) {
539
556
  scope.response.headers.set('X-Background-Messaging', `ws:${scope.clientMessaging.portID}`);
540
557
  }
541
558
  // Reponse handlers
542
559
  if (scope.response.headers.get('Location')) {
543
560
  if (scope.redirectMessage) {
544
- scope.session.set(`redirect-message:${scope.redirectMessageID}`, scope.redirectMessage);
561
+ await scope.session.set(`redirect-message:${scope.redirectMessageID}`, scope.redirectMessage);
545
562
  }
546
563
  this.writeRedirectHeaders(scope.httpEvent, scope.response);
547
564
  } else {
@@ -562,7 +579,9 @@ export class WebfloServer extends WebfloRuntime {
562
579
  scope.clientMessaging.close();
563
580
  }, 100);
564
581
  });
565
- } else scope.clientMessaging.close();
582
+ } else {
583
+ scope.clientMessaging.close();
584
+ }
566
585
  });
567
586
  return scope.response;
568
587
  }
@@ -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,