@webqit/webflo 0.20.25 → 0.20.27
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 +8 -5
- package/src/build-pi/index.js +7 -5
- package/src/init-pi/index.js +0 -1
- package/src/runtime-pi/{WebfloRuntime.js → AppRuntime.js} +57 -113
- package/src/runtime-pi/webflo-client/DeviceCapabilities.js +1 -1
- package/src/runtime-pi/webflo-client/WebfloClient.js +163 -95
- package/src/runtime-pi/webflo-client/{WebfloRootClient1.js → WebfloRootClientA.js} +39 -56
- package/src/runtime-pi/webflo-client/{WebfloRootClient2.js → WebfloRootClientB.js} +3 -3
- package/src/runtime-pi/webflo-client/WebfloSubClient.js +28 -15
- package/src/runtime-pi/webflo-client/index.js +3 -3
- package/src/runtime-pi/webflo-messaging/ClientPortMixin.js +13 -0
- package/src/runtime-pi/{webflo-server/messaging/ClientRequestRealtime.js → webflo-messaging/ClientRequestPort001.js} +13 -9
- package/src/runtime-pi/webflo-messaging/ClientRequestPort010.js +4 -0
- package/src/runtime-pi/webflo-messaging/ClientRequestPort100.js +17 -0
- package/src/runtime-pi/webflo-messaging/WebfloTenancy001.js +27 -0
- package/src/runtime-pi/webflo-messaging/WebfloTenant001.js +27 -0
- package/src/runtime-pi/webflo-routing/HttpCookies101.js +53 -0
- package/src/runtime-pi/webflo-routing/HttpCookies110.js +3 -0
- package/src/runtime-pi/webflo-routing/{HttpEvent.js → HttpEvent111.js} +95 -73
- package/src/runtime-pi/webflo-routing/HttpKeyvalInterface.js +120 -0
- package/src/runtime-pi/webflo-routing/HttpSession001.js +24 -0
- package/src/runtime-pi/webflo-routing/HttpSession110.js +3 -0
- package/src/runtime-pi/webflo-routing/{HttpThread.js → HttpThread111.js} +54 -13
- package/src/runtime-pi/webflo-routing/{HttpUser.js → HttpUser111.js} +10 -23
- package/src/runtime-pi/webflo-routing/KeyvalsFactory001.js +53 -0
- package/src/runtime-pi/webflo-routing/KeyvalsFactory110.js +48 -0
- package/src/runtime-pi/webflo-routing/KeyvalsFactoryInterface.js +56 -0
- package/src/runtime-pi/webflo-routing/{WebfloRouter.js → WebfloRouter111.js} +5 -6
- package/src/runtime-pi/webflo-server/WebfloServer.js +262 -266
- package/src/runtime-pi/webflo-worker/WebfloWorker.js +97 -44
- package/src/util.js +3 -2
- package/src/runtime-pi/apis.js +0 -9
- package/src/runtime-pi/webflo-client/ClientSideCookies.js +0 -18
- package/src/runtime-pi/webflo-fetch/LiveResponse.js +0 -476
- package/src/runtime-pi/webflo-fetch/index.js +0 -419
- package/src/runtime-pi/webflo-fetch/util.js +0 -28
- package/src/runtime-pi/webflo-messaging/WQBroadcastChannel.js +0 -10
- package/src/runtime-pi/webflo-messaging/WQMessageChannel.js +0 -26
- package/src/runtime-pi/webflo-messaging/WQMessageEvent.js +0 -87
- package/src/runtime-pi/webflo-messaging/WQMessagePort.js +0 -38
- package/src/runtime-pi/webflo-messaging/WQRelayPort.js +0 -47
- package/src/runtime-pi/webflo-messaging/WQSockPort.js +0 -111
- package/src/runtime-pi/webflo-messaging/WQStarPort.js +0 -112
- package/src/runtime-pi/webflo-messaging/wq-message-port.js +0 -413
- package/src/runtime-pi/webflo-routing/HttpCookies.js +0 -43
- package/src/runtime-pi/webflo-routing/HttpSession.js +0 -11
- package/src/runtime-pi/webflo-routing/HttpState.js +0 -182
- package/src/runtime-pi/webflo-server/ServerSideCookies.js +0 -22
- package/src/runtime-pi/webflo-server/ServerSideSession.js +0 -40
- package/src/runtime-pi/webflo-server/messaging/Client.js +0 -27
- package/src/runtime-pi/webflo-server/messaging/Clients.js +0 -25
- package/src/runtime-pi/webflo-url/Url.js +0 -156
- package/src/runtime-pi/webflo-url/index.js +0 -1
- package/src/runtime-pi/webflo-url/urlpattern.js +0 -38
- package/src/runtime-pi/webflo-url/util.js +0 -109
- package/src/runtime-pi/webflo-url/xURL.js +0 -94
- package/src/runtime-pi/webflo-worker/WorkerSideCookies.js +0 -21
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { Observer } from '@webqit/
|
|
1
|
+
import { Observer } from '@webqit/observer';
|
|
2
|
+
import { LiveResponse } from '@webqit/fetch-plus';
|
|
2
3
|
import { WebfloClient } from './WebfloClient.js';
|
|
3
4
|
import { defineElement } from './webflo-embedded.js';
|
|
4
|
-
import {
|
|
5
|
-
import { _wq } from '../../util.js';
|
|
5
|
+
import { _meta } from '../../util.js';
|
|
6
6
|
|
|
7
7
|
export class WebfloSubClient extends WebfloClient {
|
|
8
8
|
|
|
@@ -40,21 +40,26 @@ export class WebfloSubClient extends WebfloClient {
|
|
|
40
40
|
if (this.host.location.origin !== window.location.origin) {
|
|
41
41
|
throw new Error(`Webflo embeddable origin violation in "${window.location}"`);
|
|
42
42
|
}
|
|
43
|
+
|
|
43
44
|
const instanceController = await super.initialize();
|
|
44
|
-
|
|
45
|
+
|
|
46
|
+
_meta(this.background).set('parentNode', this.#superRuntime.background);
|
|
45
47
|
instanceController.signal.addEventListener('abort', () => {
|
|
46
|
-
if (
|
|
47
|
-
|
|
48
|
+
if (_meta(this.background).get('parentNode') === this.#superRuntime.background) {
|
|
49
|
+
_meta(this.background).set('parentNode', null);
|
|
48
50
|
}
|
|
49
51
|
}, { once: true });
|
|
52
|
+
|
|
50
53
|
return instanceController;
|
|
51
54
|
}
|
|
52
55
|
|
|
53
56
|
async hydrate() {
|
|
54
57
|
const instanceController = await super.hydrate();
|
|
58
|
+
|
|
55
59
|
if (this.host.hasAttribute('location')) {
|
|
56
60
|
await this.navigate(this.location.href);
|
|
57
61
|
}
|
|
62
|
+
|
|
58
63
|
return instanceController;
|
|
59
64
|
}
|
|
60
65
|
|
|
@@ -62,6 +67,7 @@ export class WebfloSubClient extends WebfloClient {
|
|
|
62
67
|
const locationCallback = (newHref) => {
|
|
63
68
|
this.host.reflectLocation(newHref);
|
|
64
69
|
};
|
|
70
|
+
|
|
65
71
|
return super.controlClassic/*IMPORTANT*/(locationCallback);
|
|
66
72
|
}
|
|
67
73
|
|
|
@@ -87,36 +93,43 @@ export class WebfloSubClient extends WebfloClient {
|
|
|
87
93
|
if (httpEvent.url.hash) {
|
|
88
94
|
this.host.querySelector(httpEvent.url.hash)?.scrollIntoView();
|
|
89
95
|
} else await super.applyPostRenderState(httpEvent);
|
|
96
|
+
|
|
90
97
|
(this.host.querySelector('[autofocus]') || this.host).focus();
|
|
91
98
|
}
|
|
92
99
|
|
|
93
|
-
redirect(location, response = null) {
|
|
100
|
+
async redirect(location, response = null) {
|
|
94
101
|
location = typeof location === 'string' ? new URL(location, this.location.origin) : location;
|
|
102
|
+
|
|
95
103
|
const width = Math.min(800, window.innerWidth);
|
|
96
104
|
const height = Math.min(600, window.innerHeight);
|
|
97
105
|
const left = (window.outerWidth - width) / 2;
|
|
98
106
|
const top = (window.outerHeight - height) / 2;
|
|
99
107
|
const popup = window.open(location, '_blank', `popup=true,width=${width},height=${height},left=${left},top=${top}`);
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
108
|
+
|
|
109
|
+
const backgroundPort = response instanceof LiveResponse
|
|
110
|
+
? response.port
|
|
111
|
+
: LiveResponse.getPort(response);
|
|
112
|
+
if (backgroundPort) {
|
|
113
|
+
Observer.set(this.navigator, 'redirecting', new URL(location), { diff: true });
|
|
114
|
+
|
|
115
|
+
backgroundPort.readyStateChange('close').then((e) => {
|
|
105
116
|
window.removeEventListener('message', windowMessageHandler);
|
|
117
|
+
|
|
106
118
|
Observer.set(this.navigator, 'redirecting', null);
|
|
107
119
|
popup.postMessage('timeout:5');
|
|
120
|
+
|
|
108
121
|
setTimeout(() => {
|
|
109
122
|
popup.close();
|
|
110
123
|
}, 5000);
|
|
111
|
-
}
|
|
124
|
+
});
|
|
125
|
+
|
|
112
126
|
const windowMessageHandler = (e) => {
|
|
113
127
|
if (e.source === popup && e.data === 'close') {
|
|
114
128
|
backgroundPort.close();
|
|
115
129
|
}
|
|
116
130
|
};
|
|
131
|
+
|
|
117
132
|
window.addEventListener('message', windowMessageHandler);
|
|
118
133
|
}
|
|
119
|
-
|
|
120
|
-
return 3; // keep-alive
|
|
121
134
|
}
|
|
122
135
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { WebfloRootClientA } from './WebfloRootClientA.js';
|
|
2
|
+
import { WebfloRootClientB } from './WebfloRootClientB.js';
|
|
3
3
|
import { WebfloSubClient } from './WebfloSubClient.js';
|
|
4
4
|
|
|
5
5
|
export async function start(bootstrap) {
|
|
6
|
-
const WebfloRootClient = window.navigation ?
|
|
6
|
+
const WebfloRootClient = window.navigation ? WebfloRootClientB : WebfloRootClientA;
|
|
7
7
|
const instance = WebfloRootClient.create(bootstrap, document);
|
|
8
8
|
await instance.initialize();
|
|
9
9
|
WebfloSubClient.defineElement();
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export const ClientPortMixin = (superClass) => class extends superClass {
|
|
2
|
+
|
|
3
|
+
async query(query, callback, options = {}) {
|
|
4
|
+
return await new Promise((resolve) => {
|
|
5
|
+
this.postRequest(
|
|
6
|
+
{ query },
|
|
7
|
+
(event) => resolve(callback ? callback(event) : event),
|
|
8
|
+
{ ...options, type: 'query' }
|
|
9
|
+
);
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
};
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { ClientPortMixin } from './ClientPortMixin.js';
|
|
2
|
+
import { StarPort } from '@webqit/port-plus';
|
|
3
|
+
import { _meta } from '../../util.js';
|
|
3
4
|
|
|
4
|
-
export class
|
|
5
|
+
export class ClientRequestPort001 extends ClientPortMixin(StarPort) {
|
|
5
6
|
|
|
6
7
|
#portID;
|
|
7
8
|
get portID() { return this.#portID; }
|
|
@@ -15,8 +16,8 @@ export class ClientRequestRealtime extends WQStarPort {
|
|
|
15
16
|
#navigatedAway = false;
|
|
16
17
|
navigatedAway() { return this.#navigatedAway; }
|
|
17
18
|
|
|
18
|
-
constructor(portID, url) {
|
|
19
|
-
super();
|
|
19
|
+
constructor(portID, url, options = {}) {
|
|
20
|
+
super(options);
|
|
20
21
|
this.#portID = portID;
|
|
21
22
|
this.#url = url;
|
|
22
23
|
const $url = new URL(url);
|
|
@@ -38,13 +39,16 @@ export class ClientRequestRealtime extends WQStarPort {
|
|
|
38
39
|
}
|
|
39
40
|
|
|
40
41
|
enterChannel(channelID, { resolveData = null } = {}) {
|
|
41
|
-
const
|
|
42
|
-
const clients =
|
|
42
|
+
const webfloTenant = _meta(this).get('parentNode');
|
|
43
|
+
const clients = webfloTenant && _meta(webfloTenant).get('parentNode');
|
|
44
|
+
|
|
43
45
|
if (!clients) {
|
|
44
46
|
throw new Error('Instance seem not connected to the messaging system.');
|
|
45
47
|
}
|
|
48
|
+
|
|
46
49
|
const channel = clients.getChannel(channelID, true);
|
|
47
|
-
const leave = channel.addPort(
|
|
50
|
+
const leave = channel.addPort(webfloTenant, { resolveData });
|
|
51
|
+
|
|
48
52
|
return { channel, leave };
|
|
49
53
|
}
|
|
50
|
-
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { ClientPortMixin } from './ClientPortMixin.js';
|
|
2
|
+
import { MessageChannelPlus } from '@webqit/port-plus';
|
|
3
|
+
|
|
4
|
+
export class ClientRequestPort100 extends MessageChannelPlus {
|
|
5
|
+
|
|
6
|
+
constructor(options = {}) {
|
|
7
|
+
super(options);
|
|
8
|
+
|
|
9
|
+
const superInstance = new (ClientPortMixin(class { }));
|
|
10
|
+
Object.defineProperty(this.port1, 'query', {
|
|
11
|
+
value: function (...args) {
|
|
12
|
+
return superInstance.query.apply(this, args);
|
|
13
|
+
}
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { RelayPort, StarPort } from '@webqit/port-plus';
|
|
2
|
+
import { WebfloTenant001 } from './WebfloTenant001.js';
|
|
3
|
+
|
|
4
|
+
export class WebfloTenancy001 extends StarPort {
|
|
5
|
+
|
|
6
|
+
#channels = new Map;
|
|
7
|
+
|
|
8
|
+
getTenant(tenantID, autoCreate = false) {
|
|
9
|
+
if (autoCreate && !this.findPort((tenant) => tenant.tenantID === tenantID)) {
|
|
10
|
+
const tenant = new WebfloTenant001(tenantID, { handshake: 1, postAwaitsOpen: true, autoClose: false });
|
|
11
|
+
const cleanup = this.addPort(tenant);
|
|
12
|
+
tenant.readyStateChange('close').then(cleanup);
|
|
13
|
+
}
|
|
14
|
+
return this.findPort((tenant) => tenant.tenantID === tenantID);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
getChannel(channelName, autoCreate = false) {
|
|
18
|
+
if (!this.#channels.has(channelName) && autoCreate) {
|
|
19
|
+
const channel = new RelayPort(channelName, { handshake: 1, postAwaitsOpen: true, autoClose: true });
|
|
20
|
+
|
|
21
|
+
this.#channels.set(channelName, channel);
|
|
22
|
+
channel.readyStateChange('close').then(() => this.#channels.delete(channelName));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return this.#channels.get(channelName);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { StarPort } from '@webqit/port-plus';
|
|
2
|
+
import { ClientRequestPort001 } from './ClientRequestPort001.js';
|
|
3
|
+
|
|
4
|
+
export class WebfloTenant001 extends StarPort {
|
|
5
|
+
|
|
6
|
+
#tenantID;
|
|
7
|
+
get tenantID() { return this.#tenantID; }
|
|
8
|
+
|
|
9
|
+
constructor(tenantID, options = {}) {
|
|
10
|
+
super(options);
|
|
11
|
+
this.#tenantID = tenantID;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
getRequestPort(portID) {
|
|
15
|
+
return this.findPort((port) => port.portID === portID);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
createRequestPort(portID, url = null) {
|
|
19
|
+
const requestPort = new ClientRequestPort001(portID, url, { handshake: 1, postAwaitsOpen: true, autoClose: true });
|
|
20
|
+
this.addPort(requestPort);
|
|
21
|
+
setTimeout(() => {
|
|
22
|
+
if (requestPort.length || !this.findPort((port) => port === requestPort)) return;
|
|
23
|
+
requestPort.close(true);
|
|
24
|
+
}, 15000/*15sec*/);
|
|
25
|
+
return requestPort;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { HttpKeyvalInterface } from './HttpKeyvalInterface.js';
|
|
2
|
+
|
|
3
|
+
export class HttpCookies101 extends HttpKeyvalInterface {
|
|
4
|
+
|
|
5
|
+
#initial;
|
|
6
|
+
|
|
7
|
+
constructor({ context = {}, store, initial = {}, realm = 0 }) {
|
|
8
|
+
super({ context, store, initial, realm });
|
|
9
|
+
this.#initial = initial;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// ------ lifecycle
|
|
13
|
+
|
|
14
|
+
async _commit(response, devMode = false) {
|
|
15
|
+
// Mutate in-place - to reflect across clones
|
|
16
|
+
Object.keys(this.#initial).forEach((k) => delete this.#initial[k]);
|
|
17
|
+
Object.assign(this.#initial, await this.json());
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// ------ extras
|
|
21
|
+
|
|
22
|
+
async render() {
|
|
23
|
+
const json = await this.json(true);
|
|
24
|
+
|
|
25
|
+
const entries = Object.keys(json).concat(Object.keys(this.#initial)).map((key) => {
|
|
26
|
+
const a = this.#initial[key];
|
|
27
|
+
const b = json[key];
|
|
28
|
+
if (a === b.value) {
|
|
29
|
+
// Same
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
if ([undefined, null].includes(b)) {
|
|
33
|
+
// Deleted
|
|
34
|
+
return { name: key, value: '', maxAge: 0 };
|
|
35
|
+
}
|
|
36
|
+
// Added or modified
|
|
37
|
+
return { name: key, ...b };
|
|
38
|
+
}).filter((e) => e);
|
|
39
|
+
|
|
40
|
+
return entries.map((e) => renderCookieObjToString(e));
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function renderCookieObjToString(cookieObj) {
|
|
45
|
+
const attrsArr = [`${cookieObj.name}=${/*encodeURIComponent*/(cookieObj.value)}`];
|
|
46
|
+
for (const attrName in cookieObj) {
|
|
47
|
+
if (['name', 'value'].includes(attrName)) continue;
|
|
48
|
+
let _attrName = attrName[0].toUpperCase() + attrName.substring(1);
|
|
49
|
+
if (_attrName === 'MaxAge') { _attrName = 'Max-Age' };
|
|
50
|
+
attrsArr.push(cookieObj[attrName] === true ? _attrName : `${_attrName}=${cookieObj[attrName]}`);
|
|
51
|
+
}
|
|
52
|
+
return attrsArr.join('; ');
|
|
53
|
+
}
|
|
@@ -1,106 +1,140 @@
|
|
|
1
1
|
import { _isObject } from '@webqit/util/js/index.js';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { xURL } from '../webflo-url/xURL.js';
|
|
5
|
-
import { _wq } from '../../util.js';
|
|
2
|
+
import { LiveResponse } from '@webqit/fetch-plus';
|
|
3
|
+
import { URLPlus } from '@webqit/url-plus';
|
|
6
4
|
|
|
7
|
-
export class
|
|
5
|
+
export class HttpEvent111 {
|
|
8
6
|
|
|
9
|
-
static create(
|
|
10
|
-
return new this(
|
|
7
|
+
static create(init = {}) {
|
|
8
|
+
return new this(init);
|
|
11
9
|
}
|
|
12
10
|
|
|
13
|
-
#
|
|
14
|
-
#url;
|
|
11
|
+
#context = {};
|
|
15
12
|
#init;
|
|
16
|
-
#
|
|
17
|
-
|
|
18
|
-
constructor(parentEvent, { request, cookies, session, user, client, detail, signal, state, ...rest }) {
|
|
19
|
-
this.#parentEvent = parentEvent;
|
|
20
|
-
this.#init = { request, cookies, session, user, client, detail, signal, state, ...rest };
|
|
21
|
-
this.#url = new xURL(this.#init.request.url);
|
|
22
|
-
this.#parentEvent?.signal.addEventListener('abort', () => this.#abortController.abort(), { once: true });
|
|
23
|
-
this.#init.request.signal?.addEventListener('abort', () => this.#abortController.abort(), { once: true });
|
|
24
|
-
this.#lifeCycleResolutionPromise.finally(() => {
|
|
25
|
-
this.#abortController.abort();
|
|
26
|
-
});
|
|
27
|
-
}
|
|
13
|
+
#url;
|
|
28
14
|
|
|
29
|
-
|
|
15
|
+
#readyStates;
|
|
16
|
+
#abortController = new AbortController;
|
|
30
17
|
|
|
31
|
-
|
|
18
|
+
#internalLiveResponse = new LiveResponse(null, { done: false });
|
|
19
|
+
get internalLiveResponse() { return this.#internalLiveResponse; }
|
|
32
20
|
|
|
33
|
-
get
|
|
21
|
+
get _context() { return this.#context; }
|
|
22
|
+
get _parentEvent() { return this.#context?.parentEvent; }
|
|
34
23
|
|
|
35
|
-
|
|
24
|
+
constructor({ context = {}, request, thread, cookies, session, user, client, detail, signal, state, ...rest }) {
|
|
25
|
+
if (context) Object.assign(this.#context, context);
|
|
36
26
|
|
|
37
|
-
|
|
27
|
+
this.#init = { context, request, thread, cookies, session, user, client, detail, signal, state, ...rest };
|
|
28
|
+
[thread, cookies, session, user].forEach((node) => {
|
|
29
|
+
if (!node) return;
|
|
30
|
+
node._context.parentEvent = this;
|
|
31
|
+
});
|
|
38
32
|
|
|
39
|
-
|
|
33
|
+
this.#url = new URLPlus(this.#init.request.url);
|
|
40
34
|
|
|
41
|
-
|
|
35
|
+
this._parentEvent?.signal.addEventListener('abort', () => this.#abortController.abort(), { once: true });
|
|
36
|
+
this.#init.request.signal?.addEventListener('abort', () => this.#abortController.abort(), { once: true });
|
|
42
37
|
|
|
43
|
-
|
|
38
|
+
const $ref = (o) => {
|
|
39
|
+
o.promise = new Promise((res, rej) => (o.resolve = res, o.reject = rej));
|
|
40
|
+
return o;
|
|
41
|
+
};
|
|
42
|
+
this.#readyStates = {
|
|
43
|
+
live: $ref({}),
|
|
44
|
+
done: $ref({}),
|
|
45
|
+
};
|
|
44
46
|
|
|
45
|
-
|
|
47
|
+
this.#readyStates.done.promise.finally(() => {
|
|
48
|
+
this.#abortController.abort();
|
|
49
|
+
});
|
|
50
|
+
}
|
|
46
51
|
|
|
47
|
-
|
|
52
|
+
// ------ lifecycle
|
|
48
53
|
|
|
49
54
|
#lifecyclePromises = new Set;
|
|
50
|
-
get lifecyclePromises() { return this.#lifecyclePromises; }
|
|
51
|
-
|
|
52
|
-
#lifeCycleResolve;
|
|
53
|
-
#lifeCycleReject;
|
|
54
|
-
#lifeCycleResolutionPromise = new Promise((resolve, reject) => {
|
|
55
|
-
this.#lifeCycleResolve = resolve;
|
|
56
|
-
this.#lifeCycleReject = reject;
|
|
57
|
-
});
|
|
58
|
-
|
|
59
55
|
#extendLifecycle(promise) {
|
|
60
|
-
if (this.#
|
|
56
|
+
if (this.#readyStates.done.state) {
|
|
61
57
|
throw new Error('Event lifecycle already complete.');
|
|
62
58
|
}
|
|
63
|
-
|
|
64
|
-
|
|
59
|
+
|
|
60
|
+
if (this._parentEvent) {
|
|
61
|
+
this._parentEvent.waitUntil(promise);
|
|
65
62
|
}
|
|
63
|
+
|
|
66
64
|
promise = Promise.resolve(promise);
|
|
67
65
|
this.#lifecyclePromises.add(promise);
|
|
68
|
-
|
|
66
|
+
|
|
67
|
+
if (!this.#readyStates.live.state) {
|
|
68
|
+
this.#readyStates.live.state = true;
|
|
69
|
+
this.#readyStates.live.resolve();
|
|
70
|
+
}
|
|
71
|
+
|
|
69
72
|
return promise.then((value) => {
|
|
70
73
|
this.#lifecyclePromises.delete(promise);
|
|
71
74
|
if (!this.#lifecyclePromises.size) {
|
|
72
|
-
this.#
|
|
75
|
+
this.#readyStates.done.state = true;
|
|
76
|
+
this.#readyStates.done.resolve(value);
|
|
73
77
|
}
|
|
74
|
-
}).catch(() => {
|
|
78
|
+
}).catch((e) => {
|
|
75
79
|
this.#lifecyclePromises.delete(promise);
|
|
76
80
|
if (!this.#lifecyclePromises.size) {
|
|
77
|
-
this.#
|
|
81
|
+
this.#readyStates.done.state = true;
|
|
82
|
+
this.#readyStates.done.reject(e);
|
|
78
83
|
}
|
|
79
84
|
});
|
|
80
85
|
}
|
|
81
86
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
87
|
+
get readyState() {
|
|
88
|
+
return this.#readyStates.done.state ? 'done'
|
|
89
|
+
: (this.#readyStates.live.state ? 'live' : 'waiting');
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
readyStateChange(query) {
|
|
93
|
+
if (!['live', 'done'].includes(query)) {
|
|
94
|
+
throw new Error(`Invalid readyState query "${query}"`);
|
|
89
95
|
}
|
|
90
|
-
return
|
|
96
|
+
return this.#readyStates[query].promise;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
spawn(init = {}) {
|
|
100
|
+
const clone = this.clone(init);
|
|
101
|
+
clone._context.parentEvent = this;
|
|
102
|
+
return clone;
|
|
91
103
|
}
|
|
92
104
|
|
|
105
|
+
abort() { this.#abortController.abort(); }
|
|
106
|
+
|
|
107
|
+
clone(init = {}) {
|
|
108
|
+
const $init = { ...this.#init, ...init };
|
|
109
|
+
['thread', 'cookies', 'session', 'user'].forEach((nodeName) => {
|
|
110
|
+
if (!init[nodeName]) $init[nodeName] = $init[nodeName].clone();
|
|
111
|
+
});
|
|
112
|
+
return this.constructor.create($init);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// ------
|
|
116
|
+
|
|
117
|
+
get signal() { return this.#abortController.signal; }
|
|
118
|
+
get url() { return this.#url; }
|
|
119
|
+
get request() { return this.#init.request; }
|
|
120
|
+
get thread() { return this.#init.thread; }
|
|
121
|
+
get cookies() { return this.#init.cookies; }
|
|
122
|
+
get session() { return this.#init.session; }
|
|
123
|
+
get user() { return this.#init.user; }
|
|
124
|
+
get client() { return this.#init.client; }
|
|
125
|
+
get detail() { return this.#init.detail; }
|
|
126
|
+
get state() { return { ...(this.#init.state || {}) }; }
|
|
127
|
+
|
|
128
|
+
// ------
|
|
129
|
+
|
|
93
130
|
async waitUntil(promise) {
|
|
94
131
|
return await this.#extendLifecycle(promise);
|
|
95
132
|
}
|
|
96
133
|
|
|
97
|
-
waitUntilNavigate() {
|
|
98
|
-
this.waitUntil(new Promise(() => { }));
|
|
134
|
+
async waitUntilNavigate() {
|
|
135
|
+
/* DO NOT AWAIT */this.waitUntil(new Promise(() => { }));
|
|
99
136
|
}
|
|
100
137
|
|
|
101
|
-
#internalLiveResponse = new LiveResponse(null, { done: false });
|
|
102
|
-
get internalLiveResponse() { return this.#internalLiveResponse; }
|
|
103
|
-
|
|
104
138
|
async respondWith(data, ...args) {
|
|
105
139
|
await this.#internalLiveResponse.replaceWith(data, ...args);
|
|
106
140
|
}
|
|
@@ -141,16 +175,4 @@ export class HttpEvent {
|
|
|
141
175
|
//-----
|
|
142
176
|
return await this.respondWith(null, { status, ...options, headers: { Location: urlRewrite.href } });
|
|
143
177
|
}
|
|
144
|
-
|
|
145
|
-
clone(init = {}) {
|
|
146
|
-
return this.constructor.create(this.#parentEvent, { ...this.#init, ...init });
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
spawn(init = {}) {
|
|
150
|
-
return this.constructor.create(this/*Main difference from clone*/, { ...this.#init, ...(init || {}) });
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
abort() {
|
|
154
|
-
this.#abortController.abort();
|
|
155
|
-
}
|
|
156
|
-
}
|
|
178
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { LiveResponse } from '@webqit/fetch-plus';
|
|
2
|
+
import { KV } from '@webqit/keyval/inmemory';
|
|
3
|
+
|
|
4
|
+
export class HttpKeyvalInterface {
|
|
5
|
+
|
|
6
|
+
static create(init = {}) {
|
|
7
|
+
return new this(init);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
#context = {};
|
|
11
|
+
#store;
|
|
12
|
+
#realm;
|
|
13
|
+
#rest;
|
|
14
|
+
|
|
15
|
+
#cleanups = new Set
|
|
16
|
+
|
|
17
|
+
get _context() { return this.#context; }
|
|
18
|
+
get _parentEvent() { return this.#context?.parentEvent; }
|
|
19
|
+
|
|
20
|
+
constructor({ context = {}, store, realm = 0, ...rest }) {
|
|
21
|
+
if (!(store instanceof KV)) {
|
|
22
|
+
throw new Error('HttpKeyval expects a valid store instance!');
|
|
23
|
+
}
|
|
24
|
+
if (context) Object.assign(this.#context, context);
|
|
25
|
+
this.#store = store;
|
|
26
|
+
this.#realm = realm;
|
|
27
|
+
this.#rest = rest;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// ------ lifecycle
|
|
31
|
+
|
|
32
|
+
clone() {
|
|
33
|
+
return new this.constructor({
|
|
34
|
+
context: this.#context,
|
|
35
|
+
store: this.#store,
|
|
36
|
+
realm: this.#realm,
|
|
37
|
+
...this.#rest,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async _cleanup() { this.#cleanups.forEach((fn) => fn()); }
|
|
42
|
+
|
|
43
|
+
// ------ standard methods
|
|
44
|
+
|
|
45
|
+
async count() { return await this.#store.count(); }
|
|
46
|
+
async keys() { return [...await this.#store.keys()]; }
|
|
47
|
+
async values() { return [...await this.#store.values()]; }
|
|
48
|
+
async entries() { return [...await this.#store.entries()]; }
|
|
49
|
+
async has(key) { return await this.#store.has(key); }
|
|
50
|
+
async get(key) { return await this.#store.get(key); }
|
|
51
|
+
async set(key, value) { await this.#store.set(key, value); }
|
|
52
|
+
async delete(key) { await this.#store.delete(key); }
|
|
53
|
+
async clear() { await this.#store.clear(); }
|
|
54
|
+
async json(...args) { return await this.#store.json(...args); }
|
|
55
|
+
|
|
56
|
+
// ------ extras
|
|
57
|
+
|
|
58
|
+
subscribe(...args) {
|
|
59
|
+
const cleanup = this.#store.subscribe(...args);
|
|
60
|
+
this.#cleanups.add(cleanup);
|
|
61
|
+
return cleanup;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async require(attrs, callback = null, options = {}) {
|
|
65
|
+
const handlersRegistry = this.#context?.handlersRegistry;
|
|
66
|
+
if (!handlersRegistry) throw new Error(`No handlers registry in context`);
|
|
67
|
+
|
|
68
|
+
if (typeof callback === 'object' && callback && arguments.length === 2) {
|
|
69
|
+
options = callback;
|
|
70
|
+
callback = null;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (callback && typeof callback !== 'function') {
|
|
74
|
+
throw new Error('Callback must be a valid function when provided');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const entries = [];
|
|
78
|
+
main: for await (const attr of [].concat(attrs)) {
|
|
79
|
+
const value = await this.get(attr);
|
|
80
|
+
if (value === undefined && !await this.has(attr)
|
|
81
|
+
|| options.noNulls && [undefined, null].includes(value)) {
|
|
82
|
+
|
|
83
|
+
const handlers = handlersRegistry.get(attr);
|
|
84
|
+
if (!handlers) {
|
|
85
|
+
throw new Error(`No handler defined for the user attribute: ${attr}`);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
for (let i = 0; i < handlers.length; i++) {
|
|
89
|
+
const handler = handlers[i];
|
|
90
|
+
|
|
91
|
+
if (handler.callback) {
|
|
92
|
+
const returnValue = await handler.callback(this._parentEvent, attr);
|
|
93
|
+
if (returnValue instanceof Response || returnValue instanceof LiveResponse) {
|
|
94
|
+
await this._parentEvent.respondWith(returnValue);
|
|
95
|
+
return new Promise(() => { });
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if ((typeof returnValue === 'undefined' || (options.noNulls && returnValue === null)) && i < handlers.length - 1) {
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
entries.push(returnValue);
|
|
103
|
+
continue main;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
await this._parentEvent.redirectWith(handler.url, handler.with || {});
|
|
107
|
+
return new Promise(() => { });
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
entries.push(value);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (callback) {
|
|
115
|
+
return await callback(...entries);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return entries;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { HttpKeyvalInterface } from './HttpKeyvalInterface.js';
|
|
2
|
+
|
|
3
|
+
export class HttpSession001 extends HttpKeyvalInterface {
|
|
4
|
+
|
|
5
|
+
#sessionID;
|
|
6
|
+
get sessionID() { return this.#sessionID; }
|
|
7
|
+
#ttl;
|
|
8
|
+
|
|
9
|
+
constructor({ context = {}, store, request, thread, sessionID, ttl }) {
|
|
10
|
+
if (!sessionID) {
|
|
11
|
+
throw new Error(`sessionID is required`);
|
|
12
|
+
}
|
|
13
|
+
super({
|
|
14
|
+
context,
|
|
15
|
+
store,
|
|
16
|
+
request,
|
|
17
|
+
thread,
|
|
18
|
+
sessionID,
|
|
19
|
+
ttl
|
|
20
|
+
});
|
|
21
|
+
this.#sessionID = sessionID;
|
|
22
|
+
this.#ttl = ttl;
|
|
23
|
+
}
|
|
24
|
+
}
|