@webqit/webflo 0.11.61-0 → 1.0.0
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/.gitignore +7 -7
- package/LICENSE +20 -20
- package/README.md +2079 -2074
- package/docker/Dockerfile +42 -42
- package/docker/README.md +91 -91
- package/docker/package.json +2 -2
- package/package.json +80 -81
- package/src/{Context.js → AbstractContext.js} +71 -79
- package/src/config-pi/deployment/Env.js +68 -68
- package/src/config-pi/deployment/Layout.js +63 -63
- package/src/config-pi/deployment/Origins.js +139 -139
- package/src/config-pi/deployment/Proxy.js +74 -74
- package/src/config-pi/deployment/index.js +17 -17
- package/src/config-pi/index.js +15 -15
- package/src/config-pi/runtime/Client.js +116 -98
- package/src/config-pi/runtime/Server.js +125 -125
- package/src/config-pi/runtime/client/Worker.js +109 -134
- package/src/config-pi/runtime/client/index.js +11 -11
- package/src/config-pi/runtime/index.js +17 -17
- package/src/config-pi/runtime/server/Headers.js +74 -74
- package/src/config-pi/runtime/server/Redirects.js +69 -69
- package/src/config-pi/runtime/server/index.js +13 -13
- package/src/config-pi/static/Manifest.js +319 -319
- package/src/config-pi/static/Ssg.js +49 -49
- package/src/config-pi/static/index.js +13 -13
- package/src/deployment-pi/index.js +10 -10
- package/src/deployment-pi/origins/index.js +216 -216
- package/src/index.js +11 -19
- package/src/runtime-pi/HttpEvent.js +126 -106
- 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} +99 -130
- 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 +3 -7
- package/src/runtime-pi/client/CookieStorage.js +17 -0
- package/src/runtime-pi/client/Router.js +38 -48
- package/src/runtime-pi/client/SessionStorage.js +33 -0
- package/src/runtime-pi/client/Url.js +156 -205
- 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 +118 -178
- package/src/runtime-pi/client/generate.js +480 -471
- package/src/runtime-pi/client/index.js +16 -21
- package/src/runtime-pi/client/worker/ClientMessaging.js +5 -0
- package/src/runtime-pi/client/worker/Context.js +3 -7
- 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 +17 -85
- package/src/runtime-pi/client/worker/index.js +10 -21
- package/src/runtime-pi/index.js +6 -13
- 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 +11 -15
- package/src/runtime-pi/server/CookieStorage.js +17 -0
- package/src/runtime-pi/server/Router.js +93 -159
- 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 +10 -21
- package/src/runtime-pi/util-http.js +322 -86
- package/src/runtime-pi/util-url.js +146 -146
- package/src/runtime-pi/xURL.js +108 -105
- package/src/runtime-pi/xfetch.js +22 -22
- package/src/services-pi/cert/http-auth-hook.js +22 -22
- package/src/services-pi/cert/http-cleanup-hook.js +22 -22
- package/src/services-pi/cert/index.js +79 -79
- package/src/services-pi/index.js +8 -8
- package/src/static-pi/index.js +10 -10
- package/src/webflo.js +30 -30
- package/test/index.test.js +26 -26
- package/test/site/package.json +9 -9
- package/test/site/public/bundle.html +5 -5
- package/test/site/public/bundle.html.json +3 -3
- package/test/site/public/bundle.js +2 -2
- package/test/site/public/bundle.webflo.js +15 -15
- package/test/site/public/index.html +29 -29
- package/test/site/public/index1.html +34 -34
- package/test/site/public/page-2/bundle.html +4 -4
- package/test/site/public/page-2/bundle.js +2 -2
- package/test/site/public/page-2/index.html +45 -45
- package/test/site/public/page-2/main.html +2 -2
- package/test/site/public/page-4/subpage/bundle.js +2 -2
- package/test/site/public/page-4/subpage/index.html +30 -30
- package/test/site/public/sparoots.json +4 -4
- package/test/site/public/worker.js +3 -3
- package/test/site/server/index.js +15 -15
- 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 -100
- package/src/runtime-pi/client/Runtime.js +0 -332
- package/src/runtime-pi/client/createStorage.js +0 -57
- package/src/runtime-pi/client/oohtml/full.js +0 -7
- package/src/runtime-pi/client/oohtml/namespacing.js +0 -7
- package/src/runtime-pi/client/oohtml/scripting.js +0 -8
- package/src/runtime-pi/client/oohtml/templating.js +0 -8
- package/src/runtime-pi/client/worker/Application.js +0 -44
- package/src/runtime-pi/client/worker/Runtime.js +0 -269
- package/src/runtime-pi/server/Application.js +0 -116
- package/src/runtime-pi/server/Runtime.js +0 -557
- 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
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export class WebfloEventTarget extends EventTarget {
|
|
2
|
+
|
|
3
|
+
#parentNode;
|
|
4
|
+
#params;
|
|
5
|
+
#listenersRegistry = new Set;
|
|
6
|
+
|
|
7
|
+
get parentNode() { return this.#parentNode; }
|
|
8
|
+
get params() { return this.#params; }
|
|
9
|
+
get length() { return this.#listenersRegistry.size; }
|
|
10
|
+
|
|
11
|
+
constructor(parentNode, params = {}) {
|
|
12
|
+
super();
|
|
13
|
+
this.#parentNode = parentNode;
|
|
14
|
+
this.#params = params;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
setParent(parentNode) {
|
|
18
|
+
this.#parentNode = parentNode;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
dispatchEvent(event) {
|
|
22
|
+
const returnValue = super.dispatchEvent(event);
|
|
23
|
+
if (this.#parentNode instanceof EventTarget && !event.defaultPrevented && !event.propagationStopped) {
|
|
24
|
+
this.#parentNode.dispatchEvent(event);
|
|
25
|
+
}
|
|
26
|
+
return returnValue;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
addEventListener(...args) {
|
|
30
|
+
this.#listenersRegistry.add(args);
|
|
31
|
+
return super.addEventListener(...args);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
$destroy() {
|
|
35
|
+
for (const listenerArgs of this.#listenersRegistry) {
|
|
36
|
+
this.removeEventListener(...listenerArgs);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
|
|
2
|
+
export class WebfloMessageEvent extends Event {
|
|
3
|
+
|
|
4
|
+
#originalTarget;
|
|
5
|
+
get originalTarget() { return this.#originalTarget; }
|
|
6
|
+
|
|
7
|
+
get runtime() {
|
|
8
|
+
let parentNode = this.#originalTarget;
|
|
9
|
+
do {
|
|
10
|
+
if (parentNode.runtime) return parentNode.runtime;
|
|
11
|
+
} while (parentNode = parentNode.parentNode)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
#data;
|
|
15
|
+
get data() { return this.#data; }
|
|
16
|
+
|
|
17
|
+
#ports = [];
|
|
18
|
+
get ports() { return this.#ports; }
|
|
19
|
+
|
|
20
|
+
constructor(originalTarget, messageType, message, ports) {
|
|
21
|
+
super(messageType);
|
|
22
|
+
this.#originalTarget = originalTarget;
|
|
23
|
+
this.#data = message;
|
|
24
|
+
this.#ports = ports;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
#immediatePropagationStopped = false;
|
|
28
|
+
get immediatePropagationStopped() { return this.#immediatePropagationStopped; }
|
|
29
|
+
|
|
30
|
+
stopImmediatePropagation() {
|
|
31
|
+
this.#immediatePropagationStopped = true;
|
|
32
|
+
this.#propagationStopped = true;
|
|
33
|
+
super.stopImmediatePropagation();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
#propagationStopped = false;
|
|
37
|
+
get propagationStopped() { return this.#propagationStopped; }
|
|
38
|
+
|
|
39
|
+
stopPropagation() {
|
|
40
|
+
this.#propagationStopped = true;
|
|
41
|
+
super.stopPropagation();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
#defaultPrevented = false;
|
|
45
|
+
get defaultPrevented() { return this.#defaultPrevented; }
|
|
46
|
+
|
|
47
|
+
preventDefault() {
|
|
48
|
+
this.#defaultPrevented = true;
|
|
49
|
+
super.preventDefault();
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
respondWith(data, transferOrOptions = []) {
|
|
53
|
+
for (const port of this.ports) {
|
|
54
|
+
port.postMessage(data, transferOrOptions);
|
|
55
|
+
}
|
|
56
|
+
return !!this.ports.length;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { WebfloEventTarget } from './WebfloEventTarget.js';
|
|
2
|
+
|
|
3
|
+
export class WebfloMessagingAPI extends WebfloEventTarget {
|
|
4
|
+
|
|
5
|
+
#isConnected = false;
|
|
6
|
+
isConnected() { return this.#isConnected; }
|
|
7
|
+
|
|
8
|
+
#isSending = false;
|
|
9
|
+
isMessaging() { return this.#isSending || !!this.length; }
|
|
10
|
+
|
|
11
|
+
#hooks = new Set;
|
|
12
|
+
on(eventName, callback, { once = false } = {}) {
|
|
13
|
+
if (eventName === 'connected' && this.#isConnected) {
|
|
14
|
+
callback();
|
|
15
|
+
if (once) {
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
const hook = { eventName, callback, once };
|
|
20
|
+
this.#hooks.add(hook);
|
|
21
|
+
return () => this.#hooks.delete(hook);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
$emit(eventName, arg) {
|
|
25
|
+
if (eventName === 'connected') {
|
|
26
|
+
this.#isConnected = true;
|
|
27
|
+
}
|
|
28
|
+
for (const hook of this.#hooks) {
|
|
29
|
+
if (hook.eventName !== eventName) continue;
|
|
30
|
+
hook.callback(arg);
|
|
31
|
+
if (hook.once) {
|
|
32
|
+
this.#hooks.delete(hook);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/* ----------------- */
|
|
38
|
+
|
|
39
|
+
postMessage(data, transferOrOptions = []) {
|
|
40
|
+
this.#isSending = true;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
postRequest(message, callback, options = {}) {
|
|
44
|
+
const { signal, once, ...$options } = options;
|
|
45
|
+
const messageChannel = new MessageChannel;
|
|
46
|
+
messageChannel.port1.addEventListener('message', (e) => callback(e), {
|
|
47
|
+
signal,
|
|
48
|
+
once
|
|
49
|
+
});
|
|
50
|
+
messageChannel.port1.start();
|
|
51
|
+
return this.postMessage(message, { ...$options, transfer: [ messageChannel.port2 ].concat($options.transfer || []) });
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
handleMessages(type, listener, options = {}) {
|
|
55
|
+
this.addEventListener(type, listener, options);
|
|
56
|
+
return () => {
|
|
57
|
+
this.removeEventListener(type, listener, options);
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
handleRequests(type, listener, options = {}) {
|
|
62
|
+
return this.handleMessages(type, async (e) => {
|
|
63
|
+
const response = await listener(e);
|
|
64
|
+
for (const p of e.ports) {
|
|
65
|
+
p.postMessage(response);
|
|
66
|
+
}
|
|
67
|
+
}, options);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -1,130 +1,99 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
const nextPathname = thisTick.destination.slice(thisTick.trail.length);
|
|
101
|
-
_next.pathname = nextPathname.join('/');
|
|
102
|
-
_next.stepname = nextPathname[0];
|
|
103
|
-
// -------------
|
|
104
|
-
return await handler.call(thisContext, thisTick.event, thisTick.arg, _next/*next*/, remoteFetch);
|
|
105
|
-
}
|
|
106
|
-
// Handler not found but exports found
|
|
107
|
-
return next(thisTick);
|
|
108
|
-
} else if ((thisTick.currentSegmentOnFile || {}).dirExists) {
|
|
109
|
-
// Exports not found but directory found
|
|
110
|
-
return next(thisTick);
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
// -------------
|
|
114
|
-
// Local file
|
|
115
|
-
// -------------
|
|
116
|
-
if (_default) {
|
|
117
|
-
return await _default.call(thisContext, thisTick.event, thisTick.arg, remoteFetch);
|
|
118
|
-
}
|
|
119
|
-
};
|
|
120
|
-
|
|
121
|
-
return next({
|
|
122
|
-
destination: this.path,
|
|
123
|
-
event,
|
|
124
|
-
method,
|
|
125
|
-
arg,
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
}
|
|
1
|
+
import { _isString, _isFunction, _isArray } from '@webqit/util/js/index.js';
|
|
2
|
+
import { _from as _arrFrom } from '@webqit/util/arr/index.js';
|
|
3
|
+
|
|
4
|
+
export class WebfloRouter {
|
|
5
|
+
|
|
6
|
+
constructor(cx, path = []) {
|
|
7
|
+
this.cx = cx;
|
|
8
|
+
this.path = _isArray(path) ? path : (path + '').split('/').filter(a => a);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
async route(method, event, arg, _default, remoteFetch = null) {
|
|
12
|
+
|
|
13
|
+
const $this = this;
|
|
14
|
+
const $runtime = this.cx.runtime;
|
|
15
|
+
|
|
16
|
+
// ----------------
|
|
17
|
+
// The loop
|
|
18
|
+
// ----------------
|
|
19
|
+
const next = async function(thisTick) {
|
|
20
|
+
const thisContext = { runtime: $runtime };
|
|
21
|
+
if (!thisTick.trail || thisTick.trail.length < thisTick.destination.length) {
|
|
22
|
+
thisTick = await $this.readTick(thisTick);
|
|
23
|
+
// -------------
|
|
24
|
+
thisContext.pathname = `/${thisTick.trail.join('/')}`;
|
|
25
|
+
thisContext.stepname = thisTick.trail[thisTick.trail.length - 1];
|
|
26
|
+
$this.finalizeHandlerContext(thisContext, thisTick);
|
|
27
|
+
// -------------
|
|
28
|
+
if (thisTick.exports) {
|
|
29
|
+
// Broadcast any hints exported by handler
|
|
30
|
+
//@obsolete if (thisTick.exports.hints) { await event.port.post({ ...thisTick.exports.hints, $type: 'handler:hints' }); }
|
|
31
|
+
const methods = _arrFrom(thisTick.method).map(m => m === 'default' ? m : m.toUpperCase());
|
|
32
|
+
const handler = _isFunction(thisTick.exports) && methods.includes('default') ? thisTick.exports : methods.reduce((_handler, name) => _handler || thisTick.exports[name], null);
|
|
33
|
+
if (handler) {
|
|
34
|
+
// -------------
|
|
35
|
+
// Dynamic response
|
|
36
|
+
// -------------
|
|
37
|
+
const _next = async (..._args) => {
|
|
38
|
+
const nextTick = { ...thisTick, arg: _args[0] };
|
|
39
|
+
if (_args.length > 1) {
|
|
40
|
+
let _url = _args[1], _request, requestInit = { ...(_args[2] || {}) };
|
|
41
|
+
if (_args[1] instanceof Request) {
|
|
42
|
+
_request = _args[1];
|
|
43
|
+
_url = _request.url;
|
|
44
|
+
} else if (!_isString(_url)) {
|
|
45
|
+
throw new Error('Router redirect url must be a string!');
|
|
46
|
+
}
|
|
47
|
+
let newDestination = _url.startsWith('/') ? _url : $this.pathJoin(`/${thisTick.trail.join('/')}`, _url);
|
|
48
|
+
if (newDestination.startsWith('../')) {
|
|
49
|
+
throw new Error('Router redirect cannot traverse beyond the routing directory! (' + _url + ' >> ' + newDestination + ')');
|
|
50
|
+
}
|
|
51
|
+
if (requestInit.method) {
|
|
52
|
+
nextTick.method = requestInit.method;
|
|
53
|
+
if (_isArray(requestInit.method)) {
|
|
54
|
+
requestInit.method = requestInit.method[0];
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
if (_request) {
|
|
58
|
+
nextTick.event = await thisTick.event.with(newDestination, _request, requestInit);
|
|
59
|
+
} else {
|
|
60
|
+
nextTick.event = await thisTick.event.with(newDestination, requestInit);
|
|
61
|
+
}
|
|
62
|
+
nextTick.source = thisTick.destination.join('/');
|
|
63
|
+
nextTick.destination = newDestination.split('?').shift().split('/').map(a => a.trim()).filter(a => a);
|
|
64
|
+
nextTick.trail = _args[1].startsWith('/') ? [] : thisTick.trail.reduce((_commonRoot, _seg, i) => _commonRoot.length === i && _seg === nextTick.destination[i] ? _commonRoot.concat(_seg) : _commonRoot, []);
|
|
65
|
+
nextTick.trailOnFile = thisTick.trailOnFile.slice(0, nextTick.trail.length);
|
|
66
|
+
}
|
|
67
|
+
return next(nextTick);
|
|
68
|
+
};
|
|
69
|
+
// -------------
|
|
70
|
+
const nextPathname = thisTick.destination.slice(thisTick.trail.length);
|
|
71
|
+
_next.pathname = nextPathname.join('/');
|
|
72
|
+
_next.stepname = nextPathname[0];
|
|
73
|
+
// -------------
|
|
74
|
+
return await handler.call(thisContext, thisTick.event, thisTick.arg, _next/*next*/, remoteFetch);
|
|
75
|
+
}
|
|
76
|
+
// Handler not found but exports found
|
|
77
|
+
return next(thisTick);
|
|
78
|
+
} else if ((thisTick.currentSegmentOnFile || {}).dirExists) {
|
|
79
|
+
// Exports not found but directory found
|
|
80
|
+
return next(thisTick);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
// -------------
|
|
84
|
+
// Local file
|
|
85
|
+
// -------------
|
|
86
|
+
if (_default) {
|
|
87
|
+
return await _default.call(thisContext, thisTick.event, thisTick.arg, remoteFetch);
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
return next({
|
|
92
|
+
destination: this.path,
|
|
93
|
+
event,
|
|
94
|
+
method,
|
|
95
|
+
arg,
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
}
|
|
99
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { _isObject } from '@webqit/util/js/index.js';
|
|
2
|
+
|
|
3
|
+
export class WebfloRuntime {
|
|
4
|
+
|
|
5
|
+
async dispatch(httpEvent, context, crossLayerFetch) {
|
|
6
|
+
// Exec routing
|
|
7
|
+
const router = new this.constructor.Router(this.cx, httpEvent.url.pathname);
|
|
8
|
+
const route = async () => {
|
|
9
|
+
return await router.route([httpEvent.request.method, 'default'], httpEvent, context, async (event) => {
|
|
10
|
+
return crossLayerFetch(event);
|
|
11
|
+
}, (...args) => this.remoteFetch(...args));
|
|
12
|
+
};
|
|
13
|
+
try {
|
|
14
|
+
// Route for response
|
|
15
|
+
return await (this.cx.middlewares || []).concat(route).reverse().reduce((next, fn) => {
|
|
16
|
+
return () => fn.call(this.cx, httpEvent, router, next);
|
|
17
|
+
}, null)();
|
|
18
|
+
|
|
19
|
+
} catch (e) {
|
|
20
|
+
console.error(e);
|
|
21
|
+
return new Response(null, { status: 500, statusText: e.message });
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async normalizeResponse(httpEvent, response, forceCommit = false) {
|
|
26
|
+
// Normalize response
|
|
27
|
+
if (!(response instanceof Response)) {
|
|
28
|
+
response = typeof response === 'undefined'
|
|
29
|
+
? new Response(null, { status: 404 })
|
|
30
|
+
: Response.create(response);
|
|
31
|
+
}
|
|
32
|
+
// Commit data
|
|
33
|
+
for (const storage of [httpEvent.cookies, httpEvent.session, httpEvent.storage]) {
|
|
34
|
+
await storage?.commit?.(response, forceCommit);
|
|
35
|
+
}
|
|
36
|
+
return response;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async execPush(clientPort, data) {
|
|
40
|
+
if (data instanceof Response) {
|
|
41
|
+
if ([301, 302, 303, 307, 308].includes(data.status) && data.headers.has('Location')) {
|
|
42
|
+
clientPort.postMessage(data.headers.get('Location'), { messageType: 'redirect' });
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
data = await data.parse();
|
|
46
|
+
}
|
|
47
|
+
if (!_isObject(data)) {
|
|
48
|
+
throw new Error('Response not serializable');
|
|
49
|
+
}
|
|
50
|
+
clientPort.postMessage(data, { messageType: 'response' });
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { _isObject } from '@webqit/util/js/index.js';
|
|
2
|
+
import { _even } from '@webqit/util/obj/index.js';
|
|
3
|
+
|
|
4
|
+
export class WebfloStorage extends Map {
|
|
5
|
+
|
|
6
|
+
#request;
|
|
7
|
+
#session;
|
|
8
|
+
|
|
9
|
+
constructor(request, session, iterable = []) {
|
|
10
|
+
super(iterable);
|
|
11
|
+
this.#request = request;
|
|
12
|
+
this.#session = session === true ? this : session;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
#originals;
|
|
16
|
+
saveOriginals() { this.#originals = new Map(this); }
|
|
17
|
+
|
|
18
|
+
getDeleted() {
|
|
19
|
+
if (!this.#originals) return [];
|
|
20
|
+
return [...this.#originals.keys()].filter((k) => {
|
|
21
|
+
return !this.has(k);
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
getAdded() {
|
|
26
|
+
if (!this.#originals) return [...this.keys()];
|
|
27
|
+
return [...new Set([...this.keys(), ...this.#originals.keys()])].filter((k) => {
|
|
28
|
+
return !this.#originals.has(k) || (this.has(k) && ((a, b) => _isObject(a) && _isObject(b) ? !_even(a, b) : a !== b)(this.get(k, true), this.#originals.get(k)));
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
commit() {
|
|
33
|
+
this.saveOriginals();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
#handlers = new Map;
|
|
37
|
+
#reverseHandlers = new Map;
|
|
38
|
+
defineHandler(attr, ...handlers) {
|
|
39
|
+
let registry = this.#handlers;
|
|
40
|
+
if (handlers[0] === false) {
|
|
41
|
+
registry = this.#reverseHandlers;
|
|
42
|
+
handlers.shift();
|
|
43
|
+
}
|
|
44
|
+
const $handlers = [];
|
|
45
|
+
for (let handler of handlers) {
|
|
46
|
+
if (typeof handler === 'function') {
|
|
47
|
+
handler = { callback: handler };
|
|
48
|
+
} else if (typeof handler === 'string') {
|
|
49
|
+
handler = { url: handler };
|
|
50
|
+
} else if (typeof handler?.callback !== 'function' && typeof handler?.url !== 'string') {
|
|
51
|
+
throw new Error(`Handler must be either an URL or a function or an object specifying either an URL (handler.url) or a function (handler.callback)`);
|
|
52
|
+
}
|
|
53
|
+
$handlers.push(handler);
|
|
54
|
+
}
|
|
55
|
+
registry.set(attr, $handlers);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
defineReverseHandler(attr, ...handlers) {
|
|
59
|
+
return this.defineHandler(attr, false, ...handlers);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
getHandlers() { return this.#handlers; }
|
|
63
|
+
|
|
64
|
+
getReverseHandlers() { return this.#reverseHandlers; }
|
|
65
|
+
|
|
66
|
+
async require(attrs, callback = null, noNulls = false) {
|
|
67
|
+
const entries = [];
|
|
68
|
+
main: for await (const attr of [].concat(attrs)) {
|
|
69
|
+
if (!this.has(attr) || (noNulls && [undefined, null].includes(this.get(attr)))) {
|
|
70
|
+
const handlers = this.#handlers.get(attr);
|
|
71
|
+
if (!handlers) {
|
|
72
|
+
throw new Error(`No handler defined for the user attribute: ${attr}`);
|
|
73
|
+
}
|
|
74
|
+
for (let i = 0; i < handlers.length; i ++) {
|
|
75
|
+
const handler = handlers[i];
|
|
76
|
+
if (handler.callback) {
|
|
77
|
+
const returnValue = await handler.callback(this, attr);
|
|
78
|
+
if (returnValue instanceof Response) {
|
|
79
|
+
return returnValue;
|
|
80
|
+
}
|
|
81
|
+
if ((typeof returnValue === 'undefined' || (noNulls && returnValue === null)) && i < handlers.length - 1) {
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
entries.push(returnValue);
|
|
85
|
+
continue main;
|
|
86
|
+
}
|
|
87
|
+
const urlRewrite = new URL(handler.url, this.#request.url);
|
|
88
|
+
if (!urlRewrite.searchParams.has('success-redirect')) {
|
|
89
|
+
urlRewrite.searchParams.set('success-redirect', this.#request.url.replace(urlRewrite.origin, ''));
|
|
90
|
+
}
|
|
91
|
+
if (handler.message) {
|
|
92
|
+
if (!this.#session) {
|
|
93
|
+
throw new Error('Storage type does not support redirect messages');
|
|
94
|
+
}
|
|
95
|
+
const messageID = (0 | Math.random() * 9e6).toString(36);
|
|
96
|
+
urlRewrite.searchParams.set('redirect-message', messageID);
|
|
97
|
+
this.#session.set(`redirect-message:${messageID}`, { status: { type: handler.type || 'info', message: handler.message }});
|
|
98
|
+
}
|
|
99
|
+
return new Response(null, { status: 302, headers: {
|
|
100
|
+
Location: urlRewrite
|
|
101
|
+
}});
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
entries.push(this.get(attr));
|
|
105
|
+
}
|
|
106
|
+
if (callback) return await callback(...entries);
|
|
107
|
+
return entries;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
@@ -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
|
+
document.cookie.split(';').map((c) => c.split('=').map((s) => s.trim()))
|
|
8
|
+
);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
commit(response) {
|
|
12
|
+
for (const cookieStr of this.render()) {
|
|
13
|
+
document.cookie = cookieStr;
|
|
14
|
+
}
|
|
15
|
+
super.commit();
|
|
16
|
+
}
|
|
17
|
+
}
|