@mcp-fe/event-tracker 0.0.16 → 0.1.0

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.
Files changed (4) hide show
  1. package/LICENSE +1 -1
  2. package/index.d.ts +1 -1
  3. package/index.js +517 -121
  4. package/package.json +2 -2
package/LICENSE CHANGED
@@ -188,7 +188,7 @@
188
188
  same page as the copyright notice for easier identification within
189
189
  third-party archives.
190
190
 
191
- Copyright 2026 [name of copyright owner]
191
+ Copyright 2026 Michal Kopecky
192
192
 
193
193
  Licensed under the Apache License, Version 2.0 (the "License");
194
194
  you may not use this file except in compliance with the License.
package/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Copyright 2026 MCP-FE Contributors
2
+ * Copyright 2026 Michal Kopecky
3
3
  *
4
4
  * Licensed under the Apache License, Version 2.0 (the "License");
5
5
  * you may not use this file except in compliance with the License.
package/index.js CHANGED
@@ -1,4 +1,21 @@
1
- class g {
1
+ const T = typeof process < "u" && process.env?.NODE_ENV === "production", p = typeof process < "u" && process.env?.MCP_DEBUG, v = p === "true" || !T && p !== "false", i = {
2
+ log: (...s) => {
3
+ v && console.log(...s);
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 {
2
19
  // Configurable worker script URLs (defaults kept for backward compatibility)
3
20
  sharedWorkerUrl = "/mcp-shared-worker.js";
4
21
  serviceWorkerUrl = "/mcp-service-worker.js";
@@ -13,59 +30,127 @@ class g {
13
30
  connectionStatusCallbacks = /* @__PURE__ */ new Set();
14
31
  // Mutex/promise to prevent concurrent init runs
15
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();
16
44
  // Initialize and choose worker implementation (prefer SharedWorker)
17
45
  // Accept either a ServiceWorkerRegistration OR WorkerInitOptions to configure URLs
18
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
+ });
19
53
  let r;
20
- const i = e;
21
- if (i && typeof i.scope == "string")
22
- r = e;
54
+ const t = e;
55
+ if (t && typeof t.scope == "string")
56
+ r = e, i.log("[WorkerClient] Using explicit ServiceWorker registration");
23
57
  else if (e) {
24
58
  const o = e;
25
- o.sharedWorkerUrl && (this.sharedWorkerUrl = o.sharedWorkerUrl), o.serviceWorkerUrl && (this.serviceWorkerUrl = o.serviceWorkerUrl), o.backendWsUrl && (this.backendWsUrl = o.backendWsUrl);
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);
26
60
  }
27
61
  return this.initPromise ? this.initPromise.then(async () => {
28
62
  r && this.workerType !== "service" && await this.init(r);
29
63
  }) : (this.initPromise = (async () => {
30
64
  try {
31
65
  if (r) {
32
- this.serviceWorkerRegistration = r, this.workerType = "service", console.log(
66
+ this.serviceWorkerRegistration = r, this.workerType = "service", i.info(
33
67
  "[WorkerClient] Using ServiceWorker (explicit registration)"
34
68
  );
35
69
  try {
36
- const n = { type: "INIT", backendUrl: this.backendWsUrl };
70
+ const n = {
71
+ type: "INIT",
72
+ backendUrl: this.backendWsUrl
73
+ };
37
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);
38
75
  } catch {
39
76
  }
40
77
  return;
41
78
  }
42
- if (await this.initSharedWorker()) return;
43
- await this.initServiceWorkerFallback();
79
+ if (await this.initSharedWorker()) {
80
+ this.markAsInitialized();
81
+ return;
82
+ }
83
+ await this.initServiceWorkerFallback(), this.markAsInitialized();
44
84
  } finally {
45
85
  this.initPromise = null;
46
86
  }
47
87
  })(), this.initPromise);
48
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
+ }
49
134
  async initSharedWorker() {
50
135
  if (typeof SharedWorker > "u")
51
- return !1;
136
+ return Promise.resolve(!1);
52
137
  try {
53
138
  this.sharedWorker = new SharedWorker(this.sharedWorkerUrl, {
54
139
  type: "module"
55
- }), this.sharedWorkerPort = this.sharedWorker.port, this.sharedWorkerPort.start(), await new Promise((r, i) => {
140
+ }), this.sharedWorkerPort = this.sharedWorker.port, this.sharedWorkerPort.start(), await new Promise((r, t) => {
56
141
  let o = !1;
57
142
  const n = setTimeout(() => {
58
143
  if (!o) {
59
- const a = this.sharedWorkerPort;
60
- a && (a.onmessage = null), i(new Error("SharedWorker initialization timeout"));
144
+ const h = this.sharedWorkerPort;
145
+ h && (h.onmessage = null), t(new Error("SharedWorker initialization timeout"));
61
146
  }
62
147
  }, 2e3), c = this.sharedWorkerPort;
63
148
  if (!c)
64
- return clearTimeout(n), i(new Error("SharedWorker port not available"));
65
- c.onmessage = (a) => {
149
+ return clearTimeout(n), t(new Error("SharedWorker port not available"));
150
+ c.onmessage = (h) => {
66
151
  try {
67
- const s = a.data;
68
- s && s.type === "CONNECTION_STATUS" && (clearTimeout(n), o = !0, this.workerType = "shared", c.onmessage = null, r());
152
+ const d = h.data;
153
+ d && d.type === "CONNECTION_STATUS" && (clearTimeout(n), o = !0, this.workerType = "shared", c.onmessage = null, r(!0));
69
154
  } catch {
70
155
  }
71
156
  };
@@ -73,30 +158,43 @@ class g {
73
158
  const e = this.sharedWorkerPort;
74
159
  if (e) {
75
160
  try {
76
- const r = { type: "INIT", backendUrl: this.backendWsUrl };
161
+ const r = {
162
+ type: "INIT",
163
+ backendUrl: this.backendWsUrl
164
+ };
77
165
  this.pendingAuthToken && (r.token = this.pendingAuthToken), e.postMessage(r), this.pendingAuthToken = null;
78
166
  } catch (r) {
79
- console.warn("[WorkerClient] Failed to send INIT to SharedWorker port:", r);
167
+ i.warn(
168
+ "[WorkerClient] Failed to send INIT to SharedWorker port:",
169
+ r
170
+ );
80
171
  }
81
172
  e.onmessage = (r) => {
82
173
  try {
83
- const i = r.data;
84
- if (i && i.type === "CONNECTION_STATUS") {
85
- const o = !!i.connected;
174
+ const t = r.data;
175
+ if (t && t.type === "CONNECTION_STATUS") {
176
+ const o = !!t.connected;
86
177
  this.connectionStatusCallbacks.forEach((n) => {
87
178
  try {
88
179
  n(o);
89
180
  } catch {
90
181
  }
91
182
  });
92
- }
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
+ );
93
191
  } catch {
94
192
  }
95
193
  };
96
194
  }
97
- return console.log("[WorkerClient] Using SharedWorker"), !0;
195
+ return i.info("[WorkerClient] Using SharedWorker"), !0;
98
196
  } catch (e) {
99
- return console.warn(
197
+ return i.warn(
100
198
  "[WorkerClient] SharedWorker not available, falling back to ServiceWorker:",
101
199
  e
102
200
  ), !1;
@@ -107,68 +205,144 @@ class g {
107
205
  try {
108
206
  const e = await navigator.serviceWorker.getRegistration();
109
207
  if (e) {
110
- this.serviceWorkerRegistration = e, this.workerType = "service", console.log(
208
+ this.serviceWorkerRegistration = e, this.workerType = "service", i.info(
111
209
  "[WorkerClient] Using existing ServiceWorker registration"
112
210
  );
113
211
  return;
114
212
  }
115
213
  this.serviceWorkerRegistration = await navigator.serviceWorker.register(
116
214
  this.serviceWorkerUrl
117
- ), this.workerType = "service", console.log("[WorkerClient] Using MCP ServiceWorker (fallback)");
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)");
118
238
  try {
119
- const r = { type: "INIT", backendUrl: this.backendWsUrl };
239
+ const r = {
240
+ type: "INIT",
241
+ backendUrl: this.backendWsUrl
242
+ };
120
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;
121
244
  } catch {
122
245
  }
123
246
  } catch (e) {
124
- throw console.error(
125
- "[WorkerClient] Failed to register ServiceWorker:",
126
- e
127
- ), e;
247
+ throw i.error("[WorkerClient] Failed to register ServiceWorker:", e), e;
128
248
  }
129
249
  else
130
250
  throw new Error("Neither SharedWorker nor ServiceWorker is supported");
131
251
  }
132
252
  // Low-level request that expects a reply via MessageChannel
133
- async request(e, r, i = 5e3) {
134
- if (this.workerType === "shared" && this.sharedWorkerPort)
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)
135
262
  return new Promise((o, n) => {
136
- const c = new MessageChannel(), a = setTimeout(() => {
137
- c.port1.onmessage = null, n(new Error("Request timeout"));
138
- }, i);
139
- c.port1.onmessage = (s) => {
140
- clearTimeout(a), s.data && s.data.success ? o(s.data) : s.data && s.data.success === !1 ? n(new Error(s.data.error || "Worker error")) : o(s.data);
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
+ }
141
286
  };
142
287
  try {
143
- const s = this.sharedWorkerPort;
144
- if (!s)
145
- return clearTimeout(a), n(new Error("SharedWorker port not available"));
146
- s.postMessage({ type: e, ...r || {} }, [c.port2]);
147
- } catch (s) {
148
- clearTimeout(a), n(s instanceof Error ? s : new Error(String(s)));
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)));
149
297
  }
150
298
  });
151
299
  if (this.workerType === "service" && this.serviceWorkerRegistration) {
152
300
  const o = this.serviceWorkerRegistration;
153
301
  if (!o) throw new Error("Service worker registration missing");
154
- if (!o.active && (await navigator.serviceWorker.ready, !o.active))
302
+ if (!o.active && (i.log("[WorkerClient] ServiceWorker not active, waiting..."), await navigator.serviceWorker.ready, !o.active))
155
303
  throw new Error("Service worker not active");
156
304
  return new Promise((n, c) => {
157
- const a = new MessageChannel(), s = setTimeout(() => {
158
- a.port1.onmessage = null, c(new Error("Request timeout"));
159
- }, i);
160
- a.port1.onmessage = (l) => {
161
- clearTimeout(s), 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);
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
+ }
162
328
  };
163
329
  try {
164
330
  const l = o.active;
165
331
  if (!l)
166
- return clearTimeout(s), c(
332
+ return clearTimeout(a), i.error(
333
+ "[WorkerClient] ServiceWorker active instance not available"
334
+ ), c(
167
335
  new Error("Service worker active instance not available")
168
336
  );
169
- l.postMessage({ type: e, ...r || {} }, [a.port2]);
337
+ i.log("[WorkerClient] Posting message to ServiceWorker:", {
338
+ type: e,
339
+ requestId: d
340
+ }), l.postMessage({ type: e, ...r || {} }, [h.port2]);
170
341
  } catch (l) {
171
- clearTimeout(s), c(l instanceof Error ? l : new Error(String(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)));
172
346
  }
173
347
  });
174
348
  }
@@ -179,8 +353,8 @@ class g {
179
353
  if (this.workerType === "shared" && this.sharedWorkerPort) {
180
354
  try {
181
355
  this.sharedWorkerPort.postMessage({ type: e, ...r || {} });
182
- } catch (i) {
183
- console.error("[WorkerClient] Failed to post to SharedWorker:", i);
356
+ } catch (t) {
357
+ i.error("[WorkerClient] Failed to post to SharedWorker:", t);
184
358
  }
185
359
  return;
186
360
  }
@@ -190,10 +364,10 @@ class g {
190
364
  type: e,
191
365
  ...r || {}
192
366
  });
193
- } catch (i) {
194
- console.error(
367
+ } catch (t) {
368
+ i.error(
195
369
  "[WorkerClient] Failed to post to ServiceWorker (active):",
196
- i
370
+ t
197
371
  );
198
372
  }
199
373
  return;
@@ -204,17 +378,17 @@ class g {
204
378
  type: e,
205
379
  ...r || {}
206
380
  });
207
- } catch (i) {
208
- console.error(
381
+ } catch (t) {
382
+ i.error(
209
383
  "[WorkerClient] Failed to post to ServiceWorker.controller:",
210
- i
384
+ t
211
385
  );
212
386
  }
213
387
  return;
214
388
  }
215
389
  if (e === "SET_AUTH_TOKEN" && r) {
216
- const i = r.token;
217
- typeof i == "string" && (this.pendingAuthToken = i);
390
+ const t = r.token;
391
+ typeof t == "string" && (this.pendingAuthToken = t);
218
392
  }
219
393
  }
220
394
  sendAuthTokenToServiceWorker(e) {
@@ -269,82 +443,304 @@ class g {
269
443
  try {
270
444
  this.sharedWorkerPort.postMessage({ type: "SET_AUTH_TOKEN", token: e }), this.pendingAuthToken = null;
271
445
  } catch (r) {
272
- console.error(
446
+ i.error(
273
447
  "[WorkerClient] Failed to set auth token on SharedWorker:",
274
448
  r
275
449
  );
276
450
  }
277
451
  else this.workerType === "service" && (this.sendAuthTokenToServiceWorker(e), this.pendingAuthToken = null);
278
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
+ }
279
675
  }
280
- const W = "user-activity-db", v = 1, k = "user-events";
281
- async function p() {
282
- return new Promise((t, e) => {
283
- const r = indexedDB.open(W, v);
284
- r.onerror = () => e(r.error), r.onsuccess = () => t(r.result), r.onupgradeneeded = (i) => {
285
- const o = i.target.result;
286
- if (!o.objectStoreNames.contains(k)) {
287
- const n = o.createObjectStore(k, { keyPath: "id" });
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" });
288
684
  n.createIndex("timestamp", "timestamp", { unique: !1 }), n.createIndex("type", "type", { unique: !1 }), n.createIndex("path", "path", { unique: !1 });
289
685
  }
290
686
  };
291
687
  });
292
688
  }
293
- async function f(t) {
294
- const o = (await p()).transaction([k], "readonly").objectStore(k).index("timestamp");
689
+ async function m(s) {
690
+ const o = (await S()).transaction([f], "readonly").objectStore(f).index("timestamp");
295
691
  return new Promise((n, c) => {
296
- const a = o.getAll();
297
- a.onsuccess = () => {
298
- let s = a.result;
299
- s.sort((l, d) => d.timestamp - l.timestamp), n(s);
300
- }, a.onerror = () => c(a.error);
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);
301
697
  });
302
698
  }
303
- const h = new g();
304
- async function T(t) {
305
- return h.init(t);
699
+ const k = new C();
700
+ async function R(s) {
701
+ return k.init(s);
306
702
  }
307
- async function u(t) {
308
- const e = { ...t, timestamp: Date.now() };
309
- await h.request("STORE_EVENT", { event: e });
703
+ async function W(s) {
704
+ const e = { ...s, timestamp: Date.now() };
705
+ await k.request("STORE_EVENT", { event: e });
310
706
  }
311
- async function m() {
312
- const t = await f();
313
- return Array.isArray(t) ? t : [];
707
+ async function E() {
708
+ const s = await m();
709
+ return Array.isArray(s) ? s : [];
314
710
  }
315
- async function w() {
316
- return h.getConnectionStatus();
711
+ async function P() {
712
+ return k.getConnectionStatus();
317
713
  }
318
- function y(t) {
319
- h.setAuthToken(t);
714
+ function A(s) {
715
+ k.setAuthToken(s);
320
716
  }
321
- function S(t) {
322
- h.onConnectionStatus(t);
717
+ function I(s) {
718
+ k.onConnectionStatus(s);
323
719
  }
324
- function C(t) {
325
- h.offConnectionStatus(t);
720
+ function b(s) {
721
+ k.offConnectionStatus(s);
326
722
  }
327
- async function E(t, e, r) {
328
- return u({ type: "navigation", from: t, to: e, path: r || e });
723
+ async function U(s, e, r) {
724
+ return W({ type: "navigation", from: s, to: e, path: r || e });
329
725
  }
330
- async function b(t, e, r) {
331
- const i = t.id || void 0, o = t.className || void 0, n = t.textContent?.trim().substring(0, 100) || void 0, c = t.tagName.toLowerCase();
332
- return u({
726
+ async function N(s, e, r) {
727
+ const t = s.id || void 0, o = s.className || void 0, n = s.textContent?.trim().substring(0, 100) || void 0, c = s.tagName.toLowerCase();
728
+ return W({
333
729
  type: "click",
334
730
  element: c,
335
- elementId: i,
731
+ elementId: t,
336
732
  elementClass: o,
337
733
  elementText: n,
338
734
  path: e || window.location.pathname,
339
735
  metadata: r
340
736
  });
341
737
  }
342
- async function A(t, e, r) {
343
- const i = t.id || void 0, o = t.className || void 0, n = t.tagName.toLowerCase();
344
- return u({
738
+ async function M(s, e, r) {
739
+ const t = s.id || void 0, o = s.className || void 0, n = s.tagName.toLowerCase();
740
+ return W({
345
741
  type: "input",
346
742
  element: n,
347
- elementId: i,
743
+ elementId: t,
348
744
  elementClass: o,
349
745
  path: r || window.location.pathname,
350
746
  metadata: {
@@ -353,26 +749,26 @@ async function A(t, e, r) {
353
749
  }
354
750
  });
355
751
  }
356
- async function N(t, e, r) {
357
- return u({
752
+ async function _(s, e, r) {
753
+ return W({
358
754
  type: "custom",
359
755
  path: r || window.location.pathname,
360
756
  metadata: {
361
- eventName: t,
757
+ eventName: s,
362
758
  ...e
363
759
  }
364
760
  });
365
761
  }
366
762
  export {
367
- w as getConnectionStatus,
368
- m as getStoredEvents,
369
- T as initEventTracker,
370
- C as offConnectionStatus,
371
- S as onConnectionStatus,
372
- y as setAuthToken,
373
- b as trackClick,
374
- N as trackCustom,
375
- u as trackEvent,
376
- A as trackInput,
377
- E as trackNavigation
763
+ P as getConnectionStatus,
764
+ E as getStoredEvents,
765
+ R as initEventTracker,
766
+ b as offConnectionStatus,
767
+ I as onConnectionStatus,
768
+ A as setAuthToken,
769
+ N as trackClick,
770
+ _ as trackCustom,
771
+ W as trackEvent,
772
+ M as trackInput,
773
+ U as trackNavigation
378
774
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mcp-fe/event-tracker",
3
- "version": "0.0.16",
3
+ "version": "0.1.0",
4
4
  "license": "Apache-2.0",
5
5
  "homepage": "https://mcp-fe.ai",
6
6
  "repository": {
@@ -13,7 +13,7 @@
13
13
  "main": "./index.js",
14
14
  "types": "./index.d.ts",
15
15
  "dependencies": {
16
- "@mcp-fe/mcp-worker": "0.0.16"
16
+ "@mcp-fe/mcp-worker": "0.1.0"
17
17
  },
18
18
  "publishConfig": {
19
19
  "access": "public"