@shellapps/experience 1.0.1 → 1.2.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/index.mjs CHANGED
@@ -1,453 +1,2579 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __export = (target, all) => {
3
+ for (var name in all)
4
+ __defProp(target, name, { get: all[name], enumerable: true });
5
+ };
6
+
7
+ // src/types.ts
8
+ var Severity = /* @__PURE__ */ ((Severity2) => {
9
+ Severity2[Severity2["INFO"] = 0] = "INFO";
10
+ Severity2[Severity2["WARNING"] = 1] = "WARNING";
11
+ Severity2[Severity2["ERROR"] = 2] = "ERROR";
12
+ Severity2[Severity2["FATAL"] = 3] = "FATAL";
13
+ return Severity2;
14
+ })(Severity || {});
15
+
16
+ // src/session.ts
17
+ var SESSION_STORAGE_KEY = "shellapps_experience_session";
18
+ var SessionManager = class {
19
+ constructor() {
20
+ this.session = null;
21
+ this.initializeSession();
22
+ }
23
+ generateUUID() {
24
+ if (typeof crypto !== "undefined" && crypto.randomUUID) {
25
+ return crypto.randomUUID();
26
+ }
27
+ return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
28
+ const r = Math.random() * 16 | 0;
29
+ const v = c === "x" ? r : r & 3 | 8;
30
+ return v.toString(16);
31
+ });
32
+ }
33
+ initializeSession() {
34
+ if (typeof sessionStorage !== "undefined") {
35
+ try {
36
+ const storedSession = sessionStorage.getItem(SESSION_STORAGE_KEY);
37
+ if (storedSession) {
38
+ const parsed = JSON.parse(storedSession);
39
+ this.session = {
40
+ id: parsed.id,
41
+ startTime: parsed.startTime,
42
+ getDuration: () => Date.now() - parsed.startTime
43
+ };
44
+ return;
45
+ }
46
+ } catch (error) {
47
+ }
48
+ }
49
+ this.createNewSession();
50
+ }
51
+ createNewSession() {
52
+ const startTime = Date.now();
53
+ const sessionId = this.generateUUID();
54
+ this.session = {
55
+ id: sessionId,
56
+ startTime,
57
+ getDuration: () => Date.now() - startTime
58
+ };
59
+ if (typeof sessionStorage !== "undefined") {
60
+ try {
61
+ sessionStorage.setItem(SESSION_STORAGE_KEY, JSON.stringify({
62
+ id: sessionId,
63
+ startTime
64
+ }));
65
+ } catch (error) {
66
+ }
67
+ }
68
+ }
69
+ getCurrentSession() {
70
+ if (!this.session) {
71
+ this.createNewSession();
72
+ }
73
+ return this.session;
74
+ }
75
+ getSessionId() {
76
+ return this.getCurrentSession().id;
77
+ }
78
+ getSessionDuration() {
79
+ return this.getCurrentSession().getDuration();
80
+ }
81
+ renewSession() {
82
+ this.createNewSession();
83
+ }
84
+ };
85
+
86
+ // src/proto/schemas.ts
87
+ var schemas_exports = {};
88
+ __export(schemas_exports, {
89
+ BreadcrumbCategoryEnum: () => BreadcrumbCategoryEnum,
90
+ BreadcrumbLevelEnum: () => BreadcrumbLevelEnum,
91
+ BreadcrumbSchema: () => BreadcrumbSchema,
92
+ BrowserContextSchema: () => BrowserContextSchema,
93
+ ElementContextSchema: () => ElementContextSchema,
94
+ ErrorBatchSchema: () => ErrorBatchSchema,
95
+ ErrorEventSchema: () => ErrorEventSchema,
96
+ EventBatchSchema: () => EventBatchSchema,
97
+ HeatmapEventSchema: () => HeatmapEventSchema,
98
+ HeatmapTypeEnum: () => HeatmapTypeEnum,
99
+ IngestResponseSchema: () => IngestResponseSchema,
100
+ PageContextSchema: () => PageContextSchema,
101
+ PositionSchema: () => PositionSchema,
102
+ SeverityEnum: () => SeverityEnum,
103
+ TrackEventSchema: () => TrackEventSchema,
104
+ ViewportSchema: () => ViewportSchema,
105
+ WireType: () => WireType
106
+ });
107
+ var TrackEventSchema = {
108
+ eventType: 1,
109
+ sessionId: 2,
110
+ timestamp: 3,
111
+ metadata: 4,
112
+ pageContext: 5,
113
+ elementContext: 6
114
+ };
115
+ var PageContextSchema = {
116
+ url: 1,
117
+ title: 2,
118
+ referrer: 3,
119
+ viewport: 4,
120
+ loadTime: 5
121
+ };
122
+ var ViewportSchema = {
123
+ width: 1,
124
+ height: 2
125
+ };
126
+ var ElementContextSchema = {
127
+ selector: 1,
128
+ dataT: 2,
129
+ tag: 3,
130
+ position: 4
131
+ };
132
+ var PositionSchema = {
133
+ x: 1,
134
+ y: 2
135
+ };
136
+ var HeatmapEventSchema = {
137
+ sessionId: 1,
138
+ timestamp: 2,
139
+ type: 3,
140
+ x: 4,
141
+ y: 5,
142
+ viewportWidth: 6,
143
+ viewportHeight: 7,
144
+ url: 8
145
+ };
146
+ var EventBatchSchema = {
147
+ events: 1,
148
+ heatmapEvents: 2
149
+ };
150
+ var IngestResponseSchema = {
151
+ success: 1,
152
+ message: 2
153
+ };
154
+ var ErrorEventSchema = {
155
+ sessionId: 1,
156
+ timestamp: 2,
157
+ message: 3,
158
+ filename: 4,
159
+ lineno: 5,
160
+ colno: 6,
161
+ stack: 7,
162
+ severity: 8,
163
+ breadcrumbs: 9,
164
+ browserContext: 10,
165
+ tags: 11,
166
+ extra: 12
167
+ };
168
+ var BreadcrumbSchema = {
169
+ timestamp: 1,
170
+ category: 2,
171
+ message: 3,
172
+ data: 4,
173
+ level: 5
174
+ };
175
+ var BrowserContextSchema = {
176
+ userAgent: 1,
177
+ url: 2,
178
+ timestamp: 3,
179
+ viewport: 4
180
+ };
181
+ var ErrorBatchSchema = {
182
+ errors: 1
183
+ };
184
+ var WireType = {
185
+ VARINT: 0,
186
+ FIXED64: 1,
187
+ LENGTH_DELIMITED: 2,
188
+ START_GROUP: 3,
189
+ END_GROUP: 4,
190
+ FIXED32: 5
191
+ };
192
+ var SeverityEnum = {
193
+ INFO: 0,
194
+ WARNING: 1,
195
+ ERROR: 2,
196
+ FATAL: 3
197
+ };
198
+ var HeatmapTypeEnum = {
199
+ click: 0,
200
+ move: 1,
201
+ hover: 2
202
+ };
203
+ var BreadcrumbCategoryEnum = {
204
+ navigation: 0,
205
+ click: 1,
206
+ console: 2,
207
+ fetch: 3,
208
+ xhr: 4,
209
+ ui: 5
210
+ };
211
+ var BreadcrumbLevelEnum = {
212
+ info: 0,
213
+ warning: 1,
214
+ error: 2
215
+ };
216
+
217
+ // src/proto/encode.ts
218
+ var ProtobufEncoder = class _ProtobufEncoder {
219
+ constructor() {
220
+ this.buffer = [];
221
+ }
222
+ // Helper methods for encoding
223
+ encodeVarint(value) {
224
+ while (value >= 128) {
225
+ this.buffer.push(value & 127 | 128);
226
+ value >>>= 7;
227
+ }
228
+ this.buffer.push(value & 127);
229
+ }
230
+ encodeTag(fieldNumber, wireType) {
231
+ this.encodeVarint(fieldNumber << 3 | wireType);
232
+ }
233
+ encodeString(value) {
234
+ const utf8Bytes = new TextEncoder().encode(value);
235
+ this.encodeVarint(utf8Bytes.length);
236
+ this.buffer.push(...utf8Bytes);
237
+ }
238
+ encodeUint64(value) {
239
+ this.encodeVarint(value);
240
+ }
241
+ encodeUint32(value) {
242
+ this.encodeVarint(value);
243
+ }
244
+ encodeBool(value) {
245
+ this.buffer.push(value ? 1 : 0);
246
+ }
247
+ encodeMessage(encoder) {
248
+ const startLength = this.buffer.length;
249
+ encoder();
250
+ const messageLength = this.buffer.length - startLength;
251
+ const lengthBytes = [];
252
+ let length = messageLength;
253
+ while (length >= 128) {
254
+ lengthBytes.push(length & 127 | 128);
255
+ length >>>= 7;
256
+ }
257
+ lengthBytes.push(length & 127);
258
+ this.buffer.splice(startLength, 0, ...lengthBytes);
259
+ return new Uint8Array(this.buffer.slice(startLength + lengthBytes.length));
260
+ }
261
+ encodeMapEntry(key, value) {
262
+ const entryEncoder = new _ProtobufEncoder();
263
+ entryEncoder.encodeTag(1, WireType.LENGTH_DELIMITED);
264
+ entryEncoder.encodeString(key);
265
+ entryEncoder.encodeTag(2, WireType.LENGTH_DELIMITED);
266
+ entryEncoder.encodeString(value);
267
+ this.encodeVarint(entryEncoder.buffer.length);
268
+ this.buffer.push(...entryEncoder.buffer);
269
+ }
270
+ // Encode viewport
271
+ encodeViewport(viewport) {
272
+ if (viewport.width) {
273
+ this.encodeTag(ViewportSchema.width, WireType.VARINT);
274
+ this.encodeUint32(viewport.width);
275
+ }
276
+ if (viewport.height) {
277
+ this.encodeTag(ViewportSchema.height, WireType.VARINT);
278
+ this.encodeUint32(viewport.height);
279
+ }
280
+ }
281
+ // Encode position
282
+ encodePosition(position) {
283
+ this.encodeTag(PositionSchema.x, WireType.VARINT);
284
+ this.encodeUint32(position.x);
285
+ this.encodeTag(PositionSchema.y, WireType.VARINT);
286
+ this.encodeUint32(position.y);
287
+ }
288
+ // Encode PageContext
289
+ encodePageContext(pageContext) {
290
+ this.encodeTag(PageContextSchema.url, WireType.LENGTH_DELIMITED);
291
+ this.encodeString(pageContext.url);
292
+ this.encodeTag(PageContextSchema.title, WireType.LENGTH_DELIMITED);
293
+ this.encodeString(pageContext.title);
294
+ this.encodeTag(PageContextSchema.referrer, WireType.LENGTH_DELIMITED);
295
+ this.encodeString(pageContext.referrer);
296
+ if (pageContext.viewport) {
297
+ this.encodeTag(PageContextSchema.viewport, WireType.LENGTH_DELIMITED);
298
+ const viewportEncoder = new _ProtobufEncoder();
299
+ viewportEncoder.encodeViewport(pageContext.viewport);
300
+ this.encodeVarint(viewportEncoder.buffer.length);
301
+ this.buffer.push(...viewportEncoder.buffer);
302
+ }
303
+ if (pageContext.loadTime) {
304
+ this.encodeTag(PageContextSchema.loadTime, WireType.VARINT);
305
+ this.encodeUint64(pageContext.loadTime);
306
+ }
307
+ }
308
+ // Encode ElementContext
309
+ encodeElementContext(elementContext) {
310
+ this.encodeTag(ElementContextSchema.selector, WireType.LENGTH_DELIMITED);
311
+ this.encodeString(elementContext.selector);
312
+ if (elementContext.dataT) {
313
+ this.encodeTag(ElementContextSchema.dataT, WireType.LENGTH_DELIMITED);
314
+ this.encodeString(elementContext.dataT);
315
+ }
316
+ this.encodeTag(ElementContextSchema.tag, WireType.LENGTH_DELIMITED);
317
+ this.encodeString(elementContext.tag);
318
+ if (elementContext.position) {
319
+ this.encodeTag(ElementContextSchema.position, WireType.LENGTH_DELIMITED);
320
+ const positionEncoder = new _ProtobufEncoder();
321
+ positionEncoder.encodePosition(elementContext.position);
322
+ this.encodeVarint(positionEncoder.buffer.length);
323
+ this.buffer.push(...positionEncoder.buffer);
324
+ }
325
+ }
326
+ // Encode TrackEvent
327
+ encodeTrackEvent(event) {
328
+ this.encodeTag(TrackEventSchema.eventType, WireType.LENGTH_DELIMITED);
329
+ this.encodeString(event.eventType);
330
+ this.encodeTag(TrackEventSchema.sessionId, WireType.LENGTH_DELIMITED);
331
+ this.encodeString(event.sessionId);
332
+ this.encodeTag(TrackEventSchema.timestamp, WireType.VARINT);
333
+ this.encodeUint64(event.timestamp);
334
+ if (event.metadata) {
335
+ for (const [key, value] of Object.entries(event.metadata)) {
336
+ this.encodeTag(TrackEventSchema.metadata, WireType.LENGTH_DELIMITED);
337
+ this.encodeMapEntry(key, value);
338
+ }
339
+ }
340
+ if (event.pageContext) {
341
+ this.encodeTag(TrackEventSchema.pageContext, WireType.LENGTH_DELIMITED);
342
+ const pageContextEncoder = new _ProtobufEncoder();
343
+ pageContextEncoder.encodePageContext(event.pageContext);
344
+ this.encodeVarint(pageContextEncoder.buffer.length);
345
+ this.buffer.push(...pageContextEncoder.buffer);
346
+ }
347
+ if (event.elementContext) {
348
+ this.encodeTag(TrackEventSchema.elementContext, WireType.LENGTH_DELIMITED);
349
+ const elementContextEncoder = new _ProtobufEncoder();
350
+ elementContextEncoder.encodeElementContext(event.elementContext);
351
+ this.encodeVarint(elementContextEncoder.buffer.length);
352
+ this.buffer.push(...elementContextEncoder.buffer);
353
+ }
354
+ }
355
+ // Encode HeatmapEvent
356
+ encodeHeatmapEvent(event) {
357
+ this.encodeTag(HeatmapEventSchema.sessionId, WireType.LENGTH_DELIMITED);
358
+ this.encodeString(event.sessionId);
359
+ this.encodeTag(HeatmapEventSchema.timestamp, WireType.VARINT);
360
+ this.encodeUint64(event.timestamp);
361
+ this.encodeTag(HeatmapEventSchema.type, WireType.VARINT);
362
+ this.encodeUint32(HeatmapTypeEnum[event.type]);
363
+ this.encodeTag(HeatmapEventSchema.x, WireType.VARINT);
364
+ this.encodeUint32(event.x);
365
+ this.encodeTag(HeatmapEventSchema.y, WireType.VARINT);
366
+ this.encodeUint32(event.y);
367
+ this.encodeTag(HeatmapEventSchema.viewportWidth, WireType.VARINT);
368
+ this.encodeUint32(event.viewportWidth);
369
+ this.encodeTag(HeatmapEventSchema.viewportHeight, WireType.VARINT);
370
+ this.encodeUint32(event.viewportHeight);
371
+ this.encodeTag(HeatmapEventSchema.url, WireType.LENGTH_DELIMITED);
372
+ this.encodeString(event.url);
373
+ }
374
+ // Encode Breadcrumb
375
+ encodeBreadcrumb(breadcrumb) {
376
+ this.encodeTag(BreadcrumbSchema.timestamp, WireType.VARINT);
377
+ this.encodeUint64(breadcrumb.timestamp);
378
+ this.encodeTag(BreadcrumbSchema.category, WireType.VARINT);
379
+ this.encodeUint32(BreadcrumbCategoryEnum[breadcrumb.category]);
380
+ this.encodeTag(BreadcrumbSchema.message, WireType.LENGTH_DELIMITED);
381
+ this.encodeString(breadcrumb.message);
382
+ if (breadcrumb.data) {
383
+ for (const [key, value] of Object.entries(breadcrumb.data)) {
384
+ this.encodeTag(BreadcrumbSchema.data, WireType.LENGTH_DELIMITED);
385
+ this.encodeMapEntry(key, JSON.stringify(value));
386
+ }
387
+ }
388
+ if (breadcrumb.level) {
389
+ this.encodeTag(BreadcrumbSchema.level, WireType.VARINT);
390
+ this.encodeUint32(BreadcrumbLevelEnum[breadcrumb.level]);
391
+ }
392
+ }
393
+ // Encode BrowserContext
394
+ encodeBrowserContext(context) {
395
+ this.encodeTag(BrowserContextSchema.userAgent, WireType.LENGTH_DELIMITED);
396
+ this.encodeString(context.userAgent);
397
+ this.encodeTag(BrowserContextSchema.url, WireType.LENGTH_DELIMITED);
398
+ this.encodeString(context.url);
399
+ this.encodeTag(BrowserContextSchema.timestamp, WireType.VARINT);
400
+ this.encodeUint64(context.timestamp);
401
+ this.encodeTag(BrowserContextSchema.viewport, WireType.LENGTH_DELIMITED);
402
+ const viewportEncoder = new _ProtobufEncoder();
403
+ viewportEncoder.encodeViewport(context.viewport);
404
+ this.encodeVarint(viewportEncoder.buffer.length);
405
+ this.buffer.push(...viewportEncoder.buffer);
406
+ }
407
+ // Encode ErrorEvent
408
+ encodeErrorEvent(error) {
409
+ this.encodeTag(ErrorEventSchema.sessionId, WireType.LENGTH_DELIMITED);
410
+ this.encodeString(error.sessionId);
411
+ this.encodeTag(ErrorEventSchema.timestamp, WireType.VARINT);
412
+ this.encodeUint64(error.timestamp);
413
+ this.encodeTag(ErrorEventSchema.message, WireType.LENGTH_DELIMITED);
414
+ this.encodeString(error.message);
415
+ if (error.filename) {
416
+ this.encodeTag(ErrorEventSchema.filename, WireType.LENGTH_DELIMITED);
417
+ this.encodeString(error.filename);
418
+ }
419
+ if (error.lineno) {
420
+ this.encodeTag(ErrorEventSchema.lineno, WireType.VARINT);
421
+ this.encodeUint32(error.lineno);
422
+ }
423
+ if (error.colno) {
424
+ this.encodeTag(ErrorEventSchema.colno, WireType.VARINT);
425
+ this.encodeUint32(error.colno);
426
+ }
427
+ if (error.stack) {
428
+ this.encodeTag(ErrorEventSchema.stack, WireType.LENGTH_DELIMITED);
429
+ this.encodeString(error.stack);
430
+ }
431
+ this.encodeTag(ErrorEventSchema.severity, WireType.VARINT);
432
+ this.encodeUint32(error.severity);
433
+ for (const breadcrumb of error.breadcrumbs) {
434
+ this.encodeTag(ErrorEventSchema.breadcrumbs, WireType.LENGTH_DELIMITED);
435
+ const breadcrumbEncoder = new _ProtobufEncoder();
436
+ breadcrumbEncoder.encodeBreadcrumb(breadcrumb);
437
+ this.encodeVarint(breadcrumbEncoder.buffer.length);
438
+ this.buffer.push(...breadcrumbEncoder.buffer);
439
+ }
440
+ this.encodeTag(ErrorEventSchema.browserContext, WireType.LENGTH_DELIMITED);
441
+ const browserContextEncoder = new _ProtobufEncoder();
442
+ browserContextEncoder.encodeBrowserContext(error.browserContext);
443
+ this.encodeVarint(browserContextEncoder.buffer.length);
444
+ this.buffer.push(...browserContextEncoder.buffer);
445
+ if (error.tags) {
446
+ for (const [key, value] of Object.entries(error.tags)) {
447
+ this.encodeTag(ErrorEventSchema.tags, WireType.LENGTH_DELIMITED);
448
+ this.encodeMapEntry(key, value);
449
+ }
450
+ }
451
+ if (error.extra) {
452
+ for (const [key, value] of Object.entries(error.extra)) {
453
+ this.encodeTag(ErrorEventSchema.extra, WireType.LENGTH_DELIMITED);
454
+ this.encodeMapEntry(key, JSON.stringify(value));
455
+ }
456
+ }
457
+ }
458
+ // Public encoding methods
459
+ encodeEventBatch(batch) {
460
+ this.buffer = [];
461
+ for (const event of batch.events) {
462
+ this.encodeTag(EventBatchSchema.events, WireType.LENGTH_DELIMITED);
463
+ const eventEncoder = new _ProtobufEncoder();
464
+ eventEncoder.encodeTrackEvent(event);
465
+ this.encodeVarint(eventEncoder.buffer.length);
466
+ this.buffer.push(...eventEncoder.buffer);
467
+ }
468
+ for (const event of batch.heatmapEvents) {
469
+ this.encodeTag(EventBatchSchema.heatmapEvents, WireType.LENGTH_DELIMITED);
470
+ const eventEncoder = new _ProtobufEncoder();
471
+ eventEncoder.encodeHeatmapEvent(event);
472
+ this.encodeVarint(eventEncoder.buffer.length);
473
+ this.buffer.push(...eventEncoder.buffer);
474
+ }
475
+ return new Uint8Array(this.buffer);
476
+ }
477
+ encodeErrorBatch(batch) {
478
+ this.buffer = [];
479
+ for (const error of batch.errors) {
480
+ this.encodeTag(ErrorBatchSchema.errors, WireType.LENGTH_DELIMITED);
481
+ const errorEncoder = new _ProtobufEncoder();
482
+ errorEncoder.encodeErrorEvent(error);
483
+ this.encodeVarint(errorEncoder.buffer.length);
484
+ this.buffer.push(...errorEncoder.buffer);
485
+ }
486
+ return new Uint8Array(this.buffer);
487
+ }
488
+ };
489
+
490
+ // src/transport.ts
491
+ var Transport = class {
492
+ constructor(config) {
493
+ this.config = config;
494
+ this.encoder = new ProtobufEncoder();
495
+ }
496
+ async sendProtobuf(endpoint, data) {
497
+ try {
498
+ const response = await fetch(`${this.config.endpoint}${endpoint}`, {
499
+ method: "POST",
500
+ headers: {
501
+ "Content-Type": "application/x-protobuf",
502
+ "X-API-Key": this.config.apiKey
503
+ },
504
+ body: data
505
+ });
506
+ if (!response.ok) {
507
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
508
+ }
509
+ try {
510
+ const result = await response.json();
511
+ return result;
512
+ } catch {
513
+ return { success: true };
514
+ }
515
+ } catch (error) {
516
+ if (this.config.debug) {
517
+ console.error("[ExperienceSDK] Protobuf transport error:", error);
518
+ }
519
+ throw error;
520
+ }
521
+ }
522
+ async sendJSON(endpoint, data) {
523
+ try {
524
+ const response = await fetch(`${this.config.endpoint}${endpoint}`, {
525
+ method: "POST",
526
+ headers: {
527
+ "Content-Type": "application/json",
528
+ "X-API-Key": this.config.apiKey
529
+ },
530
+ body: JSON.stringify(data)
531
+ });
532
+ if (!response.ok) {
533
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
534
+ }
535
+ try {
536
+ const result = await response.json();
537
+ return result;
538
+ } catch {
539
+ return { success: true };
540
+ }
541
+ } catch (error) {
542
+ if (this.config.debug) {
543
+ console.error("[ExperienceSDK] JSON transport error:", error);
544
+ }
545
+ throw error;
546
+ }
547
+ }
548
+ async sendEventBatch(batch) {
549
+ try {
550
+ if (!this.config.debug) {
551
+ const protobufData = this.encoder.encodeEventBatch(batch);
552
+ return await this.sendProtobuf("/api/v1/events/ingest", protobufData);
553
+ }
554
+ } catch (error) {
555
+ if (this.config.debug) {
556
+ console.warn("[ExperienceSDK] Protobuf failed, falling back to JSON:", error);
557
+ }
558
+ }
559
+ return this.sendJSON("/api/v1/events/ingest/json", batch);
560
+ }
561
+ async sendErrorBatch(batch) {
562
+ try {
563
+ if (!this.config.debug) {
564
+ const protobufData = this.encoder.encodeErrorBatch(batch);
565
+ return await this.sendProtobuf("/api/v1/errors/ingest", protobufData);
566
+ }
567
+ } catch (error) {
568
+ if (this.config.debug) {
569
+ console.warn("[ExperienceSDK] Protobuf failed, falling back to JSON:", error);
570
+ }
571
+ }
572
+ return this.sendJSON("/api/v1/errors/ingest/json", batch);
573
+ }
574
+ async sendEventBatchBeacon(batch) {
575
+ if (typeof navigator !== "undefined" && navigator.sendBeacon) {
576
+ try {
577
+ const protobufData = this.encoder.encodeEventBatch(batch);
578
+ const blob = new Blob([protobufData], { type: "application/x-protobuf" });
579
+ const url = `${this.config.endpoint}/api/v1/events/ingest?api_key=${encodeURIComponent(this.config.apiKey)}`;
580
+ navigator.sendBeacon(url, blob);
581
+ } catch (error) {
582
+ if (this.config.debug) {
583
+ console.error("[ExperienceSDK] Beacon send error:", error);
584
+ }
585
+ try {
586
+ const jsonBlob = new Blob([JSON.stringify(batch)], { type: "application/json" });
587
+ const jsonUrl = `${this.config.endpoint}/api/v1/events/ingest/json?api_key=${encodeURIComponent(this.config.apiKey)}`;
588
+ navigator.sendBeacon(jsonUrl, jsonBlob);
589
+ } catch (fallbackError) {
590
+ }
591
+ }
592
+ }
593
+ }
594
+ async sendErrorBatchBeacon(batch) {
595
+ if (typeof navigator !== "undefined" && navigator.sendBeacon) {
596
+ try {
597
+ const protobufData = this.encoder.encodeErrorBatch(batch);
598
+ const blob = new Blob([protobufData], { type: "application/x-protobuf" });
599
+ const url = `${this.config.endpoint}/api/v1/errors/ingest?api_key=${encodeURIComponent(this.config.apiKey)}`;
600
+ navigator.sendBeacon(url, blob);
601
+ } catch (error) {
602
+ if (this.config.debug) {
603
+ console.error("[ExperienceSDK] Error beacon send error:", error);
604
+ }
605
+ try {
606
+ const jsonBlob = new Blob([JSON.stringify(batch)], { type: "application/json" });
607
+ const jsonUrl = `${this.config.endpoint}/api/v1/errors/ingest/json?api_key=${encodeURIComponent(this.config.apiKey)}`;
608
+ navigator.sendBeacon(jsonUrl, jsonBlob);
609
+ } catch (fallbackError) {
610
+ }
611
+ }
612
+ }
613
+ }
614
+ async fetchFlags(appId, profileId) {
615
+ try {
616
+ const queryParams = new URLSearchParams();
617
+ if (profileId) {
618
+ queryParams.set("profileId", profileId);
619
+ }
620
+ const url = `${this.config.endpoint}/api/v1/flags/${appId}/resolve${queryParams.toString() ? "?" + queryParams.toString() : ""}`;
621
+ const response = await fetch(url, {
622
+ method: "GET",
623
+ headers: {
624
+ "X-API-Key": this.config.apiKey
625
+ }
626
+ });
627
+ if (!response.ok) {
628
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
629
+ }
630
+ const result = await response.json();
631
+ return result.flags || {};
632
+ } catch (error) {
633
+ if (this.config.debug) {
634
+ console.error("[ExperienceSDK] Flag fetch error:", error);
635
+ }
636
+ return {};
637
+ }
638
+ }
639
+ };
640
+
641
+ // src/batch.ts
642
+ var EventBatcher = class {
643
+ constructor(transport, batchIntervalMs = 2e3, maxBatchSize = 50, debug = false) {
644
+ this.trackEvents = [];
645
+ this.heatmapEvents = [];
646
+ this.errorEvents = [];
647
+ this.flushTimer = null;
648
+ this.unsubscribeFns = [];
649
+ this.transport = transport;
650
+ this.batchIntervalMs = batchIntervalMs;
651
+ this.maxBatchSize = maxBatchSize;
652
+ this.debug = debug;
653
+ this.setupAutoFlush();
654
+ this.setupUnloadHandler();
655
+ }
656
+ setupAutoFlush() {
657
+ this.scheduleNextFlush();
658
+ }
659
+ scheduleNextFlush() {
660
+ if (this.flushTimer !== null) {
661
+ clearTimeout(this.flushTimer);
662
+ }
663
+ this.flushTimer = window.setTimeout(() => {
664
+ this.flush();
665
+ this.scheduleNextFlush();
666
+ }, this.batchIntervalMs);
667
+ }
668
+ setupUnloadHandler() {
669
+ if (typeof window === "undefined") return;
670
+ const handleUnload = () => {
671
+ this.flushBeacon();
672
+ };
673
+ window.addEventListener("beforeunload", handleUnload);
674
+ window.addEventListener("unload", handleUnload);
675
+ window.addEventListener("pagehide", handleUnload);
676
+ if (typeof document !== "undefined") {
677
+ const handleVisibilityChange = () => {
678
+ if (document.visibilityState === "hidden") {
679
+ this.flushBeacon();
680
+ }
681
+ };
682
+ document.addEventListener("visibilitychange", handleVisibilityChange);
683
+ this.unsubscribeFns.push(() => {
684
+ window.removeEventListener("beforeunload", handleUnload);
685
+ window.removeEventListener("unload", handleUnload);
686
+ window.removeEventListener("pagehide", handleUnload);
687
+ document.removeEventListener("visibilitychange", handleVisibilityChange);
688
+ });
689
+ } else {
690
+ this.unsubscribeFns.push(() => {
691
+ window.removeEventListener("beforeunload", handleUnload);
692
+ window.removeEventListener("unload", handleUnload);
693
+ window.removeEventListener("pagehide", handleUnload);
694
+ });
695
+ }
696
+ }
697
+ addTrackEvent(event) {
698
+ this.trackEvents.push(event);
699
+ if (this.debug) {
700
+ console.log("[ExperienceSDK] Track event added:", event);
701
+ }
702
+ if (this.trackEvents.length + this.heatmapEvents.length >= this.maxBatchSize) {
703
+ this.flush();
704
+ }
705
+ }
706
+ addHeatmapEvent(event) {
707
+ this.heatmapEvents.push(event);
708
+ if (this.debug) {
709
+ console.log("[ExperienceSDK] Heatmap event added:", event);
710
+ }
711
+ if (this.trackEvents.length + this.heatmapEvents.length >= this.maxBatchSize) {
712
+ this.flush();
713
+ }
714
+ }
715
+ addErrorEvent(error) {
716
+ this.errorEvents.push(error);
717
+ if (this.debug) {
718
+ console.log("[ExperienceSDK] Error event added:", error);
719
+ }
720
+ if (error.severity >= 2) {
721
+ this.flushErrors();
722
+ } else if (this.errorEvents.length >= this.maxBatchSize) {
723
+ this.flushErrors();
724
+ }
725
+ }
726
+ async flush() {
727
+ if (this.trackEvents.length === 0 && this.heatmapEvents.length === 0) {
728
+ return;
729
+ }
730
+ const batch = {
731
+ events: [...this.trackEvents],
732
+ heatmapEvents: [...this.heatmapEvents]
733
+ };
734
+ this.trackEvents = [];
735
+ this.heatmapEvents = [];
736
+ try {
737
+ const result = await this.transport.sendEventBatch(batch);
738
+ if (this.debug) {
739
+ console.log("[ExperienceSDK] Event batch sent successfully:", result);
740
+ }
741
+ } catch (error) {
742
+ if (this.debug) {
743
+ console.error("[ExperienceSDK] Failed to send event batch:", error);
744
+ }
745
+ this.trackEvents.unshift(...batch.events);
746
+ this.heatmapEvents.unshift(...batch.heatmapEvents);
747
+ this.trackEvents = this.trackEvents.slice(0, this.maxBatchSize * 2);
748
+ this.heatmapEvents = this.heatmapEvents.slice(0, this.maxBatchSize * 2);
749
+ }
750
+ }
751
+ async flushErrors() {
752
+ if (this.errorEvents.length === 0) {
753
+ return;
754
+ }
755
+ const batch = {
756
+ errors: [...this.errorEvents]
757
+ };
758
+ this.errorEvents = [];
759
+ try {
760
+ const result = await this.transport.sendErrorBatch(batch);
761
+ if (this.debug) {
762
+ console.log("[ExperienceSDK] Error batch sent successfully:", result);
763
+ }
764
+ } catch (error) {
765
+ if (this.debug) {
766
+ console.error("[ExperienceSDK] Failed to send error batch:", error);
767
+ }
768
+ this.errorEvents.unshift(...batch.errors);
769
+ this.errorEvents = this.errorEvents.slice(0, this.maxBatchSize * 2);
770
+ }
771
+ }
772
+ flushBeacon() {
773
+ if (this.trackEvents.length > 0 || this.heatmapEvents.length > 0) {
774
+ const eventBatch = {
775
+ events: [...this.trackEvents],
776
+ heatmapEvents: [...this.heatmapEvents]
777
+ };
778
+ this.transport.sendEventBatchBeacon(eventBatch);
779
+ this.trackEvents = [];
780
+ this.heatmapEvents = [];
781
+ }
782
+ if (this.errorEvents.length > 0) {
783
+ const errorBatch = {
784
+ errors: [...this.errorEvents]
785
+ };
786
+ this.transport.sendErrorBatchBeacon(errorBatch);
787
+ this.errorEvents = [];
788
+ }
789
+ }
790
+ getPendingEventCount() {
791
+ return this.trackEvents.length + this.heatmapEvents.length;
792
+ }
793
+ getPendingErrorCount() {
794
+ return this.errorEvents.length;
795
+ }
796
+ async shutdown() {
797
+ if (this.flushTimer !== null) {
798
+ clearTimeout(this.flushTimer);
799
+ this.flushTimer = null;
800
+ }
801
+ this.unsubscribeFns.forEach((fn) => fn());
802
+ this.unsubscribeFns = [];
803
+ await Promise.all([
804
+ this.flush(),
805
+ this.flushErrors()
806
+ ]);
807
+ }
808
+ // Force immediate flush for testing/debugging
809
+ async forceFlush() {
810
+ await Promise.all([
811
+ this.flush(),
812
+ this.flushErrors()
813
+ ]);
814
+ }
815
+ };
816
+
1
817
  // src/breadcrumbs.ts
2
818
  var MAX_BREADCRUMBS = 50;
3
819
  var BreadcrumbManager = class {
4
820
  constructor() {
5
- this.buffer = [];
821
+ this.breadcrumbs = [];
822
+ this.unsubscribeFns = [];
823
+ this.originalConsole = null;
6
824
  this.originalFetch = null;
7
- this.originalXhrOpen = null;
8
- this.clickHandler = null;
825
+ this.isEnabled = false;
826
+ }
827
+ enable() {
828
+ if (this.isEnabled) return;
829
+ this.isEnabled = true;
830
+ this.setupConsoleWrapping();
831
+ this.setupFetchWrapping();
832
+ this.setupXHRWrapping();
833
+ this.setupNavigationListeners();
834
+ this.setupClickListener();
835
+ }
836
+ disable() {
837
+ if (!this.isEnabled) return;
838
+ this.isEnabled = false;
839
+ this.unsubscribeFns.forEach((fn) => fn());
840
+ this.unsubscribeFns = [];
841
+ this.restoreConsole();
842
+ this.restoreFetch();
843
+ }
844
+ addBreadcrumb(breadcrumb) {
845
+ const fullBreadcrumb = {
846
+ ...breadcrumb,
847
+ timestamp: Date.now()
848
+ };
849
+ this.breadcrumbs.push(fullBreadcrumb);
850
+ if (this.breadcrumbs.length > MAX_BREADCRUMBS) {
851
+ this.breadcrumbs = this.breadcrumbs.slice(-MAX_BREADCRUMBS);
852
+ }
853
+ }
854
+ setupConsoleWrapping() {
855
+ if (typeof console === "undefined") return;
9
856
  this.originalConsole = {
10
- log: console.log,
11
857
  warn: console.warn,
12
858
  error: console.error
13
859
  };
860
+ console.warn = (...args) => {
861
+ this.addBreadcrumb({
862
+ category: "console",
863
+ message: args.map((arg) => String(arg)).join(" "),
864
+ level: "warning"
865
+ });
866
+ this.originalConsole.warn.apply(console, args);
867
+ };
868
+ console.error = (...args) => {
869
+ this.addBreadcrumb({
870
+ category: "console",
871
+ message: args.map((arg) => String(arg)).join(" "),
872
+ level: "error"
873
+ });
874
+ this.originalConsole.error.apply(console, args);
875
+ };
876
+ }
877
+ restoreConsole() {
878
+ if (this.originalConsole && typeof console !== "undefined") {
879
+ console.warn = this.originalConsole.warn;
880
+ console.error = this.originalConsole.error;
881
+ this.originalConsole = null;
882
+ }
883
+ }
884
+ setupFetchWrapping() {
885
+ if (typeof fetch === "undefined" || typeof window === "undefined") return;
886
+ this.originalFetch = window.fetch;
887
+ window.fetch = async (input, init) => {
888
+ const url = typeof input === "string" ? input : input.toString();
889
+ const method = init?.method || "GET";
890
+ const startTime = Date.now();
891
+ try {
892
+ const response = await this.originalFetch(input, init);
893
+ const duration = Date.now() - startTime;
894
+ this.addBreadcrumb({
895
+ category: "fetch",
896
+ message: `${method} ${url}`,
897
+ data: {
898
+ url,
899
+ method,
900
+ status: response.status,
901
+ duration: `${duration}ms`
902
+ },
903
+ level: response.ok ? "info" : "error"
904
+ });
905
+ return response;
906
+ } catch (error) {
907
+ const duration = Date.now() - startTime;
908
+ this.addBreadcrumb({
909
+ category: "fetch",
910
+ message: `${method} ${url} (failed)`,
911
+ data: {
912
+ url,
913
+ method,
914
+ error: error instanceof Error ? error.message : String(error),
915
+ duration: `${duration}ms`
916
+ },
917
+ level: "error"
918
+ });
919
+ throw error;
920
+ }
921
+ };
922
+ }
923
+ restoreFetch() {
924
+ if (this.originalFetch && typeof window !== "undefined") {
925
+ window.fetch = this.originalFetch;
926
+ this.originalFetch = null;
927
+ }
928
+ }
929
+ setupXHRWrapping() {
930
+ if (typeof XMLHttpRequest === "undefined") return;
931
+ const originalOpen = XMLHttpRequest.prototype.open;
932
+ const originalSend = XMLHttpRequest.prototype.send;
933
+ XMLHttpRequest.prototype.open = function(method, url, async, user, password) {
934
+ this.__method = method;
935
+ this.__url = url.toString();
936
+ this.__startTime = Date.now();
937
+ return originalOpen.apply(this, arguments);
938
+ };
939
+ XMLHttpRequest.prototype.send = function(body) {
940
+ const xhr = this;
941
+ const method = xhr.__method || "GET";
942
+ const url = xhr.__url || "";
943
+ const originalOnReadyStateChange = xhr.onreadystatechange;
944
+ xhr.onreadystatechange = function() {
945
+ if (xhr.readyState === 4) {
946
+ const startTime = xhr.__startTime || Date.now();
947
+ const duration = Date.now() - startTime;
948
+ const breadcrumbManager = globalThis.__experienceBreadcrumbs;
949
+ if (breadcrumbManager) {
950
+ breadcrumbManager.addBreadcrumb({
951
+ category: "xhr",
952
+ message: `${method} ${url}`,
953
+ data: {
954
+ url,
955
+ method,
956
+ status: xhr.status,
957
+ duration: `${duration}ms`
958
+ },
959
+ level: xhr.status >= 200 && xhr.status < 300 ? "info" : "error"
960
+ });
961
+ }
962
+ }
963
+ if (originalOnReadyStateChange) {
964
+ originalOnReadyStateChange.apply(xhr, arguments);
965
+ }
966
+ };
967
+ return originalSend.apply(this, arguments);
968
+ };
969
+ globalThis.__experienceBreadcrumbs = this;
970
+ }
971
+ setupNavigationListeners() {
972
+ if (typeof window === "undefined" || typeof history === "undefined") return;
973
+ const handlePopState = () => {
974
+ this.addBreadcrumb({
975
+ category: "navigation",
976
+ message: "Navigation (popstate)",
977
+ data: {
978
+ url: window.location.href,
979
+ type: "popstate"
980
+ }
981
+ });
982
+ };
983
+ window.addEventListener("popstate", handlePopState);
984
+ this.unsubscribeFns.push(() => window.removeEventListener("popstate", handlePopState));
985
+ const originalPushState = history.pushState;
986
+ const originalReplaceState = history.replaceState;
987
+ history.pushState = function(...args) {
988
+ const breadcrumbManager = globalThis.__experienceBreadcrumbs;
989
+ if (breadcrumbManager) {
990
+ breadcrumbManager.addBreadcrumb({
991
+ category: "navigation",
992
+ message: "Navigation (pushState)",
993
+ data: {
994
+ url: args[2] || window.location.href,
995
+ type: "pushState"
996
+ }
997
+ });
998
+ }
999
+ return originalPushState.apply(this, args);
1000
+ };
1001
+ history.replaceState = function(...args) {
1002
+ const breadcrumbManager = globalThis.__experienceBreadcrumbs;
1003
+ if (breadcrumbManager) {
1004
+ breadcrumbManager.addBreadcrumb({
1005
+ category: "navigation",
1006
+ message: "Navigation (replaceState)",
1007
+ data: {
1008
+ url: args[2] || window.location.href,
1009
+ type: "replaceState"
1010
+ }
1011
+ });
1012
+ }
1013
+ return originalReplaceState.apply(this, args);
1014
+ };
1015
+ this.unsubscribeFns.push(() => {
1016
+ history.pushState = originalPushState;
1017
+ history.replaceState = originalReplaceState;
1018
+ });
1019
+ }
1020
+ setupClickListener() {
1021
+ if (typeof document === "undefined") return;
1022
+ const handleClick = (event) => {
1023
+ const target = event.target;
1024
+ if (!target) return;
1025
+ const tagName = target.tagName?.toLowerCase();
1026
+ const selector = this.getElementSelector(target);
1027
+ this.addBreadcrumb({
1028
+ category: "click",
1029
+ message: `Click on ${tagName}`,
1030
+ data: {
1031
+ selector,
1032
+ tag: tagName,
1033
+ text: target.textContent?.slice(0, 100) || "",
1034
+ x: event.clientX,
1035
+ y: event.clientY
1036
+ }
1037
+ });
1038
+ };
1039
+ document.addEventListener("click", handleClick, true);
1040
+ this.unsubscribeFns.push(() => document.removeEventListener("click", handleClick, true));
1041
+ }
1042
+ getElementSelector(element) {
1043
+ try {
1044
+ const tag = element.tagName.toLowerCase();
1045
+ const id = element.id ? `#${element.id}` : "";
1046
+ const className = element.className && typeof element.className === "string" ? `.${element.className.split(" ").filter((c) => c.trim()).join(".")}` : "";
1047
+ return `${tag}${id}${className}`.slice(0, 100);
1048
+ } catch {
1049
+ return element.tagName?.toLowerCase() || "unknown";
1050
+ }
1051
+ }
1052
+ getBreadcrumbs() {
1053
+ return [...this.breadcrumbs];
1054
+ }
1055
+ clear() {
1056
+ this.breadcrumbs = [];
1057
+ }
1058
+ addCustomBreadcrumb(category, message, data) {
1059
+ this.addBreadcrumb({
1060
+ category,
1061
+ message,
1062
+ data
1063
+ });
1064
+ }
1065
+ };
1066
+
1067
+ // src/heatmap.ts
1068
+ var MOUSEMOVE_THROTTLE_MS = 100;
1069
+ var HOVER_THRESHOLD_MS = 500;
1070
+ var HeatmapTracker = class {
1071
+ constructor(batcher, sessionId) {
1072
+ this.isEnabled = false;
1073
+ this.unsubscribeFns = [];
1074
+ this.lastMouseMoveTime = 0;
1075
+ this.hoverTimer = null;
1076
+ this.lastHoverPosition = null;
1077
+ this.batcher = batcher;
1078
+ this.sessionId = sessionId;
1079
+ }
1080
+ enable() {
1081
+ if (this.isEnabled || typeof document === "undefined") return;
1082
+ this.isEnabled = true;
1083
+ this.setupMouseMoveTracking();
1084
+ this.setupClickTracking();
1085
+ this.setupHoverTracking();
1086
+ }
1087
+ disable() {
1088
+ if (!this.isEnabled) return;
1089
+ this.isEnabled = false;
1090
+ if (this.hoverTimer !== null) {
1091
+ clearTimeout(this.hoverTimer);
1092
+ this.hoverTimer = null;
1093
+ }
1094
+ this.unsubscribeFns.forEach((fn) => fn());
1095
+ this.unsubscribeFns = [];
1096
+ }
1097
+ setupMouseMoveTracking() {
1098
+ const handleMouseMove = (event) => {
1099
+ const now = Date.now();
1100
+ if (now - this.lastMouseMoveTime < MOUSEMOVE_THROTTLE_MS) {
1101
+ return;
1102
+ }
1103
+ this.lastMouseMoveTime = now;
1104
+ const viewport = this.getViewportSize();
1105
+ const heatmapEvent = {
1106
+ sessionId: this.sessionId,
1107
+ timestamp: now,
1108
+ type: "move",
1109
+ x: event.clientX,
1110
+ y: event.clientY,
1111
+ viewportWidth: viewport.width,
1112
+ viewportHeight: viewport.height,
1113
+ url: window.location.href
1114
+ };
1115
+ this.batcher.addHeatmapEvent(heatmapEvent);
1116
+ };
1117
+ document.addEventListener("mousemove", handleMouseMove, { passive: true });
1118
+ this.unsubscribeFns.push(
1119
+ () => document.removeEventListener("mousemove", handleMouseMove)
1120
+ );
1121
+ }
1122
+ setupClickTracking() {
1123
+ const handleClick = (event) => {
1124
+ const viewport = this.getViewportSize();
1125
+ const heatmapEvent = {
1126
+ sessionId: this.sessionId,
1127
+ timestamp: Date.now(),
1128
+ type: "click",
1129
+ x: event.clientX,
1130
+ y: event.clientY,
1131
+ viewportWidth: viewport.width,
1132
+ viewportHeight: viewport.height,
1133
+ url: window.location.href
1134
+ };
1135
+ this.batcher.addHeatmapEvent(heatmapEvent);
1136
+ };
1137
+ document.addEventListener("click", handleClick, { passive: true });
1138
+ this.unsubscribeFns.push(
1139
+ () => document.removeEventListener("click", handleClick)
1140
+ );
1141
+ }
1142
+ setupHoverTracking() {
1143
+ const handleMouseEnter = (event) => {
1144
+ const x = event.clientX;
1145
+ const y = event.clientY;
1146
+ this.lastHoverPosition = { x, y };
1147
+ this.hoverTimer = window.setTimeout(() => {
1148
+ if (this.lastHoverPosition && Math.abs(this.lastHoverPosition.x - x) < 50 && Math.abs(this.lastHoverPosition.y - y) < 50) {
1149
+ const viewport = this.getViewportSize();
1150
+ const heatmapEvent = {
1151
+ sessionId: this.sessionId,
1152
+ timestamp: Date.now(),
1153
+ type: "hover",
1154
+ x: this.lastHoverPosition.x,
1155
+ y: this.lastHoverPosition.y,
1156
+ viewportWidth: viewport.width,
1157
+ viewportHeight: viewport.height,
1158
+ url: window.location.href
1159
+ };
1160
+ this.batcher.addHeatmapEvent(heatmapEvent);
1161
+ }
1162
+ }, HOVER_THRESHOLD_MS);
1163
+ };
1164
+ const handleMouseMove = (event) => {
1165
+ this.lastHoverPosition = { x: event.clientX, y: event.clientY };
1166
+ };
1167
+ const handleMouseLeave = () => {
1168
+ if (this.hoverTimer !== null) {
1169
+ clearTimeout(this.hoverTimer);
1170
+ this.hoverTimer = null;
1171
+ }
1172
+ this.lastHoverPosition = null;
1173
+ };
1174
+ let lastHoverMoveTime = 0;
1175
+ const throttledMouseMove = (event) => {
1176
+ const now = Date.now();
1177
+ if (now - lastHoverMoveTime >= 100) {
1178
+ lastHoverMoveTime = now;
1179
+ handleMouseMove(event);
1180
+ }
1181
+ };
1182
+ document.addEventListener("mouseenter", handleMouseEnter, { passive: true });
1183
+ document.addEventListener("mousemove", throttledMouseMove, { passive: true });
1184
+ document.addEventListener("mouseleave", handleMouseLeave, { passive: true });
1185
+ this.unsubscribeFns.push(() => {
1186
+ document.removeEventListener("mouseenter", handleMouseEnter);
1187
+ document.removeEventListener("mousemove", throttledMouseMove);
1188
+ document.removeEventListener("mouseleave", handleMouseLeave);
1189
+ });
1190
+ }
1191
+ getViewportSize() {
1192
+ if (typeof window === "undefined") {
1193
+ return { width: 1920, height: 1080 };
1194
+ }
1195
+ return {
1196
+ width: window.innerWidth || document.documentElement.clientWidth || 1920,
1197
+ height: window.innerHeight || document.documentElement.clientHeight || 1080
1198
+ };
1199
+ }
1200
+ updateSessionId(sessionId) {
1201
+ this.sessionId = sessionId;
1202
+ }
1203
+ // Manual event tracking for programmatic use
1204
+ trackClick(x, y) {
1205
+ if (!this.isEnabled) return;
1206
+ const viewport = this.getViewportSize();
1207
+ const heatmapEvent = {
1208
+ sessionId: this.sessionId,
1209
+ timestamp: Date.now(),
1210
+ type: "click",
1211
+ x: Math.round(x),
1212
+ y: Math.round(y),
1213
+ viewportWidth: viewport.width,
1214
+ viewportHeight: viewport.height,
1215
+ url: window.location.href
1216
+ };
1217
+ this.batcher.addHeatmapEvent(heatmapEvent);
1218
+ }
1219
+ trackHover(x, y) {
1220
+ if (!this.isEnabled) return;
1221
+ const viewport = this.getViewportSize();
1222
+ const heatmapEvent = {
1223
+ sessionId: this.sessionId,
1224
+ timestamp: Date.now(),
1225
+ type: "hover",
1226
+ x: Math.round(x),
1227
+ y: Math.round(y),
1228
+ viewportWidth: viewport.width,
1229
+ viewportHeight: viewport.height,
1230
+ url: window.location.href
1231
+ };
1232
+ this.batcher.addHeatmapEvent(heatmapEvent);
1233
+ }
1234
+ };
1235
+
1236
+ // src/errors.ts
1237
+ var ErrorCapture = class {
1238
+ constructor(batcher, breadcrumbManager, sessionId) {
1239
+ this.isEnabled = false;
1240
+ this.unsubscribeFns = [];
1241
+ this.originalWindowError = null;
1242
+ this.originalUnhandledRejection = null;
1243
+ this.batcher = batcher;
1244
+ this.breadcrumbManager = breadcrumbManager;
1245
+ this.sessionId = sessionId;
1246
+ }
1247
+ enable() {
1248
+ if (this.isEnabled || typeof window === "undefined") return;
1249
+ this.isEnabled = true;
1250
+ this.setupGlobalErrorHandler();
1251
+ this.setupUnhandledRejectionHandler();
1252
+ }
1253
+ disable() {
1254
+ if (!this.isEnabled) return;
1255
+ this.isEnabled = false;
1256
+ this.restoreOriginalHandlers();
1257
+ this.unsubscribeFns.forEach((fn) => fn());
1258
+ this.unsubscribeFns = [];
1259
+ }
1260
+ setupGlobalErrorHandler() {
1261
+ this.originalWindowError = window.onerror;
1262
+ window.onerror = (message, filename, lineno, colno, error) => {
1263
+ this.captureError({
1264
+ message: typeof message === "string" ? message : "Unknown error",
1265
+ filename,
1266
+ lineno,
1267
+ colno,
1268
+ stack: error?.stack,
1269
+ severity: 2 /* ERROR */
1270
+ });
1271
+ if (this.originalWindowError) {
1272
+ return this.originalWindowError.call(window, message, filename, lineno, colno, error);
1273
+ }
1274
+ return false;
1275
+ };
1276
+ }
1277
+ setupUnhandledRejectionHandler() {
1278
+ const handleUnhandledRejection = (event) => {
1279
+ let message = "Unhandled Promise Rejection";
1280
+ let stack;
1281
+ if (event.reason) {
1282
+ if (event.reason instanceof Error) {
1283
+ message = event.reason.message || "Unhandled Promise Rejection";
1284
+ stack = event.reason.stack;
1285
+ } else if (typeof event.reason === "string") {
1286
+ message = event.reason;
1287
+ } else {
1288
+ try {
1289
+ message = JSON.stringify(event.reason);
1290
+ } catch {
1291
+ message = "Unhandled Promise Rejection (non-serializable reason)";
1292
+ }
1293
+ }
1294
+ }
1295
+ this.captureError({
1296
+ message,
1297
+ stack,
1298
+ severity: 2 /* ERROR */
1299
+ });
1300
+ if (this.originalUnhandledRejection) {
1301
+ this.originalUnhandledRejection.call(window, event);
1302
+ }
1303
+ };
1304
+ this.originalUnhandledRejection = window.onunhandledrejection;
1305
+ window.addEventListener("unhandledrejection", handleUnhandledRejection);
1306
+ this.unsubscribeFns.push(() => {
1307
+ window.removeEventListener("unhandledrejection", handleUnhandledRejection);
1308
+ });
1309
+ }
1310
+ restoreOriginalHandlers() {
1311
+ if (typeof window !== "undefined") {
1312
+ if (this.originalWindowError !== null) {
1313
+ window.onerror = this.originalWindowError;
1314
+ this.originalWindowError = null;
1315
+ }
1316
+ if (this.originalUnhandledRejection !== null) {
1317
+ window.onunhandledrejection = this.originalUnhandledRejection;
1318
+ this.originalUnhandledRejection = null;
1319
+ }
1320
+ }
1321
+ }
1322
+ getBrowserContext() {
1323
+ return {
1324
+ userAgent: typeof navigator !== "undefined" ? navigator.userAgent : "Unknown",
1325
+ url: typeof window !== "undefined" ? window.location.href : "Unknown",
1326
+ timestamp: Date.now(),
1327
+ viewport: {
1328
+ width: typeof window !== "undefined" ? window.innerWidth || 1920 : 1920,
1329
+ height: typeof window !== "undefined" ? window.innerHeight || 1080 : 1080
1330
+ }
1331
+ };
1332
+ }
1333
+ captureError(errorData) {
1334
+ const errorEvent = {
1335
+ sessionId: this.sessionId,
1336
+ timestamp: Date.now(),
1337
+ message: errorData.message,
1338
+ filename: errorData.filename,
1339
+ lineno: errorData.lineno,
1340
+ colno: errorData.colno,
1341
+ stack: errorData.stack,
1342
+ severity: errorData.severity,
1343
+ breadcrumbs: this.breadcrumbManager.getBreadcrumbs(),
1344
+ browserContext: this.getBrowserContext(),
1345
+ tags: errorData.tags,
1346
+ extra: errorData.extra
1347
+ };
1348
+ this.batcher.addErrorEvent(errorEvent);
1349
+ this.breadcrumbManager.addCustomBreadcrumb("ui", `Error: ${errorData.message}`, {
1350
+ severity: Severity[errorData.severity].toLowerCase(),
1351
+ filename: errorData.filename,
1352
+ line: errorData.lineno
1353
+ });
1354
+ }
1355
+ // Public methods for manual error reporting
1356
+ captureException(error, options) {
1357
+ if (!this.isEnabled) return;
1358
+ const severity = options?.severity ?? 2 /* ERROR */;
1359
+ this.captureError({
1360
+ message: error.message || "Unknown error",
1361
+ stack: error.stack,
1362
+ severity,
1363
+ tags: options?.tags,
1364
+ extra: options?.extra
1365
+ });
1366
+ }
1367
+ captureMessage(message, severity = 0 /* INFO */, options) {
1368
+ if (!this.isEnabled) return;
1369
+ this.captureError({
1370
+ message,
1371
+ severity,
1372
+ tags: options?.tags,
1373
+ extra: options?.extra
1374
+ });
1375
+ }
1376
+ captureBreadcrumb(message, category = "ui", data) {
1377
+ this.breadcrumbManager.addCustomBreadcrumb(category, message, data);
1378
+ }
1379
+ updateSessionId(sessionId) {
1380
+ this.sessionId = sessionId;
1381
+ }
1382
+ // Utility method to extract error info from various error types
1383
+ extractErrorInfo(error) {
1384
+ if (error instanceof Error) {
1385
+ return {
1386
+ message: error.message || "Unknown error",
1387
+ stack: error.stack
1388
+ };
1389
+ }
1390
+ if (typeof error === "string") {
1391
+ return { message: error };
1392
+ }
1393
+ if (error && typeof error === "object") {
1394
+ try {
1395
+ return { message: JSON.stringify(error) };
1396
+ } catch {
1397
+ return { message: "Non-serializable error object" };
1398
+ }
1399
+ }
1400
+ return { message: "Unknown error type" };
1401
+ }
1402
+ // Helper method for severity string to enum conversion
1403
+ static getSeverityFromString(severity) {
1404
+ switch (severity.toLowerCase()) {
1405
+ case "info":
1406
+ return 0 /* INFO */;
1407
+ case "warning":
1408
+ case "warn":
1409
+ return 1 /* WARNING */;
1410
+ case "error":
1411
+ return 2 /* ERROR */;
1412
+ case "fatal":
1413
+ return 3 /* FATAL */;
1414
+ default:
1415
+ return 0 /* INFO */;
1416
+ }
1417
+ }
1418
+ // Test method for manual error triggering (useful for debugging)
1419
+ triggerTestError() {
1420
+ if (!this.isEnabled) return;
1421
+ try {
1422
+ throw new Error("Test error from ExperienceSDK");
1423
+ } catch (error) {
1424
+ this.captureException(error, {
1425
+ tags: { test: "true" },
1426
+ severity: 1 /* WARNING */
1427
+ });
1428
+ }
1429
+ }
1430
+ };
1431
+
1432
+ // src/auto-track.ts
1433
+ var AutoTracker = class {
1434
+ constructor(batcher, sessionId) {
1435
+ this.isEnabled = false;
1436
+ this.unsubscribeFns = [];
1437
+ this.lastUrl = null;
1438
+ this.pageLoadTime = Date.now();
1439
+ this.batcher = batcher;
1440
+ this.sessionId = sessionId;
1441
+ }
1442
+ enable() {
1443
+ if (this.isEnabled) return;
1444
+ this.isEnabled = true;
1445
+ this.trackInitialPageView();
1446
+ this.setupSPATracking();
1447
+ }
1448
+ disable() {
1449
+ if (!this.isEnabled) return;
1450
+ this.isEnabled = false;
1451
+ this.unsubscribeFns.forEach((fn) => fn());
1452
+ this.unsubscribeFns = [];
1453
+ }
1454
+ trackInitialPageView() {
1455
+ if (typeof window === "undefined") return;
1456
+ const loadTime = this.calculateLoadTime();
1457
+ this.trackPageView(loadTime);
1458
+ this.lastUrl = window.location.href;
1459
+ }
1460
+ calculateLoadTime() {
1461
+ if (typeof performance === "undefined" || !performance.timing) {
1462
+ return void 0;
1463
+ }
1464
+ const timing = performance.timing;
1465
+ if (timing.loadEventEnd && timing.navigationStart) {
1466
+ return timing.loadEventEnd - timing.navigationStart;
1467
+ }
1468
+ if (timing.domContentLoadedEventEnd && timing.navigationStart) {
1469
+ return timing.domContentLoadedEventEnd - timing.navigationStart;
1470
+ }
1471
+ return void 0;
1472
+ }
1473
+ setupSPATracking() {
1474
+ if (typeof window === "undefined" || typeof history === "undefined") return;
1475
+ const handlePopState = () => {
1476
+ setTimeout(() => {
1477
+ if (window.location.href !== this.lastUrl) {
1478
+ this.trackPageView();
1479
+ this.lastUrl = window.location.href;
1480
+ }
1481
+ }, 0);
1482
+ };
1483
+ window.addEventListener("popstate", handlePopState);
1484
+ this.unsubscribeFns.push(() => window.removeEventListener("popstate", handlePopState));
1485
+ const originalPushState = history.pushState;
1486
+ const originalReplaceState = history.replaceState;
1487
+ history.pushState = (...args) => {
1488
+ const result = originalPushState.apply(history, args);
1489
+ setTimeout(() => {
1490
+ if (window.location.href !== this.lastUrl) {
1491
+ this.trackPageView();
1492
+ this.lastUrl = window.location.href;
1493
+ }
1494
+ }, 0);
1495
+ return result;
1496
+ };
1497
+ history.replaceState = (...args) => {
1498
+ const result = originalReplaceState.apply(history, args);
1499
+ setTimeout(() => {
1500
+ if (window.location.href !== this.lastUrl) {
1501
+ this.trackPageView();
1502
+ this.lastUrl = window.location.href;
1503
+ }
1504
+ }, 0);
1505
+ return result;
1506
+ };
1507
+ this.unsubscribeFns.push(() => {
1508
+ history.pushState = originalPushState;
1509
+ history.replaceState = originalReplaceState;
1510
+ });
1511
+ }
1512
+ getPageContext(loadTime) {
1513
+ if (typeof window === "undefined" || typeof document === "undefined") {
1514
+ return {
1515
+ url: "unknown",
1516
+ title: "unknown",
1517
+ referrer: "",
1518
+ viewport: { width: 1920, height: 1080 }
1519
+ };
1520
+ }
1521
+ return {
1522
+ url: window.location.href,
1523
+ title: document.title || "",
1524
+ referrer: document.referrer || "",
1525
+ viewport: {
1526
+ width: window.innerWidth || document.documentElement.clientWidth || 1920,
1527
+ height: window.innerHeight || document.documentElement.clientHeight || 1080
1528
+ },
1529
+ loadTime
1530
+ };
1531
+ }
1532
+ trackPageView(loadTime) {
1533
+ const pageContext = this.getPageContext(loadTime);
1534
+ const trackEvent = {
1535
+ eventType: "page_view",
1536
+ sessionId: this.sessionId,
1537
+ timestamp: Date.now(),
1538
+ pageContext
1539
+ };
1540
+ this.batcher.addTrackEvent(trackEvent);
1541
+ }
1542
+ // Public method for manual page view tracking
1543
+ trackCurrentPage() {
1544
+ if (!this.isEnabled) return;
1545
+ this.trackPageView();
1546
+ if (typeof window !== "undefined") {
1547
+ this.lastUrl = window.location.href;
1548
+ }
1549
+ }
1550
+ // Track custom navigation events
1551
+ trackNavigation(eventType, metadata) {
1552
+ if (!this.isEnabled) return;
1553
+ const pageContext = this.getPageContext();
1554
+ const trackEvent = {
1555
+ eventType,
1556
+ sessionId: this.sessionId,
1557
+ timestamp: Date.now(),
1558
+ pageContext,
1559
+ metadata
1560
+ };
1561
+ this.batcher.addTrackEvent(trackEvent);
1562
+ }
1563
+ updateSessionId(sessionId) {
1564
+ this.sessionId = sessionId;
1565
+ }
1566
+ // Get current page info for debugging
1567
+ getCurrentPageInfo() {
1568
+ return this.getPageContext();
1569
+ }
1570
+ };
1571
+
1572
+ // src/data-t.ts
1573
+ var HOVER_THRESHOLD_MS2 = 500;
1574
+ var DataTTracker = class {
1575
+ constructor(batcher, sessionId) {
1576
+ this.isEnabled = false;
1577
+ this.unsubscribeFns = [];
1578
+ this.mutationObserver = null;
1579
+ this.intersectionObserver = null;
1580
+ this.visibleElements = /* @__PURE__ */ new Set();
1581
+ this.hoverTimers = /* @__PURE__ */ new Map();
1582
+ this.batcher = batcher;
1583
+ this.sessionId = sessionId;
14
1584
  }
15
- add(breadcrumb) {
16
- this.buffer.push({ ...breadcrumb, timestampMs: Date.now() });
17
- if (this.buffer.length > MAX_BREADCRUMBS) {
18
- this.buffer.shift();
1585
+ enable() {
1586
+ if (this.isEnabled || typeof document === "undefined") return;
1587
+ this.isEnabled = true;
1588
+ this.setupMutationObserver();
1589
+ this.setupEventDelegation();
1590
+ this.setupVisibilityTracking();
1591
+ this.setupHoverTracking();
1592
+ this.scanExistingElements();
1593
+ }
1594
+ disable() {
1595
+ if (!this.isEnabled) return;
1596
+ this.isEnabled = false;
1597
+ if (this.mutationObserver) {
1598
+ this.mutationObserver.disconnect();
1599
+ this.mutationObserver = null;
1600
+ }
1601
+ if (this.intersectionObserver) {
1602
+ this.intersectionObserver.disconnect();
1603
+ this.intersectionObserver = null;
19
1604
  }
1605
+ this.hoverTimers.forEach((timer) => clearTimeout(timer));
1606
+ this.hoverTimers.clear();
1607
+ this.unsubscribeFns.forEach((fn) => fn());
1608
+ this.unsubscribeFns = [];
1609
+ this.visibleElements.clear();
1610
+ }
1611
+ setupMutationObserver() {
1612
+ if (typeof MutationObserver === "undefined") return;
1613
+ this.mutationObserver = new MutationObserver((mutations) => {
1614
+ for (const mutation of mutations) {
1615
+ if (mutation.type === "childList") {
1616
+ mutation.addedNodes.forEach((node) => {
1617
+ if (node.nodeType === Node.ELEMENT_NODE) {
1618
+ const element = node;
1619
+ this.processNewElement(element);
1620
+ const descendants = element.querySelectorAll("[data-t]");
1621
+ descendants.forEach((desc) => this.processNewElement(desc));
1622
+ }
1623
+ });
1624
+ } else if (mutation.type === "attributes" && mutation.attributeName === "data-t") {
1625
+ const target = mutation.target;
1626
+ this.processNewElement(target);
1627
+ }
1628
+ }
1629
+ });
1630
+ this.mutationObserver.observe(document, {
1631
+ childList: true,
1632
+ subtree: true,
1633
+ attributes: true,
1634
+ attributeFilter: ["data-t"]
1635
+ });
20
1636
  }
21
- getAll() {
22
- return [...this.buffer];
1637
+ setupEventDelegation() {
1638
+ const handleClick = (event) => {
1639
+ const target = event.target;
1640
+ if (!target) return;
1641
+ const dataTElement = this.findDataTParent(target);
1642
+ if (dataTElement) {
1643
+ this.trackDataTEvent("click", dataTElement, {
1644
+ x: event.clientX,
1645
+ y: event.clientY
1646
+ });
1647
+ }
1648
+ };
1649
+ document.addEventListener("click", handleClick, true);
1650
+ this.unsubscribeFns.push(
1651
+ () => document.removeEventListener("click", handleClick, true)
1652
+ );
23
1653
  }
24
- install() {
25
- ["log", "warn", "error"].forEach((level) => {
26
- const original = this.originalConsole[level];
27
- console[level] = (...args) => {
28
- this.add({
29
- type: "console",
30
- category: level,
31
- message: args.map((a) => typeof a === "string" ? a : JSON.stringify(a)).join(" ")
1654
+ setupVisibilityTracking() {
1655
+ if (typeof IntersectionObserver === "undefined") return;
1656
+ this.intersectionObserver = new IntersectionObserver(
1657
+ (entries) => {
1658
+ entries.forEach((entry) => {
1659
+ const element = entry.target;
1660
+ const dataTValue = element.getAttribute("data-t");
1661
+ if (!dataTValue) return;
1662
+ if (entry.isIntersecting && !this.visibleElements.has(element)) {
1663
+ this.visibleElements.add(element);
1664
+ this.trackDataTEvent("view", element);
1665
+ } else if (!entry.isIntersecting && this.visibleElements.has(element)) {
1666
+ this.visibleElements.delete(element);
1667
+ this.trackDataTEvent("hide", element);
1668
+ }
32
1669
  });
33
- original.apply(console, args);
34
- };
1670
+ },
1671
+ {
1672
+ threshold: 0.1,
1673
+ // Trigger when at least 10% visible
1674
+ rootMargin: "50px"
1675
+ // Start observing 50px before entering viewport
1676
+ }
1677
+ );
1678
+ }
1679
+ setupHoverTracking() {
1680
+ const handleMouseEnter = (event) => {
1681
+ const target = event.target;
1682
+ const dataTElement = this.findDataTParent(target);
1683
+ if (!dataTElement) return;
1684
+ const timer = window.setTimeout(() => {
1685
+ this.trackDataTEvent("hover", dataTElement);
1686
+ this.hoverTimers.delete(dataTElement);
1687
+ }, HOVER_THRESHOLD_MS2);
1688
+ this.hoverTimers.set(dataTElement, timer);
1689
+ };
1690
+ const handleMouseLeave = (event) => {
1691
+ const target = event.target;
1692
+ const dataTElement = this.findDataTParent(target);
1693
+ if (!dataTElement) return;
1694
+ const timer = this.hoverTimers.get(dataTElement);
1695
+ if (timer) {
1696
+ clearTimeout(timer);
1697
+ this.hoverTimers.delete(dataTElement);
1698
+ }
1699
+ };
1700
+ document.addEventListener("mouseenter", handleMouseEnter, true);
1701
+ document.addEventListener("mouseleave", handleMouseLeave, true);
1702
+ this.unsubscribeFns.push(() => {
1703
+ document.removeEventListener("mouseenter", handleMouseEnter, true);
1704
+ document.removeEventListener("mouseleave", handleMouseLeave, true);
35
1705
  });
36
- if (typeof fetch !== "undefined") {
37
- this.originalFetch = fetch;
38
- const self = this;
39
- window.fetch = function(input, init) {
40
- const url = typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
41
- const method = init?.method || "GET";
42
- self.add({ type: "http", category: "fetch", message: `${method} ${url}` });
43
- return self.originalFetch.call(window, input, init);
44
- };
1706
+ }
1707
+ scanExistingElements() {
1708
+ const existingElements = document.querySelectorAll("[data-t]");
1709
+ existingElements.forEach((element) => this.processNewElement(element));
1710
+ }
1711
+ processNewElement(element) {
1712
+ const dataTValue = element.getAttribute("data-t");
1713
+ if (!dataTValue) return;
1714
+ if (this.intersectionObserver) {
1715
+ this.intersectionObserver.observe(element);
1716
+ }
1717
+ this.trackDataTEvent("discover", element);
1718
+ }
1719
+ findDataTParent(element) {
1720
+ let current = element;
1721
+ while (current) {
1722
+ if (current.hasAttribute("data-t")) {
1723
+ return current;
1724
+ }
1725
+ current = current.parentElement;
1726
+ }
1727
+ return null;
1728
+ }
1729
+ getElementSelector(element) {
1730
+ try {
1731
+ const tag = element.tagName.toLowerCase();
1732
+ const id = element.id ? `#${element.id}` : "";
1733
+ const classes = element.className && typeof element.className === "string" ? `.${element.className.split(" ").filter((c) => c.trim()).join(".")}` : "";
1734
+ let selector = `${tag}${id}${classes}`;
1735
+ const parent = element.parentElement;
1736
+ if (parent && !id) {
1737
+ const siblings = Array.from(parent.children).filter(
1738
+ (child) => child.tagName === element.tagName
1739
+ );
1740
+ if (siblings.length > 1) {
1741
+ const index = siblings.indexOf(element) + 1;
1742
+ selector += `:nth-child(${index})`;
1743
+ }
1744
+ }
1745
+ return selector.slice(0, 200);
1746
+ } catch (error) {
1747
+ return element.tagName?.toLowerCase() || "unknown";
45
1748
  }
46
- if (typeof XMLHttpRequest !== "undefined") {
47
- this.originalXhrOpen = XMLHttpRequest.prototype.open;
48
- const self = this;
49
- XMLHttpRequest.prototype.open = function(method, url) {
50
- self.add({ type: "http", category: "xhr", message: `${method} ${url}` });
51
- return self.originalXhrOpen.apply(this, arguments);
1749
+ }
1750
+ getElementContext(element, position) {
1751
+ return {
1752
+ selector: this.getElementSelector(element),
1753
+ dataT: element.getAttribute("data-t") || void 0,
1754
+ tag: element.tagName.toLowerCase(),
1755
+ position
1756
+ };
1757
+ }
1758
+ getPageContext() {
1759
+ if (typeof window === "undefined" || typeof document === "undefined") {
1760
+ return {
1761
+ url: "unknown",
1762
+ title: "unknown",
1763
+ referrer: "",
1764
+ viewport: { width: 1920, height: 1080 }
52
1765
  };
53
1766
  }
54
- this.clickHandler = (e) => {
55
- const target = e.target;
56
- if (!target) return;
57
- const tag = target.tagName?.toLowerCase() || "";
58
- const text = (target.textContent || "").slice(0, 100);
59
- this.add({ type: "ui", category: "click", message: `${tag}: ${text}` });
1767
+ return {
1768
+ url: window.location.href,
1769
+ title: document.title || "",
1770
+ referrer: document.referrer || "",
1771
+ viewport: {
1772
+ width: window.innerWidth || 1920,
1773
+ height: window.innerHeight || 1080
1774
+ }
60
1775
  };
61
- document.addEventListener("click", this.clickHandler, true);
62
1776
  }
63
- teardown() {
64
- console.log = this.originalConsole.log;
65
- console.warn = this.originalConsole.warn;
66
- console.error = this.originalConsole.error;
67
- if (this.originalFetch) {
68
- window.fetch = this.originalFetch;
1777
+ trackDataTEvent(eventType, element, position) {
1778
+ const dataTValue = element.getAttribute("data-t");
1779
+ if (!dataTValue) return;
1780
+ const elementContext = this.getElementContext(element, position);
1781
+ const pageContext = this.getPageContext();
1782
+ const trackEvent = {
1783
+ eventType: `data_t_${eventType}`,
1784
+ sessionId: this.sessionId,
1785
+ timestamp: Date.now(),
1786
+ pageContext,
1787
+ elementContext,
1788
+ metadata: {
1789
+ data_t: dataTValue,
1790
+ event_type: eventType
1791
+ }
1792
+ };
1793
+ this.batcher.addTrackEvent(trackEvent);
1794
+ }
1795
+ updateSessionId(sessionId) {
1796
+ this.sessionId = sessionId;
1797
+ }
1798
+ };
1799
+
1800
+ // src/flags.ts
1801
+ var FeatureFlagClient = class {
1802
+ constructor(transport, appId, debug = false) {
1803
+ this.profileId = null;
1804
+ this.flagCache = /* @__PURE__ */ new Map();
1805
+ this.cacheTimeoutMs = 5 * 60 * 1e3;
1806
+ // 5 minutes
1807
+ this.eventSource = null;
1808
+ this._backgroundFetchInProgress = false;
1809
+ this.transport = transport;
1810
+ this.appId = appId;
1811
+ this.debug = debug;
1812
+ }
1813
+ setProfileId(profileId) {
1814
+ this.profileId = profileId;
1815
+ this.flagCache.clear();
1816
+ }
1817
+ async fetchFlags() {
1818
+ try {
1819
+ const flags = await this.transport.fetchFlags(this.appId, this.profileId || void 0);
1820
+ const now = Date.now();
1821
+ for (const [key, value] of Object.entries(flags)) {
1822
+ this.flagCache.set(key, {
1823
+ value,
1824
+ fetchedAt: now
1825
+ });
1826
+ }
1827
+ if (this.debug) {
1828
+ console.log("[ExperienceSDK] Flags fetched:", flags);
1829
+ }
1830
+ } catch (error) {
1831
+ if (this.debug) {
1832
+ console.error("[ExperienceSDK] Failed to fetch flags:", error);
1833
+ }
1834
+ }
1835
+ }
1836
+ getFlag(key, defaultValue) {
1837
+ const cached = this.flagCache.get(key);
1838
+ if (cached && Date.now() - cached.fetchedAt < this.cacheTimeoutMs) {
1839
+ if (this.debug) {
1840
+ console.log(`[ExperienceSDK] Flag '${key}' from cache:`, cached.value);
1841
+ }
1842
+ return this.convertValue(cached.value, defaultValue, key);
1843
+ }
1844
+ this.fetchFlagsInBackground();
1845
+ if (this.debug) {
1846
+ console.log(`[ExperienceSDK] Flag '${key}' not in cache, returning default:`, defaultValue);
1847
+ }
1848
+ return defaultValue;
1849
+ }
1850
+ getFlagSync(key, defaultValue) {
1851
+ const cached = this.flagCache.get(key);
1852
+ if (cached) {
1853
+ return this.convertValue(cached.value, defaultValue, key);
1854
+ }
1855
+ return defaultValue;
1856
+ }
1857
+ convertValue(value, defaultValue, key) {
1858
+ try {
1859
+ if (typeof value === typeof defaultValue) {
1860
+ return value;
1861
+ }
1862
+ if (typeof defaultValue === "boolean") {
1863
+ if (typeof value === "string") {
1864
+ return value.toLowerCase() === "true" || value === "1";
1865
+ }
1866
+ return Boolean(value);
1867
+ }
1868
+ if (typeof defaultValue === "number") {
1869
+ const num = Number(value);
1870
+ return isNaN(num) ? defaultValue : num;
1871
+ }
1872
+ if (typeof defaultValue === "string") {
1873
+ return String(value);
1874
+ }
1875
+ if (typeof defaultValue === "object" && typeof value === "string") {
1876
+ try {
1877
+ return JSON.parse(value);
1878
+ } catch {
1879
+ return defaultValue;
1880
+ }
1881
+ }
1882
+ return value;
1883
+ } catch (error) {
1884
+ if (this.debug) {
1885
+ console.warn(`[ExperienceSDK] Failed to convert flag value for '${key}':`, error);
1886
+ }
1887
+ return defaultValue;
1888
+ }
1889
+ }
1890
+ fetchFlagsInBackground() {
1891
+ if (this._backgroundFetchInProgress) {
1892
+ return;
1893
+ }
1894
+ this._backgroundFetchInProgress = true;
1895
+ this.fetchFlags().finally(() => {
1896
+ this._backgroundFetchInProgress = false;
1897
+ });
1898
+ }
1899
+ // Initialize with immediate fetch
1900
+ async initialize() {
1901
+ await this.fetchFlags();
1902
+ }
1903
+ // Set up real-time flag updates via Server-Sent Events
1904
+ setupRealtimeUpdates(endpoint) {
1905
+ if (typeof EventSource === "undefined") {
1906
+ if (this.debug) {
1907
+ console.warn("[ExperienceSDK] EventSource not supported, real-time flag updates disabled");
1908
+ }
1909
+ return;
1910
+ }
1911
+ this.closeRealtimeUpdates();
1912
+ const baseEndpoint = endpoint || this.transport["config"].endpoint;
1913
+ const streamUrl = `${baseEndpoint}/api/v1/flags/${this.appId}/resolve/stream`;
1914
+ const url = new URL(streamUrl);
1915
+ if (this.profileId) {
1916
+ url.searchParams.set("profileId", this.profileId);
1917
+ }
1918
+ try {
1919
+ this.eventSource = new EventSource(url.toString());
1920
+ this.eventSource.onopen = () => {
1921
+ if (this.debug) {
1922
+ console.log("[ExperienceSDK] Real-time flag updates connected");
1923
+ }
1924
+ };
1925
+ this.eventSource.onmessage = (event) => {
1926
+ try {
1927
+ const data = JSON.parse(event.data);
1928
+ if (data.flags) {
1929
+ const now = Date.now();
1930
+ for (const [key, value] of Object.entries(data.flags)) {
1931
+ this.flagCache.set(key, {
1932
+ value,
1933
+ fetchedAt: now
1934
+ });
1935
+ }
1936
+ if (this.debug) {
1937
+ console.log("[ExperienceSDK] Flag updates received:", data.flags);
1938
+ }
1939
+ }
1940
+ } catch (error) {
1941
+ if (this.debug) {
1942
+ console.error("[ExperienceSDK] Failed to parse flag update:", error);
1943
+ }
1944
+ }
1945
+ };
1946
+ this.eventSource.onerror = (error) => {
1947
+ if (this.debug) {
1948
+ console.error("[ExperienceSDK] Real-time flag updates error:", error);
1949
+ }
1950
+ setTimeout(() => {
1951
+ if (this.eventSource?.readyState === EventSource.CLOSED) {
1952
+ this.setupRealtimeUpdates(endpoint);
1953
+ }
1954
+ }, 5e3);
1955
+ };
1956
+ } catch (error) {
1957
+ if (this.debug) {
1958
+ console.error("[ExperienceSDK] Failed to setup real-time flag updates:", error);
1959
+ }
1960
+ }
1961
+ }
1962
+ closeRealtimeUpdates() {
1963
+ if (this.eventSource) {
1964
+ this.eventSource.close();
1965
+ this.eventSource = null;
1966
+ if (this.debug) {
1967
+ console.log("[ExperienceSDK] Real-time flag updates disconnected");
1968
+ }
1969
+ }
1970
+ }
1971
+ // Clear all cached flags
1972
+ clearCache() {
1973
+ this.flagCache.clear();
1974
+ if (this.debug) {
1975
+ console.log("[ExperienceSDK] Flag cache cleared");
1976
+ }
1977
+ }
1978
+ // Get cache status for debugging
1979
+ getCacheInfo() {
1980
+ const keys = Array.from(this.flagCache.keys());
1981
+ const now = Date.now();
1982
+ let oldestEntry;
1983
+ let newestEntry;
1984
+ for (const [cacheKey, entry] of this.flagCache.entries()) {
1985
+ const age = now - entry.fetchedAt;
1986
+ if (!oldestEntry || age > oldestEntry.age) {
1987
+ oldestEntry = { key: cacheKey, age };
1988
+ }
1989
+ if (!newestEntry || age < newestEntry.age) {
1990
+ newestEntry = { key: cacheKey, age };
1991
+ }
69
1992
  }
70
- if (this.originalXhrOpen) {
71
- XMLHttpRequest.prototype.open = this.originalXhrOpen;
1993
+ return {
1994
+ size: this.flagCache.size,
1995
+ keys,
1996
+ oldestEntry,
1997
+ newestEntry
1998
+ };
1999
+ }
2000
+ // Check if a flag exists in cache
2001
+ hasCachedFlag(key) {
2002
+ return this.flagCache.has(key);
2003
+ }
2004
+ // Get all cached flags (for debugging)
2005
+ getAllCachedFlags() {
2006
+ const result = {};
2007
+ for (const [key, entry] of this.flagCache.entries()) {
2008
+ result[key] = entry.value;
72
2009
  }
73
- if (this.clickHandler) {
74
- document.removeEventListener("click", this.clickHandler, true);
2010
+ return result;
2011
+ }
2012
+ // Preload specific flags
2013
+ async preloadFlags(keys) {
2014
+ const missing = keys.filter((key) => !this.hasCachedFlag(key));
2015
+ if (missing.length > 0) {
2016
+ await this.fetchFlags();
75
2017
  }
76
2018
  }
2019
+ shutdown() {
2020
+ this.closeRealtimeUpdates();
2021
+ this.flagCache.clear();
2022
+ }
77
2023
  };
78
2024
 
79
2025
  // src/offline.ts
80
- var DB_NAME = "shellapps_experience";
81
- var STORE_NAME = "offline_queue";
2026
+ var DB_NAME = "ExperienceSDKOffline";
82
2027
  var DB_VERSION = 1;
83
- function openDB() {
84
- return new Promise((resolve, reject) => {
85
- const request = indexedDB.open(DB_NAME, DB_VERSION);
86
- request.onupgradeneeded = () => {
87
- const db = request.result;
88
- if (!db.objectStoreNames.contains(STORE_NAME)) {
89
- db.createObjectStore(STORE_NAME, { autoIncrement: true });
90
- }
91
- };
92
- request.onsuccess = () => resolve(request.result);
93
- request.onerror = () => reject(request.error);
94
- });
95
- }
96
- async function enqueueOffline(data) {
97
- const db = await openDB();
98
- const tx = db.transaction(STORE_NAME, "readwrite");
99
- tx.objectStore(STORE_NAME).add(data);
100
- await new Promise((resolve, reject) => {
101
- tx.oncomplete = () => resolve();
102
- tx.onerror = () => reject(tx.error);
103
- });
104
- db.close();
105
- }
106
- async function drainOfflineQueue(sendFn) {
107
- const db = await openDB();
108
- const tx = db.transaction(STORE_NAME, "readwrite");
109
- const store = tx.objectStore(STORE_NAME);
110
- const cursorReq = store.openCursor();
111
- await new Promise((resolve, reject) => {
112
- cursorReq.onsuccess = async () => {
113
- const cursor = cursorReq.result;
114
- if (!cursor) {
115
- resolve();
2028
+ var EVENTS_STORE = "events";
2029
+ var ERRORS_STORE = "errors";
2030
+ var MAX_OFFLINE_ITEMS = 1e3;
2031
+ var OfflineQueue = class {
2032
+ constructor(transport, debug = false) {
2033
+ this.db = null;
2034
+ this.isInitialized = false;
2035
+ this.unsubscribeFns = [];
2036
+ this.isProcessing = false;
2037
+ this.transport = transport;
2038
+ this.debug = debug;
2039
+ this.initialize();
2040
+ this.setupOnlineListener();
2041
+ }
2042
+ async initialize() {
2043
+ if (typeof indexedDB === "undefined") {
2044
+ if (this.debug) {
2045
+ console.warn("[ExperienceSDK] IndexedDB not available, offline queue disabled");
2046
+ }
2047
+ return;
2048
+ }
2049
+ try {
2050
+ this.db = await this.openDatabase();
2051
+ this.isInitialized = true;
2052
+ if (this.debug) {
2053
+ console.log("[ExperienceSDK] Offline queue initialized");
2054
+ }
2055
+ this.processStoredItems();
2056
+ } catch (error) {
2057
+ if (this.debug) {
2058
+ console.error("[ExperienceSDK] Failed to initialize offline queue:", error);
2059
+ }
2060
+ }
2061
+ }
2062
+ openDatabase() {
2063
+ return new Promise((resolve, reject) => {
2064
+ const request = indexedDB.open(DB_NAME, DB_VERSION);
2065
+ request.onerror = () => reject(request.error);
2066
+ request.onsuccess = () => resolve(request.result);
2067
+ request.onupgradeneeded = (event) => {
2068
+ const db = event.target.result;
2069
+ if (!db.objectStoreNames.contains(EVENTS_STORE)) {
2070
+ const eventsStore = db.createObjectStore(EVENTS_STORE, { keyPath: "id" });
2071
+ eventsStore.createIndex("timestamp", "timestamp");
2072
+ eventsStore.createIndex("type", "type");
2073
+ }
2074
+ if (!db.objectStoreNames.contains(ERRORS_STORE)) {
2075
+ const errorsStore = db.createObjectStore(ERRORS_STORE, { keyPath: "id" });
2076
+ errorsStore.createIndex("timestamp", "timestamp");
2077
+ errorsStore.createIndex("type", "type");
2078
+ }
2079
+ };
2080
+ });
2081
+ }
2082
+ setupOnlineListener() {
2083
+ if (typeof window === "undefined") return;
2084
+ const handleOnline = () => {
2085
+ if (this.debug) {
2086
+ console.log("[ExperienceSDK] Network online, processing offline queue");
2087
+ }
2088
+ this.processStoredItems();
2089
+ };
2090
+ window.addEventListener("online", handleOnline);
2091
+ this.unsubscribeFns.push(
2092
+ () => window.removeEventListener("online", handleOnline)
2093
+ );
2094
+ }
2095
+ generateId() {
2096
+ return `${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
2097
+ }
2098
+ async storeEventBatch(batch) {
2099
+ if (!this.isInitialized || !this.db) return;
2100
+ const storedBatch = {
2101
+ id: this.generateId(),
2102
+ timestamp: Date.now(),
2103
+ type: "events",
2104
+ data: batch,
2105
+ retryCount: 0
2106
+ };
2107
+ try {
2108
+ await this.storeItem(EVENTS_STORE, storedBatch);
2109
+ if (this.debug) {
2110
+ console.log("[ExperienceSDK] Event batch stored offline:", storedBatch.id);
2111
+ }
2112
+ this.cleanupOldItems(EVENTS_STORE);
2113
+ } catch (error) {
2114
+ if (this.debug) {
2115
+ console.error("[ExperienceSDK] Failed to store event batch offline:", error);
2116
+ }
2117
+ }
2118
+ }
2119
+ async storeErrorBatch(batch) {
2120
+ if (!this.isInitialized || !this.db) return;
2121
+ const storedBatch = {
2122
+ id: this.generateId(),
2123
+ timestamp: Date.now(),
2124
+ type: "errors",
2125
+ data: batch,
2126
+ retryCount: 0
2127
+ };
2128
+ try {
2129
+ await this.storeItem(ERRORS_STORE, storedBatch);
2130
+ if (this.debug) {
2131
+ console.log("[ExperienceSDK] Error batch stored offline:", storedBatch.id);
2132
+ }
2133
+ this.cleanupOldItems(ERRORS_STORE);
2134
+ } catch (error) {
2135
+ if (this.debug) {
2136
+ console.error("[ExperienceSDK] Failed to store error batch offline:", error);
2137
+ }
2138
+ }
2139
+ }
2140
+ storeItem(storeName, item) {
2141
+ return new Promise((resolve, reject) => {
2142
+ if (!this.db) {
2143
+ reject(new Error("Database not initialized"));
2144
+ return;
2145
+ }
2146
+ const transaction = this.db.transaction([storeName], "readwrite");
2147
+ const store = transaction.objectStore(storeName);
2148
+ const request = store.put(item);
2149
+ request.onerror = () => reject(request.error);
2150
+ request.onsuccess = () => resolve();
2151
+ });
2152
+ }
2153
+ async cleanupOldItems(storeName) {
2154
+ if (!this.db) return;
2155
+ try {
2156
+ const items = await this.getAllItems(storeName);
2157
+ if (items.length > MAX_OFFLINE_ITEMS) {
2158
+ const itemsToRemove = items.sort((a, b) => a.timestamp - b.timestamp).slice(0, items.length - MAX_OFFLINE_ITEMS);
2159
+ await Promise.all(
2160
+ itemsToRemove.map((item) => this.removeItem(storeName, item.id))
2161
+ );
2162
+ if (this.debug) {
2163
+ console.log(`[ExperienceSDK] Cleaned up ${itemsToRemove.length} old offline items`);
2164
+ }
2165
+ }
2166
+ } catch (error) {
2167
+ if (this.debug) {
2168
+ console.error("[ExperienceSDK] Failed to cleanup old items:", error);
2169
+ }
2170
+ }
2171
+ }
2172
+ getAllItems(storeName) {
2173
+ return new Promise((resolve, reject) => {
2174
+ if (!this.db) {
2175
+ reject(new Error("Database not initialized"));
2176
+ return;
2177
+ }
2178
+ const transaction = this.db.transaction([storeName], "readonly");
2179
+ const store = transaction.objectStore(storeName);
2180
+ const request = store.getAll();
2181
+ request.onerror = () => reject(request.error);
2182
+ request.onsuccess = () => resolve(request.result);
2183
+ });
2184
+ }
2185
+ removeItem(storeName, id) {
2186
+ return new Promise((resolve, reject) => {
2187
+ if (!this.db) {
2188
+ reject(new Error("Database not initialized"));
116
2189
  return;
117
2190
  }
118
- const item = cursor.value;
2191
+ const transaction = this.db.transaction([storeName], "readwrite");
2192
+ const store = transaction.objectStore(storeName);
2193
+ const request = store.delete(id);
2194
+ request.onerror = () => reject(request.error);
2195
+ request.onsuccess = () => resolve();
2196
+ });
2197
+ }
2198
+ async processStoredItems() {
2199
+ if (!this.isInitialized || !this.db || this.isProcessing) return;
2200
+ if (typeof navigator !== "undefined" && navigator.onLine === false) {
2201
+ if (this.debug) {
2202
+ console.log("[ExperienceSDK] Still offline, skipping queue processing");
2203
+ }
2204
+ return;
2205
+ }
2206
+ this.isProcessing = true;
2207
+ try {
2208
+ await this.processStoreItems(EVENTS_STORE);
2209
+ await this.processStoreItems(ERRORS_STORE);
2210
+ } finally {
2211
+ this.isProcessing = false;
2212
+ }
2213
+ }
2214
+ async processStoreItems(storeName) {
2215
+ try {
2216
+ const items = await this.getAllItems(storeName);
2217
+ if (items.length === 0) return;
2218
+ if (this.debug) {
2219
+ console.log(`[ExperienceSDK] Processing ${items.length} offline ${storeName}`);
2220
+ }
2221
+ items.sort((a, b) => a.timestamp - b.timestamp);
2222
+ for (const item of items) {
2223
+ try {
2224
+ let success = false;
2225
+ if (storeName === EVENTS_STORE && item.type === "events") {
2226
+ const result = await this.transport.sendEventBatch(item.data);
2227
+ success = result.success !== false;
2228
+ } else if (storeName === ERRORS_STORE && item.type === "errors") {
2229
+ const result = await this.transport.sendErrorBatch(item.data);
2230
+ success = result.success !== false;
2231
+ }
2232
+ if (success) {
2233
+ await this.removeItem(storeName, item.id);
2234
+ if (this.debug) {
2235
+ console.log(`[ExperienceSDK] Offline ${item.type} sent successfully:`, item.id);
2236
+ }
2237
+ } else {
2238
+ throw new Error("Send failed");
2239
+ }
2240
+ } catch (error) {
2241
+ if (this.debug) {
2242
+ console.error(`[ExperienceSDK] Failed to send offline ${item.type}:`, error);
2243
+ }
2244
+ item.retryCount++;
2245
+ if (item.retryCount >= 3) {
2246
+ await this.removeItem(storeName, item.id);
2247
+ if (this.debug) {
2248
+ console.log(`[ExperienceSDK] Removing ${item.type} after ${item.retryCount} retries:`, item.id);
2249
+ }
2250
+ } else {
2251
+ await this.storeItem(storeName, item);
2252
+ }
2253
+ }
2254
+ }
2255
+ } catch (error) {
2256
+ if (this.debug) {
2257
+ console.error(`[ExperienceSDK] Failed to process offline ${storeName}:`, error);
2258
+ }
2259
+ }
2260
+ }
2261
+ // Get queue status for debugging
2262
+ async getQueueStatus() {
2263
+ let eventCount = 0;
2264
+ let errorCount = 0;
2265
+ if (this.isInitialized && this.db) {
119
2266
  try {
120
- await sendFn(item.url, item.body);
121
- cursor.delete();
122
- cursor.continue();
123
- } catch {
124
- resolve();
2267
+ const events = await this.getAllItems(EVENTS_STORE);
2268
+ const errors = await this.getAllItems(ERRORS_STORE);
2269
+ eventCount = events.length;
2270
+ errorCount = errors.length;
2271
+ } catch (error) {
125
2272
  }
2273
+ }
2274
+ return {
2275
+ events: eventCount,
2276
+ errors: errorCount,
2277
+ isOnline: typeof navigator !== "undefined" ? navigator.onLine : true,
2278
+ isInitialized: this.isInitialized
126
2279
  };
127
- cursorReq.onerror = () => reject(cursorReq.error);
128
- });
129
- db.close();
130
- }
131
-
132
- // src/utils.ts
133
- function generateUUID() {
134
- if (typeof crypto !== "undefined" && crypto.randomUUID) {
135
- return crypto.randomUUID();
136
- }
137
- return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
138
- const r = Math.random() * 16 | 0;
139
- const v = c === "x" ? r : r & 3 | 8;
140
- return v.toString(16);
141
- });
142
- }
143
- function getPageContext() {
144
- return {
145
- path: location.pathname,
146
- title: document.title,
147
- referrer: document.referrer,
148
- viewportWidth: window.innerWidth,
149
- viewportHeight: window.innerHeight
150
- };
151
- }
152
- function throttle(fn, ms) {
153
- let last = 0;
154
- return ((...args) => {
155
- const now = Date.now();
156
- if (now - last >= ms) {
157
- last = now;
158
- fn(...args);
2280
+ }
2281
+ // Clear all offline data
2282
+ async clear() {
2283
+ if (!this.isInitialized || !this.db) return;
2284
+ try {
2285
+ const transaction = this.db.transaction([EVENTS_STORE, ERRORS_STORE], "readwrite");
2286
+ await Promise.all([
2287
+ new Promise((resolve, reject) => {
2288
+ const request = transaction.objectStore(EVENTS_STORE).clear();
2289
+ request.onerror = () => reject(request.error);
2290
+ request.onsuccess = () => resolve();
2291
+ }),
2292
+ new Promise((resolve, reject) => {
2293
+ const request = transaction.objectStore(ERRORS_STORE).clear();
2294
+ request.onerror = () => reject(request.error);
2295
+ request.onsuccess = () => resolve();
2296
+ })
2297
+ ]);
2298
+ if (this.debug) {
2299
+ console.log("[ExperienceSDK] Offline queue cleared");
2300
+ }
2301
+ } catch (error) {
2302
+ if (this.debug) {
2303
+ console.error("[ExperienceSDK] Failed to clear offline queue:", error);
2304
+ }
159
2305
  }
160
- });
161
- }
2306
+ }
2307
+ shutdown() {
2308
+ this.unsubscribeFns.forEach((fn) => fn());
2309
+ this.unsubscribeFns = [];
2310
+ if (this.db) {
2311
+ this.db.close();
2312
+ this.db = null;
2313
+ }
2314
+ this.isInitialized = false;
2315
+ }
2316
+ };
162
2317
 
163
2318
  // src/experience.ts
164
- var DEFAULT_CONFIG = {
165
- endpoint: "",
166
- enableTracking: true,
167
- enableErrorCapture: true,
168
- enableHeatmaps: false,
169
- enableBreadcrumbs: true,
170
- sampleRate: 1,
171
- batchIntervalMs: 5e3,
172
- maxBatchSize: 50,
173
- debug: false
174
- };
175
- var Experience = class _Experience {
2319
+ var _Experience = class _Experience {
176
2320
  constructor(config) {
177
- this.profileId = "";
178
- this.eventQueue = [];
179
- this.errorQueue = [];
180
- this.batchTimer = null;
181
- this.flags = {};
182
- this.flagsFetched = false;
183
- this.teardownFns = [];
184
- this.destroyed = false;
185
- this.config = { ...DEFAULT_CONFIG, ...config };
186
- this.breadcrumbs = new BreadcrumbManager();
187
- const stored = typeof sessionStorage !== "undefined" ? sessionStorage.getItem("exp_session_id") : null;
188
- if (stored) {
189
- this.sessionId = stored;
190
- } else {
191
- this.sessionId = generateUUID();
192
- if (typeof sessionStorage !== "undefined") {
193
- sessionStorage.setItem("exp_session_id", this.sessionId);
194
- }
2321
+ this.isShutdown = false;
2322
+ this.isInSample = true;
2323
+ this.config = this.resolveConfig(config);
2324
+ this.isInSample = Math.random() < this.config.sampleRate;
2325
+ if (this.config.debug) {
2326
+ console.log("[ExperienceSDK] Initializing with config:", this.config);
2327
+ console.log("[ExperienceSDK] In sample:", this.isInSample);
195
2328
  }
2329
+ this.sessionManager = new SessionManager();
2330
+ this.transport = new Transport({
2331
+ endpoint: this.config.endpoint,
2332
+ apiKey: this.config.apiKey,
2333
+ debug: this.config.debug
2334
+ });
2335
+ this.batcher = new EventBatcher(
2336
+ this.transport,
2337
+ this.config.batchIntervalMs,
2338
+ this.config.maxBatchSize,
2339
+ this.config.debug
2340
+ );
2341
+ this.breadcrumbManager = new BreadcrumbManager();
2342
+ this.heatmapTracker = new HeatmapTracker(this.batcher, this.sessionManager.getSessionId());
2343
+ this.errorCapture = new ErrorCapture(this.batcher, this.breadcrumbManager, this.sessionManager.getSessionId());
2344
+ this.autoTracker = new AutoTracker(this.batcher, this.sessionManager.getSessionId());
2345
+ this.dataTTracker = new DataTTracker(this.batcher, this.sessionManager.getSessionId());
2346
+ this.flagClient = new FeatureFlagClient(this.transport, this.config.appId, this.config.debug);
2347
+ this.offlineQueue = new OfflineQueue(this.transport, this.config.debug);
2348
+ this.setupFailureHandling();
2349
+ this.enableFeatures();
196
2350
  }
197
- static init(config) {
198
- const instance = new _Experience(config);
199
- instance.setup();
200
- return instance;
2351
+ resolveConfig(config) {
2352
+ return {
2353
+ appId: config.appId,
2354
+ apiKey: config.apiKey,
2355
+ endpoint: config.endpoint || "https://experience.shellapps.com",
2356
+ enableTracking: config.enableTracking ?? true,
2357
+ enableErrorCapture: config.enableErrorCapture ?? true,
2358
+ enableHeatmaps: config.enableHeatmaps ?? false,
2359
+ enableBreadcrumbs: config.enableBreadcrumbs ?? true,
2360
+ sampleRate: Math.max(0, Math.min(1, config.sampleRate ?? 1)),
2361
+ batchIntervalMs: config.batchIntervalMs ?? 2e3,
2362
+ maxBatchSize: config.maxBatchSize ?? 50,
2363
+ debug: config.debug ?? false
2364
+ };
201
2365
  }
202
- log(...args) {
203
- if (this.config.debug) {
204
- console.log("[Experience]", ...args);
205
- }
2366
+ setupFailureHandling() {
2367
+ const originalSendEventBatch = this.transport.sendEventBatch.bind(this.transport);
2368
+ const originalSendErrorBatch = this.transport.sendErrorBatch.bind(this.transport);
2369
+ this.transport.sendEventBatch = async (batch) => {
2370
+ try {
2371
+ return await originalSendEventBatch(batch);
2372
+ } catch (error) {
2373
+ await this.offlineQueue.storeEventBatch(batch);
2374
+ throw error;
2375
+ }
2376
+ };
2377
+ this.transport.sendErrorBatch = async (batch) => {
2378
+ try {
2379
+ return await originalSendErrorBatch(batch);
2380
+ } catch (error) {
2381
+ await this.offlineQueue.storeErrorBatch(batch);
2382
+ throw error;
2383
+ }
2384
+ };
206
2385
  }
207
- setup() {
208
- if (Math.random() > this.config.sampleRate) {
209
- this.log("Sampled out");
2386
+ enableFeatures() {
2387
+ if (!this.isInSample) {
2388
+ if (this.config.debug) {
2389
+ console.log("[ExperienceSDK] Not in sample, features disabled");
2390
+ }
210
2391
  return;
211
2392
  }
212
2393
  if (this.config.enableBreadcrumbs) {
213
- this.breadcrumbs.install();
2394
+ this.breadcrumbManager.enable();
214
2395
  }
215
2396
  if (this.config.enableErrorCapture) {
216
- this.setupErrorCapture();
2397
+ this.errorCapture.enable();
217
2398
  }
218
2399
  if (this.config.enableTracking) {
219
- this.setupAutoPageViews();
2400
+ this.autoTracker.enable();
2401
+ this.dataTTracker.enable();
220
2402
  }
221
2403
  if (this.config.enableHeatmaps) {
222
- this.setupHeatmaps();
223
- }
224
- this.batchTimer = setInterval(() => this.flush(), this.config.batchIntervalMs);
225
- const beforeUnload = () => this.flushSync();
226
- window.addEventListener("beforeunload", beforeUnload);
227
- this.teardownFns.push(() => window.removeEventListener("beforeunload", beforeUnload));
228
- const onlineHandler = () => {
229
- this.log("Back online, draining queue");
230
- drainOfflineQueue((url, body) => this.sendRequest(url, body)).catch(() => {
231
- });
232
- };
233
- window.addEventListener("online", onlineHandler);
234
- this.teardownFns.push(() => window.removeEventListener("online", onlineHandler));
235
- this.fetchFlags().catch(() => {
2404
+ this.heatmapTracker.enable();
2405
+ }
2406
+ this.flagClient.initialize().catch((error) => {
2407
+ if (this.config.debug) {
2408
+ console.error("[ExperienceSDK] Failed to initialize flags:", error);
2409
+ }
236
2410
  });
237
- this.log("Initialized", this.config.appId);
238
2411
  }
239
- // --- Public API ---
240
- track(eventName, metadata) {
241
- if (this.destroyed) return;
242
- this.enqueueEvent("custom", eventName, metadata);
2412
+ // Public API
2413
+ track(eventType, metadata) {
2414
+ if (!this.isInSample || this.isShutdown || !this.config.enableTracking) return;
2415
+ const pageContext = this.getCurrentPageContext();
2416
+ const trackEvent = {
2417
+ eventType,
2418
+ sessionId: this.sessionManager.getSessionId(),
2419
+ timestamp: Date.now(),
2420
+ metadata,
2421
+ pageContext
2422
+ };
2423
+ this.batcher.addTrackEvent(trackEvent);
2424
+ if (this.config.debug) {
2425
+ console.log("[ExperienceSDK] Event tracked:", trackEvent);
2426
+ }
243
2427
  }
244
2428
  trackPageView() {
245
- if (this.destroyed) return;
246
- this.enqueueEvent("page_view", "page_view");
247
- this.breadcrumbs.add({ type: "navigation", category: "page_view", message: location.href });
2429
+ this.autoTracker.trackCurrentPage();
248
2430
  }
249
- captureError(error, extra) {
250
- if (this.destroyed) return;
251
- this.enqueueError(error.message, error.stack || "", "error", extra);
2431
+ captureError(error, options) {
2432
+ if (!this.isInSample || this.isShutdown || !this.config.enableErrorCapture) return;
2433
+ this.errorCapture.captureException(error, options);
252
2434
  }
253
- captureMessage(msg, severity = "info") {
254
- if (this.destroyed) return;
255
- this.enqueueError(msg, "", severity);
2435
+ captureMessage(message, severity) {
2436
+ if (!this.isInSample || this.isShutdown || !this.config.enableErrorCapture) return;
2437
+ const sev = severity ? ErrorCapture.getSeverityFromString(severity) : 0 /* INFO */;
2438
+ this.errorCapture.captureMessage(message, sev);
256
2439
  }
257
- getFlag(name, defaultValue) {
258
- if (!this.flagsFetched || !(name in this.flags)) return defaultValue;
259
- return this.flags[name];
2440
+ getFlag(key, defaultValue) {
2441
+ if (this.isShutdown) return defaultValue;
2442
+ return this.flagClient.getFlag(key, defaultValue);
260
2443
  }
261
2444
  identify(profileId) {
262
- this.profileId = profileId;
263
- this.log("Identified", profileId);
2445
+ if (this.isShutdown) return;
2446
+ this.flagClient.setProfileId(profileId);
2447
+ if (this.config.debug) {
2448
+ console.log("[ExperienceSDK] Profile identified:", profileId);
2449
+ }
264
2450
  }
265
2451
  async shutdown() {
266
- if (this.destroyed) return;
267
- this.destroyed = true;
268
- if (this.batchTimer) {
269
- clearInterval(this.batchTimer);
270
- this.batchTimer = null;
271
- }
272
- await this.flush();
273
- this.breadcrumbs.teardown();
274
- this.teardownFns.forEach((fn) => fn());
275
- this.teardownFns = [];
276
- this.log("Shutdown complete");
277
- }
278
- // --- Internal ---
279
- enqueueEvent(eventType, eventName, metadata) {
280
- const event = {
281
- eventId: generateUUID(),
282
- appId: this.config.appId,
283
- profileId: this.profileId,
284
- sessionId: this.sessionId,
285
- timestampMs: Date.now(),
286
- eventType,
287
- pageUrl: location.href,
288
- elementTid: metadata?.elementTid || "",
289
- metadata: { eventName, ...metadata },
290
- pageContext: getPageContext(),
291
- elementContext: null
292
- };
293
- this.eventQueue.push(event);
294
- if (this.eventQueue.length >= this.config.maxBatchSize) {
295
- this.flush().catch(() => {
296
- });
2452
+ if (this.isShutdown) return;
2453
+ this.isShutdown = true;
2454
+ if (this.config.debug) {
2455
+ console.log("[ExperienceSDK] Shutting down...");
2456
+ }
2457
+ this.breadcrumbManager.disable();
2458
+ this.errorCapture.disable();
2459
+ this.heatmapTracker.disable();
2460
+ this.autoTracker.disable();
2461
+ this.dataTTracker.disable();
2462
+ await Promise.all([
2463
+ this.batcher.shutdown(),
2464
+ this.flagClient.shutdown(),
2465
+ this.offlineQueue.shutdown()
2466
+ ]);
2467
+ if (this.config.debug) {
2468
+ console.log("[ExperienceSDK] Shutdown complete");
297
2469
  }
298
2470
  }
299
- enqueueError(message, stack, severity, extra) {
300
- const report = {
301
- eventId: generateUUID(),
302
- appId: this.config.appId,
303
- profileId: this.profileId,
304
- sessionId: this.sessionId,
305
- timestampMs: Date.now(),
306
- message,
307
- stack,
308
- severity,
309
- extra: extra || {},
310
- pageContext: getPageContext(),
311
- breadcrumbs: this.breadcrumbs.getAll()
2471
+ // Utility methods
2472
+ getCurrentPageContext() {
2473
+ if (typeof window === "undefined" || typeof document === "undefined") {
2474
+ return {
2475
+ url: "unknown",
2476
+ title: "unknown",
2477
+ referrer: "",
2478
+ viewport: { width: 1920, height: 1080 }
2479
+ };
2480
+ }
2481
+ return {
2482
+ url: window.location.href,
2483
+ title: document.title || "",
2484
+ referrer: document.referrer || "",
2485
+ viewport: {
2486
+ width: window.innerWidth || 1920,
2487
+ height: window.innerHeight || 1080
2488
+ }
2489
+ };
2490
+ }
2491
+ // Debug methods
2492
+ getSessionInfo() {
2493
+ return {
2494
+ sessionId: this.sessionManager.getSessionId(),
2495
+ duration: this.sessionManager.getSessionDuration(),
2496
+ isInSample: this.isInSample
2497
+ };
2498
+ }
2499
+ async getQueueStatus() {
2500
+ const offlineStatus = await this.offlineQueue.getQueueStatus();
2501
+ return {
2502
+ pendingEvents: this.batcher.getPendingEventCount(),
2503
+ pendingErrors: this.batcher.getPendingErrorCount(),
2504
+ offlineQueue: offlineStatus
312
2505
  };
313
- this.errorQueue.push(report);
314
2506
  }
2507
+ getBreadcrumbs() {
2508
+ return this.breadcrumbManager.getBreadcrumbs();
2509
+ }
2510
+ getFlagCacheInfo() {
2511
+ return this.flagClient.getCacheInfo();
2512
+ }
2513
+ // Manual flush for testing
315
2514
  async flush() {
316
- if (this.eventQueue.length > 0) {
317
- const events = this.eventQueue.splice(0);
318
- const url = `${this.config.endpoint}/api/v1/ingest/events`;
319
- const body = JSON.stringify({ events });
320
- await this.sendOrQueue(url, body);
321
- }
322
- if (this.errorQueue.length > 0) {
323
- const errors = this.errorQueue.splice(0);
324
- const url = `${this.config.endpoint}/api/v1/ingest/errors`;
325
- const body = JSON.stringify({ errors });
326
- await this.sendOrQueue(url, body);
327
- }
328
- }
329
- flushSync() {
330
- if (typeof navigator.sendBeacon !== "function") return;
331
- if (this.eventQueue.length > 0) {
332
- const url = `${this.config.endpoint}/api/v1/ingest/events`;
333
- const blob = new Blob([JSON.stringify({ events: this.eventQueue.splice(0) })], { type: "application/json" });
334
- navigator.sendBeacon(url, blob);
335
- }
336
- if (this.errorQueue.length > 0) {
337
- const url = `${this.config.endpoint}/api/v1/ingest/errors`;
338
- const blob = new Blob([JSON.stringify({ errors: this.errorQueue.splice(0) })], { type: "application/json" });
339
- navigator.sendBeacon(url, blob);
340
- }
341
- }
342
- async sendOrQueue(url, body) {
343
- if (typeof navigator !== "undefined" && !navigator.onLine) {
344
- await enqueueOffline({ url, body });
345
- return;
346
- }
347
- try {
348
- await this.sendRequest(url, body);
349
- } catch {
350
- await enqueueOffline({ url, body }).catch(() => {
351
- });
352
- }
2515
+ if (this.isShutdown) return;
2516
+ await this.batcher.forceFlush();
2517
+ await this.offlineQueue.processStoredItems();
353
2518
  }
354
- async sendRequest(url, body) {
355
- const response = await fetch(url, {
356
- method: "POST",
357
- headers: {
358
- "Content-Type": "application/json",
359
- "Authorization": `Bearer ${this.config.apiKey}`
360
- },
361
- body
362
- });
363
- if (!response.ok) {
364
- throw new Error(`HTTP ${response.status}`);
2519
+ // Static factory method (singleton pattern)
2520
+ static init(config) {
2521
+ if (_Experience.instance) {
2522
+ if (config.debug) {
2523
+ console.warn("[ExperienceSDK] Already initialized, returning existing instance");
2524
+ }
2525
+ return _Experience.instance;
365
2526
  }
2527
+ _Experience.instance = new _Experience(config);
2528
+ return _Experience.instance;
366
2529
  }
367
- async fetchFlags() {
368
- try {
369
- const url = `${this.config.endpoint}/api/v1/flags/${this.config.appId}`;
370
- const response = await fetch(url, {
371
- headers: { "Authorization": `Bearer ${this.config.apiKey}` }
372
- });
373
- if (response.ok) {
374
- this.flags = await response.json();
375
- this.flagsFetched = true;
376
- this.log("Flags loaded", this.flags);
377
- }
378
- } catch {
379
- this.log("Failed to fetch flags");
380
- }
381
- }
382
- // --- Auto features ---
383
- setupErrorCapture() {
384
- const onError = (event) => {
385
- this.enqueueError(
386
- event.message,
387
- event.error?.stack || "",
388
- "error",
389
- { filename: event.filename, lineno: event.lineno, colno: event.colno }
390
- );
391
- };
392
- window.addEventListener("error", onError);
393
- this.teardownFns.push(() => window.removeEventListener("error", onError));
394
- const onUnhandledRejection = (event) => {
395
- const msg = event.reason instanceof Error ? event.reason.message : String(event.reason);
396
- const stack = event.reason instanceof Error ? event.reason.stack || "" : "";
397
- this.enqueueError(msg, stack, "error", { type: "unhandledrejection" });
398
- };
399
- window.addEventListener("unhandledrejection", onUnhandledRejection);
400
- this.teardownFns.push(() => window.removeEventListener("unhandledrejection", onUnhandledRejection));
401
- }
402
- setupAutoPageViews() {
403
- const originalPushState = history.pushState.bind(history);
404
- const originalReplaceState = history.replaceState.bind(history);
405
- history.pushState = (...args) => {
406
- originalPushState(...args);
407
- this.trackPageView();
2530
+ static getInstance() {
2531
+ return _Experience.instance;
2532
+ }
2533
+ // Advanced tracking methods
2534
+ trackElement(element, eventType) {
2535
+ if (!this.isInSample || this.isShutdown || !this.config.enableTracking) return;
2536
+ const elementContext = {
2537
+ selector: this.getElementSelector(element),
2538
+ tag: element.tagName.toLowerCase(),
2539
+ dataT: element.getAttribute("data-t") || void 0
408
2540
  };
409
- history.replaceState = (...args) => {
410
- originalReplaceState(...args);
411
- this.trackPageView();
2541
+ const trackEvent = {
2542
+ eventType,
2543
+ sessionId: this.sessionManager.getSessionId(),
2544
+ timestamp: Date.now(),
2545
+ pageContext: this.getCurrentPageContext(),
2546
+ elementContext
412
2547
  };
413
- this.teardownFns.push(() => {
414
- history.pushState = originalPushState;
415
- history.replaceState = originalReplaceState;
416
- });
417
- const onPopState = () => this.trackPageView();
418
- window.addEventListener("popstate", onPopState);
419
- this.teardownFns.push(() => window.removeEventListener("popstate", onPopState));
420
- this.trackPageView();
2548
+ this.batcher.addTrackEvent(trackEvent);
421
2549
  }
422
- setupHeatmaps() {
423
- const handleMove = throttle((e) => {
424
- this.enqueueEvent("heatmap_move", "mousemove", {
425
- x: e.clientX,
426
- y: e.clientY,
427
- viewportWidth: window.innerWidth,
428
- viewportHeight: window.innerHeight
429
- });
430
- }, 100);
431
- const handleClick = (e) => {
432
- const target = e.target;
433
- this.enqueueEvent("heatmap_click", "click", {
434
- x: e.clientX,
435
- y: e.clientY,
436
- viewportWidth: window.innerWidth,
437
- viewportHeight: window.innerHeight,
438
- tag: target?.tagName?.toLowerCase() || "",
439
- text: (target?.textContent || "").slice(0, 100)
440
- });
441
- };
442
- document.addEventListener("mousemove", handleMove);
443
- document.addEventListener("click", handleClick);
444
- this.teardownFns.push(() => {
445
- document.removeEventListener("mousemove", handleMove);
446
- document.removeEventListener("click", handleClick);
447
- });
2550
+ getElementSelector(element) {
2551
+ try {
2552
+ const tag = element.tagName.toLowerCase();
2553
+ const id = element.id ? `#${element.id}` : "";
2554
+ const className = element.className && typeof element.className === "string" ? `.${element.className.split(" ").filter((c) => c.trim()).join(".")}` : "";
2555
+ return `${tag}${id}${className}`.slice(0, 100);
2556
+ } catch {
2557
+ return element.tagName?.toLowerCase() || "unknown";
2558
+ }
448
2559
  }
449
2560
  };
2561
+ _Experience.instance = null;
2562
+ var Experience = _Experience;
450
2563
  export {
451
- Experience
2564
+ AutoTracker,
2565
+ BreadcrumbManager,
2566
+ DataTTracker,
2567
+ ErrorCapture,
2568
+ EventBatcher,
2569
+ Experience,
2570
+ FeatureFlagClient,
2571
+ HeatmapTracker,
2572
+ OfflineQueue,
2573
+ schemas_exports as ProtoSchemas,
2574
+ ProtobufEncoder,
2575
+ SessionManager,
2576
+ Severity,
2577
+ Transport
452
2578
  };
453
2579
  //# sourceMappingURL=index.mjs.map