@b9g/platform 0.1.2 → 0.1.4
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 +18 -2
- package/src/directory-storage.d.ts +4 -0
- package/src/directory-storage.js +6 -0
- package/src/index.d.ts +1 -0
- package/src/index.js +2 -0
- package/src/service-worker.d.ts +56 -30
- package/src/service-worker.js +144 -82
- package/src/worker-pool.d.ts +103 -0
- package/src/worker-pool.js +271 -0
- package/src/worker-web.js +119 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@b9g/platform",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "Base Platform interface and types for Shovel deployment adapters",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"shovel",
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"@types/wicg-file-system-access": "^2023.10.7"
|
|
14
14
|
},
|
|
15
15
|
"devDependencies": {
|
|
16
|
-
"@b9g/libuild": "^0.1.
|
|
16
|
+
"@b9g/libuild": "^0.1.11",
|
|
17
17
|
"bun-types": "latest"
|
|
18
18
|
},
|
|
19
19
|
"type": "module",
|
|
@@ -104,6 +104,22 @@
|
|
|
104
104
|
"./directory-storage.js": {
|
|
105
105
|
"types": "./src/directory-storage.d.ts",
|
|
106
106
|
"import": "./src/directory-storage.js"
|
|
107
|
+
},
|
|
108
|
+
"./worker-pool": {
|
|
109
|
+
"types": "./src/worker-pool.d.ts",
|
|
110
|
+
"import": "./src/worker-pool.js"
|
|
111
|
+
},
|
|
112
|
+
"./worker-pool.js": {
|
|
113
|
+
"types": "./src/worker-pool.d.ts",
|
|
114
|
+
"import": "./src/worker-pool.js"
|
|
115
|
+
},
|
|
116
|
+
"./worker-web": {
|
|
117
|
+
"types": "./src/undefined.d.ts",
|
|
118
|
+
"import": "./src/worker-web.js"
|
|
119
|
+
},
|
|
120
|
+
"./worker-web.js": {
|
|
121
|
+
"types": "./src/undefined.d.ts",
|
|
122
|
+
"import": "./src/worker-web.js"
|
|
107
123
|
}
|
|
108
124
|
}
|
|
109
125
|
}
|
|
@@ -30,6 +30,10 @@ export declare class PlatformBucketStorage implements BucketStorageInterface {
|
|
|
30
30
|
* List all available bucket names
|
|
31
31
|
*/
|
|
32
32
|
keys(): Promise<string[]>;
|
|
33
|
+
/**
|
|
34
|
+
* Alias for open() - for compatibility with File System Access API naming
|
|
35
|
+
*/
|
|
36
|
+
getDirectoryHandle(name: string): Promise<FileSystemDirectoryHandle>;
|
|
33
37
|
}
|
|
34
38
|
/**
|
|
35
39
|
* Create a BucketStorage instance from a root path
|
package/src/directory-storage.js
CHANGED
|
@@ -37,6 +37,12 @@ var PlatformBucketStorage = class {
|
|
|
37
37
|
async keys() {
|
|
38
38
|
return await this.buckets.keys();
|
|
39
39
|
}
|
|
40
|
+
/**
|
|
41
|
+
* Alias for open() - for compatibility with File System Access API naming
|
|
42
|
+
*/
|
|
43
|
+
async getDirectoryHandle(name) {
|
|
44
|
+
return await this.open(name);
|
|
45
|
+
}
|
|
40
46
|
};
|
|
41
47
|
function createBucketStorage(rootPath = "./dist") {
|
|
42
48
|
return new PlatformBucketStorage(rootPath);
|
package/src/index.d.ts
CHANGED
|
@@ -12,3 +12,4 @@ export { platformRegistry, detectPlatform, getPlatform, getPlatformAsync, } from
|
|
|
12
12
|
export { detectRuntime, detectDevelopmentPlatform, detectPlatforms, getBestPlatformDetection, resolvePlatform, createPlatform, displayPlatformInfo, } from "./detection.js";
|
|
13
13
|
export { parseTTL, mergeCacheConfig, validateCacheConfig, createCorsHeaders, mergeHeaders, isPreflightRequest, createPreflightResponse, } from "./utils.js";
|
|
14
14
|
export { getDirectoryHandle, getBucket, getFileSystemRoot } from "./filesystem.js";
|
|
15
|
+
export { WorkerPool, type WorkerFactory, type PlatformWorker, type WorkerPoolOptions } from "./worker-pool.js";
|
package/src/index.js
CHANGED
|
@@ -34,10 +34,12 @@ import {
|
|
|
34
34
|
createPreflightResponse
|
|
35
35
|
} from "./utils.js";
|
|
36
36
|
import { getDirectoryHandle, getBucket, getFileSystemRoot } from "./filesystem.js";
|
|
37
|
+
import { WorkerPool } from "./worker-pool.js";
|
|
37
38
|
export {
|
|
38
39
|
BasePlatform,
|
|
39
40
|
PlatformBucketStorage,
|
|
40
41
|
ServiceWorkerRuntime,
|
|
42
|
+
WorkerPool,
|
|
41
43
|
createBucketStorage,
|
|
42
44
|
createCorsHeaders,
|
|
43
45
|
createPlatform,
|
package/src/service-worker.d.ts
CHANGED
|
@@ -3,39 +3,56 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Provides ServiceWorker APIs (self, addEventListener, etc.) in any JavaScript runtime
|
|
5
5
|
*/
|
|
6
|
+
/**
|
|
7
|
+
* ExtendableEvent base class following ServiceWorker spec
|
|
8
|
+
*/
|
|
9
|
+
export declare class ExtendableEvent extends Event {
|
|
10
|
+
private promises;
|
|
11
|
+
private pendingPromises;
|
|
12
|
+
constructor(type: string, pendingPromises: Set<Promise<any>>);
|
|
13
|
+
waitUntil(promise: Promise<any>): void;
|
|
14
|
+
getPromises(): Promise<any>[];
|
|
15
|
+
}
|
|
6
16
|
/**
|
|
7
17
|
* ServiceWorker-style fetch event
|
|
8
18
|
*/
|
|
9
|
-
export
|
|
10
|
-
readonly type: "fetch";
|
|
19
|
+
export declare class FetchEvent extends ExtendableEvent {
|
|
11
20
|
readonly request: Request;
|
|
21
|
+
private responsePromise;
|
|
22
|
+
private responded;
|
|
23
|
+
constructor(request: Request, pendingPromises: Set<Promise<any>>);
|
|
12
24
|
respondWith(response: Response | Promise<Response>): void;
|
|
13
|
-
|
|
25
|
+
getResponse(): Promise<Response> | null;
|
|
26
|
+
hasResponded(): boolean;
|
|
14
27
|
}
|
|
15
28
|
/**
|
|
16
29
|
* ServiceWorker-style install event
|
|
17
30
|
*/
|
|
18
|
-
export
|
|
19
|
-
|
|
20
|
-
waitUntil(promise: Promise<any>): void;
|
|
31
|
+
export declare class InstallEvent extends ExtendableEvent {
|
|
32
|
+
constructor(pendingPromises: Set<Promise<any>>);
|
|
21
33
|
}
|
|
22
34
|
/**
|
|
23
35
|
* ServiceWorker-style activate event
|
|
24
36
|
*/
|
|
25
|
-
export
|
|
26
|
-
|
|
27
|
-
waitUntil(promise: Promise<any>): void;
|
|
37
|
+
export declare class ActivateEvent extends ExtendableEvent {
|
|
38
|
+
constructor(pendingPromises: Set<Promise<any>>);
|
|
28
39
|
}
|
|
29
40
|
/**
|
|
30
|
-
*
|
|
41
|
+
* Legacy interfaces for backward compatibility
|
|
31
42
|
*/
|
|
32
|
-
export interface
|
|
33
|
-
readonly type: "
|
|
34
|
-
readonly
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
43
|
+
export interface ShovelFetchEvent extends Event {
|
|
44
|
+
readonly type: "fetch";
|
|
45
|
+
readonly request: Request;
|
|
46
|
+
respondWith(response: Response | Promise<Response>): void;
|
|
47
|
+
waitUntil(promise: Promise<any>): void;
|
|
48
|
+
}
|
|
49
|
+
export interface ShovelInstallEvent extends Event {
|
|
50
|
+
readonly type: "install";
|
|
51
|
+
waitUntil(promise: Promise<any>): void;
|
|
52
|
+
}
|
|
53
|
+
export interface ShovelActivateEvent extends Event {
|
|
54
|
+
readonly type: "activate";
|
|
55
|
+
waitUntil(promise: Promise<any>): void;
|
|
39
56
|
}
|
|
40
57
|
/**
|
|
41
58
|
* ServiceWorker runtime that can be embedded in any platform
|
|
@@ -44,7 +61,10 @@ export declare class ServiceWorkerRuntime extends EventTarget {
|
|
|
44
61
|
private pendingPromises;
|
|
45
62
|
private isInstalled;
|
|
46
63
|
private isActivated;
|
|
64
|
+
private eventListeners;
|
|
47
65
|
constructor();
|
|
66
|
+
addEventListener(type: string, listener: Function): void;
|
|
67
|
+
removeEventListener(type: string, listener: Function): void;
|
|
48
68
|
/**
|
|
49
69
|
* Create a fetch event and dispatch it
|
|
50
70
|
*/
|
|
@@ -57,10 +77,6 @@ export declare class ServiceWorkerRuntime extends EventTarget {
|
|
|
57
77
|
* Activate the ServiceWorker
|
|
58
78
|
*/
|
|
59
79
|
activate(): Promise<void>;
|
|
60
|
-
/**
|
|
61
|
-
* Collect static routes for pre-rendering
|
|
62
|
-
*/
|
|
63
|
-
collectStaticRoutes(outDir: string, baseUrl?: string): Promise<string[]>;
|
|
64
80
|
/**
|
|
65
81
|
* Check if ready to handle requests
|
|
66
82
|
*/
|
|
@@ -84,6 +100,10 @@ export interface BucketStorage {
|
|
|
84
100
|
* Well-known names: 'assets', 'static', 'uploads', 'temp'
|
|
85
101
|
*/
|
|
86
102
|
open(name: string): Promise<FileSystemDirectoryHandle>;
|
|
103
|
+
/**
|
|
104
|
+
* Alias for open() - for compatibility with File System Access API naming
|
|
105
|
+
*/
|
|
106
|
+
getDirectoryHandle(name: string): Promise<FileSystemDirectoryHandle>;
|
|
87
107
|
/**
|
|
88
108
|
* Check if a named bucket exists
|
|
89
109
|
*/
|
|
@@ -103,16 +123,9 @@ export interface BucketStorage {
|
|
|
103
123
|
export declare function createServiceWorkerGlobals(runtime: ServiceWorkerRuntime, options?: {
|
|
104
124
|
caches?: any;
|
|
105
125
|
buckets?: BucketStorage;
|
|
126
|
+
isDevelopment?: boolean;
|
|
127
|
+
hotReload?: () => Promise<void>;
|
|
106
128
|
}): {
|
|
107
|
-
self: ServiceWorkerRuntime;
|
|
108
|
-
addEventListener: any;
|
|
109
|
-
removeEventListener: any;
|
|
110
|
-
dispatchEvent: any;
|
|
111
|
-
skipWaiting: () => Promise<void>;
|
|
112
|
-
clients: {
|
|
113
|
-
claim: () => Promise<void>;
|
|
114
|
-
matchAll: () => Promise<any[]>;
|
|
115
|
-
};
|
|
116
129
|
console: Console;
|
|
117
130
|
setTimeout: typeof setTimeout;
|
|
118
131
|
clearTimeout: typeof clearTimeout;
|
|
@@ -146,4 +159,17 @@ export declare function createServiceWorkerGlobals(runtime: ServiceWorkerRuntime
|
|
|
146
159
|
new (init?: string[][] | Record<string, string> | string | URLSearchParams): URLSearchParams;
|
|
147
160
|
prototype: URLSearchParams;
|
|
148
161
|
};
|
|
162
|
+
caches: any;
|
|
163
|
+
buckets: BucketStorage;
|
|
164
|
+
self: ServiceWorkerRuntime;
|
|
165
|
+
addEventListener: any;
|
|
166
|
+
removeEventListener: any;
|
|
167
|
+
dispatchEvent: any;
|
|
168
|
+
skipWaiting: () => Promise<void>;
|
|
169
|
+
clients: {
|
|
170
|
+
claim(): Promise<void>;
|
|
171
|
+
get(id: string): Promise<any>;
|
|
172
|
+
matchAll(options?: any): Promise<any[]>;
|
|
173
|
+
openWindow(url: string | URL): Promise<any>;
|
|
174
|
+
};
|
|
149
175
|
};
|
package/src/service-worker.js
CHANGED
|
@@ -1,12 +1,81 @@
|
|
|
1
1
|
/// <reference types="./service-worker.d.ts" />
|
|
2
2
|
// src/service-worker.ts
|
|
3
|
+
var ExtendableEvent = class extends Event {
|
|
4
|
+
promises = [];
|
|
5
|
+
pendingPromises;
|
|
6
|
+
constructor(type, pendingPromises) {
|
|
7
|
+
super(type);
|
|
8
|
+
this.pendingPromises = pendingPromises;
|
|
9
|
+
}
|
|
10
|
+
waitUntil(promise) {
|
|
11
|
+
this.promises.push(promise);
|
|
12
|
+
this.pendingPromises.add(promise);
|
|
13
|
+
promise.finally(() => this.pendingPromises.delete(promise));
|
|
14
|
+
}
|
|
15
|
+
getPromises() {
|
|
16
|
+
return [...this.promises];
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
var FetchEvent = class extends ExtendableEvent {
|
|
20
|
+
request;
|
|
21
|
+
responsePromise = null;
|
|
22
|
+
responded = false;
|
|
23
|
+
constructor(request, pendingPromises) {
|
|
24
|
+
super("fetch", pendingPromises);
|
|
25
|
+
this.request = request;
|
|
26
|
+
}
|
|
27
|
+
respondWith(response) {
|
|
28
|
+
if (this.responded) {
|
|
29
|
+
throw new Error("respondWith() already called");
|
|
30
|
+
}
|
|
31
|
+
this.responded = true;
|
|
32
|
+
this.responsePromise = Promise.resolve(response);
|
|
33
|
+
}
|
|
34
|
+
getResponse() {
|
|
35
|
+
return this.responsePromise;
|
|
36
|
+
}
|
|
37
|
+
hasResponded() {
|
|
38
|
+
return this.responded;
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
var InstallEvent = class extends ExtendableEvent {
|
|
42
|
+
constructor(pendingPromises) {
|
|
43
|
+
super("install", pendingPromises);
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
var ActivateEvent = class extends ExtendableEvent {
|
|
47
|
+
constructor(pendingPromises) {
|
|
48
|
+
super("activate", pendingPromises);
|
|
49
|
+
}
|
|
50
|
+
};
|
|
3
51
|
var ServiceWorkerRuntime = class extends EventTarget {
|
|
4
52
|
pendingPromises = /* @__PURE__ */ new Set();
|
|
5
53
|
isInstalled = false;
|
|
6
54
|
isActivated = false;
|
|
55
|
+
eventListeners = /* @__PURE__ */ new Map();
|
|
7
56
|
constructor() {
|
|
8
57
|
super();
|
|
9
58
|
}
|
|
59
|
+
addEventListener(type, listener) {
|
|
60
|
+
super.addEventListener(type, listener);
|
|
61
|
+
if (!this.eventListeners.has(type)) {
|
|
62
|
+
this.eventListeners.set(type, []);
|
|
63
|
+
}
|
|
64
|
+
this.eventListeners.get(type).push(listener);
|
|
65
|
+
}
|
|
66
|
+
removeEventListener(type, listener) {
|
|
67
|
+
super.removeEventListener(type, listener);
|
|
68
|
+
if (this.eventListeners.has(type)) {
|
|
69
|
+
const listeners = this.eventListeners.get(type);
|
|
70
|
+
const index = listeners.indexOf(listener);
|
|
71
|
+
if (index > -1) {
|
|
72
|
+
listeners.splice(index, 1);
|
|
73
|
+
if (listeners.length === 0) {
|
|
74
|
+
this.eventListeners.delete(type);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
10
79
|
/**
|
|
11
80
|
* Create a fetch event and dispatch it
|
|
12
81
|
*/
|
|
@@ -15,28 +84,22 @@ var ServiceWorkerRuntime = class extends EventTarget {
|
|
|
15
84
|
throw new Error("ServiceWorker not activated");
|
|
16
85
|
}
|
|
17
86
|
return new Promise((resolve, reject) => {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
throw new Error("respondWith() already called");
|
|
25
|
-
}
|
|
26
|
-
responded = true;
|
|
27
|
-
Promise.resolve(response).then(resolve).catch(reject);
|
|
28
|
-
},
|
|
29
|
-
waitUntil: (promise) => {
|
|
30
|
-
promises.push(promise);
|
|
31
|
-
this.pendingPromises.add(promise);
|
|
32
|
-
promise.finally(() => this.pendingPromises.delete(promise));
|
|
87
|
+
const event = new FetchEvent(request, this.pendingPromises);
|
|
88
|
+
process.nextTick(() => {
|
|
89
|
+
this.dispatchEvent(event);
|
|
90
|
+
const promises = event.getPromises();
|
|
91
|
+
if (promises.length > 0) {
|
|
92
|
+
Promise.allSettled(promises).catch(console.error);
|
|
33
93
|
}
|
|
34
94
|
});
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
95
|
+
setTimeout(() => {
|
|
96
|
+
if (event.hasResponded()) {
|
|
97
|
+
const responsePromise = event.getResponse();
|
|
98
|
+
responsePromise.then(resolve).catch(reject);
|
|
99
|
+
} else {
|
|
100
|
+
reject(new Error("No response provided for fetch event"));
|
|
101
|
+
}
|
|
102
|
+
}, 0);
|
|
40
103
|
});
|
|
41
104
|
}
|
|
42
105
|
/**
|
|
@@ -46,22 +109,20 @@ var ServiceWorkerRuntime = class extends EventTarget {
|
|
|
46
109
|
if (this.isInstalled)
|
|
47
110
|
return;
|
|
48
111
|
return new Promise((resolve, reject) => {
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
this.pendingPromises.add(promise);
|
|
55
|
-
promise.finally(() => this.pendingPromises.delete(promise));
|
|
56
|
-
}
|
|
57
|
-
});
|
|
58
|
-
this.dispatchEvent(event);
|
|
59
|
-
Promise.allSettled(promises).then(() => {
|
|
60
|
-
if (!installCancelled) {
|
|
112
|
+
const event = new InstallEvent(this.pendingPromises);
|
|
113
|
+
process.nextTick(() => {
|
|
114
|
+
this.dispatchEvent(event);
|
|
115
|
+
const promises = event.getPromises();
|
|
116
|
+
if (promises.length === 0) {
|
|
61
117
|
this.isInstalled = true;
|
|
62
118
|
resolve();
|
|
119
|
+
} else {
|
|
120
|
+
Promise.all(promises).then(() => {
|
|
121
|
+
this.isInstalled = true;
|
|
122
|
+
resolve();
|
|
123
|
+
}).catch(reject);
|
|
63
124
|
}
|
|
64
|
-
})
|
|
125
|
+
});
|
|
65
126
|
});
|
|
66
127
|
}
|
|
67
128
|
/**
|
|
@@ -74,46 +135,20 @@ var ServiceWorkerRuntime = class extends EventTarget {
|
|
|
74
135
|
if (this.isActivated)
|
|
75
136
|
return;
|
|
76
137
|
return new Promise((resolve, reject) => {
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
}).catch(reject);
|
|
90
|
-
});
|
|
91
|
-
}
|
|
92
|
-
/**
|
|
93
|
-
* Collect static routes for pre-rendering
|
|
94
|
-
*/
|
|
95
|
-
async collectStaticRoutes(outDir, baseUrl) {
|
|
96
|
-
return new Promise((resolve, reject) => {
|
|
97
|
-
let routes = [];
|
|
98
|
-
const promises = [];
|
|
99
|
-
const event = Object.assign(new Event("static"), {
|
|
100
|
-
detail: { outDir, baseUrl },
|
|
101
|
-
waitUntil: (promise) => {
|
|
102
|
-
promises.push(
|
|
103
|
-
promise.then((routeList) => {
|
|
104
|
-
routes = routes.concat(routeList);
|
|
105
|
-
})
|
|
106
|
-
);
|
|
107
|
-
this.pendingPromises.add(promise);
|
|
108
|
-
promise.finally(() => this.pendingPromises.delete(promise));
|
|
138
|
+
const event = new ActivateEvent(this.pendingPromises);
|
|
139
|
+
process.nextTick(() => {
|
|
140
|
+
this.dispatchEvent(event);
|
|
141
|
+
const promises = event.getPromises();
|
|
142
|
+
if (promises.length === 0) {
|
|
143
|
+
this.isActivated = true;
|
|
144
|
+
resolve();
|
|
145
|
+
} else {
|
|
146
|
+
Promise.all(promises).then(() => {
|
|
147
|
+
this.isActivated = true;
|
|
148
|
+
resolve();
|
|
149
|
+
}).catch(reject);
|
|
109
150
|
}
|
|
110
151
|
});
|
|
111
|
-
this.dispatchEvent(event);
|
|
112
|
-
if (promises.length === 0) {
|
|
113
|
-
resolve([]);
|
|
114
|
-
} else {
|
|
115
|
-
Promise.allSettled(promises).then(() => resolve([...new Set(routes)])).catch(reject);
|
|
116
|
-
}
|
|
117
152
|
});
|
|
118
153
|
}
|
|
119
154
|
/**
|
|
@@ -137,12 +172,12 @@ var ServiceWorkerRuntime = class extends EventTarget {
|
|
|
137
172
|
this.isInstalled = false;
|
|
138
173
|
this.isActivated = false;
|
|
139
174
|
this.pendingPromises.clear();
|
|
140
|
-
const listeners
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
delete listeners[type];
|
|
175
|
+
for (const [type, listeners] of this.eventListeners) {
|
|
176
|
+
for (const listener of listeners) {
|
|
177
|
+
super.removeEventListener(type, listener);
|
|
144
178
|
}
|
|
145
179
|
}
|
|
180
|
+
this.eventListeners.clear();
|
|
146
181
|
}
|
|
147
182
|
};
|
|
148
183
|
function createServiceWorkerGlobals(runtime, options = {}) {
|
|
@@ -152,17 +187,38 @@ function createServiceWorkerGlobals(runtime, options = {}) {
|
|
|
152
187
|
if (options.buckets) {
|
|
153
188
|
runtime.buckets = options.buckets;
|
|
154
189
|
}
|
|
155
|
-
|
|
190
|
+
const skipWaiting = async () => {
|
|
191
|
+
if (options.isDevelopment && options.hotReload) {
|
|
192
|
+
console.info("[ServiceWorker] skipWaiting() - triggering hot reload");
|
|
193
|
+
await options.hotReload();
|
|
194
|
+
} else if (!options.isDevelopment) {
|
|
195
|
+
console.info("[ServiceWorker] skipWaiting() - production graceful restart not implemented");
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
const clients = {
|
|
199
|
+
async claim() {
|
|
200
|
+
},
|
|
201
|
+
async get(id) {
|
|
202
|
+
return void 0;
|
|
203
|
+
},
|
|
204
|
+
async matchAll(options2) {
|
|
205
|
+
return [];
|
|
206
|
+
},
|
|
207
|
+
async openWindow(url) {
|
|
208
|
+
return null;
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
const globals = {
|
|
156
212
|
self: runtime,
|
|
157
213
|
addEventListener: runtime.addEventListener.bind(runtime),
|
|
158
214
|
removeEventListener: runtime.removeEventListener.bind(runtime),
|
|
159
215
|
dispatchEvent: runtime.dispatchEvent.bind(runtime),
|
|
160
|
-
// ServiceWorker-specific globals
|
|
161
|
-
skipWaiting
|
|
162
|
-
clients
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
},
|
|
216
|
+
// ServiceWorker-specific globals with proper implementations
|
|
217
|
+
skipWaiting,
|
|
218
|
+
clients,
|
|
219
|
+
// Platform resources
|
|
220
|
+
...options.buckets && { buckets: options.buckets },
|
|
221
|
+
...options.caches && { caches: options.caches },
|
|
166
222
|
// Standard globals
|
|
167
223
|
console,
|
|
168
224
|
setTimeout,
|
|
@@ -176,8 +232,14 @@ function createServiceWorkerGlobals(runtime, options = {}) {
|
|
|
176
232
|
URL,
|
|
177
233
|
URLSearchParams
|
|
178
234
|
};
|
|
235
|
+
Object.assign(globalThis, globals);
|
|
236
|
+
return globals;
|
|
179
237
|
}
|
|
180
238
|
export {
|
|
239
|
+
ActivateEvent,
|
|
240
|
+
ExtendableEvent,
|
|
241
|
+
FetchEvent,
|
|
242
|
+
InstallEvent,
|
|
181
243
|
ServiceWorkerRuntime,
|
|
182
244
|
createServiceWorkerGlobals
|
|
183
245
|
};
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Common WorkerPool abstraction based on web standards
|
|
3
|
+
* Provides platform-agnostic worker management for ServiceWorker execution
|
|
4
|
+
*/
|
|
5
|
+
import { CustomCacheStorage } from "@b9g/cache";
|
|
6
|
+
export interface WorkerPoolOptions {
|
|
7
|
+
/** Number of workers in the pool (default: 1) */
|
|
8
|
+
workerCount?: number;
|
|
9
|
+
/** Request timeout in milliseconds (default: 30000) */
|
|
10
|
+
requestTimeout?: number;
|
|
11
|
+
/** Enable hot reloading (default: true in development) */
|
|
12
|
+
hotReload?: boolean;
|
|
13
|
+
/** Working directory for file resolution */
|
|
14
|
+
cwd?: string;
|
|
15
|
+
}
|
|
16
|
+
export interface WorkerMessage {
|
|
17
|
+
type: string;
|
|
18
|
+
[key: string]: any;
|
|
19
|
+
}
|
|
20
|
+
export interface WorkerRequest extends WorkerMessage {
|
|
21
|
+
type: "request";
|
|
22
|
+
request: {
|
|
23
|
+
url: string;
|
|
24
|
+
method: string;
|
|
25
|
+
headers: Record<string, string>;
|
|
26
|
+
body?: any;
|
|
27
|
+
};
|
|
28
|
+
requestId: number;
|
|
29
|
+
}
|
|
30
|
+
export interface WorkerResponse extends WorkerMessage {
|
|
31
|
+
type: "response";
|
|
32
|
+
response: {
|
|
33
|
+
status: number;
|
|
34
|
+
statusText: string;
|
|
35
|
+
headers: Record<string, string>;
|
|
36
|
+
body: string;
|
|
37
|
+
};
|
|
38
|
+
requestId: number;
|
|
39
|
+
}
|
|
40
|
+
export interface WorkerLoadMessage extends WorkerMessage {
|
|
41
|
+
type: "load";
|
|
42
|
+
version: number | string;
|
|
43
|
+
entrypoint?: string;
|
|
44
|
+
}
|
|
45
|
+
export interface WorkerReadyMessage extends WorkerMessage {
|
|
46
|
+
type: "ready" | "worker-ready";
|
|
47
|
+
version?: number | string;
|
|
48
|
+
}
|
|
49
|
+
export interface WorkerErrorMessage extends WorkerMessage {
|
|
50
|
+
type: "error";
|
|
51
|
+
error: string;
|
|
52
|
+
stack?: string;
|
|
53
|
+
requestId?: number;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Common WorkerPool implementation based on web standards
|
|
57
|
+
* Provides round-robin request handling, hot reloading, and cache coordination
|
|
58
|
+
*/
|
|
59
|
+
export declare class WorkerPool {
|
|
60
|
+
private workers;
|
|
61
|
+
private currentWorker;
|
|
62
|
+
private requestId;
|
|
63
|
+
private pendingRequests;
|
|
64
|
+
private options;
|
|
65
|
+
private cacheStorage;
|
|
66
|
+
private appEntrypoint?;
|
|
67
|
+
constructor(cacheStorage: CustomCacheStorage, options?: WorkerPoolOptions, appEntrypoint?: string);
|
|
68
|
+
/**
|
|
69
|
+
* Initialize workers (must be called after construction)
|
|
70
|
+
*/
|
|
71
|
+
init(): Promise<void>;
|
|
72
|
+
private initWorkers;
|
|
73
|
+
private createWorker;
|
|
74
|
+
private handleWorkerMessage;
|
|
75
|
+
private handleResponse;
|
|
76
|
+
private handleError;
|
|
77
|
+
private handleReady;
|
|
78
|
+
/**
|
|
79
|
+
* Platform-specific cache message handling
|
|
80
|
+
* Override this method in platform implementations for custom cache coordination
|
|
81
|
+
*/
|
|
82
|
+
protected handleCacheMessage(message: WorkerMessage): void;
|
|
83
|
+
/**
|
|
84
|
+
* Handle HTTP request using round-robin worker selection
|
|
85
|
+
*/
|
|
86
|
+
handleRequest(request: Request): Promise<Response>;
|
|
87
|
+
/**
|
|
88
|
+
* Reload ServiceWorker with new version (hot reload simulation)
|
|
89
|
+
*/
|
|
90
|
+
reloadWorkers(version?: number | string): Promise<void>;
|
|
91
|
+
/**
|
|
92
|
+
* Graceful shutdown of all workers
|
|
93
|
+
*/
|
|
94
|
+
terminate(): Promise<void>;
|
|
95
|
+
/**
|
|
96
|
+
* Get the number of active workers
|
|
97
|
+
*/
|
|
98
|
+
get workerCount(): number;
|
|
99
|
+
/**
|
|
100
|
+
* Check if the pool is ready to handle requests
|
|
101
|
+
*/
|
|
102
|
+
get ready(): boolean;
|
|
103
|
+
}
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
/// <reference types="./worker-pool.d.ts" />
|
|
2
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
3
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
4
|
+
}) : x)(function(x) {
|
|
5
|
+
if (typeof require !== "undefined")
|
|
6
|
+
return require.apply(this, arguments);
|
|
7
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
// src/worker-pool.ts
|
|
11
|
+
import * as Path from "path";
|
|
12
|
+
function resolveWorkerScript(entrypoint) {
|
|
13
|
+
if (entrypoint) {
|
|
14
|
+
const entryDir = Path.dirname(entrypoint);
|
|
15
|
+
const bundledWorker = Path.join(entryDir, "worker.js");
|
|
16
|
+
try {
|
|
17
|
+
if (typeof Bun !== "undefined") {
|
|
18
|
+
const file = Bun.file(bundledWorker);
|
|
19
|
+
if (file.size > 0) {
|
|
20
|
+
console.debug(`[WorkerPool] Using bundled worker: ${bundledWorker}`);
|
|
21
|
+
return bundledWorker;
|
|
22
|
+
}
|
|
23
|
+
} else if (typeof __require !== "undefined") {
|
|
24
|
+
const fs = __require("fs");
|
|
25
|
+
if (fs.existsSync(bundledWorker)) {
|
|
26
|
+
console.debug(`[WorkerPool] Using bundled worker: ${bundledWorker}`);
|
|
27
|
+
return bundledWorker;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
} catch {
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
try {
|
|
34
|
+
const workerUrl = import.meta.resolve("@b9g/platform/worker-web.js");
|
|
35
|
+
let workerScript;
|
|
36
|
+
if (workerUrl.startsWith("file://")) {
|
|
37
|
+
workerScript = workerUrl.slice(7);
|
|
38
|
+
} else {
|
|
39
|
+
workerScript = workerUrl;
|
|
40
|
+
}
|
|
41
|
+
console.debug(`[WorkerPool] Using Web Worker-compatible script: ${workerScript}`);
|
|
42
|
+
return workerScript;
|
|
43
|
+
} catch (error) {
|
|
44
|
+
const bundledPath = entrypoint ? Path.join(Path.dirname(entrypoint), "worker-web.js") : "worker-web.js";
|
|
45
|
+
throw new Error(
|
|
46
|
+
`Could not resolve worker-web.js. Checked bundled path: ${bundledPath} and package: @b9g/platform/worker-web.js. Error: ${error instanceof Error ? error.message : String(error)}`
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
async function createWebWorker(workerScript) {
|
|
51
|
+
if (typeof Worker !== "undefined") {
|
|
52
|
+
return new Worker(workerScript, { type: "module" });
|
|
53
|
+
}
|
|
54
|
+
const isNodeJs = typeof process !== "undefined" && process.versions?.node;
|
|
55
|
+
if (isNodeJs) {
|
|
56
|
+
try {
|
|
57
|
+
const { Worker: NodeWebWorker } = await import("@b9g/node-webworker");
|
|
58
|
+
console.debug("[WorkerPool] Using @b9g/node-webworker shim for Node.js");
|
|
59
|
+
return new NodeWebWorker(workerScript, { type: "module" });
|
|
60
|
+
} catch (shimError) {
|
|
61
|
+
console.error("\n\u274C MISSING WEB STANDARD: Node.js lacks native Web Worker support");
|
|
62
|
+
console.error("\u{1F517} CANONICAL ISSUE: https://github.com/nodejs/node/issues/43583");
|
|
63
|
+
console.error("\u{1F4AC} This is a basic web standard from 2009 - help push for implementation!");
|
|
64
|
+
console.error("\u{1F5F3}\uFE0F Please \u{1F44D} react and comment on the issue to show demand\n");
|
|
65
|
+
throw new Error(`\u274C Web Worker not available on Node.js
|
|
66
|
+
|
|
67
|
+
\u{1F517} Node.js doesn't implement the Web Worker standard yet.
|
|
68
|
+
CANONICAL ISSUE: https://github.com/nodejs/node/issues/43583
|
|
69
|
+
|
|
70
|
+
\u{1F5F3}\uFE0F Please \u{1F44D} react and comment to show demand for this basic web standard!
|
|
71
|
+
|
|
72
|
+
\u{1F4A1} Immediate workaround:
|
|
73
|
+
npm install @b9g/node-webworker
|
|
74
|
+
|
|
75
|
+
This installs our minimal, reliable Web Worker shim for Node.js.
|
|
76
|
+
|
|
77
|
+
\u{1F4DA} Learn more: https://developer.mozilla.org/en-US/docs/Web/API/Worker`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
const runtime = typeof Bun !== "undefined" ? "Bun" : typeof Deno !== "undefined" ? "Deno" : "Unknown";
|
|
81
|
+
throw new Error(`\u274C Web Worker not available on ${runtime}
|
|
82
|
+
|
|
83
|
+
This runtime should support Web Workers but the API is not available.
|
|
84
|
+
Please check your runtime version and configuration.
|
|
85
|
+
|
|
86
|
+
\u{1F4DA} Web Worker standard: https://developer.mozilla.org/en-US/docs/Web/API/Worker`);
|
|
87
|
+
}
|
|
88
|
+
var WorkerPool = class {
|
|
89
|
+
workers = [];
|
|
90
|
+
currentWorker = 0;
|
|
91
|
+
requestId = 0;
|
|
92
|
+
pendingRequests = /* @__PURE__ */ new Map();
|
|
93
|
+
options;
|
|
94
|
+
cacheStorage;
|
|
95
|
+
appEntrypoint;
|
|
96
|
+
constructor(cacheStorage, options = {}, appEntrypoint) {
|
|
97
|
+
this.cacheStorage = cacheStorage;
|
|
98
|
+
this.appEntrypoint = appEntrypoint;
|
|
99
|
+
this.options = {
|
|
100
|
+
workerCount: 1,
|
|
101
|
+
requestTimeout: 3e4,
|
|
102
|
+
hotReload: process.env.NODE_ENV !== "production",
|
|
103
|
+
cwd: process.cwd(),
|
|
104
|
+
...options
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Initialize workers (must be called after construction)
|
|
109
|
+
*/
|
|
110
|
+
async init() {
|
|
111
|
+
await this.initWorkers();
|
|
112
|
+
}
|
|
113
|
+
async initWorkers() {
|
|
114
|
+
for (let i = 0; i < this.options.workerCount; i++) {
|
|
115
|
+
await this.createWorker();
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
async createWorker() {
|
|
119
|
+
const workerScript = resolveWorkerScript(this.appEntrypoint);
|
|
120
|
+
const worker = await createWebWorker(workerScript);
|
|
121
|
+
worker.addEventListener("message", (event) => {
|
|
122
|
+
this.handleWorkerMessage(event.data || event);
|
|
123
|
+
});
|
|
124
|
+
worker.addEventListener("error", (error) => {
|
|
125
|
+
console.error("[WorkerPool] Worker error:", error);
|
|
126
|
+
});
|
|
127
|
+
this.workers.push(worker);
|
|
128
|
+
return worker;
|
|
129
|
+
}
|
|
130
|
+
handleWorkerMessage(message) {
|
|
131
|
+
if (message.type?.startsWith("cache:")) {
|
|
132
|
+
this.handleCacheMessage(message);
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
switch (message.type) {
|
|
136
|
+
case "response":
|
|
137
|
+
this.handleResponse(message);
|
|
138
|
+
break;
|
|
139
|
+
case "error":
|
|
140
|
+
this.handleError(message);
|
|
141
|
+
break;
|
|
142
|
+
case "ready":
|
|
143
|
+
case "worker-ready":
|
|
144
|
+
this.handleReady(message);
|
|
145
|
+
break;
|
|
146
|
+
default:
|
|
147
|
+
console.warn("[WorkerPool] Unknown message type:", message.type);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
handleResponse(message) {
|
|
151
|
+
const pending = this.pendingRequests.get(message.requestId);
|
|
152
|
+
if (pending) {
|
|
153
|
+
const response = new Response(message.response.body, {
|
|
154
|
+
status: message.response.status,
|
|
155
|
+
statusText: message.response.statusText,
|
|
156
|
+
headers: message.response.headers
|
|
157
|
+
});
|
|
158
|
+
pending.resolve(response);
|
|
159
|
+
this.pendingRequests.delete(message.requestId);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
handleError(message) {
|
|
163
|
+
if (message.requestId) {
|
|
164
|
+
const pending = this.pendingRequests.get(message.requestId);
|
|
165
|
+
if (pending) {
|
|
166
|
+
pending.reject(new Error(message.error));
|
|
167
|
+
this.pendingRequests.delete(message.requestId);
|
|
168
|
+
}
|
|
169
|
+
} else {
|
|
170
|
+
console.error("[WorkerPool] Worker error:", message.error);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
handleReady(message) {
|
|
174
|
+
if (message.type === "ready") {
|
|
175
|
+
console.info(`[WorkerPool] ServiceWorker ready (v${message.version})`);
|
|
176
|
+
} else if (message.type === "worker-ready") {
|
|
177
|
+
console.info("[WorkerPool] Worker initialized");
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Platform-specific cache message handling
|
|
182
|
+
* Override this method in platform implementations for custom cache coordination
|
|
183
|
+
*/
|
|
184
|
+
handleCacheMessage(message) {
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Handle HTTP request using round-robin worker selection
|
|
188
|
+
*/
|
|
189
|
+
async handleRequest(request) {
|
|
190
|
+
const worker = this.workers[this.currentWorker];
|
|
191
|
+
console.info(
|
|
192
|
+
`[WorkerPool] Dispatching to worker ${this.currentWorker + 1} of ${this.workers.length}`
|
|
193
|
+
);
|
|
194
|
+
this.currentWorker = (this.currentWorker + 1) % this.workers.length;
|
|
195
|
+
const requestId = ++this.requestId;
|
|
196
|
+
return new Promise((resolve, reject) => {
|
|
197
|
+
this.pendingRequests.set(requestId, { resolve, reject });
|
|
198
|
+
const workerRequest = {
|
|
199
|
+
type: "request",
|
|
200
|
+
request: {
|
|
201
|
+
url: request.url,
|
|
202
|
+
method: request.method,
|
|
203
|
+
headers: Object.fromEntries(request.headers.entries()),
|
|
204
|
+
body: request.body
|
|
205
|
+
},
|
|
206
|
+
requestId
|
|
207
|
+
};
|
|
208
|
+
worker.postMessage(workerRequest);
|
|
209
|
+
setTimeout(() => {
|
|
210
|
+
if (this.pendingRequests.has(requestId)) {
|
|
211
|
+
this.pendingRequests.delete(requestId);
|
|
212
|
+
reject(new Error("Request timeout"));
|
|
213
|
+
}
|
|
214
|
+
}, this.options.requestTimeout);
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Reload ServiceWorker with new version (hot reload simulation)
|
|
219
|
+
*/
|
|
220
|
+
async reloadWorkers(version = Date.now()) {
|
|
221
|
+
console.info(`[WorkerPool] Reloading ServiceWorker (v${version})`);
|
|
222
|
+
const loadPromises = this.workers.map((worker) => {
|
|
223
|
+
return new Promise((resolve) => {
|
|
224
|
+
const handleReady = (event) => {
|
|
225
|
+
const message = event.data || event;
|
|
226
|
+
if (message.type === "ready" && message.version === version) {
|
|
227
|
+
worker.removeEventListener("message", handleReady);
|
|
228
|
+
resolve();
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
console.info("[WorkerPool] Sending load message:", {
|
|
232
|
+
version,
|
|
233
|
+
entrypoint: this.appEntrypoint
|
|
234
|
+
});
|
|
235
|
+
worker.addEventListener("message", handleReady);
|
|
236
|
+
const loadMessage = {
|
|
237
|
+
type: "load",
|
|
238
|
+
version,
|
|
239
|
+
entrypoint: this.appEntrypoint
|
|
240
|
+
};
|
|
241
|
+
worker.postMessage(loadMessage);
|
|
242
|
+
});
|
|
243
|
+
});
|
|
244
|
+
await Promise.all(loadPromises);
|
|
245
|
+
console.info(`[WorkerPool] All workers reloaded (v${version})`);
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Graceful shutdown of all workers
|
|
249
|
+
*/
|
|
250
|
+
async terminate() {
|
|
251
|
+
const terminatePromises = this.workers.map((worker) => worker.terminate());
|
|
252
|
+
await Promise.allSettled(terminatePromises);
|
|
253
|
+
this.workers = [];
|
|
254
|
+
this.pendingRequests.clear();
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Get the number of active workers
|
|
258
|
+
*/
|
|
259
|
+
get workerCount() {
|
|
260
|
+
return this.workers.length;
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Check if the pool is ready to handle requests
|
|
264
|
+
*/
|
|
265
|
+
get ready() {
|
|
266
|
+
return this.workers.length > 0;
|
|
267
|
+
}
|
|
268
|
+
};
|
|
269
|
+
export {
|
|
270
|
+
WorkerPool
|
|
271
|
+
};
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
// src/worker-web.js
|
|
2
|
+
async function initializeWorker() {
|
|
3
|
+
let messagePort = self;
|
|
4
|
+
let sendMessage2 = (message) => postMessage(message);
|
|
5
|
+
onmessage = function(event) {
|
|
6
|
+
handleMessage(event.data);
|
|
7
|
+
};
|
|
8
|
+
return { messagePort, sendMessage: sendMessage2 };
|
|
9
|
+
}
|
|
10
|
+
var { createServiceWorkerGlobals, ServiceWorkerRuntime, createBucketStorage } = await import("./index.js");
|
|
11
|
+
var { CustomCacheStorage, PostMessageCache } = await import("@b9g/cache");
|
|
12
|
+
var caches = new CustomCacheStorage((name) => {
|
|
13
|
+
return new PostMessageCache(name, {
|
|
14
|
+
maxEntries: 1e3,
|
|
15
|
+
maxAge: 60 * 60 * 1e3
|
|
16
|
+
// 1 hour
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
var buckets = createBucketStorage(process.cwd() + "/dist");
|
|
20
|
+
var runtime = new ServiceWorkerRuntime();
|
|
21
|
+
createServiceWorkerGlobals(runtime, { caches, buckets });
|
|
22
|
+
var workerSelf = runtime;
|
|
23
|
+
var currentApp = null;
|
|
24
|
+
var serviceWorkerReady = false;
|
|
25
|
+
var loadedVersion = null;
|
|
26
|
+
async function handleFetchEvent(request) {
|
|
27
|
+
if (!currentApp || !serviceWorkerReady) {
|
|
28
|
+
throw new Error("ServiceWorker not ready");
|
|
29
|
+
}
|
|
30
|
+
try {
|
|
31
|
+
const response = await runtime.handleRequest(request);
|
|
32
|
+
return response;
|
|
33
|
+
} catch (error) {
|
|
34
|
+
console.error("[Worker] ServiceWorker request failed:", error);
|
|
35
|
+
const response = new Response("ServiceWorker request failed", {
|
|
36
|
+
status: 500
|
|
37
|
+
});
|
|
38
|
+
return response;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
async function loadServiceWorker(version, entrypoint) {
|
|
42
|
+
try {
|
|
43
|
+
console.info("[Worker] loadServiceWorker called with:", { version, entrypoint });
|
|
44
|
+
const entrypointPath = process.env.SERVICEWORKER_PATH || entrypoint || `${process.cwd()}/dist/server/app.js`;
|
|
45
|
+
console.info("[Worker] Loading from:", entrypointPath);
|
|
46
|
+
if (loadedVersion !== null && loadedVersion !== version) {
|
|
47
|
+
console.info(`[Worker] Hot reload detected: ${loadedVersion} -> ${version}`);
|
|
48
|
+
console.info("[Worker] Creating completely fresh ServiceWorker context");
|
|
49
|
+
runtime = new ServiceWorkerRuntime();
|
|
50
|
+
createServiceWorkerGlobals(runtime, { caches, buckets });
|
|
51
|
+
workerSelf = runtime;
|
|
52
|
+
currentApp = null;
|
|
53
|
+
serviceWorkerReady = false;
|
|
54
|
+
}
|
|
55
|
+
if (loadedVersion === version) {
|
|
56
|
+
console.info("[Worker] ServiceWorker already loaded for version", version);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
globalThis.self = runtime;
|
|
60
|
+
globalThis.addEventListener = runtime.addEventListener.bind(runtime);
|
|
61
|
+
globalThis.removeEventListener = runtime.removeEventListener.bind(runtime);
|
|
62
|
+
globalThis.dispatchEvent = runtime.dispatchEvent.bind(runtime);
|
|
63
|
+
const appModule = await import(`${entrypointPath}?v=${version}`);
|
|
64
|
+
loadedVersion = version;
|
|
65
|
+
currentApp = appModule;
|
|
66
|
+
await runtime.install();
|
|
67
|
+
await runtime.activate();
|
|
68
|
+
serviceWorkerReady = true;
|
|
69
|
+
console.info(`[Worker] ServiceWorker loaded and activated (v${version}) from ${entrypointPath}`);
|
|
70
|
+
} catch (error) {
|
|
71
|
+
console.error("[Worker] Failed to load ServiceWorker:", error);
|
|
72
|
+
serviceWorkerReady = false;
|
|
73
|
+
throw error;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
var workerId = Math.random().toString(36).substring(2, 8);
|
|
77
|
+
var sendMessage;
|
|
78
|
+
async function handleMessage(message) {
|
|
79
|
+
try {
|
|
80
|
+
if (message.type === "load") {
|
|
81
|
+
await loadServiceWorker(message.version, message.entrypoint);
|
|
82
|
+
sendMessage({ type: "ready", version: message.version });
|
|
83
|
+
} else if (message.type === "request") {
|
|
84
|
+
console.log(`[Worker-${workerId}] Handling request:`, message.request.url);
|
|
85
|
+
const request = new Request(message.request.url, {
|
|
86
|
+
method: message.request.method,
|
|
87
|
+
headers: message.request.headers,
|
|
88
|
+
body: message.request.body
|
|
89
|
+
});
|
|
90
|
+
const response = await handleFetchEvent(request);
|
|
91
|
+
sendMessage({
|
|
92
|
+
type: "response",
|
|
93
|
+
response: {
|
|
94
|
+
status: response.status,
|
|
95
|
+
statusText: response.statusText,
|
|
96
|
+
headers: Object.fromEntries(response.headers.entries()),
|
|
97
|
+
body: await response.text()
|
|
98
|
+
},
|
|
99
|
+
requestId: message.requestId
|
|
100
|
+
});
|
|
101
|
+
} else if (message.type.startsWith("cache:") || message.type.startsWith("cachestorage:")) {
|
|
102
|
+
} else {
|
|
103
|
+
console.warn("[Worker] Unknown message type:", message.type);
|
|
104
|
+
}
|
|
105
|
+
} catch (error) {
|
|
106
|
+
sendMessage({
|
|
107
|
+
type: "error",
|
|
108
|
+
error: error.message,
|
|
109
|
+
stack: error.stack,
|
|
110
|
+
requestId: message.requestId
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
initializeWorker().then(({ messagePort, sendMessage: send }) => {
|
|
115
|
+
sendMessage = send;
|
|
116
|
+
sendMessage({ type: "worker-ready" });
|
|
117
|
+
}).catch((error) => {
|
|
118
|
+
console.error("[Worker] Failed to initialize:", error);
|
|
119
|
+
});
|