@introspection-sdk/introspection-browser 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/client.js ADDED
@@ -0,0 +1,676 @@
1
+ /**
2
+ * Introspection Client for Browser
3
+ * Provides an API for tracking events and feedback using OTLP Logs.
4
+ *
5
+ * Identity and gen_ai context can be managed via baggage (requires OpenTelemetry
6
+ * browser SDK with Zone.js for automatic propagation) or via client instance.
7
+ */
8
+ import { SeverityNumber } from "@opentelemetry/api-logs";
9
+ import { LoggerProvider, BatchLogRecordProcessor, } from "@opentelemetry/sdk-logs";
10
+ import { OTLPLogExporter } from "@opentelemetry/exporter-logs-otlp-http";
11
+ import { propagation, context } from "@opentelemetry/api";
12
+ import { VERSION } from "./version.js";
13
+ import { generateEventId, } from "./types.js";
14
+ const ANONYMOUS_ID_KEY = "introspection_anonymous_id";
15
+ const USER_ID_KEY = "introspection_user_id";
16
+ const TRAITS_KEY = "introspection_traits";
17
+ /**
18
+ * Generate a UUID-like anonymous ID
19
+ */
20
+ function generateAnonymousId() {
21
+ const segments = [];
22
+ for (let i = 0; i < 4; i++) {
23
+ segments.push(Math.random().toString(16).substring(2, 6));
24
+ }
25
+ return segments.join("-");
26
+ }
27
+ /**
28
+ * Simple logger that respects debug flag
29
+ */
30
+ class Logger {
31
+ debug;
32
+ constructor(debug) {
33
+ this.debug = debug;
34
+ }
35
+ log(...args) {
36
+ if (this.debug) {
37
+ console.log("[introspection-sdk]", ...args);
38
+ }
39
+ }
40
+ warn(...args) {
41
+ console.warn("[introspection-sdk]", ...args);
42
+ }
43
+ error(...args) {
44
+ console.error("[introspection-sdk]", ...args);
45
+ }
46
+ }
47
+ /**
48
+ * Introspection client for tracking events and feedback in the browser using OTLP Logs.
49
+ *
50
+ * Identity (user_id, anonymous_id) and gen_ai context (conversation_id, previous_response_id, agent)
51
+ * can be managed via client instance (persisted to localStorage) or via OpenTelemetry baggage
52
+ * for scoped context.
53
+ *
54
+ * Note: Automatic baggage propagation in browsers requires Zone.js from @opentelemetry/context-zone.
55
+ *
56
+ * @example
57
+ * ```typescript
58
+ * const client = new IntrospectionClient({
59
+ * token: "intro_xxx",
60
+ * debug: true,
61
+ * });
62
+ *
63
+ * // Set identity once (persisted to localStorage)
64
+ * client.identify("user_123", { email: "user@example.com" });
65
+ *
66
+ * // Simple feedback
67
+ * client.feedback("thumbs_up", { comments: "Great response!" });
68
+ *
69
+ * // With explicit gen_ai context
70
+ * client.feedback("thumbs_down", {
71
+ * conversationId: "conv_456",
72
+ * previousResponseId: "msg_123",
73
+ * comments: "Off topic",
74
+ * });
75
+ *
76
+ * // With scoped baggage context
77
+ * await client.withConversation("conv_456", undefined, async () => {
78
+ * client.feedback("thumbs_up"); // automatically includes conversation_id
79
+ * });
80
+ *
81
+ * // Track a custom event
82
+ * client.track("Button Clicked", { buttonId: "submit" });
83
+ * ```
84
+ */
85
+ export class IntrospectionClient {
86
+ loggerProvider;
87
+ otelLogger;
88
+ userId;
89
+ anonymousId;
90
+ traits = {};
91
+ logger;
92
+ /**
93
+ * @param options - Client configuration (token, service name, advanced settings).
94
+ */
95
+ constructor(options = {}) {
96
+ const token = options.token || "";
97
+ const advanced = options.advanced || {};
98
+ const baseUrl = advanced.baseUrl ||
99
+ (typeof process !== "undefined" && process.env?.INTROSPECTION_BASE_URL) ||
100
+ "https://otel.introspection.dev";
101
+ this.logger = new Logger(advanced.debug ?? false);
102
+ // Load persisted state from localStorage
103
+ this.anonymousId = this.loadAnonymousId();
104
+ this.userId = this.loadUserId();
105
+ this.traits = this.loadTraits();
106
+ if (!token) {
107
+ this.logger.warn("No token provided. Events will not be sent.");
108
+ }
109
+ // Construct endpoint URL for logs
110
+ let endpoint;
111
+ if (baseUrl.endsWith("/v1/logs")) {
112
+ endpoint = baseUrl;
113
+ }
114
+ else {
115
+ endpoint = `${baseUrl.replace(/\/$/, "")}/v1/logs`;
116
+ }
117
+ // Build headers
118
+ const headers = {
119
+ Authorization: `Bearer ${token}`,
120
+ };
121
+ if (advanced.additionalHeaders) {
122
+ Object.assign(headers, advanced.additionalHeaders);
123
+ }
124
+ // Create OTLP log exporter with HTTP/JSON
125
+ const exporter = new OTLPLogExporter({
126
+ url: endpoint,
127
+ headers,
128
+ });
129
+ // Create batch processor
130
+ const processor = new BatchLogRecordProcessor(exporter, {
131
+ maxQueueSize: advanced.maxBatchSize ?? 100,
132
+ scheduledDelayMillis: advanced.flushInterval ?? 5000,
133
+ });
134
+ // Create logger provider with processor
135
+ this.loggerProvider = new LoggerProvider({
136
+ processors: [processor],
137
+ });
138
+ // Get a logger instance
139
+ this.otelLogger = this.loggerProvider.getLogger("@introspection-sdk/introspection-browser", VERSION);
140
+ // Flush on page unload
141
+ if (typeof window !== "undefined") {
142
+ window.addEventListener("beforeunload", () => {
143
+ this.loggerProvider.forceFlush();
144
+ });
145
+ document.addEventListener("visibilitychange", () => {
146
+ if (document.visibilityState === "hidden") {
147
+ this.loggerProvider.forceFlush();
148
+ }
149
+ });
150
+ }
151
+ this.logger.log(`Initialized with endpoint: ${endpoint}`);
152
+ }
153
+ /**
154
+ * Load anonymous ID from localStorage, or generate a new one
155
+ */
156
+ loadAnonymousId() {
157
+ if (typeof localStorage !== "undefined") {
158
+ const stored = localStorage.getItem(ANONYMOUS_ID_KEY);
159
+ if (stored) {
160
+ return stored;
161
+ }
162
+ }
163
+ const newId = generateAnonymousId();
164
+ this.persistAnonymousId(newId);
165
+ return newId;
166
+ }
167
+ /**
168
+ * Persist anonymous ID to localStorage
169
+ */
170
+ persistAnonymousId(id) {
171
+ if (typeof localStorage !== "undefined") {
172
+ localStorage.setItem(ANONYMOUS_ID_KEY, id);
173
+ }
174
+ }
175
+ /**
176
+ * Load user ID from localStorage
177
+ */
178
+ loadUserId() {
179
+ if (typeof localStorage !== "undefined") {
180
+ return localStorage.getItem(USER_ID_KEY) || undefined;
181
+ }
182
+ return undefined;
183
+ }
184
+ /**
185
+ * Persist user ID to localStorage
186
+ */
187
+ persistUserId(id) {
188
+ if (typeof localStorage !== "undefined") {
189
+ localStorage.setItem(USER_ID_KEY, id);
190
+ }
191
+ }
192
+ /**
193
+ * Load traits from localStorage
194
+ */
195
+ loadTraits() {
196
+ if (typeof localStorage !== "undefined") {
197
+ const stored = localStorage.getItem(TRAITS_KEY);
198
+ if (stored) {
199
+ try {
200
+ return JSON.parse(stored);
201
+ }
202
+ catch {
203
+ return {};
204
+ }
205
+ }
206
+ }
207
+ return {};
208
+ }
209
+ /**
210
+ * Persist traits to localStorage
211
+ */
212
+ persistTraits(traits) {
213
+ if (typeof localStorage !== "undefined") {
214
+ localStorage.setItem(TRAITS_KEY, JSON.stringify(traits));
215
+ }
216
+ }
217
+ /** Get gen_ai context from baggage */
218
+ getGenAiFromContext() {
219
+ const baggage = propagation.getBaggage(context.active());
220
+ return {
221
+ conversationId: baggage?.getEntry("gen_ai.conversation.id")?.value,
222
+ previousResponseId: baggage?.getEntry("gen_ai.request.previous_response_id")?.value,
223
+ agentName: baggage?.getEntry("gen_ai.agent.name")?.value,
224
+ agentId: baggage?.getEntry("gen_ai.agent.id")?.value,
225
+ };
226
+ }
227
+ /** Get identity from baggage or client instance */
228
+ getIdentityFromContext() {
229
+ const baggage = propagation.getBaggage(context.active());
230
+ return {
231
+ userId: baggage?.getEntry("identity.user_id")?.value || this.userId,
232
+ anonymousId: baggage?.getEntry("identity.anonymous_id")?.value || this.anonymousId,
233
+ };
234
+ }
235
+ /**
236
+ * Get current timestamp as OpenTelemetry HrTime.
237
+ * Uses performance.now() for high-resolution timing in browsers.
238
+ */
239
+ getTimestamp() {
240
+ const timeOrigin = performance.timeOrigin;
241
+ const now = performance.now();
242
+ const epochMs = timeOrigin + now;
243
+ const seconds = Math.floor(epochMs / 1000);
244
+ const nanos = Math.floor((epochMs % 1000) * 1_000_000);
245
+ return [seconds, nanos];
246
+ }
247
+ /**
248
+ * Build log record attributes.
249
+ */
250
+ buildAttributes(eventName, options = {}) {
251
+ const { properties, traits, conversationId, previousResponseId, eventId } = options;
252
+ // Core fields
253
+ const attributes = {
254
+ "event.name": eventName,
255
+ "event.id": eventId || generateEventId(),
256
+ };
257
+ // Browser context
258
+ if (typeof window !== "undefined") {
259
+ attributes["context.page.path"] = window.location.pathname;
260
+ attributes["context.page.url"] = window.location.href;
261
+ attributes["context.page.title"] = document.title;
262
+ attributes["context.page.referrer"] = document.referrer;
263
+ attributes["context.page.search"] = window.location.search;
264
+ attributes["context.userAgent"] = navigator.userAgent;
265
+ attributes["context.locale"] = navigator.language;
266
+ attributes["context.timezone"] =
267
+ Intl.DateTimeFormat().resolvedOptions().timeZone;
268
+ attributes["context.screen.width"] = window.screen.width;
269
+ attributes["context.screen.height"] = window.screen.height;
270
+ attributes["context.screen.density"] = window.devicePixelRatio;
271
+ }
272
+ // Identity
273
+ const identity = this.getIdentityFromContext();
274
+ if (identity.userId) {
275
+ attributes["identity.user.id"] = identity.userId;
276
+ }
277
+ if (identity.anonymousId) {
278
+ attributes["identity.anonymous.id"] = identity.anonymousId;
279
+ }
280
+ // Gen AI context (explicit params override baggage)
281
+ const genAi = this.getGenAiFromContext();
282
+ const finalConversationId = conversationId || genAi.conversationId;
283
+ const finalPreviousResponseId = previousResponseId || genAi.previousResponseId;
284
+ if (finalConversationId) {
285
+ attributes["gen_ai.conversation.id"] = finalConversationId;
286
+ }
287
+ if (finalPreviousResponseId) {
288
+ attributes["gen_ai.request.previous_response_id"] =
289
+ finalPreviousResponseId;
290
+ }
291
+ if (genAi.agentName) {
292
+ attributes["gen_ai.agent.name"] = genAi.agentName;
293
+ }
294
+ if (genAi.agentId) {
295
+ attributes["gen_ai.agent.id"] = genAi.agentId;
296
+ }
297
+ // Flatten properties with "properties." prefix
298
+ if (properties) {
299
+ for (const [key, value] of Object.entries(properties)) {
300
+ if (value != null) {
301
+ const attrKey = `properties.${key}`;
302
+ if (typeof value === "object") {
303
+ attributes[attrKey] = JSON.stringify(value);
304
+ }
305
+ else if (typeof value === "string" ||
306
+ typeof value === "number" ||
307
+ typeof value === "boolean") {
308
+ attributes[attrKey] = value;
309
+ }
310
+ }
311
+ }
312
+ }
313
+ // Flatten traits with "context.traits." prefix
314
+ if (traits) {
315
+ for (const [key, value] of Object.entries(traits)) {
316
+ if (value != null) {
317
+ const attrKey = `context.traits.${key}`;
318
+ if (typeof value === "object") {
319
+ attributes[attrKey] = JSON.stringify(value);
320
+ }
321
+ else if (typeof value === "string" ||
322
+ typeof value === "number" ||
323
+ typeof value === "boolean") {
324
+ attributes[attrKey] = value;
325
+ }
326
+ }
327
+ }
328
+ }
329
+ return attributes;
330
+ }
331
+ /**
332
+ * Track a custom event.
333
+ *
334
+ * @param eventName - The event name (e.g., `"Button Clicked"`).
335
+ * @param properties - Optional properties to attach to the event.
336
+ * @param options - Optional overrides (e.g., custom `eventId`).
337
+ *
338
+ * @example
339
+ * ```ts
340
+ * client.track("Button Clicked", { buttonId: "submit" });
341
+ * ```
342
+ */
343
+ track(eventName, properties, options) {
344
+ const attributes = this.buildAttributes(eventName, {
345
+ properties,
346
+ eventId: options?.eventId,
347
+ });
348
+ this.otelLogger.emit({
349
+ timestamp: this.getTimestamp(),
350
+ context: context.active(),
351
+ severityNumber: SeverityNumber.INFO,
352
+ severityText: "INFO",
353
+ attributes,
354
+ });
355
+ this.logger.log(`Tracked: ${eventName}`);
356
+ }
357
+ /**
358
+ * Track feedback on a message or response.
359
+ *
360
+ * gen_ai.conversation.id, gen_ai.request.previous_response_id, and agent context
361
+ * are automatically extracted from baggage if not provided explicitly.
362
+ *
363
+ * @param name - Feedback name/action (e.g., "thumbs_up", "thumbs_down")
364
+ * @param options - Optional feedback options
365
+ *
366
+ * @example
367
+ * ```typescript
368
+ * // Simple feedback
369
+ * client.feedback("thumbs_up");
370
+ *
371
+ * // With comments
372
+ * client.feedback("thumbs_down", { comments: "Off topic" });
373
+ *
374
+ * // With explicit gen_ai context
375
+ * client.feedback("thumbs_up", {
376
+ * conversationId: "conv_456",
377
+ * previousResponseId: "msg_123",
378
+ * });
379
+ *
380
+ * // With baggage context
381
+ * await client.withConversation("conv_456", undefined, async () => {
382
+ * client.feedback("thumbs_up"); // auto-includes conversation_id
383
+ * });
384
+ * ```
385
+ */
386
+ feedback(name, options = {}) {
387
+ const { comments, conversationId, previousResponseId, eventId, ...extra } = options;
388
+ const properties = { name, ...extra };
389
+ if (comments) {
390
+ properties.comments = comments;
391
+ }
392
+ const attributes = this.buildAttributes("introspection.feedback", {
393
+ properties,
394
+ conversationId: conversationId,
395
+ previousResponseId: previousResponseId,
396
+ eventId: eventId,
397
+ });
398
+ this.otelLogger.emit({
399
+ timestamp: this.getTimestamp(),
400
+ context: context.active(),
401
+ severityNumber: SeverityNumber.INFO,
402
+ severityText: "INFO",
403
+ attributes,
404
+ });
405
+ this.logger.log(`Feedback: ${name}`);
406
+ }
407
+ /**
408
+ * Identify a user and persist their traits to `localStorage`.
409
+ *
410
+ * @param userId - The user's unique identifier.
411
+ * @param traits - Optional user traits (email, name, plan, etc.).
412
+ * @param anonymousId - Optional anonymous ID to associate.
413
+ * @param eventId - Optional event ID (auto-generated if omitted).
414
+ *
415
+ * @example
416
+ * ```ts
417
+ * client.identify("user-123", { email: "user@example.com", plan: "pro" });
418
+ * ```
419
+ */
420
+ identify(userId, traits, anonymousId, eventId) {
421
+ // Store and persist
422
+ this.userId = userId;
423
+ this.persistUserId(userId);
424
+ if (anonymousId) {
425
+ this.anonymousId = anonymousId;
426
+ this.persistAnonymousId(anonymousId);
427
+ }
428
+ if (traits) {
429
+ this.traits = { ...this.traits, ...traits };
430
+ this.persistTraits(this.traits);
431
+ }
432
+ const attributes = this.buildAttributes("identify", { traits, eventId });
433
+ this.otelLogger.emit({
434
+ timestamp: this.getTimestamp(),
435
+ context: context.active(),
436
+ severityNumber: SeverityNumber.INFO,
437
+ severityText: "INFO",
438
+ attributes,
439
+ });
440
+ this.logger.log(`Identified: ${userId}`);
441
+ }
442
+ /**
443
+ * Set the authenticated user ID and persist it to `localStorage`.
444
+ *
445
+ * @param userId - The user's unique identifier.
446
+ *
447
+ * @example
448
+ * ```ts
449
+ * client.setUserId("user-123");
450
+ * ```
451
+ */
452
+ setUserId(userId) {
453
+ this.userId = userId;
454
+ this.persistUserId(userId);
455
+ }
456
+ /**
457
+ * Override the anonymous ID and persist it to `localStorage`.
458
+ *
459
+ * @param anonymousId - The anonymous visitor identifier.
460
+ *
461
+ * @example
462
+ * ```ts
463
+ * client.setAnonymousId("anon-abc");
464
+ * ```
465
+ */
466
+ setAnonymousId(anonymousId) {
467
+ this.anonymousId = anonymousId;
468
+ this.persistAnonymousId(anonymousId);
469
+ }
470
+ /**
471
+ * Get the current anonymous visitor identifier.
472
+ *
473
+ * @returns The anonymous ID (auto-generated on first access if not set).
474
+ *
475
+ * @example
476
+ * ```ts
477
+ * const anonId = client.getAnonymousId();
478
+ * ```
479
+ */
480
+ getAnonymousId() {
481
+ return this.anonymousId;
482
+ }
483
+ /**
484
+ * Create a context with baggage values set.
485
+ * Use with context.with() to run code in that context.
486
+ *
487
+ * Note: In browsers, automatic context propagation requires Zone.js.
488
+ * Without it, you must manually pass context through async operations.
489
+ *
490
+ * @param values - Key/value pairs to set as baggage
491
+ * @returns Context with baggage set
492
+ *
493
+ * @example
494
+ * ```typescript
495
+ * const ctx = client.createBaggageContext({
496
+ * "gen_ai.agent.name": "support-bot",
497
+ * "gen_ai.conversation.id": "conv_456"
498
+ * });
499
+ *
500
+ * await context.with(ctx, async () => {
501
+ * client.feedback("thumbs_up"); // picks up baggage
502
+ * });
503
+ * ```
504
+ */
505
+ createBaggageContext(values) {
506
+ const ctx = context.active();
507
+ let bag = propagation.getBaggage(ctx) || propagation.createBaggage();
508
+ for (const [key, value] of Object.entries(values)) {
509
+ bag = bag.setEntry(key, { value });
510
+ }
511
+ return propagation.setBaggage(ctx, bag);
512
+ }
513
+ /**
514
+ * Helper to set multiple baggage values and run a callback.
515
+ *
516
+ * @param values - Key/value pairs to set as baggage.
517
+ * @param callback - Function to run with baggage context.
518
+ * @returns The return value of the callback.
519
+ *
520
+ * @example
521
+ * ```typescript
522
+ * await client.withBaggage({
523
+ * "gen_ai.conversation.id": "conv_456",
524
+ * "gen_ai.request.previous_response_id": "resp_123"
525
+ * }, async () => {
526
+ * client.feedback("thumbs_up");
527
+ * });
528
+ * ```
529
+ */
530
+ async withBaggage(values, callback) {
531
+ const ctx = this.createBaggageContext(values);
532
+ return await context.with(ctx, callback);
533
+ }
534
+ /**
535
+ * Helper to set agent baggage and run a callback.
536
+ *
537
+ * @param agentName - Name of the agent.
538
+ * @param agentId - Optional unique identifier for the agent.
539
+ * @param callback - Function to run with agent context.
540
+ * @returns The return value of the callback.
541
+ *
542
+ * @example
543
+ * ```typescript
544
+ * await client.withAgent("support-bot", "agent_123", async () => {
545
+ * client.feedback("thumbs_up");
546
+ * });
547
+ * ```
548
+ */
549
+ async withAgent(agentName, agentId, callback) {
550
+ const values = {
551
+ "gen_ai.agent.name": agentName,
552
+ };
553
+ if (agentId) {
554
+ values["gen_ai.agent.id"] = agentId;
555
+ }
556
+ const ctx = this.createBaggageContext(values);
557
+ return await context.with(ctx, callback);
558
+ }
559
+ /**
560
+ * Helper to set conversation baggage and run a callback.
561
+ *
562
+ * @param conversationId - Unique identifier for the conversation (optional).
563
+ * @param previousResponseId - Previous response ID for conversation continuity (optional).
564
+ * @param callback - Function to run with conversation context.
565
+ * @returns The return value of the callback.
566
+ *
567
+ * @example
568
+ * ```typescript
569
+ * // Simple conversation context
570
+ * await client.withConversation("conv_456", undefined, async () => {
571
+ * client.feedback("thumbs_up");
572
+ * });
573
+ *
574
+ * // With previous response for conversation continuity
575
+ * await client.withConversation("conv_456", "resp_123", async () => {
576
+ * client.feedback("thumbs_up");
577
+ * });
578
+ *
579
+ * // Just previousResponseId when already in a conversation
580
+ * await client.withConversation("conv_456", undefined, async () => {
581
+ * await client.withConversation(undefined, "resp_1", async () => {
582
+ * client.feedback("thumbs_up");
583
+ * });
584
+ * });
585
+ * ```
586
+ */
587
+ async withConversation(conversationId, previousResponseId, callback) {
588
+ const values = {};
589
+ if (conversationId) {
590
+ values["gen_ai.conversation.id"] = conversationId;
591
+ }
592
+ if (previousResponseId) {
593
+ values["gen_ai.request.previous_response_id"] = previousResponseId;
594
+ }
595
+ const ctx = this.createBaggageContext(values);
596
+ return await context.with(ctx, callback);
597
+ }
598
+ /**
599
+ * Helper to set user ID baggage and run a callback.
600
+ *
601
+ * @param userId - User identifier.
602
+ * @param callback - Function to run with user context.
603
+ * @returns The return value of the callback.
604
+ *
605
+ * @example
606
+ * ```typescript
607
+ * await client.withUserId("user_123", async () => {
608
+ * client.feedback("thumbs_up");
609
+ * });
610
+ * ```
611
+ */
612
+ async withUserId(userId, callback) {
613
+ const ctx = this.createBaggageContext({
614
+ "identity.user_id": userId,
615
+ });
616
+ return await context.with(ctx, callback);
617
+ }
618
+ /**
619
+ * Helper to set anonymous ID baggage and run a callback.
620
+ *
621
+ * @param anonymousId - Anonymous visitor identifier.
622
+ * @param callback - Function to run with anonymous context.
623
+ * @returns The return value of the callback.
624
+ *
625
+ * @example
626
+ * ```ts
627
+ * await client.withAnonymousId("anon-abc", async () => {
628
+ * client.feedback("thumbs_up");
629
+ * });
630
+ * ```
631
+ */
632
+ async withAnonymousId(anonymousId, callback) {
633
+ const ctx = this.createBaggageContext({
634
+ "identity.anonymous_id": anonymousId,
635
+ });
636
+ return await context.with(ctx, callback);
637
+ }
638
+ /**
639
+ * Reset the client state, clearing user identity and generating a new
640
+ * anonymous ID. Equivalent to a logout operation.
641
+ *
642
+ * @example
643
+ * ```ts
644
+ * client.reset();
645
+ * ```
646
+ */
647
+ reset() {
648
+ this.userId = undefined;
649
+ this.anonymousId = generateAnonymousId();
650
+ this.traits = {};
651
+ if (typeof localStorage !== "undefined") {
652
+ localStorage.removeItem(USER_ID_KEY);
653
+ localStorage.removeItem(TRAITS_KEY);
654
+ localStorage.setItem(ANONYMOUS_ID_KEY, this.anonymousId);
655
+ }
656
+ this.logger.log("Reset complete");
657
+ }
658
+ /**
659
+ * Flush all queued log records to the Introspection backend.
660
+ *
661
+ * @returns A promise that resolves once the flush completes.
662
+ */
663
+ async flush() {
664
+ await this.loggerProvider.forceFlush();
665
+ this.logger.log("Flushed pending log records");
666
+ }
667
+ /**
668
+ * Shut down the client, flushing all pending log records.
669
+ *
670
+ * @returns A promise that resolves once shutdown is complete.
671
+ */
672
+ async shutdown() {
673
+ await this.loggerProvider.shutdown();
674
+ this.logger.log("Shutdown complete");
675
+ }
676
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Introspection Browser SDK
3
+ *
4
+ * Provides an API for tracking events and feedback in the browser.
5
+ *
6
+ * @example
7
+ * ```typescript
8
+ * import { IntrospectionClient } from "@introspection-sdk/introspection-browser";
9
+ *
10
+ * const client = new IntrospectionClient({
11
+ * token: "intro_xxx",
12
+ * });
13
+ *
14
+ * // Set identity once
15
+ * client.identify("user_123", { email: "user@example.com" });
16
+ *
17
+ * // Track events
18
+ * client.track("Button Clicked", { buttonId: "submit" });
19
+ *
20
+ * // Track feedback
21
+ * client.feedback("thumbs_up", { comments: "Very helpful response" });
22
+ * client.feedback("thumbs_down", {
23
+ * responseId: "msg_123",
24
+ * comments: "Off topic",
25
+ * });
26
+ * ```
27
+ */
28
+ export { IntrospectionClient } from "./client.js";
29
+ export type { AdvancedOptions, IntrospectionClientOptions, FeedbackOptions, UserTraits, } from "./types.js";
30
+ export { VERSION } from "./version.js";
31
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAGH,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAGlD,YAAY,EACV,eAAe,EACf,0BAA0B,EAC1B,eAAe,EACf,UAAU,GACX,MAAM,YAAY,CAAC;AAGpB,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC"}