@kryptonhq/analytics 0.1.1 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -39,25 +39,31 @@ function generateId() {
39
39
  }
40
40
  function getSessionId() {
41
41
  const key = "_krypton_sid";
42
- if (typeof sessionStorage !== "undefined") {
43
- let sid = sessionStorage.getItem(key);
44
- if (!sid) {
45
- sid = generateId();
46
- sessionStorage.setItem(key, sid);
42
+ try {
43
+ if (typeof sessionStorage !== "undefined") {
44
+ let sid = sessionStorage.getItem(key);
45
+ if (!sid) {
46
+ sid = generateId();
47
+ sessionStorage.setItem(key, sid);
48
+ }
49
+ return sid;
47
50
  }
48
- return sid;
51
+ } catch {
49
52
  }
50
53
  return generateId();
51
54
  }
52
55
  function getDistinctId() {
53
56
  const key = "_krypton_did";
54
- if (typeof localStorage !== "undefined") {
55
- let did = localStorage.getItem(key);
56
- if (!did) {
57
- did = generateId();
58
- localStorage.setItem(key, did);
57
+ try {
58
+ if (typeof localStorage !== "undefined") {
59
+ let did = localStorage.getItem(key);
60
+ if (!did) {
61
+ did = generateId();
62
+ localStorage.setItem(key, did);
63
+ }
64
+ return did;
59
65
  }
60
- return did;
66
+ } catch {
61
67
  }
62
68
  return generateId();
63
69
  }
@@ -111,6 +117,7 @@ var Krypton = class {
111
117
  this.geoContext = null;
112
118
  this.serverConfig = null;
113
119
  this.configFetched = false;
120
+ this.logPrefix = "[Krypton SDK]";
114
121
  this.config = {
115
122
  autoPageview: true,
116
123
  heatmap: false,
@@ -137,6 +144,8 @@ var Krypton = class {
137
144
  if (this.config.consentRequired && this.config.showConsentBanner && !this.hasStoredConsent()) {
138
145
  this.showConsentBanner();
139
146
  }
147
+ }).catch((err) => {
148
+ this.warn("Initialization failed", err);
140
149
  });
141
150
  }
142
151
  resolveInitialConsent() {
@@ -156,14 +165,18 @@ var Krypton = class {
156
165
  return `_krypton_consent_${this.config.apiKey}`;
157
166
  }
158
167
  hasStoredConsent() {
159
- if (typeof localStorage === "undefined") return false;
160
- return !!localStorage.getItem(this.consentStorageKey());
168
+ try {
169
+ if (typeof localStorage === "undefined") return false;
170
+ return !!localStorage.getItem(this.consentStorageKey());
171
+ } catch {
172
+ return false;
173
+ }
161
174
  }
162
175
  loadStoredConsent() {
163
- if (typeof localStorage === "undefined") return null;
164
- const raw = localStorage.getItem(this.consentStorageKey());
165
- if (!raw) return null;
166
176
  try {
177
+ if (typeof localStorage === "undefined") return null;
178
+ const raw = localStorage.getItem(this.consentStorageKey());
179
+ if (!raw) return null;
167
180
  const parsed = JSON.parse(raw);
168
181
  return {
169
182
  analytics: !!parsed.analytics,
@@ -175,8 +188,11 @@ var Krypton = class {
175
188
  }
176
189
  }
177
190
  persistConsent() {
178
- if (typeof localStorage === "undefined") return;
179
- localStorage.setItem(this.consentStorageKey(), JSON.stringify(this.consent));
191
+ try {
192
+ if (typeof localStorage === "undefined") return;
193
+ localStorage.setItem(this.consentStorageKey(), JSON.stringify(this.consent));
194
+ } catch {
195
+ }
180
196
  }
181
197
  promotePersistentIds() {
182
198
  this.distinctId = getDistinctId();
@@ -193,7 +209,12 @@ var Krypton = class {
193
209
  async fetchConfig() {
194
210
  if (typeof window === "undefined") return;
195
211
  const cacheKey = `_krypton_config_${this.config.apiKey}`;
196
- const cached = sessionStorage.getItem(cacheKey);
212
+ let cached = null;
213
+ try {
214
+ cached = sessionStorage.getItem(cacheKey);
215
+ } catch {
216
+ cached = null;
217
+ }
197
218
  if (cached) {
198
219
  try {
199
220
  const parsed = JSON.parse(cached);
@@ -213,44 +234,63 @@ var Krypton = class {
213
234
  const data = await res.json();
214
235
  this.serverConfig = data;
215
236
  this.configFetched = true;
216
- sessionStorage.setItem(cacheKey, JSON.stringify({ ...data, _ts: Date.now() }));
237
+ try {
238
+ sessionStorage.setItem(cacheKey, JSON.stringify({ ...data, _ts: Date.now() }));
239
+ } catch {
240
+ }
217
241
  }
218
242
  } catch {
219
243
  }
220
244
  }
221
245
  setupTracking() {
222
- this.flushTimer = setInterval(() => this.flush(), this.config.flushInterval);
246
+ this.flushTimer = setInterval(() => {
247
+ void this.flush();
248
+ }, this.config.flushInterval);
223
249
  if (typeof window === "undefined") return;
224
- window.addEventListener("beforeunload", () => this.flush());
250
+ window.addEventListener("beforeunload", () => {
251
+ void this.flush();
252
+ });
225
253
  if (this.config.autoPageview && this.canTrackAnalytics()) {
226
254
  this.trackPageview();
227
255
  }
228
256
  const originalPushState = history.pushState;
229
257
  history.pushState = (...args) => {
230
- originalPushState.apply(history, args);
231
- if (this.config.autoPageview && this.canTrackAnalytics()) {
232
- setTimeout(() => this.trackPageview(), 0);
233
- }
234
- if (this.isHeatmapFeatureEnabled()) {
235
- setTimeout(() => this.captureSnapshot(), 0);
258
+ try {
259
+ originalPushState.apply(history, args);
260
+ if (this.config.autoPageview && this.canTrackAnalytics()) {
261
+ setTimeout(() => this.trackPageview(), 0);
262
+ }
263
+ if (this.isHeatmapFeatureEnabled()) {
264
+ setTimeout(() => this.captureSnapshot(), 0);
265
+ }
266
+ } catch (err) {
267
+ this.warn("pushState hook failed", err);
236
268
  }
237
269
  };
238
270
  const originalReplaceState = history.replaceState;
239
271
  history.replaceState = (...args) => {
240
- originalReplaceState.apply(history, args);
241
- if (this.config.autoPageview && this.canTrackAnalytics()) {
242
- setTimeout(() => this.trackPageview(), 0);
243
- }
244
- if (this.isHeatmapFeatureEnabled()) {
245
- setTimeout(() => this.captureSnapshot(), 0);
272
+ try {
273
+ originalReplaceState.apply(history, args);
274
+ if (this.config.autoPageview && this.canTrackAnalytics()) {
275
+ setTimeout(() => this.trackPageview(), 0);
276
+ }
277
+ if (this.isHeatmapFeatureEnabled()) {
278
+ setTimeout(() => this.captureSnapshot(), 0);
279
+ }
280
+ } catch (err) {
281
+ this.warn("replaceState hook failed", err);
246
282
  }
247
283
  };
248
284
  window.addEventListener("popstate", () => {
249
- if (this.config.autoPageview && this.canTrackAnalytics()) {
250
- setTimeout(() => this.trackPageview(), 0);
251
- }
252
- if (this.isHeatmapFeatureEnabled()) {
253
- setTimeout(() => this.captureSnapshot(), 0);
285
+ try {
286
+ if (this.config.autoPageview && this.canTrackAnalytics()) {
287
+ setTimeout(() => this.trackPageview(), 0);
288
+ }
289
+ if (this.isHeatmapFeatureEnabled()) {
290
+ setTimeout(() => this.captureSnapshot(), 0);
291
+ }
292
+ } catch (err) {
293
+ this.warn("popstate hook failed", err);
254
294
  }
255
295
  });
256
296
  if (this.isHeatmapFeatureEnabled()) {
@@ -307,58 +347,68 @@ var Krypton = class {
307
347
  `;
308
348
  const remove = () => {
309
349
  banner.remove();
350
+ try {
351
+ if (document.body) document.body.style.cursor = "";
352
+ if (document.documentElement) document.documentElement.style.cursor = "";
353
+ } catch {
354
+ }
310
355
  };
311
356
  const readValue = (id) => {
312
357
  const el = document.getElementById(id);
313
358
  return !!el?.checked;
314
359
  };
315
360
  banner.querySelector("#krypton-consent-save")?.addEventListener("click", () => {
316
- this.setConsent({
361
+ const next = {
317
362
  analytics: readValue("krypton-consent-analytics"),
318
363
  heatmaps: readValue("krypton-consent-heatmaps"),
319
364
  geo: readValue("krypton-consent-geo")
320
- });
365
+ };
321
366
  remove();
367
+ setTimeout(() => this.setConsent(next), 0);
322
368
  });
323
369
  banner.querySelector("#krypton-consent-all")?.addEventListener("click", () => {
324
- this.setConsent({ analytics: true, heatmaps: true, geo: true });
325
370
  remove();
371
+ setTimeout(() => this.setConsent({ analytics: true, heatmaps: true, geo: true }), 0);
326
372
  });
327
373
  banner.querySelector("#krypton-consent-none")?.addEventListener("click", () => {
328
- this.setConsent({ analytics: false, heatmaps: false, geo: false });
329
374
  remove();
375
+ setTimeout(() => this.setConsent({ analytics: false, heatmaps: false, geo: false }), 0);
330
376
  });
331
377
  document.body.appendChild(banner);
332
378
  }
333
379
  /** Set one or more consent categories and apply changes immediately. */
334
380
  setConsent(next, persist = true) {
335
- const prev = { ...this.consent };
336
- this.consent = {
337
- analytics: next.analytics ?? prev.analytics,
338
- heatmaps: next.heatmaps ?? prev.heatmaps,
339
- geo: next.geo ?? prev.geo
340
- };
341
- if (persist) {
342
- this.persistConsent();
343
- }
344
- if (!prev.analytics && this.consent.analytics) {
345
- this.promotePersistentIds();
346
- if (this.config.autoPageview) {
347
- this.trackPageview();
381
+ try {
382
+ const prev = { ...this.consent };
383
+ this.consent = {
384
+ analytics: next.analytics ?? prev.analytics,
385
+ heatmaps: next.heatmaps ?? prev.heatmaps,
386
+ geo: next.geo ?? prev.geo
387
+ };
388
+ if (persist) {
389
+ this.persistConsent();
348
390
  }
349
- }
350
- if (prev.analytics && !this.consent.analytics) {
351
- this.eventQueue = [];
352
- }
353
- if (!prev.heatmaps && this.isHeatmapFeatureEnabled()) {
354
- this.setupHeatmap();
355
- this.captureSnapshot();
356
- }
357
- if (prev.heatmaps && !this.consent.heatmaps) {
358
- this.heatmapQueue = [];
359
- }
360
- if (!prev.geo && this.consent.geo) {
361
- this.captureGeoOnce();
391
+ if (!prev.analytics && this.consent.analytics) {
392
+ this.promotePersistentIds();
393
+ if (this.config.autoPageview) {
394
+ this.trackPageview();
395
+ }
396
+ }
397
+ if (prev.analytics && !this.consent.analytics) {
398
+ this.eventQueue = [];
399
+ }
400
+ if (!prev.heatmaps && this.isHeatmapFeatureEnabled()) {
401
+ this.setupHeatmap();
402
+ this.captureSnapshot();
403
+ }
404
+ if (prev.heatmaps && !this.consent.heatmaps) {
405
+ this.heatmapQueue = [];
406
+ }
407
+ if (!prev.geo && this.consent.geo) {
408
+ this.captureGeoOnce();
409
+ }
410
+ } catch (err) {
411
+ this.warn("setConsent failed", err);
362
412
  }
363
413
  }
364
414
  getConsent() {
@@ -376,60 +426,81 @@ var Krypton = class {
376
426
  }
377
427
  /** Identify a user with a custom distinct ID */
378
428
  identify(distinctId) {
379
- this.distinctId = distinctId;
380
- if (typeof localStorage !== "undefined" && this.canTrackAnalytics()) {
381
- localStorage.setItem("_krypton_did", distinctId);
429
+ try {
430
+ this.distinctId = distinctId;
431
+ if (this.canTrackAnalytics()) {
432
+ try {
433
+ if (typeof localStorage !== "undefined") {
434
+ localStorage.setItem("_krypton_did", distinctId);
435
+ }
436
+ } catch {
437
+ }
438
+ }
439
+ } catch (err) {
440
+ this.warn("identify failed", err);
382
441
  }
383
442
  }
384
443
  /** Track a pageview */
385
444
  trackPageview(properties) {
386
- if (!this.canTrackAnalytics()) return;
387
- this.track("$pageview", {
388
- ...properties
389
- });
445
+ try {
446
+ if (!this.canTrackAnalytics()) return;
447
+ this.track("$pageview", {
448
+ ...properties
449
+ });
450
+ } catch (err) {
451
+ this.warn("trackPageview failed", err);
452
+ }
390
453
  }
391
454
  /** Track a custom event */
392
455
  track(eventName, properties) {
393
- if (!this.canTrackAnalytics()) return;
394
- const utm = getUTMParams();
395
- const mergedProperties = { ...properties || {} };
396
- if (this.consent.geo && this.geoContext) {
397
- mergedProperties.geo = this.geoContext;
398
- }
399
- const event = {
400
- project_id: this.config.apiKey,
401
- distinct_id: this.distinctId,
402
- event_name: eventName,
403
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
404
- properties: mergedProperties,
405
- page_url: typeof window !== "undefined" ? window.location.href : "",
406
- page_title: typeof document !== "undefined" ? document.title : "",
407
- referrer: typeof document !== "undefined" ? document.referrer : "",
408
- utm_source: utm.utm_source || "",
409
- utm_medium: utm.utm_medium || "",
410
- utm_campaign: utm.utm_campaign || "",
411
- utm_term: utm.utm_term || "",
412
- utm_content: utm.utm_content || "",
413
- device_type: getDeviceType(),
414
- browser: this.getBrowser(),
415
- screen_width: typeof window !== "undefined" ? window.screen.width : 0,
416
- screen_height: typeof window !== "undefined" ? window.screen.height : 0,
417
- session_id: this.sessionId
418
- };
419
- this.eventQueue.push(event);
420
- if (this.eventQueue.length >= this.config.batchSize) {
421
- this.flush();
456
+ try {
457
+ if (!this.canTrackAnalytics()) return;
458
+ const utm = getUTMParams();
459
+ const mergedProperties = { ...properties || {} };
460
+ if (this.consent.geo && this.geoContext) {
461
+ mergedProperties.geo = this.geoContext;
462
+ }
463
+ const event = {
464
+ project_id: this.config.apiKey,
465
+ distinct_id: this.distinctId,
466
+ event_name: eventName,
467
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
468
+ properties: mergedProperties,
469
+ page_url: typeof window !== "undefined" ? window.location.href : "",
470
+ page_title: typeof document !== "undefined" ? document.title : "",
471
+ referrer: typeof document !== "undefined" ? document.referrer : "",
472
+ utm_source: utm.utm_source || "",
473
+ utm_medium: utm.utm_medium || "",
474
+ utm_campaign: utm.utm_campaign || "",
475
+ utm_term: utm.utm_term || "",
476
+ utm_content: utm.utm_content || "",
477
+ device_type: getDeviceType(),
478
+ browser: this.getBrowser(),
479
+ screen_width: typeof window !== "undefined" ? window.screen.width : 0,
480
+ screen_height: typeof window !== "undefined" ? window.screen.height : 0,
481
+ session_id: this.sessionId
482
+ };
483
+ this.eventQueue.push(event);
484
+ if (this.eventQueue.length >= this.config.batchSize) {
485
+ void this.flush();
486
+ }
487
+ } catch (err) {
488
+ this.warn("track failed", err);
422
489
  }
423
490
  }
424
491
  async flush() {
425
- await Promise.all([this.flushEvents(), this.flushHeatmapEvents()]);
492
+ try {
493
+ await Promise.all([this.flushEvents(), this.flushHeatmapEvents()]);
494
+ } catch (err) {
495
+ this.warn("flush failed", err);
496
+ }
426
497
  }
427
498
  shutdown() {
428
499
  if (this.flushTimer) {
429
500
  clearInterval(this.flushTimer);
430
501
  this.flushTimer = null;
431
502
  }
432
- this.flush();
503
+ void this.flush();
433
504
  }
434
505
  captureGeoOnce() {
435
506
  if (this.geoRequested) return;
@@ -462,14 +533,8 @@ var Krypton = class {
462
533
  api_key: this.config.apiKey,
463
534
  events
464
535
  };
465
- try {
466
- await fetch(`${this.config.endpoint}/api/v1/ingest/batch`, {
467
- method: "POST",
468
- headers: { "Content-Type": "application/json" },
469
- body: JSON.stringify(payload),
470
- keepalive: true
471
- });
472
- } catch {
536
+ const ok = await this.safePost(`${this.config.endpoint}/api/v1/ingest/batch`, payload);
537
+ if (!ok) {
473
538
  if (this.eventQueue.length < this.config.batchSize * 5) {
474
539
  this.eventQueue.unshift(...events);
475
540
  }
@@ -482,14 +547,8 @@ var Krypton = class {
482
547
  api_key: this.config.apiKey,
483
548
  events
484
549
  };
485
- try {
486
- await fetch(`${this.config.endpoint}/api/v1/heatmap`, {
487
- method: "POST",
488
- headers: { "Content-Type": "application/json" },
489
- body: JSON.stringify(payload),
490
- keepalive: true
491
- });
492
- } catch {
550
+ const ok = await this.safePost(`${this.config.endpoint}/api/v1/heatmap`, payload);
551
+ if (!ok) {
493
552
  if (this.heatmapQueue.length < this.config.batchSize * 5) {
494
553
  this.heatmapQueue.unshift(...events);
495
554
  }
@@ -520,13 +579,7 @@ var Krypton = class {
520
579
  page_height: document.documentElement.scrollHeight,
521
580
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
522
581
  };
523
- fetch(`${this.config.endpoint}/api/v1/snapshot`, {
524
- method: "POST",
525
- headers: { "Content-Type": "application/json" },
526
- body: JSON.stringify(payload),
527
- keepalive: true
528
- }).catch(() => {
529
- });
582
+ void this.safePost(`${this.config.endpoint}/api/v1/snapshot`, payload);
530
583
  } catch {
531
584
  }
532
585
  }, 1e3);
@@ -538,51 +591,83 @@ var Krypton = class {
538
591
  this.captureSnapshot();
539
592
  document.addEventListener("click", (e) => {
540
593
  if (!this.isHeatmapFeatureEnabled()) return;
541
- const target = e.target;
542
- this.heatmapQueue.push({
543
- project_id: this.config.apiKey,
544
- distinct_id: this.distinctId,
545
- session_id: this.sessionId,
546
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
547
- page_url: window.location.href,
548
- interaction_type: "click",
549
- x: e.clientX + window.scrollX,
550
- y: e.clientY + window.scrollY,
551
- viewport_width: window.innerWidth,
552
- viewport_height: window.innerHeight,
553
- page_width: document.documentElement.scrollWidth,
554
- page_height: document.documentElement.scrollHeight,
555
- selector: getSelector(target)
556
- });
557
- });
558
- let maxScrollDepth = 0;
559
- let scrollTimeout = null;
560
- window.addEventListener("scroll", () => {
561
- if (!this.isHeatmapFeatureEnabled()) return;
562
- const scrollTop = window.scrollY || document.documentElement.scrollTop;
563
- const docHeight = document.documentElement.scrollHeight - window.innerHeight;
564
- const depth = docHeight > 0 ? scrollTop / docHeight * 100 : 0;
565
- if (depth > maxScrollDepth) {
566
- maxScrollDepth = depth;
567
- }
568
- if (scrollTimeout) clearTimeout(scrollTimeout);
569
- scrollTimeout = setTimeout(() => {
594
+ try {
595
+ const target = e.target;
570
596
  this.heatmapQueue.push({
571
597
  project_id: this.config.apiKey,
572
598
  distinct_id: this.distinctId,
573
599
  session_id: this.sessionId,
574
600
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
575
601
  page_url: window.location.href,
576
- interaction_type: "scroll",
577
- scroll_depth: maxScrollDepth,
602
+ interaction_type: "click",
603
+ x: e.clientX + window.scrollX,
604
+ y: e.clientY + window.scrollY,
578
605
  viewport_width: window.innerWidth,
579
606
  viewport_height: window.innerHeight,
580
607
  page_width: document.documentElement.scrollWidth,
581
- page_height: document.documentElement.scrollHeight
608
+ page_height: document.documentElement.scrollHeight,
609
+ selector: getSelector(target)
582
610
  });
583
- }, 500);
611
+ } catch (err) {
612
+ this.warn("heatmap click capture failed", err);
613
+ }
614
+ });
615
+ let maxScrollDepth = 0;
616
+ let scrollTimeout = null;
617
+ window.addEventListener("scroll", () => {
618
+ if (!this.isHeatmapFeatureEnabled()) return;
619
+ try {
620
+ const scrollTop = window.scrollY || document.documentElement.scrollTop;
621
+ const docHeight = document.documentElement.scrollHeight - window.innerHeight;
622
+ const depth = docHeight > 0 ? scrollTop / docHeight * 100 : 0;
623
+ if (depth > maxScrollDepth) {
624
+ maxScrollDepth = depth;
625
+ }
626
+ if (scrollTimeout) clearTimeout(scrollTimeout);
627
+ scrollTimeout = setTimeout(() => {
628
+ this.heatmapQueue.push({
629
+ project_id: this.config.apiKey,
630
+ distinct_id: this.distinctId,
631
+ session_id: this.sessionId,
632
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
633
+ page_url: window.location.href,
634
+ interaction_type: "scroll",
635
+ scroll_depth: maxScrollDepth,
636
+ viewport_width: window.innerWidth,
637
+ viewport_height: window.innerHeight,
638
+ page_width: document.documentElement.scrollWidth,
639
+ page_height: document.documentElement.scrollHeight
640
+ });
641
+ }, 500);
642
+ } catch (err) {
643
+ this.warn("heatmap scroll capture failed", err);
644
+ }
584
645
  });
585
646
  }
647
+ async safePost(url, payload) {
648
+ try {
649
+ const res = await fetch(url, {
650
+ method: "POST",
651
+ headers: { "Content-Type": "application/json" },
652
+ body: JSON.stringify(payload),
653
+ keepalive: true
654
+ });
655
+ if (!res.ok) {
656
+ this.warn(`API request failed (${res.status})`, { url });
657
+ return false;
658
+ }
659
+ return true;
660
+ } catch (err) {
661
+ this.warn("API request failed", { url, err });
662
+ return false;
663
+ }
664
+ }
665
+ warn(message, details) {
666
+ try {
667
+ console.warn(`${this.logPrefix} ${message}`, details ?? "");
668
+ } catch {
669
+ }
670
+ }
586
671
  getBrowser() {
587
672
  if (typeof navigator === "undefined") return "unknown";
588
673
  const ua = navigator.userAgent;