@webqit/webflo 0.11.61 → 1.0.1
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 +1 -1
- package/src/{Context.js → AbstractContext.js} +1 -9
- package/src/deployment-pi/origins/index.js +1 -1
- package/src/index.js +1 -9
- package/src/runtime-pi/HttpEvent.js +101 -81
- package/src/runtime-pi/HttpUser.js +126 -0
- package/src/runtime-pi/MessagingOverBroadcast.js +9 -0
- package/src/runtime-pi/MessagingOverChannel.js +85 -0
- package/src/runtime-pi/MessagingOverSocket.js +106 -0
- package/src/runtime-pi/MultiportMessagingAPI.js +81 -0
- package/src/runtime-pi/WebfloCookieStorage.js +27 -0
- package/src/runtime-pi/WebfloEventTarget.js +39 -0
- package/src/runtime-pi/WebfloMessageEvent.js +58 -0
- package/src/runtime-pi/WebfloMessagingAPI.js +69 -0
- package/src/runtime-pi/{Router.js → WebfloRouter.js} +3 -34
- package/src/runtime-pi/WebfloRuntime.js +52 -0
- package/src/runtime-pi/WebfloStorage.js +109 -0
- package/src/runtime-pi/client/ClientMessaging.js +5 -0
- package/src/runtime-pi/client/Context.js +2 -6
- package/src/runtime-pi/client/CookieStorage.js +17 -0
- package/src/runtime-pi/client/Router.js +3 -13
- package/src/runtime-pi/client/SessionStorage.js +33 -0
- package/src/runtime-pi/client/Url.js +24 -72
- package/src/runtime-pi/client/WebfloClient.js +544 -0
- package/src/runtime-pi/client/WebfloRootClient1.js +179 -0
- package/src/runtime-pi/client/WebfloRootClient2.js +109 -0
- package/src/runtime-pi/client/WebfloSubClient.js +165 -0
- package/src/runtime-pi/client/Workport.js +89 -161
- package/src/runtime-pi/client/generate.js +3 -3
- package/src/runtime-pi/client/index.js +13 -18
- package/src/runtime-pi/client/worker/ClientMessaging.js +5 -0
- package/src/runtime-pi/client/worker/Context.js +2 -6
- package/src/runtime-pi/client/worker/CookieStorage.js +17 -0
- package/src/runtime-pi/client/worker/SessionStorage.js +13 -0
- package/src/runtime-pi/client/worker/WebfloWorker.js +294 -0
- package/src/runtime-pi/client/worker/Workport.js +13 -73
- package/src/runtime-pi/client/worker/index.js +7 -18
- package/src/runtime-pi/index.js +1 -8
- package/src/runtime-pi/server/ClientMessaging.js +18 -0
- package/src/runtime-pi/server/ClientMessagingRegistry.js +57 -0
- package/src/runtime-pi/server/Context.js +2 -6
- package/src/runtime-pi/server/CookieStorage.js +17 -0
- package/src/runtime-pi/server/Router.js +2 -68
- package/src/runtime-pi/server/SessionStorage.js +53 -0
- package/src/runtime-pi/server/WebfloServer.js +755 -0
- package/src/runtime-pi/server/index.js +7 -18
- package/src/runtime-pi/util-http.js +268 -32
- package/src/runtime-pi/xURL.js +25 -22
- package/src/runtime-pi/xfetch.js +2 -2
- package/src/runtime-pi/Application.js +0 -29
- package/src/runtime-pi/Cookies.js +0 -82
- package/src/runtime-pi/Runtime.js +0 -21
- package/src/runtime-pi/client/Application.js +0 -76
- package/src/runtime-pi/client/Runtime.js +0 -525
- package/src/runtime-pi/client/createStorage.js +0 -58
- package/src/runtime-pi/client/worker/Application.js +0 -44
- package/src/runtime-pi/client/worker/Runtime.js +0 -275
- package/src/runtime-pi/server/Application.js +0 -101
- package/src/runtime-pi/server/Runtime.js +0 -558
- package/src/runtime-pi/xFormData.js +0 -24
- package/src/runtime-pi/xHeaders.js +0 -146
- package/src/runtime-pi/xRequest.js +0 -46
- package/src/runtime-pi/xRequestHeaders.js +0 -109
- package/src/runtime-pi/xResponse.js +0 -33
- package/src/runtime-pi/xResponseHeaders.js +0 -117
- package/src/runtime-pi/xxHttpMessage.js +0 -102
|
@@ -1,78 +1,18 @@
|
|
|
1
|
+
export class Workport {
|
|
1
2
|
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
constructor() {
|
|
5
|
-
// --------
|
|
6
|
-
// Post messaging
|
|
7
|
-
// --------
|
|
8
|
-
this.messaging = {
|
|
9
|
-
post: (message, client = this.client) => {
|
|
10
|
-
if (!client) throw new Error(`No client for this operation.`);
|
|
11
|
-
client.postMessage(message);
|
|
12
|
-
return this;
|
|
13
|
-
},
|
|
14
|
-
listen: (callback, client = this.client) => {
|
|
15
|
-
(client || self).addEventListener('message', evt => {
|
|
16
|
-
this.client = evt.source;
|
|
17
|
-
const response = callback(evt);
|
|
18
|
-
let responsePort = evt.ports[0];
|
|
19
|
-
if (responsePort) {
|
|
20
|
-
if (response instanceof Promise) {
|
|
21
|
-
response.then(data => {
|
|
22
|
-
responsePort.postMessage(data);
|
|
23
|
-
});
|
|
24
|
-
} else {
|
|
25
|
-
responsePort.postMessage(response);
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
});
|
|
29
|
-
return this;
|
|
30
|
-
},
|
|
31
|
-
request: (message, client = this.client) => {
|
|
32
|
-
if (!client) throw new Error(`No client for this operation.`);
|
|
33
|
-
return new Promise(res => {
|
|
34
|
-
let messageChannel = new MessageChannel();
|
|
35
|
-
client.postMessage(message, [ messageChannel.port2 ]);
|
|
36
|
-
messageChannel.port1.onmessage = e => res(e.data);
|
|
37
|
-
});
|
|
38
|
-
},
|
|
39
|
-
channel(channelId) {
|
|
40
|
-
if (!this.channels.has(channelId)) { this.channels.set(channelId, new BroadcastChannel(channel)); }
|
|
41
|
-
let channel = this.channels.get(channelId);
|
|
42
|
-
return {
|
|
43
|
-
broadcast: message => channel.postMessage(message),
|
|
44
|
-
listen: callback => channel.addEventListener('message', callback),
|
|
45
|
-
};
|
|
46
|
-
},
|
|
47
|
-
channels: new Map,
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
// --------
|
|
51
|
-
// Notifications
|
|
52
|
-
// --------
|
|
53
|
-
this.notifications = {
|
|
54
|
-
handle: callback => {
|
|
55
|
-
self.addEventListener('notificationclick', e => e.waitUntil(callback(e)));
|
|
56
|
-
return this;
|
|
57
|
-
},
|
|
58
|
-
fire: (title, params = {}) => {
|
|
59
|
-
return self.registration.showNotification(title, params);
|
|
60
|
-
},
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
// --------
|
|
64
|
-
// Push Notifications
|
|
65
|
-
// --------
|
|
66
|
-
this.push = {
|
|
67
|
-
listen: callback => {
|
|
68
|
-
self.addEventListener('push', e => e.waitUntil(callback(e)));
|
|
69
|
-
return this;
|
|
70
|
-
},
|
|
71
|
-
};
|
|
3
|
+
showNotification(title, params = {}) {
|
|
4
|
+
return self.registration.showNotification(title, params);
|
|
72
5
|
}
|
|
73
6
|
|
|
74
|
-
|
|
75
|
-
|
|
7
|
+
handleNotificationClick(callback) {
|
|
8
|
+
const handler = (e) => e.waitUntil(callback(e));
|
|
9
|
+
self.addEventListener('notificationclick', handler);
|
|
10
|
+
return () => self.removeEventListener('notificationclick', handler);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
handlePush(callback) {
|
|
14
|
+
const handler = (e) => e.waitUntil(callback(e));
|
|
15
|
+
self.addEventListener('push', handler);
|
|
16
|
+
return () => self.removeEventListener('notificationclick', handler);
|
|
76
17
|
}
|
|
77
|
-
|
|
78
18
|
}
|
|
@@ -1,21 +1,10 @@
|
|
|
1
|
+
import { WebfloWorker } from './WebfloWorker.js';
|
|
1
2
|
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
import Context from './Context.js';
|
|
6
|
-
import Application from './Application.js';
|
|
7
|
-
import Runtime from './Runtime.js';
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* @start
|
|
11
|
-
*/
|
|
12
|
-
export async function start(applicationInstance = null) {
|
|
13
|
-
const cx = this || {};
|
|
14
|
-
const defaultApplicationInstance = _cx => new Application(_cx);
|
|
15
|
-
return new Runtime(Context.create(cx), applicationInstance || defaultApplicationInstance);
|
|
3
|
+
export function start() {
|
|
4
|
+
const instance = WebfloWorker.create(this || {});
|
|
5
|
+
instance.initialize();
|
|
16
6
|
}
|
|
17
7
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
export * as APIS from './Runtime.js';
|
|
8
|
+
export {
|
|
9
|
+
WebfloWorker
|
|
10
|
+
}
|
package/src/runtime-pi/index.js
CHANGED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { MultiportMessagingAPI } from '../MultiportMessagingAPI.js';
|
|
2
|
+
|
|
3
|
+
export class ClientMessaging extends MultiportMessagingAPI {
|
|
4
|
+
|
|
5
|
+
get runtime() { return this.parentNode.parentNode; }
|
|
6
|
+
|
|
7
|
+
#portID;
|
|
8
|
+
get portID() { return this.#portID; }
|
|
9
|
+
|
|
10
|
+
constructor(parentNode/*ClientMessagingRegistry*/, portID, params = {}) {
|
|
11
|
+
super(parentNode, params);
|
|
12
|
+
this.#portID = portID;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
createBroadcastChannel(name) {
|
|
16
|
+
return this.parentNode.createBroadcastChannel(name);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { ClientMessaging } from './ClientMessaging.js';
|
|
2
|
+
import crypto from 'crypto';
|
|
3
|
+
|
|
4
|
+
export class ClientMessagingRegistry extends Map {
|
|
5
|
+
|
|
6
|
+
#parentNode;
|
|
7
|
+
get parentNode() { return this.#parentNode; }
|
|
8
|
+
|
|
9
|
+
#sessionID;
|
|
10
|
+
get sessionID() { return this.#sessionID; }
|
|
11
|
+
|
|
12
|
+
#params;
|
|
13
|
+
get params() { return this.#params; }
|
|
14
|
+
|
|
15
|
+
#channels = new Map;
|
|
16
|
+
|
|
17
|
+
constructor(parentNode/*WebfloServer*/, sessionID, params = {}) {
|
|
18
|
+
super();
|
|
19
|
+
this.#parentNode = parentNode;
|
|
20
|
+
this.#sessionID = sessionID;
|
|
21
|
+
this.#params = params;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
createPort() {
|
|
25
|
+
const portID = crypto.randomUUID();
|
|
26
|
+
const portInstance = new ClientMessaging(this, portID, this.#params);
|
|
27
|
+
this.set(portID, portInstance);
|
|
28
|
+
portInstance.on('empty', () => {
|
|
29
|
+
this.delete(portID);
|
|
30
|
+
});
|
|
31
|
+
setTimeout(() => {
|
|
32
|
+
if (portInstance.ports.size || !this.has(portID)) return;
|
|
33
|
+
this.delete(portID);
|
|
34
|
+
}, 30000/*30sec*/);
|
|
35
|
+
return portInstance;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
createBroadcastChannel(name) {
|
|
39
|
+
if (!this.#channels.has(name)) {
|
|
40
|
+
this.#channels.set(name, new BroadcastChannel(this, name));
|
|
41
|
+
}
|
|
42
|
+
return this.#channels.get(name);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export class BroadcastChannel extends EventTarget {
|
|
47
|
+
#manager;
|
|
48
|
+
#name;
|
|
49
|
+
constructor(manager, name) {
|
|
50
|
+
super();
|
|
51
|
+
this.#manager = manager;
|
|
52
|
+
this.#name = name;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
postMessage() {
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -1,10 +1,6 @@
|
|
|
1
|
+
import { AbstractContext } from '../../AbstractContext.js';
|
|
1
2
|
|
|
2
|
-
|
|
3
|
-
* @imports
|
|
4
|
-
*/
|
|
5
|
-
import _Context from '../../Context.js';
|
|
6
|
-
|
|
7
|
-
export default class Context extends _Context {
|
|
3
|
+
export class Context extends AbstractContext {
|
|
8
4
|
// env
|
|
9
5
|
get env() {
|
|
10
6
|
return this.dict.env || {};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { WebfloCookieStorage } from '../WebfloCookieStorage.js';
|
|
2
|
+
|
|
3
|
+
export class CookieStorage extends WebfloCookieStorage {
|
|
4
|
+
static create(request) {
|
|
5
|
+
return new this(
|
|
6
|
+
request,
|
|
7
|
+
request.headers.get('Cookie', true).map((c) => [c.name, c])
|
|
8
|
+
);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
commit(response) {
|
|
12
|
+
for (const cookieStr of this.render()) {
|
|
13
|
+
response.headers.append('Set-Cookie', cookieStr);
|
|
14
|
+
}
|
|
15
|
+
super.commit();
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -1,20 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
/**
|
|
3
|
-
* @imports
|
|
4
|
-
*/
|
|
5
1
|
import Fs from 'fs';
|
|
6
2
|
import Url from 'url';
|
|
7
3
|
import Path from 'path';
|
|
8
|
-
import
|
|
9
|
-
import _Router from '../Router.js';
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* ---------------------------
|
|
13
|
-
* The Router class
|
|
14
|
-
* ---------------------------
|
|
15
|
-
*/
|
|
4
|
+
import { WebfloRouter } from '../WebfloRouter.js';
|
|
16
5
|
|
|
17
|
-
export
|
|
6
|
+
export class Router extends WebfloRouter {
|
|
18
7
|
|
|
19
8
|
async readTick(thisTick) {
|
|
20
9
|
thisTick = { ...thisTick };
|
|
@@ -52,61 +41,6 @@ export default class Router extends _Router {
|
|
|
52
41
|
return Path.join(...args);
|
|
53
42
|
}
|
|
54
43
|
|
|
55
|
-
/**
|
|
56
|
-
* Reads a static file from the public directory.
|
|
57
|
-
*
|
|
58
|
-
* @param ServerNavigationEvent httpEvent
|
|
59
|
-
*
|
|
60
|
-
* @return Promise
|
|
61
|
-
*/
|
|
62
|
-
file(httpEvent) {
|
|
63
|
-
let filename = Path.join(this.cx.CWD, this.cx.layout.PUBLIC_DIR, decodeURIComponent(httpEvent.url.pathname));
|
|
64
|
-
let index, ext = Path.parse(httpEvent.url.pathname).ext;
|
|
65
|
-
// if is a directory search for index file matching the extention
|
|
66
|
-
if (!ext && Fs.existsSync(filename) && Fs.lstatSync(filename).isDirectory()) {
|
|
67
|
-
ext = '.html';
|
|
68
|
-
index = `index${ext}`;
|
|
69
|
-
filename = Path.join(filename, index);
|
|
70
|
-
}
|
|
71
|
-
let enc, acceptEncs = [], supportedEncs = { gzip: '.gz', br: '.br' };
|
|
72
|
-
// based on the URL path, extract the file extention. e.g. .js, .doc, ...
|
|
73
|
-
// and process encoding
|
|
74
|
-
if ((acceptEncs = (httpEvent.request.headers.get('Accept-Encoding') || '').split(',').map(e => e.trim())).length
|
|
75
|
-
&& (enc = acceptEncs.reduce((prev, _enc) => prev || (Fs.existsSync(filename + supportedEncs[_enc]) && _enc), null))) {
|
|
76
|
-
filename = filename + supportedEncs[enc];
|
|
77
|
-
} else {
|
|
78
|
-
if (!Fs.existsSync(filename)) return;
|
|
79
|
-
if (Object.values(supportedEncs).includes(ext)) {
|
|
80
|
-
enc = Object.keys(supportedEncs).reduce((prev, _enc) => prev || (supportedEncs[_enc] === ext && _enc), null);
|
|
81
|
-
ext = Path.parse(filename.substring(0, filename.length - ext.length)).ext;
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// read file from file system
|
|
86
|
-
return new Promise(resolve => {
|
|
87
|
-
Fs.readFile(filename, function(err, data) {
|
|
88
|
-
let response;
|
|
89
|
-
if (err) {
|
|
90
|
-
response = new httpEvent.Response(null, { status: 500, statusText: `Error reading static file: ${filename}` } );
|
|
91
|
-
} else {
|
|
92
|
-
// if the file is found, set Content-type and send data
|
|
93
|
-
let mime = Mime.lookup(ext);
|
|
94
|
-
response = new httpEvent.Response(data, { headers: {
|
|
95
|
-
contentType: mime === 'application/javascript' ? 'text/javascript' : mime,
|
|
96
|
-
contentLength: Buffer.byteLength(data),
|
|
97
|
-
} });
|
|
98
|
-
if (enc) {
|
|
99
|
-
response.headers.set('Content-Encoding', enc);
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
response.attrs.filename = filename;
|
|
103
|
-
response.attrs.static = true;
|
|
104
|
-
response.attrs.index = index;
|
|
105
|
-
resolve(response);
|
|
106
|
-
});
|
|
107
|
-
});
|
|
108
|
-
}
|
|
109
|
-
|
|
110
44
|
/**
|
|
111
45
|
* Writes a file to the public directory.
|
|
112
46
|
*
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { WebfloStorage } from '../WebfloStorage.js';
|
|
2
|
+
import crypto from 'crypto';
|
|
3
|
+
|
|
4
|
+
const sessionStorage = new Map;
|
|
5
|
+
export class SessionStorage extends WebfloStorage {
|
|
6
|
+
|
|
7
|
+
static create(request, params = {}) {
|
|
8
|
+
let sessionID = request.headers.get('Cookie', true).find((c) => c.name === '__sessid')?.value;
|
|
9
|
+
if (sessionID?.includes('.')) {
|
|
10
|
+
const [rand, signature] = sessionID.split('.');
|
|
11
|
+
const expectedSignature = crypto.createHmac('sha256', params.secret)
|
|
12
|
+
.update(rand)
|
|
13
|
+
.digest('hex');
|
|
14
|
+
if (signature !== expectedSignature) {
|
|
15
|
+
sessionID = undefined;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
if (!sessionID) {
|
|
19
|
+
if (params.secret) {
|
|
20
|
+
const rand = `${(0 | Math.random() * 9e6).toString(36)}`;
|
|
21
|
+
const signature = crypto.createHmac('sha256', params.secret)
|
|
22
|
+
.update(rand)
|
|
23
|
+
.digest('hex');
|
|
24
|
+
sessionID = `${rand}.${signature}`
|
|
25
|
+
} else {
|
|
26
|
+
sessionID = crypto.randomUUID();
|
|
27
|
+
}
|
|
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;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
#sessionID;
|
|
43
|
+
get sessionID() {
|
|
44
|
+
return this.#sessionID;
|
|
45
|
+
}
|
|
46
|
+
|
|
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();
|
|
52
|
+
}
|
|
53
|
+
}
|