@chordcommerce/analytics 1.22.0-beta.3 → 1.22.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.
@@ -45,7 +45,7 @@ var utils_js_1 = require("./utils.js");
45
45
  * Returns initial consent configuration to be tracked after CDP is fully ready.
46
46
  */
47
47
  var addCdpSnippet = function (options) { return __awaiter(void 0, void 0, void 0, function () {
48
- var normalizedCDPDomain, cdpReadyPromise, s, initialConsent;
48
+ var normalizedCDPDomain, cdpReadyPromise, escapedNamespace, existingScript, s, initialConsent;
49
49
  return __generator(this, function (_a) {
50
50
  switch (_a.label) {
51
51
  case 0:
@@ -65,14 +65,20 @@ var addCdpSnippet = function (options) { return __awaiter(void 0, void 0, void 0
65
65
  };
66
66
  checkCdpReady();
67
67
  });
68
- s = document.createElement('script');
69
- s.async = true;
70
- s.src = "".concat(normalizedCDPDomain, "/p.js");
71
- s.setAttribute('data-debug', "".concat(!!options.debug));
72
- s.setAttribute('data-init-only', 'true');
73
- s.setAttribute('data-namespace', "_".concat(options.namespace));
74
- s.setAttribute('data-write-key', options.cdpWriteKey);
75
- document.head.appendChild(s);
68
+ escapedNamespace = typeof CSS !== 'undefined' && typeof CSS.escape === 'function'
69
+ ? CSS.escape("_".concat(options.namespace))
70
+ : "_".concat(options.namespace);
71
+ existingScript = document.querySelector("script[data-namespace=\"".concat(escapedNamespace, "\"]"));
72
+ if (!existingScript) {
73
+ s = document.createElement('script');
74
+ s.async = true;
75
+ s.src = "".concat(normalizedCDPDomain, "/p.js");
76
+ s.setAttribute('data-debug', "".concat(!!options.debug));
77
+ s.setAttribute('data-init-only', 'true');
78
+ s.setAttribute('data-namespace', "_".concat(options.namespace));
79
+ s.setAttribute('data-write-key', options.cdpWriteKey);
80
+ document.head.appendChild(s);
81
+ }
76
82
  // Wait for CDP to be ready before setting up consent
77
83
  return [4 /*yield*/, cdpReadyPromise];
78
84
  case 1:
@@ -1,6 +1,5 @@
1
1
  import type { AnyAnalytics, EventProperties, Options, ValidateResult, ChordAnalyticsOptions, AnyOptions, CartViewedInput, IdentifyTraits, CheckoutStartedInput, CheckoutStepCompletedInput, CheckoutStepViewedInput, EmailCapturedInput, ProductListViewedInput, ProductListFilteredInput, ProductAddedInput, ProductClickedInput, VariantClickedInput, ProductRemovedInput, ProductViewedInput, ProductsSearchedInput, CouponAppliedInput, CouponDeniedInput, CouponEnteredInput, CouponRemovedInput, SignedInInput, SignedOutInput, SignedUpInput, LoginStartedInput, ObjectTypes, OrderCompletedInput, SubscriptionCancelledInput, NavigationClickedInput, PaymentInfoEnteredInput } from '../types/index.js';
2
2
  import { AnalyticsMiddleware } from '../types/client.js';
3
- import { type IdentifierSyncOptions, type ShopifyCart } from './identifiers.js';
4
3
  export interface ChordAnalyticsSnippet {
5
4
  _loadOptions: ChordAnalyticsOptions<ObjectTypes>;
6
5
  invoked: boolean;
@@ -170,18 +169,6 @@ export declare class ChordAnalytics<T extends ObjectTypes = ObjectTypes> {
170
169
  * @returns A cleanup function to remove the listener
171
170
  */
172
171
  setupWebPixelListener: () => (() => void);
173
- /**
174
- * Sync CDP anonymous ID and Google Client ID to Shopify cart attributes.
175
- * This enables server-side events (webhooks) to correlate with client-side tracking.
176
- *
177
- * By default, syncs:
178
- * - `_cdpAnonymousId`: from the `__eventn_id` cookie (Chord/Jitsu CDP)
179
- * - `_googleClientId`: from the `_ga` cookie (Google Analytics)
180
- *
181
- * @param options - Optional configuration for the sync
182
- * @returns The updated cart, or undefined if no update was needed or an error occurred
183
- */
184
- syncIdentifiersToCart: (options?: IdentifierSyncOptions) => Promise<ShopifyCart | undefined>;
185
172
  trackCartViewed: (props: CartViewedInput<T>, options?: AnyOptions) => Promise<void>;
186
173
  trackCheckoutStarted: (props: CheckoutStartedInput<T>, options?: AnyOptions) => Promise<void>;
187
174
  trackCheckoutStepCompleted: (props: CheckoutStepCompletedInput, options?: AnyOptions) => Promise<void>;
@@ -61,7 +61,6 @@ var cdp_js_1 = require("./cdp.js");
61
61
  var index_js_1 = require("./consent/index.js");
62
62
  var index_js_2 = require("../validators/index.js");
63
63
  var utils_js_1 = require("../utils.js");
64
- var identifiers_js_1 = require("./identifiers.js");
65
64
  var DEBUG_MODE_STORAGE_KEY = 'chordcommerce/analytics/debug';
66
65
  var CONSENT_QUEUE_STORAGE_KEY = 'chordcommerce/analytics/consent-queue';
67
66
  var CONSENT_QUEUE_TTL_MS = 5 * 60 * 1000; // 5 minutes
@@ -70,7 +69,7 @@ var CONSENT_QUEUE_MAX_EVENTS = 50; // Max number of events to queue
70
69
  var ChordAnalytics = /** @class */ (function () {
71
70
  function ChordAnalytics(options) {
72
71
  var _this = this;
73
- var _a, _b, _c, _d, _e, _f, _g;
72
+ var _a, _b, _c, _d;
74
73
  /**
75
74
  * Allows snippet.js to detect whether this library has been initialized yet.
76
75
  */
@@ -825,6 +824,17 @@ var ChordAnalytics = /** @class */ (function () {
825
824
  return;
826
825
  if (event.data.source !== 'chord-web-pixel')
827
826
  return;
827
+ // Validate origin — only accept messages from the same hostname
828
+ try {
829
+ if (event.origin) {
830
+ var eventHostname = new URL(event.origin).hostname;
831
+ if (eventHostname !== window.location.hostname)
832
+ return;
833
+ }
834
+ }
835
+ catch (_a) {
836
+ return;
837
+ }
828
838
  var message = event.data;
829
839
  if (_this.options.enableLogging && _this.options.debug) {
830
840
  // eslint-disable-next-line no-console
@@ -833,20 +843,26 @@ var ChordAnalytics = /** @class */ (function () {
833
843
  try {
834
844
  switch (message.type) {
835
845
  case 'track': {
836
- var _a = message.payload, eventName = _a[0], properties = _a[1];
846
+ var _b = message.payload, eventName = _b[0], properties = _b[1];
837
847
  if (eventName) {
838
- _this.track(eventName, properties);
848
+ Promise.resolve(_this.track(eventName, properties)).catch(function (err) {
849
+ return _this.logger('[Chord Analytics]: Error in web pixel track:', err);
850
+ });
839
851
  }
840
852
  break;
841
853
  }
842
854
  case 'identify': {
843
- var _b = message.payload, userId = _b[0], traits = _b[1];
844
- _this.identify(userId, traits);
855
+ var _c = message.payload, userId = _c[0], traits = _c[1];
856
+ Promise.resolve(_this.identify(userId, traits)).catch(function (err) {
857
+ return _this.logger('[Chord Analytics]: Error in web pixel identify:', err);
858
+ });
845
859
  break;
846
860
  }
847
861
  case 'page': {
848
862
  var properties = message.payload[0];
849
- _this.page(properties);
863
+ Promise.resolve(_this.page(undefined, properties)).catch(function (err) {
864
+ return _this.logger('[Chord Analytics]: Error in web pixel page:', err);
865
+ });
850
866
  break;
851
867
  }
852
868
  default:
@@ -868,31 +884,6 @@ var ChordAnalytics = /** @class */ (function () {
868
884
  }
869
885
  };
870
886
  };
871
- /**
872
- * Sync CDP anonymous ID and Google Client ID to Shopify cart attributes.
873
- * This enables server-side events (webhooks) to correlate with client-side tracking.
874
- *
875
- * By default, syncs:
876
- * - `_cdpAnonymousId`: from the `__eventn_id` cookie (Chord/Jitsu CDP)
877
- * - `_googleClientId`: from the `_ga` cookie (Google Analytics)
878
- *
879
- * @param options - Optional configuration for the sync
880
- * @returns The updated cart, or undefined if no update was needed or an error occurred
881
- */
882
- this.syncIdentifiersToCart = function (options) { return __awaiter(_this, void 0, void 0, function () {
883
- var result;
884
- return __generator(this, function (_a) {
885
- switch (_a.label) {
886
- case 0: return [4 /*yield*/, (0, identifiers_js_1.syncIdentifiersToCart)(options)];
887
- case 1:
888
- result = _a.sent();
889
- if (result) {
890
- this.logger('[Chord Analytics]: Identifiers synced to cart');
891
- }
892
- return [2 /*return*/, result];
893
- }
894
- });
895
- }); };
896
887
  // TODO: Add support for props.products
897
888
  this.trackCartViewed = function (props, options) { return __awaiter(_this, void 0, void 0, function () {
898
889
  var cart, payload, formattedPayload;
@@ -1455,12 +1446,6 @@ var ChordAnalytics = /** @class */ (function () {
1455
1446
  if (shouldEnableWebPixel) {
1456
1447
  this.setupWebPixelListener();
1457
1448
  }
1458
- // Sync identifiers to cart: explicit true/false takes precedence,
1459
- // otherwise auto-detect by checking for window.Shopify.shop (Liquid storefront)
1460
- var shouldEnableIdentifierSync = (_f = (_e = this.options) === null || _e === void 0 ? void 0 : _e.enableIdentifierSync) !== null && _f !== void 0 ? _f : (typeof window !== 'undefined' && !!((_g = window.Shopify) === null || _g === void 0 ? void 0 : _g.shop));
1461
- if (shouldEnableIdentifierSync) {
1462
- this.syncIdentifiersToCart();
1463
- }
1464
1449
  }
1465
1450
  /**
1466
1451
  * Initialize CDP snippet and wait for it to be ready.
@@ -1,3 +1,2 @@
1
1
  export * from './client/index.js';
2
- export * from './client/identifiers.js';
3
2
  export * from './types/index.js';
package/dist/cjs/index.js CHANGED
@@ -15,5 +15,4 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
17
  __exportStar(require("./client/index.js"), exports);
18
- __exportStar(require("./client/identifiers.js"), exports);
19
18
  __exportStar(require("./types/index.js"), exports);
@@ -34,20 +34,7 @@ export interface ChordAnalyticsOptions<T extends ObjectTypes = ObjectTypes> {
34
34
  awaitConsent?: boolean;
35
35
  debug?: boolean;
36
36
  enableLogging?: boolean;
37
- /**
38
- * Controls the postMessage listener for events from the Shopify Custom Web
39
- * Pixel sandbox. Enabled by default in browser environments. The listener
40
- * only processes messages with source === 'chord-web-pixel', so it is safe
41
- * to leave enabled on non-Shopify sites. Set to false to explicitly disable.
42
- */
43
37
  enableWebPixelListener?: boolean;
44
- /**
45
- * Controls syncing CDP anonymous ID and Google Client ID to Shopify cart
46
- * attributes. When true, always enables sync. When false, always disables it.
47
- * When undefined (default), auto-detects by checking for window.Shopify.shop
48
- * which is present on Shopify Liquid storefronts.
49
- */
50
- enableIdentifierSync?: boolean;
51
38
  formatters: ChordAnalyticsFormatters<T>;
52
39
  namespace?: string;
53
40
  stripNull?: boolean;
@@ -32,14 +32,21 @@ export const addCdpSnippet = (options) => __awaiter(void 0, void 0, void 0, func
32
32
  };
33
33
  checkCdpReady();
34
34
  });
35
- const s = document.createElement('script');
36
- s.async = true;
37
- s.src = `${normalizedCDPDomain}/p.js`;
38
- s.setAttribute('data-debug', `${!!options.debug}`);
39
- s.setAttribute('data-init-only', 'true');
40
- s.setAttribute('data-namespace', `_${options.namespace}`);
41
- s.setAttribute('data-write-key', options.cdpWriteKey);
42
- document.head.appendChild(s);
35
+ // Guard against duplicate script injection (e.g. React StrictMode remounts)
36
+ const escapedNamespace = typeof CSS !== 'undefined' && typeof CSS.escape === 'function'
37
+ ? CSS.escape(`_${options.namespace}`)
38
+ : `_${options.namespace}`;
39
+ const existingScript = document.querySelector(`script[data-namespace="${escapedNamespace}"]`);
40
+ if (!existingScript) {
41
+ const s = document.createElement('script');
42
+ s.async = true;
43
+ s.src = `${normalizedCDPDomain}/p.js`;
44
+ s.setAttribute('data-debug', `${!!options.debug}`);
45
+ s.setAttribute('data-init-only', 'true');
46
+ s.setAttribute('data-namespace', `_${options.namespace}`);
47
+ s.setAttribute('data-write-key', options.cdpWriteKey);
48
+ document.head.appendChild(s);
49
+ }
43
50
  // Wait for CDP to be ready before setting up consent
44
51
  yield cdpReadyPromise;
45
52
  let initialConsent = null;
@@ -1,6 +1,5 @@
1
1
  import type { AnyAnalytics, EventProperties, Options, ValidateResult, ChordAnalyticsOptions, AnyOptions, CartViewedInput, IdentifyTraits, CheckoutStartedInput, CheckoutStepCompletedInput, CheckoutStepViewedInput, EmailCapturedInput, ProductListViewedInput, ProductListFilteredInput, ProductAddedInput, ProductClickedInput, VariantClickedInput, ProductRemovedInput, ProductViewedInput, ProductsSearchedInput, CouponAppliedInput, CouponDeniedInput, CouponEnteredInput, CouponRemovedInput, SignedInInput, SignedOutInput, SignedUpInput, LoginStartedInput, ObjectTypes, OrderCompletedInput, SubscriptionCancelledInput, NavigationClickedInput, PaymentInfoEnteredInput } from '../types/index.js';
2
2
  import { AnalyticsMiddleware } from '../types/client.js';
3
- import { type IdentifierSyncOptions, type ShopifyCart } from './identifiers.js';
4
3
  export interface ChordAnalyticsSnippet {
5
4
  _loadOptions: ChordAnalyticsOptions<ObjectTypes>;
6
5
  invoked: boolean;
@@ -170,18 +169,6 @@ export declare class ChordAnalytics<T extends ObjectTypes = ObjectTypes> {
170
169
  * @returns A cleanup function to remove the listener
171
170
  */
172
171
  setupWebPixelListener: () => (() => void);
173
- /**
174
- * Sync CDP anonymous ID and Google Client ID to Shopify cart attributes.
175
- * This enables server-side events (webhooks) to correlate with client-side tracking.
176
- *
177
- * By default, syncs:
178
- * - `_cdpAnonymousId`: from the `__eventn_id` cookie (Chord/Jitsu CDP)
179
- * - `_googleClientId`: from the `_ga` cookie (Google Analytics)
180
- *
181
- * @param options - Optional configuration for the sync
182
- * @returns The updated cart, or undefined if no update was needed or an error occurred
183
- */
184
- syncIdentifiersToCart: (options?: IdentifierSyncOptions) => Promise<ShopifyCart | undefined>;
185
172
  trackCartViewed: (props: CartViewedInput<T>, options?: AnyOptions) => Promise<void>;
186
173
  trackCheckoutStarted: (props: CheckoutStartedInput<T>, options?: AnyOptions) => Promise<void>;
187
174
  trackCheckoutStepCompleted: (props: CheckoutStepCompletedInput, options?: AnyOptions) => Promise<void>;