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