@webqit/webflo 0.11.61 → 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/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 +1 -1
- 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
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { _isObject } from '@webqit/util/js/index.js';
|
|
2
|
+
import { renderCookieObj } from './util-http.js';
|
|
3
|
+
import { WebfloStorage } from './WebfloStorage.js';
|
|
4
|
+
|
|
5
|
+
export class WebfloCookieStorage extends WebfloStorage {
|
|
6
|
+
constructor(request, iterable = []) {
|
|
7
|
+
iterable = [...iterable].map(([key, value]) => [key, !_isObject(value) ? { name: key, value } : value]);
|
|
8
|
+
super(request, null, iterable);
|
|
9
|
+
this.saveOriginals();
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
render() {
|
|
13
|
+
return this.getAdded().map((key) => renderCookieObj({ name: key, ...this.get(key, true) })).concat(
|
|
14
|
+
this.getDeleted().map((key) => renderCookieObj({ name: key, value: '', maxAge: 0 }))
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
set(key, value) {
|
|
19
|
+
if (!_isObject(value)) { value = { name: key, value }; }
|
|
20
|
+
return super.set(key, value);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
get(key, withDetail = false) {
|
|
24
|
+
if (!withDetail) return super.get(key)?.value;
|
|
25
|
+
return super.get(key);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -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,43 +1,13 @@
|
|
|
1
|
-
|
|
2
|
-
/**
|
|
3
|
-
* @imports
|
|
4
|
-
*/
|
|
5
1
|
import { _isString, _isFunction, _isArray } from '@webqit/util/js/index.js';
|
|
6
2
|
import { _from as _arrFrom } from '@webqit/util/arr/index.js';
|
|
7
3
|
|
|
8
|
-
|
|
9
|
-
* ---------------------------
|
|
10
|
-
* The Router class
|
|
11
|
-
* ---------------------------
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
export default class Router {
|
|
4
|
+
export class WebfloRouter {
|
|
15
5
|
|
|
16
|
-
/**
|
|
17
|
-
* Constructs a new Router instance
|
|
18
|
-
* over route definitions.
|
|
19
|
-
*
|
|
20
|
-
* @param Context cx
|
|
21
|
-
* @param String|Array path
|
|
22
|
-
*
|
|
23
|
-
* @return void
|
|
24
|
-
*/
|
|
25
6
|
constructor(cx, path = []) {
|
|
26
7
|
this.cx = cx;
|
|
27
8
|
this.path = _isArray(path) ? path : (path + '').split('/').filter(a => a);
|
|
28
9
|
}
|
|
29
10
|
|
|
30
|
-
/**
|
|
31
|
-
* Performs dynamic routing
|
|
32
|
-
*
|
|
33
|
-
* @param array|string method
|
|
34
|
-
* @param Object event
|
|
35
|
-
* @param any arg
|
|
36
|
-
* @param function _default
|
|
37
|
-
* @param function remoteFetch
|
|
38
|
-
*
|
|
39
|
-
* @return object
|
|
40
|
-
*/
|
|
41
11
|
async route(method, event, arg, _default, remoteFetch = null) {
|
|
42
12
|
|
|
43
13
|
const $this = this;
|
|
@@ -57,7 +27,7 @@ export default class Router {
|
|
|
57
27
|
// -------------
|
|
58
28
|
if (thisTick.exports) {
|
|
59
29
|
// Broadcast any hints exported by handler
|
|
60
|
-
if (thisTick.exports.hints) { await event.port.post({ ...thisTick.exports.hints, $type: 'handler:hints' }); }
|
|
30
|
+
//@obsolete if (thisTick.exports.hints) { await event.port.post({ ...thisTick.exports.hints, $type: 'handler:hints' }); }
|
|
61
31
|
const methods = _arrFrom(thisTick.method).map(m => m === 'default' ? m : m.toUpperCase());
|
|
62
32
|
const handler = _isFunction(thisTick.exports) && methods.includes('default') ? thisTick.exports : methods.reduce((_handler, name) => _handler || thisTick.exports[name], null);
|
|
63
33
|
if (handler) {
|
|
@@ -68,7 +38,7 @@ export default class Router {
|
|
|
68
38
|
const nextTick = { ...thisTick, arg: _args[0] };
|
|
69
39
|
if (_args.length > 1) {
|
|
70
40
|
let _url = _args[1], _request, requestInit = { ...(_args[2] || {}) };
|
|
71
|
-
if (_args[1] instanceof
|
|
41
|
+
if (_args[1] instanceof Request) {
|
|
72
42
|
_request = _args[1];
|
|
73
43
|
_url = _request.url;
|
|
74
44
|
} else if (!_isString(_url)) {
|
|
@@ -126,5 +96,4 @@ export default class Router {
|
|
|
126
96
|
});
|
|
127
97
|
|
|
128
98
|
}
|
|
129
|
-
|
|
130
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
|
+
}
|
|
@@ -1,17 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
/**
|
|
3
|
-
* @imports
|
|
4
|
-
*/
|
|
5
1
|
import { path as Path } from '../util-url.js';
|
|
6
|
-
import
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* ---------------------------
|
|
10
|
-
* The Router class
|
|
11
|
-
* ---------------------------
|
|
12
|
-
*/
|
|
2
|
+
import { WebfloRouter } from '../WebfloRouter.js';
|
|
13
3
|
|
|
14
|
-
export
|
|
4
|
+
export class Router extends WebfloRouter {
|
|
15
5
|
|
|
16
6
|
async readTick(thisTick) {
|
|
17
7
|
thisTick = { ...thisTick };
|
|
@@ -45,4 +35,4 @@ export default class Router extends _Router {
|
|
|
45
35
|
pathJoin(...args) {
|
|
46
36
|
return Path.join(...args);
|
|
47
37
|
}
|
|
48
|
-
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { WebfloStorage } from '../WebfloStorage.js';
|
|
2
|
+
|
|
3
|
+
export class SessionStorage extends WebfloStorage {
|
|
4
|
+
static get type() { return 'session'; }
|
|
5
|
+
|
|
6
|
+
static create(request) {
|
|
7
|
+
const keys = [];
|
|
8
|
+
const storeType = this.type === 'user' ? 'localStorage' : 'sessionStorage';
|
|
9
|
+
for(let i = 0; i < window[storeType].length; i ++){
|
|
10
|
+
keys.push(window[storeType].key(i));
|
|
11
|
+
};
|
|
12
|
+
const instance = new this(
|
|
13
|
+
request,
|
|
14
|
+
keys.map((key) => [key, window[storeType].getItem(key)])
|
|
15
|
+
);
|
|
16
|
+
return instance;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
constructor(request, iterable) {
|
|
20
|
+
super(request, true, iterable);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
commit() {
|
|
24
|
+
const storeType = this.constructor.type === 'user' ? 'localStorage' : 'sessionStorage';
|
|
25
|
+
for (const key of this.getAdded()) {
|
|
26
|
+
window[storeType].setItem(key, this.get(key));
|
|
27
|
+
}
|
|
28
|
+
for (const key of this.getDeleted()) {
|
|
29
|
+
window[storeType].removeItem(key);
|
|
30
|
+
}
|
|
31
|
+
super.commit();
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -1,28 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
/**
|
|
3
|
-
* @imports
|
|
4
|
-
*/
|
|
5
1
|
import { _with } from '@webqit/util/obj/index.js';
|
|
6
2
|
import { _isArray, _isObject, _isTypeObject, _isString, _isEmpty } from '@webqit/util/js/index.js';
|
|
7
|
-
import { Observer } from './Runtime.js';
|
|
8
3
|
import { params } from '../util-url.js';
|
|
9
4
|
|
|
10
|
-
|
|
11
|
-
* ---------------------------
|
|
12
|
-
* The Url class
|
|
13
|
-
* ---------------------------
|
|
14
|
-
*/
|
|
5
|
+
const { Observer } = webqit;
|
|
15
6
|
|
|
16
|
-
export
|
|
7
|
+
export class Url {
|
|
17
8
|
|
|
18
|
-
/**
|
|
19
|
-
* Constructs a new Url instance.
|
|
20
|
-
*
|
|
21
|
-
* @param object input
|
|
22
|
-
* @param object pathMappingScheme
|
|
23
|
-
*
|
|
24
|
-
* @return void
|
|
25
|
-
*/
|
|
26
9
|
constructor(input) {
|
|
27
10
|
const Self = this.constructor;
|
|
28
11
|
// -----------------------
|
|
@@ -56,6 +39,9 @@ export default class Url {
|
|
|
56
39
|
// ----------
|
|
57
40
|
if (e.key === 'href' && e.related.length === 1) {
|
|
58
41
|
var urlObj = Self.parseUrl(e.value);
|
|
42
|
+
if (urlObj.pathname) {
|
|
43
|
+
urlObj.pathname = '/' + urlObj.pathname.split('/').filter(s => s.trim()).join('/');
|
|
44
|
+
}
|
|
59
45
|
delete urlObj.query;
|
|
60
46
|
delete urlObj.href;
|
|
61
47
|
onlyHrefChanged = true;
|
|
@@ -68,13 +54,27 @@ export default class Url {
|
|
|
68
54
|
urlObj.search = search;
|
|
69
55
|
}
|
|
70
56
|
}
|
|
71
|
-
if (e.key === 'search') {
|
|
57
|
+
if (e.key === 'search' && !e.related.includes('query')) {
|
|
72
58
|
// "search" was updated. So we update "query"
|
|
73
59
|
var query = Self.toQuery(urlObj.search || this.search); // Not e.value, as that might be a href value
|
|
74
60
|
if (!_strictEven(query, this.query)) {
|
|
75
61
|
urlObj.query = query;
|
|
76
62
|
}
|
|
77
63
|
}
|
|
64
|
+
if (e.key === 'pathname' && !e.related.includes('ancestorPathname')) {
|
|
65
|
+
// "pathname" was updated. So we update "ancestorPathname"
|
|
66
|
+
var ancestorPathname = (urlObj.pathname || this.pathname).replace(new RegExp('/[^/]+(?:/)?$'), '');
|
|
67
|
+
if (ancestorPathname !== this.ancestorPathname) {
|
|
68
|
+
urlObj.ancestorPathname = ancestorPathname;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
if (e.key === 'ancestorPathname' && !e.related.includes('pathname')) {
|
|
72
|
+
// "ancestorPathname" was updated. So we update "pathname"
|
|
73
|
+
var pathname = '/' + (urlObj.ancestorPathname || this.ancestorPathname).split('/').filter(s => s).concat((urlObj.pathname || this.pathname).split('/').filter(s => s).pop()).join('/');
|
|
74
|
+
if (pathname !== this.pathname) {
|
|
75
|
+
urlObj.pathname = pathname;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
78
|
}
|
|
79
79
|
if (!onlyHrefChanged) {
|
|
80
80
|
var fullOrigin = this.origin,
|
|
@@ -110,85 +110,37 @@ export default class Url {
|
|
|
110
110
|
Observer.set(this, _isString(input) ? Self.parseUrl(input) : Url.copy(input));
|
|
111
111
|
}
|
|
112
112
|
|
|
113
|
-
/**
|
|
114
|
-
* Converts the instance to string.
|
|
115
|
-
*
|
|
116
|
-
* @return string
|
|
117
|
-
*/
|
|
118
113
|
toString() {
|
|
119
114
|
return this.href;
|
|
120
115
|
}
|
|
121
116
|
|
|
122
|
-
/**
|
|
123
|
-
* Creates an instance from parsing an URL string
|
|
124
|
-
* or from a regular object.
|
|
125
|
-
*
|
|
126
|
-
* @param string|object href
|
|
127
|
-
*
|
|
128
|
-
* @return Url
|
|
129
|
-
*/
|
|
130
117
|
static from(href) {
|
|
131
118
|
return new this(_isObject(href) ? href : this.parseUrl(href));
|
|
132
119
|
}
|
|
133
120
|
|
|
134
|
-
/**
|
|
135
|
-
* Copies URL properties off
|
|
136
|
-
* the given object.
|
|
137
|
-
*
|
|
138
|
-
* @param object urlObj
|
|
139
|
-
*
|
|
140
|
-
* @return object
|
|
141
|
-
*/
|
|
142
121
|
static copy(urlObj) {
|
|
143
|
-
var url = urlProperties.reduce((obj, prop) => _with(obj, prop, urlObj[prop]), {});
|
|
122
|
+
var url = urlProperties.reduce((obj, prop) => _with(obj, prop, urlObj[prop] || ''), {});
|
|
144
123
|
if (!('query' in urlObj)) {
|
|
145
124
|
delete url.query;
|
|
146
125
|
}
|
|
147
126
|
return url;
|
|
148
127
|
}
|
|
149
128
|
|
|
150
|
-
/**
|
|
151
|
-
* Parses an URL and returns its properties
|
|
152
|
-
*
|
|
153
|
-
* @param string href
|
|
154
|
-
*
|
|
155
|
-
* @return object
|
|
156
|
-
*/
|
|
157
129
|
static parseUrl(href) {
|
|
158
|
-
var a =
|
|
159
|
-
a.href = href;
|
|
130
|
+
var a = new URL(href);
|
|
160
131
|
return this.copy(a);
|
|
161
132
|
}
|
|
162
133
|
|
|
163
|
-
/**
|
|
164
|
-
* Parses the input search string into a named map
|
|
165
|
-
*
|
|
166
|
-
* @param string search
|
|
167
|
-
*
|
|
168
|
-
* @return object
|
|
169
|
-
*/
|
|
170
134
|
static toQuery(search) {
|
|
171
135
|
return params.parse(search);
|
|
172
136
|
}
|
|
173
137
|
|
|
174
|
-
/**
|
|
175
|
-
* Stringifies the input query to search string.
|
|
176
|
-
*
|
|
177
|
-
* @param object query
|
|
178
|
-
*
|
|
179
|
-
* @return string
|
|
180
|
-
*/
|
|
181
138
|
static toSearch(query) {
|
|
182
139
|
var search = params.stringify(query);
|
|
183
140
|
return search ? '?' + search : '';
|
|
184
|
-
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
185
143
|
|
|
186
|
-
/**
|
|
187
|
-
* These are standard
|
|
188
|
-
* and shouldnt'/can't be modified
|
|
189
|
-
*
|
|
190
|
-
* @array
|
|
191
|
-
*/
|
|
192
144
|
const urlProperties = [
|
|
193
145
|
'protocol',
|
|
194
146
|
'username',
|