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