@webqit/webflo 1.0.19 → 1.0.20
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 +4 -3
- package/src/config-pi/runtime/Client.js +50 -46
- package/src/config-pi/runtime/Server.js +37 -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 +35 -36
- package/src/runtime-pi/WebfloCookieStorage.js +8 -8
- package/src/runtime-pi/WebfloRouter.js +2 -2
- package/src/runtime-pi/WebfloRuntime.js +7 -2
- package/src/runtime-pi/WebfloStorage.js +47 -16
- package/src/runtime-pi/client/Capabilities.js +211 -0
- package/src/runtime-pi/client/CookieStorage.js +2 -2
- package/src/runtime-pi/client/SessionStorage.js +2 -2
- 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 +2 -2
- package/src/runtime-pi/client/worker/SessionStorage.js +1 -1
- 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 +2 -2
- package/src/runtime-pi/server/SessionStorage.js +3 -3
- package/src/runtime-pi/server/WebfloServer.js +26 -11
- 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
|
@@ -7,9 +7,12 @@ export class WebfloStorage extends Map {
|
|
|
7
7
|
#session;
|
|
8
8
|
|
|
9
9
|
constructor(request, session, iterable = []) {
|
|
10
|
-
super(
|
|
10
|
+
super();
|
|
11
11
|
this.#request = request;
|
|
12
12
|
this.#session = session === true ? this : session;
|
|
13
|
+
for (const [k, v] of iterable) {
|
|
14
|
+
this.set(k, v);
|
|
15
|
+
}
|
|
13
16
|
}
|
|
14
17
|
|
|
15
18
|
#originals;
|
|
@@ -29,18 +32,52 @@ export class WebfloStorage extends Map {
|
|
|
29
32
|
});
|
|
30
33
|
}
|
|
31
34
|
|
|
32
|
-
commit() {
|
|
35
|
+
async commit() {
|
|
33
36
|
this.saveOriginals();
|
|
34
37
|
}
|
|
35
38
|
|
|
39
|
+
#listeners = new Set;
|
|
40
|
+
observe(attr, handler) {
|
|
41
|
+
const args = { attr, handler };
|
|
42
|
+
this.#listeners.add(args);
|
|
43
|
+
return () => {
|
|
44
|
+
this.#listeners.delete(args);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async emit(attr, value) {
|
|
49
|
+
const returnValues = [];
|
|
50
|
+
for (const { attr: $attr, handler } of this.#listeners) {
|
|
51
|
+
if (arguments.length && $attr !== attr) continue;
|
|
52
|
+
if (arguments.length > 1) {
|
|
53
|
+
returnValues.push(handler(value));
|
|
54
|
+
} else {
|
|
55
|
+
returnValues.push(handler());
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return Promise.all(returnValues);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async set(attr, value) {
|
|
62
|
+
const returnValue = super.set(attr, value);
|
|
63
|
+
await this.emit(attr, value);
|
|
64
|
+
return returnValue;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async delete(attr) {
|
|
68
|
+
const returnValue = super.delete(attr);
|
|
69
|
+
await this.emit(attr);
|
|
70
|
+
return returnValue;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async clear() {
|
|
74
|
+
const returnValue = super.clear();
|
|
75
|
+
await this.emit();
|
|
76
|
+
return returnValue;
|
|
77
|
+
}
|
|
78
|
+
|
|
36
79
|
#handlers = new Map;
|
|
37
|
-
#reverseHandlers = new Map;
|
|
38
80
|
defineHandler(attr, ...handlers) {
|
|
39
|
-
let registry = this.#handlers;
|
|
40
|
-
if (handlers[0] === false) {
|
|
41
|
-
registry = this.#reverseHandlers;
|
|
42
|
-
handlers.shift();
|
|
43
|
-
}
|
|
44
81
|
const $handlers = [];
|
|
45
82
|
for (let handler of handlers) {
|
|
46
83
|
if (typeof handler === 'function') {
|
|
@@ -52,17 +89,11 @@ export class WebfloStorage extends Map {
|
|
|
52
89
|
}
|
|
53
90
|
$handlers.push(handler);
|
|
54
91
|
}
|
|
55
|
-
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
defineReverseHandler(attr, ...handlers) {
|
|
59
|
-
return this.defineHandler(attr, false, ...handlers);
|
|
92
|
+
this.#handlers.set(attr, $handlers);
|
|
60
93
|
}
|
|
61
94
|
|
|
62
95
|
getHandlers() { return this.#handlers; }
|
|
63
96
|
|
|
64
|
-
getReverseHandlers() { return this.#reverseHandlers; }
|
|
65
|
-
|
|
66
97
|
async require(attrs, callback = null, noNulls = false) {
|
|
67
98
|
const entries = [];
|
|
68
99
|
main: for await (const attr of [].concat(attrs)) {
|
|
@@ -94,7 +125,7 @@ export class WebfloStorage extends Map {
|
|
|
94
125
|
}
|
|
95
126
|
const messageID = (0 | Math.random() * 9e6).toString(36);
|
|
96
127
|
urlRewrite.searchParams.set('redirect-message', messageID);
|
|
97
|
-
this.#session.set(`redirect-message:${messageID}`, { status: { type: handler.type || 'info', message: handler.message }});
|
|
128
|
+
await this.#session.set(`redirect-message:${messageID}`, { status: { type: handler.type || 'info', message: handler.message }});
|
|
98
129
|
}
|
|
99
130
|
return new Response(null, { status: 302, headers: {
|
|
100
131
|
Location: urlRewrite
|
|
@@ -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.app_public_webhook_url = instance.#params.app_public_webhook_url_variable && instance.#params.env[instance.#params.app_public_webhook_url_variable];
|
|
16
|
+
instance.#params.app_vapid_public_key = instance.#params.app_vapid_public_key_variable && instance.#params.env[instance.#params.app_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.app_public_webhook_url) {
|
|
30
|
+
// --------
|
|
31
|
+
// app.installed
|
|
32
|
+
const onappinstalled = () => {
|
|
33
|
+
fetch(instance.#params.app_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.app_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.app_vapid_public_key) {
|
|
188
|
+
params = { ...params, applicationServerKey: urlBase64ToUint8Array(this.#params.app_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) {
|
|
11
|
+
async commit(response) {
|
|
12
12
|
for (const cookieStr of this.render()) {
|
|
13
13
|
document.cookie = cookieStr;
|
|
14
14
|
}
|
|
15
|
-
super.commit();
|
|
15
|
+
await super.commit();
|
|
16
16
|
}
|
|
17
17
|
}
|
|
@@ -20,7 +20,7 @@ export class SessionStorage extends WebfloStorage {
|
|
|
20
20
|
super(request, true, iterable);
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
commit() {
|
|
23
|
+
async commit() {
|
|
24
24
|
const storeType = this.constructor.type === 'user' ? 'localStorage' : 'sessionStorage';
|
|
25
25
|
for (const key of this.getAdded()) {
|
|
26
26
|
window[storeType].setItem(key, this.get(key));
|
|
@@ -28,6 +28,6 @@ export class SessionStorage extends WebfloStorage {
|
|
|
28
28
|
for (const key of this.getDeleted()) {
|
|
29
29
|
window[storeType].removeItem(key);
|
|
30
30
|
}
|
|
31
|
-
super.commit();
|
|
31
|
+
await super.commit();
|
|
32
32
|
}
|
|
33
33
|
}
|
|
@@ -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) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { WebfloClient } from './WebfloClient.js';
|
|
2
2
|
import { Context } from './Context.js';
|
|
3
3
|
import { Workport } from './Workport.js';
|
|
4
|
+
import { Capabilities } from './Capabilities.js';
|
|
4
5
|
|
|
5
6
|
const { Observer } = webqit;
|
|
6
7
|
|
|
@@ -10,6 +11,8 @@ export class WebfloRootClient1 extends WebfloClient {
|
|
|
10
11
|
|
|
11
12
|
static get Workport() { return Workport; }
|
|
12
13
|
|
|
14
|
+
static get Capabilities() { return Capabilities; }
|
|
15
|
+
|
|
13
16
|
static create(host, cx = {}) {
|
|
14
17
|
return new this(host, this.Context.create(cx));
|
|
15
18
|
}
|
|
@@ -17,6 +20,15 @@ export class WebfloRootClient1 extends WebfloClient {
|
|
|
17
20
|
#cx;
|
|
18
21
|
get cx() { return this.#cx; }
|
|
19
22
|
|
|
23
|
+
#network;
|
|
24
|
+
get network() { return this.#network; }
|
|
25
|
+
|
|
26
|
+
#workport;
|
|
27
|
+
get workport() { return this.#workport; }
|
|
28
|
+
|
|
29
|
+
#capabilities;
|
|
30
|
+
get capabilities() { return this.#capabilities; }
|
|
31
|
+
|
|
20
32
|
constructor(host, cx) {
|
|
21
33
|
if (!(host instanceof Document)) {
|
|
22
34
|
throw new Error('Argument #1 must be a Document instance');
|
|
@@ -26,11 +38,42 @@ export class WebfloRootClient1 extends WebfloClient {
|
|
|
26
38
|
throw new Error('Argument #2 must be a Webflo Context instance');
|
|
27
39
|
}
|
|
28
40
|
this.#cx = cx;
|
|
41
|
+
this.#network = { status: window.navigator.onLine };
|
|
29
42
|
}
|
|
30
43
|
|
|
31
|
-
initialize() {
|
|
32
|
-
//
|
|
33
|
-
|
|
44
|
+
async initialize() {
|
|
45
|
+
// --------
|
|
46
|
+
// INITIALIZATIONS
|
|
47
|
+
const cleanups = [await super.initialize()];
|
|
48
|
+
// --------
|
|
49
|
+
// Service Worker && Capabilities
|
|
50
|
+
if (this.cx.params.capabilities?.service_worker?.filename) {
|
|
51
|
+
const { service_worker: { filename, ...restServiceWorkerParams } = {} } = this.cx.params.capabilities;
|
|
52
|
+
this.#workport = await this.constructor.Workport.initialize(null, (this.cx.params.public_base_url || '') + filename, restServiceWorkerParams);
|
|
53
|
+
cleanups.push(() => this.#workport.close());
|
|
54
|
+
}
|
|
55
|
+
this.#capabilities = await this.constructor.Capabilities.initialize({ ...this.cx.params.capabilities, env: this.cx.params.env });
|
|
56
|
+
cleanups.push(() => this.#capabilities.close());
|
|
57
|
+
// --------
|
|
58
|
+
// Bind network status handlers
|
|
59
|
+
const onlineHandler = () => Observer.set(this.network, 'status', window.navigator.onLine);
|
|
60
|
+
window.addEventListener('online', onlineHandler);
|
|
61
|
+
window.addEventListener('offline', onlineHandler);
|
|
62
|
+
cleanups.push(() => {
|
|
63
|
+
window.removeEventListener('online', onlineHandler);
|
|
64
|
+
window.removeEventListener('offline', onlineHandler);
|
|
65
|
+
});
|
|
66
|
+
// --------
|
|
67
|
+
// Window opener pinging
|
|
68
|
+
let beforeunloadCleanup;
|
|
69
|
+
if (window.opener) {
|
|
70
|
+
const beforeunloadHandler = () => window.opener.postMessage('close');
|
|
71
|
+
window.addEventListener('beforeunload', beforeunloadHandler);
|
|
72
|
+
cleanups.push(() => {
|
|
73
|
+
window.removeEventListener('beforeunload', beforeunloadHandler);
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
// --------
|
|
34
77
|
// Bind global prompt handlers
|
|
35
78
|
const promptsHandler = (e) => {
|
|
36
79
|
e.stopPropagation();
|
|
@@ -49,9 +92,10 @@ export class WebfloRootClient1 extends WebfloClient {
|
|
|
49
92
|
});
|
|
50
93
|
}, 10);
|
|
51
94
|
};
|
|
52
|
-
|
|
53
|
-
this.backgroundMessaging.handleMessages('prompt', promptsHandler);
|
|
54
|
-
//
|
|
95
|
+
cleanups.push(this.backgroundMessaging.handleMessages('confirm', promptsHandler));
|
|
96
|
+
cleanups.push(this.backgroundMessaging.handleMessages('prompt', promptsHandler));
|
|
97
|
+
// --------
|
|
98
|
+
// HYDRATION
|
|
55
99
|
const scope = {};
|
|
56
100
|
if (scope.backgroundMessagingMeta = document.querySelector('meta[name="X-Background-Messaging"]')) {
|
|
57
101
|
scope.backgroundMessaging = this.$createBackgroundMessagingFrom(scope.backgroundMessagingMeta.content);
|
|
@@ -66,32 +110,9 @@ export class WebfloRootClient1 extends WebfloClient {
|
|
|
66
110
|
});
|
|
67
111
|
} catch(e) {}
|
|
68
112
|
}
|
|
69
|
-
//
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
const { vapid_key_env, push_registration_url_env, ..._restServiceWorkerParams } = restServiceWorkerParams;
|
|
73
|
-
const swParams = {
|
|
74
|
-
..._restServiceWorkerParams,
|
|
75
|
-
VAPID_PUBLIC_KEY: this.cx.params.env[vapid_key_env],
|
|
76
|
-
PUSH_REGISTRATION_PUBLIC_URL: this.cx.params.env[push_registration_url_env],
|
|
77
|
-
startMessages: true
|
|
78
|
-
};
|
|
79
|
-
this.workport = new this.constructor.Workport;
|
|
80
|
-
this.workport.registerServiceWorker(base + filename, swParams);
|
|
81
|
-
}
|
|
82
|
-
if (window.opener) {
|
|
83
|
-
// Window opener pinging
|
|
84
|
-
const $undoControl = undoControl;
|
|
85
|
-
const beforeunloadHandler = () => {
|
|
86
|
-
window.opener.postMessage('close');
|
|
87
|
-
};
|
|
88
|
-
window.addEventListener('beforeunload', beforeunloadHandler);
|
|
89
|
-
undoControl = () => {
|
|
90
|
-
window.removeEventListener('beforeunload', beforeunloadHandler);
|
|
91
|
-
$undoControl();
|
|
92
|
-
};
|
|
93
|
-
}
|
|
94
|
-
return undoControl
|
|
113
|
+
// --------
|
|
114
|
+
// CLEAN UP
|
|
115
|
+
return () => cleanups.forEach((c) => c());
|
|
95
116
|
}
|
|
96
117
|
|
|
97
118
|
/**
|
|
@@ -102,7 +123,7 @@ export class WebfloRootClient1 extends WebfloClient {
|
|
|
102
123
|
|
|
103
124
|
control() {
|
|
104
125
|
// IMPORTANT: we're calling super.controlClassic()
|
|
105
|
-
const
|
|
126
|
+
const cleanupSuper = super.controlClassic((newHref) => {
|
|
106
127
|
try {
|
|
107
128
|
// Save current scroll position
|
|
108
129
|
window.history.replaceState({
|
|
@@ -134,7 +155,7 @@ export class WebfloRootClient1 extends WebfloClient {
|
|
|
134
155
|
window.addEventListener('popstate', popstateHandler);
|
|
135
156
|
return () => {
|
|
136
157
|
this.host.removeEventListener('popstate', popstateHandler);
|
|
137
|
-
|
|
158
|
+
cleanupSuper();
|
|
138
159
|
};
|
|
139
160
|
}
|
|
140
161
|
|
|
@@ -9,14 +9,14 @@ export class WebfloRootClient2 extends WebfloRootClient1 {
|
|
|
9
9
|
let navigationOrigins = [];
|
|
10
10
|
// Capture all link-clicks
|
|
11
11
|
const clickHandler = (e) => {
|
|
12
|
-
if (!this._canIntercept(e)) return;
|
|
12
|
+
if (!this._canIntercept(e) || e.defaultPrevented) return;
|
|
13
13
|
let anchorEl = e.target.closest('a');
|
|
14
14
|
if (!anchorEl || !anchorEl.href || anchorEl.target) return;
|
|
15
15
|
navigationOrigins = [anchorEl, null, anchorEl.closest('[navigationcontext]')];
|
|
16
16
|
};
|
|
17
17
|
// Capture all form-submits
|
|
18
18
|
const submitHandler = (e) => {
|
|
19
|
-
if (!this._canIntercept(e)) return;
|
|
19
|
+
if (!this._canIntercept(e) || e.defaultPrevented) return;
|
|
20
20
|
navigationOrigins = [e.submitter, e.target.closest('form'), e.target.closest('[navigationcontext]')];
|
|
21
21
|
};
|
|
22
22
|
// Handle navigation event which happens after the above
|
|
@@ -50,9 +50,9 @@ export class WebfloSubClient extends WebfloClient {
|
|
|
50
50
|
this.location = newValue;
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
connectedCallback() {
|
|
53
|
+
async connectedCallback() {
|
|
54
54
|
this.#superRuntime = (this.parentNode?.closest(embedTagNames) || document).webfloRuntime;
|
|
55
|
-
this.#webfloControllerUninitialize = WebfloSubClient.create(this, this.#superRuntime).initialize();
|
|
55
|
+
this.#webfloControllerUninitialize = await WebfloSubClient.create(this, this.#superRuntime).initialize();
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
disconnectedCallback() {
|
|
@@ -70,8 +70,12 @@ export class WebfloSubClient extends WebfloClient {
|
|
|
70
70
|
|
|
71
71
|
get cx() { return this.#superRuntime.cx; }
|
|
72
72
|
|
|
73
|
+
get network() { return this.#superRuntime.network; }
|
|
74
|
+
|
|
73
75
|
get workport() { return this.#superRuntime.workport; }
|
|
74
76
|
|
|
77
|
+
get capabilities() { return this.#superRuntime.capabilities; }
|
|
78
|
+
|
|
75
79
|
get withViewTransitions() { return this.host.hasAttribute('viewtransitions'); }
|
|
76
80
|
|
|
77
81
|
constructor(host, superRuntime) {
|
|
@@ -85,11 +89,11 @@ export class WebfloSubClient extends WebfloClient {
|
|
|
85
89
|
this.#superRuntime = superRuntime;
|
|
86
90
|
}
|
|
87
91
|
|
|
88
|
-
initialize() {
|
|
92
|
+
async initialize() {
|
|
89
93
|
if (this.host.location.origin !== window.location.origin) {
|
|
90
94
|
throw new Error(`Webflo embeddable origin violation in "${window.location}"`);
|
|
91
95
|
}
|
|
92
|
-
const
|
|
96
|
+
const cleanupSuper = await super.initialize();
|
|
93
97
|
this.backgroundMessaging.setParent(this.#superRuntime.backgroundMessaging);
|
|
94
98
|
if (this.host.getAttribute('location')) {
|
|
95
99
|
this.navigate(this.location.href);
|
|
@@ -98,7 +102,7 @@ export class WebfloSubClient extends WebfloClient {
|
|
|
98
102
|
if (this.backgroundMessaging.parentNode === this.#superRuntime.backgroundMessaging) {
|
|
99
103
|
this.backgroundMessaging.setParent(null);
|
|
100
104
|
}
|
|
101
|
-
|
|
105
|
+
cleanupSuper();
|
|
102
106
|
};
|
|
103
107
|
}
|
|
104
108
|
|