@virtu3d/event-manager 0.2.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.
@@ -0,0 +1,1140 @@
1
+ 'use strict';
2
+
3
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
4
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
5
+ }) : x)(function(x) {
6
+ if (typeof require !== "undefined") return require.apply(this, arguments);
7
+ throw Error('Dynamic require of "' + x + '" is not supported');
8
+ });
9
+
10
+ // src/connectors/base.connector.ts
11
+ var BaseConnector = class {
12
+ constructor(config = {}) {
13
+ this.ready = false;
14
+ this.config = {
15
+ enabled: true,
16
+ debug: false,
17
+ ...config
18
+ };
19
+ }
20
+ async init() {
21
+ if (!this.config.enabled) {
22
+ this.log("Connector disabled, skipping init");
23
+ return;
24
+ }
25
+ await this.setup();
26
+ this.ready = true;
27
+ }
28
+ async shutdown() {
29
+ this.ready = false;
30
+ }
31
+ isReady() {
32
+ return this.ready && (this.config.enabled ?? true);
33
+ }
34
+ log(message, data) {
35
+ if (this.config.debug) {
36
+ console.log(`[${this.name}] ${message}`, data || "");
37
+ }
38
+ }
39
+ };
40
+
41
+ // src/browser/otel-browser.connector.ts
42
+ var BrowserOtelConnector = class extends BaseConnector {
43
+ constructor(config) {
44
+ super(config);
45
+ this.name = "otel-browser";
46
+ this.type = "observability";
47
+ this.otelConfig = config;
48
+ }
49
+ async setup() {
50
+ try {
51
+ const [
52
+ traceWebModule,
53
+ traceBaseModule,
54
+ exporterModule,
55
+ contextZoneModule,
56
+ resourcesModule,
57
+ semconvModule,
58
+ apiModule
59
+ ] = await Promise.all([
60
+ import('@opentelemetry/sdk-trace-web'),
61
+ import('@opentelemetry/sdk-trace-base'),
62
+ import('@opentelemetry/exporter-trace-otlp-http'),
63
+ import('@opentelemetry/context-zone'),
64
+ import('@opentelemetry/resources'),
65
+ import('@opentelemetry/semantic-conventions'),
66
+ import('@opentelemetry/api')
67
+ ]);
68
+ const { WebTracerProvider } = traceWebModule;
69
+ const { SimpleSpanProcessor, BatchSpanProcessor } = traceBaseModule;
70
+ const { OTLPTraceExporter } = exporterModule;
71
+ const { ZoneContextManager } = contextZoneModule;
72
+ const resourceFromAttributes = resourcesModule.resourceFromAttributes || ((attrs) => new resourcesModule.Resource(attrs));
73
+ const ATTR_SERVICE_NAME = semconvModule.ATTR_SERVICE_NAME || semconvModule.SEMRESATTRS_SERVICE_NAME || "service.name";
74
+ const { trace } = apiModule;
75
+ const resourceAttrs = {
76
+ [ATTR_SERVICE_NAME]: this.otelConfig.serviceName,
77
+ "telemetry.sdk.language": "webjs",
78
+ "telemetry.sdk.name": "@fountaneengineering/event-manager",
79
+ ...this.otelConfig.resourceAttributes
80
+ };
81
+ const spanProcessors = [];
82
+ if (this.otelConfig.collectorUrl) {
83
+ const exporter = new OTLPTraceExporter({
84
+ url: this.otelConfig.collectorUrl
85
+ });
86
+ spanProcessors.push(new BatchSpanProcessor(exporter));
87
+ } else {
88
+ spanProcessors.push(
89
+ new SimpleSpanProcessor({
90
+ export: (spans, resultCallback) => {
91
+ if (this.config.debug) {
92
+ console.log("[otel-browser] Spans:", spans);
93
+ }
94
+ resultCallback({ code: 0 });
95
+ },
96
+ shutdown: () => Promise.resolve()
97
+ })
98
+ );
99
+ }
100
+ this.provider = new WebTracerProvider({
101
+ resource: resourceFromAttributes(resourceAttrs),
102
+ spanProcessors
103
+ });
104
+ this.provider.register({
105
+ contextManager: new ZoneContextManager()
106
+ });
107
+ this.tracer = trace.getTracer(this.otelConfig.serviceName, "1.0.0");
108
+ this.log("Browser OTEL tracer initialized", {
109
+ serviceName: this.otelConfig.serviceName,
110
+ collectorUrl: this.otelConfig.collectorUrl || "none (debug mode)"
111
+ });
112
+ } catch (error) {
113
+ this.log("Failed to initialize OTEL", error);
114
+ throw error;
115
+ }
116
+ }
117
+ track(event) {
118
+ if (!this.tracer) {
119
+ this.log("Tracer not initialized, skipping track");
120
+ return;
121
+ }
122
+ const span = this.tracer.startSpan(event.name);
123
+ span.setAttribute("event.name", event.name);
124
+ span.setAttribute("event.timestamp", event.timestamp || (/* @__PURE__ */ new Date()).toISOString());
125
+ if (event.payload) {
126
+ Object.entries(event.payload).forEach(([key, value]) => {
127
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
128
+ span.setAttribute(`payload.${key}`, value);
129
+ } else if (value !== null && value !== void 0) {
130
+ try {
131
+ span.setAttribute(`payload.${key}`, JSON.stringify(value));
132
+ } catch {
133
+ }
134
+ }
135
+ });
136
+ }
137
+ if (event.meta) {
138
+ Object.entries(event.meta).forEach(([key, value]) => {
139
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
140
+ span.setAttribute(`meta.${key}`, value);
141
+ }
142
+ });
143
+ }
144
+ span.end();
145
+ this.log("Span created", { name: event.name });
146
+ }
147
+ identify(event) {
148
+ if (!this.tracer) return;
149
+ const span = this.tracer.startSpan("user.identify");
150
+ span.setAttribute("user.id", event.userId);
151
+ if (event.traits) {
152
+ Object.entries(event.traits).forEach(([key, value]) => {
153
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
154
+ span.setAttribute(`user.traits.${key}`, value);
155
+ }
156
+ });
157
+ }
158
+ span.end();
159
+ this.log("User identified", { userId: event.userId });
160
+ }
161
+ page(event) {
162
+ this.track({
163
+ name: "page_view",
164
+ payload: {
165
+ pageName: event.name || window.location.pathname,
166
+ url: window.location.href,
167
+ referrer: document.referrer,
168
+ ...event.properties
169
+ },
170
+ timestamp: event.timestamp
171
+ });
172
+ }
173
+ async shutdown() {
174
+ if (this.provider) {
175
+ try {
176
+ await this.provider.shutdown();
177
+ this.log("Provider shutdown complete");
178
+ } catch (error) {
179
+ this.log("Error during shutdown", error);
180
+ }
181
+ }
182
+ await super.shutdown();
183
+ }
184
+ /**
185
+ * Get the underlying tracer for advanced use cases
186
+ */
187
+ getTracer() {
188
+ return this.tracer;
189
+ }
190
+ /**
191
+ * Get the provider for advanced configuration
192
+ */
193
+ getProvider() {
194
+ return this.provider;
195
+ }
196
+ };
197
+ function createBrowserOtelConnector(config) {
198
+ return new BrowserOtelConnector(config);
199
+ }
200
+
201
+ // src/context/index.ts
202
+ async function getTraceHeaders() {
203
+ try {
204
+ const { context, propagation, trace } = await import('@opentelemetry/api');
205
+ const headers = {};
206
+ propagation.inject(context.active(), headers);
207
+ if (!headers["traceparent"]) {
208
+ const traceId = generateTraceId();
209
+ const spanId = generateSpanId();
210
+ headers["traceparent"] = generateTraceparent(traceId, spanId, 1);
211
+ }
212
+ return headers;
213
+ } catch {
214
+ const traceId = generateTraceId();
215
+ const spanId = generateSpanId();
216
+ return {
217
+ traceparent: generateTraceparent(traceId, spanId, 1)
218
+ };
219
+ }
220
+ }
221
+ function getTraceHeadersSync() {
222
+ try {
223
+ const { context, propagation } = __require("@opentelemetry/api");
224
+ const headers = {};
225
+ propagation.inject(context.active(), headers);
226
+ if (!headers["traceparent"]) {
227
+ const traceId = generateTraceId();
228
+ const spanId = generateSpanId();
229
+ headers["traceparent"] = generateTraceparent(traceId, spanId, 1);
230
+ }
231
+ return headers;
232
+ } catch {
233
+ const traceId = generateTraceId();
234
+ const spanId = generateSpanId();
235
+ return {
236
+ traceparent: generateTraceparent(traceId, spanId, 1)
237
+ };
238
+ }
239
+ }
240
+ async function extractTraceContext(headers) {
241
+ try {
242
+ const { trace, context, propagation, isSpanContextValid } = await import('@opentelemetry/api');
243
+ const normalizedHeaders = {};
244
+ for (const [key, value] of Object.entries(headers)) {
245
+ const lowerKey = key.toLowerCase();
246
+ if (typeof value === "string") {
247
+ normalizedHeaders[lowerKey] = value;
248
+ } else if (Array.isArray(value)) {
249
+ normalizedHeaders[lowerKey] = value[0] || "";
250
+ }
251
+ }
252
+ const extractedContext = propagation.extract(context.active(), normalizedHeaders);
253
+ const spanContext = trace.getSpanContext(extractedContext);
254
+ if (!spanContext || !isSpanContextValid(spanContext)) {
255
+ const traceparent = normalizedHeaders["traceparent"];
256
+ if (traceparent) {
257
+ const parsed = parseTraceparent(traceparent);
258
+ if (parsed) {
259
+ return {
260
+ traceId: parsed.traceId,
261
+ spanId: parsed.spanId,
262
+ traceFlags: parsed.traceFlags
263
+ };
264
+ }
265
+ }
266
+ return null;
267
+ }
268
+ return {
269
+ traceId: spanContext.traceId,
270
+ spanId: spanContext.spanId,
271
+ traceFlags: spanContext.traceFlags
272
+ };
273
+ } catch {
274
+ const traceparent = headers["traceparent"] || headers["Traceparent"];
275
+ if (traceparent) {
276
+ const value = typeof traceparent === "string" ? traceparent : traceparent[0];
277
+ const parsed = parseTraceparent(value || "");
278
+ if (parsed) {
279
+ return {
280
+ traceId: parsed.traceId,
281
+ spanId: parsed.spanId,
282
+ traceFlags: parsed.traceFlags
283
+ };
284
+ }
285
+ }
286
+ return null;
287
+ }
288
+ }
289
+ async function withTraceContext(headers, fn) {
290
+ try {
291
+ const { context, propagation } = await import('@opentelemetry/api');
292
+ const normalizedHeaders = {};
293
+ for (const [key, value] of Object.entries(headers)) {
294
+ if (typeof value === "string") {
295
+ normalizedHeaders[key] = value;
296
+ } else if (Array.isArray(value)) {
297
+ normalizedHeaders[key] = value[0] || "";
298
+ }
299
+ }
300
+ const extractedContext = propagation.extract(context.active(), normalizedHeaders);
301
+ return context.with(extractedContext, fn);
302
+ } catch {
303
+ return fn();
304
+ }
305
+ }
306
+ async function startSpan(name, serviceName = "telemetry") {
307
+ try {
308
+ const { trace } = await import('@opentelemetry/api');
309
+ const tracer = trace.getTracer(serviceName);
310
+ return tracer.startSpan(name);
311
+ } catch {
312
+ return {
313
+ setAttribute: () => {
314
+ },
315
+ setStatus: () => {
316
+ },
317
+ addEvent: () => {
318
+ },
319
+ end: () => {
320
+ },
321
+ spanContext: () => ({ traceId: "", spanId: "", traceFlags: 0 })
322
+ };
323
+ }
324
+ }
325
+ function parseTraceparent(traceparent) {
326
+ if (!traceparent) return null;
327
+ const parts = traceparent.split("-");
328
+ if (parts.length !== 4) return null;
329
+ const [version, traceId, spanId, flags] = parts;
330
+ if (version.length !== 2 || traceId.length !== 32 || spanId.length !== 16 || flags.length !== 2) {
331
+ return null;
332
+ }
333
+ return {
334
+ version,
335
+ traceId,
336
+ spanId,
337
+ traceFlags: parseInt(flags, 16)
338
+ };
339
+ }
340
+ function generateTraceparent(traceId, spanId, traceFlags = 1) {
341
+ const flagsHex = traceFlags.toString(16).padStart(2, "0");
342
+ return `00-${traceId}-${spanId}-${flagsHex}`;
343
+ }
344
+ function generateTraceId() {
345
+ const bytes = new Uint8Array(16);
346
+ if (typeof crypto !== "undefined" && crypto.getRandomValues) {
347
+ crypto.getRandomValues(bytes);
348
+ } else {
349
+ for (let i = 0; i < bytes.length; i++) {
350
+ bytes[i] = Math.floor(Math.random() * 256);
351
+ }
352
+ }
353
+ return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
354
+ }
355
+ function generateSpanId() {
356
+ const bytes = new Uint8Array(8);
357
+ if (typeof crypto !== "undefined" && crypto.getRandomValues) {
358
+ crypto.getRandomValues(bytes);
359
+ } else {
360
+ for (let i = 0; i < bytes.length; i++) {
361
+ bytes[i] = Math.floor(Math.random() * 256);
362
+ }
363
+ }
364
+ return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
365
+ }
366
+
367
+ // src/browser/interceptors.ts
368
+ function matchesPatterns(url, patterns) {
369
+ return patterns.some((pattern) => {
370
+ if (typeof pattern === "string") {
371
+ return url.includes(pattern);
372
+ }
373
+ return pattern.test(url);
374
+ });
375
+ }
376
+ function shouldPropagate(url, includePatterns, excludePatterns) {
377
+ if (!matchesPatterns(url, includePatterns)) {
378
+ return false;
379
+ }
380
+ if (excludePatterns && excludePatterns.length > 0) {
381
+ return !matchesPatterns(url, excludePatterns);
382
+ }
383
+ return true;
384
+ }
385
+ function createTracedFetch(config) {
386
+ const originalFetch = config.originalFetch || globalThis.fetch;
387
+ return async function tracedFetch(input, init) {
388
+ let url;
389
+ if (typeof input === "string") {
390
+ url = input;
391
+ } else if (input instanceof URL) {
392
+ url = input.href;
393
+ } else {
394
+ url = input.url;
395
+ }
396
+ if (!shouldPropagate(url, config.propagateToUrls, config.excludeUrls)) {
397
+ return originalFetch(input, init);
398
+ }
399
+ const traceHeaders = await getTraceHeaders();
400
+ const headers = new Headers(init?.headers);
401
+ Object.entries(traceHeaders).forEach(([key, value]) => {
402
+ if (!headers.has(key)) {
403
+ headers.set(key, value);
404
+ }
405
+ });
406
+ const newInit = {
407
+ ...init,
408
+ headers
409
+ };
410
+ if (config.createSpans) {
411
+ return fetchWithSpan(originalFetch, input, newInit, url, config.serviceName);
412
+ }
413
+ return originalFetch(input, newInit);
414
+ };
415
+ }
416
+ async function fetchWithSpan(originalFetch, input, init, url, serviceName = "browser-fetch") {
417
+ try {
418
+ const { trace, SpanStatusCode } = await import('@opentelemetry/api');
419
+ const tracer = trace.getTracer(serviceName);
420
+ const parsedUrl = new URL(url, window.location.origin);
421
+ const spanName = `HTTP ${init.method || "GET"} ${parsedUrl.pathname}`;
422
+ const span = tracer.startSpan(spanName);
423
+ try {
424
+ span.setAttribute("http.method", init.method || "GET");
425
+ span.setAttribute("http.url", url);
426
+ span.setAttribute("http.host", parsedUrl.host);
427
+ span.setAttribute("http.scheme", parsedUrl.protocol.replace(":", ""));
428
+ const response = await originalFetch(input, init);
429
+ span.setAttribute("http.status_code", response.status);
430
+ span.setAttribute("http.response_content_length", response.headers.get("content-length") || 0);
431
+ if (response.ok) {
432
+ span.setStatus({ code: SpanStatusCode.OK });
433
+ } else {
434
+ span.setStatus({
435
+ code: SpanStatusCode.ERROR,
436
+ message: `HTTP ${response.status} ${response.statusText}`
437
+ });
438
+ }
439
+ return response;
440
+ } catch (error) {
441
+ span.setStatus({
442
+ code: SpanStatusCode.ERROR,
443
+ message: error instanceof Error ? error.message : "Fetch failed"
444
+ });
445
+ span.recordException(error instanceof Error ? error : new Error(String(error)));
446
+ throw error;
447
+ } finally {
448
+ span.end();
449
+ }
450
+ } catch {
451
+ return originalFetch(input, init);
452
+ }
453
+ }
454
+ var storedOriginalFetch = null;
455
+ function installFetchInterceptor(config) {
456
+ if (typeof globalThis.fetch === "undefined") {
457
+ console.warn("[@fountaneengineering/event-manager] fetch is not available in this environment");
458
+ return;
459
+ }
460
+ const originalFetch = config.originalFetch || globalThis.fetch;
461
+ storedOriginalFetch = originalFetch;
462
+ globalThis.fetch = createTracedFetch({
463
+ ...config,
464
+ originalFetch
465
+ });
466
+ }
467
+ function uninstallFetchInterceptor(originalFetch) {
468
+ if (typeof globalThis.fetch !== "undefined") {
469
+ const fetchToRestore = originalFetch || storedOriginalFetch;
470
+ if (fetchToRestore) {
471
+ globalThis.fetch = fetchToRestore;
472
+ storedOriginalFetch = null;
473
+ }
474
+ }
475
+ }
476
+ function installXHRInterceptor(config) {
477
+ if (typeof XMLHttpRequest === "undefined") {
478
+ console.warn("[@fountaneengineering/event-manager] XMLHttpRequest is not available in this environment");
479
+ return;
480
+ }
481
+ const OriginalXHR = XMLHttpRequest;
482
+ const originalOpen = OriginalXHR.prototype.open;
483
+ const originalSend = OriginalXHR.prototype.send;
484
+ const urlMap = /* @__PURE__ */ new WeakMap();
485
+ OriginalXHR.prototype.open = function(method, url, async = true, username, password) {
486
+ const urlString = typeof url === "string" ? url : url.href;
487
+ urlMap.set(this, urlString);
488
+ return originalOpen.call(this, method, urlString, async, username, password);
489
+ };
490
+ OriginalXHR.prototype.send = async function(body) {
491
+ const url = urlMap.get(this);
492
+ if (url && shouldPropagate(url, config.propagateToUrls, config.excludeUrls)) {
493
+ try {
494
+ const traceHeaders = await getTraceHeaders();
495
+ Object.entries(traceHeaders).forEach(([key, value]) => {
496
+ this.setRequestHeader(key, value);
497
+ });
498
+ } catch {
499
+ }
500
+ }
501
+ return originalSend.call(this, body);
502
+ };
503
+ }
504
+ function createAxiosTraceInterceptor(config) {
505
+ return async (axiosConfig) => {
506
+ const url = axiosConfig.url || "";
507
+ const baseURL = axiosConfig.baseURL || "";
508
+ const fullUrl = url.startsWith("http") ? url : `${baseURL}${url}`;
509
+ if (shouldPropagate(fullUrl, config.propagateToUrls, config.excludeUrls)) {
510
+ const traceHeaders = await getTraceHeaders();
511
+ axiosConfig.headers = {
512
+ ...axiosConfig.headers,
513
+ ...traceHeaders
514
+ };
515
+ }
516
+ return axiosConfig;
517
+ };
518
+ }
519
+ function installAxiosInterceptor(axiosInstance, config) {
520
+ return axiosInstance.interceptors.request.use(createAxiosTraceInterceptor(config));
521
+ }
522
+ async function getTracedHeaders(existingHeaders = {}) {
523
+ const traceHeaders = await getTraceHeaders();
524
+ return {
525
+ ...existingHeaders,
526
+ ...traceHeaders
527
+ };
528
+ }
529
+
530
+ // src/connectors/console.connector.ts
531
+ var ConsoleConnector = class extends BaseConnector {
532
+ constructor(config = {}) {
533
+ super(config);
534
+ this.name = "console";
535
+ this.type = "custom";
536
+ this.prefix = config.prefix || "[Telemetry]";
537
+ }
538
+ setup() {
539
+ }
540
+ track(event) {
541
+ console.log(`${this.prefix} TRACK:`, event.name, event.payload);
542
+ }
543
+ identify(event) {
544
+ console.log(`${this.prefix} IDENTIFY:`, event.userId, event.traits);
545
+ }
546
+ page(event) {
547
+ console.log(`${this.prefix} PAGE:`, event.name, event.properties);
548
+ }
549
+ };
550
+ function createConsoleConnector(config) {
551
+ return new ConsoleConnector(config);
552
+ }
553
+
554
+ // src/connectors/otel.connector.ts
555
+ var OtelConnector = class extends BaseConnector {
556
+ constructor(config) {
557
+ super(config);
558
+ this.name = "otel";
559
+ this.type = "observability";
560
+ this.serviceName = config.serviceName;
561
+ this.attributesExtractor = config.attributesExtractor;
562
+ }
563
+ async setup() {
564
+ const api = await import('@opentelemetry/api');
565
+ this.tracer = api.trace.getTracer(this.serviceName);
566
+ this.context = api.context;
567
+ this.log("OTEL tracer initialized");
568
+ }
569
+ track(event) {
570
+ if (!this.tracer) {
571
+ this.log("Tracer not initialized");
572
+ return;
573
+ }
574
+ const span = this.tracer.startSpan(event.name, {}, this.context.active());
575
+ span.setAttribute("event.name", event.name);
576
+ span.setAttribute("event.timestamp", event.timestamp || (/* @__PURE__ */ new Date()).toISOString());
577
+ if (this.attributesExtractor) {
578
+ const customAttrs = this.attributesExtractor(event);
579
+ Object.entries(customAttrs).forEach(([key, value]) => {
580
+ span.setAttribute(key, value);
581
+ });
582
+ }
583
+ Object.entries(event.payload).forEach(([key, value]) => {
584
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
585
+ span.setAttribute(`payload.${key}`, value);
586
+ }
587
+ });
588
+ if (event.meta) {
589
+ Object.entries(event.meta).forEach(([key, value]) => {
590
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
591
+ span.setAttribute(`meta.${key}`, value);
592
+ }
593
+ });
594
+ }
595
+ span.end();
596
+ this.log("Span created", { name: event.name });
597
+ }
598
+ identify(event) {
599
+ this.log("Identify received", { userId: event.userId });
600
+ }
601
+ page(event) {
602
+ this.track({
603
+ name: "page_view",
604
+ payload: { pageName: event.name, ...event.properties },
605
+ timestamp: event.timestamp
606
+ });
607
+ }
608
+ };
609
+
610
+ // src/connectors/cloudwatch-emf.connector.ts
611
+ var CloudWatchEmfConnector = class extends BaseConnector {
612
+ constructor(config) {
613
+ super(config);
614
+ this.name = "cloudwatch-emf";
615
+ this.type = "metrics";
616
+ this.namespace = config.namespace;
617
+ this.serviceName = config.serviceName;
618
+ this.region = config.region || process.env.AWS_REGION || "us-east-1";
619
+ this.metricMappings = config.metricMappings || {};
620
+ }
621
+ setup() {
622
+ this.log("CloudWatch EMF connector initialized", {
623
+ namespace: this.namespace,
624
+ serviceName: this.serviceName,
625
+ mappedEvents: Object.keys(this.metricMappings)
626
+ });
627
+ }
628
+ track(event) {
629
+ const mapping = this.metricMappings[event.name];
630
+ const metricConfig = mapping || {
631
+ metricName: this.toMetricName(event.name),
632
+ unit: "Count",
633
+ dimensions: ["service"]
634
+ };
635
+ const metricValue = metricConfig.valueField ? Number(event.payload[metricConfig.valueField]) || 1 : 1;
636
+ const dimensions = {
637
+ service: this.serviceName
638
+ };
639
+ if (metricConfig.dimensions) {
640
+ for (const dim of metricConfig.dimensions) {
641
+ if (dim !== "service" && event.payload[dim] !== void 0) {
642
+ dimensions[dim] = String(event.payload[dim]);
643
+ }
644
+ }
645
+ }
646
+ if (event.meta?.userId) dimensions["userId"] = String(event.meta.userId);
647
+ if (event.meta?.orgId) dimensions["orgId"] = String(event.meta.orgId);
648
+ const emfLog = {
649
+ _aws: {
650
+ Timestamp: Date.now(),
651
+ CloudWatchMetrics: [
652
+ {
653
+ Namespace: this.namespace,
654
+ Dimensions: [Object.keys(dimensions)],
655
+ Metrics: [
656
+ {
657
+ Name: metricConfig.metricName,
658
+ Unit: metricConfig.unit
659
+ }
660
+ ]
661
+ }
662
+ ]
663
+ },
664
+ // Dimensions as top-level fields
665
+ ...dimensions,
666
+ // Metric value
667
+ [metricConfig.metricName]: metricValue,
668
+ // Additional context (not dimensions, just for log inspection)
669
+ eventName: event.name,
670
+ timestamp: event.timestamp,
671
+ payload: event.payload
672
+ };
673
+ console.log(JSON.stringify(emfLog));
674
+ this.log("EMF metric emitted", {
675
+ metric: metricConfig.metricName,
676
+ value: metricValue,
677
+ dimensions
678
+ });
679
+ }
680
+ identify(event) {
681
+ const emfLog = {
682
+ _aws: {
683
+ Timestamp: Date.now(),
684
+ CloudWatchMetrics: [
685
+ {
686
+ Namespace: this.namespace,
687
+ Dimensions: [["service"]],
688
+ Metrics: [
689
+ {
690
+ Name: "UserIdentified",
691
+ Unit: "Count"
692
+ }
693
+ ]
694
+ }
695
+ ]
696
+ },
697
+ service: this.serviceName,
698
+ UserIdentified: 1,
699
+ userId: event.userId,
700
+ traits: event.traits
701
+ };
702
+ console.log(JSON.stringify(emfLog));
703
+ }
704
+ /**
705
+ * Convert event name to CloudWatch metric name
706
+ * e.g., 'ai_chat_completed' -> 'AiChatCompleted'
707
+ */
708
+ toMetricName(eventName) {
709
+ return eventName.split("_").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join("");
710
+ }
711
+ };
712
+
713
+ // src/factories/index.ts
714
+ var factoryRegistry = /* @__PURE__ */ new Map();
715
+ function registerFactory(typeName, factory) {
716
+ factoryRegistry.set(typeName, factory);
717
+ }
718
+ function getFactory(typeName) {
719
+ return factoryRegistry.get(typeName);
720
+ }
721
+ function hasFactory(typeName) {
722
+ return factoryRegistry.has(typeName);
723
+ }
724
+ function getRegisteredFactories() {
725
+ return Array.from(factoryRegistry.keys());
726
+ }
727
+ function createConnector(typeName, config) {
728
+ const factory = factoryRegistry.get(typeName);
729
+ if (!factory) {
730
+ throw new Error(
731
+ `Unknown connector type: "${typeName}". Registered types: ${getRegisteredFactories().join(", ")}`
732
+ );
733
+ }
734
+ return factory(config);
735
+ }
736
+ function createConnectorsFromConfig(providers) {
737
+ return providers.filter((p) => p.enabled !== false).map((provider) => {
738
+ const connector = createConnector(provider.type, provider.config || {});
739
+ if (provider.name && provider.name !== connector.name) {
740
+ connector.name = provider.name;
741
+ }
742
+ return connector;
743
+ });
744
+ }
745
+ registerFactory("console", (config) => new ConsoleConnector(config));
746
+ registerFactory("otel", (config) => new OtelConnector(config));
747
+ registerFactory("cloudwatch-emf", (config) => new CloudWatchEmfConnector(config));
748
+ registerFactory("mixpanel", (config) => {
749
+ try {
750
+ const dynamicRequire = new Function("modulePath", "return require(modulePath)");
751
+ const { MixpanelConnector } = dynamicRequire("../connectors/mixpanel.connector");
752
+ return new MixpanelConnector(config);
753
+ } catch (err) {
754
+ throw new Error(
755
+ 'Mixpanel connector requires Node.js environment and the "mixpanel" package. Install with: npm install mixpanel'
756
+ );
757
+ }
758
+ });
759
+ registerFactory("posthog", (config) => {
760
+ try {
761
+ const dynamicRequire = new Function("modulePath", "return require(modulePath)");
762
+ const { PostHogConnector } = dynamicRequire("../connectors/posthog.connector");
763
+ return new PostHogConnector(config);
764
+ } catch (err) {
765
+ throw new Error(
766
+ 'PostHog connector requires Node.js environment and the "posthog-node" package. Install with: npm install posthog-node'
767
+ );
768
+ }
769
+ });
770
+
771
+ // src/core/router.ts
772
+ var TelemetryRouter = class {
773
+ constructor(config = {}) {
774
+ this.connectors = /* @__PURE__ */ new Map();
775
+ this.plugins = [];
776
+ this.initialized = false;
777
+ this.config = {
778
+ debug: false,
779
+ globalMeta: {},
780
+ ...config
781
+ };
782
+ this.plugins = config.plugins || [];
783
+ if (config.providers && config.providers.length > 0) {
784
+ try {
785
+ const connectors = createConnectorsFromConfig(config.providers);
786
+ for (const connector of connectors) {
787
+ this.registerConnector(connector);
788
+ }
789
+ } catch (error) {
790
+ this.log("Failed to auto-register providers", error);
791
+ }
792
+ }
793
+ }
794
+ /**
795
+ * Initialize all registered connectors
796
+ */
797
+ async init() {
798
+ if (this.initialized) return;
799
+ const initPromises = Array.from(this.connectors.values()).map(
800
+ async (connector) => {
801
+ try {
802
+ await connector.init?.();
803
+ this.log(`Connector initialized: ${connector.name}`);
804
+ } catch (error) {
805
+ this.log(`Failed to initialize connector: ${connector.name}`, error);
806
+ }
807
+ }
808
+ );
809
+ await Promise.allSettled(initPromises);
810
+ this.initialized = true;
811
+ }
812
+ /**
813
+ * Register a connector instance directly
814
+ */
815
+ registerConnector(connector) {
816
+ if (this.connectors.has(connector.name)) {
817
+ this.log(`Connector "${connector.name}" already registered, replacing...`);
818
+ }
819
+ this.connectors.set(connector.name, connector);
820
+ this.log(`Connector registered: ${connector.name} (${connector.type})`);
821
+ return this;
822
+ }
823
+ /**
824
+ * Register connector using a factory
825
+ */
826
+ use(factory, config) {
827
+ const connector = factory.create(config);
828
+ return this.registerConnector(connector);
829
+ }
830
+ /**
831
+ * Unregister a connector by name
832
+ */
833
+ unregisterConnector(name) {
834
+ const connector = this.connectors.get(name);
835
+ if (connector) {
836
+ connector.shutdown?.();
837
+ this.connectors.delete(name);
838
+ this.log(`Connector unregistered: ${name}`);
839
+ }
840
+ return this;
841
+ }
842
+ /**
843
+ * Add a plugin
844
+ */
845
+ addPlugin(plugin) {
846
+ this.plugins.push(plugin);
847
+ this.log(`Plugin added: ${plugin.name}`);
848
+ return this;
849
+ }
850
+ /**
851
+ * Track a custom event
852
+ */
853
+ async track(eventName, payload = {}) {
854
+ const event = {
855
+ name: eventName,
856
+ payload,
857
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
858
+ meta: { ...this.config.globalMeta }
859
+ };
860
+ return this.dispatch("track", event);
861
+ }
862
+ /**
863
+ * Identify a user
864
+ */
865
+ async identify(userId, traits = {}) {
866
+ const event = {
867
+ userId,
868
+ traits,
869
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
870
+ };
871
+ const telemetryEvent = {
872
+ name: "identify",
873
+ payload: { userId, traits },
874
+ timestamp: event.timestamp,
875
+ meta: { ...this.config.globalMeta, userId }
876
+ };
877
+ return this.dispatch("identify", telemetryEvent, event);
878
+ }
879
+ /**
880
+ * Track a page view
881
+ */
882
+ async page(name, properties = {}) {
883
+ const event = {
884
+ name,
885
+ properties,
886
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
887
+ };
888
+ const telemetryEvent = {
889
+ name: "page_view",
890
+ payload: { pageName: name, ...properties },
891
+ timestamp: event.timestamp,
892
+ meta: { ...this.config.globalMeta }
893
+ };
894
+ return this.dispatch("page", telemetryEvent, event);
895
+ }
896
+ /**
897
+ * Core dispatch method - sends to all connectors in parallel
898
+ */
899
+ async dispatch(method, event, originalEvent) {
900
+ let processedEvent = event;
901
+ for (const plugin of this.plugins) {
902
+ if (plugin.beforeDispatch) {
903
+ processedEvent = plugin.beforeDispatch(processedEvent);
904
+ if (!processedEvent) {
905
+ this.log(`Event blocked by plugin: ${plugin.name}`);
906
+ return {
907
+ event,
908
+ results: [],
909
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
910
+ };
911
+ }
912
+ }
913
+ }
914
+ const connectors = Array.from(this.connectors.values());
915
+ const startTime = Date.now();
916
+ const settledResults = await Promise.allSettled(
917
+ connectors.map(async (connector) => {
918
+ const connectorStart = Date.now();
919
+ try {
920
+ switch (method) {
921
+ case "track":
922
+ await connector.track(processedEvent);
923
+ break;
924
+ case "identify":
925
+ await connector.identify?.(originalEvent);
926
+ break;
927
+ case "page":
928
+ await connector.page?.(originalEvent);
929
+ break;
930
+ }
931
+ return {
932
+ connector: connector.name,
933
+ success: true,
934
+ duration: Date.now() - connectorStart
935
+ };
936
+ } catch (error) {
937
+ return {
938
+ connector: connector.name,
939
+ success: false,
940
+ error: error instanceof Error ? error : new Error(String(error)),
941
+ duration: Date.now() - connectorStart
942
+ };
943
+ }
944
+ })
945
+ );
946
+ const results = settledResults.map((result) => {
947
+ if (result.status === "fulfilled") {
948
+ return result.value;
949
+ }
950
+ return {
951
+ connector: "unknown",
952
+ success: false,
953
+ error: result.reason
954
+ };
955
+ });
956
+ this.log(`Event dispatched: ${event.name}`, {
957
+ duration: Date.now() - startTime,
958
+ results: results.map((r) => ({
959
+ connector: r.connector,
960
+ success: r.success
961
+ }))
962
+ });
963
+ for (const plugin of this.plugins) {
964
+ plugin.afterDispatch?.(processedEvent, results);
965
+ }
966
+ return {
967
+ event: processedEvent,
968
+ results,
969
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
970
+ };
971
+ }
972
+ /**
973
+ * Get all registered connector names
974
+ */
975
+ getConnectors() {
976
+ return Array.from(this.connectors.keys());
977
+ }
978
+ /**
979
+ * Get connectors by type
980
+ */
981
+ getConnectorsByType(type) {
982
+ return Array.from(this.connectors.values()).filter((c) => c.type === type).map((c) => c.name);
983
+ }
984
+ /**
985
+ * Shutdown all connectors
986
+ */
987
+ async shutdown() {
988
+ const shutdownPromises = Array.from(this.connectors.values()).map(
989
+ async (connector) => {
990
+ try {
991
+ await connector.shutdown?.();
992
+ this.log(`Connector shutdown: ${connector.name}`);
993
+ } catch (error) {
994
+ this.log(`Failed to shutdown connector: ${connector.name}`, error);
995
+ }
996
+ }
997
+ );
998
+ await Promise.allSettled(shutdownPromises);
999
+ this.connectors.clear();
1000
+ this.initialized = false;
1001
+ }
1002
+ /**
1003
+ * Internal logger
1004
+ */
1005
+ log(message, data) {
1006
+ if (this.config.debug) {
1007
+ console.log(`[TelemetryRouter] ${message}`, data || "");
1008
+ }
1009
+ }
1010
+ };
1011
+ function createTelemetryRouter(config) {
1012
+ return new TelemetryRouter(config);
1013
+ }
1014
+
1015
+ // src/utils/index.ts
1016
+ function detectEnvironment() {
1017
+ if (typeof window !== "undefined" && typeof document !== "undefined") {
1018
+ return "browser";
1019
+ }
1020
+ return "node";
1021
+ }
1022
+ function generateSessionId() {
1023
+ const timestamp = Date.now().toString(36);
1024
+ const randomPart = Math.random().toString(36).substring(2, 10);
1025
+ return `${timestamp}-${randomPart}`;
1026
+ }
1027
+ function generateEventId() {
1028
+ return `${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
1029
+ }
1030
+ function safeStringify(obj, space) {
1031
+ const seen = /* @__PURE__ */ new WeakSet();
1032
+ return JSON.stringify(
1033
+ obj,
1034
+ (key, value) => {
1035
+ if (typeof value === "object" && value !== null) {
1036
+ if (seen.has(value)) {
1037
+ return "[Circular]";
1038
+ }
1039
+ seen.add(value);
1040
+ }
1041
+ if (typeof value === "bigint") {
1042
+ return value.toString();
1043
+ }
1044
+ if (value instanceof Error) {
1045
+ return {
1046
+ name: value.name,
1047
+ message: value.message,
1048
+ stack: value.stack
1049
+ };
1050
+ }
1051
+ return value;
1052
+ },
1053
+ space
1054
+ );
1055
+ }
1056
+ function deepMerge(target, source) {
1057
+ const result = { ...target };
1058
+ for (const key in source) {
1059
+ if (Object.prototype.hasOwnProperty.call(source, key)) {
1060
+ const sourceValue = source[key];
1061
+ const targetValue = result[key];
1062
+ if (isObject(sourceValue) && isObject(targetValue)) {
1063
+ result[key] = deepMerge(targetValue, sourceValue);
1064
+ } else if (sourceValue !== void 0) {
1065
+ result[key] = sourceValue;
1066
+ }
1067
+ }
1068
+ }
1069
+ return result;
1070
+ }
1071
+ function isObject(value) {
1072
+ return value !== null && typeof value === "object" && !Array.isArray(value);
1073
+ }
1074
+ function sanitizePayload(payload, sensitiveFields = ["password", "token", "secret", "apiKey", "api_key", "authorization"]) {
1075
+ const result = {};
1076
+ for (const [key, value] of Object.entries(payload)) {
1077
+ const lowerKey = key.toLowerCase();
1078
+ if (sensitiveFields.some((field) => lowerKey.includes(field.toLowerCase()))) {
1079
+ result[key] = "[REDACTED]";
1080
+ } else if (isObject(value)) {
1081
+ result[key] = sanitizePayload(value, sensitiveFields);
1082
+ } else {
1083
+ result[key] = value;
1084
+ }
1085
+ }
1086
+ return result;
1087
+ }
1088
+ function debounce(fn, delay) {
1089
+ let timeoutId = null;
1090
+ return (...args) => {
1091
+ if (timeoutId) {
1092
+ clearTimeout(timeoutId);
1093
+ }
1094
+ timeoutId = setTimeout(() => {
1095
+ fn(...args);
1096
+ timeoutId = null;
1097
+ }, delay);
1098
+ };
1099
+ }
1100
+
1101
+ // src/browser/index.ts
1102
+ registerFactory("otel-browser", (config) => new BrowserOtelConnector(config));
1103
+
1104
+ exports.BaseConnector = BaseConnector;
1105
+ exports.BrowserOtelConnector = BrowserOtelConnector;
1106
+ exports.ConsoleConnector = ConsoleConnector;
1107
+ exports.TelemetryRouter = TelemetryRouter;
1108
+ exports.createAxiosTraceInterceptor = createAxiosTraceInterceptor;
1109
+ exports.createBrowserOtelConnector = createBrowserOtelConnector;
1110
+ exports.createConnector = createConnector;
1111
+ exports.createConsoleConnector = createConsoleConnector;
1112
+ exports.createTelemetryRouter = createTelemetryRouter;
1113
+ exports.createTracedFetch = createTracedFetch;
1114
+ exports.debounce = debounce;
1115
+ exports.deepMerge = deepMerge;
1116
+ exports.detectEnvironment = detectEnvironment;
1117
+ exports.extractTraceContext = extractTraceContext;
1118
+ exports.generateEventId = generateEventId;
1119
+ exports.generateSessionId = generateSessionId;
1120
+ exports.generateSpanId = generateSpanId;
1121
+ exports.generateTraceId = generateTraceId;
1122
+ exports.generateTraceparent = generateTraceparent;
1123
+ exports.getFactory = getFactory;
1124
+ exports.getTraceHeaders = getTraceHeaders;
1125
+ exports.getTraceHeadersSync = getTraceHeadersSync;
1126
+ exports.getTracedHeaders = getTracedHeaders;
1127
+ exports.hasFactory = hasFactory;
1128
+ exports.installAxiosInterceptor = installAxiosInterceptor;
1129
+ exports.installFetchInterceptor = installFetchInterceptor;
1130
+ exports.installXHRInterceptor = installXHRInterceptor;
1131
+ exports.isObject = isObject;
1132
+ exports.parseTraceparent = parseTraceparent;
1133
+ exports.registerFactory = registerFactory;
1134
+ exports.safeStringify = safeStringify;
1135
+ exports.sanitizePayload = sanitizePayload;
1136
+ exports.startSpan = startSpan;
1137
+ exports.uninstallFetchInterceptor = uninstallFetchInterceptor;
1138
+ exports.withTraceContext = withTraceContext;
1139
+ //# sourceMappingURL=index.js.map
1140
+ //# sourceMappingURL=index.js.map