blinker-sdk 1.0.1 → 2.0.1

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.
Files changed (91) hide show
  1. package/README.md +226 -147
  2. package/dist/blinker.min.js +452 -1
  3. package/dist/capture/deduplication.d.ts +26 -0
  4. package/dist/capture/deduplication.d.ts.map +1 -0
  5. package/dist/capture/errorHandler.d.ts +5 -0
  6. package/dist/capture/errorHandler.d.ts.map +1 -0
  7. package/dist/capture/index.d.ts +4 -0
  8. package/dist/capture/index.d.ts.map +1 -0
  9. package/dist/capture/types.d.ts +13 -0
  10. package/dist/capture/types.d.ts.map +1 -0
  11. package/dist/context/SessionManager.d.ts +16 -0
  12. package/dist/context/SessionManager.d.ts.map +1 -0
  13. package/dist/context/UserContext.d.ts +19 -0
  14. package/dist/context/UserContext.d.ts.map +1 -0
  15. package/dist/context/index.d.ts +4 -0
  16. package/dist/context/index.d.ts.map +1 -0
  17. package/dist/context/types.d.ts +18 -0
  18. package/dist/context/types.d.ts.map +1 -0
  19. package/dist/core/Blinker.d.ts +45 -0
  20. package/dist/core/Blinker.d.ts.map +1 -0
  21. package/dist/core/config.d.ts +29 -0
  22. package/dist/core/config.d.ts.map +1 -0
  23. package/dist/core/index.d.ts +4 -0
  24. package/dist/core/index.d.ts.map +1 -0
  25. package/dist/core/types.d.ts +54 -0
  26. package/dist/core/types.d.ts.map +1 -0
  27. package/dist/filters/FilterEngine.d.ts +9 -0
  28. package/dist/filters/FilterEngine.d.ts.map +1 -0
  29. package/dist/filters/defaults.d.ts +3 -0
  30. package/dist/filters/defaults.d.ts.map +1 -0
  31. package/dist/filters/index.d.ts +4 -0
  32. package/dist/filters/index.d.ts.map +1 -0
  33. package/dist/filters/types.d.ts +8 -0
  34. package/dist/filters/types.d.ts.map +1 -0
  35. package/dist/index.cjs.js +2192 -266
  36. package/dist/index.d.ts +7 -32
  37. package/dist/index.d.ts.map +1 -1
  38. package/dist/index.esm.js +2192 -266
  39. package/dist/queue/EventQueue.d.ts +21 -0
  40. package/dist/queue/EventQueue.d.ts.map +1 -0
  41. package/dist/queue/index.d.ts +4 -0
  42. package/dist/queue/index.d.ts.map +1 -0
  43. package/dist/queue/storage.d.ts +16 -0
  44. package/dist/queue/storage.d.ts.map +1 -0
  45. package/dist/queue/types.d.ts +21 -0
  46. package/dist/queue/types.d.ts.map +1 -0
  47. package/dist/sampling/RateLimiter.d.ts +12 -0
  48. package/dist/sampling/RateLimiter.d.ts.map +1 -0
  49. package/dist/sampling/Sampler.d.ts +16 -0
  50. package/dist/sampling/Sampler.d.ts.map +1 -0
  51. package/dist/sampling/index.d.ts +4 -0
  52. package/dist/sampling/index.d.ts.map +1 -0
  53. package/dist/sampling/types.d.ts +10 -0
  54. package/dist/sampling/types.d.ts.map +1 -0
  55. package/dist/transport/index.d.ts +3 -0
  56. package/dist/transport/index.d.ts.map +1 -0
  57. package/dist/transport/sender.d.ts +4 -0
  58. package/dist/transport/sender.d.ts.map +1 -0
  59. package/dist/transport/types.d.ts +12 -0
  60. package/dist/transport/types.d.ts.map +1 -0
  61. package/dist/utils/browser.d.ts +10 -0
  62. package/dist/utils/browser.d.ts.map +1 -0
  63. package/dist/utils/hash.d.ts +4 -0
  64. package/dist/utils/hash.d.ts.map +1 -0
  65. package/dist/utils/index.d.ts +4 -0
  66. package/dist/utils/index.d.ts.map +1 -0
  67. package/dist/utils/logger.d.ts +9 -0
  68. package/dist/utils/logger.d.ts.map +1 -0
  69. package/dist/widget/Avatar.d.ts +20 -0
  70. package/dist/widget/Avatar.d.ts.map +1 -0
  71. package/dist/widget/Dialog.d.ts +25 -0
  72. package/dist/widget/Dialog.d.ts.map +1 -0
  73. package/dist/widget/QuestionEngine.d.ts +7 -0
  74. package/dist/widget/QuestionEngine.d.ts.map +1 -0
  75. package/dist/widget/Widget.d.ts +28 -0
  76. package/dist/widget/Widget.d.ts.map +1 -0
  77. package/dist/widget/index.d.ts +8 -0
  78. package/dist/widget/index.d.ts.map +1 -0
  79. package/dist/widget/styles.d.ts +5 -0
  80. package/dist/widget/styles.d.ts.map +1 -0
  81. package/dist/widget/types.d.ts +46 -0
  82. package/dist/widget/types.d.ts.map +1 -0
  83. package/package.json +1 -1
  84. package/dist/Blinker.d.ts +0 -65
  85. package/dist/Blinker.d.ts.map +0 -1
  86. package/dist/errorHandler.d.ts +0 -19
  87. package/dist/errorHandler.d.ts.map +0 -1
  88. package/dist/sender.d.ts +0 -11
  89. package/dist/sender.d.ts.map +0 -1
  90. package/dist/types.d.ts +0 -92
  91. package/dist/types.d.ts.map +0 -1
package/dist/index.esm.js CHANGED
@@ -1,364 +1,2288 @@
1
- // src/sender.ts
2
- async function sendEvent(api, token, event, debug = false) {
3
- const endpoint = `${api.replace(/\/$/, "")}/events`;
1
+ // src/core/config.ts
2
+ var BLINKER_API = "https://api.blinker.live";
3
+ var SDK_VERSION = "2.0.1";
4
+ var STORAGE_KEYS = {
5
+ EVENT_QUEUE: "blinker_event_queue",
6
+ SESSION: "blinker_session",
7
+ USER_CONTEXT: "blinker_user_context"
8
+ };
9
+ var DEFAULTS = {
10
+ endpoint: BLINKER_API,
11
+ enabled: true,
12
+ captureErrors: true,
13
+ captureRejections: true,
14
+ enableBatching: true,
15
+ batchSize: 10,
16
+ flushInterval: 5e3,
17
+ enableOfflineStorage: true,
18
+ maxStoredEvents: 100,
19
+ enableDeduplication: true,
20
+ deduplicationWindow: 6e4,
21
+ maxEventsPerMinute: 100,
22
+ sampleRate: 1,
23
+ debug: false
24
+ };
25
+ var TIMING = {
26
+ OFFLINE_RETRY_TIMEOUT: 3e4,
27
+ MIN_FLUSH_INTERVAL: 1e3,
28
+ MAX_FLUSH_INTERVAL: 6e4
29
+ };
30
+
31
+ // src/filters/defaults.ts
32
+ var DEFAULT_FILTERS = {
33
+ ignoreHttpCodes: [401, 403],
34
+ ignoreErrorTypes: [],
35
+ ignoreMessagePatterns: [
36
+ "ResizeObserver loop",
37
+ "Script error",
38
+ "ResizeObserver loop completed"
39
+ ],
40
+ captureAll: false
41
+ };
42
+
43
+ // src/utils/logger.ts
44
+ var PREFIX = "[Blinker]";
45
+ function createLogger(debug) {
46
+ const noop = () => {
47
+ };
48
+ if (!debug) {
49
+ return {
50
+ log: noop,
51
+ warn: noop,
52
+ error: noop
53
+ };
54
+ }
55
+ return {
56
+ log: (...args) => console.log(PREFIX, ...args),
57
+ warn: (...args) => console.warn(PREFIX, ...args),
58
+ error: (...args) => console.error(PREFIX, ...args)
59
+ };
60
+ }
61
+ var globalLogger = createLogger(false);
62
+ function setGlobalLogger(debug) {
63
+ globalLogger = createLogger(debug);
64
+ }
65
+ function getLogger() {
66
+ return globalLogger;
67
+ }
68
+
69
+ // src/filters/FilterEngine.ts
70
+ var FilterEngine = class {
71
+ constructor(userFilters) {
72
+ var _a, _b, _c, _d;
73
+ this.filters = {
74
+ ignoreHttpCodes: (_a = userFilters == null ? void 0 : userFilters.ignoreHttpCodes) != null ? _a : DEFAULT_FILTERS.ignoreHttpCodes,
75
+ ignoreErrorTypes: (_b = userFilters == null ? void 0 : userFilters.ignoreErrorTypes) != null ? _b : DEFAULT_FILTERS.ignoreErrorTypes,
76
+ ignoreMessagePatterns: (_c = userFilters == null ? void 0 : userFilters.ignoreMessagePatterns) != null ? _c : DEFAULT_FILTERS.ignoreMessagePatterns,
77
+ captureAll: (_d = userFilters == null ? void 0 : userFilters.captureAll) != null ? _d : DEFAULT_FILTERS.captureAll
78
+ };
79
+ }
80
+ shouldCapture(message, errorType, httpCode) {
81
+ const logger = getLogger();
82
+ if (this.filters.captureAll) {
83
+ return true;
84
+ }
85
+ if (httpCode !== void 0 && this.filters.ignoreHttpCodes.includes(httpCode)) {
86
+ logger.log("Event filtered out by HTTP code:", httpCode);
87
+ return false;
88
+ }
89
+ if (errorType && this.filters.ignoreErrorTypes.includes(errorType)) {
90
+ logger.log("Event filtered out by error type:", errorType);
91
+ return false;
92
+ }
93
+ for (const pattern of this.filters.ignoreMessagePatterns) {
94
+ if (message.toLowerCase().includes(pattern.toLowerCase())) {
95
+ logger.log("Event filtered out by message pattern:", pattern);
96
+ return false;
97
+ }
98
+ }
99
+ if (httpCode === void 0) {
100
+ const httpCodeMatch = message.match(/(?:HTTP\s*|status[:\s]*)?(\d{3})(?:\s|$|:)/i);
101
+ if (httpCodeMatch) {
102
+ const extractedCode = parseInt(httpCodeMatch[1], 10);
103
+ if (extractedCode >= 100 && extractedCode < 600) {
104
+ if (this.filters.ignoreHttpCodes.includes(extractedCode)) {
105
+ logger.log("Event filtered out by extracted HTTP code:", extractedCode);
106
+ return false;
107
+ }
108
+ }
109
+ }
110
+ }
111
+ return true;
112
+ }
113
+ getFilters() {
114
+ return { ...this.filters };
115
+ }
116
+ updateFilters(newFilters) {
117
+ var _a, _b, _c, _d;
118
+ this.filters = {
119
+ ignoreHttpCodes: (_a = newFilters.ignoreHttpCodes) != null ? _a : this.filters.ignoreHttpCodes,
120
+ ignoreErrorTypes: (_b = newFilters.ignoreErrorTypes) != null ? _b : this.filters.ignoreErrorTypes,
121
+ ignoreMessagePatterns: (_c = newFilters.ignoreMessagePatterns) != null ? _c : this.filters.ignoreMessagePatterns,
122
+ captureAll: (_d = newFilters.captureAll) != null ? _d : this.filters.captureAll
123
+ };
124
+ }
125
+ };
126
+
127
+ // src/utils/browser.ts
128
+ function isBrowser() {
129
+ return typeof window !== "undefined";
130
+ }
131
+ function isLocalStorageAvailable() {
132
+ if (!isBrowser()) return false;
133
+ try {
134
+ const testKey = "__blinker_test__";
135
+ localStorage.setItem(testKey, "test");
136
+ localStorage.removeItem(testKey);
137
+ return true;
138
+ } catch (e) {
139
+ return false;
140
+ }
141
+ }
142
+ function isSessionStorageAvailable() {
143
+ if (!isBrowser()) return false;
144
+ try {
145
+ const testKey = "__blinker_test__";
146
+ sessionStorage.setItem(testKey, "test");
147
+ sessionStorage.removeItem(testKey);
148
+ return true;
149
+ } catch (e) {
150
+ return false;
151
+ }
152
+ }
153
+ function isOnline() {
154
+ var _a;
155
+ if (!isBrowser()) return true;
156
+ return (_a = navigator.onLine) != null ? _a : true;
157
+ }
158
+ function getCurrentUrl() {
159
+ if (!isBrowser()) return void 0;
160
+ return window.location.href;
161
+ }
162
+ function getCurrentPath() {
163
+ if (!isBrowser()) return "unknown";
164
+ return window.location.pathname;
165
+ }
166
+ function getUserAgent() {
167
+ if (!isBrowser()) return void 0;
168
+ return navigator.userAgent;
169
+ }
170
+ function getReferrer() {
171
+ if (!isBrowser()) return void 0;
172
+ return document.referrer || void 0;
173
+ }
174
+
175
+ // src/queue/storage.ts
176
+ var OfflineStorage = class {
177
+ constructor(maxEvents = 100) {
178
+ this.storageKey = STORAGE_KEYS.EVENT_QUEUE;
179
+ this.maxEvents = maxEvents;
180
+ this.available = isLocalStorageAvailable();
181
+ }
182
+ isAvailable() {
183
+ return this.available;
184
+ }
185
+ store(event) {
186
+ if (!this.available) return false;
187
+ const logger = getLogger();
188
+ try {
189
+ const events = this.getAll();
190
+ const storedEvent = {
191
+ event,
192
+ storedAt: (/* @__PURE__ */ new Date()).toISOString(),
193
+ retries: 0
194
+ };
195
+ events.push(storedEvent);
196
+ while (events.length > this.maxEvents) {
197
+ events.shift();
198
+ }
199
+ localStorage.setItem(this.storageKey, JSON.stringify(events));
200
+ logger.log(`Event stored offline. Total stored: ${events.length}`);
201
+ return true;
202
+ } catch (error) {
203
+ logger.error("Failed to store event offline:", error);
204
+ return false;
205
+ }
206
+ }
207
+ storeMany(eventsToStore) {
208
+ if (!this.available || eventsToStore.length === 0) return 0;
209
+ const logger = getLogger();
210
+ let stored = 0;
211
+ try {
212
+ const events = this.getAll();
213
+ for (const event of eventsToStore) {
214
+ const storedEvent = {
215
+ event,
216
+ storedAt: (/* @__PURE__ */ new Date()).toISOString(),
217
+ retries: 0
218
+ };
219
+ events.push(storedEvent);
220
+ stored++;
221
+ }
222
+ while (events.length > this.maxEvents) {
223
+ events.shift();
224
+ }
225
+ localStorage.setItem(this.storageKey, JSON.stringify(events));
226
+ logger.log(`Stored ${stored} events offline. Total stored: ${events.length}`);
227
+ return stored;
228
+ } catch (error) {
229
+ logger.error("Failed to store events offline:", error);
230
+ return stored;
231
+ }
232
+ }
233
+ getAll() {
234
+ if (!this.available) return [];
235
+ try {
236
+ const data = localStorage.getItem(this.storageKey);
237
+ if (!data) return [];
238
+ return JSON.parse(data);
239
+ } catch (e) {
240
+ return [];
241
+ }
242
+ }
243
+ retrieveAll() {
244
+ if (!this.available) return [];
245
+ const logger = getLogger();
246
+ try {
247
+ const stored = this.getAll();
248
+ if (stored.length === 0) return [];
249
+ localStorage.removeItem(this.storageKey);
250
+ logger.log(`Retrieved ${stored.length} stored events`);
251
+ return stored.map((s) => s.event);
252
+ } catch (error) {
253
+ logger.error("Failed to retrieve stored events:", error);
254
+ return [];
255
+ }
256
+ }
257
+ count() {
258
+ return this.getAll().length;
259
+ }
260
+ clear() {
261
+ if (!this.available) return;
262
+ try {
263
+ localStorage.removeItem(this.storageKey);
264
+ } catch (e) {
265
+ }
266
+ }
267
+ };
268
+
269
+ // src/transport/sender.ts
270
+ async function sendBatch(apiUrl, token, events) {
271
+ const logger = getLogger();
272
+ const endpoint = `${apiUrl.replace(/\/$/, "")}/events`;
273
+ if (events.length === 0) {
274
+ return { success: true, count: 0 };
275
+ }
4
276
  const sendRequest = async () => {
5
277
  try {
278
+ const body = events.length === 1 ? formatEventForApi(events[0]) : { events: events.map(formatEventForApi) };
6
279
  const response = await fetch(endpoint, {
7
280
  method: "POST",
8
281
  headers: {
9
282
  "Content-Type": "application/json",
10
283
  "x-blinker-token": token
11
284
  },
12
- body: JSON.stringify({
13
- type: event.type,
14
- message: event.message,
15
- payload: event.payload || {},
16
- timestamp: event.timestamp,
17
- url: event.url,
18
- userAgent: event.userAgent
19
- })
285
+ body: JSON.stringify(body)
20
286
  });
21
287
  if (!response.ok) {
22
288
  const errorText = await response.text().catch(() => "Unknown error");
23
- if (debug) {
24
- console.error(`[Blinker] Failed to send event: ${response.status} - ${errorText}`);
25
- }
289
+ logger.error(`Failed to send events: ${response.status} - ${errorText}`);
26
290
  return { success: false, error: `HTTP ${response.status}: ${errorText}` };
27
291
  }
28
- if (debug) {
29
- console.log(`[Blinker] Event sent successfully:`, event.type);
30
- }
31
- return { success: true };
292
+ logger.log(`Events sent successfully: ${events.length} event(s)`);
293
+ return { success: true, count: events.length };
32
294
  } catch (error) {
33
295
  const errorMessage = error instanceof Error ? error.message : "Unknown error";
34
- if (debug) {
35
- console.error(`[Blinker] Error sending event:`, errorMessage);
36
- }
296
+ logger.error("Error sending events:", errorMessage);
37
297
  return { success: false, error: errorMessage };
38
298
  }
39
- };
40
- const result = await sendRequest();
41
- if (!result.success && typeof navigator !== "undefined" && !navigator.onLine) {
42
- if (debug) {
43
- console.log(`[Blinker] Offline, waiting for connection to retry...`);
44
- }
45
- const waitForOnline = new Promise((resolve) => {
46
- const timeout = setTimeout(() => resolve(false), 3e4);
47
- const onlineHandler = () => {
48
- clearTimeout(timeout);
49
- if (typeof window !== "undefined") {
50
- window.removeEventListener("online", onlineHandler);
51
- }
52
- resolve(true);
53
- };
54
- if (typeof window !== "undefined") {
55
- window.addEventListener("online", onlineHandler);
56
- } else {
57
- resolve(false);
58
- }
59
- });
60
- const isOnline = await waitForOnline;
61
- if (isOnline) {
62
- if (debug) {
63
- console.log(`[Blinker] Back online, retrying...`);
64
- }
65
- return sendRequest();
299
+ };
300
+ const result = await sendRequest();
301
+ if (!result.success && isBrowser() && !isOnline()) {
302
+ logger.log("Offline, waiting for connection to retry...");
303
+ const isBackOnline = await waitForOnline(TIMING.OFFLINE_RETRY_TIMEOUT);
304
+ if (isBackOnline) {
305
+ logger.log("Back online, retrying...");
306
+ return sendRequest();
307
+ }
308
+ }
309
+ return result;
310
+ }
311
+ function formatEventForApi(event) {
312
+ return {
313
+ type: event.type,
314
+ message: event.message,
315
+ payload: event.payload || {},
316
+ timestamp: event.timestamp,
317
+ url: event.url,
318
+ userAgent: event.userAgent,
319
+ sessionId: event.sessionId,
320
+ userId: event.userId,
321
+ userTraits: event.userTraits,
322
+ context: event.context,
323
+ count: event.count
324
+ };
325
+ }
326
+ function waitForOnline(timeout) {
327
+ return new Promise((resolve) => {
328
+ if (!isBrowser()) {
329
+ resolve(false);
330
+ return;
331
+ }
332
+ const timeoutId = setTimeout(() => resolve(false), timeout);
333
+ const onlineHandler = () => {
334
+ clearTimeout(timeoutId);
335
+ window.removeEventListener("online", onlineHandler);
336
+ resolve(true);
337
+ };
338
+ window.addEventListener("online", onlineHandler);
339
+ });
340
+ }
341
+
342
+ // src/queue/EventQueue.ts
343
+ var EventQueue = class {
344
+ constructor(token, config) {
345
+ this.queue = [];
346
+ this.flushTimer = null;
347
+ this.isFlushing = false;
348
+ this.handleOnline = () => {
349
+ const logger = getLogger();
350
+ logger.log("Back online, flushing stored events");
351
+ this.flush();
352
+ };
353
+ var _a, _b, _c, _d, _e, _f;
354
+ this.token = token;
355
+ this.config = {
356
+ endpoint: (_a = config == null ? void 0 : config.endpoint) != null ? _a : DEFAULTS.endpoint,
357
+ enableBatching: (_b = config == null ? void 0 : config.enableBatching) != null ? _b : DEFAULTS.enableBatching,
358
+ batchSize: (_c = config == null ? void 0 : config.batchSize) != null ? _c : DEFAULTS.batchSize,
359
+ flushInterval: Math.max(
360
+ TIMING.MIN_FLUSH_INTERVAL,
361
+ Math.min((_d = config == null ? void 0 : config.flushInterval) != null ? _d : DEFAULTS.flushInterval, TIMING.MAX_FLUSH_INTERVAL)
362
+ ),
363
+ enableOfflineStorage: (_e = config == null ? void 0 : config.enableOfflineStorage) != null ? _e : DEFAULTS.enableOfflineStorage,
364
+ maxStoredEvents: (_f = config == null ? void 0 : config.maxStoredEvents) != null ? _f : DEFAULTS.maxStoredEvents,
365
+ onError: config == null ? void 0 : config.onError
366
+ };
367
+ this.storage = new OfflineStorage(this.config.maxStoredEvents);
368
+ if (this.config.enableBatching) {
369
+ this.startFlushTimer();
370
+ }
371
+ if (isBrowser() && this.config.enableOfflineStorage) {
372
+ window.addEventListener("online", this.handleOnline);
373
+ }
374
+ }
375
+ async add(event) {
376
+ const logger = getLogger();
377
+ if (!this.config.enableBatching) {
378
+ return this.sendImmediately([event]);
379
+ }
380
+ this.queue.push(event);
381
+ logger.log(`Event queued. Queue size: ${this.queue.length}`);
382
+ if (this.queue.length >= this.config.batchSize) {
383
+ return this.flush();
384
+ }
385
+ return { success: true };
386
+ }
387
+ async flush() {
388
+ const logger = getLogger();
389
+ if (this.isFlushing) {
390
+ logger.log("Flush already in progress, skipping");
391
+ return { success: true };
392
+ }
393
+ const events = [...this.queue];
394
+ this.queue = [];
395
+ if (this.config.enableOfflineStorage && isOnline()) {
396
+ const storedEvents = this.storage.retrieveAll();
397
+ if (storedEvents.length > 0) {
398
+ events.unshift(...storedEvents);
399
+ logger.log(`Including ${storedEvents.length} stored offline events`);
400
+ }
401
+ }
402
+ if (events.length === 0) {
403
+ return { success: true, count: 0 };
404
+ }
405
+ this.isFlushing = true;
406
+ try {
407
+ const result = await this.sendImmediately(events);
408
+ if (!result.success && this.config.enableOfflineStorage && !isOnline()) {
409
+ logger.log("Send failed while offline, storing events");
410
+ this.storage.storeMany(events);
411
+ }
412
+ return result;
413
+ } finally {
414
+ this.isFlushing = false;
415
+ }
416
+ }
417
+ async sendImmediately(events) {
418
+ const logger = getLogger();
419
+ if (!isOnline() && this.config.enableOfflineStorage) {
420
+ logger.log("Offline, storing events for later");
421
+ const stored = this.storage.storeMany(events);
422
+ return { success: true, count: stored };
423
+ }
424
+ const result = await sendBatch(this.config.endpoint, this.token, events);
425
+ if (!result.success && this.config.onError) {
426
+ this.config.onError(result.error || "Unknown error sending events");
427
+ }
428
+ return result;
429
+ }
430
+ getStats() {
431
+ const queueSize = this.queue.length;
432
+ const storedSize = this.storage.count();
433
+ return {
434
+ queueSize,
435
+ storedSize,
436
+ totalPending: queueSize + storedSize
437
+ };
438
+ }
439
+ size() {
440
+ return this.queue.length;
441
+ }
442
+ startFlushTimer() {
443
+ if (this.flushTimer) return;
444
+ this.flushTimer = setInterval(() => {
445
+ if (this.queue.length > 0) {
446
+ this.flush();
447
+ }
448
+ }, this.config.flushInterval);
449
+ }
450
+ stopFlushTimer() {
451
+ if (this.flushTimer) {
452
+ clearInterval(this.flushTimer);
453
+ this.flushTimer = null;
454
+ }
455
+ }
456
+ destroy() {
457
+ this.stopFlushTimer();
458
+ if (isBrowser()) {
459
+ window.removeEventListener("online", this.handleOnline);
460
+ }
461
+ if (this.queue.length > 0 && isOnline()) {
462
+ this.flush();
463
+ } else if (this.queue.length > 0 && this.config.enableOfflineStorage) {
464
+ this.storage.storeMany(this.queue);
465
+ }
466
+ this.queue = [];
467
+ }
468
+ };
469
+
470
+ // src/utils/hash.ts
471
+ function simpleHash(str) {
472
+ let hash = 5381;
473
+ for (let i = 0; i < str.length; i++) {
474
+ hash = (hash << 5) + hash ^ str.charCodeAt(i);
475
+ }
476
+ return (hash >>> 0).toString(16);
477
+ }
478
+ function generateErrorFingerprint(message, stack) {
479
+ let input = message;
480
+ if (stack) {
481
+ const stackLines = stack.split("\n").slice(1, 4);
482
+ const cleanedStack = stackLines.map((line) => line.replace(/:\d+:\d+\)?$/, "").trim()).join("|");
483
+ input += "|" + cleanedStack;
484
+ }
485
+ return simpleHash(input);
486
+ }
487
+ function generateUniqueId() {
488
+ const timestamp = Date.now().toString(36);
489
+ const randomPart = Math.random().toString(36).substring(2, 10);
490
+ return `${timestamp}-${randomPart}`;
491
+ }
492
+
493
+ // src/context/SessionManager.ts
494
+ var SessionManager = class {
495
+ constructor() {
496
+ this.session = null;
497
+ this.storageKey = STORAGE_KEYS.SESSION;
498
+ this.storageAvailable = isSessionStorageAvailable();
499
+ this.initSession();
500
+ }
501
+ initSession() {
502
+ const logger = getLogger();
503
+ if (this.storageAvailable) {
504
+ try {
505
+ const stored = sessionStorage.getItem(this.storageKey);
506
+ if (stored) {
507
+ this.session = JSON.parse(stored);
508
+ logger.log("Session restored:", this.session.id);
509
+ return;
510
+ }
511
+ } catch (e) {
512
+ }
513
+ }
514
+ this.session = {
515
+ id: generateUniqueId(),
516
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
517
+ pageViews: 0,
518
+ lastActivityAt: (/* @__PURE__ */ new Date()).toISOString()
519
+ };
520
+ this.persist();
521
+ logger.log("New session created:", this.session.id);
522
+ }
523
+ getSessionId() {
524
+ var _a, _b;
525
+ return (_b = (_a = this.session) == null ? void 0 : _a.id) != null ? _b : generateUniqueId();
526
+ }
527
+ getSession() {
528
+ if (!this.session) {
529
+ this.initSession();
530
+ }
531
+ return { ...this.session };
532
+ }
533
+ recordPageView() {
534
+ if (this.session) {
535
+ this.session.pageViews++;
536
+ this.session.lastActivityAt = (/* @__PURE__ */ new Date()).toISOString();
537
+ this.persist();
538
+ }
539
+ }
540
+ touch() {
541
+ if (this.session) {
542
+ this.session.lastActivityAt = (/* @__PURE__ */ new Date()).toISOString();
543
+ this.persist();
544
+ }
545
+ }
546
+ getDuration() {
547
+ if (!this.session) return 0;
548
+ const start = new Date(this.session.startedAt).getTime();
549
+ return Date.now() - start;
550
+ }
551
+ persist() {
552
+ if (!this.storageAvailable || !this.session) return;
553
+ try {
554
+ sessionStorage.setItem(this.storageKey, JSON.stringify(this.session));
555
+ } catch (e) {
556
+ }
557
+ }
558
+ endSession() {
559
+ if (this.storageAvailable) {
560
+ try {
561
+ sessionStorage.removeItem(this.storageKey);
562
+ } catch (e) {
563
+ }
564
+ }
565
+ this.session = null;
566
+ }
567
+ };
568
+
569
+ // src/context/UserContext.ts
570
+ var UserContext = class {
571
+ constructor(sessionManager) {
572
+ this.user = null;
573
+ this.customContext = {};
574
+ this.sessionManager = sessionManager;
575
+ }
576
+ identify(userId, traits) {
577
+ const logger = getLogger();
578
+ this.user = {
579
+ userId,
580
+ traits: traits ? { ...traits } : void 0,
581
+ identifiedAt: (/* @__PURE__ */ new Date()).toISOString()
582
+ };
583
+ logger.log("User identified:", userId);
584
+ }
585
+ getUserId() {
586
+ var _a;
587
+ return (_a = this.user) == null ? void 0 : _a.userId;
588
+ }
589
+ getUserTraits() {
590
+ var _a;
591
+ return ((_a = this.user) == null ? void 0 : _a.traits) ? { ...this.user.traits } : void 0;
592
+ }
593
+ isIdentified() {
594
+ return this.user !== null;
595
+ }
596
+ setContext(key, value) {
597
+ const logger = getLogger();
598
+ this.customContext[key] = value;
599
+ logger.log("Context set:", key);
600
+ }
601
+ getContextValue(key) {
602
+ return this.customContext[key];
603
+ }
604
+ getCustomContext() {
605
+ return { ...this.customContext };
606
+ }
607
+ getEventContext() {
608
+ var _a, _b;
609
+ return {
610
+ sessionId: this.sessionManager.getSessionId(),
611
+ userId: (_a = this.user) == null ? void 0 : _a.userId,
612
+ userTraits: ((_b = this.user) == null ? void 0 : _b.traits) ? { ...this.user.traits } : void 0,
613
+ custom: { ...this.customContext }
614
+ };
615
+ }
616
+ clearIdentity() {
617
+ const logger = getLogger();
618
+ this.user = null;
619
+ logger.log("User identity cleared");
620
+ }
621
+ clearAll() {
622
+ const logger = getLogger();
623
+ this.user = null;
624
+ this.customContext = {};
625
+ logger.log("All context cleared");
626
+ }
627
+ };
628
+
629
+ // src/sampling/RateLimiter.ts
630
+ var MINUTE_MS = 60 * 1e3;
631
+ var RateLimiter = class {
632
+ constructor(maxEventsPerMinute) {
633
+ this.maxPerMinute = maxEventsPerMinute != null ? maxEventsPerMinute : DEFAULTS.maxEventsPerMinute;
634
+ this.state = {
635
+ count: 0,
636
+ windowStart: Date.now(),
637
+ dropped: 0
638
+ };
639
+ }
640
+ allow() {
641
+ const logger = getLogger();
642
+ if (this.maxPerMinute === 0) {
643
+ return true;
644
+ }
645
+ const now = Date.now();
646
+ if (now - this.state.windowStart >= MINUTE_MS) {
647
+ if (this.state.dropped > 0) {
648
+ logger.warn(`Rate limit: ${this.state.dropped} events dropped in last minute`);
649
+ }
650
+ this.state = {
651
+ count: 0,
652
+ windowStart: now,
653
+ dropped: 0
654
+ };
655
+ }
656
+ if (this.state.count < this.maxPerMinute) {
657
+ this.state.count++;
658
+ return true;
659
+ }
660
+ this.state.dropped++;
661
+ if (this.state.dropped === 1) {
662
+ logger.warn(`Rate limit reached: ${this.maxPerMinute} events/minute`);
663
+ }
664
+ return false;
665
+ }
666
+ getState() {
667
+ return { ...this.state };
668
+ }
669
+ getRemaining() {
670
+ if (this.maxPerMinute === 0) return Infinity;
671
+ if (Date.now() - this.state.windowStart >= MINUTE_MS) {
672
+ return this.maxPerMinute;
673
+ }
674
+ return Math.max(0, this.maxPerMinute - this.state.count);
675
+ }
676
+ reset() {
677
+ this.state = {
678
+ count: 0,
679
+ windowStart: Date.now(),
680
+ dropped: 0
681
+ };
682
+ }
683
+ setLimit(maxEventsPerMinute) {
684
+ this.maxPerMinute = maxEventsPerMinute;
685
+ }
686
+ };
687
+
688
+ // src/sampling/Sampler.ts
689
+ var Sampler = class {
690
+ constructor(sampleRate) {
691
+ this.sampled = 0;
692
+ this.dropped = 0;
693
+ this.sampleRate = Math.max(0, Math.min(1, sampleRate != null ? sampleRate : DEFAULTS.sampleRate));
694
+ }
695
+ shouldSample(isError = false) {
696
+ const logger = getLogger();
697
+ if (isError) {
698
+ this.sampled++;
699
+ return true;
700
+ }
701
+ if (this.sampleRate >= 1) {
702
+ this.sampled++;
703
+ return true;
704
+ }
705
+ if (this.sampleRate <= 0) {
706
+ this.dropped++;
707
+ return false;
708
+ }
709
+ if (Math.random() < this.sampleRate) {
710
+ this.sampled++;
711
+ return true;
712
+ }
713
+ this.dropped++;
714
+ logger.log("Event sampled out");
715
+ return false;
716
+ }
717
+ getStats() {
718
+ return {
719
+ sampled: this.sampled,
720
+ dropped: this.dropped,
721
+ rate: this.sampleRate
722
+ };
723
+ }
724
+ setRate(rate) {
725
+ this.sampleRate = Math.max(0, Math.min(1, rate));
726
+ }
727
+ getRate() {
728
+ return this.sampleRate;
729
+ }
730
+ resetStats() {
731
+ this.sampled = 0;
732
+ this.dropped = 0;
733
+ }
734
+ };
735
+
736
+ // src/capture/deduplication.ts
737
+ var Deduplicator = class {
738
+ constructor(enabled, windowMs) {
739
+ this.entries = /* @__PURE__ */ new Map();
740
+ this.cleanupTimer = null;
741
+ this.enabled = enabled != null ? enabled : DEFAULTS.enableDeduplication;
742
+ this.windowMs = windowMs != null ? windowMs : DEFAULTS.deduplicationWindow;
743
+ if (this.enabled) {
744
+ this.startCleanup();
745
+ }
746
+ }
747
+ check(message, stack) {
748
+ const fingerprint = generateErrorFingerprint(message, stack);
749
+ if (!this.enabled) {
750
+ return {
751
+ shouldSend: true,
752
+ fingerprint,
753
+ count: 1,
754
+ isDuplicate: false
755
+ };
756
+ }
757
+ const logger = getLogger();
758
+ const now = Date.now();
759
+ const existing = this.entries.get(fingerprint);
760
+ if (existing) {
761
+ if (now - existing.firstSeen < this.windowMs) {
762
+ existing.count++;
763
+ existing.lastSeen = now;
764
+ logger.log(`Duplicate error detected (count: ${existing.count}):`, message.substring(0, 50));
765
+ return {
766
+ shouldSend: false,
767
+ fingerprint,
768
+ count: existing.count,
769
+ isDuplicate: true
770
+ };
771
+ }
772
+ this.entries.delete(fingerprint);
773
+ }
774
+ this.entries.set(fingerprint, {
775
+ fingerprint,
776
+ firstSeen: now,
777
+ count: 1,
778
+ lastSeen: now
779
+ });
780
+ return {
781
+ shouldSend: true,
782
+ fingerprint,
783
+ count: 1,
784
+ isDuplicate: false
785
+ };
786
+ }
787
+ getCount(fingerprint) {
788
+ var _a, _b;
789
+ return (_b = (_a = this.entries.get(fingerprint)) == null ? void 0 : _a.count) != null ? _b : 1;
790
+ }
791
+ flush() {
792
+ const duplicates = Array.from(this.entries.values()).filter((e) => e.count > 1);
793
+ this.entries.clear();
794
+ return duplicates;
795
+ }
796
+ startCleanup() {
797
+ this.cleanupTimer = setInterval(() => {
798
+ this.cleanup();
799
+ }, this.windowMs);
800
+ }
801
+ cleanup() {
802
+ const now = Date.now();
803
+ const logger = getLogger();
804
+ let removed = 0;
805
+ for (const [fingerprint, entry] of this.entries) {
806
+ if (now - entry.firstSeen >= this.windowMs) {
807
+ this.entries.delete(fingerprint);
808
+ removed++;
809
+ }
810
+ }
811
+ if (removed > 0) {
812
+ logger.log(`Deduplication cleanup: removed ${removed} expired entries`);
813
+ }
814
+ }
815
+ destroy() {
816
+ if (this.cleanupTimer) {
817
+ clearInterval(this.cleanupTimer);
818
+ this.cleanupTimer = null;
819
+ }
820
+ this.entries.clear();
821
+ }
822
+ getStats() {
823
+ return {
824
+ entries: this.entries.size,
825
+ enabled: this.enabled,
826
+ windowMs: this.windowMs
827
+ };
828
+ }
829
+ };
830
+
831
+ // src/capture/errorHandler.ts
832
+ var originalOnError = null;
833
+ var originalOnUnhandledRejection = null;
834
+ var isSetup = false;
835
+ function setupErrorHandlers(callback, options = {}) {
836
+ const { captureErrors = true, captureRejections = true } = options;
837
+ const logger = getLogger();
838
+ if (isSetup) {
839
+ logger.warn("Error handlers already set up");
840
+ return;
841
+ }
842
+ if (!isBrowser()) {
843
+ logger.log("Not in browser environment, skipping error handlers");
844
+ return;
845
+ }
846
+ if (captureErrors) {
847
+ originalOnError = window.onerror;
848
+ window.onerror = function(message, source, lineno, colno, error) {
849
+ const errorMessage = typeof message === "string" ? message : message.type || "Unknown error";
850
+ const payload = {
851
+ source,
852
+ lineno,
853
+ colno,
854
+ stack: error == null ? void 0 : error.stack,
855
+ errorType: error == null ? void 0 : error.name
856
+ };
857
+ logger.log("Captured error:", errorMessage);
858
+ callback("error", errorMessage, payload);
859
+ if (typeof originalOnError === "function") {
860
+ return originalOnError.call(window, message, source, lineno, colno, error);
861
+ }
862
+ return false;
863
+ };
864
+ }
865
+ if (captureRejections) {
866
+ originalOnUnhandledRejection = window.onunhandledrejection;
867
+ window.onunhandledrejection = function(event) {
868
+ let message = "Unhandled Rejection";
869
+ const payload = {};
870
+ const reason = event.reason;
871
+ if (reason instanceof Error) {
872
+ message = reason.message || message;
873
+ payload.stack = reason.stack;
874
+ payload.errorType = reason.name;
875
+ } else if (typeof reason === "string") {
876
+ message = reason;
877
+ } else if (reason !== void 0 && reason !== null) {
878
+ try {
879
+ message = JSON.stringify(reason);
880
+ } catch (e) {
881
+ message = String(reason);
882
+ }
883
+ }
884
+ logger.log("Captured unhandled rejection:", message);
885
+ callback("error", message, payload);
886
+ if (typeof originalOnUnhandledRejection === "function") {
887
+ originalOnUnhandledRejection.call(window, event);
888
+ }
889
+ };
890
+ }
891
+ isSetup = true;
892
+ logger.log("Error handlers set up");
893
+ }
894
+ function teardownErrorHandlers() {
895
+ const logger = getLogger();
896
+ if (!isBrowser()) {
897
+ return;
898
+ }
899
+ if (originalOnError !== void 0) {
900
+ window.onerror = originalOnError;
901
+ }
902
+ if (originalOnUnhandledRejection !== void 0) {
903
+ window.onunhandledrejection = originalOnUnhandledRejection;
904
+ }
905
+ originalOnError = null;
906
+ originalOnUnhandledRejection = null;
907
+ isSetup = false;
908
+ logger.log("Error handlers torn down");
909
+ }
910
+
911
+ // src/widget/types.ts
912
+ var DEFAULT_WIDGET_CONFIG = {
913
+ enabled: false,
914
+ theme: {
915
+ primary: "#6366f1",
916
+ background: "#ffffff",
917
+ text: "#1f2937",
918
+ accent: "#10b981"
919
+ },
920
+ messages: {
921
+ greeting: "Opa! Algo n\xE3o saiu como esperado.",
922
+ subtext: "Pode nos ajudar a entender o que aconteceu?",
923
+ thankYou: "Valeu! J\xE1 estamos olhando isso.",
924
+ buttonSend: "Enviar",
925
+ buttonSkip: "Agora n\xE3o"
926
+ },
927
+ position: "bottom-right",
928
+ delay: 2e3,
929
+ maxShowsPerSession: 2,
930
+ cooldownMinutes: 30
931
+ };
932
+
933
+ // src/widget/Avatar.ts
934
+ var Avatar = class {
935
+ constructor(theme, onClick) {
936
+ this.element = null;
937
+ this.pupilLeft = null;
938
+ this.pupilRight = null;
939
+ this.mouseTrackingEnabled = true;
940
+ this.theme = theme;
941
+ this.onClick = onClick;
942
+ this.handleMouseMove = this.handleMouseMove.bind(this);
943
+ }
944
+ create() {
945
+ const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
946
+ svg.setAttribute("viewBox", "0 0 100 100");
947
+ svg.setAttribute("class", "blinker-avatar");
948
+ svg.innerHTML = this.getSVGContent();
949
+ svg.addEventListener("click", this.onClick);
950
+ svg.addEventListener("mouseenter", () => this.setState("hover"));
951
+ svg.addEventListener("mouseleave", () => this.setState("idle"));
952
+ this.element = svg;
953
+ this.pupilLeft = svg.querySelector(".blinker-pupil-left");
954
+ this.pupilRight = svg.querySelector(".blinker-pupil-right");
955
+ document.addEventListener("mousemove", this.handleMouseMove);
956
+ return svg;
957
+ }
958
+ getSVGContent() {
959
+ return `
960
+ <defs>
961
+ <linearGradient id="blinker-body-gradient" x1="0%" y1="0%" x2="0%" y2="100%">
962
+ <stop offset="0%" style="stop-color:${this.theme.primary};stop-opacity:1" />
963
+ <stop offset="100%" style="stop-color:${this.adjustColor(this.theme.primary, -30)};stop-opacity:1" />
964
+ </linearGradient>
965
+ <filter id="blinker-shadow" x="-20%" y="-20%" width="140%" height="140%">
966
+ <feDropShadow dx="0" dy="4" stdDeviation="4" flood-opacity="0.2"/>
967
+ </filter>
968
+ </defs>
969
+
970
+ <!-- Glow effect -->
971
+ <ellipse class="blinker-avatar-glow" cx="50" cy="85" rx="25" ry="8" />
972
+
973
+ <!-- Body -->
974
+ <g class="blinker-avatar-body" filter="url(#blinker-shadow)">
975
+ <!-- Main body shape - rounded rectangle with personality -->
976
+ <path d="
977
+ M 25 30
978
+ Q 25 15, 50 15
979
+ Q 75 15, 75 30
980
+ L 75 65
981
+ Q 75 85, 50 85
982
+ Q 25 85, 25 65
983
+ Z
984
+ " fill="url(#blinker-body-gradient)" />
985
+
986
+ <!-- Antenna/Top detail -->
987
+ <circle cx="50" cy="12" r="4" fill="${this.theme.primary}" />
988
+ <line x1="50" y1="16" x2="50" y2="22" stroke="${this.theme.primary}" stroke-width="3" stroke-linecap="round" />
989
+ </g>
990
+
991
+ <!-- Face -->
992
+ <g class="blinker-avatar-face">
993
+ <!-- Left eye container -->
994
+ <ellipse class="blinker-avatar-eye blinker-avatar-eye-left" cx="38" cy="42" rx="10" ry="11" />
995
+ <!-- Right eye container -->
996
+ <ellipse class="blinker-avatar-eye blinker-avatar-eye-right" cx="62" cy="42" rx="10" ry="11" />
997
+
998
+ <!-- Left pupil -->
999
+ <circle class="blinker-avatar-pupil blinker-pupil-left" cx="38" cy="43" r="5" />
1000
+ <!-- Right pupil -->
1001
+ <circle class="blinker-avatar-pupil blinker-pupil-right" cx="62" cy="43" r="5" />
1002
+
1003
+ <!-- Eye shine -->
1004
+ <circle cx="35" cy="40" r="2" fill="white" opacity="0.8" />
1005
+ <circle cx="59" cy="40" r="2" fill="white" opacity="0.8" />
1006
+
1007
+ <!-- Mouth -->
1008
+ <path class="blinker-avatar-mouth" d="M 40 60 Q 50 68, 60 60" />
1009
+ </g>
1010
+
1011
+ <!-- Decorative elements -->
1012
+ <circle cx="30" cy="55" r="3" fill="${this.theme.primary}" opacity="0.3" />
1013
+ <circle cx="70" cy="55" r="3" fill="${this.theme.primary}" opacity="0.3" />
1014
+ `;
1015
+ }
1016
+ handleMouseMove(e) {
1017
+ if (!this.mouseTrackingEnabled || !this.element || !this.pupilLeft || !this.pupilRight) return;
1018
+ const rect = this.element.getBoundingClientRect();
1019
+ const centerX = rect.left + rect.width / 2;
1020
+ const centerY = rect.top + rect.height / 2;
1021
+ const deltaX = (e.clientX - centerX) / 50;
1022
+ const deltaY = (e.clientY - centerY) / 50;
1023
+ const maxOffset = 3;
1024
+ const offsetX = Math.max(-maxOffset, Math.min(maxOffset, deltaX));
1025
+ const offsetY = Math.max(-maxOffset, Math.min(maxOffset, deltaY));
1026
+ this.pupilLeft.setAttribute("transform", `translate(${offsetX}, ${offsetY})`);
1027
+ this.pupilRight.setAttribute("transform", `translate(${offsetX}, ${offsetY})`);
1028
+ }
1029
+ setState(state) {
1030
+ if (!this.element) return;
1031
+ this.element.classList.remove("idle", "hover", "minimized", "talking");
1032
+ this.element.classList.add(state);
1033
+ }
1034
+ setMinimized(minimized) {
1035
+ if (!this.element) return;
1036
+ if (minimized) {
1037
+ this.element.classList.add("minimized");
1038
+ this.mouseTrackingEnabled = false;
1039
+ } else {
1040
+ this.element.classList.remove("minimized");
1041
+ this.mouseTrackingEnabled = true;
1042
+ }
1043
+ }
1044
+ updateTheme(theme) {
1045
+ this.theme = theme;
1046
+ if (this.element) {
1047
+ this.element.innerHTML = this.getSVGContent();
1048
+ this.pupilLeft = this.element.querySelector(".blinker-pupil-left");
1049
+ this.pupilRight = this.element.querySelector(".blinker-pupil-right");
1050
+ }
1051
+ }
1052
+ adjustColor(hex, amount) {
1053
+ const num = parseInt(hex.replace("#", ""), 16);
1054
+ const r = Math.max(0, Math.min(255, (num >> 16) + amount));
1055
+ const g = Math.max(0, Math.min(255, (num >> 8 & 255) + amount));
1056
+ const b = Math.max(0, Math.min(255, (num & 255) + amount));
1057
+ return `#${(r << 16 | g << 8 | b).toString(16).padStart(6, "0")}`;
1058
+ }
1059
+ getElement() {
1060
+ return this.element;
1061
+ }
1062
+ destroy() {
1063
+ document.removeEventListener("mousemove", this.handleMouseMove);
1064
+ if (this.element) {
1065
+ this.element.removeEventListener("click", this.onClick);
1066
+ this.element.remove();
1067
+ this.element = null;
1068
+ }
1069
+ }
1070
+ };
1071
+
1072
+ // src/widget/Dialog.ts
1073
+ var Dialog = class {
1074
+ constructor(config, onSubmit, onClose) {
1075
+ this.overlay = null;
1076
+ this.dialog = null;
1077
+ this.questions = [];
1078
+ this.answers = {};
1079
+ this.errorId = "";
1080
+ this.state = "questions";
1081
+ this.config = config;
1082
+ this.onSubmit = onSubmit;
1083
+ this.onClose = onClose;
1084
+ }
1085
+ open(errorId, questions) {
1086
+ this.errorId = errorId;
1087
+ this.questions = questions;
1088
+ this.answers = {};
1089
+ this.state = "questions";
1090
+ this.render();
1091
+ }
1092
+ close() {
1093
+ if (this.overlay) {
1094
+ this.overlay.remove();
1095
+ this.overlay = null;
1096
+ }
1097
+ if (this.dialog) {
1098
+ this.dialog.remove();
1099
+ this.dialog = null;
1100
+ }
1101
+ this.onClose();
1102
+ }
1103
+ render() {
1104
+ this.close();
1105
+ this.overlay = document.createElement("div");
1106
+ this.overlay.className = "blinker-dialog-overlay";
1107
+ this.overlay.addEventListener("click", () => this.close());
1108
+ this.dialog = document.createElement("div");
1109
+ this.dialog.className = `blinker-dialog ${this.config.position}`;
1110
+ this.dialog.innerHTML = this.state === "questions" ? this.getQuestionsHTML() : this.getThankYouHTML();
1111
+ document.body.appendChild(this.overlay);
1112
+ document.body.appendChild(this.dialog);
1113
+ this.attachEventListeners();
1114
+ }
1115
+ getQuestionsHTML() {
1116
+ const { messages } = this.config;
1117
+ return `
1118
+ <div class="blinker-dialog-header">
1119
+ <svg class="blinker-dialog-avatar" viewBox="0 0 100 100">
1120
+ <defs>
1121
+ <linearGradient id="dialog-gradient" x1="0%" y1="0%" x2="0%" y2="100%">
1122
+ <stop offset="0%" style="stop-color:${this.config.theme.primary}" />
1123
+ <stop offset="100%" style="stop-color:${this.adjustColor(this.config.theme.primary, -30)}" />
1124
+ </linearGradient>
1125
+ </defs>
1126
+ <path d="M 25 30 Q 25 15, 50 15 Q 75 15, 75 30 L 75 65 Q 75 85, 50 85 Q 25 85, 25 65 Z" fill="url(#dialog-gradient)" />
1127
+ <ellipse cx="38" cy="42" rx="10" ry="11" fill="${this.config.theme.background}" />
1128
+ <ellipse cx="62" cy="42" rx="10" ry="11" fill="${this.config.theme.background}" />
1129
+ <circle cx="38" cy="43" r="5" fill="${this.config.theme.text}" />
1130
+ <circle cx="62" cy="43" r="5" fill="${this.config.theme.text}" />
1131
+ <path d="M 40 60 Q 50 68, 60 60" stroke="${this.config.theme.background}" stroke-width="2" fill="none" stroke-linecap="round" />
1132
+ </svg>
1133
+ <div class="blinker-dialog-title">
1134
+ <p class="blinker-dialog-greeting">${messages.greeting}</p>
1135
+ <p class="blinker-dialog-subtext">${messages.subtext}</p>
1136
+ </div>
1137
+ <button class="blinker-dialog-close" aria-label="Fechar">
1138
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
1139
+ <path d="M18 6L6 18M6 6l12 12" />
1140
+ </svg>
1141
+ </button>
1142
+ </div>
1143
+ <div class="blinker-dialog-body">
1144
+ ${this.questions.map((q) => this.getQuestionHTML(q)).join("")}
1145
+ </div>
1146
+ <div class="blinker-dialog-footer">
1147
+ <button class="blinker-btn blinker-btn-secondary" data-action="skip">
1148
+ ${messages.buttonSkip}
1149
+ </button>
1150
+ <button class="blinker-btn blinker-btn-primary" data-action="submit">
1151
+ ${messages.buttonSend}
1152
+ </button>
1153
+ </div>
1154
+ `;
1155
+ }
1156
+ getQuestionHTML(question) {
1157
+ var _a;
1158
+ const required = question.required ? " *" : "";
1159
+ switch (question.type) {
1160
+ case "text":
1161
+ return `
1162
+ <div class="blinker-question" data-question-id="${question.id}">
1163
+ <label class="blinker-question-label">${question.text}${required}</label>
1164
+ <textarea
1165
+ class="blinker-question-input blinker-question-textarea"
1166
+ placeholder="Digite sua resposta..."
1167
+ data-question-id="${question.id}"
1168
+ ></textarea>
1169
+ </div>
1170
+ `;
1171
+ case "select":
1172
+ return `
1173
+ <div class="blinker-question" data-question-id="${question.id}">
1174
+ <label class="blinker-question-label">${question.text}${required}</label>
1175
+ <select class="blinker-question-input blinker-question-select" data-question-id="${question.id}">
1176
+ <option value="">Selecione...</option>
1177
+ ${(_a = question.options) == null ? void 0 : _a.map((opt) => `<option value="${opt}">${opt}</option>`).join("")}
1178
+ </select>
1179
+ </div>
1180
+ `;
1181
+ case "boolean":
1182
+ return `
1183
+ <div class="blinker-question" data-question-id="${question.id}">
1184
+ <label class="blinker-question-label">${question.text}${required}</label>
1185
+ <div class="blinker-question-boolean">
1186
+ <button type="button" data-question-id="${question.id}" data-value="true">Sim</button>
1187
+ <button type="button" data-question-id="${question.id}" data-value="false">N\xE3o</button>
1188
+ </div>
1189
+ </div>
1190
+ `;
1191
+ default:
1192
+ return "";
1193
+ }
1194
+ }
1195
+ getThankYouHTML() {
1196
+ return `
1197
+ <div class="blinker-thank-you">
1198
+ <svg class="blinker-thank-you-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
1199
+ <path d="M22 11.08V12a10 10 0 1 1-5.93-9.14" />
1200
+ <polyline points="22 4 12 14.01 9 11.01" />
1201
+ </svg>
1202
+ <p class="blinker-thank-you-text">${this.config.messages.thankYou}</p>
1203
+ </div>
1204
+ `;
1205
+ }
1206
+ attachEventListeners() {
1207
+ if (!this.dialog) return;
1208
+ const closeBtn = this.dialog.querySelector(".blinker-dialog-close");
1209
+ closeBtn == null ? void 0 : closeBtn.addEventListener("click", () => this.close());
1210
+ const submitBtn = this.dialog.querySelector('[data-action="submit"]');
1211
+ submitBtn == null ? void 0 : submitBtn.addEventListener("click", () => this.handleSubmit());
1212
+ const skipBtn = this.dialog.querySelector('[data-action="skip"]');
1213
+ skipBtn == null ? void 0 : skipBtn.addEventListener("click", () => this.close());
1214
+ const textareas = this.dialog.querySelectorAll("textarea");
1215
+ textareas.forEach((textarea) => {
1216
+ textarea.addEventListener("input", (e) => {
1217
+ const target = e.target;
1218
+ const questionId = target.dataset.questionId;
1219
+ if (questionId) {
1220
+ this.answers[questionId] = target.value;
1221
+ }
1222
+ });
1223
+ });
1224
+ const selects = this.dialog.querySelectorAll("select");
1225
+ selects.forEach((select) => {
1226
+ select.addEventListener("change", (e) => {
1227
+ const target = e.target;
1228
+ const questionId = target.dataset.questionId;
1229
+ if (questionId) {
1230
+ this.answers[questionId] = target.value;
1231
+ }
1232
+ });
1233
+ });
1234
+ const booleanBtns = this.dialog.querySelectorAll(".blinker-question-boolean button");
1235
+ booleanBtns.forEach((btn) => {
1236
+ btn.addEventListener("click", (e) => {
1237
+ const target = e.target;
1238
+ const questionId = target.dataset.questionId;
1239
+ const value = target.dataset.value === "true";
1240
+ if (questionId) {
1241
+ this.answers[questionId] = value;
1242
+ const container = target.parentElement;
1243
+ container == null ? void 0 : container.querySelectorAll("button").forEach((b) => b.classList.remove("selected"));
1244
+ target.classList.add("selected");
1245
+ }
1246
+ });
1247
+ });
1248
+ }
1249
+ handleSubmit() {
1250
+ var _a;
1251
+ const requiredQuestions = this.questions.filter((q) => q.required);
1252
+ const missingRequired = requiredQuestions.some((q) => {
1253
+ const answer = this.answers[q.id];
1254
+ return answer === void 0 || answer === "";
1255
+ });
1256
+ if (missingRequired) {
1257
+ const firstRequired = requiredQuestions.find((q) => !this.answers[q.id]);
1258
+ if (firstRequired) {
1259
+ const input = (_a = this.dialog) == null ? void 0 : _a.querySelector(`[data-question-id="${firstRequired.id}"]`);
1260
+ if (input instanceof HTMLElement) {
1261
+ input.focus();
1262
+ input.style.borderColor = "#ef4444";
1263
+ setTimeout(() => {
1264
+ input.style.borderColor = "";
1265
+ }, 2e3);
1266
+ }
1267
+ }
1268
+ return;
1269
+ }
1270
+ const feedback = {
1271
+ errorId: this.errorId,
1272
+ answers: { ...this.answers },
1273
+ submittedAt: (/* @__PURE__ */ new Date()).toISOString()
1274
+ };
1275
+ this.onSubmit(feedback);
1276
+ this.state = "thankyou";
1277
+ this.render();
1278
+ setTimeout(() => {
1279
+ this.close();
1280
+ }, 2500);
1281
+ }
1282
+ updateConfig(config) {
1283
+ this.config = config;
1284
+ }
1285
+ adjustColor(hex, amount) {
1286
+ const num = parseInt(hex.replace("#", ""), 16);
1287
+ const r = Math.max(0, Math.min(255, (num >> 16) + amount));
1288
+ const g = Math.max(0, Math.min(255, (num >> 8 & 255) + amount));
1289
+ const b = Math.max(0, Math.min(255, (num & 255) + amount));
1290
+ return `#${(r << 16 | g << 8 | b).toString(16).padStart(6, "0")}`;
1291
+ }
1292
+ isOpen() {
1293
+ return this.dialog !== null;
1294
+ }
1295
+ };
1296
+
1297
+ // src/widget/QuestionEngine.ts
1298
+ var BASE_QUESTIONS = [
1299
+ {
1300
+ id: "what_doing",
1301
+ text: "O que voc\xEA estava tentando fazer?",
1302
+ type: "text",
1303
+ required: true
1304
+ }
1305
+ ];
1306
+ var QUESTION_RULES = [
1307
+ {
1308
+ id: "server_error",
1309
+ condition: (e) => e.httpCode !== void 0 && e.httpCode >= 500,
1310
+ priority: 10,
1311
+ questions: [
1312
+ {
1313
+ id: "form_filled",
1314
+ text: "Voc\xEA preencheu algum formul\xE1rio antes disso?",
1315
+ type: "boolean"
1316
+ },
1317
+ {
1318
+ id: "data_lost",
1319
+ text: "Voc\xEA perdeu algum dado importante?",
1320
+ type: "boolean"
1321
+ }
1322
+ ]
1323
+ },
1324
+ {
1325
+ id: "network_error",
1326
+ condition: (e) => e.message.toLowerCase().includes("network") || e.message.toLowerCase().includes("fetch") || e.message.toLowerCase().includes("failed to fetch") || e.errorType === "TypeError" && e.message.includes("fetch"),
1327
+ priority: 9,
1328
+ questions: [
1329
+ {
1330
+ id: "internet_working",
1331
+ text: "Sua internet est\xE1 funcionando normalmente?",
1332
+ type: "boolean"
1333
+ },
1334
+ {
1335
+ id: "using_vpn",
1336
+ text: "Voc\xEA est\xE1 usando VPN ou proxy?",
1337
+ type: "boolean"
1338
+ }
1339
+ ]
1340
+ },
1341
+ {
1342
+ id: "timeout_error",
1343
+ condition: (e) => e.message.toLowerCase().includes("timeout") || e.message.toLowerCase().includes("timed out"),
1344
+ priority: 8,
1345
+ questions: [
1346
+ {
1347
+ id: "page_slow",
1348
+ text: "A p\xE1gina estava lenta antes do erro?",
1349
+ type: "boolean"
1350
+ },
1351
+ {
1352
+ id: "first_time",
1353
+ text: "\xC9 a primeira vez que isso acontece?",
1354
+ type: "boolean"
1355
+ }
1356
+ ]
1357
+ },
1358
+ {
1359
+ id: "auth_error",
1360
+ condition: (e) => e.httpCode === 401 || e.httpCode === 403 || e.message.toLowerCase().includes("unauthorized") || e.message.toLowerCase().includes("forbidden"),
1361
+ priority: 7,
1362
+ questions: [
1363
+ {
1364
+ id: "logged_in",
1365
+ text: "Voc\xEA estava logado no sistema?",
1366
+ type: "boolean"
1367
+ },
1368
+ {
1369
+ id: "session_expired",
1370
+ text: "Ficou muito tempo sem usar o sistema?",
1371
+ type: "boolean"
1372
+ }
1373
+ ]
1374
+ },
1375
+ {
1376
+ id: "not_found",
1377
+ condition: (e) => e.httpCode === 404,
1378
+ priority: 6,
1379
+ questions: [
1380
+ {
1381
+ id: "how_arrived",
1382
+ text: "Como voc\xEA chegou nessa p\xE1gina?",
1383
+ type: "select",
1384
+ options: ["Digitei o link", "Cliquei em um bot\xE3o", "Usei o menu", "Outro"]
1385
+ }
1386
+ ]
1387
+ },
1388
+ {
1389
+ id: "validation_error",
1390
+ condition: (e) => e.httpCode === 400 || e.httpCode === 422 || e.message.toLowerCase().includes("validation") || e.message.toLowerCase().includes("invalid"),
1391
+ priority: 5,
1392
+ questions: [
1393
+ {
1394
+ id: "what_filled",
1395
+ text: "O que voc\xEA preencheu no formul\xE1rio?",
1396
+ type: "text"
1397
+ },
1398
+ {
1399
+ id: "special_chars",
1400
+ text: "Usou caracteres especiais ou emojis?",
1401
+ type: "boolean"
1402
+ }
1403
+ ]
1404
+ },
1405
+ {
1406
+ id: "ui_error",
1407
+ condition: (e) => e.errorType === "TypeError" || e.message.toLowerCase().includes("undefined") || e.message.toLowerCase().includes("null"),
1408
+ priority: 4,
1409
+ questions: [
1410
+ {
1411
+ id: "clicked_what",
1412
+ text: "Voc\xEA clicou em algo espec\xEDfico?",
1413
+ type: "text"
1414
+ },
1415
+ {
1416
+ id: "page_loaded",
1417
+ text: "A p\xE1gina tinha carregado completamente?",
1418
+ type: "boolean"
1419
+ }
1420
+ ]
1421
+ },
1422
+ {
1423
+ id: "load_error",
1424
+ condition: (e) => e.message.toLowerCase().includes("chunk") || e.message.toLowerCase().includes("loading") || e.message.toLowerCase().includes("script"),
1425
+ priority: 3,
1426
+ questions: [
1427
+ {
1428
+ id: "refreshed",
1429
+ text: "Voc\xEA tentou atualizar a p\xE1gina?",
1430
+ type: "boolean"
1431
+ },
1432
+ {
1433
+ id: "browser",
1434
+ text: "Qual navegador voc\xEA est\xE1 usando?",
1435
+ type: "select",
1436
+ options: ["Chrome", "Firefox", "Safari", "Edge", "Outro"]
1437
+ }
1438
+ ]
1439
+ }
1440
+ ];
1441
+ var FALLBACK_QUESTIONS = [
1442
+ {
1443
+ id: "happened_before",
1444
+ text: "Isso j\xE1 aconteceu antes?",
1445
+ type: "boolean"
1446
+ },
1447
+ {
1448
+ id: "additional_info",
1449
+ text: "Algo mais que possa nos ajudar?",
1450
+ type: "text",
1451
+ required: false
1452
+ }
1453
+ ];
1454
+ var QuestionEngine = class {
1455
+ inferQuestions(message, payload) {
1456
+ const context = {
1457
+ message,
1458
+ type: "error",
1459
+ httpCode: payload == null ? void 0 : payload.httpCode,
1460
+ errorType: payload == null ? void 0 : payload.errorType,
1461
+ stack: payload == null ? void 0 : payload.stack
1462
+ };
1463
+ const matchedRules = QUESTION_RULES.filter((rule) => rule.condition(context)).sort((a, b) => b.priority - a.priority);
1464
+ const questions = [...BASE_QUESTIONS];
1465
+ const addedIds = new Set(questions.map((q) => q.id));
1466
+ for (const rule of matchedRules.slice(0, 2)) {
1467
+ for (const question of rule.questions) {
1468
+ if (!addedIds.has(question.id)) {
1469
+ questions.push(question);
1470
+ addedIds.add(question.id);
1471
+ }
1472
+ }
1473
+ }
1474
+ if (questions.length < 3) {
1475
+ for (const question of FALLBACK_QUESTIONS) {
1476
+ if (!addedIds.has(question.id) && questions.length < 4) {
1477
+ questions.push(question);
1478
+ addedIds.add(question.id);
1479
+ }
1480
+ }
1481
+ }
1482
+ return questions.slice(0, 4);
1483
+ }
1484
+ shouldShowWidget(message, payload, backendDecision) {
1485
+ if (backendDecision !== void 0) {
1486
+ return backendDecision;
1487
+ }
1488
+ if ((payload == null ? void 0 : payload.httpCode) !== void 0) {
1489
+ if (payload.httpCode >= 500) return true;
1490
+ if (payload.httpCode === 400 || payload.httpCode === 422) return true;
1491
+ }
1492
+ const criticalPatterns = [
1493
+ "unexpected",
1494
+ "critical",
1495
+ "fatal",
1496
+ "crash",
1497
+ "failed to",
1498
+ "cannot read",
1499
+ "cannot access",
1500
+ "undefined is not",
1501
+ "null is not"
1502
+ ];
1503
+ const lowerMessage = message.toLowerCase();
1504
+ return criticalPatterns.some((pattern) => lowerMessage.includes(pattern));
1505
+ }
1506
+ };
1507
+
1508
+ // src/widget/styles.ts
1509
+ function injectStyles(theme) {
1510
+ if (document.getElementById("blinker-widget-styles")) return;
1511
+ const styles = document.createElement("style");
1512
+ styles.id = "blinker-widget-styles";
1513
+ styles.textContent = getStyles(theme);
1514
+ document.head.appendChild(styles);
1515
+ }
1516
+ function removeStyles() {
1517
+ const styles = document.getElementById("blinker-widget-styles");
1518
+ if (styles) styles.remove();
1519
+ }
1520
+ function updateStyles(theme) {
1521
+ const styles = document.getElementById("blinker-widget-styles");
1522
+ if (styles) {
1523
+ styles.textContent = getStyles(theme);
1524
+ }
1525
+ }
1526
+ function getStyles(theme) {
1527
+ return `
1528
+ @keyframes blinker-breathe {
1529
+ 0%, 100% { transform: scale(1); }
1530
+ 50% { transform: scale(1.05); }
1531
+ }
1532
+
1533
+ @keyframes blinker-pulse {
1534
+ 0%, 100% { opacity: 1; }
1535
+ 50% { opacity: 0.7; }
1536
+ }
1537
+
1538
+ @keyframes blinker-float {
1539
+ 0%, 100% { transform: translateY(0); }
1540
+ 50% { transform: translateY(-5px); }
1541
+ }
1542
+
1543
+ @keyframes blinker-fade-in {
1544
+ from { opacity: 0; transform: scale(0.8) translateY(20px); }
1545
+ to { opacity: 1; transform: scale(1) translateY(0); }
1546
+ }
1547
+
1548
+ @keyframes blinker-fade-out {
1549
+ from { opacity: 1; transform: scale(1) translateY(0); }
1550
+ to { opacity: 0; transform: scale(0.8) translateY(20px); }
1551
+ }
1552
+
1553
+ @keyframes blinker-slide-up {
1554
+ from { opacity: 0; transform: translateY(30px); }
1555
+ to { opacity: 1; transform: translateY(0); }
1556
+ }
1557
+
1558
+ @keyframes blinker-eye-blink {
1559
+ 0%, 45%, 55%, 100% { transform: scaleY(1); }
1560
+ 50% { transform: scaleY(0.1); }
1561
+ }
1562
+
1563
+ @keyframes blinker-glow {
1564
+ 0%, 100% { filter: drop-shadow(0 0 8px ${theme.primary}40); }
1565
+ 50% { filter: drop-shadow(0 0 15px ${theme.primary}60); }
1566
+ }
1567
+
1568
+ .blinker-widget-container {
1569
+ position: fixed;
1570
+ z-index: 999999;
1571
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
1572
+ }
1573
+
1574
+ .blinker-widget-container.bottom-right {
1575
+ bottom: 20px;
1576
+ right: 20px;
1577
+ }
1578
+
1579
+ .blinker-widget-container.bottom-left {
1580
+ bottom: 20px;
1581
+ left: 20px;
1582
+ }
1583
+
1584
+ .blinker-avatar {
1585
+ width: 60px;
1586
+ height: 60px;
1587
+ cursor: pointer;
1588
+ animation: blinker-float 3s ease-in-out infinite, blinker-glow 2s ease-in-out infinite;
1589
+ transition: transform 0.3s ease;
1590
+ }
1591
+
1592
+ .blinker-avatar:hover {
1593
+ transform: scale(1.1);
1594
+ }
1595
+
1596
+ .blinker-avatar.minimized {
1597
+ width: 40px;
1598
+ height: 40px;
1599
+ }
1600
+
1601
+ .blinker-avatar-body {
1602
+ fill: ${theme.primary};
1603
+ animation: blinker-breathe 4s ease-in-out infinite;
1604
+ transform-origin: center;
1605
+ }
1606
+
1607
+ .blinker-avatar-eye {
1608
+ fill: ${theme.background};
1609
+ animation: blinker-eye-blink 4s ease-in-out infinite;
1610
+ transform-origin: center;
1611
+ }
1612
+
1613
+ .blinker-avatar-eye-left {
1614
+ animation-delay: 0.1s;
1615
+ }
1616
+
1617
+ .blinker-avatar-pupil {
1618
+ fill: ${theme.text};
1619
+ transition: transform 0.1s ease;
1620
+ }
1621
+
1622
+ .blinker-avatar-mouth {
1623
+ stroke: ${theme.background};
1624
+ stroke-width: 2;
1625
+ fill: none;
1626
+ stroke-linecap: round;
1627
+ }
1628
+
1629
+ .blinker-avatar-glow {
1630
+ fill: ${theme.primary};
1631
+ opacity: 0.3;
1632
+ animation: blinker-pulse 2s ease-in-out infinite;
1633
+ }
1634
+
1635
+ .blinker-dialog-overlay {
1636
+ position: fixed;
1637
+ top: 0;
1638
+ left: 0;
1639
+ right: 0;
1640
+ bottom: 0;
1641
+ background: rgba(0, 0, 0, 0.3);
1642
+ z-index: 999998;
1643
+ animation: blinker-fade-in 0.3s ease;
1644
+ }
1645
+
1646
+ .blinker-dialog {
1647
+ position: fixed;
1648
+ z-index: 999999;
1649
+ width: 340px;
1650
+ max-width: calc(100vw - 40px);
1651
+ background: ${theme.background};
1652
+ border-radius: 16px;
1653
+ box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
1654
+ animation: blinker-slide-up 0.4s ease;
1655
+ overflow: hidden;
66
1656
  }
67
- }
68
- return result;
1657
+
1658
+ .blinker-dialog.bottom-right {
1659
+ bottom: 90px;
1660
+ right: 20px;
1661
+ }
1662
+
1663
+ .blinker-dialog.bottom-left {
1664
+ bottom: 90px;
1665
+ left: 20px;
1666
+ }
1667
+
1668
+ .blinker-dialog-header {
1669
+ padding: 20px 20px 0;
1670
+ display: flex;
1671
+ align-items: flex-start;
1672
+ gap: 12px;
1673
+ }
1674
+
1675
+ .blinker-dialog-avatar {
1676
+ width: 40px;
1677
+ height: 40px;
1678
+ flex-shrink: 0;
1679
+ }
1680
+
1681
+ .blinker-dialog-title {
1682
+ flex: 1;
1683
+ }
1684
+
1685
+ .blinker-dialog-greeting {
1686
+ font-size: 16px;
1687
+ font-weight: 600;
1688
+ color: ${theme.text};
1689
+ margin: 0 0 4px;
1690
+ }
1691
+
1692
+ .blinker-dialog-subtext {
1693
+ font-size: 13px;
1694
+ color: ${theme.text}99;
1695
+ margin: 0;
1696
+ }
1697
+
1698
+ .blinker-dialog-close {
1699
+ background: none;
1700
+ border: none;
1701
+ padding: 4px;
1702
+ cursor: pointer;
1703
+ color: ${theme.text}66;
1704
+ border-radius: 6px;
1705
+ transition: all 0.2s;
1706
+ }
1707
+
1708
+ .blinker-dialog-close:hover {
1709
+ background: ${theme.text}10;
1710
+ color: ${theme.text};
1711
+ }
1712
+
1713
+ .blinker-dialog-body {
1714
+ padding: 20px;
1715
+ }
1716
+
1717
+ .blinker-question {
1718
+ margin-bottom: 16px;
1719
+ }
1720
+
1721
+ .blinker-question:last-child {
1722
+ margin-bottom: 0;
1723
+ }
1724
+
1725
+ .blinker-question-label {
1726
+ display: block;
1727
+ font-size: 14px;
1728
+ font-weight: 500;
1729
+ color: ${theme.text};
1730
+ margin-bottom: 8px;
1731
+ }
1732
+
1733
+ .blinker-question-input {
1734
+ width: 100%;
1735
+ padding: 10px 12px;
1736
+ border: 1px solid ${theme.text}20;
1737
+ border-radius: 8px;
1738
+ font-size: 14px;
1739
+ color: ${theme.text};
1740
+ background: ${theme.background};
1741
+ transition: border-color 0.2s, box-shadow 0.2s;
1742
+ box-sizing: border-box;
1743
+ }
1744
+
1745
+ .blinker-question-input:focus {
1746
+ outline: none;
1747
+ border-color: ${theme.primary};
1748
+ box-shadow: 0 0 0 3px ${theme.primary}20;
1749
+ }
1750
+
1751
+ .blinker-question-input::placeholder {
1752
+ color: ${theme.text}50;
1753
+ }
1754
+
1755
+ .blinker-question-textarea {
1756
+ resize: vertical;
1757
+ min-height: 80px;
1758
+ }
1759
+
1760
+ .blinker-question-select {
1761
+ appearance: none;
1762
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%23666' d='M6 8L1 3h10z'/%3E%3C/svg%3E");
1763
+ background-repeat: no-repeat;
1764
+ background-position: right 12px center;
1765
+ padding-right: 36px;
1766
+ }
1767
+
1768
+ .blinker-question-boolean {
1769
+ display: flex;
1770
+ gap: 8px;
1771
+ }
1772
+
1773
+ .blinker-question-boolean button {
1774
+ flex: 1;
1775
+ padding: 10px;
1776
+ border: 1px solid ${theme.text}20;
1777
+ border-radius: 8px;
1778
+ background: ${theme.background};
1779
+ color: ${theme.text};
1780
+ font-size: 14px;
1781
+ cursor: pointer;
1782
+ transition: all 0.2s;
1783
+ }
1784
+
1785
+ .blinker-question-boolean button:hover {
1786
+ border-color: ${theme.primary};
1787
+ }
1788
+
1789
+ .blinker-question-boolean button.selected {
1790
+ background: ${theme.primary};
1791
+ border-color: ${theme.primary};
1792
+ color: white;
1793
+ }
1794
+
1795
+ .blinker-dialog-footer {
1796
+ padding: 16px 20px 20px;
1797
+ display: flex;
1798
+ gap: 8px;
1799
+ }
1800
+
1801
+ .blinker-btn {
1802
+ flex: 1;
1803
+ padding: 12px 16px;
1804
+ border-radius: 8px;
1805
+ font-size: 14px;
1806
+ font-weight: 500;
1807
+ cursor: pointer;
1808
+ transition: all 0.2s;
1809
+ border: none;
1810
+ }
1811
+
1812
+ .blinker-btn-primary {
1813
+ background: ${theme.accent};
1814
+ color: white;
1815
+ }
1816
+
1817
+ .blinker-btn-primary:hover {
1818
+ filter: brightness(1.1);
1819
+ }
1820
+
1821
+ .blinker-btn-primary:disabled {
1822
+ opacity: 0.5;
1823
+ cursor: not-allowed;
1824
+ }
1825
+
1826
+ .blinker-btn-secondary {
1827
+ background: ${theme.text}10;
1828
+ color: ${theme.text}99;
1829
+ }
1830
+
1831
+ .blinker-btn-secondary:hover {
1832
+ background: ${theme.text}20;
1833
+ }
1834
+
1835
+ .blinker-thank-you {
1836
+ text-align: center;
1837
+ padding: 40px 20px;
1838
+ }
1839
+
1840
+ .blinker-thank-you-icon {
1841
+ width: 48px;
1842
+ height: 48px;
1843
+ margin: 0 auto 16px;
1844
+ color: ${theme.accent};
1845
+ }
1846
+
1847
+ .blinker-thank-you-text {
1848
+ font-size: 16px;
1849
+ color: ${theme.text};
1850
+ margin: 0;
1851
+ }
1852
+ `;
69
1853
  }
70
1854
 
71
- // src/errorHandler.ts
72
- var originalOnError = null;
73
- var originalOnUnhandledRejection = null;
74
- var isSetup = false;
75
- function setupErrorHandlers(callback, options = {}) {
76
- const { captureErrors = true, captureRejections = true, debug = false } = options;
77
- if (isSetup) {
78
- if (debug) {
79
- console.warn("[Blinker] Error handlers already set up");
1855
+ // src/widget/Widget.ts
1856
+ var STORAGE_KEY = "blinker_widget_state";
1857
+ var Widget = class {
1858
+ constructor(config) {
1859
+ this.container = null;
1860
+ this.avatar = null;
1861
+ this.dialog = null;
1862
+ this.onFeedbackSubmit = null;
1863
+ this.config = { ...DEFAULT_WIDGET_CONFIG, ...config };
1864
+ this.state = this.loadState();
1865
+ this.questionEngine = new QuestionEngine();
1866
+ }
1867
+ init(onFeedbackSubmit) {
1868
+ if (!this.config.enabled) return;
1869
+ if (typeof document === "undefined") return;
1870
+ this.onFeedbackSubmit = onFeedbackSubmit || null;
1871
+ injectStyles(this.config.theme);
1872
+ this.createContainer();
1873
+ }
1874
+ createContainer() {
1875
+ if (this.container) return;
1876
+ this.container = document.createElement("div");
1877
+ this.container.className = `blinker-widget-container ${this.config.position}`;
1878
+ document.body.appendChild(this.container);
1879
+ this.avatar = new Avatar(this.config.theme, () => this.handleAvatarClick());
1880
+ const avatarElement = this.avatar.create();
1881
+ this.container.appendChild(avatarElement);
1882
+ this.dialog = new Dialog(
1883
+ this.config,
1884
+ (feedback) => this.handleFeedbackSubmit(feedback),
1885
+ () => this.handleDialogClose()
1886
+ );
1887
+ this.hide();
1888
+ }
1889
+ show(errorId, message, payload, backendDecision) {
1890
+ if (!this.config.enabled) return;
1891
+ if (!this.container || !this.avatar) return;
1892
+ const shouldShow = this.questionEngine.shouldShowWidget(message, payload, backendDecision);
1893
+ if (!shouldShow) return;
1894
+ if (!this.canShowWidget()) return;
1895
+ const questions = this.questionEngine.inferQuestions(message, payload);
1896
+ this.state.currentErrorId = errorId;
1897
+ this.state.questions = questions;
1898
+ this.state.isVisible = true;
1899
+ this.state.showCount++;
1900
+ this.state.lastShowTime = Date.now();
1901
+ this.saveState();
1902
+ this.container.style.display = "block";
1903
+ this.avatar.setMinimized(false);
1904
+ }
1905
+ hide() {
1906
+ if (!this.container) return;
1907
+ this.state.isVisible = false;
1908
+ this.state.isDialogOpen = false;
1909
+ this.container.style.display = "none";
1910
+ }
1911
+ handleAvatarClick() {
1912
+ var _a;
1913
+ if (!this.dialog || !this.state.currentErrorId) return;
1914
+ if (this.state.isDialogOpen) {
1915
+ this.dialog.close();
1916
+ } else {
1917
+ this.state.isDialogOpen = true;
1918
+ this.dialog.open(this.state.currentErrorId, this.state.questions);
1919
+ (_a = this.avatar) == null ? void 0 : _a.setMinimized(true);
80
1920
  }
81
- return;
82
1921
  }
83
- if (typeof window === "undefined") {
84
- if (debug) {
85
- console.log("[Blinker] Not in browser environment, skipping error handlers");
1922
+ handleFeedbackSubmit(feedback) {
1923
+ if (this.onFeedbackSubmit) {
1924
+ this.onFeedbackSubmit(feedback);
86
1925
  }
87
- return;
88
1926
  }
89
- if (captureErrors) {
90
- originalOnError = window.onerror;
91
- window.onerror = function(message, source, lineno, colno, error) {
92
- const errorMessage = typeof message === "string" ? message : message.type || "Unknown error";
93
- const payload = {
94
- source,
95
- lineno,
96
- colno,
97
- stack: error == null ? void 0 : error.stack,
98
- errorType: error == null ? void 0 : error.name
99
- };
100
- if (debug) {
101
- console.log("[Blinker] Captured error:", errorMessage);
102
- }
103
- callback("error", errorMessage, payload);
104
- if (typeof originalOnError === "function") {
105
- return originalOnError.call(window, message, source, lineno, colno, error);
106
- }
107
- return false;
108
- };
1927
+ handleDialogClose() {
1928
+ var _a;
1929
+ this.state.isDialogOpen = false;
1930
+ (_a = this.avatar) == null ? void 0 : _a.setMinimized(false);
1931
+ setTimeout(() => {
1932
+ this.hide();
1933
+ }, 500);
109
1934
  }
110
- if (captureRejections) {
111
- originalOnUnhandledRejection = window.onunhandledrejection;
112
- window.onunhandledrejection = function(event) {
113
- let message = "Unhandled Rejection";
114
- const payload = {};
115
- const reason = event.reason;
116
- if (reason instanceof Error) {
117
- message = reason.message || message;
118
- payload.stack = reason.stack;
119
- payload.errorType = reason.name;
120
- } else if (typeof reason === "string") {
121
- message = reason;
122
- } else if (reason !== void 0 && reason !== null) {
123
- try {
124
- message = JSON.stringify(reason);
125
- } catch (e) {
126
- message = String(reason);
127
- }
128
- }
129
- if (debug) {
130
- console.log("[Blinker] Captured unhandled rejection:", message);
131
- }
132
- callback("error", message, payload);
133
- if (typeof originalOnUnhandledRejection === "function") {
134
- originalOnUnhandledRejection.call(window, event);
1935
+ canShowWidget() {
1936
+ if (this.state.showCount >= this.config.maxShowsPerSession) {
1937
+ return false;
1938
+ }
1939
+ if (this.state.lastShowTime) {
1940
+ const cooldownMs = this.config.cooldownMinutes * 60 * 1e3;
1941
+ const timeSinceLastShow = Date.now() - this.state.lastShowTime;
1942
+ if (timeSinceLastShow < cooldownMs) {
1943
+ return false;
135
1944
  }
1945
+ }
1946
+ return true;
1947
+ }
1948
+ loadState() {
1949
+ const defaultState = {
1950
+ isVisible: false,
1951
+ isMinimized: false,
1952
+ isDialogOpen: false,
1953
+ currentErrorId: null,
1954
+ questions: [],
1955
+ showCount: 0,
1956
+ lastShowTime: null
136
1957
  };
1958
+ if (typeof sessionStorage === "undefined") return defaultState;
1959
+ try {
1960
+ const stored = sessionStorage.getItem(STORAGE_KEY);
1961
+ if (stored) {
1962
+ const parsed = JSON.parse(stored);
1963
+ return {
1964
+ ...defaultState,
1965
+ showCount: parsed.showCount || 0,
1966
+ lastShowTime: parsed.lastShowTime || null
1967
+ };
1968
+ }
1969
+ } catch (e) {
1970
+ }
1971
+ return defaultState;
137
1972
  }
138
- isSetup = true;
139
- }
140
- function teardownErrorHandlers() {
141
- if (typeof window === "undefined") {
142
- return;
1973
+ saveState() {
1974
+ if (typeof sessionStorage === "undefined") return;
1975
+ try {
1976
+ sessionStorage.setItem(STORAGE_KEY, JSON.stringify({
1977
+ showCount: this.state.showCount,
1978
+ lastShowTime: this.state.lastShowTime
1979
+ }));
1980
+ } catch (e) {
1981
+ }
143
1982
  }
144
- if (originalOnError !== void 0) {
145
- window.onerror = originalOnError;
1983
+ updateConfig(config) {
1984
+ var _a, _b;
1985
+ this.config = { ...this.config, ...config };
1986
+ if (config.theme) {
1987
+ updateStyles(this.config.theme);
1988
+ (_a = this.avatar) == null ? void 0 : _a.updateTheme(this.config.theme);
1989
+ }
1990
+ (_b = this.dialog) == null ? void 0 : _b.updateConfig(this.config);
146
1991
  }
147
- if (originalOnUnhandledRejection !== void 0) {
148
- window.onunhandledrejection = originalOnUnhandledRejection;
1992
+ getConfig() {
1993
+ return { ...this.config };
1994
+ }
1995
+ isVisible() {
1996
+ return this.state.isVisible;
1997
+ }
1998
+ isDialogOpen() {
1999
+ return this.state.isDialogOpen;
2000
+ }
2001
+ destroy() {
2002
+ var _a, _b;
2003
+ (_a = this.avatar) == null ? void 0 : _a.destroy();
2004
+ (_b = this.dialog) == null ? void 0 : _b.close();
2005
+ if (this.container) {
2006
+ this.container.remove();
2007
+ this.container = null;
2008
+ }
2009
+ removeStyles();
2010
+ this.avatar = null;
2011
+ this.dialog = null;
149
2012
  }
150
- originalOnError = null;
151
- originalOnUnhandledRejection = null;
152
- isSetup = false;
153
- }
154
-
155
- // src/Blinker.ts
156
- var BLINKER_API = "https://api.blinker.live";
157
- var DEFAULT_FILTERS = {
158
- // Auth/permission errors are usually expected behavior, not bugs
159
- ignoreHttpCodes: [401, 403],
160
- // All JS error types are captured by default
161
- ignoreErrorTypes: [],
162
- // Common benign browser errors that aren't actionable
163
- ignoreMessagePatterns: [
164
- "ResizeObserver loop",
165
- // Common benign browser error
166
- "Script error",
167
- // Cross-origin script errors (no useful info)
168
- "ResizeObserver loop completed"
169
- // Another variant of ResizeObserver
170
- ],
171
- // By default, respect the filters
172
- captureAll: false
173
2013
  };
2014
+
2015
+ // src/core/Blinker.ts
174
2016
  var Blinker = class {
175
2017
  constructor() {
176
2018
  this.config = null;
177
2019
  this.initialized = false;
178
- this.filters = { ...DEFAULT_FILTERS };
2020
+ this.filterEngine = null;
2021
+ this.queue = null;
2022
+ this.sessionManager = null;
2023
+ this.userContext = null;
2024
+ this.rateLimiter = null;
2025
+ this.sampler = null;
2026
+ this.deduplicator = null;
2027
+ this.widget = null;
2028
+ }
2029
+ get version() {
2030
+ return SDK_VERSION;
179
2031
  }
180
- /**
181
- * Initialize the Blinker SDK
182
- * @param config Configuration options
183
- */
184
2032
  init(config) {
185
- var _a, _b, _c, _d, _e, _f, _g, _h;
2033
+ var _a, _b, _c, _d;
186
2034
  if (this.initialized) {
187
- if (config.debug) {
188
- console.warn("[Blinker] SDK already initialized");
189
- }
2035
+ const logger2 = getLogger();
2036
+ logger2.warn("SDK already initialized");
190
2037
  return;
191
2038
  }
192
2039
  if (!config.token) {
193
2040
  throw new Error("[Blinker] Token is required");
194
2041
  }
2042
+ const enabled = (_a = config.enabled) != null ? _a : DEFAULTS.enabled;
2043
+ if (!enabled) {
2044
+ setGlobalLogger((_b = config.debug) != null ? _b : false);
2045
+ const logger2 = getLogger();
2046
+ logger2.log("SDK disabled via config");
2047
+ this.config = { ...DEFAULTS, ...config };
2048
+ return;
2049
+ }
195
2050
  this.config = {
196
- captureErrors: true,
197
- captureRejections: true,
198
- debug: false,
2051
+ ...DEFAULTS,
199
2052
  ...config
200
2053
  };
201
- this.filters = {
202
- ignoreHttpCodes: (_b = (_a = config.filters) == null ? void 0 : _a.ignoreHttpCodes) != null ? _b : DEFAULT_FILTERS.ignoreHttpCodes,
203
- ignoreErrorTypes: (_d = (_c = config.filters) == null ? void 0 : _c.ignoreErrorTypes) != null ? _d : DEFAULT_FILTERS.ignoreErrorTypes,
204
- ignoreMessagePatterns: (_f = (_e = config.filters) == null ? void 0 : _e.ignoreMessagePatterns) != null ? _f : DEFAULT_FILTERS.ignoreMessagePatterns,
205
- captureAll: (_h = (_g = config.filters) == null ? void 0 : _g.captureAll) != null ? _h : DEFAULT_FILTERS.captureAll
206
- };
207
- if (this.config.debug) {
208
- console.log("[Blinker] Filters configured:", this.filters);
209
- }
2054
+ setGlobalLogger((_c = this.config.debug) != null ? _c : false);
2055
+ const logger = getLogger();
2056
+ this.filterEngine = new FilterEngine(config.filters);
2057
+ logger.log("Filter engine initialized");
2058
+ this.queue = new EventQueue(config.token, {
2059
+ endpoint: this.config.endpoint,
2060
+ enableBatching: this.config.enableBatching,
2061
+ batchSize: this.config.batchSize,
2062
+ flushInterval: this.config.flushInterval,
2063
+ enableOfflineStorage: this.config.enableOfflineStorage,
2064
+ maxStoredEvents: this.config.maxStoredEvents,
2065
+ onError: this.config.onError
2066
+ });
2067
+ logger.log("Event queue initialized");
2068
+ this.sessionManager = new SessionManager();
2069
+ this.userContext = new UserContext(this.sessionManager);
2070
+ logger.log("Context managers initialized");
2071
+ this.rateLimiter = new RateLimiter(this.config.maxEventsPerMinute);
2072
+ this.sampler = new Sampler(this.config.sampleRate);
2073
+ logger.log("Sampling modules initialized");
2074
+ this.deduplicator = new Deduplicator(
2075
+ this.config.enableDeduplication,
2076
+ this.config.deduplicationWindow
2077
+ );
2078
+ logger.log("Deduplicator initialized");
210
2079
  if (this.config.captureErrors || this.config.captureRejections) {
211
2080
  setupErrorHandlers(
212
- (type, message, payload) => {
213
- var _a2;
214
- if (this.shouldCapture(message, payload == null ? void 0 : payload.errorType)) {
215
- this.track(type, message, payload);
216
- } else if ((_a2 = this.config) == null ? void 0 : _a2.debug) {
217
- console.log("[Blinker] Error filtered out:", message);
218
- }
219
- },
2081
+ (type, message, payload) => this.handleCapturedError(type, message, payload),
220
2082
  {
221
2083
  captureErrors: this.config.captureErrors,
222
- captureRejections: this.config.captureRejections,
223
- debug: this.config.debug
2084
+ captureRejections: this.config.captureRejections
224
2085
  }
225
2086
  );
2087
+ logger.log("Error handlers set up");
226
2088
  }
227
- this.initialized = true;
228
- if (this.config.debug) {
229
- console.log("[Blinker] SDK initialized successfully");
2089
+ if ((_d = this.config.widget) == null ? void 0 : _d.enabled) {
2090
+ this.widget = new Widget(this.config.widget);
2091
+ this.widget.init((feedback) => this.handleWidgetFeedback(feedback));
2092
+ logger.log("Widget initialized");
230
2093
  }
2094
+ this.initialized = true;
2095
+ logger.log("SDK initialized successfully");
231
2096
  }
232
- /**
233
- * Check if an error should be captured based on filter settings
234
- * @param message Error message
235
- * @param errorType Optional JS error type (e.g., 'TypeError')
236
- * @param httpCode Optional HTTP status code
237
- * @returns true if the error should be captured, false if filtered out
238
- */
239
- shouldCapture(message, errorType, httpCode) {
240
- if (this.filters.captureAll) {
241
- return true;
242
- }
243
- if (httpCode !== void 0 && this.filters.ignoreHttpCodes.includes(httpCode)) {
244
- return false;
245
- }
246
- if (errorType && this.filters.ignoreErrorTypes.includes(errorType)) {
247
- return false;
2097
+ handleCapturedError(type, message, payload) {
2098
+ var _a, _b, _c, _d, _e;
2099
+ if (!((_a = this.filterEngine) == null ? void 0 : _a.shouldCapture(message, payload.errorType, payload.httpCode))) {
2100
+ return;
248
2101
  }
249
- for (const pattern of this.filters.ignoreMessagePatterns) {
250
- if (message.toLowerCase().includes(pattern.toLowerCase())) {
251
- return false;
252
- }
2102
+ const dedupResult = (_b = this.deduplicator) == null ? void 0 : _b.check(message, payload.stack);
2103
+ if (dedupResult && !dedupResult.shouldSend) {
2104
+ return;
253
2105
  }
254
- if (httpCode === void 0) {
255
- const httpCodeMatch = message.match(/(?:HTTP\s*|status[:\s]*)?(\d{3})(?:\s|$|:)/i);
256
- if (httpCodeMatch) {
257
- const extractedCode = parseInt(httpCodeMatch[1], 10);
258
- if (extractedCode >= 100 && extractedCode < 600) {
259
- if (this.filters.ignoreHttpCodes.includes(extractedCode)) {
260
- return false;
261
- }
262
- }
263
- }
2106
+ const errorId = generateUniqueId();
2107
+ this.track(type, message, {
2108
+ ...payload,
2109
+ errorId,
2110
+ count: dedupResult == null ? void 0 : dedupResult.count
2111
+ });
2112
+ if (this.widget && ((_d = (_c = this.config) == null ? void 0 : _c.widget) == null ? void 0 : _d.enabled)) {
2113
+ setTimeout(() => {
2114
+ var _a2;
2115
+ (_a2 = this.widget) == null ? void 0 : _a2.show(errorId, message, payload);
2116
+ }, (_e = this.config.widget.delay) != null ? _e : 2e3);
264
2117
  }
265
- return true;
266
2118
  }
267
- /**
268
- * Track a custom event
269
- * @param type Event type (e.g., 'click', 'pageview', 'custom')
270
- * @param message Event message or description
271
- * @param payload Optional additional data
272
- */
2119
+ handleWidgetFeedback(feedback) {
2120
+ const logger = getLogger();
2121
+ logger.log("Widget feedback received:", feedback.errorId);
2122
+ this.track("feedback", "User feedback submitted", {
2123
+ errorId: feedback.errorId,
2124
+ answers: feedback.answers,
2125
+ submittedAt: feedback.submittedAt
2126
+ });
2127
+ }
273
2128
  track(type, message, payload) {
274
- var _a;
275
- if (!this.initialized || !this.config) {
276
- if ((_a = this.config) == null ? void 0 : _a.debug) {
277
- console.warn("[Blinker] SDK not initialized. Call blinker.init() first.");
278
- }
2129
+ var _a, _b, _c;
2130
+ if (!this.config) {
279
2131
  return Promise.resolve({ success: false, error: "SDK not initialized" });
280
2132
  }
281
- const event = {
2133
+ if (!this.config.enabled) {
2134
+ return Promise.resolve({ success: true, error: "SDK disabled" });
2135
+ }
2136
+ if (!this.initialized) {
2137
+ return Promise.resolve({ success: false, error: "SDK not initialized" });
2138
+ }
2139
+ const logger = getLogger();
2140
+ const isError = type === "error";
2141
+ if (!((_a = this.rateLimiter) == null ? void 0 : _a.allow())) {
2142
+ logger.log("Event dropped due to rate limit");
2143
+ return Promise.resolve({ success: false, error: "Rate limited" });
2144
+ }
2145
+ if (!((_b = this.sampler) == null ? void 0 : _b.shouldSample(isError))) {
2146
+ return Promise.resolve({ success: true, error: "Sampled out" });
2147
+ }
2148
+ const context = (_c = this.userContext) == null ? void 0 : _c.getEventContext();
2149
+ let event = {
2150
+ id: generateUniqueId(),
282
2151
  type,
283
2152
  message,
284
2153
  payload,
285
2154
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
286
- url: typeof window !== "undefined" ? window.location.href : void 0,
287
- userAgent: typeof navigator !== "undefined" ? navigator.userAgent : void 0
2155
+ url: getCurrentUrl(),
2156
+ userAgent: getUserAgent(),
2157
+ sessionId: context == null ? void 0 : context.sessionId,
2158
+ userId: context == null ? void 0 : context.userId,
2159
+ userTraits: context == null ? void 0 : context.userTraits,
2160
+ context: context == null ? void 0 : context.custom
288
2161
  };
289
- return sendEvent(BLINKER_API, this.config.token, event, this.config.debug);
2162
+ if (this.config.onBeforeSend) {
2163
+ event = this.config.onBeforeSend(event);
2164
+ if (!event) {
2165
+ logger.log("Event filtered by onBeforeSend");
2166
+ return Promise.resolve({ success: true, error: "Filtered by onBeforeSend" });
2167
+ }
2168
+ }
2169
+ logger.log("Tracking event:", type, message.substring(0, 50));
2170
+ return this.queue.add(event);
290
2171
  }
291
- /**
292
- * Track an error manually
293
- * @param error Error object or message
294
- * @param additionalPayload Optional additional data (can include httpCode for filtering)
295
- */
296
2172
  captureError(error, additionalPayload) {
297
- var _a;
2173
+ var _a, _b;
298
2174
  const message = error instanceof Error ? error.message : error;
299
2175
  const errorType = error instanceof Error ? error.name : void 0;
2176
+ const stack = error instanceof Error ? error.stack : void 0;
300
2177
  const httpCode = additionalPayload == null ? void 0 : additionalPayload.httpCode;
301
- if (!this.shouldCapture(message, errorType, httpCode)) {
302
- if ((_a = this.config) == null ? void 0 : _a.debug) {
303
- console.log("[Blinker] Error filtered out:", { message, errorType, httpCode });
304
- }
305
- return Promise.resolve({ success: true, error: "Filtered out by settings" });
2178
+ if (!((_a = this.filterEngine) == null ? void 0 : _a.shouldCapture(message, errorType, httpCode))) {
2179
+ return Promise.resolve({ success: true, error: "Filtered out" });
2180
+ }
2181
+ const dedupResult = (_b = this.deduplicator) == null ? void 0 : _b.check(message, stack);
2182
+ if (dedupResult && !dedupResult.shouldSend) {
2183
+ return Promise.resolve({ success: true, error: "Deduplicated" });
306
2184
  }
307
2185
  const payload = {
308
- ...additionalPayload
2186
+ ...additionalPayload,
2187
+ errorType,
2188
+ stack,
2189
+ count: dedupResult == null ? void 0 : dedupResult.count
309
2190
  };
310
- if (error instanceof Error) {
311
- payload.stack = error.stack;
312
- payload.errorType = error.name;
313
- }
314
2191
  return this.track("error", message, payload);
315
2192
  }
316
- /**
317
- * Track a page view
318
- * @param pageName Optional page name (defaults to current URL path)
319
- * @param additionalPayload Optional additional data
320
- */
321
2193
  trackPageView(pageName, additionalPayload) {
322
- const page = pageName || (typeof window !== "undefined" ? window.location.pathname : "unknown");
2194
+ var _a;
2195
+ const page = pageName || getCurrentPath();
2196
+ (_a = this.sessionManager) == null ? void 0 : _a.recordPageView();
323
2197
  return this.track("pageview", page, {
324
2198
  ...additionalPayload,
325
- referrer: typeof document !== "undefined" ? document.referrer : void 0
2199
+ referrer: getReferrer()
326
2200
  });
327
2201
  }
328
- /**
329
- * Check if SDK is initialized
330
- */
2202
+ identify(userId, traits) {
2203
+ var _a;
2204
+ (_a = this.userContext) == null ? void 0 : _a.identify(userId, traits);
2205
+ }
2206
+ setContext(key, value) {
2207
+ var _a;
2208
+ (_a = this.userContext) == null ? void 0 : _a.setContext(key, value);
2209
+ }
2210
+ getContext() {
2211
+ var _a, _b;
2212
+ return (_b = (_a = this.userContext) == null ? void 0 : _a.getCustomContext()) != null ? _b : {};
2213
+ }
2214
+ clearContext() {
2215
+ var _a;
2216
+ (_a = this.userContext) == null ? void 0 : _a.clearAll();
2217
+ }
2218
+ getSessionId() {
2219
+ var _a, _b;
2220
+ return (_b = (_a = this.sessionManager) == null ? void 0 : _a.getSessionId()) != null ? _b : "";
2221
+ }
2222
+ getSession() {
2223
+ var _a, _b;
2224
+ return (_b = (_a = this.sessionManager) == null ? void 0 : _a.getSession()) != null ? _b : null;
2225
+ }
2226
+ async flush() {
2227
+ var _a;
2228
+ await ((_a = this.queue) == null ? void 0 : _a.flush());
2229
+ }
2230
+ getQueueSize() {
2231
+ var _a, _b;
2232
+ return (_b = (_a = this.queue) == null ? void 0 : _a.size()) != null ? _b : 0;
2233
+ }
331
2234
  isInitialized() {
332
2235
  return this.initialized;
333
2236
  }
334
- /**
335
- * Get current configuration (without token for security)
336
- */
337
2237
  getConfig() {
338
2238
  if (!this.config) return null;
339
2239
  const { token: _token, ...safeConfig } = this.config;
340
2240
  return safeConfig;
341
2241
  }
342
- /**
343
- * Destroy the SDK instance
344
- * Removes error handlers and resets state
345
- */
346
- destroy() {
2242
+ getFilters() {
2243
+ var _a, _b;
2244
+ return (_b = (_a = this.filterEngine) == null ? void 0 : _a.getFilters()) != null ? _b : { ...DEFAULT_FILTERS };
2245
+ }
2246
+ showWidget(errorId, message, payload) {
2247
+ var _a;
2248
+ (_a = this.widget) == null ? void 0 : _a.show(errorId, message, payload);
2249
+ }
2250
+ hideWidget() {
2251
+ var _a;
2252
+ (_a = this.widget) == null ? void 0 : _a.hide();
2253
+ }
2254
+ getWidgetConfig() {
2255
+ var _a, _b;
2256
+ return (_b = (_a = this.widget) == null ? void 0 : _a.getConfig()) != null ? _b : null;
2257
+ }
2258
+ updateWidgetConfig(config) {
347
2259
  var _a;
348
- const wasDebug = (_a = this.config) == null ? void 0 : _a.debug;
2260
+ (_a = this.widget) == null ? void 0 : _a.updateConfig(config);
2261
+ }
2262
+ isWidgetVisible() {
2263
+ var _a, _b;
2264
+ return (_b = (_a = this.widget) == null ? void 0 : _a.isVisible()) != null ? _b : false;
2265
+ }
2266
+ destroy() {
2267
+ var _a, _b, _c, _d;
2268
+ const logger = getLogger();
2269
+ (_a = this.queue) == null ? void 0 : _a.destroy();
349
2270
  teardownErrorHandlers();
2271
+ (_b = this.deduplicator) == null ? void 0 : _b.destroy();
2272
+ (_c = this.sessionManager) == null ? void 0 : _c.endSession();
2273
+ (_d = this.widget) == null ? void 0 : _d.destroy();
350
2274
  this.config = null;
351
2275
  this.initialized = false;
352
- this.filters = { ...DEFAULT_FILTERS };
353
- if (wasDebug) {
354
- console.log("[Blinker] SDK destroyed");
355
- }
356
- }
357
- /**
358
- * Get current filter settings
359
- */
360
- getFilters() {
361
- return { ...this.filters };
2276
+ this.filterEngine = null;
2277
+ this.queue = null;
2278
+ this.sessionManager = null;
2279
+ this.userContext = null;
2280
+ this.rateLimiter = null;
2281
+ this.sampler = null;
2282
+ this.deduplicator = null;
2283
+ this.widget = null;
2284
+ logger.log("SDK destroyed");
2285
+ setGlobalLogger(false);
362
2286
  }
363
2287
  };
364
2288
 
@@ -368,6 +2292,8 @@ var index_default = blinker;
368
2292
  export {
369
2293
  Blinker,
370
2294
  DEFAULT_FILTERS,
2295
+ DEFAULT_WIDGET_CONFIG,
2296
+ SDK_VERSION,
371
2297
  blinker,
372
2298
  index_default as default
373
2299
  };