@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.
- package/package.json +4 -3
- package/src/config-pi/runtime/Client.js +50 -46
- package/src/config-pi/runtime/Server.js +37 -14
- package/src/config-pi/runtime/client/Worker.js +22 -20
- package/src/runtime-pi/HttpEvent.js +34 -19
- package/src/runtime-pi/HttpUser.js +35 -36
- package/src/runtime-pi/WebfloCookieStorage.js +8 -8
- package/src/runtime-pi/WebfloRouter.js +4 -3
- package/src/runtime-pi/WebfloRuntime.js +27 -19
- package/src/runtime-pi/WebfloStorage.js +47 -16
- package/src/runtime-pi/client/Capabilities.js +211 -0
- package/src/runtime-pi/client/CookieStorage.js +2 -2
- package/src/runtime-pi/client/SessionStorage.js +2 -2
- package/src/runtime-pi/client/WebfloClient.js +17 -25
- package/src/runtime-pi/client/WebfloRootClient1.js +55 -34
- package/src/runtime-pi/client/WebfloRootClient2.js +2 -2
- package/src/runtime-pi/client/WebfloSubClient.js +9 -5
- package/src/runtime-pi/client/Workport.js +64 -91
- package/src/runtime-pi/client/generate.js +25 -16
- package/src/runtime-pi/client/index.js +3 -2
- package/src/runtime-pi/client/worker/CookieStorage.js +2 -2
- package/src/runtime-pi/client/worker/SessionStorage.js +1 -1
- package/src/runtime-pi/client/worker/WebfloWorker.js +70 -56
- package/src/runtime-pi/client/worker/index.js +3 -2
- package/src/runtime-pi/server/CookieStorage.js +2 -2
- package/src/runtime-pi/server/SessionStorage.js +3 -3
- package/src/runtime-pi/server/WebfloServer.js +32 -13
- package/src/runtime-pi/server/index.js +1 -0
- package/src/runtime-pi/util-http.js +15 -2
- package/src/services-pi/index.js +2 -0
- package/src/services-pi/push/index.js +23 -0
- 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.
|
|
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(() =>
|
|
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
|
-
|
|
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.
|
|
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.
|
|
537
|
-
scope.
|
|
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
|
|
582
|
+
} else {
|
|
583
|
+
scope.clientMessaging.close();
|
|
584
|
+
}
|
|
566
585
|
});
|
|
567
586
|
return scope.response;
|
|
568
587
|
}
|
|
@@ -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) => (
|
|
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
|
-
|
|
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';
|
package/src/services-pi/index.js
CHANGED
|
@@ -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
|
+
}
|