@chordcommerce/analytics 1.14.1 → 1.16.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.
@@ -10,11 +10,14 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
10
10
  import { ketchConsentAdapter } from './ketch.js';
11
11
  import { onetrustConsentAdapter } from './onetrust.js';
12
12
  import { shopifyConsentAdapter } from './shopify.js';
13
+ import { segmentConsentAdapter } from './segment.js';
13
14
  export const addCdpConsent = (options) => {
14
15
  let adapter = null;
15
16
  let lastProcessedConsent = null;
16
17
  const POLLING_INTERVAL_MS = 50;
17
- const TIMEOUT_MS = 60000;
18
+ const TIMEOUT_MS = options.awaitConsentTimeout
19
+ ? options.awaitConsentTimeout
20
+ : 60000;
18
21
  /**
19
22
  * Returns the adapter for the specified consent management platform.
20
23
  */
@@ -27,6 +30,8 @@ export const addCdpConsent = (options) => {
27
30
  return onetrustConsentAdapter();
28
31
  case 'shopify':
29
32
  return shopifyConsentAdapter();
33
+ case 'segment':
34
+ return segmentConsentAdapter();
30
35
  default:
31
36
  return null;
32
37
  }
@@ -54,6 +59,35 @@ export const addCdpConsent = (options) => {
54
59
  }, TIMEOUT_MS);
55
60
  });
56
61
  });
62
+ /**
63
+ * Attempts to configure CDP with retry logic.
64
+ */
65
+ const configureCdpWithRetry = (updatedConsent, maxRetries = 5, baseDelay = 100) => __awaiter(void 0, void 0, void 0, function* () {
66
+ let attempt = 0;
67
+ const tryConfigure = () => __awaiter(void 0, void 0, void 0, function* () {
68
+ const cdp = window[`_${options.namespace}`];
69
+ if (cdp && typeof cdp.configure === 'function') {
70
+ try {
71
+ cdp.configure({ privacy: updatedConsent });
72
+ return true;
73
+ }
74
+ catch (error) {
75
+ // Continue to retry on error
76
+ }
77
+ }
78
+ if (attempt < maxRetries) {
79
+ const delay = baseDelay * Math.pow(2, attempt); // Exponential backoff
80
+ yield new Promise((resolve) => {
81
+ setTimeout(resolve, delay);
82
+ });
83
+ attempt++;
84
+ return tryConfigure();
85
+ }
86
+ console.warn('CDP configuration failed after all retries, falling back to jitsuConfig');
87
+ return false;
88
+ });
89
+ return tryConfigure();
90
+ });
57
91
  /**
58
92
  * Updates consent for all future events.
59
93
  */
@@ -64,7 +98,6 @@ export const addCdpConsent = (options) => {
64
98
  }
65
99
  lastProcessedConsent = updatedConsent;
66
100
  const chordAnalytics = window[options.namespace];
67
- const cdp = window[`_${options.namespace}`];
68
101
  const shouldTrackConsentUpdate = chordAnalytics &&
69
102
  typeof chordAnalytics.track === 'function' &&
70
103
  updatedConsent.consentCategories !== undefined;
@@ -73,11 +106,9 @@ export const addCdpConsent = (options) => {
73
106
  updatedConsent.dontSend === true) {
74
107
  yield chordAnalytics.track('Consent Preferences Updated', updatedConsent);
75
108
  }
76
- if (cdp && typeof cdp.configure === 'function') {
77
- cdp.configure({ privacy: updatedConsent });
78
- }
79
- else {
80
- // CDP has not yet loaded via the snippet.
109
+ const cdpConfigured = yield configureCdpWithRetry(updatedConsent);
110
+ if (!cdpConfigured) {
111
+ // CDP has not yet loaded via the snippet or all retries failed.
81
112
  window.jitsuConfig = { privacy: updatedConsent };
82
113
  }
83
114
  if (shouldTrackConsentUpdate &&
@@ -97,8 +128,8 @@ export const addCdpConsent = (options) => {
97
128
  updateCdpConsent(updatedConsent);
98
129
  });
99
130
  // Process initial consent - this will be skipped if the adapter already triggered an update
100
- const initialConsent = adapter.getCurrentConsent();
101
- updateCdpConsent(initialConsent);
131
+ const initialConsent = yield adapter.getCurrentConsent();
132
+ yield updateCdpConsent(initialConsent);
102
133
  });
103
134
  /**
104
135
  * Initializes consent management.
@@ -0,0 +1,17 @@
1
+ import type { ConsentConfiguration } from '../../types/index.js';
2
+ declare global {
3
+ interface Window {
4
+ segmentConsent?: SegmentConsentCategories;
5
+ }
6
+ }
7
+ interface SegmentConsentCategories {
8
+ [key: string]: {
9
+ [key: string]: boolean;
10
+ };
11
+ }
12
+ export declare const segmentConsentAdapter: () => {
13
+ getCurrentConsent: () => Promise<ConsentConfiguration>;
14
+ handleConsentUpdates: (updateCdpConsent: (consent: ConsentConfiguration) => void) => void;
15
+ userCanBeTracked: () => Promise<boolean>;
16
+ };
17
+ export {};
@@ -0,0 +1,74 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ const getApi = () => window.segmentConsent;
11
+ /**
12
+ * Waits for the Segment consent API to be populated with retry logic.
13
+ */
14
+ const waitForApi = (maxRetries = 10, baseDelay = 100) => __awaiter(void 0, void 0, void 0, function* () {
15
+ let attempt = 0;
16
+ const checkAndWait = () => __awaiter(void 0, void 0, void 0, function* () {
17
+ const api = getApi();
18
+ if (api &&
19
+ typeof api === 'object' &&
20
+ api.customPreferences &&
21
+ typeof api.customPreferences === 'object' &&
22
+ Object.keys(api.customPreferences).length > 0) {
23
+ return api;
24
+ }
25
+ if (attempt < maxRetries) {
26
+ const delay = baseDelay * Math.pow(2, attempt);
27
+ yield new Promise((resolve) => {
28
+ setTimeout(resolve, delay);
29
+ });
30
+ attempt++;
31
+ return checkAndWait();
32
+ }
33
+ return null;
34
+ });
35
+ return checkAndWait();
36
+ console.warn('Segment API failed to load after all retries');
37
+ return null;
38
+ });
39
+ /**
40
+ * Returns whether the user can currently be tracked.
41
+ */
42
+ const userCanBeTracked = () => __awaiter(void 0, void 0, void 0, function* () {
43
+ const api = yield waitForApi();
44
+ return (!!api &&
45
+ !!(api.customPreferences.functional &&
46
+ api.customPreferences.marketingAndAnalytics));
47
+ });
48
+ /**
49
+ * Returns the user's current consent configuration.
50
+ */
51
+ const getCurrentConsent = () => __awaiter(void 0, void 0, void 0, function* () {
52
+ const api = yield waitForApi();
53
+ if (!api)
54
+ return {};
55
+ return {
56
+ consentCategories: Object.assign(Object.assign({}, api.customPreferences), api.destinationPreferences),
57
+ dontSend: !(yield userCanBeTracked()),
58
+ };
59
+ });
60
+ /**
61
+ * Handles subsequent changes to consent configuration.
62
+ */
63
+ const handleConsentUpdates = (updateCdpConsent) => {
64
+ document.addEventListener('chord-cdp:consent-updated', () => __awaiter(void 0, void 0, void 0, function* () {
65
+ updateCdpConsent(yield getCurrentConsent());
66
+ }));
67
+ };
68
+ export const segmentConsentAdapter = () => {
69
+ return {
70
+ getCurrentConsent,
71
+ handleConsentUpdates,
72
+ userCanBeTracked,
73
+ };
74
+ };
@@ -30,7 +30,7 @@ export interface ChordAnalyticsOptions<T extends ObjectTypes = ObjectTypes> {
30
30
  cdp?: AnyAnalytics | (() => AnyAnalytics);
31
31
  cdpDomain?: string;
32
32
  cdpWriteKey?: string;
33
- consent?: 'ketch' | 'onetrust' | 'shopify';
33
+ consent?: 'ketch' | 'onetrust' | 'shopify' | 'segment';
34
34
  awaitConsent?: boolean;
35
35
  debug?: boolean;
36
36
  enableLogging?: boolean;
@@ -38,6 +38,7 @@ export interface ChordAnalyticsOptions<T extends ObjectTypes = ObjectTypes> {
38
38
  namespace?: string;
39
39
  stripNull?: boolean;
40
40
  metadata: EventMetadata;
41
+ awaitConsentTimeout?: number;
41
42
  }
42
43
  export interface EventMetadata {
43
44
  i18n: i18nMetadata;
@@ -1,7 +1,7 @@
1
1
  export interface ConsentAdapter {
2
- getCurrentConsent: () => ConsentConfiguration;
2
+ getCurrentConsent: () => ConsentConfiguration | Promise<ConsentConfiguration>;
3
3
  handleConsentUpdates: (fn: (ConsentConfiguration: any) => void) => void;
4
- userCanBeTracked: () => boolean;
4
+ userCanBeTracked: () => boolean | Promise<boolean>;
5
5
  }
6
6
  export interface ConsentConfiguration {
7
7
  consentCategories?: {
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.14.1",
4
+ "version": "1.16.0",
5
5
  "author": "Chord Commerce <help@chord.co> (https://www.chord.co)",
6
6
  "type": "module",
7
7
  "main": "dist/index.js",