@mcp-fe/mcp-worker 0.0.5 → 0.0.7

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.
@@ -1,369 +0,0 @@
1
- type WorkerType = 'shared' | 'service';
2
-
3
- export class WorkerClient {
4
- private serviceWorkerRegistration: ServiceWorkerRegistration | null = null;
5
- private sharedWorker: SharedWorker | null = null;
6
- private sharedWorkerPort: MessagePort | null = null;
7
- private workerType: WorkerType | null = null;
8
- private pendingAuthToken: string | null = null;
9
- // connection status subscribers
10
- private connectionStatusCallbacks: Set<(connected: boolean) => void> = new Set();
11
- private serviceWorkerMessageHandler: ((ev: MessageEvent) => void) | null = null;
12
-
13
- // Mutex/promise to prevent concurrent init runs
14
- private initPromise: Promise<void> | null = null;
15
-
16
- // Initialize and choose worker implementation (prefer SharedWorker)
17
- public async init(registration?: ServiceWorkerRegistration): Promise<void> {
18
- // If an init is already in progress, wait for it and optionally retry if caller provided a registration
19
- if (this.initPromise) {
20
- return this.initPromise.then(async () => {
21
- if (registration && this.workerType !== 'service') {
22
- // retry once with provided registration after current init finished
23
- await this.init(registration);
24
- }
25
- });
26
- }
27
-
28
- // Start initialization and store the promise as a mutex
29
- this.initPromise = (async () => {
30
- try {
31
- // If an explicit ServiceWorker registration is provided, use it
32
- if (registration) {
33
- this.serviceWorkerRegistration = registration;
34
- this.workerType = 'service';
35
- console.log('[WorkerClient] Using ServiceWorker (explicit registration)');
36
- // send pending token if exists
37
- if (this.pendingAuthToken) {
38
- this.sendAuthTokenToServiceWorker(this.pendingAuthToken);
39
- this.pendingAuthToken = null;
40
- }
41
- return;
42
- }
43
-
44
- // Try SharedWorker first
45
- if (typeof SharedWorker !== 'undefined') {
46
- try {
47
- this.sharedWorker = new SharedWorker('/shared-worker.js', { type: 'module' });
48
- this.sharedWorkerPort = this.sharedWorker.port;
49
- this.sharedWorkerPort.start();
50
-
51
- if (this.pendingAuthToken && this.sharedWorkerPort) {
52
- try {
53
- this.sharedWorkerPort.postMessage({ type: 'SET_AUTH_TOKEN', token: this.pendingAuthToken });
54
- } catch (err) {
55
- console.warn('[WorkerClient] Immediate postMessage to SharedWorker failed (will retry after init):', err);
56
- }
57
- }
58
-
59
- this.sharedWorker.onerror = (event: ErrorEvent) => {
60
- console.error('[WorkerClient] SharedWorker error:', event.message || event.error || event);
61
- if (this.workerType !== 'shared') {
62
- this.initServiceWorkerFallback().catch((err) => {
63
- console.error('[WorkerClient] Failed to initialize ServiceWorker fallback:', err);
64
- });
65
- }
66
- };
67
-
68
- await new Promise<void>((resolve, reject) => {
69
- let resolved = false;
70
- const timeout = setTimeout(() => {
71
- if (!resolved) {
72
- const p = this.sharedWorkerPort;
73
- if (p) p.onmessage = null;
74
- reject(new Error('SharedWorker initialization timeout'));
75
- }
76
- }, 2000);
77
-
78
- const p = this.sharedWorkerPort;
79
- if (!p) {
80
- clearTimeout(timeout);
81
- return reject(new Error('SharedWorker port not available'));
82
- }
83
-
84
- p.onmessage = (ev: MessageEvent) => {
85
- try {
86
- const data = ev.data;
87
- if (data && data.type === 'CONNECTION_STATUS') {
88
- clearTimeout(timeout);
89
- resolved = true;
90
- this.workerType = 'shared';
91
- p.onmessage = null;
92
- resolve();
93
- }
94
- } catch {
95
- // ignore parse/handler errors
96
- }
97
- };
98
- });
99
-
100
- const portAfterInit = this.sharedWorkerPort;
101
- if (this.pendingAuthToken && portAfterInit) {
102
- try {
103
- portAfterInit.postMessage({ type: 'SET_AUTH_TOKEN', token: this.pendingAuthToken });
104
- this.pendingAuthToken = null;
105
- } catch (e) {
106
- console.error('[WorkerClient] Failed to send pending auth token to SharedWorker:', e);
107
- }
108
- }
109
-
110
- if (portAfterInit) {
111
- portAfterInit.onmessage = (ev: MessageEvent) => {
112
- try {
113
- const data = ev.data;
114
- if (data && data.type === 'CONNECTION_STATUS') {
115
- const connected = !!data.connected;
116
- this.connectionStatusCallbacks.forEach((cb) => {
117
- try { cb(connected); } catch (e) { /* ignore callback errors */ }
118
- });
119
- }
120
- } catch {
121
- // ignore
122
- }
123
- };
124
- }
125
-
126
- console.log('[WorkerClient] Using SharedWorker');
127
- return;
128
- } catch (error) {
129
- console.warn('[WorkerClient] SharedWorker not available, falling back to ServiceWorker:', error);
130
- }
131
- }
132
-
133
- // If SharedWorker isn't supported or failed, use service worker
134
- console.log("this should not be called");
135
- await this.initServiceWorkerFallback();
136
-
137
- // Send pending token if any
138
- if (this.pendingAuthToken && this.workerType === 'service') {
139
- this.sendAuthTokenToServiceWorker(this.pendingAuthToken);
140
- this.pendingAuthToken = null;
141
- }
142
- } finally {
143
- // Clear the mutex so future init calls can proceed
144
- this.initPromise = null;
145
- }
146
- })();
147
-
148
- return this.initPromise;
149
- }
150
-
151
- private async initServiceWorkerFallback(): Promise<void> {
152
- console.log("initServiceWorkerFallback called");
153
- if ('serviceWorker' in navigator) {
154
- try {
155
- const existingRegistration = await navigator.serviceWorker.getRegistration();
156
- if (existingRegistration) {
157
- this.serviceWorkerRegistration = existingRegistration;
158
- this.workerType = 'service';
159
- console.log('[WorkerClient] Using existing ServiceWorker registration');
160
- return;
161
- }
162
-
163
- const reg = await navigator.serviceWorker.register('/sw.js');
164
- this.serviceWorkerRegistration = reg;
165
- this.workerType = 'service';
166
- console.log('[WorkerClient] Using ServiceWorker (fallback)');
167
- if (this.serviceWorkerMessageHandler) {
168
- navigator.serviceWorker.removeEventListener('message', this.serviceWorkerMessageHandler);
169
- this.serviceWorkerMessageHandler = null;
170
- }
171
- this.serviceWorkerMessageHandler = (ev: MessageEvent) => {
172
- try {
173
- const data = ev.data;
174
- if (data && data.type === 'CONNECTION_STATUS') {
175
- const connected = !!data.connected;
176
- this.connectionStatusCallbacks.forEach((cb) => {
177
- try { cb(connected); } catch (e) { /* ignore callback errors */ }
178
- });
179
- }
180
- } catch {
181
- // ignore
182
- }
183
- };
184
- navigator.serviceWorker.addEventListener('message', this.serviceWorkerMessageHandler);
185
- } catch (error) {
186
- console.error('[WorkerClient] Failed to register ServiceWorker:', error);
187
- throw error;
188
- }
189
- } else {
190
- throw new Error('Neither SharedWorker nor ServiceWorker is supported');
191
- }
192
- }
193
-
194
- // Low-level request that expects a reply via MessageChannel
195
- public async request<T = any>(type: string, payload?: Record<string, unknown>, timeoutMs = 5000): Promise<T> {
196
- // If using shared worker
197
- if (this.workerType === 'shared' && this.sharedWorkerPort) {
198
- return new Promise<T>((resolve, reject) => {
199
- const mc = new MessageChannel();
200
- const timer = setTimeout(() => {
201
- mc.port1.onmessage = null;
202
- reject(new Error('Request timeout'));
203
- }, timeoutMs);
204
-
205
- mc.port1.onmessage = (ev: MessageEvent) => {
206
- clearTimeout(timer);
207
- if (ev.data && ev.data.success) {
208
- resolve(ev.data as T);
209
- } else if (ev.data && ev.data.success === false) {
210
- reject(new Error(ev.data.error || 'Worker error'));
211
- } else {
212
- resolve(ev.data as T);
213
- }
214
- };
215
-
216
- try {
217
- const port = this.sharedWorkerPort;
218
- if (!port) {
219
- clearTimeout(timer);
220
- return reject(new Error('SharedWorker port not available'));
221
- }
222
- port.postMessage({ type, ...(payload || {}) }, [mc.port2]);
223
- } catch (e) {
224
- clearTimeout(timer);
225
- reject(e instanceof Error ? e : new Error(String(e)));
226
- }
227
- });
228
- }
229
-
230
- // If using service worker
231
- if (this.workerType === 'service' && this.serviceWorkerRegistration) {
232
- // Ensure service worker active
233
- const reg = this.serviceWorkerRegistration;
234
- if (!reg) throw new Error('Service worker registration missing');
235
- if (!reg.active) {
236
- await navigator.serviceWorker.ready;
237
- if (!reg.active) {
238
- throw new Error('Service worker not active');
239
- }
240
- }
241
-
242
- return new Promise<T>((resolve, reject) => {
243
- const mc = new MessageChannel();
244
- const timer = setTimeout(() => {
245
- mc.port1.onmessage = null;
246
- reject(new Error('Request timeout'));
247
- }, timeoutMs);
248
-
249
- mc.port1.onmessage = (ev: MessageEvent) => {
250
- clearTimeout(timer);
251
- if (ev.data && ev.data.success) {
252
- resolve(ev.data as T);
253
- } else if (ev.data && ev.data.success === false) {
254
- reject(new Error(ev.data.error || 'Worker error'));
255
- } else {
256
- resolve(ev.data as T);
257
- }
258
- };
259
-
260
- try {
261
- const active = reg.active;
262
- if (!active) {
263
- clearTimeout(timer);
264
- return reject(new Error('Service worker active instance not available'));
265
- }
266
- active.postMessage({ type, ...(payload || {}) }, [mc.port2]);
267
- } catch (e) {
268
- clearTimeout(timer);
269
- reject(e instanceof Error ? e : new Error(String(e)));
270
- }
271
- });
272
- }
273
-
274
- // No worker available
275
- throw new Error('No worker registered');
276
- }
277
-
278
- // Fire-and-forget postMessage (no response expected)
279
- public async post(type: string, payload?: Record<string, unknown>): Promise<void> {
280
- if (this.workerType === 'shared' && this.sharedWorkerPort) {
281
- try {
282
- this.sharedWorkerPort.postMessage({ type, ...(payload || {}) });
283
- } catch (e) {
284
- console.error('[WorkerClient] Failed to post to SharedWorker:', e);
285
- }
286
- return;
287
- }
288
-
289
- if (this.workerType === 'service' && this.serviceWorkerRegistration?.active) {
290
- try {
291
- this.serviceWorkerRegistration.active.postMessage({ type, ...(payload || {}) });
292
- } catch (e) {
293
- console.error('[WorkerClient] Failed to post to ServiceWorker (active):', e);
294
- }
295
- return;
296
- }
297
-
298
- if ('serviceWorker' in navigator && navigator.serviceWorker.controller) {
299
- try {
300
- navigator.serviceWorker.controller.postMessage({ type, ...(payload || {}) });
301
- } catch (e) {
302
- console.error('[WorkerClient] Failed to post to ServiceWorker.controller:', e);
303
- }
304
- return;
305
- }
306
-
307
- // If no worker yet, queue token if SET_AUTH_TOKEN
308
- if (type === 'SET_AUTH_TOKEN' && payload) {
309
- const token = (payload as any)['token'];
310
- if (typeof token === 'string') this.pendingAuthToken = token;
311
- }
312
- }
313
-
314
- private sendAuthTokenToServiceWorker(token: string): void {
315
- if (this.serviceWorkerRegistration?.active) {
316
- try {
317
- this.serviceWorkerRegistration.active.postMessage({ type: 'SET_AUTH_TOKEN', token });
318
- } catch (e) {
319
- console.error('[WorkerClient] Failed to send auth token to ServiceWorker:', e);
320
- }
321
- } else if ('serviceWorker' in navigator && navigator.serviceWorker.controller) {
322
- try {
323
- navigator.serviceWorker.controller.postMessage({ type: 'SET_AUTH_TOKEN', token });
324
- } catch (e) {
325
- console.error('[WorkerClient] Failed to send auth token to ServiceWorker.controller:', e);
326
- }
327
- } else {
328
- // keep as pending
329
- this.pendingAuthToken = token;
330
- }
331
- }
332
-
333
- // Subscription API for consumers to listen for connection status updates
334
- public onConnectionStatus(cb: (connected: boolean) => void): void {
335
- this.connectionStatusCallbacks.add(cb);
336
- }
337
-
338
- public offConnectionStatus(cb: (connected: boolean) => void): void {
339
- this.connectionStatusCallbacks.delete(cb);
340
- }
341
-
342
- public async getConnectionStatus(): Promise<boolean> {
343
- try {
344
- const res = await this.request('GET_CONNECTION_STATUS', undefined, 2000);
345
- if (res && typeof res === 'object' && 'connected' in (res as any)) return !!(res as any).connected;
346
- return !!(res as any).connected;
347
- } catch {
348
- return false;
349
- }
350
- }
351
-
352
- public setAuthToken(token: string): void {
353
- this.pendingAuthToken = token;
354
- // Try to send immediately if possible
355
- if (this.workerType === 'shared' && this.sharedWorkerPort) {
356
- try {
357
- this.sharedWorkerPort.postMessage({ type: 'SET_AUTH_TOKEN', token });
358
- this.pendingAuthToken = null;
359
- } catch (e) {
360
- console.error('[WorkerClient] Failed to set auth token on SharedWorker:', e);
361
- }
362
- } else if (this.workerType === 'service') {
363
- this.sendAuthTokenToServiceWorker(token);
364
- this.pendingAuthToken = null;
365
- } else {
366
- // queued and will be sent when init finishes
367
- }
368
- }
369
- }
package/tsconfig.json DELETED
@@ -1,25 +0,0 @@
1
- {
2
- "extends": "../../tsconfig.base.json",
3
- "compilerOptions": {
4
- "module": "esnext",
5
- "moduleResolution": "bundler",
6
- "forceConsistentCasingInFileNames": true,
7
- "strict": true,
8
- "importHelpers": true,
9
- "noImplicitOverride": true,
10
- "noImplicitReturns": true,
11
- "noFallthroughCasesInSwitch": true,
12
- "noPropertyAccessFromIndexSignature": true,
13
- "lib": ["esnext", "dom", "webworker"]
14
- },
15
- "files": [],
16
- "include": [],
17
- "references": [
18
- {
19
- "path": "./tsconfig.lib.json"
20
- },
21
- {
22
- "path": "./tsconfig.spec.json"
23
- }
24
- ]
25
- }
package/tsconfig.lib.json DELETED
@@ -1,24 +0,0 @@
1
- {
2
- "extends": "./tsconfig.json",
3
- "compilerOptions": {
4
- "baseUrl": ".",
5
- "outDir": "../../dist/out-tsc",
6
- "declaration": true,
7
- "types": ["node", "vite/client"]
8
- },
9
- "include": ["src/**/*.ts"],
10
- "exclude": [
11
- "vite.config.ts",
12
- "vite.config.mts",
13
- "vitest.config.ts",
14
- "vitest.config.mts",
15
- "src/**/*.test.ts",
16
- "src/**/*.spec.ts",
17
- "src/**/*.test.tsx",
18
- "src/**/*.spec.tsx",
19
- "src/**/*.test.js",
20
- "src/**/*.spec.js",
21
- "src/**/*.test.jsx",
22
- "src/**/*.spec.jsx"
23
- ]
24
- }
@@ -1,28 +0,0 @@
1
- {
2
- "extends": "./tsconfig.json",
3
- "compilerOptions": {
4
- "outDir": "../../dist/out-tsc",
5
- "types": [
6
- "vitest/globals",
7
- "vitest/importMeta",
8
- "vite/client",
9
- "node",
10
- "vitest"
11
- ]
12
- },
13
- "include": [
14
- "vite.config.ts",
15
- "vite.config.mts",
16
- "vitest.config.ts",
17
- "vitest.config.mts",
18
- "src/**/*.test.ts",
19
- "src/**/*.spec.ts",
20
- "src/**/*.test.tsx",
21
- "src/**/*.spec.tsx",
22
- "src/**/*.test.js",
23
- "src/**/*.spec.js",
24
- "src/**/*.test.jsx",
25
- "src/**/*.spec.jsx",
26
- "src/**/*.d.ts"
27
- ]
28
- }
package/vite.config.mts DELETED
@@ -1,59 +0,0 @@
1
- /// <reference types='vitest' />
2
- import { defineConfig } from 'vite';
3
- import dts from 'vite-plugin-dts';
4
- import * as path from 'path';
5
- import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
6
- import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin';
7
-
8
- export default defineConfig(() => ({
9
- root: import.meta.dirname,
10
- cacheDir: '../../node_modules/.vite/libs/mcp-worker',
11
- plugins: [
12
- nxViteTsPaths(),
13
- nxCopyAssetsPlugin(['*.md']),
14
- dts({
15
- entryRoot: 'src',
16
- tsconfigPath: path.join(import.meta.dirname, 'tsconfig.lib.json'),
17
- pathsToAliases: false,
18
- }),
19
- ],
20
- // Uncomment this if you are using workers.
21
- // worker: {
22
- // plugins: () => [ nxViteTsPaths() ],
23
- // },
24
- // Configuration for building your library.
25
- // See: https://vite.dev/guide/build.html#library-mode
26
- build: {
27
- outDir: '../../dist/libs/mcp-worker',
28
- emptyOutDir: true,
29
- reportCompressedSize: true,
30
- commonjsOptions: {
31
- transformMixedEsModules: true,
32
- },
33
- lib: {
34
- // Could also be a dictionary or array of multiple entry points.
35
- entry: 'src/index.ts',
36
- name: 'mcp-worker',
37
- fileName: 'index',
38
- // Change this to the formats you want to support.
39
- // Don't forget to update your package.json as well.
40
- formats: ['es' as const],
41
- },
42
- rollupOptions: {
43
- // External packages that should not be bundled into your library.
44
- external: [],
45
- },
46
- },
47
- test: {
48
- name: 'mcp-worker',
49
- watch: false,
50
- globals: true,
51
- environment: 'node',
52
- include: ['{src,tests}/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
53
- reporters: ['default'],
54
- coverage: {
55
- reportsDirectory: '../../coverage/libs/mcp-worker',
56
- provider: 'v8' as const,
57
- },
58
- },
59
- }));