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