@b9g/platform 0.1.11 → 0.1.12
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/README.md +1 -1
- package/package.json +7 -31
- package/src/index.d.ts +216 -11
- package/src/index.js +488 -103
- package/src/runtime.d.ts +155 -7
- package/src/runtime.js +393 -245
- package/src/shovel-config.d.ts +10 -0
- package/src/worker.d.ts +39 -0
- package/src/worker.js +285 -0
- package/src/config.d.ts +0 -172
- package/src/config.js +0 -641
- package/src/cookie-store.d.ts +0 -80
- package/src/cookie-store.js +0 -233
- package/src/single-threaded.d.ts +0 -59
- package/src/single-threaded.js +0 -114
- package/src/worker-pool.d.ts +0 -93
- package/src/worker-pool.js +0 -390
package/src/runtime.js
CHANGED
|
@@ -2,13 +2,228 @@
|
|
|
2
2
|
import "../chunk-P57PW2II.js";
|
|
3
3
|
|
|
4
4
|
// src/runtime.ts
|
|
5
|
-
import { RequestCookieStore } from "./cookie-store.js";
|
|
6
5
|
import { AsyncContext } from "@b9g/async-context";
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
6
|
+
import {
|
|
7
|
+
configure
|
|
8
|
+
} from "@logtape/logtape";
|
|
9
|
+
function parseCookieHeader(cookieHeader) {
|
|
10
|
+
const cookies = /* @__PURE__ */ new Map();
|
|
11
|
+
if (!cookieHeader)
|
|
12
|
+
return cookies;
|
|
13
|
+
const pairs = cookieHeader.split(/;\s*/);
|
|
14
|
+
for (const pair of pairs) {
|
|
15
|
+
const [name, ...valueParts] = pair.split("=");
|
|
16
|
+
if (name) {
|
|
17
|
+
const value = valueParts.join("=");
|
|
18
|
+
cookies.set(name.trim(), decodeURIComponent(value || ""));
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return cookies;
|
|
22
|
+
}
|
|
23
|
+
function serializeCookie(cookie) {
|
|
24
|
+
let header = `${encodeURIComponent(cookie.name)}=${encodeURIComponent(cookie.value)}`;
|
|
25
|
+
if (cookie.expires !== void 0 && cookie.expires !== null) {
|
|
26
|
+
const date = new Date(cookie.expires);
|
|
27
|
+
header += `; Expires=${date.toUTCString()}`;
|
|
28
|
+
}
|
|
29
|
+
if (cookie.domain) {
|
|
30
|
+
header += `; Domain=${cookie.domain}`;
|
|
31
|
+
}
|
|
32
|
+
if (cookie.path) {
|
|
33
|
+
header += `; Path=${cookie.path}`;
|
|
34
|
+
} else {
|
|
35
|
+
header += `; Path=/`;
|
|
36
|
+
}
|
|
37
|
+
if (cookie.sameSite) {
|
|
38
|
+
header += `; SameSite=${cookie.sameSite.charAt(0).toUpperCase() + cookie.sameSite.slice(1)}`;
|
|
39
|
+
} else {
|
|
40
|
+
header += `; SameSite=Strict`;
|
|
41
|
+
}
|
|
42
|
+
if (cookie.partitioned) {
|
|
43
|
+
header += `; Partitioned`;
|
|
44
|
+
}
|
|
45
|
+
header += `; Secure`;
|
|
46
|
+
return header;
|
|
47
|
+
}
|
|
48
|
+
function parseSetCookieHeader(setCookieHeader) {
|
|
49
|
+
const parts = setCookieHeader.split(/;\s*/);
|
|
50
|
+
const [nameValue, ...attributes] = parts;
|
|
51
|
+
const [name, ...valueParts] = nameValue.split("=");
|
|
52
|
+
const value = valueParts.join("=");
|
|
53
|
+
const cookie = {
|
|
54
|
+
name: decodeURIComponent(name.trim()),
|
|
55
|
+
value: decodeURIComponent(value || "")
|
|
56
|
+
};
|
|
57
|
+
for (const attr of attributes) {
|
|
58
|
+
const [key, ...attrValueParts] = attr.split("=");
|
|
59
|
+
const attrKey = key.trim().toLowerCase();
|
|
60
|
+
const attrValue = attrValueParts.join("=").trim();
|
|
61
|
+
switch (attrKey) {
|
|
62
|
+
case "expires":
|
|
63
|
+
cookie.expires = new Date(attrValue).getTime();
|
|
64
|
+
break;
|
|
65
|
+
case "max-age":
|
|
66
|
+
cookie.expires = Date.now() + parseInt(attrValue, 10) * 1e3;
|
|
67
|
+
break;
|
|
68
|
+
case "domain":
|
|
69
|
+
cookie.domain = attrValue;
|
|
70
|
+
break;
|
|
71
|
+
case "path":
|
|
72
|
+
cookie.path = attrValue;
|
|
73
|
+
break;
|
|
74
|
+
case "secure":
|
|
75
|
+
cookie.secure = true;
|
|
76
|
+
break;
|
|
77
|
+
case "samesite":
|
|
78
|
+
cookie.sameSite = attrValue.toLowerCase();
|
|
79
|
+
break;
|
|
80
|
+
case "partitioned":
|
|
81
|
+
cookie.partitioned = true;
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return cookie;
|
|
86
|
+
}
|
|
87
|
+
var RequestCookieStore = class extends EventTarget {
|
|
88
|
+
#cookies;
|
|
89
|
+
#changes;
|
|
90
|
+
// null = deleted
|
|
91
|
+
#request;
|
|
92
|
+
// Event handler for cookie changes (spec compliance)
|
|
93
|
+
// eslint-disable-next-line no-restricted-syntax
|
|
94
|
+
onchange = null;
|
|
95
|
+
constructor(request) {
|
|
96
|
+
super();
|
|
97
|
+
this.#cookies = /* @__PURE__ */ new Map();
|
|
98
|
+
this.#changes = /* @__PURE__ */ new Map();
|
|
99
|
+
this.#request = request || null;
|
|
100
|
+
if (request) {
|
|
101
|
+
const cookieHeader = request.headers.get("cookie");
|
|
102
|
+
if (cookieHeader) {
|
|
103
|
+
const parsed = parseCookieHeader(cookieHeader);
|
|
104
|
+
for (const [name, value] of parsed) {
|
|
105
|
+
this.#cookies.set(name, { name, value });
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
async get(nameOrOptions) {
|
|
111
|
+
const name = typeof nameOrOptions === "string" ? nameOrOptions : nameOrOptions.name;
|
|
112
|
+
if (!name) {
|
|
113
|
+
throw new TypeError("Cookie name is required");
|
|
114
|
+
}
|
|
115
|
+
if (this.#changes.has(name)) {
|
|
116
|
+
const change = this.#changes.get(name);
|
|
117
|
+
if (change === null || change === void 0)
|
|
118
|
+
return null;
|
|
119
|
+
return {
|
|
120
|
+
name: change.name,
|
|
121
|
+
value: change.value,
|
|
122
|
+
domain: change.domain ?? void 0,
|
|
123
|
+
path: change.path,
|
|
124
|
+
expires: change.expires ?? void 0,
|
|
125
|
+
sameSite: change.sameSite,
|
|
126
|
+
partitioned: change.partitioned
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
return this.#cookies.get(name) || null;
|
|
130
|
+
}
|
|
131
|
+
async getAll(nameOrOptions) {
|
|
132
|
+
const name = typeof nameOrOptions === "string" ? nameOrOptions : nameOrOptions?.name;
|
|
133
|
+
const result = [];
|
|
134
|
+
const allNames = /* @__PURE__ */ new Set([
|
|
135
|
+
...this.#cookies.keys(),
|
|
136
|
+
...this.#changes.keys()
|
|
137
|
+
]);
|
|
138
|
+
for (const cookieName of allNames) {
|
|
139
|
+
if (name && cookieName !== name)
|
|
140
|
+
continue;
|
|
141
|
+
if (this.#changes.has(cookieName) && this.#changes.get(cookieName) === null) {
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
const cookie = await this.get(cookieName);
|
|
145
|
+
if (cookie) {
|
|
146
|
+
result.push(cookie);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return result;
|
|
150
|
+
}
|
|
151
|
+
async set(nameOrOptions, value) {
|
|
152
|
+
let cookie;
|
|
153
|
+
if (typeof nameOrOptions === "string") {
|
|
154
|
+
if (value === void 0) {
|
|
155
|
+
throw new TypeError("Cookie value is required");
|
|
156
|
+
}
|
|
157
|
+
cookie = {
|
|
158
|
+
name: nameOrOptions,
|
|
159
|
+
value,
|
|
160
|
+
path: "/",
|
|
161
|
+
sameSite: "strict"
|
|
162
|
+
};
|
|
163
|
+
} else {
|
|
164
|
+
cookie = {
|
|
165
|
+
path: "/",
|
|
166
|
+
sameSite: "strict",
|
|
167
|
+
...nameOrOptions
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
const size = cookie.name.length + cookie.value.length;
|
|
171
|
+
if (size > 4096) {
|
|
172
|
+
throw new TypeError(
|
|
173
|
+
`Cookie name+value too large: ${size} bytes (max 4096)`
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
this.#changes.set(cookie.name, cookie);
|
|
177
|
+
}
|
|
178
|
+
async delete(nameOrOptions) {
|
|
179
|
+
const name = typeof nameOrOptions === "string" ? nameOrOptions : nameOrOptions.name;
|
|
180
|
+
if (!name) {
|
|
181
|
+
throw new TypeError("Cookie name is required");
|
|
182
|
+
}
|
|
183
|
+
this.#changes.set(name, null);
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Get Set-Cookie headers for all changes
|
|
187
|
+
* This should be called when constructing the Response
|
|
188
|
+
*/
|
|
189
|
+
getSetCookieHeaders() {
|
|
190
|
+
const headers = [];
|
|
191
|
+
for (const [name, change] of this.#changes) {
|
|
192
|
+
if (change === null) {
|
|
193
|
+
headers.push(
|
|
194
|
+
serializeCookie({
|
|
195
|
+
name,
|
|
196
|
+
value: "",
|
|
197
|
+
expires: 0,
|
|
198
|
+
path: "/"
|
|
199
|
+
})
|
|
200
|
+
);
|
|
201
|
+
} else {
|
|
202
|
+
headers.push(serializeCookie(change));
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return headers;
|
|
206
|
+
}
|
|
207
|
+
hasChanges() {
|
|
208
|
+
return this.#changes.size > 0;
|
|
209
|
+
}
|
|
210
|
+
clearChanges() {
|
|
211
|
+
this.#changes.clear();
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
var CustomLoggerStorage = class {
|
|
215
|
+
#factory;
|
|
216
|
+
constructor(factory) {
|
|
217
|
+
this.#factory = factory;
|
|
218
|
+
}
|
|
219
|
+
open(...categories) {
|
|
220
|
+
return this.#factory(...categories);
|
|
221
|
+
}
|
|
222
|
+
};
|
|
223
|
+
var SERVICE_WORKER_EVENTS = ["fetch", "install", "activate"];
|
|
224
|
+
function isServiceWorkerEvent(type) {
|
|
225
|
+
return SERVICE_WORKER_EVENTS.includes(type);
|
|
226
|
+
}
|
|
12
227
|
if (import.meta.env && !import.meta.env.MODE && import.meta.env.NODE_ENV) {
|
|
13
228
|
import.meta.env.MODE = import.meta.env.NODE_ENV;
|
|
14
229
|
}
|
|
@@ -19,7 +234,8 @@ var PATCHED_KEYS = [
|
|
|
19
234
|
"self",
|
|
20
235
|
"fetch",
|
|
21
236
|
"caches",
|
|
22
|
-
"
|
|
237
|
+
"directories",
|
|
238
|
+
"loggers",
|
|
23
239
|
"registration",
|
|
24
240
|
"clients",
|
|
25
241
|
"skipWaiting",
|
|
@@ -133,9 +349,7 @@ var ShovelClient = class {
|
|
|
133
349
|
}
|
|
134
350
|
// Implementation
|
|
135
351
|
postMessage(_message, _transferOrOptions) {
|
|
136
|
-
|
|
137
|
-
"[ServiceWorker] Client.postMessage() not supported in server context"
|
|
138
|
-
);
|
|
352
|
+
self.loggers.open("platform").warn("Client.postMessage() not supported in server context");
|
|
139
353
|
}
|
|
140
354
|
};
|
|
141
355
|
var ShovelWindowClient = class extends ShovelClient {
|
|
@@ -147,15 +361,11 @@ var ShovelWindowClient = class extends ShovelClient {
|
|
|
147
361
|
this.visibilityState = options.visibilityState || "hidden";
|
|
148
362
|
}
|
|
149
363
|
async focus() {
|
|
150
|
-
|
|
151
|
-
"[ServiceWorker] WindowClient.focus() not supported in server context"
|
|
152
|
-
);
|
|
364
|
+
self.loggers.open("platform").warn("WindowClient.focus() not supported in server context");
|
|
153
365
|
return this;
|
|
154
366
|
}
|
|
155
367
|
async navigate(_url) {
|
|
156
|
-
|
|
157
|
-
"[ServiceWorker] WindowClient.navigate() not supported in server context"
|
|
158
|
-
);
|
|
368
|
+
self.loggers.open("platform").warn("WindowClient.navigate() not supported in server context");
|
|
159
369
|
return null;
|
|
160
370
|
}
|
|
161
371
|
};
|
|
@@ -169,9 +379,7 @@ var ShovelClients = class {
|
|
|
169
379
|
return [];
|
|
170
380
|
}
|
|
171
381
|
async openWindow(_url) {
|
|
172
|
-
|
|
173
|
-
"[ServiceWorker] Clients.openWindow() not supported in server context"
|
|
174
|
-
);
|
|
382
|
+
self.loggers.open("platform").warn("Clients.openWindow() not supported in server context");
|
|
175
383
|
return null;
|
|
176
384
|
}
|
|
177
385
|
};
|
|
@@ -205,9 +413,7 @@ var ShovelServiceWorker = class extends EventTarget {
|
|
|
205
413
|
}
|
|
206
414
|
// Implementation
|
|
207
415
|
postMessage(_message, _transferOrOptions) {
|
|
208
|
-
|
|
209
|
-
"[ServiceWorker] ServiceWorker.postMessage() not implemented in server context"
|
|
210
|
-
);
|
|
416
|
+
self.loggers.open("platform").warn("ServiceWorker.postMessage() not implemented in server context");
|
|
211
417
|
}
|
|
212
418
|
// Internal method to update state and dispatch statechange event
|
|
213
419
|
_setState(newState) {
|
|
@@ -239,9 +445,9 @@ var ShovelServiceWorkerRegistration = class extends EventTarget {
|
|
|
239
445
|
cookies;
|
|
240
446
|
pushManager;
|
|
241
447
|
onupdatefound;
|
|
242
|
-
constructor(
|
|
448
|
+
constructor(scope = "/", scriptURL = "/") {
|
|
243
449
|
super();
|
|
244
|
-
this.scope =
|
|
450
|
+
this.scope = scope;
|
|
245
451
|
this.updateViaCache = "imports";
|
|
246
452
|
this.navigationPreload = new ShovelNavigationPreloadManager();
|
|
247
453
|
this._serviceWorker = new ShovelServiceWorker(scriptURL, "parsed");
|
|
@@ -264,9 +470,7 @@ var ShovelServiceWorkerRegistration = class extends EventTarget {
|
|
|
264
470
|
return [];
|
|
265
471
|
}
|
|
266
472
|
async showNotification(_title, _options) {
|
|
267
|
-
|
|
268
|
-
"[ServiceWorker] Notifications not supported in server context"
|
|
269
|
-
);
|
|
473
|
+
self.loggers.open("platform").warn("Notifications not supported in server context");
|
|
270
474
|
}
|
|
271
475
|
async sync() {
|
|
272
476
|
}
|
|
@@ -365,7 +569,9 @@ var ShovelServiceWorkerRegistration = class extends EventTarget {
|
|
|
365
569
|
const response = await event.getResponse();
|
|
366
570
|
const promises = event.getPromises();
|
|
367
571
|
if (promises.length > 0) {
|
|
368
|
-
Promise.allSettled(promises).catch(
|
|
572
|
+
Promise.allSettled(promises).catch(
|
|
573
|
+
(err) => self.loggers.open("platform").error`waitUntil error: ${err}`
|
|
574
|
+
);
|
|
369
575
|
}
|
|
370
576
|
if (event.cookieStore.hasChanges()) {
|
|
371
577
|
const setCookieHeaders = event.cookieStore.getSetCookieHeaders();
|
|
@@ -412,8 +618,8 @@ var ShovelServiceWorkerContainer = class extends EventTarget {
|
|
|
412
618
|
/**
|
|
413
619
|
* Get registration for a specific scope
|
|
414
620
|
*/
|
|
415
|
-
async getRegistration(
|
|
416
|
-
return this.#registrations.get(
|
|
621
|
+
async getRegistration(scope = "/") {
|
|
622
|
+
return this.#registrations.get(scope);
|
|
417
623
|
}
|
|
418
624
|
/**
|
|
419
625
|
* Get all registrations
|
|
@@ -426,26 +632,26 @@ var ShovelServiceWorkerContainer = class extends EventTarget {
|
|
|
426
632
|
*/
|
|
427
633
|
async register(scriptURL, options) {
|
|
428
634
|
const url = typeof scriptURL === "string" ? scriptURL : scriptURL.toString();
|
|
429
|
-
const
|
|
430
|
-
let
|
|
431
|
-
if (
|
|
432
|
-
|
|
433
|
-
|
|
635
|
+
const scope = this.#normalizeScope(options?.scope || "/");
|
|
636
|
+
let registration = this.#registrations.get(scope);
|
|
637
|
+
if (registration) {
|
|
638
|
+
registration._serviceWorker.scriptURL = url;
|
|
639
|
+
registration._serviceWorker._setState("parsed");
|
|
434
640
|
} else {
|
|
435
|
-
|
|
436
|
-
this.#registrations.set(
|
|
641
|
+
registration = new ShovelServiceWorkerRegistration(scope, url);
|
|
642
|
+
this.#registrations.set(scope, registration);
|
|
437
643
|
this.dispatchEvent(new Event("updatefound"));
|
|
438
644
|
}
|
|
439
|
-
return
|
|
645
|
+
return registration;
|
|
440
646
|
}
|
|
441
647
|
/**
|
|
442
648
|
* Unregister a ServiceWorker registration
|
|
443
649
|
*/
|
|
444
|
-
async unregister(
|
|
445
|
-
const
|
|
446
|
-
if (
|
|
447
|
-
await
|
|
448
|
-
this.#registrations.delete(
|
|
650
|
+
async unregister(scope) {
|
|
651
|
+
const registration = this.#registrations.get(scope);
|
|
652
|
+
if (registration) {
|
|
653
|
+
await registration.unregister();
|
|
654
|
+
this.#registrations.delete(scope);
|
|
449
655
|
return true;
|
|
450
656
|
}
|
|
451
657
|
return false;
|
|
@@ -458,9 +664,9 @@ var ShovelServiceWorkerContainer = class extends EventTarget {
|
|
|
458
664
|
const pathname = url.pathname;
|
|
459
665
|
const matchingScope = this.#findMatchingScope(pathname);
|
|
460
666
|
if (matchingScope) {
|
|
461
|
-
const
|
|
462
|
-
if (
|
|
463
|
-
return await
|
|
667
|
+
const registration = this.#registrations.get(matchingScope);
|
|
668
|
+
if (registration && registration.ready) {
|
|
669
|
+
return await registration.handleRequest(request);
|
|
464
670
|
}
|
|
465
671
|
}
|
|
466
672
|
return null;
|
|
@@ -470,9 +676,9 @@ var ShovelServiceWorkerContainer = class extends EventTarget {
|
|
|
470
676
|
*/
|
|
471
677
|
async installAll() {
|
|
472
678
|
const installations = Array.from(this.#registrations.values()).map(
|
|
473
|
-
async (
|
|
474
|
-
await
|
|
475
|
-
await
|
|
679
|
+
async (registration) => {
|
|
680
|
+
await registration.install();
|
|
681
|
+
await registration.activate();
|
|
476
682
|
}
|
|
477
683
|
);
|
|
478
684
|
await promiseWithTimeout(
|
|
@@ -492,14 +698,14 @@ var ShovelServiceWorkerContainer = class extends EventTarget {
|
|
|
492
698
|
/**
|
|
493
699
|
* Normalize scope to ensure it starts and ends correctly
|
|
494
700
|
*/
|
|
495
|
-
#normalizeScope(
|
|
496
|
-
if (!
|
|
497
|
-
|
|
701
|
+
#normalizeScope(scope) {
|
|
702
|
+
if (!scope.startsWith("/")) {
|
|
703
|
+
scope = "/" + scope;
|
|
498
704
|
}
|
|
499
|
-
if (
|
|
500
|
-
|
|
705
|
+
if (scope !== "/" && !scope.endsWith("/")) {
|
|
706
|
+
scope = scope + "/";
|
|
501
707
|
}
|
|
502
|
-
return
|
|
708
|
+
return scope;
|
|
503
709
|
}
|
|
504
710
|
/**
|
|
505
711
|
* Find the most specific scope that matches a pathname
|
|
@@ -507,9 +713,9 @@ var ShovelServiceWorkerContainer = class extends EventTarget {
|
|
|
507
713
|
#findMatchingScope(pathname) {
|
|
508
714
|
const scopes = Array.from(this.#registrations.keys());
|
|
509
715
|
scopes.sort((a, b) => b.length - a.length);
|
|
510
|
-
for (const
|
|
511
|
-
if (pathname.startsWith(
|
|
512
|
-
return
|
|
716
|
+
for (const scope of scopes) {
|
|
717
|
+
if (pathname.startsWith(scope === "/" ? "/" : scope)) {
|
|
718
|
+
return scope;
|
|
513
719
|
}
|
|
514
720
|
}
|
|
515
721
|
return null;
|
|
@@ -561,9 +767,7 @@ var Notification = class extends EventTarget {
|
|
|
561
767
|
this.onshow = null;
|
|
562
768
|
}
|
|
563
769
|
close() {
|
|
564
|
-
|
|
565
|
-
"[ServiceWorker] Notification.close() not supported in server context"
|
|
566
|
-
);
|
|
770
|
+
self.loggers.open("platform").warn("Notification.close() not supported in server context");
|
|
567
771
|
}
|
|
568
772
|
static async requestPermission() {
|
|
569
773
|
return "denied";
|
|
@@ -637,7 +841,8 @@ var ServiceWorkerGlobals = class {
|
|
|
637
841
|
registration;
|
|
638
842
|
// Storage APIs
|
|
639
843
|
caches;
|
|
640
|
-
|
|
844
|
+
directories;
|
|
845
|
+
loggers;
|
|
641
846
|
// Clients API
|
|
642
847
|
// Our custom Clients implementation provides core functionality compatible with the Web API
|
|
643
848
|
clients;
|
|
@@ -664,9 +869,7 @@ var ServiceWorkerGlobals = class {
|
|
|
664
869
|
crypto;
|
|
665
870
|
// WorkerGlobalScope methods (stubs for server context)
|
|
666
871
|
importScripts(..._urls) {
|
|
667
|
-
|
|
668
|
-
"[ServiceWorker] importScripts() not supported in server context"
|
|
669
|
-
);
|
|
872
|
+
self.loggers.open("platform").warn("importScripts() not supported in server context");
|
|
670
873
|
}
|
|
671
874
|
atob(data) {
|
|
672
875
|
return globalThis.atob(data);
|
|
@@ -707,7 +910,7 @@ var ServiceWorkerGlobals = class {
|
|
|
707
910
|
globalThis.queueMicrotask(callback);
|
|
708
911
|
}
|
|
709
912
|
reportError(e) {
|
|
710
|
-
|
|
913
|
+
self.loggers.open("platform").error`reportError: ${e}`;
|
|
711
914
|
}
|
|
712
915
|
setInterval(handler, timeout, ...args) {
|
|
713
916
|
return globalThis.setInterval(handler, timeout, ...args);
|
|
@@ -747,7 +950,8 @@ var ServiceWorkerGlobals = class {
|
|
|
747
950
|
this.self = globalThis;
|
|
748
951
|
this.registration = options.registration;
|
|
749
952
|
this.caches = options.caches;
|
|
750
|
-
this.
|
|
953
|
+
this.directories = options.directories;
|
|
954
|
+
this.loggers = options.loggers;
|
|
751
955
|
this.#isDevelopment = options.isDevelopment ?? false;
|
|
752
956
|
this.clients = this.#createClientsAPI();
|
|
753
957
|
this.serviceWorker = null;
|
|
@@ -783,28 +987,48 @@ var ServiceWorkerGlobals = class {
|
|
|
783
987
|
* Allows the ServiceWorker to activate immediately
|
|
784
988
|
*/
|
|
785
989
|
async skipWaiting() {
|
|
786
|
-
|
|
990
|
+
self.loggers.open("platform").info("skipWaiting() called");
|
|
787
991
|
if (!this.#isDevelopment) {
|
|
788
|
-
|
|
789
|
-
"[ServiceWorker] skipWaiting() - production graceful restart not implemented"
|
|
790
|
-
);
|
|
992
|
+
self.loggers.open("platform").info("skipWaiting() - production graceful restart not implemented");
|
|
791
993
|
}
|
|
792
994
|
}
|
|
793
995
|
/**
|
|
794
|
-
* Event target delegation to registration
|
|
996
|
+
* Event target delegation - ServiceWorker events go to registration,
|
|
997
|
+
* other events (like "message" for worker threads) go to native handler
|
|
795
998
|
*/
|
|
796
999
|
addEventListener(type, listener, options) {
|
|
797
|
-
if (listener)
|
|
1000
|
+
if (!listener)
|
|
1001
|
+
return;
|
|
1002
|
+
if (isServiceWorkerEvent(type)) {
|
|
798
1003
|
this.registration.addEventListener(type, listener, options);
|
|
1004
|
+
} else {
|
|
1005
|
+
const original = this.#originals.addEventListener;
|
|
1006
|
+
if (original) {
|
|
1007
|
+
original.call(globalThis, type, listener, options);
|
|
1008
|
+
}
|
|
799
1009
|
}
|
|
800
1010
|
}
|
|
801
1011
|
removeEventListener(type, listener, options) {
|
|
802
|
-
if (listener)
|
|
1012
|
+
if (!listener)
|
|
1013
|
+
return;
|
|
1014
|
+
if (isServiceWorkerEvent(type)) {
|
|
803
1015
|
this.registration.removeEventListener(type, listener, options);
|
|
1016
|
+
} else {
|
|
1017
|
+
const original = this.#originals.removeEventListener;
|
|
1018
|
+
if (original) {
|
|
1019
|
+
original.call(globalThis, type, listener, options);
|
|
1020
|
+
}
|
|
804
1021
|
}
|
|
805
1022
|
}
|
|
806
1023
|
dispatchEvent(event) {
|
|
807
|
-
|
|
1024
|
+
if (isServiceWorkerEvent(event.type)) {
|
|
1025
|
+
return this.registration.dispatchEvent(event);
|
|
1026
|
+
}
|
|
1027
|
+
const original = this.#originals.dispatchEvent;
|
|
1028
|
+
if (original) {
|
|
1029
|
+
return original.call(globalThis, event);
|
|
1030
|
+
}
|
|
1031
|
+
return false;
|
|
808
1032
|
}
|
|
809
1033
|
/**
|
|
810
1034
|
* Create Clients API implementation
|
|
@@ -828,7 +1052,8 @@ var ServiceWorkerGlobals = class {
|
|
|
828
1052
|
g.removeEventListener = this.removeEventListener.bind(this);
|
|
829
1053
|
g.dispatchEvent = this.dispatchEvent.bind(this);
|
|
830
1054
|
g.caches = this.caches;
|
|
831
|
-
g.
|
|
1055
|
+
g.directories = this.directories;
|
|
1056
|
+
g.loggers = this.loggers;
|
|
832
1057
|
g.registration = this.registration;
|
|
833
1058
|
g.skipWaiting = this.skipWaiting.bind(this);
|
|
834
1059
|
g.clients = this.clients;
|
|
@@ -854,187 +1079,105 @@ var ServiceWorkerGlobals = class {
|
|
|
854
1079
|
}
|
|
855
1080
|
}
|
|
856
1081
|
};
|
|
857
|
-
var
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
var
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
}
|
|
884
|
-
if (!registration) {
|
|
885
|
-
throw new Error("ServiceWorker runtime not initialized");
|
|
886
|
-
}
|
|
887
|
-
try {
|
|
888
|
-
const response = await registration.handleRequest(request);
|
|
889
|
-
return response;
|
|
890
|
-
} catch (error) {
|
|
891
|
-
logger.error("[Worker] ServiceWorker request failed: {error}", { error });
|
|
892
|
-
console.error("[Worker] ServiceWorker request failed:", error);
|
|
893
|
-
const response = new Response("ServiceWorker request failed", {
|
|
894
|
-
status: 500
|
|
895
|
-
});
|
|
896
|
-
return response;
|
|
1082
|
+
var SHOVEL_CATEGORIES = [
|
|
1083
|
+
"cli",
|
|
1084
|
+
"build",
|
|
1085
|
+
"platform",
|
|
1086
|
+
"watcher",
|
|
1087
|
+
"worker",
|
|
1088
|
+
"single-threaded",
|
|
1089
|
+
"assets",
|
|
1090
|
+
"platform-node",
|
|
1091
|
+
"platform-bun",
|
|
1092
|
+
"platform-cloudflare",
|
|
1093
|
+
"cache",
|
|
1094
|
+
"cache-redis",
|
|
1095
|
+
"router"
|
|
1096
|
+
];
|
|
1097
|
+
var BUILTIN_SINK_PROVIDERS = {
|
|
1098
|
+
console: { module: "@logtape/logtape", factory: "getConsoleSink" },
|
|
1099
|
+
file: { module: "@logtape/file", factory: "getFileSink" },
|
|
1100
|
+
rotating: { module: "@logtape/file", factory: "getRotatingFileSink" },
|
|
1101
|
+
"stream-file": { module: "@logtape/file", factory: "getStreamFileSink" },
|
|
1102
|
+
otel: { module: "@logtape/otel", factory: "getOpenTelemetrySink" },
|
|
1103
|
+
sentry: { module: "@logtape/sentry", factory: "getSentrySink" },
|
|
1104
|
+
syslog: { module: "@logtape/syslog", factory: "getSyslogSink" },
|
|
1105
|
+
cloudwatch: {
|
|
1106
|
+
module: "@logtape/cloudwatch-logs",
|
|
1107
|
+
factory: "getCloudWatchLogsSink"
|
|
897
1108
|
}
|
|
898
|
-
}
|
|
899
|
-
async function
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
if (!caches || !buckets) {
|
|
913
|
-
throw new Error("Runtime not initialized - missing caches or buckets");
|
|
914
|
-
}
|
|
915
|
-
scope = new ServiceWorkerGlobals({
|
|
916
|
-
registration,
|
|
917
|
-
caches,
|
|
918
|
-
buckets
|
|
919
|
-
});
|
|
920
|
-
scope.install();
|
|
921
|
-
_workerSelf = scope;
|
|
922
|
-
currentApp = null;
|
|
923
|
-
serviceWorkerReady = false;
|
|
924
|
-
}
|
|
925
|
-
if (loadedEntrypoint === entrypoint) {
|
|
926
|
-
logger.info("[Worker] ServiceWorker already loaded for entrypoint", {
|
|
927
|
-
entrypoint
|
|
928
|
-
});
|
|
929
|
-
return;
|
|
930
|
-
}
|
|
931
|
-
const appModule = await import(entrypoint);
|
|
932
|
-
loadedEntrypoint = entrypoint;
|
|
933
|
-
currentApp = appModule;
|
|
934
|
-
if (!registration) {
|
|
935
|
-
throw new Error("ServiceWorker runtime not initialized");
|
|
936
|
-
}
|
|
937
|
-
await registration.install();
|
|
938
|
-
await registration.activate();
|
|
939
|
-
serviceWorkerReady = true;
|
|
940
|
-
logger.info(
|
|
941
|
-
`[Worker] ServiceWorker loaded and activated from ${entrypoint}`
|
|
1109
|
+
};
|
|
1110
|
+
async function createSink(config) {
|
|
1111
|
+
const { provider, factory: preImportedFactory, ...sinkOptions } = config;
|
|
1112
|
+
if (preImportedFactory) {
|
|
1113
|
+
return preImportedFactory(sinkOptions);
|
|
1114
|
+
}
|
|
1115
|
+
const builtin = BUILTIN_SINK_PROVIDERS[provider];
|
|
1116
|
+
const modulePath = builtin?.module || provider;
|
|
1117
|
+
const factoryName = builtin?.factory || "default";
|
|
1118
|
+
const module = await import(modulePath);
|
|
1119
|
+
const factory = module[factoryName] || module.default;
|
|
1120
|
+
if (!factory) {
|
|
1121
|
+
throw new Error(
|
|
1122
|
+
`Sink module "${modulePath}" has no export "${factoryName}"`
|
|
942
1123
|
);
|
|
943
|
-
} catch (error) {
|
|
944
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
945
|
-
const errorStack = error instanceof Error ? error.stack : void 0;
|
|
946
|
-
logger.error("[Worker] Failed to load ServiceWorker", {
|
|
947
|
-
error: errorMessage,
|
|
948
|
-
stack: errorStack,
|
|
949
|
-
entrypoint
|
|
950
|
-
});
|
|
951
|
-
serviceWorkerReady = false;
|
|
952
|
-
throw error;
|
|
953
1124
|
}
|
|
1125
|
+
return factory(sinkOptions);
|
|
954
1126
|
}
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
1127
|
+
async function configureLogging(loggingConfig, options = {}) {
|
|
1128
|
+
const level = loggingConfig.level || "info";
|
|
1129
|
+
const defaultSinkConfigs = loggingConfig.sinks || [{ provider: "console" }];
|
|
1130
|
+
const categories = loggingConfig.categories || {};
|
|
1131
|
+
const reset = options.reset !== false;
|
|
1132
|
+
const sinkByKey = /* @__PURE__ */ new Map();
|
|
1133
|
+
for (const config of defaultSinkConfigs) {
|
|
1134
|
+
const key = JSON.stringify(config);
|
|
1135
|
+
if (!sinkByKey.has(key)) {
|
|
1136
|
+
sinkByKey.set(key, { config, name: `sink_${sinkByKey.size}` });
|
|
961
1137
|
}
|
|
962
|
-
logger.info(`[Worker-${workerId}] Initializing runtime with config`, {
|
|
963
|
-
config,
|
|
964
|
-
baseDir
|
|
965
|
-
});
|
|
966
|
-
logger.info(`[Worker-${workerId}] Creating cache storage`);
|
|
967
|
-
caches = new CustomCacheStorage(createCacheFactory({ config }));
|
|
968
|
-
logger.info(`[Worker-${workerId}] Creating bucket storage`);
|
|
969
|
-
buckets = new CustomBucketStorage(createBucketFactory({ baseDir, config }));
|
|
970
|
-
logger.info(`[Worker-${workerId}] Creating and installing scope`);
|
|
971
|
-
registration = new ShovelServiceWorkerRegistration();
|
|
972
|
-
scope = new ServiceWorkerGlobals({ registration, caches, buckets });
|
|
973
|
-
scope.install();
|
|
974
|
-
_workerSelf = scope;
|
|
975
|
-
logger.info(`[Worker-${workerId}] Runtime initialized successfully`);
|
|
976
|
-
} catch (error) {
|
|
977
|
-
logger.error(`[Worker-${workerId}] Failed to initialize runtime`, { error });
|
|
978
|
-
throw error;
|
|
979
1138
|
}
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
logger.info(`[Worker-${workerId}] Sending initialized message`);
|
|
988
|
-
sendMessage({ type: "initialized" });
|
|
989
|
-
} else if (message.type === "load") {
|
|
990
|
-
const loadMsg = message;
|
|
991
|
-
await loadServiceWorker(loadMsg.entrypoint);
|
|
992
|
-
sendMessage({ type: "ready", entrypoint: loadMsg.entrypoint });
|
|
993
|
-
} else if (message.type === "request") {
|
|
994
|
-
const reqMsg = message;
|
|
995
|
-
const request = new Request(reqMsg.request.url, {
|
|
996
|
-
method: reqMsg.request.method,
|
|
997
|
-
headers: reqMsg.request.headers,
|
|
998
|
-
body: reqMsg.request.body
|
|
999
|
-
});
|
|
1000
|
-
const response = await handleFetchEvent(request);
|
|
1001
|
-
const body = await response.arrayBuffer();
|
|
1002
|
-
const headers = Object.fromEntries(response.headers.entries());
|
|
1003
|
-
if (!headers["Content-Type"] && !headers["content-type"]) {
|
|
1004
|
-
headers["Content-Type"] = "text/plain; charset=utf-8";
|
|
1139
|
+
for (const [_, categoryConfig] of Object.entries(categories)) {
|
|
1140
|
+
if (categoryConfig.sinks) {
|
|
1141
|
+
for (const config of categoryConfig.sinks) {
|
|
1142
|
+
const key = JSON.stringify(config);
|
|
1143
|
+
if (!sinkByKey.has(key)) {
|
|
1144
|
+
sinkByKey.set(key, { config, name: `sink_${sinkByKey.size}` });
|
|
1145
|
+
}
|
|
1005
1146
|
}
|
|
1006
|
-
const responseMsg = {
|
|
1007
|
-
type: "response",
|
|
1008
|
-
response: {
|
|
1009
|
-
status: response.status,
|
|
1010
|
-
statusText: response.statusText,
|
|
1011
|
-
headers,
|
|
1012
|
-
body
|
|
1013
|
-
},
|
|
1014
|
-
requestID: reqMsg.requestID
|
|
1015
|
-
};
|
|
1016
|
-
sendMessage(responseMsg, [body]);
|
|
1017
1147
|
}
|
|
1018
|
-
} catch (error) {
|
|
1019
|
-
const errorMsg = {
|
|
1020
|
-
type: "error",
|
|
1021
|
-
error: error instanceof Error ? error.message : String(error),
|
|
1022
|
-
stack: error instanceof Error ? error.stack : void 0,
|
|
1023
|
-
requestID: message.requestID
|
|
1024
|
-
};
|
|
1025
|
-
sendMessage(errorMsg);
|
|
1026
1148
|
}
|
|
1027
|
-
}
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1149
|
+
const sinks = {};
|
|
1150
|
+
for (const { config, name } of sinkByKey.values()) {
|
|
1151
|
+
sinks[name] = await createSink(config);
|
|
1152
|
+
}
|
|
1153
|
+
const getSinkNames = (configs) => {
|
|
1154
|
+
return configs.map((config) => sinkByKey.get(JSON.stringify(config))?.name ?? "").filter(Boolean);
|
|
1155
|
+
};
|
|
1156
|
+
const defaultSinkNames = getSinkNames(defaultSinkConfigs);
|
|
1157
|
+
const loggers = SHOVEL_CATEGORIES.map((category) => {
|
|
1158
|
+
const categoryConfig = categories[category];
|
|
1159
|
+
const categoryLevel = categoryConfig?.level || level;
|
|
1160
|
+
const categorySinks = categoryConfig?.sinks ? getSinkNames(categoryConfig.sinks) : defaultSinkNames;
|
|
1161
|
+
return {
|
|
1162
|
+
category: [category],
|
|
1163
|
+
level: categoryLevel,
|
|
1164
|
+
sinks: categorySinks
|
|
1165
|
+
};
|
|
1166
|
+
});
|
|
1167
|
+
loggers.push({
|
|
1168
|
+
category: ["logtape", "meta"],
|
|
1169
|
+
level: "warning",
|
|
1170
|
+
sinks: []
|
|
1171
|
+
});
|
|
1172
|
+
await configure({
|
|
1173
|
+
reset,
|
|
1174
|
+
sinks,
|
|
1175
|
+
loggers
|
|
1034
1176
|
});
|
|
1035
1177
|
}
|
|
1036
1178
|
export {
|
|
1037
1179
|
ActivateEvent,
|
|
1180
|
+
CustomLoggerStorage,
|
|
1038
1181
|
DedicatedWorkerGlobalScope,
|
|
1039
1182
|
ExtendableEvent,
|
|
1040
1183
|
ExtendableMessageEvent,
|
|
@@ -1043,6 +1186,7 @@ export {
|
|
|
1043
1186
|
Notification,
|
|
1044
1187
|
NotificationEvent,
|
|
1045
1188
|
PushEvent,
|
|
1189
|
+
RequestCookieStore,
|
|
1046
1190
|
ServiceWorkerGlobals,
|
|
1047
1191
|
ShovelClient,
|
|
1048
1192
|
ShovelClients,
|
|
@@ -1053,5 +1197,9 @@ export {
|
|
|
1053
1197
|
ShovelServiceWorkerRegistration,
|
|
1054
1198
|
ShovelWindowClient,
|
|
1055
1199
|
SyncEvent,
|
|
1056
|
-
WorkerGlobalScope
|
|
1200
|
+
WorkerGlobalScope,
|
|
1201
|
+
configureLogging,
|
|
1202
|
+
parseCookieHeader,
|
|
1203
|
+
parseSetCookieHeader,
|
|
1204
|
+
serializeCookie
|
|
1057
1205
|
};
|