@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
@@ -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
  // -----------------
@@ -491,7 +494,8 @@ export class WebfloServer extends WebfloRuntime {
491
494
  },
492
495
  respondWith: async (response, isRedirectMessage = false) => {
493
496
  if (!isRedirectMessage && scope.eventLifecyclePromises.dirty && !scope.eventLifecyclePromises.size) {
494
- throw new Error('Final response already sent');
497
+ console.error('Final response already sent');
498
+ return;
495
499
  }
496
500
  return await this.execPush(scope.clientMessaging, response);
497
501
  },
@@ -503,7 +507,7 @@ export class WebfloServer extends WebfloRuntime {
503
507
  : [];
504
508
  scope.request = this.createRequest(scope.url.href, scope.init, scope.autoHeaders.filter((header) => header.type === 'request'));
505
509
  scope.cookies = this.constructor.CookieStorage.create(scope.request);
506
- 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] });
507
511
  const sessionID = scope.session.sessionID;
508
512
  if (!this.#globalMessagingRegistry.has(sessionID)) {
509
513
  this.#globalMessagingRegistry.set(sessionID, new ClientMessagingRegistry(this, sessionID));
@@ -521,13 +525,24 @@ export class WebfloServer extends WebfloRuntime {
521
525
  cookies: scope.cookies,
522
526
  session: scope.session,
523
527
  user: scope.user,
524
- client: scope.clientMessaging
528
+ client: scope.clientMessaging,
529
+ sdk: { ...(this.#cx.server.capabilities?.webpush ? { webpush } : {}) }
525
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);
526
541
  // Restore session before dispatching
527
542
  if (scope.request.method === 'GET'
528
543
  && (scope.redirectMessageID = scope.httpEvent.url.query['redirect-message'])
529
544
  && (scope.redirectMessage = scope.session.get(`redirect-message:${scope.redirectMessageID}`))) {
530
- scope.session.delete(`redirect-message:${scope.redirectMessageID}`);
545
+ await scope.session.delete(`redirect-message:${scope.redirectMessageID}`);
531
546
  }
532
547
  // Dispatch for response
533
548
  scope.response = await this.dispatch(scope.httpEvent, {}, async (event) => {
@@ -535,15 +550,15 @@ export class WebfloServer extends WebfloRuntime {
535
550
  });
536
551
  // ---------------
537
552
  // 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);
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'));
540
555
  if (scope.hasBackgroundActivity) {
541
556
  scope.response.headers.set('X-Background-Messaging', `ws:${scope.clientMessaging.portID}`);
542
557
  }
543
558
  // Reponse handlers
544
559
  if (scope.response.headers.get('Location')) {
545
560
  if (scope.redirectMessage) {
546
- scope.session.set(`redirect-message:${scope.redirectMessageID}`, scope.redirectMessage);
561
+ await scope.session.set(`redirect-message:${scope.redirectMessageID}`, scope.redirectMessage);
547
562
  }
548
563
  this.writeRedirectHeaders(scope.httpEvent, scope.response);
549
564
  } 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,