@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.
- package/LICENSE +21 -0
- package/README.md +624 -0
- package/dist/browser/index.d.mts +631 -0
- package/dist/browser/index.d.ts +631 -0
- package/dist/browser/index.js +1140 -0
- package/dist/browser/index.js.map +1 -0
- package/dist/browser/index.mjs +1104 -0
- package/dist/browser/index.mjs.map +1 -0
- package/dist/context/index.d.mts +111 -0
- package/dist/context/index.d.ts +111 -0
- package/dist/context/index.js +186 -0
- package/dist/context/index.js.map +1 -0
- package/dist/context/index.mjs +176 -0
- package/dist/context/index.mjs.map +1 -0
- package/dist/index.d.mts +568 -0
- package/dist/index.d.ts +568 -0
- package/dist/index.js +1044 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +999 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +118 -0
|
@@ -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
|