@mcp-fe/event-tracker 0.1.2 → 0.1.3
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/index.js +53 -751
- package/package.json +3 -3
package/index.js
CHANGED
|
@@ -1,774 +1,76 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
},
|
|
5
|
-
debug: (...s) => {
|
|
6
|
-
v && console.debug(...s);
|
|
7
|
-
},
|
|
8
|
-
info: (...s) => {
|
|
9
|
-
console.info(...s);
|
|
10
|
-
},
|
|
11
|
-
error: (...s) => {
|
|
12
|
-
console.error(...s);
|
|
13
|
-
},
|
|
14
|
-
warn: (...s) => {
|
|
15
|
-
v && console.warn(...s);
|
|
16
|
-
}
|
|
17
|
-
};
|
|
18
|
-
class C {
|
|
19
|
-
// Configurable worker script URLs (defaults kept for backward compatibility)
|
|
20
|
-
sharedWorkerUrl = "/mcp-shared-worker.js";
|
|
21
|
-
serviceWorkerUrl = "/mcp-service-worker.js";
|
|
22
|
-
// Backend websocket URL to pass into the worker(s)
|
|
23
|
-
backendWsUrl = "ws://localhost:3001";
|
|
24
|
-
serviceWorkerRegistration = null;
|
|
25
|
-
sharedWorker = null;
|
|
26
|
-
sharedWorkerPort = null;
|
|
27
|
-
workerType = null;
|
|
28
|
-
pendingAuthToken = null;
|
|
29
|
-
// connection status subscribers
|
|
30
|
-
connectionStatusCallbacks = /* @__PURE__ */ new Set();
|
|
31
|
-
// Mutex/promise to prevent concurrent init runs
|
|
32
|
-
initPromise = null;
|
|
33
|
-
// Initialization state
|
|
34
|
-
isInitialized = !1;
|
|
35
|
-
initResolvers = [];
|
|
36
|
-
// Queue for operations that need to wait for initialization
|
|
37
|
-
pendingRegistrations = [];
|
|
38
|
-
// Map to store tool handlers in main thread
|
|
39
|
-
toolHandlers = /* @__PURE__ */ new Map();
|
|
40
|
-
// Tool registry for tracking registrations and reference counting
|
|
41
|
-
toolRegistry = /* @__PURE__ */ new Map();
|
|
42
|
-
// Subscribers for tool changes (for React hooks reactivity)
|
|
43
|
-
toolChangeListeners = /* @__PURE__ */ new Map();
|
|
44
|
-
// Initialize and choose worker implementation (prefer SharedWorker)
|
|
45
|
-
// Accept either a ServiceWorkerRegistration OR WorkerInitOptions to configure URLs
|
|
46
|
-
async init(e) {
|
|
47
|
-
i.log("[WorkerClient] init() called", {
|
|
48
|
-
hasOptions: !!e,
|
|
49
|
-
currentWorkerType: this.workerType,
|
|
50
|
-
initInProgress: !!this.initPromise,
|
|
51
|
-
timestamp: Date.now()
|
|
52
|
-
});
|
|
53
|
-
let r;
|
|
54
|
-
const t = e;
|
|
55
|
-
if (t && typeof t.scope == "string")
|
|
56
|
-
r = e, i.log("[WorkerClient] Using explicit ServiceWorker registration");
|
|
57
|
-
else if (e) {
|
|
58
|
-
const o = e;
|
|
59
|
-
i.log("[WorkerClient] Using WorkerClientInitOptions:", o), o.sharedWorkerUrl && (this.sharedWorkerUrl = o.sharedWorkerUrl), o.serviceWorkerUrl && (this.serviceWorkerUrl = o.serviceWorkerUrl), o.backendWsUrl && (this.backendWsUrl = o.backendWsUrl);
|
|
60
|
-
}
|
|
61
|
-
return this.initPromise ? this.initPromise.then(async () => {
|
|
62
|
-
r && this.workerType !== "service" && await this.init(r);
|
|
63
|
-
}) : (this.initPromise = (async () => {
|
|
64
|
-
try {
|
|
65
|
-
if (r) {
|
|
66
|
-
this.serviceWorkerRegistration = r, this.workerType = "service", i.info(
|
|
67
|
-
"[WorkerClient] Using ServiceWorker (explicit registration)"
|
|
68
|
-
);
|
|
69
|
-
try {
|
|
70
|
-
const n = {
|
|
71
|
-
type: "INIT",
|
|
72
|
-
backendUrl: this.backendWsUrl
|
|
73
|
-
};
|
|
74
|
-
this.pendingAuthToken && (n.token = this.pendingAuthToken), this.serviceWorkerRegistration.active ? this.serviceWorkerRegistration.active.postMessage(n) : "serviceWorker" in navigator && navigator.serviceWorker.controller && navigator.serviceWorker.controller.postMessage(n);
|
|
75
|
-
} catch {
|
|
76
|
-
}
|
|
77
|
-
return;
|
|
78
|
-
}
|
|
79
|
-
if (await this.initSharedWorker()) {
|
|
80
|
-
this.markAsInitialized();
|
|
81
|
-
return;
|
|
82
|
-
}
|
|
83
|
-
await this.initServiceWorkerFallback(), this.markAsInitialized();
|
|
84
|
-
} finally {
|
|
85
|
-
this.initPromise = null;
|
|
86
|
-
}
|
|
87
|
-
})(), this.initPromise);
|
|
88
|
-
}
|
|
89
|
-
/**
|
|
90
|
-
* Mark worker as initialized and process pending registrations
|
|
91
|
-
* @private
|
|
92
|
-
*/
|
|
93
|
-
markAsInitialized() {
|
|
94
|
-
this.isInitialized = !0, i.log(
|
|
95
|
-
"[WorkerClient] Worker initialized, processing pending operations"
|
|
96
|
-
), this.initResolvers.forEach((r) => r()), this.initResolvers = [];
|
|
97
|
-
const e = [...this.pendingRegistrations];
|
|
98
|
-
this.pendingRegistrations = [], e.forEach(
|
|
99
|
-
async ({ name: r, description: t, inputSchema: o, handler: n, resolve: c, reject: h }) => {
|
|
100
|
-
try {
|
|
101
|
-
await this.registerToolInternal(
|
|
102
|
-
r,
|
|
103
|
-
t,
|
|
104
|
-
o,
|
|
105
|
-
n
|
|
106
|
-
), c();
|
|
107
|
-
} catch (d) {
|
|
108
|
-
h(d instanceof Error ? d : new Error(String(d)));
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
);
|
|
112
|
-
}
|
|
113
|
-
/**
|
|
114
|
-
* Wait for worker initialization
|
|
115
|
-
* @returns Promise that resolves when worker is initialized
|
|
116
|
-
*/
|
|
117
|
-
async waitForInit() {
|
|
118
|
-
if (this.isInitialized)
|
|
119
|
-
return Promise.resolve();
|
|
120
|
-
if (this.initPromise) {
|
|
121
|
-
await this.initPromise;
|
|
122
|
-
return;
|
|
123
|
-
}
|
|
124
|
-
return new Promise((e) => {
|
|
125
|
-
this.initResolvers.push(e);
|
|
126
|
-
});
|
|
127
|
-
}
|
|
128
|
-
/**
|
|
129
|
-
* Check if worker is initialized
|
|
130
|
-
*/
|
|
131
|
-
get initialized() {
|
|
132
|
-
return this.isInitialized;
|
|
133
|
-
}
|
|
134
|
-
async initSharedWorker() {
|
|
135
|
-
if (typeof SharedWorker > "u")
|
|
136
|
-
return Promise.resolve(!1);
|
|
137
|
-
try {
|
|
138
|
-
this.sharedWorker = new SharedWorker(this.sharedWorkerUrl, {
|
|
139
|
-
type: "module"
|
|
140
|
-
}), this.sharedWorkerPort = this.sharedWorker.port, this.sharedWorkerPort.start(), await new Promise((r, t) => {
|
|
141
|
-
let o = !1;
|
|
142
|
-
const n = setTimeout(() => {
|
|
143
|
-
if (!o) {
|
|
144
|
-
const h = this.sharedWorkerPort;
|
|
145
|
-
h && (h.onmessage = null), t(new Error("SharedWorker initialization timeout"));
|
|
146
|
-
}
|
|
147
|
-
}, 2e3), c = this.sharedWorkerPort;
|
|
148
|
-
if (!c)
|
|
149
|
-
return clearTimeout(n), t(new Error("SharedWorker port not available"));
|
|
150
|
-
c.onmessage = (h) => {
|
|
151
|
-
try {
|
|
152
|
-
const d = h.data;
|
|
153
|
-
d && d.type === "CONNECTION_STATUS" && (clearTimeout(n), o = !0, this.workerType = "shared", c.onmessage = null, r(!0));
|
|
154
|
-
} catch {
|
|
155
|
-
}
|
|
156
|
-
};
|
|
157
|
-
});
|
|
158
|
-
const e = this.sharedWorkerPort;
|
|
159
|
-
if (e) {
|
|
160
|
-
try {
|
|
161
|
-
const r = {
|
|
162
|
-
type: "INIT",
|
|
163
|
-
backendUrl: this.backendWsUrl
|
|
164
|
-
};
|
|
165
|
-
this.pendingAuthToken && (r.token = this.pendingAuthToken), e.postMessage(r), this.pendingAuthToken = null;
|
|
166
|
-
} catch (r) {
|
|
167
|
-
i.warn(
|
|
168
|
-
"[WorkerClient] Failed to send INIT to SharedWorker port:",
|
|
169
|
-
r
|
|
170
|
-
);
|
|
171
|
-
}
|
|
172
|
-
e.onmessage = (r) => {
|
|
173
|
-
try {
|
|
174
|
-
const t = r.data;
|
|
175
|
-
if (t && t.type === "CONNECTION_STATUS") {
|
|
176
|
-
const o = !!t.connected;
|
|
177
|
-
this.connectionStatusCallbacks.forEach((n) => {
|
|
178
|
-
try {
|
|
179
|
-
n(o);
|
|
180
|
-
} catch {
|
|
181
|
-
}
|
|
182
|
-
});
|
|
183
|
-
} else t && t.type === "CALL_TOOL" && this.handleToolCall(t.toolName, t.args, t.callId).catch(
|
|
184
|
-
(o) => {
|
|
185
|
-
i.error(
|
|
186
|
-
"[WorkerClient] Failed to handle tool call:",
|
|
187
|
-
o
|
|
188
|
-
);
|
|
189
|
-
}
|
|
190
|
-
);
|
|
191
|
-
} catch {
|
|
192
|
-
}
|
|
193
|
-
};
|
|
194
|
-
}
|
|
195
|
-
return i.info("[WorkerClient] Using SharedWorker"), !0;
|
|
196
|
-
} catch (e) {
|
|
197
|
-
return i.warn(
|
|
198
|
-
"[WorkerClient] SharedWorker not available, falling back to ServiceWorker:",
|
|
199
|
-
e
|
|
200
|
-
), !1;
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
async initServiceWorkerFallback() {
|
|
204
|
-
if ("serviceWorker" in navigator)
|
|
205
|
-
try {
|
|
206
|
-
const e = await navigator.serviceWorker.getRegistration();
|
|
207
|
-
if (e) {
|
|
208
|
-
this.serviceWorkerRegistration = e, this.workerType = "service", i.info(
|
|
209
|
-
"[WorkerClient] Using existing ServiceWorker registration"
|
|
210
|
-
);
|
|
211
|
-
return;
|
|
212
|
-
}
|
|
213
|
-
this.serviceWorkerRegistration = await navigator.serviceWorker.register(
|
|
214
|
-
this.serviceWorkerUrl
|
|
215
|
-
), this.workerType = "service", "serviceWorker" in navigator && navigator.serviceWorker.addEventListener(
|
|
216
|
-
"message",
|
|
217
|
-
(r) => {
|
|
218
|
-
try {
|
|
219
|
-
const t = r.data;
|
|
220
|
-
t && t.type === "CALL_TOOL" && this.handleToolCall(
|
|
221
|
-
t.toolName,
|
|
222
|
-
t.args,
|
|
223
|
-
t.callId
|
|
224
|
-
).catch((o) => {
|
|
225
|
-
i.error(
|
|
226
|
-
"[WorkerClient] Failed to handle tool call:",
|
|
227
|
-
o
|
|
228
|
-
);
|
|
229
|
-
});
|
|
230
|
-
} catch (t) {
|
|
231
|
-
i.error(
|
|
232
|
-
"[WorkerClient] Error processing ServiceWorker message:",
|
|
233
|
-
t
|
|
234
|
-
);
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
), i.info("[WorkerClient] Using MCP ServiceWorker (fallback)");
|
|
238
|
-
try {
|
|
239
|
-
const r = {
|
|
240
|
-
type: "INIT",
|
|
241
|
-
backendUrl: this.backendWsUrl
|
|
242
|
-
};
|
|
243
|
-
this.pendingAuthToken && (r.token = this.pendingAuthToken), this.serviceWorkerRegistration.active ? this.serviceWorkerRegistration.active.postMessage(r) : "serviceWorker" in navigator && navigator.serviceWorker.controller && navigator.serviceWorker.controller.postMessage(r), this.pendingAuthToken = null;
|
|
244
|
-
} catch {
|
|
245
|
-
}
|
|
246
|
-
} catch (e) {
|
|
247
|
-
throw i.error("[WorkerClient] Failed to register ServiceWorker:", e), e;
|
|
248
|
-
}
|
|
249
|
-
else
|
|
250
|
-
throw new Error("Neither SharedWorker nor ServiceWorker is supported");
|
|
251
|
-
}
|
|
252
|
-
// Low-level request that expects a reply via MessageChannel
|
|
253
|
-
async request(e, r, t = 5e3) {
|
|
254
|
-
if (i.log("[WorkerClient] Request started:", {
|
|
255
|
-
type: e,
|
|
256
|
-
payload: r,
|
|
257
|
-
timeoutMs: t,
|
|
258
|
-
workerType: this.workerType,
|
|
259
|
-
hasSharedWorkerPort: !!this.sharedWorkerPort,
|
|
260
|
-
hasServiceWorkerReg: !!this.serviceWorkerRegistration
|
|
261
|
-
}), this.workerType === "shared" && this.sharedWorkerPort)
|
|
262
|
-
return new Promise((o, n) => {
|
|
263
|
-
const c = new MessageChannel(), h = Math.random().toString(36).substring(7), d = Date.now(), g = setTimeout(() => {
|
|
264
|
-
const a = Date.now() - d;
|
|
265
|
-
i.error("[WorkerClient] Request timeout:", {
|
|
266
|
-
type: e,
|
|
267
|
-
requestId: h,
|
|
268
|
-
elapsed: a,
|
|
269
|
-
timeoutMs: t
|
|
270
|
-
}), c.port1.onmessage = null, n(new Error("Request timeout"));
|
|
271
|
-
}, t);
|
|
272
|
-
c.port1.onmessage = (a) => {
|
|
273
|
-
try {
|
|
274
|
-
const l = Date.now() - d;
|
|
275
|
-
i.log("[WorkerClient] Request response received:", {
|
|
276
|
-
type: e,
|
|
277
|
-
requestId: h,
|
|
278
|
-
elapsed: l,
|
|
279
|
-
success: a.data?.success
|
|
280
|
-
}), clearTimeout(g), a.data && a.data.success ? o(a.data) : a.data && a.data.success === !1 ? n(new Error(a.data.error || "Worker error")) : o(a.data);
|
|
281
|
-
} catch (l) {
|
|
282
|
-
clearTimeout(g), c.port1.onmessage = null, n(
|
|
283
|
-
l instanceof Error ? l : new Error(String(l))
|
|
284
|
-
);
|
|
285
|
-
}
|
|
286
|
-
};
|
|
287
|
-
try {
|
|
288
|
-
const a = this.sharedWorkerPort;
|
|
289
|
-
if (!a)
|
|
290
|
-
return clearTimeout(g), i.error("[WorkerClient] SharedWorker port not available"), n(new Error("SharedWorker port not available"));
|
|
291
|
-
i.log("[WorkerClient] Posting message to SharedWorker:", {
|
|
292
|
-
type: e,
|
|
293
|
-
requestId: h
|
|
294
|
-
}), a.postMessage({ type: e, ...r || {} }, [c.port2]);
|
|
295
|
-
} catch (a) {
|
|
296
|
-
clearTimeout(g), i.error("[WorkerClient] Failed to post message:", a), n(a instanceof Error ? a : new Error(String(a)));
|
|
297
|
-
}
|
|
298
|
-
});
|
|
299
|
-
if (this.workerType === "service" && this.serviceWorkerRegistration) {
|
|
300
|
-
const o = this.serviceWorkerRegistration;
|
|
301
|
-
if (!o) throw new Error("Service worker registration missing");
|
|
302
|
-
if (!o.active && (i.log("[WorkerClient] ServiceWorker not active, waiting..."), await navigator.serviceWorker.ready, !o.active))
|
|
303
|
-
throw new Error("Service worker not active");
|
|
304
|
-
return new Promise((n, c) => {
|
|
305
|
-
const h = new MessageChannel(), d = Math.random().toString(36).substring(7), g = Date.now(), a = setTimeout(() => {
|
|
306
|
-
const l = Date.now() - g;
|
|
307
|
-
i.error("[WorkerClient] ServiceWorker request timeout:", {
|
|
308
|
-
type: e,
|
|
309
|
-
requestId: d,
|
|
310
|
-
elapsed: l,
|
|
311
|
-
timeoutMs: t
|
|
312
|
-
}), h.port1.onmessage = null, c(new Error("Request timeout"));
|
|
313
|
-
}, t);
|
|
314
|
-
h.port1.onmessage = (l) => {
|
|
315
|
-
try {
|
|
316
|
-
const u = Date.now() - g;
|
|
317
|
-
i.log("[WorkerClient] ServiceWorker response received:", {
|
|
318
|
-
type: e,
|
|
319
|
-
requestId: d,
|
|
320
|
-
elapsed: u,
|
|
321
|
-
success: l.data?.success
|
|
322
|
-
}), clearTimeout(a), l.data && l.data.success ? n(l.data) : l.data && l.data.success === !1 ? c(new Error(l.data.error || "Worker error")) : n(l.data);
|
|
323
|
-
} catch (u) {
|
|
324
|
-
clearTimeout(a), h.port1.onmessage = null, c(
|
|
325
|
-
u instanceof Error ? u : new Error(String(u))
|
|
326
|
-
);
|
|
327
|
-
}
|
|
328
|
-
};
|
|
329
|
-
try {
|
|
330
|
-
const l = o.active;
|
|
331
|
-
if (!l)
|
|
332
|
-
return clearTimeout(a), i.error(
|
|
333
|
-
"[WorkerClient] ServiceWorker active instance not available"
|
|
334
|
-
), c(
|
|
335
|
-
new Error("Service worker active instance not available")
|
|
336
|
-
);
|
|
337
|
-
i.log("[WorkerClient] Posting message to ServiceWorker:", {
|
|
338
|
-
type: e,
|
|
339
|
-
requestId: d
|
|
340
|
-
}), l.postMessage({ type: e, ...r || {} }, [h.port2]);
|
|
341
|
-
} catch (l) {
|
|
342
|
-
clearTimeout(a), i.error(
|
|
343
|
-
"[WorkerClient] Failed to post message to ServiceWorker:",
|
|
344
|
-
l
|
|
345
|
-
), c(l instanceof Error ? l : new Error(String(l)));
|
|
346
|
-
}
|
|
347
|
-
});
|
|
348
|
-
}
|
|
349
|
-
throw new Error("No worker registered");
|
|
350
|
-
}
|
|
351
|
-
// Fire-and-forget postMessage (no response expected)
|
|
352
|
-
async post(e, r) {
|
|
353
|
-
if (this.workerType === "shared" && this.sharedWorkerPort) {
|
|
354
|
-
try {
|
|
355
|
-
this.sharedWorkerPort.postMessage({ type: e, ...r || {} });
|
|
356
|
-
} catch (t) {
|
|
357
|
-
i.error("[WorkerClient] Failed to post to SharedWorker:", t);
|
|
358
|
-
}
|
|
359
|
-
return;
|
|
360
|
-
}
|
|
361
|
-
if (this.workerType === "service" && this.serviceWorkerRegistration?.active) {
|
|
362
|
-
try {
|
|
363
|
-
this.serviceWorkerRegistration.active.postMessage({
|
|
364
|
-
type: e,
|
|
365
|
-
...r || {}
|
|
366
|
-
});
|
|
367
|
-
} catch (t) {
|
|
368
|
-
i.error(
|
|
369
|
-
"[WorkerClient] Failed to post to ServiceWorker (active):",
|
|
370
|
-
t
|
|
371
|
-
);
|
|
372
|
-
}
|
|
373
|
-
return;
|
|
374
|
-
}
|
|
375
|
-
if ("serviceWorker" in navigator && navigator.serviceWorker.controller) {
|
|
376
|
-
try {
|
|
377
|
-
navigator.serviceWorker.controller.postMessage({
|
|
378
|
-
type: e,
|
|
379
|
-
...r || {}
|
|
380
|
-
});
|
|
381
|
-
} catch (t) {
|
|
382
|
-
i.error(
|
|
383
|
-
"[WorkerClient] Failed to post to ServiceWorker.controller:",
|
|
384
|
-
t
|
|
385
|
-
);
|
|
386
|
-
}
|
|
387
|
-
return;
|
|
388
|
-
}
|
|
389
|
-
if (e === "SET_AUTH_TOKEN" && r) {
|
|
390
|
-
const t = r.token;
|
|
391
|
-
typeof t == "string" && (this.pendingAuthToken = t);
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
sendAuthTokenToServiceWorker(e) {
|
|
395
|
-
if (this.serviceWorkerRegistration?.active)
|
|
396
|
-
try {
|
|
397
|
-
this.serviceWorkerRegistration.active.postMessage({
|
|
398
|
-
type: "SET_AUTH_TOKEN",
|
|
399
|
-
token: e
|
|
400
|
-
});
|
|
401
|
-
} catch (r) {
|
|
402
|
-
console.error(
|
|
403
|
-
"[WorkerClient] Failed to send auth token to ServiceWorker:",
|
|
404
|
-
r
|
|
405
|
-
);
|
|
406
|
-
}
|
|
407
|
-
else if ("serviceWorker" in navigator && navigator.serviceWorker.controller)
|
|
408
|
-
try {
|
|
409
|
-
navigator.serviceWorker.controller.postMessage({
|
|
410
|
-
type: "SET_AUTH_TOKEN",
|
|
411
|
-
token: e
|
|
412
|
-
});
|
|
413
|
-
} catch (r) {
|
|
414
|
-
console.error(
|
|
415
|
-
"[WorkerClient] Failed to send auth token to ServiceWorker.controller:",
|
|
416
|
-
r
|
|
417
|
-
);
|
|
418
|
-
}
|
|
419
|
-
else
|
|
420
|
-
this.pendingAuthToken = e;
|
|
421
|
-
}
|
|
422
|
-
// Subscription API for consumers to listen for connection status updates
|
|
423
|
-
onConnectionStatus(e) {
|
|
424
|
-
this.connectionStatusCallbacks.add(e);
|
|
425
|
-
}
|
|
426
|
-
offConnectionStatus(e) {
|
|
427
|
-
this.connectionStatusCallbacks.delete(e);
|
|
428
|
-
}
|
|
429
|
-
async getConnectionStatus() {
|
|
430
|
-
try {
|
|
431
|
-
const e = await this.request(
|
|
432
|
-
"GET_CONNECTION_STATUS",
|
|
433
|
-
void 0,
|
|
434
|
-
2e3
|
|
435
|
-
);
|
|
436
|
-
return e && typeof e == "object" && "connected" in e ? !!e.connected : !!e?.connected;
|
|
437
|
-
} catch {
|
|
438
|
-
return !1;
|
|
439
|
-
}
|
|
440
|
-
}
|
|
441
|
-
setAuthToken(e) {
|
|
442
|
-
if (this.pendingAuthToken = e, this.workerType === "shared" && this.sharedWorkerPort)
|
|
443
|
-
try {
|
|
444
|
-
this.sharedWorkerPort.postMessage({ type: "SET_AUTH_TOKEN", token: e }), this.pendingAuthToken = null;
|
|
445
|
-
} catch (r) {
|
|
446
|
-
i.error(
|
|
447
|
-
"[WorkerClient] Failed to set auth token on SharedWorker:",
|
|
448
|
-
r
|
|
449
|
-
);
|
|
450
|
-
}
|
|
451
|
-
else this.workerType === "service" && (this.sendAuthTokenToServiceWorker(e), this.pendingAuthToken = null);
|
|
452
|
-
}
|
|
453
|
-
/**
|
|
454
|
-
* Register a custom MCP tool dynamically
|
|
455
|
-
*
|
|
456
|
-
* The handler function runs in the MAIN THREAD (browser context), not in the worker.
|
|
457
|
-
* This means you have full access to:
|
|
458
|
-
* - React context, hooks, Redux store
|
|
459
|
-
* - DOM API, window, localStorage
|
|
460
|
-
* - All your imports and dependencies
|
|
461
|
-
* - Closures and external variables
|
|
462
|
-
*
|
|
463
|
-
* The worker acts as a proxy - it receives MCP tool calls and forwards them
|
|
464
|
-
* to your handler via MessageChannel.
|
|
465
|
-
*
|
|
466
|
-
* @param name - Tool name
|
|
467
|
-
* @param description - Tool description
|
|
468
|
-
* @param inputSchema - JSON Schema for tool inputs
|
|
469
|
-
* @param handler - Async function that handles the tool execution (runs in main thread)
|
|
470
|
-
* @returns Promise that resolves when tool is registered
|
|
471
|
-
*
|
|
472
|
-
* @example With full access to imports and context:
|
|
473
|
-
* ```typescript
|
|
474
|
-
* import { z } from 'zod';
|
|
475
|
-
* import { useMyStore } from './store';
|
|
476
|
-
*
|
|
477
|
-
* const store = useMyStore();
|
|
478
|
-
*
|
|
479
|
-
* await client.registerTool(
|
|
480
|
-
* 'validate_user',
|
|
481
|
-
* 'Validate user data',
|
|
482
|
-
* { type: 'object', properties: { username: { type: 'string' } } },
|
|
483
|
-
* async (args: any) => {
|
|
484
|
-
* // Full access to everything!
|
|
485
|
-
* const schema = z.object({ username: z.string().min(3) });
|
|
486
|
-
* const validated = schema.parse(args);
|
|
487
|
-
*
|
|
488
|
-
* // Can access store, context, etc.
|
|
489
|
-
* const currentUser = store.getState().user;
|
|
490
|
-
*
|
|
491
|
-
* return {
|
|
492
|
-
* content: [{
|
|
493
|
-
* type: 'text',
|
|
494
|
-
* text: JSON.stringify({ validated, currentUser })
|
|
495
|
-
* }]
|
|
496
|
-
* };
|
|
497
|
-
* }
|
|
498
|
-
* );
|
|
499
|
-
* ```
|
|
500
|
-
*/
|
|
501
|
-
async registerTool(e, r, t, o) {
|
|
502
|
-
return this.isInitialized ? this.registerToolInternal(e, r, t, o) : (i.log(
|
|
503
|
-
`[WorkerClient] Queueing tool registration '${e}' (worker not initialized yet)`
|
|
504
|
-
), new Promise((n, c) => {
|
|
505
|
-
this.pendingRegistrations.push({
|
|
506
|
-
name: e,
|
|
507
|
-
description: r,
|
|
508
|
-
inputSchema: t,
|
|
509
|
-
handler: o,
|
|
510
|
-
resolve: n,
|
|
511
|
-
reject: c
|
|
512
|
-
});
|
|
513
|
-
}));
|
|
514
|
-
}
|
|
515
|
-
/**
|
|
516
|
-
* Internal method to register tool (assumes worker is initialized)
|
|
517
|
-
* @private
|
|
518
|
-
*/
|
|
519
|
-
async registerToolInternal(e, r, t, o) {
|
|
520
|
-
const n = this.toolRegistry.get(e);
|
|
521
|
-
if (n) {
|
|
522
|
-
n.refCount++, i.log(
|
|
523
|
-
`[WorkerClient] Incremented ref count for '${e}': ${n.refCount}`
|
|
524
|
-
), this.toolHandlers.set(e, o), this.notifyToolChange(e);
|
|
525
|
-
return;
|
|
526
|
-
}
|
|
527
|
-
this.toolHandlers.set(e, o), await this.request("REGISTER_TOOL", {
|
|
528
|
-
name: e,
|
|
529
|
-
description: r,
|
|
530
|
-
inputSchema: t,
|
|
531
|
-
handlerType: "proxy"
|
|
532
|
-
// Tell worker this is a proxy handler
|
|
533
|
-
}), this.toolRegistry.set(e, {
|
|
534
|
-
refCount: 1,
|
|
535
|
-
description: r,
|
|
536
|
-
inputSchema: t,
|
|
537
|
-
isRegistered: !0
|
|
538
|
-
}), i.log(
|
|
539
|
-
`[WorkerClient] Registered tool '${e}' with main-thread handler`
|
|
540
|
-
), this.notifyToolChange(e);
|
|
541
|
-
}
|
|
542
|
-
/**
|
|
543
|
-
* Unregister a previously registered MCP tool
|
|
544
|
-
* @param name - Tool name to unregister
|
|
545
|
-
* @returns Promise that resolves to true if tool was found and removed
|
|
546
|
-
*/
|
|
547
|
-
async unregisterTool(e) {
|
|
548
|
-
const r = this.toolRegistry.get(e);
|
|
549
|
-
if (!r)
|
|
550
|
-
return i.warn(`[WorkerClient] Cannot unregister '${e}': not found`), !1;
|
|
551
|
-
if (r.refCount--, i.log(
|
|
552
|
-
`[WorkerClient] Decremented ref count for '${e}': ${r.refCount}`
|
|
553
|
-
), r.refCount <= 0) {
|
|
554
|
-
this.toolHandlers.delete(e);
|
|
555
|
-
const t = await this.request(
|
|
556
|
-
"UNREGISTER_TOOL",
|
|
557
|
-
{ name: e }
|
|
558
|
-
);
|
|
559
|
-
return this.toolRegistry.delete(e), i.log(`[WorkerClient] Unregistered tool '${e}'`), this.notifyToolChange(e), t?.success ?? !1;
|
|
560
|
-
}
|
|
561
|
-
return this.notifyToolChange(e), !0;
|
|
562
|
-
}
|
|
563
|
-
/**
|
|
564
|
-
* Subscribe to tool changes for a specific tool
|
|
565
|
-
* Returns unsubscribe function
|
|
566
|
-
*/
|
|
567
|
-
onToolChange(e, r) {
|
|
568
|
-
this.toolChangeListeners.has(e) || this.toolChangeListeners.set(e, /* @__PURE__ */ new Set());
|
|
569
|
-
const t = this.toolChangeListeners.get(e);
|
|
570
|
-
t.add(r);
|
|
571
|
-
const o = this.toolRegistry.get(e);
|
|
572
|
-
return r(o ? {
|
|
573
|
-
refCount: o.refCount,
|
|
574
|
-
isRegistered: o.isRegistered
|
|
575
|
-
} : null), () => {
|
|
576
|
-
t.delete(r), t.size === 0 && this.toolChangeListeners.delete(e);
|
|
577
|
-
};
|
|
578
|
-
}
|
|
579
|
-
/**
|
|
580
|
-
* Notify all listeners about tool changes
|
|
581
|
-
* @private
|
|
582
|
-
*/
|
|
583
|
-
notifyToolChange(e) {
|
|
584
|
-
const r = this.toolChangeListeners.get(e);
|
|
585
|
-
if (!r || r.size === 0) return;
|
|
586
|
-
const t = this.toolRegistry.get(e), o = t ? {
|
|
587
|
-
refCount: t.refCount,
|
|
588
|
-
isRegistered: t.isRegistered
|
|
589
|
-
} : null;
|
|
590
|
-
r.forEach((n) => {
|
|
591
|
-
try {
|
|
592
|
-
n(o);
|
|
593
|
-
} catch (c) {
|
|
594
|
-
i.error("[WorkerClient] Error in tool change listener:", c);
|
|
595
|
-
}
|
|
596
|
-
});
|
|
597
|
-
}
|
|
598
|
-
/**
|
|
599
|
-
* Get tool info from registry
|
|
600
|
-
*/
|
|
601
|
-
getToolInfo(e) {
|
|
602
|
-
const r = this.toolRegistry.get(e);
|
|
603
|
-
return r ? {
|
|
604
|
-
refCount: r.refCount,
|
|
605
|
-
isRegistered: r.isRegistered
|
|
606
|
-
} : null;
|
|
607
|
-
}
|
|
608
|
-
/**
|
|
609
|
-
* Get all registered tool names
|
|
610
|
-
*/
|
|
611
|
-
getRegisteredTools() {
|
|
612
|
-
return Array.from(this.toolRegistry.keys()).filter(
|
|
613
|
-
(e) => this.toolRegistry.get(e)?.isRegistered
|
|
614
|
-
);
|
|
615
|
-
}
|
|
616
|
-
/**
|
|
617
|
-
* Check if a tool is registered
|
|
618
|
-
*/
|
|
619
|
-
isToolRegistered(e) {
|
|
620
|
-
return this.toolRegistry.get(e)?.isRegistered ?? !1;
|
|
621
|
-
}
|
|
622
|
-
/**
|
|
623
|
-
* Handle tool call from worker - execute handler in main thread and return result
|
|
624
|
-
* @private
|
|
625
|
-
*/
|
|
626
|
-
async handleToolCall(e, r, t) {
|
|
627
|
-
i.log(`[WorkerClient] Handling tool call: ${e}`, {
|
|
628
|
-
callId: t,
|
|
629
|
-
args: r
|
|
630
|
-
});
|
|
631
|
-
try {
|
|
632
|
-
const o = this.toolHandlers.get(e);
|
|
633
|
-
if (!o)
|
|
634
|
-
throw new Error(`Tool handler not found: ${e}`);
|
|
635
|
-
const n = await o(r);
|
|
636
|
-
this.sendToolCallResult(t, { success: !0, result: n });
|
|
637
|
-
} catch (o) {
|
|
638
|
-
i.error(`[WorkerClient] Tool call failed: ${e}`, o), this.sendToolCallResult(t, {
|
|
639
|
-
success: !1,
|
|
640
|
-
error: o instanceof Error ? o.message : "Unknown error"
|
|
641
|
-
});
|
|
642
|
-
}
|
|
643
|
-
}
|
|
644
|
-
/**
|
|
645
|
-
* Send tool call result back to worker
|
|
646
|
-
* @private
|
|
647
|
-
*/
|
|
648
|
-
sendToolCallResult(e, r) {
|
|
649
|
-
const t = {
|
|
650
|
-
type: "TOOL_CALL_RESULT",
|
|
651
|
-
callId: e,
|
|
652
|
-
success: r.success,
|
|
653
|
-
result: r.result,
|
|
654
|
-
error: r.error
|
|
655
|
-
};
|
|
656
|
-
if (this.workerType === "shared" && this.sharedWorkerPort)
|
|
657
|
-
try {
|
|
658
|
-
this.sharedWorkerPort.postMessage(t);
|
|
659
|
-
} catch (o) {
|
|
660
|
-
i.error(
|
|
661
|
-
"[WorkerClient] Failed to send result to SharedWorker:",
|
|
662
|
-
o
|
|
663
|
-
);
|
|
664
|
-
}
|
|
665
|
-
else if (this.workerType === "service" && this.serviceWorkerRegistration?.active)
|
|
666
|
-
try {
|
|
667
|
-
this.serviceWorkerRegistration.active.postMessage(t);
|
|
668
|
-
} catch (o) {
|
|
669
|
-
i.error(
|
|
670
|
-
"[WorkerClient] Failed to send result to ServiceWorker:",
|
|
671
|
-
o
|
|
672
|
-
);
|
|
673
|
-
}
|
|
674
|
-
}
|
|
675
|
-
}
|
|
676
|
-
const w = "user-activity-db", y = 1, f = "user-events";
|
|
677
|
-
async function S() {
|
|
678
|
-
return new Promise((s, e) => {
|
|
679
|
-
const r = indexedDB.open(w, y);
|
|
680
|
-
r.onerror = () => e(r.error), r.onsuccess = () => s(r.result), r.onupgradeneeded = (t) => {
|
|
681
|
-
const o = t.target.result;
|
|
682
|
-
if (!o.objectStoreNames.contains(f)) {
|
|
683
|
-
const n = o.createObjectStore(f, { keyPath: "id" });
|
|
684
|
-
n.createIndex("timestamp", "timestamp", { unique: !1 }), n.createIndex("type", "type", { unique: !1 }), n.createIndex("path", "path", { unique: !1 });
|
|
685
|
-
}
|
|
686
|
-
};
|
|
687
|
-
});
|
|
688
|
-
}
|
|
689
|
-
async function m(s) {
|
|
690
|
-
const o = (await S()).transaction([f], "readonly").objectStore(f).index("timestamp");
|
|
691
|
-
return new Promise((n, c) => {
|
|
692
|
-
const h = o.getAll();
|
|
693
|
-
h.onsuccess = () => {
|
|
694
|
-
let d = h.result;
|
|
695
|
-
d.sort((g, a) => a.timestamp - g.timestamp), n(d);
|
|
696
|
-
}, h.onerror = () => c(h.error);
|
|
697
|
-
});
|
|
698
|
-
}
|
|
699
|
-
const k = new C();
|
|
700
|
-
async function R(s) {
|
|
701
|
-
return k.init(s);
|
|
1
|
+
import { workerClient as a, queryEvents as u } from "@mcp-fe/mcp-worker";
|
|
2
|
+
async function f(t) {
|
|
3
|
+
return a.init(t);
|
|
702
4
|
}
|
|
703
|
-
async function
|
|
704
|
-
const
|
|
705
|
-
await
|
|
5
|
+
async function o(t) {
|
|
6
|
+
const n = { ...t, timestamp: Date.now() };
|
|
7
|
+
await a.request("STORE_EVENT", { event: n });
|
|
706
8
|
}
|
|
707
|
-
async function
|
|
708
|
-
const
|
|
709
|
-
return Array.isArray(
|
|
9
|
+
async function p() {
|
|
10
|
+
const t = await u();
|
|
11
|
+
return Array.isArray(t) ? t : [];
|
|
710
12
|
}
|
|
711
|
-
async function
|
|
712
|
-
return
|
|
13
|
+
async function d() {
|
|
14
|
+
return a.getConnectionStatus();
|
|
713
15
|
}
|
|
714
|
-
function
|
|
715
|
-
|
|
16
|
+
function y(t) {
|
|
17
|
+
a.setAuthToken(t);
|
|
716
18
|
}
|
|
717
|
-
function
|
|
718
|
-
|
|
19
|
+
function C(t) {
|
|
20
|
+
a.onConnectionStatus(t);
|
|
719
21
|
}
|
|
720
|
-
function
|
|
721
|
-
|
|
22
|
+
function l(t) {
|
|
23
|
+
a.offConnectionStatus(t);
|
|
722
24
|
}
|
|
723
|
-
async function
|
|
724
|
-
return
|
|
25
|
+
async function v(t, n, e) {
|
|
26
|
+
return o({ type: "navigation", from: t, to: n, path: e || n });
|
|
725
27
|
}
|
|
726
|
-
async function
|
|
727
|
-
const
|
|
728
|
-
return
|
|
28
|
+
async function g(t, n, e) {
|
|
29
|
+
const c = t.id || void 0, i = t.className || void 0, s = t.textContent?.trim().substring(0, 100) || void 0, r = t.tagName.toLowerCase();
|
|
30
|
+
return o({
|
|
729
31
|
type: "click",
|
|
730
|
-
element:
|
|
731
|
-
elementId:
|
|
732
|
-
elementClass:
|
|
733
|
-
elementText:
|
|
734
|
-
path:
|
|
735
|
-
metadata:
|
|
32
|
+
element: r,
|
|
33
|
+
elementId: c,
|
|
34
|
+
elementClass: i,
|
|
35
|
+
elementText: s,
|
|
36
|
+
path: n || window.location.pathname,
|
|
37
|
+
metadata: e
|
|
736
38
|
});
|
|
737
39
|
}
|
|
738
|
-
async function
|
|
739
|
-
const
|
|
740
|
-
return
|
|
40
|
+
async function w(t, n, e) {
|
|
41
|
+
const c = t.id || void 0, i = t.className || void 0, s = t.tagName.toLowerCase();
|
|
42
|
+
return o({
|
|
741
43
|
type: "input",
|
|
742
|
-
element:
|
|
743
|
-
elementId:
|
|
744
|
-
elementClass:
|
|
745
|
-
path:
|
|
44
|
+
element: s,
|
|
45
|
+
elementId: c,
|
|
46
|
+
elementClass: i,
|
|
47
|
+
path: e || window.location.pathname,
|
|
746
48
|
metadata: {
|
|
747
|
-
valueLength:
|
|
748
|
-
value:
|
|
49
|
+
valueLength: n?.length || 0,
|
|
50
|
+
value: n
|
|
749
51
|
}
|
|
750
52
|
});
|
|
751
53
|
}
|
|
752
|
-
async function
|
|
753
|
-
return
|
|
54
|
+
async function h(t, n, e) {
|
|
55
|
+
return o({
|
|
754
56
|
type: "custom",
|
|
755
|
-
path:
|
|
57
|
+
path: e || window.location.pathname,
|
|
756
58
|
metadata: {
|
|
757
|
-
eventName:
|
|
758
|
-
...
|
|
59
|
+
eventName: t,
|
|
60
|
+
...n
|
|
759
61
|
}
|
|
760
62
|
});
|
|
761
63
|
}
|
|
762
64
|
export {
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
65
|
+
d as getConnectionStatus,
|
|
66
|
+
p as getStoredEvents,
|
|
67
|
+
f as initEventTracker,
|
|
68
|
+
l as offConnectionStatus,
|
|
69
|
+
C as onConnectionStatus,
|
|
70
|
+
y as setAuthToken,
|
|
71
|
+
g as trackClick,
|
|
72
|
+
h as trackCustom,
|
|
73
|
+
o as trackEvent,
|
|
74
|
+
w as trackInput,
|
|
75
|
+
v as trackNavigation
|
|
774
76
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mcp-fe/event-tracker",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"homepage": "https://mcp-fe.ai",
|
|
6
6
|
"repository": {
|
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
"type": "module",
|
|
13
13
|
"main": "./index.js",
|
|
14
14
|
"types": "./index.d.ts",
|
|
15
|
-
"
|
|
16
|
-
"@mcp-fe/mcp-worker": "0.1.
|
|
15
|
+
"peerDependencies": {
|
|
16
|
+
"@mcp-fe/mcp-worker": "^0.1.3"
|
|
17
17
|
},
|
|
18
18
|
"publishConfig": {
|
|
19
19
|
"access": "public"
|