@chordcommerce/analytics 1.16.0 → 1.17.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.
@@ -16,6 +16,19 @@ export const addCdpSnippet = (options) => __awaiter(void 0, void 0, void 0, func
16
16
  if (!document || typeof document.createElement === 'undefined')
17
17
  return;
18
18
  const normalizedCDPDomain = normalizeCDPDomain(options.cdpDomain);
19
+ // Create a promise that resolves when CDP is ready
20
+ const cdpReadyPromise = new Promise((resolve) => {
21
+ const checkCdpReady = () => {
22
+ const cdp = window[`_${options.namespace}`];
23
+ if (cdp && typeof cdp.configure === 'function') {
24
+ resolve();
25
+ }
26
+ else {
27
+ setTimeout(checkCdpReady, 50);
28
+ }
29
+ };
30
+ checkCdpReady();
31
+ });
19
32
  const s = document.createElement('script');
20
33
  s.async = true;
21
34
  s.src = `${normalizedCDPDomain}/p.js`;
@@ -23,20 +36,11 @@ export const addCdpSnippet = (options) => __awaiter(void 0, void 0, void 0, func
23
36
  s.setAttribute('data-init-only', 'true');
24
37
  s.setAttribute('data-namespace', `_${options.namespace}`);
25
38
  s.setAttribute('data-write-key', options.cdpWriteKey);
39
+ document.head.appendChild(s);
40
+ // Wait for CDP to be ready before setting up consent
41
+ yield cdpReadyPromise;
26
42
  if (options.consent) {
27
- // If awaitConsent is true, wait for consent before loading CDP
28
- if (options.awaitConsent) {
29
- yield addCdpConsent(options).init();
30
- document.head.appendChild(s);
31
- }
32
- else {
33
- document.head.appendChild(s);
34
- // If not awaiting consent, initialize in the background
35
- addCdpConsent(options).init().catch(console.error);
36
- }
37
- }
38
- else {
39
- document.head.appendChild(s);
43
+ addCdpConsent(options).init();
40
44
  }
41
45
  });
42
46
  /*
@@ -68,7 +68,9 @@ export const addCdpConsent = (options) => {
68
68
  const cdp = window[`_${options.namespace}`];
69
69
  if (cdp && typeof cdp.configure === 'function') {
70
70
  try {
71
- cdp.configure({ privacy: updatedConsent });
71
+ if (Object.keys(updatedConsent).length > 0) {
72
+ cdp.configure({ privacy: updatedConsent });
73
+ }
72
74
  return true;
73
75
  }
74
76
  catch (error) {
@@ -1,4 +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, SubscriptionCancelledInput, NavigationClickedInput, PaymentInfoEnteredInput } from '../types/index.js';
2
+ import { AnalyticsMiddleware } from '../types/client.js';
2
3
  export interface ChordAnalyticsSnippet {
3
4
  _loadOptions: ChordAnalyticsOptions<ObjectTypes>;
4
5
  invoked: boolean;
@@ -21,7 +22,19 @@ export declare class ChordAnalytics<T extends ObjectTypes = ObjectTypes> {
21
22
  * Options for ChordAnalytics.
22
23
  */
23
24
  options: ChordAnalyticsOptions<T>;
25
+ /**
26
+ * Middleware to be applied to the event properties
27
+ */
28
+ middleware: any[];
29
+ /**
30
+ * Promise that resolves when CDP is ready
31
+ */
32
+ private cdpReadyPromise;
24
33
  constructor(options: ChordAnalyticsOptions<T>);
34
+ /**
35
+ * Initialize CDP snippet and wait for it to be ready
36
+ */
37
+ private initializeCdp;
25
38
  /**
26
39
  * Set debug mode for Chord Analytics
27
40
  * @param enabled - Whether debug mode should be enabled
@@ -52,6 +65,7 @@ export declare class ChordAnalytics<T extends ObjectTypes = ObjectTypes> {
52
65
  platform: import("../types/client.js").platformMetadata;
53
66
  store: import("../types/client.js").storeMetadata;
54
67
  };
68
+ addSourceMiddleware: (func: AnalyticsMiddleware) => void;
55
69
  /**
56
70
  * Send a `track` event to the CDP with any event name and properties.
57
71
  */
@@ -22,6 +22,14 @@ export class ChordAnalytics {
22
22
  * Allows snippet.js to detect whether the snippet has started running yet.
23
23
  */
24
24
  this.invoked = true;
25
+ /**
26
+ * Middleware to be applied to the event properties
27
+ */
28
+ this.middleware = [];
29
+ /**
30
+ * Promise that resolves when CDP is ready
31
+ */
32
+ this.cdpReadyPromise = null;
25
33
  /**
26
34
  * Set debug mode for Chord Analytics
27
35
  * @param enabled - Whether debug mode should be enabled
@@ -89,14 +97,54 @@ export class ChordAnalytics {
89
97
  patch: 0,
90
98
  } });
91
99
  };
100
+ // eslint-disable-next-line class-methods-use-this
101
+ this.addSourceMiddleware = (func) => {
102
+ this.middleware.push(func);
103
+ };
92
104
  /**
93
105
  * Send a `track` event to the CDP with any event name and properties.
94
106
  */
95
- this.track = (event, props, options) => {
107
+ this.track = (event, props, options) => __awaiter(this, void 0, void 0, function* () {
96
108
  if (!event)
97
109
  this.logger('No event name provided');
110
+ // Wait for CDP to be ready before sending track events
111
+ if (this.cdpReadyPromise) {
112
+ yield this.cdpReadyPromise;
113
+ }
98
114
  const formattedProps = this.options.stripNull && props ? pruneNullValues(props) : props;
99
- const finalEventProps = Object.assign(Object.assign({}, formattedProps), { meta: this.meta() });
115
+ let middlewareProps = {};
116
+ if (this.middleware.length > 0) {
117
+ const searchParams = typeof window !== 'undefined' ? window.location.search : '';
118
+ const urlSearchParams = new URLSearchParams(searchParams);
119
+ const searchData = Object.fromEntries(urlSearchParams.entries());
120
+ // process middleware synchronously
121
+ this.middleware.forEach((func) => {
122
+ try {
123
+ const result = func({
124
+ payload: {
125
+ obj: {
126
+ properties: props,
127
+ event,
128
+ context: {
129
+ page: {
130
+ search: searchParams,
131
+ searchParams: searchData,
132
+ },
133
+ },
134
+ },
135
+ },
136
+ next: (_) => _,
137
+ });
138
+ if (result && typeof result === 'object') {
139
+ middlewareProps = Object.assign(Object.assign({}, middlewareProps), result);
140
+ }
141
+ }
142
+ catch (error) {
143
+ console.warn('Middleware error:', error);
144
+ }
145
+ });
146
+ }
147
+ const finalEventProps = Object.assign(Object.assign(Object.assign({}, formattedProps), middlewareProps), { meta: this.meta() });
100
148
  if (this.options.debug) {
101
149
  this.validate(event, finalEventProps).forEach((valid) => {
102
150
  if (!valid.success) {
@@ -136,11 +184,15 @@ export class ChordAnalytics {
136
184
  resolve();
137
185
  }
138
186
  });
139
- };
187
+ });
140
188
  /**
141
189
  * Send an `identify` event to the CDP with user id and traits.
142
190
  */
143
- this.identify = (...props) => {
191
+ this.identify = (...props) => __awaiter(this, void 0, void 0, function* () {
192
+ // Wait for CDP to be ready before sending identify events
193
+ if (this.cdpReadyPromise) {
194
+ yield this.cdpReadyPromise;
195
+ }
144
196
  try {
145
197
  const identifyPromises = [];
146
198
  const a = this.ccdp();
@@ -163,11 +215,15 @@ export class ChordAnalytics {
163
215
  catch (error) {
164
216
  this.logger(error);
165
217
  }
166
- };
218
+ });
167
219
  /**
168
220
  * Send a `page` event to the CDP.
169
221
  */
170
- this.page = (props = {}) => {
222
+ this.page = (props = {}) => __awaiter(this, void 0, void 0, function* () {
223
+ // Wait for CDP to be ready before sending page events
224
+ if (this.cdpReadyPromise) {
225
+ yield this.cdpReadyPromise;
226
+ }
171
227
  try {
172
228
  const pagePromises = [];
173
229
  const a = this.ccdp();
@@ -190,7 +246,7 @@ export class ChordAnalytics {
190
246
  catch (error) {
191
247
  this.logger(error);
192
248
  }
193
- };
249
+ });
194
250
  this.reset = () => {
195
251
  try {
196
252
  const resetPromises = [];
@@ -609,12 +665,25 @@ export class ChordAnalytics {
609
665
  enableLogging: debug,
610
666
  namespace: 'chord',
611
667
  stripNull: true,
612
- awaitConsent: true,
613
668
  };
614
669
  this.options = Object.assign(Object.assign({}, defaults), options);
615
670
  if (((_b = this.options) === null || _b === void 0 ? void 0 : _b.cdpDomain) && ((_c = this.options) === null || _c === void 0 ? void 0 : _c.cdpWriteKey) && window) {
616
671
  window[this.options.namespace] = (window === null || window === void 0 ? void 0 : window[this.options.namespace]) || this;
617
- addCdpSnippet(this.options);
672
+ this.initializeCdp();
618
673
  }
619
674
  }
675
+ /**
676
+ * Initialize CDP snippet and wait for it to be ready
677
+ */
678
+ initializeCdp() {
679
+ return __awaiter(this, void 0, void 0, function* () {
680
+ try {
681
+ this.cdpReadyPromise = addCdpSnippet(this.options);
682
+ yield this.cdpReadyPromise;
683
+ }
684
+ catch (error) {
685
+ console.error('CDP initialization failed:', error);
686
+ }
687
+ });
688
+ }
620
689
  }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,4 @@
1
+ // eslint-disable-next-line import/no-extraneous-dependencies
2
+ import { jest } from '@jest/globals';
3
+ // Make jest available globally
4
+ globalThis.jest = jest;
@@ -1,4 +1,4 @@
1
- import type { AnalyticsSnippet, Analytics, AnalyticsBrowser, Options } from '@segment/analytics-next';
1
+ import type { EventProperties, AnalyticsSnippet, Analytics, AnalyticsBrowser, Options } from '@segment/analytics-next';
2
2
  import { ChordAnalyticsFormatters } from './formatters.js';
3
3
  export interface ValidateResult {
4
4
  success: boolean;
@@ -39,6 +39,7 @@ export interface ChordAnalyticsOptions<T extends ObjectTypes = ObjectTypes> {
39
39
  stripNull?: boolean;
40
40
  metadata: EventMetadata;
41
41
  awaitConsentTimeout?: number;
42
+ middleware?: Array<() => void>;
42
43
  }
43
44
  export interface EventMetadata {
44
45
  i18n: i18nMetadata;
@@ -74,3 +75,22 @@ export type ChordAnalyticsQueue = {
74
75
  page: (...args: any[]) => void;
75
76
  reset: (...args: any[]) => void;
76
77
  };
78
+ type PageContext = {
79
+ search: string;
80
+ searchParams: Record<string, string>;
81
+ };
82
+ type MiddlewarePayload = {
83
+ obj: {
84
+ properties?: EventProperties;
85
+ event: string;
86
+ context: {
87
+ page: PageContext;
88
+ };
89
+ };
90
+ };
91
+ type MiddlewareArgs = {
92
+ payload: MiddlewarePayload;
93
+ next: <T>(arg: T) => T;
94
+ };
95
+ export type AnalyticsMiddleware = (args: MiddlewareArgs) => Partial<EventProperties> | void;
96
+ export {};
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@chordcommerce/analytics",
3
3
  "description": "Chord Commerce event tracking",
4
- "version": "1.16.0",
4
+ "version": "1.17.0",
5
5
  "author": "Chord Commerce <help@chord.co> (https://www.chord.co)",
6
6
  "type": "module",
7
7
  "main": "dist/index.js",
@@ -18,6 +18,7 @@
18
18
  "clean": "rimraf dist",
19
19
  "test": "jest",
20
20
  "test:watch": "jest --watch",
21
+ "test:file": "NODE_OPTIONS=--experimental-vm-modules jest",
21
22
  "test:ci": "yarn test --ci --runInBand --reporters=default --passWithNoTests",
22
23
  "zod": "yarn ts-to-zod src/types/typewriter.ts src/validators/tracking-plan.ts && yarn ts-to-zod src/types/objects.ts src/validators/objects.ts"
23
24
  },