@chordcommerce/analytics 1.22.0-beta.3 → 1.22.1-beta.1

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,18 @@ 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 or from
828
+ // null-origin sandboxed iframes (Shopify web pixel sandbox has origin "null")
829
+ try {
830
+ if (event.origin && event.origin !== 'null') {
831
+ var eventHostname = new URL(event.origin).hostname;
832
+ if (eventHostname !== window.location.hostname)
833
+ return;
834
+ }
835
+ }
836
+ catch (_a) {
837
+ return;
838
+ }
828
839
  var message = event.data;
829
840
  if (_this.options.enableLogging && _this.options.debug) {
830
841
  // eslint-disable-next-line no-console
@@ -833,20 +844,26 @@ var ChordAnalytics = /** @class */ (function () {
833
844
  try {
834
845
  switch (message.type) {
835
846
  case 'track': {
836
- var _a = message.payload, eventName = _a[0], properties = _a[1];
847
+ var _b = message.payload, eventName = _b[0], properties = _b[1];
837
848
  if (eventName) {
838
- _this.track(eventName, properties);
849
+ Promise.resolve(_this.track(eventName, properties)).catch(function (err) {
850
+ return _this.logger('[Chord Analytics]: Error in web pixel track:', err);
851
+ });
839
852
  }
840
853
  break;
841
854
  }
842
855
  case 'identify': {
843
- var _b = message.payload, userId = _b[0], traits = _b[1];
844
- _this.identify(userId, traits);
856
+ var _c = message.payload, userId = _c[0], traits = _c[1];
857
+ Promise.resolve(_this.identify(userId, traits)).catch(function (err) {
858
+ return _this.logger('[Chord Analytics]: Error in web pixel identify:', err);
859
+ });
845
860
  break;
846
861
  }
847
862
  case 'page': {
848
863
  var properties = message.payload[0];
849
- _this.page(properties);
864
+ Promise.resolve(_this.page(undefined, properties)).catch(function (err) {
865
+ return _this.logger('[Chord Analytics]: Error in web pixel page:', err);
866
+ });
850
867
  break;
851
868
  }
852
869
  default:
@@ -868,31 +885,6 @@ var ChordAnalytics = /** @class */ (function () {
868
885
  }
869
886
  };
870
887
  };
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
888
  // TODO: Add support for props.products
897
889
  this.trackCartViewed = function (props, options) { return __awaiter(_this, void 0, void 0, function () {
898
890
  var cart, payload, formattedPayload;
@@ -1455,12 +1447,6 @@ var ChordAnalytics = /** @class */ (function () {
1455
1447
  if (shouldEnableWebPixel) {
1456
1448
  this.setupWebPixelListener();
1457
1449
  }
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
1450
  }
1465
1451
  /**
1466
1452
  * 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>;