@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.mjs CHANGED
@@ -14,25 +14,31 @@ function generateId() {
14
14
  }
15
15
  function getSessionId() {
16
16
  const key = "_krypton_sid";
17
- if (typeof sessionStorage !== "undefined") {
18
- let sid = sessionStorage.getItem(key);
19
- if (!sid) {
20
- sid = generateId();
21
- sessionStorage.setItem(key, sid);
17
+ try {
18
+ if (typeof sessionStorage !== "undefined") {
19
+ let sid = sessionStorage.getItem(key);
20
+ if (!sid) {
21
+ sid = generateId();
22
+ sessionStorage.setItem(key, sid);
23
+ }
24
+ return sid;
22
25
  }
23
- return sid;
26
+ } catch {
24
27
  }
25
28
  return generateId();
26
29
  }
27
30
  function getDistinctId() {
28
31
  const key = "_krypton_did";
29
- if (typeof localStorage !== "undefined") {
30
- let did = localStorage.getItem(key);
31
- if (!did) {
32
- did = generateId();
33
- localStorage.setItem(key, did);
32
+ try {
33
+ if (typeof localStorage !== "undefined") {
34
+ let did = localStorage.getItem(key);
35
+ if (!did) {
36
+ did = generateId();
37
+ localStorage.setItem(key, did);
38
+ }
39
+ return did;
34
40
  }
35
- return did;
41
+ } catch {
36
42
  }
37
43
  return generateId();
38
44
  }
@@ -86,6 +92,7 @@ var Krypton = class {
86
92
  this.geoContext = null;
87
93
  this.serverConfig = null;
88
94
  this.configFetched = false;
95
+ this.logPrefix = "[Krypton SDK]";
89
96
  this.config = {
90
97
  autoPageview: true,
91
98
  heatmap: false,
@@ -112,6 +119,8 @@ var Krypton = class {
112
119
  if (this.config.consentRequired && this.config.showConsentBanner && !this.hasStoredConsent()) {
113
120
  this.showConsentBanner();
114
121
  }
122
+ }).catch((err) => {
123
+ this.warn("Initialization failed", err);
115
124
  });
116
125
  }
117
126
  resolveInitialConsent() {
@@ -131,14 +140,18 @@ var Krypton = class {
131
140
  return `_krypton_consent_${this.config.apiKey}`;
132
141
  }
133
142
  hasStoredConsent() {
134
- if (typeof localStorage === "undefined") return false;
135
- return !!localStorage.getItem(this.consentStorageKey());
143
+ try {
144
+ if (typeof localStorage === "undefined") return false;
145
+ return !!localStorage.getItem(this.consentStorageKey());
146
+ } catch {
147
+ return false;
148
+ }
136
149
  }
137
150
  loadStoredConsent() {
138
- if (typeof localStorage === "undefined") return null;
139
- const raw = localStorage.getItem(this.consentStorageKey());
140
- if (!raw) return null;
141
151
  try {
152
+ if (typeof localStorage === "undefined") return null;
153
+ const raw = localStorage.getItem(this.consentStorageKey());
154
+ if (!raw) return null;
142
155
  const parsed = JSON.parse(raw);
143
156
  return {
144
157
  analytics: !!parsed.analytics,
@@ -150,8 +163,11 @@ var Krypton = class {
150
163
  }
151
164
  }
152
165
  persistConsent() {
153
- if (typeof localStorage === "undefined") return;
154
- localStorage.setItem(this.consentStorageKey(), JSON.stringify(this.consent));
166
+ try {
167
+ if (typeof localStorage === "undefined") return;
168
+ localStorage.setItem(this.consentStorageKey(), JSON.stringify(this.consent));
169
+ } catch {
170
+ }
155
171
  }
156
172
  promotePersistentIds() {
157
173
  this.distinctId = getDistinctId();
@@ -168,7 +184,12 @@ var Krypton = class {
168
184
  async fetchConfig() {
169
185
  if (typeof window === "undefined") return;
170
186
  const cacheKey = `_krypton_config_${this.config.apiKey}`;
171
- const cached = sessionStorage.getItem(cacheKey);
187
+ let cached = null;
188
+ try {
189
+ cached = sessionStorage.getItem(cacheKey);
190
+ } catch {
191
+ cached = null;
192
+ }
172
193
  if (cached) {
173
194
  try {
174
195
  const parsed = JSON.parse(cached);
@@ -188,44 +209,63 @@ var Krypton = class {
188
209
  const data = await res.json();
189
210
  this.serverConfig = data;
190
211
  this.configFetched = true;
191
- sessionStorage.setItem(cacheKey, JSON.stringify({ ...data, _ts: Date.now() }));
212
+ try {
213
+ sessionStorage.setItem(cacheKey, JSON.stringify({ ...data, _ts: Date.now() }));
214
+ } catch {
215
+ }
192
216
  }
193
217
  } catch {
194
218
  }
195
219
  }
196
220
  setupTracking() {
197
- this.flushTimer = setInterval(() => this.flush(), this.config.flushInterval);
221
+ this.flushTimer = setInterval(() => {
222
+ void this.flush();
223
+ }, this.config.flushInterval);
198
224
  if (typeof window === "undefined") return;
199
- window.addEventListener("beforeunload", () => this.flush());
225
+ window.addEventListener("beforeunload", () => {
226
+ void this.flush();
227
+ });
200
228
  if (this.config.autoPageview && this.canTrackAnalytics()) {
201
229
  this.trackPageview();
202
230
  }
203
231
  const originalPushState = history.pushState;
204
232
  history.pushState = (...args) => {
205
- originalPushState.apply(history, args);
206
- if (this.config.autoPageview && this.canTrackAnalytics()) {
207
- setTimeout(() => this.trackPageview(), 0);
208
- }
209
- if (this.isHeatmapFeatureEnabled()) {
210
- setTimeout(() => this.captureSnapshot(), 0);
233
+ try {
234
+ originalPushState.apply(history, args);
235
+ if (this.config.autoPageview && this.canTrackAnalytics()) {
236
+ setTimeout(() => this.trackPageview(), 0);
237
+ }
238
+ if (this.isHeatmapFeatureEnabled()) {
239
+ setTimeout(() => this.captureSnapshot(), 0);
240
+ }
241
+ } catch (err) {
242
+ this.warn("pushState hook failed", err);
211
243
  }
212
244
  };
213
245
  const originalReplaceState = history.replaceState;
214
246
  history.replaceState = (...args) => {
215
- originalReplaceState.apply(history, args);
216
- if (this.config.autoPageview && this.canTrackAnalytics()) {
217
- setTimeout(() => this.trackPageview(), 0);
218
- }
219
- if (this.isHeatmapFeatureEnabled()) {
220
- setTimeout(() => this.captureSnapshot(), 0);
247
+ try {
248
+ originalReplaceState.apply(history, args);
249
+ if (this.config.autoPageview && this.canTrackAnalytics()) {
250
+ setTimeout(() => this.trackPageview(), 0);
251
+ }
252
+ if (this.isHeatmapFeatureEnabled()) {
253
+ setTimeout(() => this.captureSnapshot(), 0);
254
+ }
255
+ } catch (err) {
256
+ this.warn("replaceState hook failed", err);
221
257
  }
222
258
  };
223
259
  window.addEventListener("popstate", () => {
224
- if (this.config.autoPageview && this.canTrackAnalytics()) {
225
- setTimeout(() => this.trackPageview(), 0);
226
- }
227
- if (this.isHeatmapFeatureEnabled()) {
228
- setTimeout(() => this.captureSnapshot(), 0);
260
+ try {
261
+ if (this.config.autoPageview && this.canTrackAnalytics()) {
262
+ setTimeout(() => this.trackPageview(), 0);
263
+ }
264
+ if (this.isHeatmapFeatureEnabled()) {
265
+ setTimeout(() => this.captureSnapshot(), 0);
266
+ }
267
+ } catch (err) {
268
+ this.warn("popstate hook failed", err);
229
269
  }
230
270
  });
231
271
  if (this.isHeatmapFeatureEnabled()) {
@@ -282,58 +322,68 @@ var Krypton = class {
282
322
  `;
283
323
  const remove = () => {
284
324
  banner.remove();
325
+ try {
326
+ if (document.body) document.body.style.cursor = "";
327
+ if (document.documentElement) document.documentElement.style.cursor = "";
328
+ } catch {
329
+ }
285
330
  };
286
331
  const readValue = (id) => {
287
332
  const el = document.getElementById(id);
288
333
  return !!el?.checked;
289
334
  };
290
335
  banner.querySelector("#krypton-consent-save")?.addEventListener("click", () => {
291
- this.setConsent({
336
+ const next = {
292
337
  analytics: readValue("krypton-consent-analytics"),
293
338
  heatmaps: readValue("krypton-consent-heatmaps"),
294
339
  geo: readValue("krypton-consent-geo")
295
- });
340
+ };
296
341
  remove();
342
+ setTimeout(() => this.setConsent(next), 0);
297
343
  });
298
344
  banner.querySelector("#krypton-consent-all")?.addEventListener("click", () => {
299
- this.setConsent({ analytics: true, heatmaps: true, geo: true });
300
345
  remove();
346
+ setTimeout(() => this.setConsent({ analytics: true, heatmaps: true, geo: true }), 0);
301
347
  });
302
348
  banner.querySelector("#krypton-consent-none")?.addEventListener("click", () => {
303
- this.setConsent({ analytics: false, heatmaps: false, geo: false });
304
349
  remove();
350
+ setTimeout(() => this.setConsent({ analytics: false, heatmaps: false, geo: false }), 0);
305
351
  });
306
352
  document.body.appendChild(banner);
307
353
  }
308
354
  /** Set one or more consent categories and apply changes immediately. */
309
355
  setConsent(next, persist = true) {
310
- const prev = { ...this.consent };
311
- this.consent = {
312
- analytics: next.analytics ?? prev.analytics,
313
- heatmaps: next.heatmaps ?? prev.heatmaps,
314
- geo: next.geo ?? prev.geo
315
- };
316
- if (persist) {
317
- this.persistConsent();
318
- }
319
- if (!prev.analytics && this.consent.analytics) {
320
- this.promotePersistentIds();
321
- if (this.config.autoPageview) {
322
- this.trackPageview();
356
+ try {
357
+ const prev = { ...this.consent };
358
+ this.consent = {
359
+ analytics: next.analytics ?? prev.analytics,
360
+ heatmaps: next.heatmaps ?? prev.heatmaps,
361
+ geo: next.geo ?? prev.geo
362
+ };
363
+ if (persist) {
364
+ this.persistConsent();
323
365
  }
324
- }
325
- if (prev.analytics && !this.consent.analytics) {
326
- this.eventQueue = [];
327
- }
328
- if (!prev.heatmaps && this.isHeatmapFeatureEnabled()) {
329
- this.setupHeatmap();
330
- this.captureSnapshot();
331
- }
332
- if (prev.heatmaps && !this.consent.heatmaps) {
333
- this.heatmapQueue = [];
334
- }
335
- if (!prev.geo && this.consent.geo) {
336
- this.captureGeoOnce();
366
+ if (!prev.analytics && this.consent.analytics) {
367
+ this.promotePersistentIds();
368
+ if (this.config.autoPageview) {
369
+ this.trackPageview();
370
+ }
371
+ }
372
+ if (prev.analytics && !this.consent.analytics) {
373
+ this.eventQueue = [];
374
+ }
375
+ if (!prev.heatmaps && this.isHeatmapFeatureEnabled()) {
376
+ this.setupHeatmap();
377
+ this.captureSnapshot();
378
+ }
379
+ if (prev.heatmaps && !this.consent.heatmaps) {
380
+ this.heatmapQueue = [];
381
+ }
382
+ if (!prev.geo && this.consent.geo) {
383
+ this.captureGeoOnce();
384
+ }
385
+ } catch (err) {
386
+ this.warn("setConsent failed", err);
337
387
  }
338
388
  }
339
389
  getConsent() {
@@ -351,60 +401,81 @@ var Krypton = class {
351
401
  }
352
402
  /** Identify a user with a custom distinct ID */
353
403
  identify(distinctId) {
354
- this.distinctId = distinctId;
355
- if (typeof localStorage !== "undefined" && this.canTrackAnalytics()) {
356
- localStorage.setItem("_krypton_did", distinctId);
404
+ try {
405
+ this.distinctId = distinctId;
406
+ if (this.canTrackAnalytics()) {
407
+ try {
408
+ if (typeof localStorage !== "undefined") {
409
+ localStorage.setItem("_krypton_did", distinctId);
410
+ }
411
+ } catch {
412
+ }
413
+ }
414
+ } catch (err) {
415
+ this.warn("identify failed", err);
357
416
  }
358
417
  }
359
418
  /** Track a pageview */
360
419
  trackPageview(properties) {
361
- if (!this.canTrackAnalytics()) return;
362
- this.track("$pageview", {
363
- ...properties
364
- });
420
+ try {
421
+ if (!this.canTrackAnalytics()) return;
422
+ this.track("$pageview", {
423
+ ...properties
424
+ });
425
+ } catch (err) {
426
+ this.warn("trackPageview failed", err);
427
+ }
365
428
  }
366
429
  /** Track a custom event */
367
430
  track(eventName, properties) {
368
- if (!this.canTrackAnalytics()) return;
369
- const utm = getUTMParams();
370
- const mergedProperties = { ...properties || {} };
371
- if (this.consent.geo && this.geoContext) {
372
- mergedProperties.geo = this.geoContext;
373
- }
374
- const event = {
375
- project_id: this.config.apiKey,
376
- distinct_id: this.distinctId,
377
- event_name: eventName,
378
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
379
- properties: mergedProperties,
380
- page_url: typeof window !== "undefined" ? window.location.href : "",
381
- page_title: typeof document !== "undefined" ? document.title : "",
382
- referrer: typeof document !== "undefined" ? document.referrer : "",
383
- utm_source: utm.utm_source || "",
384
- utm_medium: utm.utm_medium || "",
385
- utm_campaign: utm.utm_campaign || "",
386
- utm_term: utm.utm_term || "",
387
- utm_content: utm.utm_content || "",
388
- device_type: getDeviceType(),
389
- browser: this.getBrowser(),
390
- screen_width: typeof window !== "undefined" ? window.screen.width : 0,
391
- screen_height: typeof window !== "undefined" ? window.screen.height : 0,
392
- session_id: this.sessionId
393
- };
394
- this.eventQueue.push(event);
395
- if (this.eventQueue.length >= this.config.batchSize) {
396
- this.flush();
431
+ try {
432
+ if (!this.canTrackAnalytics()) return;
433
+ const utm = getUTMParams();
434
+ const mergedProperties = { ...properties || {} };
435
+ if (this.consent.geo && this.geoContext) {
436
+ mergedProperties.geo = this.geoContext;
437
+ }
438
+ const event = {
439
+ project_id: this.config.apiKey,
440
+ distinct_id: this.distinctId,
441
+ event_name: eventName,
442
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
443
+ properties: mergedProperties,
444
+ page_url: typeof window !== "undefined" ? window.location.href : "",
445
+ page_title: typeof document !== "undefined" ? document.title : "",
446
+ referrer: typeof document !== "undefined" ? document.referrer : "",
447
+ utm_source: utm.utm_source || "",
448
+ utm_medium: utm.utm_medium || "",
449
+ utm_campaign: utm.utm_campaign || "",
450
+ utm_term: utm.utm_term || "",
451
+ utm_content: utm.utm_content || "",
452
+ device_type: getDeviceType(),
453
+ browser: this.getBrowser(),
454
+ screen_width: typeof window !== "undefined" ? window.screen.width : 0,
455
+ screen_height: typeof window !== "undefined" ? window.screen.height : 0,
456
+ session_id: this.sessionId
457
+ };
458
+ this.eventQueue.push(event);
459
+ if (this.eventQueue.length >= this.config.batchSize) {
460
+ void this.flush();
461
+ }
462
+ } catch (err) {
463
+ this.warn("track failed", err);
397
464
  }
398
465
  }
399
466
  async flush() {
400
- await Promise.all([this.flushEvents(), this.flushHeatmapEvents()]);
467
+ try {
468
+ await Promise.all([this.flushEvents(), this.flushHeatmapEvents()]);
469
+ } catch (err) {
470
+ this.warn("flush failed", err);
471
+ }
401
472
  }
402
473
  shutdown() {
403
474
  if (this.flushTimer) {
404
475
  clearInterval(this.flushTimer);
405
476
  this.flushTimer = null;
406
477
  }
407
- this.flush();
478
+ void this.flush();
408
479
  }
409
480
  captureGeoOnce() {
410
481
  if (this.geoRequested) return;
@@ -437,14 +508,8 @@ var Krypton = class {
437
508
  api_key: this.config.apiKey,
438
509
  events
439
510
  };
440
- try {
441
- await fetch(`${this.config.endpoint}/api/v1/ingest/batch`, {
442
- method: "POST",
443
- headers: { "Content-Type": "application/json" },
444
- body: JSON.stringify(payload),
445
- keepalive: true
446
- });
447
- } catch {
511
+ const ok = await this.safePost(`${this.config.endpoint}/api/v1/ingest/batch`, payload);
512
+ if (!ok) {
448
513
  if (this.eventQueue.length < this.config.batchSize * 5) {
449
514
  this.eventQueue.unshift(...events);
450
515
  }
@@ -457,14 +522,8 @@ var Krypton = class {
457
522
  api_key: this.config.apiKey,
458
523
  events
459
524
  };
460
- try {
461
- await fetch(`${this.config.endpoint}/api/v1/heatmap`, {
462
- method: "POST",
463
- headers: { "Content-Type": "application/json" },
464
- body: JSON.stringify(payload),
465
- keepalive: true
466
- });
467
- } catch {
525
+ const ok = await this.safePost(`${this.config.endpoint}/api/v1/heatmap`, payload);
526
+ if (!ok) {
468
527
  if (this.heatmapQueue.length < this.config.batchSize * 5) {
469
528
  this.heatmapQueue.unshift(...events);
470
529
  }
@@ -495,13 +554,7 @@ var Krypton = class {
495
554
  page_height: document.documentElement.scrollHeight,
496
555
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
497
556
  };
498
- fetch(`${this.config.endpoint}/api/v1/snapshot`, {
499
- method: "POST",
500
- headers: { "Content-Type": "application/json" },
501
- body: JSON.stringify(payload),
502
- keepalive: true
503
- }).catch(() => {
504
- });
557
+ void this.safePost(`${this.config.endpoint}/api/v1/snapshot`, payload);
505
558
  } catch {
506
559
  }
507
560
  }, 1e3);
@@ -513,51 +566,83 @@ var Krypton = class {
513
566
  this.captureSnapshot();
514
567
  document.addEventListener("click", (e) => {
515
568
  if (!this.isHeatmapFeatureEnabled()) return;
516
- const target = e.target;
517
- this.heatmapQueue.push({
518
- project_id: this.config.apiKey,
519
- distinct_id: this.distinctId,
520
- session_id: this.sessionId,
521
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
522
- page_url: window.location.href,
523
- interaction_type: "click",
524
- x: e.clientX + window.scrollX,
525
- y: e.clientY + window.scrollY,
526
- viewport_width: window.innerWidth,
527
- viewport_height: window.innerHeight,
528
- page_width: document.documentElement.scrollWidth,
529
- page_height: document.documentElement.scrollHeight,
530
- selector: getSelector(target)
531
- });
532
- });
533
- let maxScrollDepth = 0;
534
- let scrollTimeout = null;
535
- window.addEventListener("scroll", () => {
536
- if (!this.isHeatmapFeatureEnabled()) return;
537
- const scrollTop = window.scrollY || document.documentElement.scrollTop;
538
- const docHeight = document.documentElement.scrollHeight - window.innerHeight;
539
- const depth = docHeight > 0 ? scrollTop / docHeight * 100 : 0;
540
- if (depth > maxScrollDepth) {
541
- maxScrollDepth = depth;
542
- }
543
- if (scrollTimeout) clearTimeout(scrollTimeout);
544
- scrollTimeout = setTimeout(() => {
569
+ try {
570
+ const target = e.target;
545
571
  this.heatmapQueue.push({
546
572
  project_id: this.config.apiKey,
547
573
  distinct_id: this.distinctId,
548
574
  session_id: this.sessionId,
549
575
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
550
576
  page_url: window.location.href,
551
- interaction_type: "scroll",
552
- scroll_depth: maxScrollDepth,
577
+ interaction_type: "click",
578
+ x: e.clientX + window.scrollX,
579
+ y: e.clientY + window.scrollY,
553
580
  viewport_width: window.innerWidth,
554
581
  viewport_height: window.innerHeight,
555
582
  page_width: document.documentElement.scrollWidth,
556
- page_height: document.documentElement.scrollHeight
583
+ page_height: document.documentElement.scrollHeight,
584
+ selector: getSelector(target)
557
585
  });
558
- }, 500);
586
+ } catch (err) {
587
+ this.warn("heatmap click capture failed", err);
588
+ }
589
+ });
590
+ let maxScrollDepth = 0;
591
+ let scrollTimeout = null;
592
+ window.addEventListener("scroll", () => {
593
+ if (!this.isHeatmapFeatureEnabled()) return;
594
+ try {
595
+ const scrollTop = window.scrollY || document.documentElement.scrollTop;
596
+ const docHeight = document.documentElement.scrollHeight - window.innerHeight;
597
+ const depth = docHeight > 0 ? scrollTop / docHeight * 100 : 0;
598
+ if (depth > maxScrollDepth) {
599
+ maxScrollDepth = depth;
600
+ }
601
+ if (scrollTimeout) clearTimeout(scrollTimeout);
602
+ scrollTimeout = setTimeout(() => {
603
+ this.heatmapQueue.push({
604
+ project_id: this.config.apiKey,
605
+ distinct_id: this.distinctId,
606
+ session_id: this.sessionId,
607
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
608
+ page_url: window.location.href,
609
+ interaction_type: "scroll",
610
+ scroll_depth: maxScrollDepth,
611
+ viewport_width: window.innerWidth,
612
+ viewport_height: window.innerHeight,
613
+ page_width: document.documentElement.scrollWidth,
614
+ page_height: document.documentElement.scrollHeight
615
+ });
616
+ }, 500);
617
+ } catch (err) {
618
+ this.warn("heatmap scroll capture failed", err);
619
+ }
559
620
  });
560
621
  }
622
+ async safePost(url, payload) {
623
+ try {
624
+ const res = await fetch(url, {
625
+ method: "POST",
626
+ headers: { "Content-Type": "application/json" },
627
+ body: JSON.stringify(payload),
628
+ keepalive: true
629
+ });
630
+ if (!res.ok) {
631
+ this.warn(`API request failed (${res.status})`, { url });
632
+ return false;
633
+ }
634
+ return true;
635
+ } catch (err) {
636
+ this.warn("API request failed", { url, err });
637
+ return false;
638
+ }
639
+ }
640
+ warn(message, details) {
641
+ try {
642
+ console.warn(`${this.logPrefix} ${message}`, details ?? "");
643
+ } catch {
644
+ }
645
+ }
561
646
  getBrowser() {
562
647
  if (typeof navigator === "undefined") return "unknown";
563
648
  const ua = navigator.userAgent;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kryptonhq/analytics",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Krypton Analytics JavaScript SDK — privacy-first analytics tracking",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",