@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
|
@@ -25,13 +25,13 @@ export class WebfloWorker extends WebfloRuntime {
|
|
|
25
25
|
|
|
26
26
|
static get SessionStorage() { return SessionStorage; }
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
static get HttpUser() { return HttpUser; }
|
|
29
29
|
|
|
30
30
|
static get Workport() { return Workport; }
|
|
31
31
|
|
|
32
32
|
static create(cx) {
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
return new this(this.Context.create(cx));
|
|
34
|
+
}
|
|
35
35
|
|
|
36
36
|
#cx;
|
|
37
37
|
get cx() { return this.#cx; }
|
|
@@ -44,7 +44,7 @@ export class WebfloWorker extends WebfloRuntime {
|
|
|
44
44
|
this.#cx = cx;
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
initialize() {
|
|
47
|
+
async initialize() {
|
|
48
48
|
// ONINSTALL
|
|
49
49
|
const installHandler = (event) => {
|
|
50
50
|
if (this.cx.params.skip_waiting) self.skipWaiting();
|
|
@@ -53,7 +53,7 @@ export class WebfloWorker extends WebfloRuntime {
|
|
|
53
53
|
// Add files to cache
|
|
54
54
|
event.waitUntil(self.caches.open(this.cx.params.cache_name).then(async cache => {
|
|
55
55
|
if (this.cx.logger) { this.cx.logger.log('[ServiceWorker] Pre-caching resources.'); }
|
|
56
|
-
for (const urls of [
|
|
56
|
+
for (const urls of ['cache_first_urls', 'cache_only_urls']) {
|
|
57
57
|
const _urls = (this.cx.params[urls] || []).map(c => c.trim()).filter(c => c && !pattern(c, self.origin).isPattern());
|
|
58
58
|
await cache.addAll(_urls);
|
|
59
59
|
}
|
|
@@ -74,7 +74,7 @@ export class WebfloWorker extends WebfloRuntime {
|
|
|
74
74
|
return self.caches.delete(key);
|
|
75
75
|
}
|
|
76
76
|
}));
|
|
77
|
-
})
|
|
77
|
+
})
|
|
78
78
|
}
|
|
79
79
|
resolve();
|
|
80
80
|
}));
|
|
@@ -82,11 +82,11 @@ export class WebfloWorker extends WebfloRuntime {
|
|
|
82
82
|
self.addEventListener('install', installHandler);
|
|
83
83
|
self.addEventListener('activate', activateHandler);
|
|
84
84
|
const uncontrols = this.control();
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
85
|
+
return () => {
|
|
86
|
+
self.removeEventListener('install', installHandler);
|
|
87
|
+
self.removeEventListener('activate', activateHandler);
|
|
88
88
|
uncontrols();
|
|
89
|
-
|
|
89
|
+
};
|
|
90
90
|
}
|
|
91
91
|
|
|
92
92
|
control() {
|
|
@@ -112,42 +112,54 @@ export class WebfloWorker extends WebfloRuntime {
|
|
|
112
112
|
event.respondWith(this.navigate(event.request.url, event.request, { event }));
|
|
113
113
|
}
|
|
114
114
|
};
|
|
115
|
+
const webpushHandler = (event) => {
|
|
116
|
+
if (!(self.Notification && self.Notification.permission === 'granted')) return;
|
|
117
|
+
let data;
|
|
118
|
+
try {
|
|
119
|
+
data = event.data?.json() ?? {};
|
|
120
|
+
} catch(e) { return; }
|
|
121
|
+
const { type, title, ...params } = data;
|
|
122
|
+
if (type !== 'notification') return;
|
|
123
|
+
self.registration.showNotification(title, params);
|
|
124
|
+
};
|
|
115
125
|
self.addEventListener('fetch', fetchHandler);
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
126
|
+
self.addEventListener('push', webpushHandler);
|
|
127
|
+
return () => {
|
|
128
|
+
self.removeEventListener('fetch', fetchHandler);
|
|
129
|
+
self.removeEventListener('push', webpushHandler);
|
|
130
|
+
};
|
|
119
131
|
}
|
|
120
132
|
|
|
121
|
-
|
|
133
|
+
createRequest(href, init = {}) {
|
|
122
134
|
if (init instanceof Request && init.url === (href.href || href)) {
|
|
123
135
|
return init;
|
|
124
136
|
}
|
|
125
137
|
return new Request(href, init);
|
|
126
|
-
|
|
138
|
+
}
|
|
127
139
|
|
|
128
140
|
async navigate(url, init = {}, detail = {}) {
|
|
129
141
|
// Resolve inputs
|
|
130
|
-
|
|
142
|
+
const scope = { url, init, detail };
|
|
131
143
|
if (typeof scope.url === 'string') {
|
|
132
144
|
scope.url = new URL(scope.url, self.location.origin);
|
|
133
145
|
}
|
|
134
146
|
// ---------------
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
147
|
+
// Event lifecycle
|
|
148
|
+
scope.eventLifecyclePromises = new Set;
|
|
149
|
+
scope.eventLifecycleHooks = {
|
|
150
|
+
waitUntil: (promise) => {
|
|
151
|
+
promise = Promise.resolve(promise);
|
|
152
|
+
scope.eventLifecyclePromises.add(promise);
|
|
153
|
+
scope.eventLifecyclePromises.dirty = true;
|
|
154
|
+
promise.then(() => scope.eventLifecyclePromises.delete(promise));
|
|
155
|
+
},
|
|
156
|
+
respondWith: async (response) => {
|
|
157
|
+
if (scope.eventLifecyclePromises.dirty && !scope.eventLifecyclePromises.size) {
|
|
158
|
+
throw new Error('Final response already sent');
|
|
159
|
+
}
|
|
160
|
+
return await this.execPush(scope.clientMessaging, response);
|
|
161
|
+
},
|
|
162
|
+
};
|
|
151
163
|
// Create and route request
|
|
152
164
|
scope.request = this.createRequest(scope.url, scope.init);
|
|
153
165
|
scope.cookies = this.constructor.CookieStorage.create(scope.request);
|
|
@@ -165,13 +177,15 @@ export class WebfloWorker extends WebfloRuntime {
|
|
|
165
177
|
cookies: scope.cookies,
|
|
166
178
|
session: scope.session,
|
|
167
179
|
user: scope.user,
|
|
168
|
-
client: scope.clientMessaging
|
|
180
|
+
client: scope.clientMessaging,
|
|
181
|
+
sdk: {}
|
|
169
182
|
});
|
|
183
|
+
await this.setup(scope.httpEvent);
|
|
170
184
|
// Restore session before dispatching
|
|
171
|
-
if (scope.request.method === 'GET'
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
scope.session.delete(`redirect-message:${scope.redirectMessageID}`);
|
|
185
|
+
if (scope.request.method === 'GET'
|
|
186
|
+
&& (scope.redirectMessageID = scope.httpEvent.url.query['redirect-message'])
|
|
187
|
+
&& (scope.redirectMessage = await scope.session.get(`redirect-message:${scope.redirectMessageID}`))) {
|
|
188
|
+
await scope.session.delete(`redirect-message:${scope.redirectMessageID}`);
|
|
175
189
|
}
|
|
176
190
|
// Dispatch for response
|
|
177
191
|
scope.response = await this.dispatch(scope.httpEvent, {}, async (event) => {
|
|
@@ -182,30 +196,30 @@ export class WebfloWorker extends WebfloRuntime {
|
|
|
182
196
|
return await this.remoteFetch(event.request);
|
|
183
197
|
});
|
|
184
198
|
// ---------------
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
199
|
+
// Response processing
|
|
200
|
+
scope.response = await this.normalizeResponse(scope.httpEvent, scope.response);
|
|
201
|
+
scope.hasBackgroundActivity = scope.clientMessaging.isMessaging() || scope.eventLifecyclePromises.size || (scope.redirectMessage && !scope.response.headers.get('Location'));
|
|
188
202
|
if (scope.hasBackgroundActivity) {
|
|
189
203
|
scope.response.headers.set('X-Background-Messaging', `ch:${scope.clientMessaging.port.name}`);
|
|
190
|
-
|
|
204
|
+
}
|
|
191
205
|
if (scope.response.headers.get('Location')) {
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
206
|
+
if (scope.redirectMessage) {
|
|
207
|
+
scope.session.set(`redirect-message:${scope.redirectMessageID}`, scope.redirectMessage);
|
|
208
|
+
}
|
|
195
209
|
} else {
|
|
196
210
|
if (scope.redirectMessage) {
|
|
197
|
-
|
|
198
|
-
|
|
211
|
+
scope.eventLifecycleHooks.respondWith(scope.redirectMessage);
|
|
212
|
+
}
|
|
199
213
|
}
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
214
|
+
Promise.all([...scope.eventLifecyclePromises]).then(() => {
|
|
215
|
+
if (scope.clientMessaging.isMessaging()) {
|
|
216
|
+
scope.clientMessaging.on('connected', () => {
|
|
217
|
+
setTimeout(() => {
|
|
218
|
+
scope.clientMessaging.close();
|
|
219
|
+
}, 100);
|
|
220
|
+
});
|
|
221
|
+
} else scope.clientMessaging.close();
|
|
222
|
+
});
|
|
209
223
|
return scope.response;
|
|
210
224
|
}
|
|
211
225
|
|
|
@@ -287,7 +301,7 @@ export class WebfloWorker extends WebfloRuntime {
|
|
|
287
301
|
|
|
288
302
|
async getRequestCache(request) {
|
|
289
303
|
const cacheName = request.headers.get('Accept') === 'application/json'
|
|
290
|
-
? this.cx.params.cache_name + '_json'
|
|
304
|
+
? this.cx.params.cache_name + '_json'
|
|
291
305
|
: this.cx.params.cache_name;
|
|
292
306
|
return self.caches.open(cacheName);
|
|
293
307
|
}
|
|
@@ -8,10 +8,12 @@ export class CookieStorage extends WebfloCookieStorage {
|
|
|
8
8
|
);
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
commit(response) {
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
async commit(response = null) {
|
|
12
|
+
if (response) {
|
|
13
|
+
for (const cookieStr of await this.render()) {
|
|
14
|
+
response.headers.append('Set-Cookie', cookieStr);
|
|
15
|
+
}
|
|
14
16
|
}
|
|
15
|
-
super.commit();
|
|
17
|
+
await super.commit();
|
|
16
18
|
}
|
|
17
19
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { WebfloStorage } from '../WebfloStorage.js';
|
|
2
2
|
import crypto from 'crypto';
|
|
3
3
|
|
|
4
|
-
const
|
|
4
|
+
const inmemSessionRegistry = new Map;
|
|
5
5
|
export class SessionStorage extends WebfloStorage {
|
|
6
6
|
|
|
7
7
|
static create(request, params = {}) {
|
|
@@ -26,28 +26,26 @@ export class SessionStorage extends WebfloStorage {
|
|
|
26
26
|
sessionID = crypto.randomUUID();
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
|
-
|
|
30
|
-
return sessionStorage.get(sessionID);
|
|
31
|
-
}
|
|
32
|
-
const instance = new this(request, sessionID);
|
|
33
|
-
sessionStorage.set(sessionID, instance);
|
|
34
|
-
return instance;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
constructor(request, sessionID) {
|
|
38
|
-
super(request, true);
|
|
39
|
-
this.#sessionID = sessionID;
|
|
29
|
+
return new this(params.registry || inmemSessionRegistry, sessionID, request);
|
|
40
30
|
}
|
|
41
31
|
|
|
42
32
|
#sessionID;
|
|
43
|
-
get sessionID() {
|
|
44
|
-
|
|
33
|
+
get sessionID() { return this.#sessionID; }
|
|
34
|
+
|
|
35
|
+
constructor(reqistry, sessionID, request) {
|
|
36
|
+
super(
|
|
37
|
+
reqistry,
|
|
38
|
+
`session:${sessionID}`,
|
|
39
|
+
request,
|
|
40
|
+
true
|
|
41
|
+
);
|
|
42
|
+
this.#sessionID = sessionID;
|
|
45
43
|
}
|
|
46
44
|
|
|
47
|
-
commit(response
|
|
48
|
-
if (response.headers.get('Set-Cookie', true).find((c) => c.name === '__sessid'))
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
super.commit();
|
|
45
|
+
async commit(response = null) {
|
|
46
|
+
if (response && !response.headers.get('Set-Cookie', true).find((c) => c.name === '__sessid')) {
|
|
47
|
+
response.headers.append('Set-Cookie', `__sessid=${this.#sessionID}; Path=/; Secure; HttpOnly; SameSite=Lax; Max-Age=31536000`);
|
|
48
|
+
}
|
|
49
|
+
await super.commit();
|
|
52
50
|
}
|
|
53
51
|
}
|
|
@@ -54,6 +54,9 @@ export class WebfloServer extends WebfloRuntime {
|
|
|
54
54
|
// Typically for access by Router
|
|
55
55
|
get cx() { return this.#cx; }
|
|
56
56
|
|
|
57
|
+
#sdk = {};
|
|
58
|
+
get sdk() { return this.#sdk; }
|
|
59
|
+
|
|
57
60
|
constructor(cx) {
|
|
58
61
|
super();
|
|
59
62
|
if (!(cx instanceof this.constructor.Context)) {
|
|
@@ -99,6 +102,46 @@ export class WebfloServer extends WebfloRuntime {
|
|
|
99
102
|
}));
|
|
100
103
|
}
|
|
101
104
|
// ---------------
|
|
105
|
+
if (this.#cx.server.capabilities?.database) {
|
|
106
|
+
if (this.#cx.server.capabilities.database_dialect !== 'postgres') {
|
|
107
|
+
throw new Error(`Only postgres supported for now for database dialect`);
|
|
108
|
+
}
|
|
109
|
+
if (this.#cx.env.entries[this.#cx.server.capabilities.database_url_variable]) {
|
|
110
|
+
const { SQLClient } = await import('@linked-db/linked-ql/sql');
|
|
111
|
+
const { default: pg } = await import('pg');
|
|
112
|
+
// Obtain pg client
|
|
113
|
+
const pgClient = new pg.Pool({
|
|
114
|
+
connectionString: this.#cx.env.entries[this.#cx.server.capabilities.database_url_variable],
|
|
115
|
+
database: 'postgres',
|
|
116
|
+
});
|
|
117
|
+
// Connect
|
|
118
|
+
await pgClient.connect();
|
|
119
|
+
this.#sdk.db = new SQLClient(pgClient, { dialect: 'postgres' });
|
|
120
|
+
} else {
|
|
121
|
+
//const { ODBClient } = await import('@linked-db/linked-ql/odb');
|
|
122
|
+
//this.#sdk.db = new ODBClient({ dialect: 'postgres' });
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
if (this.#cx.server.capabilities?.redis && this.#cx.env.entries[this.#cx.server.capabilities.redis_url_variable]) {
|
|
126
|
+
const { Redis } = await import('ioredis');
|
|
127
|
+
this.#sdk.redis = !this.#cx.env.entries[this.#cx.server.capabilities.redis_url_variable]
|
|
128
|
+
? new Redis : new Redis(this.#cx.env.entries[this.#cx.server.capabilities.redis_url_variable], {
|
|
129
|
+
tls: { rejectUnauthorized: false }, // Required for Upstash
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
if (this.#cx.server.capabilities?.webpush) {
|
|
133
|
+
const { default: webpush } = await import('web-push');
|
|
134
|
+
this.#sdk.webpush = webpush;
|
|
135
|
+
if (this.#cx.env.entries[this.#cx.server.capabilities.vapid_public_key_variable]
|
|
136
|
+
&& this.#cx.env.entries[this.#cx.server.capabilities.vapid_private_key_variable]) {
|
|
137
|
+
webpush.setVapidDetails(
|
|
138
|
+
this.#cx.server.capabilities.vapid_subject,
|
|
139
|
+
this.#cx.env.entries[this.#cx.server.capabilities.vapid_public_key_variable],
|
|
140
|
+
this.#cx.env.entries[this.#cx.server.capabilities.vapid_private_key_variable]
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
// ---------------
|
|
102
145
|
this.control();
|
|
103
146
|
if (this.#cx.logger) {
|
|
104
147
|
if (this.#servers.size) {
|
|
@@ -196,7 +239,7 @@ export class WebfloServer extends WebfloRuntime {
|
|
|
196
239
|
if (!scope.error) {
|
|
197
240
|
if (!hosts.includes(scope.url.hostname) && !hosts.includes('*')) {
|
|
198
241
|
scope.error = 'Unrecognized host';
|
|
199
|
-
} else if (scope.url.protocol === 'ws:' && this.#cx.server.https.force) {
|
|
242
|
+
} else if (scope.url.protocol === 'ws:' && this.#cx.server.https.port && this.#cx.server.https.force) {
|
|
200
243
|
scope.error = `Only secure connections allowed (wss:)`;
|
|
201
244
|
} else if (scope.url.hostname.startsWith('www.') && this.#cx.server.force_www === 'remove') {
|
|
202
245
|
scope.error = `Connections not allowed over the www subdomain`;
|
|
@@ -208,7 +251,7 @@ export class WebfloServer extends WebfloRuntime {
|
|
|
208
251
|
// Level 3 validation
|
|
209
252
|
// and actual processing
|
|
210
253
|
scope.request = this.createRequest(scope.url.href, requestInit);
|
|
211
|
-
scope.session = this.constructor.SessionStorage.create(scope.request, { secret: this.#cx.env.entries.
|
|
254
|
+
scope.session = this.constructor.SessionStorage.create(scope.request, { secret: this.#cx.env.entries[this.#cx.server.session_key_variable] });
|
|
212
255
|
if (!scope.error) {
|
|
213
256
|
if (!(scope.clientMessagingRegistry = this.#globalMessagingRegistry.get(scope.session.sessionID))) {
|
|
214
257
|
scope.error = `Lost or invalid clientID`;
|
|
@@ -257,7 +300,7 @@ export class WebfloServer extends WebfloRuntime {
|
|
|
257
300
|
if (!hosts.includes(scope.url.hostname) && !hosts.includes('*')) {
|
|
258
301
|
scope.exit = { status: 500 };
|
|
259
302
|
scope.exitMessage = 'Unrecognized host';
|
|
260
|
-
} else if (scope.url.protocol === 'http:' && this.#cx.server.https.force) {
|
|
303
|
+
} else if (scope.url.protocol === 'http:' && this.#cx.server.https.port && this.#cx.server.https.force) {
|
|
261
304
|
scope.exit = {
|
|
262
305
|
status: 302,
|
|
263
306
|
headers: { Location: (scope.url.protocol = 'https:', scope.url.href) }
|
|
@@ -289,7 +332,9 @@ export class WebfloServer extends WebfloRuntime {
|
|
|
289
332
|
if (!scope.response) {
|
|
290
333
|
scope.response = await this.navigate(fullUrl, requestInit, {
|
|
291
334
|
request: nodeRequest,
|
|
292
|
-
response: nodeResponse
|
|
335
|
+
response: nodeResponse,
|
|
336
|
+
ipAddress: nodeRequest.headers['x-forwarded-for']?.split(',')[0]
|
|
337
|
+
|| nodeRequest.socket.remoteAddress
|
|
293
338
|
});
|
|
294
339
|
}
|
|
295
340
|
// -----------------
|
|
@@ -491,7 +536,8 @@ export class WebfloServer extends WebfloRuntime {
|
|
|
491
536
|
},
|
|
492
537
|
respondWith: async (response, isRedirectMessage = false) => {
|
|
493
538
|
if (!isRedirectMessage && scope.eventLifecyclePromises.dirty && !scope.eventLifecyclePromises.size) {
|
|
494
|
-
|
|
539
|
+
console.error('Final response already sent');
|
|
540
|
+
return;
|
|
495
541
|
}
|
|
496
542
|
return await this.execPush(scope.clientMessaging, response);
|
|
497
543
|
},
|
|
@@ -503,7 +549,13 @@ export class WebfloServer extends WebfloRuntime {
|
|
|
503
549
|
: [];
|
|
504
550
|
scope.request = this.createRequest(scope.url.href, scope.init, scope.autoHeaders.filter((header) => header.type === 'request'));
|
|
505
551
|
scope.cookies = this.constructor.CookieStorage.create(scope.request);
|
|
506
|
-
scope.session = this.constructor.SessionStorage.create(scope.request, {
|
|
552
|
+
scope.session = this.constructor.SessionStorage.create(scope.request, {
|
|
553
|
+
secret: this.#cx.env.entries[this.#cx.server.session_key_variable],
|
|
554
|
+
registry: this.#sdk.redis && {
|
|
555
|
+
get: async (key) => { return await this.#sdk.redis.hgetall(key) },
|
|
556
|
+
set: async (key, value) => { return await this.#sdk.redis.hset(key, value) },
|
|
557
|
+
},
|
|
558
|
+
});
|
|
507
559
|
const sessionID = scope.session.sessionID;
|
|
508
560
|
if (!this.#globalMessagingRegistry.has(sessionID)) {
|
|
509
561
|
this.#globalMessagingRegistry.set(sessionID, new ClientMessagingRegistry(this, sessionID));
|
|
@@ -521,13 +573,15 @@ export class WebfloServer extends WebfloRuntime {
|
|
|
521
573
|
cookies: scope.cookies,
|
|
522
574
|
session: scope.session,
|
|
523
575
|
user: scope.user,
|
|
524
|
-
client: scope.clientMessaging
|
|
576
|
+
client: scope.clientMessaging,
|
|
577
|
+
sdk: this.#sdk
|
|
525
578
|
});
|
|
579
|
+
await this.setup(scope.httpEvent);
|
|
526
580
|
// Restore session before dispatching
|
|
527
581
|
if (scope.request.method === 'GET'
|
|
528
582
|
&& (scope.redirectMessageID = scope.httpEvent.url.query['redirect-message'])
|
|
529
|
-
&& (scope.redirectMessage = scope.session.get(`redirect-message:${scope.redirectMessageID}`))) {
|
|
530
|
-
scope.session.delete(`redirect-message:${scope.redirectMessageID}`);
|
|
583
|
+
&& (scope.redirectMessage = await scope.session.get(`redirect-message:${scope.redirectMessageID}`))) {
|
|
584
|
+
await scope.session.delete(`redirect-message:${scope.redirectMessageID}`);
|
|
531
585
|
}
|
|
532
586
|
// Dispatch for response
|
|
533
587
|
scope.response = await this.dispatch(scope.httpEvent, {}, async (event) => {
|
|
@@ -535,15 +589,15 @@ export class WebfloServer extends WebfloRuntime {
|
|
|
535
589
|
});
|
|
536
590
|
// ---------------
|
|
537
591
|
// Response processing
|
|
538
|
-
scope.
|
|
539
|
-
scope.
|
|
592
|
+
scope.response = await this.normalizeResponse(scope.httpEvent, scope.response);
|
|
593
|
+
scope.hasBackgroundActivity = scope.clientMessaging.isMessaging() || scope.eventLifecyclePromises.size || (scope.redirectMessage && !scope.response.headers.get('Location'));
|
|
540
594
|
if (scope.hasBackgroundActivity) {
|
|
541
595
|
scope.response.headers.set('X-Background-Messaging', `ws:${scope.clientMessaging.portID}`);
|
|
542
596
|
}
|
|
543
597
|
// Reponse handlers
|
|
544
598
|
if (scope.response.headers.get('Location')) {
|
|
545
599
|
if (scope.redirectMessage) {
|
|
546
|
-
scope.session.set(`redirect-message:${scope.redirectMessageID}`, scope.redirectMessage);
|
|
600
|
+
await scope.session.set(`redirect-message:${scope.redirectMessageID}`, scope.redirectMessage);
|
|
547
601
|
}
|
|
548
602
|
this.writeRedirectHeaders(scope.httpEvent, scope.response);
|
|
549
603
|
} else {
|
|
@@ -47,6 +47,9 @@ export function renderHttpMessageInit(httpMessageInit) {
|
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
export async function parseHttpMessage(httpMessage) {
|
|
50
|
+
if (httpMessage.meta && 'body' in httpMessage.meta && httpMessage.meta.type === 'json') {
|
|
51
|
+
return httpMessage.meta.body;
|
|
52
|
+
}
|
|
50
53
|
let result;
|
|
51
54
|
const contentType = httpMessage.headers.get('Content-Type') || '';
|
|
52
55
|
if (contentType === 'application/x-www-form-urlencoded' || contentType.startsWith('multipart/form-data')) {
|
|
@@ -132,9 +135,19 @@ Object.defineProperties(Request, {
|
|
|
132
135
|
value: async function (request, init = {}) {
|
|
133
136
|
const requestInit = [
|
|
134
137
|
'method', 'headers', 'mode', 'credentials', 'cache', 'redirect', 'referrer', 'integrity',
|
|
135
|
-
].reduce((init, prop) => (
|
|
138
|
+
].reduce(($init, prop) => (
|
|
139
|
+
{ ...$init, [prop]: prop in init ? init[prop] : (prop === 'headers' ? new Headers(request[prop]) : request[prop]) }
|
|
140
|
+
), {});
|
|
136
141
|
if (!['GET', 'HEAD'].includes(init.method?.toUpperCase() || request.method)) {
|
|
137
|
-
|
|
142
|
+
if ('body' in init) {
|
|
143
|
+
requestInit.body = init.body
|
|
144
|
+
if (!('headers' in init)) {
|
|
145
|
+
requestInit.headers.delete('Content-Type');
|
|
146
|
+
requestInit.headers.delete('Content-Length');
|
|
147
|
+
}
|
|
148
|
+
} else {
|
|
149
|
+
requestInit.body = await request.clone().arrayBuffer();
|
|
150
|
+
}
|
|
138
151
|
}
|
|
139
152
|
if (requestInit.mode === 'navigate') {
|
|
140
153
|
requestInit.mode = 'cors';
|
package/src/services-pi/index.js
CHANGED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
|
|
2
|
+
/**
|
|
3
|
+
* imports
|
|
4
|
+
*/
|
|
5
|
+
import webpush from 'web-push';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @description
|
|
9
|
+
*/
|
|
10
|
+
export const desc = {
|
|
11
|
+
generate: 'Generate a set of VAPID keys for push notifications.',
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Reads SSL from file.
|
|
16
|
+
*
|
|
17
|
+
* @return object
|
|
18
|
+
*/
|
|
19
|
+
export async function generate() {
|
|
20
|
+
const cx = this || {};
|
|
21
|
+
const vapidKeys = webpush.generateVAPIDKeys();
|
|
22
|
+
cx.logger.log(vapidKeys);
|
|
23
|
+
}
|