@webqit/webflo 1.0.19 → 1.0.21
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 +7 -4
- package/src/config-pi/runtime/Client.js +50 -46
- package/src/config-pi/runtime/Server.js +77 -14
- package/src/config-pi/runtime/client/Worker.js +22 -20
- package/src/runtime-pi/HttpEvent.js +34 -19
- package/src/runtime-pi/HttpUser.js +8 -84
- package/src/runtime-pi/WebfloCookieStorage.js +28 -12
- package/src/runtime-pi/WebfloRouter.js +2 -2
- package/src/runtime-pi/WebfloRuntime.js +9 -4
- package/src/runtime-pi/WebfloStorage.js +91 -34
- package/src/runtime-pi/client/Capabilities.js +211 -0
- package/src/runtime-pi/client/CookieStorage.js +3 -3
- package/src/runtime-pi/client/SessionStorage.js +8 -25
- package/src/runtime-pi/client/WebfloClient.js +15 -23
- package/src/runtime-pi/client/WebfloRootClient1.js +55 -34
- package/src/runtime-pi/client/WebfloRootClient2.js +2 -2
- package/src/runtime-pi/client/WebfloSubClient.js +9 -5
- package/src/runtime-pi/client/Workport.js +64 -91
- package/src/runtime-pi/client/generate.js +25 -16
- package/src/runtime-pi/client/index.js +3 -2
- package/src/runtime-pi/client/worker/CookieStorage.js +6 -4
- package/src/runtime-pi/client/worker/SessionStorage.js +3 -7
- package/src/runtime-pi/client/worker/WebfloWorker.js +70 -56
- package/src/runtime-pi/client/worker/index.js +3 -2
- package/src/runtime-pi/server/CookieStorage.js +6 -4
- package/src/runtime-pi/server/SessionStorage.js +17 -19
- package/src/runtime-pi/server/WebfloServer.js +66 -12
- package/src/runtime-pi/server/index.js +1 -0
- package/src/runtime-pi/util-http.js +15 -2
- package/src/services-pi/index.js +2 -0
- package/src/services-pi/push/index.js +23 -0
- package/src/static-pi/index.js +1 -1
|
@@ -2,6 +2,11 @@ import { _isObject } from '@webqit/util/js/index.js';
|
|
|
2
2
|
|
|
3
3
|
export class WebfloRuntime {
|
|
4
4
|
|
|
5
|
+
async setup(httpEvent) {
|
|
6
|
+
const router = new this.constructor.Router(this.cx, httpEvent.url.pathname);
|
|
7
|
+
return await router.route(['SETUP'], httpEvent);
|
|
8
|
+
}
|
|
9
|
+
|
|
5
10
|
async dispatch(httpEvent, context, crossLayerFetch) {
|
|
6
11
|
const requestLifecycle = {};
|
|
7
12
|
requestLifecycle.responsePromise = new Promise(async (res) => {
|
|
@@ -25,16 +30,16 @@ export class WebfloRuntime {
|
|
|
25
30
|
return await requestLifecycle.responsePromise;
|
|
26
31
|
}
|
|
27
32
|
|
|
28
|
-
async normalizeResponse(httpEvent, response
|
|
33
|
+
async normalizeResponse(httpEvent, response) {
|
|
29
34
|
// Normalize response
|
|
30
35
|
if (!(response instanceof Response)) {
|
|
31
36
|
response = typeof response === 'undefined'
|
|
32
37
|
? new Response(null, { status: 404 })
|
|
33
38
|
: Response.create(response);
|
|
34
39
|
}
|
|
35
|
-
// Commit data
|
|
36
|
-
for (const storage of [httpEvent.
|
|
37
|
-
await storage?.commit?.(response
|
|
40
|
+
// Commit data in the exact order. Reason: in how they depend on each other
|
|
41
|
+
for (const storage of [httpEvent.user, httpEvent.session, httpEvent.cookies]) {
|
|
42
|
+
await storage?.commit?.(response);
|
|
38
43
|
}
|
|
39
44
|
return response;
|
|
40
45
|
}
|
|
@@ -1,46 +1,109 @@
|
|
|
1
1
|
import { _isObject } from '@webqit/util/js/index.js';
|
|
2
2
|
import { _even } from '@webqit/util/obj/index.js';
|
|
3
3
|
|
|
4
|
-
export class WebfloStorage
|
|
4
|
+
export class WebfloStorage {
|
|
5
5
|
|
|
6
6
|
#request;
|
|
7
7
|
#session;
|
|
8
|
+
#registry;
|
|
9
|
+
#key;
|
|
10
|
+
#store;
|
|
8
11
|
|
|
9
|
-
constructor(request, session
|
|
10
|
-
|
|
12
|
+
constructor(registry, key, request, session = null) {
|
|
13
|
+
this.#registry = registry;
|
|
14
|
+
this.#key = key;
|
|
11
15
|
this.#request = request;
|
|
12
16
|
this.#session = session === true ? this : session;
|
|
13
17
|
}
|
|
14
18
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
if (!this.#
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
}
|
|
19
|
+
async store() {
|
|
20
|
+
if (!this.#key) {
|
|
21
|
+
return this.#registry;
|
|
22
|
+
}
|
|
23
|
+
if (!this.#store && !(this.#store = await this.#registry.get(this.#key))) {
|
|
24
|
+
this.#store = {};
|
|
25
|
+
await this.#registry.set(this.#key, this.#store);
|
|
26
|
+
}
|
|
27
|
+
return this.#store;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async commit() {
|
|
31
|
+
if (!this.#store || !this.#key) return;
|
|
32
|
+
await this.#registry.set(this.#key, this.#store);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
get size() { return this.store().then((store) => Object.keys(store).length); }
|
|
36
|
+
|
|
37
|
+
[ Symbol.iterator ]() { return this.entries().then((entries) => entries[ Symbol.iterator ]()); }
|
|
38
|
+
|
|
39
|
+
async json(arg = null) {
|
|
40
|
+
if (!arguments.length || typeof arg === 'boolean') {
|
|
41
|
+
return { ...(await this.store()) };
|
|
42
|
+
}
|
|
43
|
+
if (!_isObject(arg)) {
|
|
44
|
+
throw new Error(`Argument must be a valid JSON object`);
|
|
45
|
+
}
|
|
46
|
+
return await Promise.all(Object.entries(arg).map(([key, value]) => {
|
|
47
|
+
return this.set(key, value);
|
|
48
|
+
}));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async get(key) { return Reflect.get(await this.store(), key); }
|
|
52
|
+
|
|
53
|
+
async has(key) { return Reflect.has(await this.store(), key); }
|
|
54
|
+
|
|
55
|
+
async keys() { return Object.keys(await this.store()); }
|
|
56
|
+
|
|
57
|
+
async values() { return Object.values(await this.store()); }
|
|
58
|
+
|
|
59
|
+
async entries() { return Object.entries(await this.store()); }
|
|
60
|
+
|
|
61
|
+
async forEach(callback) { (await this.entries()).forEach(callback); }
|
|
62
|
+
|
|
63
|
+
async set(key, value) {
|
|
64
|
+
Reflect.set(await this.store(), key, value);
|
|
65
|
+
await this.emit(key, value);
|
|
66
|
+
return this;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async delete(key) {
|
|
70
|
+
Reflect.deleteProperty(await this.store(), key);
|
|
71
|
+
await this.emit(key);
|
|
72
|
+
return this;
|
|
23
73
|
}
|
|
24
74
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
75
|
+
async clear() {
|
|
76
|
+
for (const key of await this.keys()) {
|
|
77
|
+
Reflect.deleteProperty(await this.store(), key);
|
|
78
|
+
}
|
|
79
|
+
await this.emit();
|
|
80
|
+
return this;
|
|
30
81
|
}
|
|
31
82
|
|
|
32
|
-
|
|
33
|
-
|
|
83
|
+
#listeners = new Set;
|
|
84
|
+
observe(attr, handler) {
|
|
85
|
+
const args = { attr, handler };
|
|
86
|
+
this.#listeners.add(args);
|
|
87
|
+
return () => {
|
|
88
|
+
this.#listeners.delete(args);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async emit(attr, value) {
|
|
93
|
+
const returnValues = [];
|
|
94
|
+
for (const { attr: $attr, handler } of this.#listeners) {
|
|
95
|
+
if (arguments.length && $attr !== attr) continue;
|
|
96
|
+
if (arguments.length > 1) {
|
|
97
|
+
returnValues.push(handler(value));
|
|
98
|
+
} else {
|
|
99
|
+
returnValues.push(handler());
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return Promise.all(returnValues);
|
|
34
103
|
}
|
|
35
104
|
|
|
36
105
|
#handlers = new Map;
|
|
37
|
-
#reverseHandlers = new Map;
|
|
38
106
|
defineHandler(attr, ...handlers) {
|
|
39
|
-
let registry = this.#handlers;
|
|
40
|
-
if (handlers[0] === false) {
|
|
41
|
-
registry = this.#reverseHandlers;
|
|
42
|
-
handlers.shift();
|
|
43
|
-
}
|
|
44
107
|
const $handlers = [];
|
|
45
108
|
for (let handler of handlers) {
|
|
46
109
|
if (typeof handler === 'function') {
|
|
@@ -52,21 +115,15 @@ export class WebfloStorage extends Map {
|
|
|
52
115
|
}
|
|
53
116
|
$handlers.push(handler);
|
|
54
117
|
}
|
|
55
|
-
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
defineReverseHandler(attr, ...handlers) {
|
|
59
|
-
return this.defineHandler(attr, false, ...handlers);
|
|
118
|
+
this.#handlers.set(attr, $handlers);
|
|
60
119
|
}
|
|
61
120
|
|
|
62
121
|
getHandlers() { return this.#handlers; }
|
|
63
122
|
|
|
64
|
-
getReverseHandlers() { return this.#reverseHandlers; }
|
|
65
|
-
|
|
66
123
|
async require(attrs, callback = null, noNulls = false) {
|
|
67
124
|
const entries = [];
|
|
68
125
|
main: for await (const attr of [].concat(attrs)) {
|
|
69
|
-
if (!this.has(attr) || (noNulls && [undefined, null].includes(this.get(attr)))) {
|
|
126
|
+
if (!(await this.has(attr)) || (noNulls && [undefined, null].includes(await this.get(attr)))) {
|
|
70
127
|
const handlers = this.#handlers.get(attr);
|
|
71
128
|
if (!handlers) {
|
|
72
129
|
throw new Error(`No handler defined for the user attribute: ${attr}`);
|
|
@@ -94,14 +151,14 @@ export class WebfloStorage extends Map {
|
|
|
94
151
|
}
|
|
95
152
|
const messageID = (0 | Math.random() * 9e6).toString(36);
|
|
96
153
|
urlRewrite.searchParams.set('redirect-message', messageID);
|
|
97
|
-
this.#session.set(`redirect-message:${messageID}`, { status: { type: handler.type || 'info', message: handler.message }});
|
|
154
|
+
await this.#session.set(`redirect-message:${messageID}`, { status: { type: handler.type || 'info', message: handler.message }});
|
|
98
155
|
}
|
|
99
156
|
return new Response(null, { status: 302, headers: {
|
|
100
157
|
Location: urlRewrite
|
|
101
158
|
}});
|
|
102
159
|
}
|
|
103
160
|
}
|
|
104
|
-
entries.push(this.get(attr));
|
|
161
|
+
entries.push(await this.get(attr));
|
|
105
162
|
}
|
|
106
163
|
if (callback) return await callback(...entries);
|
|
107
164
|
return entries;
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
const { Observer } = webqit;
|
|
2
|
+
|
|
3
|
+
export class Capabilities {
|
|
4
|
+
|
|
5
|
+
#params;
|
|
6
|
+
|
|
7
|
+
#exposed = {};
|
|
8
|
+
get exposed() { return this.#exposed; }
|
|
9
|
+
|
|
10
|
+
#cleanups = [];
|
|
11
|
+
|
|
12
|
+
static async initialize(params) {
|
|
13
|
+
const instance = new this;
|
|
14
|
+
instance.#params = params;
|
|
15
|
+
instance.#params.generic_public_webhook_url = instance.#params.generic_public_webhook_url_variable && instance.#params.env[instance.#params.generic_public_webhook_url_variable];
|
|
16
|
+
instance.#params.vapid_public_key = instance.#params.vapid_public_key_variable && instance.#params.env[instance.#params.vapid_public_key_variable];
|
|
17
|
+
// --------
|
|
18
|
+
// Custom install
|
|
19
|
+
const onbeforeinstallprompt = (e) => {
|
|
20
|
+
if (instance.#params.custom_install && instance.#exposed.custom_install !== 'granted') {
|
|
21
|
+
e.preventDefault();
|
|
22
|
+
Observer.set(instance.#exposed, 'custom_install', e);
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
window.addEventListener('beforeinstallprompt', onbeforeinstallprompt);
|
|
26
|
+
instance.#cleanups.push(() => window.removeEventListener('beforeinstallprompt', onbeforeinstallprompt));
|
|
27
|
+
// --------
|
|
28
|
+
// Webhooks
|
|
29
|
+
if (instance.#params.generic_public_webhook_url) {
|
|
30
|
+
// --------
|
|
31
|
+
// app.installed
|
|
32
|
+
const onappinstalled = () => {
|
|
33
|
+
fetch(instance.#params.generic_public_webhook_url, {
|
|
34
|
+
method: 'POST',
|
|
35
|
+
headers: { 'Content-Type': 'application/json' },
|
|
36
|
+
body: JSON.stringify({ type: 'app.installed', data: true })
|
|
37
|
+
}).catch(() => {});
|
|
38
|
+
};
|
|
39
|
+
window.addEventListener('appinstalled', onappinstalled);
|
|
40
|
+
instance.#cleanups.push(() => window.removeEventListener('appinstalled', onappinstalled));
|
|
41
|
+
// --------
|
|
42
|
+
// push.subscribe/unsubscribe
|
|
43
|
+
if (instance.#params.webpush) {
|
|
44
|
+
try {
|
|
45
|
+
const pushPermissionStatus = await navigator.permissions.query({ name: 'push', userVisibleOnly: true });
|
|
46
|
+
const pushPermissionStatusHandler = async () => {
|
|
47
|
+
const pushManager = (await navigator.serviceWorker.getRegistration()).pushManager;
|
|
48
|
+
const eventPayload = pushPermissionStatus.state === 'granted'
|
|
49
|
+
? { type: 'push.subscribe', data: await pushManager.getSubscription() }
|
|
50
|
+
: { type: 'push.unsubscribe' };
|
|
51
|
+
if (eventPayload.type === 'push.subscribe' && !eventPayload.data) {
|
|
52
|
+
return window.queueMicrotask(pushPermissionStatusHandler);
|
|
53
|
+
}
|
|
54
|
+
fetch(instance.#params.generic_public_webhook_url, {
|
|
55
|
+
method: 'POST',
|
|
56
|
+
headers: { 'Content-Type': 'application/json' },
|
|
57
|
+
body: JSON.stringify(eventPayload)
|
|
58
|
+
}).catch(() => {});
|
|
59
|
+
}
|
|
60
|
+
pushPermissionStatus.addEventListener('change', pushPermissionStatusHandler);
|
|
61
|
+
instance.#cleanups.push(() => pushPermissionStatus.removeEventListener('change', pushPermissionStatusHandler));
|
|
62
|
+
} catch(e) {}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
// --------
|
|
66
|
+
// Exposure
|
|
67
|
+
if (Array.isArray(instance.#params.exposed) && instance.#params.exposed.length) {
|
|
68
|
+
const [permissions, cleanup] = await instance.query(instance.#params.exposed.map((s) => s.trim()), true);
|
|
69
|
+
instance.#exposed = permissions;
|
|
70
|
+
instance.#cleanups.push(cleanup);
|
|
71
|
+
}
|
|
72
|
+
return instance;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async query(query, live = false) {
|
|
76
|
+
const permissions = {}, cleanups = [];
|
|
77
|
+
for (let q of [].concat(query)) {
|
|
78
|
+
q = this.resolveQuery(q);
|
|
79
|
+
// ------
|
|
80
|
+
// Display mode
|
|
81
|
+
if (q.name === 'display-mode') {
|
|
82
|
+
const handleDisplayMode = () => {
|
|
83
|
+
if (document.referrer.startsWith('android-app://')) {
|
|
84
|
+
Observer.set(permissions, 'display_mode', 'twa');
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
for (const dm of ['browser', 'standalone', 'minimal-ui', 'fullscreen', 'window-controls-overlay']) {
|
|
88
|
+
const mediaQuery = window.matchMedia(`(display-mode: ${dm})`);
|
|
89
|
+
if (mediaQuery.matches) {
|
|
90
|
+
Observer.set(permissions, 'display_mode', dm);
|
|
91
|
+
if (live) {
|
|
92
|
+
mediaQuery.addEventListener('change', handleDisplayMode, { once: true });
|
|
93
|
+
cleanups.push(() => mediaQuery.removeEventListener('change', handleDisplayMode));
|
|
94
|
+
}
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
handleDisplayMode();
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
// ------
|
|
103
|
+
// Others
|
|
104
|
+
try {
|
|
105
|
+
const permissionStatus = await navigator.permissions.query(q);
|
|
106
|
+
permissions[permissionStatus.name.replace(/-/g, '_')] = permissionStatus.state;
|
|
107
|
+
if (live) {
|
|
108
|
+
const onchange = () => {
|
|
109
|
+
Observer.set(permissions, permissionStatus.name.replace(/-/g, '_'), permissionStatus.state);
|
|
110
|
+
};
|
|
111
|
+
permissionStatus.addEventListener('change', onchange);
|
|
112
|
+
cleanups.push(() => permissionStatus.removeEventListener('change', onchange));
|
|
113
|
+
}
|
|
114
|
+
} catch(e) {
|
|
115
|
+
permissions[q.name.replace(/-/g, '_')] = 'unsupported';
|
|
116
|
+
console.log(e);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
if (live) {
|
|
120
|
+
return [
|
|
121
|
+
permissions,
|
|
122
|
+
() => cleanups.forEach((c) => c())
|
|
123
|
+
];
|
|
124
|
+
}
|
|
125
|
+
return permissions;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
async request(name, params = {}) {
|
|
129
|
+
params = this.resolveRequest(name, params);
|
|
130
|
+
// ------
|
|
131
|
+
// install
|
|
132
|
+
if (name === 'install') {
|
|
133
|
+
let returnValue;
|
|
134
|
+
if (this.#exposed.custom_install === 'granted') return;
|
|
135
|
+
if (this.#exposed.custom_install) {
|
|
136
|
+
returnValue = await this.#exposed.custom_install.prompt?.();
|
|
137
|
+
}
|
|
138
|
+
Observer.set(this.#exposed, 'custom_install', 'granted');
|
|
139
|
+
return returnValue;
|
|
140
|
+
}
|
|
141
|
+
// ------
|
|
142
|
+
// notification
|
|
143
|
+
if (name === 'notification') {
|
|
144
|
+
return await new Promise(async (resolve, reject) => {
|
|
145
|
+
const permissionResult = Notification.requestPermission(resolve);
|
|
146
|
+
if (permissionResult) {
|
|
147
|
+
permissionResult.then(resolve, reject);
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
// ------
|
|
152
|
+
// push
|
|
153
|
+
if (name === 'push') {
|
|
154
|
+
const pushManager = (await navigator.serviceWorker.getRegistration()).pushManager;
|
|
155
|
+
const subscription = (await pushManager.getSubscription()) || await pushManager.subscribe(params);
|
|
156
|
+
return subscription;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
async supports(q) {
|
|
161
|
+
try {
|
|
162
|
+
await navigator.permissions.query(this.resolveQuery(q));
|
|
163
|
+
return true;
|
|
164
|
+
} catch(e) {
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
resolveQuery(q) {
|
|
170
|
+
if (typeof q === 'string') {
|
|
171
|
+
q = { name: q };
|
|
172
|
+
}
|
|
173
|
+
if (q.name === 'push' && !q.userVisibleOnly) {
|
|
174
|
+
q = { ...q, userVisibleOnly: true };
|
|
175
|
+
}
|
|
176
|
+
if (q.name === 'top-level-storage-access' && !q.requestedOrigin) {
|
|
177
|
+
q = { ...q, requestedOrigin: window.location.origin };
|
|
178
|
+
}
|
|
179
|
+
return q;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
resolveRequest(name, params = {}) {
|
|
183
|
+
if (name === 'push') {
|
|
184
|
+
if (!params.userVisibleOnly) {
|
|
185
|
+
params = { ...params, userVisibleOnly: true };
|
|
186
|
+
}
|
|
187
|
+
if (!params.applicationServerKey && this.#params.vapid_public_key) {
|
|
188
|
+
params = { ...params, applicationServerKey: urlBase64ToUint8Array(this.#params.vapid_public_key) };
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
return params;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
close() {
|
|
195
|
+
this.#cleanups.forEach((c) => c());
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Public base64 to Uint
|
|
200
|
+
function urlBase64ToUint8Array(base64String) {
|
|
201
|
+
const padding = '='.repeat((4 - base64String.length % 4) % 4);
|
|
202
|
+
const base64 = (base64String + padding)
|
|
203
|
+
.replace(/\-/g, '+')
|
|
204
|
+
.replace(/_/g, '/');
|
|
205
|
+
const rawData = window.atob(base64);
|
|
206
|
+
const outputArray = new Uint8Array(rawData.length);
|
|
207
|
+
for (let i = 0; i < rawData.length; ++i) {
|
|
208
|
+
outputArray[i] = rawData.charCodeAt(i);
|
|
209
|
+
}
|
|
210
|
+
return outputArray;
|
|
211
|
+
}
|
|
@@ -8,10 +8,10 @@ export class CookieStorage extends WebfloCookieStorage {
|
|
|
8
8
|
);
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
commit(response) {
|
|
12
|
-
for (const cookieStr of this.render()) {
|
|
11
|
+
async commit(response) {
|
|
12
|
+
for (const cookieStr of await this.render()) {
|
|
13
13
|
document.cookie = cookieStr;
|
|
14
14
|
}
|
|
15
|
-
super.commit();
|
|
15
|
+
await super.commit();
|
|
16
16
|
}
|
|
17
17
|
}
|
|
@@ -1,33 +1,16 @@
|
|
|
1
1
|
import { WebfloStorage } from '../WebfloStorage.js';
|
|
2
2
|
|
|
3
3
|
export class SessionStorage extends WebfloStorage {
|
|
4
|
-
static get type() { return 'session'; }
|
|
5
|
-
|
|
6
4
|
static create(request) {
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
5
|
+
const registry = {
|
|
6
|
+
async get(key) { return localStorage.getItem(key) },
|
|
7
|
+
async set(key, value) { return localStorage.setItem(key, value) },
|
|
8
|
+
};
|
|
9
|
+
return new this(
|
|
10
|
+
registry,
|
|
11
|
+
'session',
|
|
13
12
|
request,
|
|
14
|
-
|
|
13
|
+
true
|
|
15
14
|
);
|
|
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
15
|
}
|
|
33
16
|
}
|
|
@@ -32,9 +32,6 @@ export class WebfloClient extends WebfloRuntime {
|
|
|
32
32
|
#host;
|
|
33
33
|
get host() { return this.#host; }
|
|
34
34
|
|
|
35
|
-
#network;
|
|
36
|
-
get network() { return this.#network; }
|
|
37
|
-
|
|
38
35
|
#location;
|
|
39
36
|
get location() { return this.#location; }
|
|
40
37
|
|
|
@@ -55,7 +52,6 @@ export class WebfloClient extends WebfloRuntime {
|
|
|
55
52
|
super();
|
|
56
53
|
this.#host = host;
|
|
57
54
|
Object.defineProperty(this.host, 'webfloRuntime', { get: () => this });
|
|
58
|
-
this.#network = { status: window.navigator.onLine };
|
|
59
55
|
this.#location = new Url/*NOT URL*/(this.host.location);
|
|
60
56
|
this.#navigator = {
|
|
61
57
|
requesting: null,
|
|
@@ -72,7 +68,7 @@ export class WebfloClient extends WebfloRuntime {
|
|
|
72
68
|
};
|
|
73
69
|
}
|
|
74
70
|
|
|
75
|
-
initialize() {
|
|
71
|
+
async initialize() {
|
|
76
72
|
this.#backgroundMessaging = new MultiportMessagingAPI(this, { runtime: this });
|
|
77
73
|
// Bind response and redirect handlers
|
|
78
74
|
const responseHandler = (e) => {
|
|
@@ -92,19 +88,13 @@ export class WebfloClient extends WebfloRuntime {
|
|
|
92
88
|
});
|
|
93
89
|
}, 10);
|
|
94
90
|
};
|
|
95
|
-
this.backgroundMessaging.handleMessages('response', responseHandler);
|
|
96
|
-
this.backgroundMessaging.handleMessages('redirect', responseHandler);
|
|
97
|
-
|
|
98
|
-
const onlineHandler = () => Observer.set(this.network, 'status', window.navigator.onLine);
|
|
99
|
-
window.addEventListener('online', onlineHandler);
|
|
100
|
-
window.addEventListener('offline', onlineHandler);
|
|
101
|
-
// Start controlling
|
|
102
|
-
const uncontrols = this.control();
|
|
91
|
+
const responseHandler1Cleanup = this.backgroundMessaging.handleMessages('response', responseHandler);
|
|
92
|
+
const responseHandler2Cleanup = this.backgroundMessaging.handleMessages('redirect', responseHandler);
|
|
93
|
+
const controlCleanup = this.control();
|
|
103
94
|
return () => {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
uncontrols();
|
|
95
|
+
responseHandler1Cleanup();
|
|
96
|
+
responseHandler2Cleanup();
|
|
97
|
+
controlCleanup();
|
|
108
98
|
};
|
|
109
99
|
}
|
|
110
100
|
|
|
@@ -128,7 +118,7 @@ export class WebfloClient extends WebfloRuntime {
|
|
|
128
118
|
// -----------------------
|
|
129
119
|
// Capture all link-clicks
|
|
130
120
|
const clickHandler = (e) => {
|
|
131
|
-
if (!this._canIntercept(e)) return;
|
|
121
|
+
if (!this._canIntercept(e) || e.defaultPrevented) return;
|
|
132
122
|
var anchorEl = e.target.closest('a');
|
|
133
123
|
if (!anchorEl || !anchorEl.href || (anchorEl.target && !anchorEl.target.startsWith('_webflo:')) || anchorEl.download || !this.isSpaRoute(anchorEl)) return;
|
|
134
124
|
const resolvedUrl = new URL(anchorEl.hasAttribute('href') ? anchorEl.getAttribute('href') : '', this.location.href);
|
|
@@ -176,7 +166,7 @@ export class WebfloClient extends WebfloRuntime {
|
|
|
176
166
|
// -----------------------
|
|
177
167
|
// Capture all form-submits
|
|
178
168
|
const submitHandler = (e) => {
|
|
179
|
-
if (!this._canIntercept(e)) return;
|
|
169
|
+
if (!this._canIntercept(e) || e.defaultPrevented) return;
|
|
180
170
|
// ---------------
|
|
181
171
|
// Declare form submission modifyers
|
|
182
172
|
const form = e.target.closest('form');
|
|
@@ -348,8 +338,10 @@ export class WebfloClient extends WebfloRuntime {
|
|
|
348
338
|
cookies: scope.cookies,
|
|
349
339
|
session: scope.session,
|
|
350
340
|
user: scope.user,
|
|
351
|
-
client: scope.clientMessaging
|
|
341
|
+
client: scope.clientMessaging,
|
|
342
|
+
sdk: {}
|
|
352
343
|
});
|
|
344
|
+
await this.setup(scope.httpEvent);
|
|
353
345
|
scope.httpEvent.onRequestClone = () => this.createRequest(scope.url, scope.init);
|
|
354
346
|
// Ste pre-request states
|
|
355
347
|
Observer.set(this.navigator, {
|
|
@@ -385,11 +377,11 @@ export class WebfloClient extends WebfloRuntime {
|
|
|
385
377
|
});
|
|
386
378
|
// ---------------
|
|
387
379
|
// Response processing
|
|
388
|
-
scope.hasBackgroundActivity = scope.clientMessaging.isMessaging() || scope.eventLifecyclePromises.size || (scope.redirectMessage && !(scope.response instanceof Response && scope.response.headers.get('Location')));
|
|
389
380
|
scope.response = await this.normalizeResponse(scope.httpEvent, scope.response);
|
|
381
|
+
scope.hasBackgroundActivity = scope.clientMessaging.isMessaging() || scope.eventLifecyclePromises.size || (scope.redirectMessage && !scope.response.headers.get('Location'));
|
|
390
382
|
if (scope.response.headers.get('Location')) {
|
|
391
383
|
if (scope.redirectMessage) {
|
|
392
|
-
scope.session.set(`redirect-message:${scope.redirectMessageID}`, scope.redirectMessage);
|
|
384
|
+
await scope.session.set(`redirect-message:${scope.redirectMessageID}`, scope.redirectMessage);
|
|
393
385
|
}
|
|
394
386
|
} else {
|
|
395
387
|
if (scope.redirectMessage) {
|
|
@@ -539,8 +531,8 @@ export class WebfloClient extends WebfloRuntime {
|
|
|
539
531
|
navigator: this.navigator,
|
|
540
532
|
location: this.location,
|
|
541
533
|
network: this.network, // request, redirect, error, status, remote
|
|
534
|
+
capabilities: this.capabilities,
|
|
542
535
|
transition: this.transition,
|
|
543
|
-
background: null
|
|
544
536
|
}, { diff: true, merge });
|
|
545
537
|
let overridenKeys;
|
|
546
538
|
if (_isObject(data) && (overridenKeys = ['env', 'navigator', 'location', 'network', 'transition', 'background'].filter((k) => k in data)).length) {
|