@builtonveya/analytics-web 4.0.0 → 4.0.2

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.
@@ -1,4 +1,4 @@
1
- /*! @builtonveya/analytics-web v4.0.0 — https://github.com/adam-3owl/veya-analytics-api */
1
+ /*! @builtonveya/analytics-web v4.0.2 — https://github.com/adam-3owl/veya-analytics-api */
2
2
  "use strict";
3
3
  var VeyaAnalyticsSDK = (() => {
4
4
  var __defProp = Object.defineProperty;
@@ -37,8 +37,7 @@ var VeyaAnalyticsSDK = (() => {
37
37
  tenantId: "",
38
38
  storeId: "",
39
39
  storeName: "",
40
- endpoint: "https://analytics.veya.io/v1/events",
41
- aiEndpoint: "https://analytics.veya.io/v1/classify",
40
+ endpoint: "https://analytics.builtonveya.com/v1/events",
42
41
  batchSize: 10,
43
42
  flushInterval: 5e3,
44
43
  sessionTimeout: 30 * 60 * 1e3,
@@ -46,8 +45,8 @@ var VeyaAnalyticsSDK = (() => {
46
45
  autoDetect: true,
47
46
  autoTrackProducts: true,
48
47
  autoTrackCart: true,
49
- interceptOloApi: true,
50
- useAiClassification: true,
48
+ // Opt-in: patches window.fetch and reads order/basket responses as placed orders
49
+ interceptOloApi: false,
51
50
  confidenceThreshold: 0.7,
52
51
  platform: "web"
53
52
  };
@@ -65,6 +64,8 @@ var VeyaAnalyticsSDK = (() => {
65
64
  constructor() {
66
65
  __publicField(this, "config");
67
66
  __publicField(this, "initialized", false);
67
+ // The in-flight (or completed) init run, shared by every init() call — see init()
68
+ __publicField(this, "initPromise", null);
68
69
  __publicField(this, "eventQueue", []);
69
70
  __publicField(this, "flushTimer", null);
70
71
  // Identifiers
@@ -95,11 +96,27 @@ var VeyaAnalyticsSDK = (() => {
95
96
  // ============================================================================
96
97
  // INITIALIZATION
97
98
  // ============================================================================
98
- async init(options) {
99
- if (this.initialized) {
100
- this.log("warn", "SDK already initialized");
101
- return this;
99
+ /**
100
+ * Initialize the SDK. Safe to call more than once: a call made while another is still in
101
+ * flight shares that run (React StrictMode's double effects, or two call sites, can't set up
102
+ * listeners or send session_start twice), and a call after a successful init resolves at once.
103
+ * A failed init (e.g. a missing tenantSlug) can be retried with corrected options.
104
+ *
105
+ * Platform SDKs set their platform by overriding init() and passing it through, and do their
106
+ * own setup in onInitialized(), which runs exactly once.
107
+ */
108
+ init(options) {
109
+ if (this.initPromise) {
110
+ if (this.initialized) this.log("warn", "SDK already initialized");
111
+ return this.initPromise;
102
112
  }
113
+ this.initPromise = this.runInit(options).catch((error) => {
114
+ if (!this.initialized) this.initPromise = null;
115
+ throw error;
116
+ });
117
+ return this.initPromise;
118
+ }
119
+ async runInit(options) {
103
120
  if (!options.tenantSlug && !options.tenantId) {
104
121
  throw new Error("VeyaAnalytics: tenantSlug is required");
105
122
  }
@@ -108,19 +125,30 @@ var VeyaAnalyticsSDK = (() => {
108
125
  this.config.tenantSlug = this.config.tenantId;
109
126
  }
110
127
  await this.initVisitor();
111
- await this.initSession();
128
+ const isNewSession = await this.initSession();
112
129
  this.deviceInfo = await this.getDeviceInfo();
113
130
  this.startFlushTimer();
114
- this.trackSessionStart();
115
131
  this.initialized = true;
132
+ if (isNewSession) {
133
+ this.trackSessionStart();
134
+ }
116
135
  this.log("info", "VeyaAnalytics initialized", {
117
136
  tenantSlug: this.config.tenantSlug,
118
137
  visitorId: this.visitorId,
119
138
  sessionId: this.sessionId,
120
- platform: this.config.platform
139
+ platform: this.config.platform,
140
+ newSession: isNewSession
121
141
  });
142
+ try {
143
+ await this.onInitialized();
144
+ } catch (error) {
145
+ this.log("error", "Platform setup failed", error);
146
+ }
122
147
  return this;
123
148
  }
149
+ /** Platform hook: runs exactly once per instance, after the core is initialized. */
150
+ async onInitialized() {
151
+ }
124
152
  async initVisitor() {
125
153
  if (!this.storage) {
126
154
  this.visitorId = generateUUID();
@@ -134,10 +162,11 @@ var VeyaAnalyticsSDK = (() => {
134
162
  await this.storage.setItem("veya_visitor_id", this.visitorId);
135
163
  }
136
164
  }
165
+ /** Resolves true when a new session was started, false when a stored one was resumed. */
137
166
  async initSession() {
138
167
  if (!this.storage) {
139
168
  this.sessionId = generateUUID();
140
- return;
169
+ return true;
141
170
  }
142
171
  const stored = await this.storage.getItem("veya_session");
143
172
  const now = Date.now();
@@ -148,13 +177,14 @@ var VeyaAnalyticsSDK = (() => {
148
177
  this.sessionId = sessionData.sessionId;
149
178
  this.sessionData = { ...this.sessionData, ...sessionData.data };
150
179
  await this.saveSession();
151
- return;
180
+ return false;
152
181
  }
153
182
  } catch (e) {
154
183
  }
155
184
  }
156
185
  this.sessionId = generateUUID();
157
186
  await this.saveSession();
187
+ return true;
158
188
  }
159
189
  async saveSession() {
160
190
  if (!this.storage) return;
@@ -247,7 +277,7 @@ var VeyaAnalyticsSDK = (() => {
247
277
  delete sanitized.referrer;
248
278
  delete sanitized.funnel_step;
249
279
  delete sanitized.funnel_step_number;
250
- const piiFields = ["email", "phone", "password", "ssn", "credit_card", "cvv"];
280
+ const piiFields = ["email", "phone", "password", "ssn", "credit_card", "cvv", "address", "street", "postal_code", "zip"];
251
281
  piiFields.forEach((field) => delete sanitized[field]);
252
282
  return sanitized;
253
283
  }
@@ -402,6 +432,19 @@ var VeyaAnalyticsSDK = (() => {
402
432
  ...properties
403
433
  });
404
434
  }
435
+ /**
436
+ * The shopper chose or entered a payment method. Sends add_payment_info with
437
+ * { payment_method } at funnel step checkout_payment (4); the cart value and item count ride
438
+ * on the event itself, as on every event (matching the v1 bundle's addPaymentInfo).
439
+ */
440
+ addPaymentInfo(paymentMethod, properties = {}) {
441
+ return this.track("add_payment_info", {
442
+ payment_method: paymentMethod,
443
+ funnel_step: "checkout_payment",
444
+ funnel_step_number: 4,
445
+ ...properties
446
+ });
447
+ }
405
448
  checkoutComplete(order, properties = {}) {
406
449
  this.updateFunnelStep("confirmation");
407
450
  const event = this.track("checkout_complete", {
@@ -728,6 +771,7 @@ var VeyaAnalyticsSDK = (() => {
728
771
  titlePatterns: []
729
772
  }
730
773
  };
774
+ var SCROLL_DEPTHS = [25, 50, 75, 100];
731
775
  var VeyaAnalyticsWeb = class extends VeyaAnalyticsCore {
732
776
  constructor() {
733
777
  super();
@@ -735,11 +779,18 @@ var VeyaAnalyticsSDK = (() => {
735
779
  this.productTracker = null;
736
780
  this.cartTracker = null;
737
781
  this.networkInterceptor = null;
782
+ // Page-level state for time_on_page and scroll depth; both reset on every page view
783
+ this.pageStartedAt = Date.now();
784
+ this.scrollDepthsFired = /* @__PURE__ */ new Set();
785
+ this.scrollCheckPending = false;
738
786
  this.storage = new LocalStorageAdapter();
739
787
  }
740
- async init(options) {
741
- options.platform = "web";
742
- await super.init(options);
788
+ init(options) {
789
+ return super.init({ ...options, platform: "web" });
790
+ }
791
+ // Runs exactly once, after the core is initialized (see VeyaAnalyticsCore.init)
792
+ async onInitialized() {
793
+ this.pageStartedAt = Date.now();
743
794
  if (this.config.autoDetect) {
744
795
  this.setupPageChangeDetection();
745
796
  }
@@ -756,8 +807,9 @@ var VeyaAnalyticsSDK = (() => {
756
807
  this.networkInterceptor.init();
757
808
  }
758
809
  this.setupBrowserListeners();
759
- this.trackCurrentPage();
760
- return this;
810
+ if (this.config.autoDetect) {
811
+ this.trackCurrentPage();
812
+ }
761
813
  }
762
814
  async getDeviceInfo() {
763
815
  const ua = navigator.userAgent;
@@ -887,6 +939,13 @@ var VeyaAnalyticsSDK = (() => {
887
939
  ...this.getUTMParams()
888
940
  });
889
941
  }
942
+ // Every page view (SDK-detected or sent by the app) starts a new page: time_on_page restarts and
943
+ // the scroll-depth thresholds can fire again.
944
+ pageView(properties = {}) {
945
+ this.pageStartedAt = Date.now();
946
+ this.scrollDepthsFired.clear();
947
+ return super.pageView(properties);
948
+ }
890
949
  getUTMParams() {
891
950
  const params = new URLSearchParams(window.location.search);
892
951
  return {
@@ -903,13 +962,32 @@ var VeyaAnalyticsSDK = (() => {
903
962
  setupBrowserListeners() {
904
963
  document.addEventListener("visibilitychange", () => {
905
964
  if (document.visibilityState === "hidden") {
965
+ this.track("page_hidden", { time_on_page: this.timeOnPage() });
906
966
  this.flush(true);
967
+ } else if (document.visibilityState === "visible") {
968
+ this.track("page_visible");
907
969
  }
908
970
  });
909
- window.addEventListener("beforeunload", () => {
910
- this.trackSessionEnd();
971
+ window.addEventListener("pagehide", (event) => {
972
+ this.track("page_unload", { time_on_page: this.timeOnPage(), persisted: event.persisted });
911
973
  this.flush(true);
912
974
  });
975
+ window.addEventListener("pageshow", (event) => {
976
+ if (!event.persisted) return;
977
+ this.pageStartedAt = Date.now();
978
+ this.scrollDepthsFired.clear();
979
+ if (this.config.autoDetect) {
980
+ this.trackCurrentPage();
981
+ }
982
+ });
983
+ window.addEventListener("scroll", () => {
984
+ if (this.scrollCheckPending) return;
985
+ this.scrollCheckPending = true;
986
+ setTimeout(() => {
987
+ this.scrollCheckPending = false;
988
+ this.checkScrollDepth();
989
+ }, 100);
990
+ }, { passive: true });
913
991
  window.addEventListener("error", (e) => {
914
992
  this.error("javascript", e.message, {
915
993
  filename: e.filename,
@@ -922,6 +1000,22 @@ var VeyaAnalyticsSDK = (() => {
922
1000
  this.error("promise_rejection", ((_a = e.reason) == null ? void 0 : _a.message) || String(e.reason));
923
1001
  });
924
1002
  }
1003
+ timeOnPage() {
1004
+ return Math.max(0, Date.now() - this.pageStartedAt);
1005
+ }
1006
+ // Fires every threshold at or below the current depth that hasn't fired yet on this page, so a
1007
+ // fast scroll that jumps past several thresholds still reports each of them once.
1008
+ checkScrollDepth() {
1009
+ const scrollable = document.documentElement.scrollHeight - window.innerHeight;
1010
+ if (scrollable <= 0) return;
1011
+ const percent = Math.min(100, Math.round(window.scrollY / scrollable * 100));
1012
+ for (const depth of SCROLL_DEPTHS) {
1013
+ if (percent >= depth && !this.scrollDepthsFired.has(depth)) {
1014
+ this.scrollDepthsFired.add(depth);
1015
+ this.track("scroll_depth", { depth });
1016
+ }
1017
+ }
1018
+ }
925
1019
  // ============================================================================
926
1020
  // FLUSH OVERRIDE FOR WEB
927
1021
  // ============================================================================