@chordcommerce/analytics 1.18.2 → 1.19.0-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.
- package/dist/cjs/client/cdp.d.ts +5 -2
- package/dist/cjs/client/cdp.js +22 -6
- package/dist/cjs/client/consent/index.d.ts +6 -8
- package/dist/cjs/client/consent/index.js +62 -89
- package/dist/cjs/client/consent/ketch.d.ts +2 -0
- package/dist/cjs/client/consent/ketch.js +5 -1
- package/dist/cjs/client/consent/onetrust-api.d.ts +103 -0
- package/dist/cjs/client/consent/onetrust-api.js +128 -0
- package/dist/cjs/client/consent/onetrust.d.ts +4 -16
- package/dist/cjs/client/consent/onetrust.js +25 -34
- package/dist/cjs/client/consent/segment.d.ts +2 -0
- package/dist/cjs/client/consent/segment.js +16 -10
- package/dist/cjs/client/consent/shopify.d.ts +2 -0
- package/dist/cjs/client/consent/shopify.js +12 -1
- package/dist/cjs/client/index.d.ts +54 -2
- package/dist/cjs/client/index.js +375 -81
- package/dist/cjs/types/consent.d.ts +13 -2
- package/dist/client/cdp.d.ts +5 -2
- package/dist/client/cdp.js +17 -3
- package/dist/client/consent/index.d.ts +6 -8
- package/dist/client/consent/index.js +56 -64
- package/dist/client/consent/ketch.d.ts +2 -0
- package/dist/client/consent/ketch.js +5 -1
- package/dist/client/consent/onetrust-api.d.ts +103 -0
- package/dist/client/consent/onetrust-api.js +119 -0
- package/dist/client/consent/onetrust.d.ts +4 -16
- package/dist/client/consent/onetrust.js +25 -33
- package/dist/client/consent/segment.d.ts +2 -0
- package/dist/client/consent/segment.js +10 -3
- package/dist/client/consent/shopify.d.ts +2 -0
- package/dist/client/consent/shopify.js +12 -1
- package/dist/client/index.d.ts +54 -2
- package/dist/client/index.js +244 -10
- package/dist/types/consent.d.ts +13 -2
- package/package.json +1 -1
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
import type { ChordAnalyticsOptions, ConsentConfiguration } from '../../types/index.js';
|
|
2
|
-
declare global {
|
|
3
|
-
interface Window {
|
|
4
|
-
jitsuConfig: {
|
|
5
|
-
privacy: ConsentConfiguration;
|
|
6
|
-
};
|
|
7
|
-
}
|
|
8
|
-
}
|
|
9
2
|
export declare const addCdpConsent: (options: ChordAnalyticsOptions) => {
|
|
10
|
-
init: () => Promise<
|
|
3
|
+
init: () => Promise<ConsentConfiguration | null>;
|
|
4
|
+
getConsentConfig: () => {
|
|
5
|
+
getCategories: () => Record<string, boolean>;
|
|
6
|
+
isConsentReady: () => boolean;
|
|
7
|
+
debug: boolean;
|
|
8
|
+
};
|
|
11
9
|
};
|
|
@@ -12,7 +12,6 @@ import { onetrustConsentAdapter } from './onetrust.js';
|
|
|
12
12
|
import { shopifyConsentAdapter } from './shopify.js';
|
|
13
13
|
import { segmentConsentAdapter } from './segment.js';
|
|
14
14
|
export const addCdpConsent = (options) => {
|
|
15
|
-
let adapter = null;
|
|
16
15
|
let lastProcessedConsent = null;
|
|
17
16
|
const POLLING_INTERVAL_MS = 50;
|
|
18
17
|
const TIMEOUT_MS = options.awaitConsentTimeout
|
|
@@ -36,6 +35,8 @@ export const addCdpConsent = (options) => {
|
|
|
36
35
|
return null;
|
|
37
36
|
}
|
|
38
37
|
};
|
|
38
|
+
// Initialize adapter immediately so getConsentConfig() works
|
|
39
|
+
const adapter = getAdapter();
|
|
39
40
|
/**
|
|
40
41
|
* Polls if user can be tracked based on consent
|
|
41
42
|
* @returns Promise that resolves to true if condition is met within timeout, false otherwise
|
|
@@ -60,101 +61,92 @@ export const addCdpConsent = (options) => {
|
|
|
60
61
|
});
|
|
61
62
|
});
|
|
62
63
|
/**
|
|
63
|
-
*
|
|
64
|
+
* Tracks consent preference changes.
|
|
65
|
+
* Note: Per-event consent stamping is handled in ChordAnalytics via stampConsent().
|
|
64
66
|
*/
|
|
65
|
-
const
|
|
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
|
-
if (Object.keys(updatedConsent).length > 0) {
|
|
72
|
-
cdp.configure({ privacy: updatedConsent });
|
|
73
|
-
}
|
|
74
|
-
return true;
|
|
75
|
-
}
|
|
76
|
-
catch (error) {
|
|
77
|
-
// Continue to retry on error
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
if (attempt < maxRetries) {
|
|
81
|
-
const delay = baseDelay * Math.pow(2, attempt); // Exponential backoff
|
|
82
|
-
yield new Promise((resolve) => {
|
|
83
|
-
setTimeout(resolve, delay);
|
|
84
|
-
});
|
|
85
|
-
attempt++;
|
|
86
|
-
return tryConfigure();
|
|
87
|
-
}
|
|
88
|
-
console.warn('CDP configuration failed after all retries, falling back to jitsuConfig');
|
|
89
|
-
return false;
|
|
90
|
-
});
|
|
91
|
-
return tryConfigure();
|
|
92
|
-
});
|
|
93
|
-
/**
|
|
94
|
-
* Updates consent for all future events.
|
|
95
|
-
*/
|
|
96
|
-
const updateCdpConsent = (updatedConsent) => __awaiter(void 0, void 0, void 0, function* () {
|
|
67
|
+
const trackConsentUpdate = (updatedConsent) => __awaiter(void 0, void 0, void 0, function* () {
|
|
97
68
|
if (lastProcessedConsent &&
|
|
98
69
|
JSON.stringify(lastProcessedConsent) === JSON.stringify(updatedConsent)) {
|
|
99
70
|
return;
|
|
100
71
|
}
|
|
101
72
|
lastProcessedConsent = updatedConsent;
|
|
102
73
|
const chordAnalytics = window[options.namespace];
|
|
103
|
-
|
|
74
|
+
if (chordAnalytics &&
|
|
104
75
|
typeof chordAnalytics.track === 'function' &&
|
|
105
|
-
updatedConsent.consentCategories !== undefined
|
|
106
|
-
if (shouldTrackConsentUpdate &&
|
|
107
|
-
// we have to send this BEFORE the CDP is reconfigured or else the track will not be sent, usually in the case of a user opting out
|
|
108
|
-
updatedConsent.dontSend === true) {
|
|
109
|
-
yield chordAnalytics.track('Consent Preferences Updated', updatedConsent);
|
|
110
|
-
}
|
|
111
|
-
const cdpConfigured = yield configureCdpWithRetry(updatedConsent);
|
|
112
|
-
if (!cdpConfigured) {
|
|
113
|
-
// CDP has not yet loaded via the snippet or all retries failed.
|
|
114
|
-
window.jitsuConfig = { privacy: updatedConsent };
|
|
115
|
-
}
|
|
116
|
-
if (shouldTrackConsentUpdate &&
|
|
117
|
-
// we have to send this AFTER the CDP is reconfigured to ensure the track is sent, usually in the case of a user opting in
|
|
118
|
-
updatedConsent.dontSend === false) {
|
|
76
|
+
updatedConsent.consentCategories !== undefined) {
|
|
119
77
|
yield chordAnalytics.track('Consent Preferences Updated', updatedConsent);
|
|
78
|
+
if (options.enableLogging && options.debug) {
|
|
79
|
+
console.log('[Chord Analytics DEBUG]: Consent preferences updated, sent tracking event');
|
|
80
|
+
}
|
|
120
81
|
}
|
|
121
82
|
});
|
|
122
83
|
/**
|
|
123
84
|
* Sets up consent management by initializing the adapter and handling consent updates.
|
|
85
|
+
* Returns the initial consent so it can be tracked after CDP is fully ready (avoiding circular await).
|
|
124
86
|
*/
|
|
125
87
|
const setupConsentManagement = () => __awaiter(void 0, void 0, void 0, function* () {
|
|
126
88
|
if (!adapter)
|
|
127
|
-
return;
|
|
89
|
+
return null;
|
|
128
90
|
// Set up the consent update handler first to catch any immediate updates
|
|
129
91
|
adapter.handleConsentUpdates((updatedConsent) => {
|
|
130
|
-
|
|
92
|
+
trackConsentUpdate(updatedConsent);
|
|
131
93
|
});
|
|
132
|
-
//
|
|
94
|
+
// Return initial consent to be tracked after CDP is fully initialized
|
|
133
95
|
const initialConsent = yield adapter.getCurrentConsent();
|
|
134
|
-
|
|
96
|
+
lastProcessedConsent = initialConsent;
|
|
97
|
+
return initialConsent;
|
|
135
98
|
});
|
|
136
99
|
/**
|
|
137
|
-
* Initializes consent management.
|
|
138
|
-
* Returns
|
|
100
|
+
* Initializes consent management and sets up consent update handlers.
|
|
101
|
+
* Returns initial consent configuration to be tracked after CDP is fully ready,
|
|
102
|
+
* or null if consent setup failed or was blocked.
|
|
139
103
|
*/
|
|
140
104
|
const init = () => __awaiter(void 0, void 0, void 0, function* () {
|
|
141
|
-
|
|
105
|
+
if (options.debug && options.enableLogging) {
|
|
106
|
+
console.log('[Chord Analytics DEBUG]: consent init() called, adapter:', adapter ? options.consent : 'null');
|
|
107
|
+
}
|
|
142
108
|
if (adapter === null) {
|
|
143
109
|
console.error(`Consent management adapter "${options.consent}" is not supported.`);
|
|
144
|
-
return
|
|
110
|
+
return null;
|
|
145
111
|
}
|
|
146
112
|
if (options.awaitConsent) {
|
|
113
|
+
if (options.debug && options.enableLogging) {
|
|
114
|
+
console.log('[Chord Analytics DEBUG]: awaitConsent is true, checking if user can be tracked');
|
|
115
|
+
}
|
|
147
116
|
const canBeTracked = yield checkUserCanBeTracked();
|
|
148
|
-
if (!canBeTracked)
|
|
149
|
-
|
|
117
|
+
if (!canBeTracked) {
|
|
118
|
+
if (options.debug && options.enableLogging) {
|
|
119
|
+
console.log('[Chord Analytics DEBUG]: User cannot be tracked, returning null');
|
|
120
|
+
}
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
try {
|
|
125
|
+
const result = yield setupConsentManagement();
|
|
126
|
+
if (options.debug && options.enableLogging) {
|
|
127
|
+
console.log('[Chord Analytics DEBUG]: setupConsentManagement() returned:', JSON.stringify(result));
|
|
128
|
+
}
|
|
129
|
+
return result;
|
|
130
|
+
}
|
|
131
|
+
catch (error) {
|
|
132
|
+
if (options.debug && options.enableLogging) {
|
|
133
|
+
console.error('[Chord Analytics DEBUG]: Error setting up consent management:', error);
|
|
134
|
+
}
|
|
135
|
+
return null;
|
|
150
136
|
}
|
|
151
|
-
// Set up consent management in the background
|
|
152
|
-
setupConsentManagement().catch((error) => {
|
|
153
|
-
console.error('Error setting up consent management:', error);
|
|
154
|
-
});
|
|
155
|
-
return true;
|
|
156
137
|
});
|
|
138
|
+
const getConsentConfig = () => {
|
|
139
|
+
if (!(adapter === null || adapter === void 0 ? void 0 : adapter.getCategories) || !(adapter === null || adapter === void 0 ? void 0 : adapter.isConsentReady)) {
|
|
140
|
+
return undefined;
|
|
141
|
+
}
|
|
142
|
+
return {
|
|
143
|
+
getCategories: () => adapter.getCategories(),
|
|
144
|
+
isConsentReady: () => adapter.isConsentReady(),
|
|
145
|
+
debug: options.debug && options.enableLogging,
|
|
146
|
+
};
|
|
147
|
+
};
|
|
157
148
|
return {
|
|
158
149
|
init,
|
|
150
|
+
getConsentConfig,
|
|
159
151
|
};
|
|
160
152
|
};
|
|
@@ -12,5 +12,7 @@ export declare const ketchConsentAdapter: () => {
|
|
|
12
12
|
getCurrentConsent: () => ConsentConfiguration;
|
|
13
13
|
handleConsentUpdates: (updateCdpConsent: (consent: ConsentConfiguration) => void) => void;
|
|
14
14
|
userCanBeTracked: () => boolean;
|
|
15
|
+
getCategories: () => Record<string, boolean>;
|
|
16
|
+
isConsentReady: () => boolean;
|
|
15
17
|
};
|
|
16
18
|
export {};
|
|
@@ -18,7 +18,6 @@ const getCurrentConsent = () => {
|
|
|
18
18
|
return {};
|
|
19
19
|
return {
|
|
20
20
|
consentCategories: Object.assign({}, api),
|
|
21
|
-
dontSend: !userCanBeTracked(),
|
|
22
21
|
};
|
|
23
22
|
};
|
|
24
23
|
/**
|
|
@@ -34,5 +33,10 @@ export const ketchConsentAdapter = () => {
|
|
|
34
33
|
getCurrentConsent,
|
|
35
34
|
handleConsentUpdates,
|
|
36
35
|
userCanBeTracked,
|
|
36
|
+
getCategories: () => {
|
|
37
|
+
const api = getApi();
|
|
38
|
+
return api ? Object.assign({}, api) : {};
|
|
39
|
+
},
|
|
40
|
+
isConsentReady: () => !!getApi(),
|
|
37
41
|
};
|
|
38
42
|
};
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OneTrust API Utilities
|
|
3
|
+
*
|
|
4
|
+
* Provides type-safe access to OneTrust's global API with proper duck typing
|
|
5
|
+
* to ensure the API is fully loaded before use.
|
|
6
|
+
*
|
|
7
|
+
* Modeled after analytics-next's onetrust-api.ts
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* OneTrust consent model types
|
|
11
|
+
*/
|
|
12
|
+
export declare enum OtConsentModel {
|
|
13
|
+
optIn = "opt-in",
|
|
14
|
+
optOut = "opt-out",
|
|
15
|
+
implied = "implied consent"
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* OneTrust domain data structure
|
|
19
|
+
*/
|
|
20
|
+
export interface OneTrustDomainData {
|
|
21
|
+
ShowAlertNotice: boolean;
|
|
22
|
+
Groups: OneTrustGroup[];
|
|
23
|
+
ConsentModel: {
|
|
24
|
+
Name: OtConsentModel | string;
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
export interface OneTrustGroup {
|
|
28
|
+
CustomGroupId: string;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* OneTrust consent changed event
|
|
32
|
+
*/
|
|
33
|
+
type OtConsentChangedEvent = CustomEvent<string[]>;
|
|
34
|
+
/**
|
|
35
|
+
* OneTrust global API interface
|
|
36
|
+
*/
|
|
37
|
+
export interface OneTrustGlobal {
|
|
38
|
+
GetDomainData: () => OneTrustDomainData;
|
|
39
|
+
OnConsentChanged: (cb: (event: OtConsentChangedEvent) => void) => void;
|
|
40
|
+
IsAlertBoxClosed: () => boolean;
|
|
41
|
+
}
|
|
42
|
+
declare global {
|
|
43
|
+
interface Window {
|
|
44
|
+
OneTrust?: OneTrustGlobal | Record<string, unknown>;
|
|
45
|
+
OnetrustActiveGroups?: string;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Get the OneTrust global API if it's ready.
|
|
50
|
+
*
|
|
51
|
+
* Uses duck typing to check if OneTrust is fully loaded.
|
|
52
|
+
* window.OneTrust may exist but not be ready (e.g., just has geolocationResponse).
|
|
53
|
+
*
|
|
54
|
+
* @returns OneTrustGlobal if ready, undefined otherwise
|
|
55
|
+
*/
|
|
56
|
+
export declare const getOneTrustGlobal: () => OneTrustGlobal | undefined;
|
|
57
|
+
/**
|
|
58
|
+
* Get the raw OnetrustActiveGroups string.
|
|
59
|
+
*
|
|
60
|
+
* @returns Comma-separated string of active group IDs, or undefined
|
|
61
|
+
*/
|
|
62
|
+
export declare const getOneTrustActiveGroups: () => string | undefined;
|
|
63
|
+
/**
|
|
64
|
+
* Get normalized active group IDs as an array.
|
|
65
|
+
* Reads FRESH from window.OnetrustActiveGroups each time - no caching!
|
|
66
|
+
*
|
|
67
|
+
* @returns Array of active group IDs
|
|
68
|
+
*
|
|
69
|
+
* @example
|
|
70
|
+
* // window.OnetrustActiveGroups = ",C0001,C0003,"
|
|
71
|
+
* getNormalizedActiveGroupIds() // ["C0001", "C0003"]
|
|
72
|
+
*/
|
|
73
|
+
export declare const getNormalizedActiveGroupIds: () => string[];
|
|
74
|
+
/**
|
|
75
|
+
* Get all configured consent groups from OneTrust.
|
|
76
|
+
*
|
|
77
|
+
* @returns Array of group info objects
|
|
78
|
+
*/
|
|
79
|
+
export declare const getAllGroups: () => {
|
|
80
|
+
groupId: string;
|
|
81
|
+
}[];
|
|
82
|
+
/**
|
|
83
|
+
* Get normalized consent categories.
|
|
84
|
+
* Reads FRESH from OneTrust globals each time - NO CACHING!
|
|
85
|
+
*
|
|
86
|
+
* This ensures if a user changes consent between two track() calls,
|
|
87
|
+
* each event reflects the consent state AT THAT MOMENT.
|
|
88
|
+
*
|
|
89
|
+
* @returns Record of category ID to consent boolean
|
|
90
|
+
*
|
|
91
|
+
* @example
|
|
92
|
+
* getNormalizedCategories()
|
|
93
|
+
* // { "C0001": true, "C0002": false, "C0003": true }
|
|
94
|
+
*/
|
|
95
|
+
export declare const getNormalizedCategories: () => Record<string, boolean>;
|
|
96
|
+
/**
|
|
97
|
+
* Coerce OneTrust consent model name to our simplified model.
|
|
98
|
+
*
|
|
99
|
+
* @param model - OneTrust consent model name
|
|
100
|
+
* @returns 'opt-in' or 'opt-out'
|
|
101
|
+
*/
|
|
102
|
+
export declare const coerceConsentModel: (model: OtConsentModel | string) => 'opt-in' | 'opt-out';
|
|
103
|
+
export {};
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OneTrust API Utilities
|
|
3
|
+
*
|
|
4
|
+
* Provides type-safe access to OneTrust's global API with proper duck typing
|
|
5
|
+
* to ensure the API is fully loaded before use.
|
|
6
|
+
*
|
|
7
|
+
* Modeled after analytics-next's onetrust-api.ts
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* OneTrust consent model types
|
|
11
|
+
*/
|
|
12
|
+
// eslint-disable-next-line no-shadow
|
|
13
|
+
export var OtConsentModel;
|
|
14
|
+
(function (OtConsentModel) {
|
|
15
|
+
OtConsentModel["optIn"] = "opt-in";
|
|
16
|
+
OtConsentModel["optOut"] = "opt-out";
|
|
17
|
+
OtConsentModel["implied"] = "implied consent";
|
|
18
|
+
})(OtConsentModel || (OtConsentModel = {}));
|
|
19
|
+
/**
|
|
20
|
+
* Get the OneTrust global API if it's ready.
|
|
21
|
+
*
|
|
22
|
+
* Uses duck typing to check if OneTrust is fully loaded.
|
|
23
|
+
* window.OneTrust may exist but not be ready (e.g., just has geolocationResponse).
|
|
24
|
+
*
|
|
25
|
+
* @returns OneTrustGlobal if ready, undefined otherwise
|
|
26
|
+
*/
|
|
27
|
+
export const getOneTrustGlobal = () => {
|
|
28
|
+
const oneTrust = window.OneTrust;
|
|
29
|
+
if (!oneTrust)
|
|
30
|
+
return undefined;
|
|
31
|
+
// Duck typing - check for required methods to ensure OneTrust is fully loaded
|
|
32
|
+
if (typeof oneTrust === 'object' &&
|
|
33
|
+
'OnConsentChanged' in oneTrust &&
|
|
34
|
+
'IsAlertBoxClosed' in oneTrust &&
|
|
35
|
+
'GetDomainData' in oneTrust) {
|
|
36
|
+
return oneTrust;
|
|
37
|
+
}
|
|
38
|
+
return undefined;
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* Get the raw OnetrustActiveGroups string.
|
|
42
|
+
*
|
|
43
|
+
* @returns Comma-separated string of active group IDs, or undefined
|
|
44
|
+
*/
|
|
45
|
+
export const getOneTrustActiveGroups = () => {
|
|
46
|
+
const groups = window.OnetrustActiveGroups;
|
|
47
|
+
if (!groups || typeof groups !== 'string')
|
|
48
|
+
return undefined;
|
|
49
|
+
return groups;
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* Get normalized active group IDs as an array.
|
|
53
|
+
* Reads FRESH from window.OnetrustActiveGroups each time - no caching!
|
|
54
|
+
*
|
|
55
|
+
* @returns Array of active group IDs
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* // window.OnetrustActiveGroups = ",C0001,C0003,"
|
|
59
|
+
* getNormalizedActiveGroupIds() // ["C0001", "C0003"]
|
|
60
|
+
*/
|
|
61
|
+
export const getNormalizedActiveGroupIds = () => {
|
|
62
|
+
const groups = getOneTrustActiveGroups();
|
|
63
|
+
if (!groups)
|
|
64
|
+
return [];
|
|
65
|
+
return groups.trim().split(',').filter(Boolean);
|
|
66
|
+
};
|
|
67
|
+
/**
|
|
68
|
+
* Get all configured consent groups from OneTrust.
|
|
69
|
+
*
|
|
70
|
+
* @returns Array of group info objects
|
|
71
|
+
*/
|
|
72
|
+
export const getAllGroups = () => {
|
|
73
|
+
const oneTrust = getOneTrustGlobal();
|
|
74
|
+
if (!oneTrust)
|
|
75
|
+
return [];
|
|
76
|
+
return oneTrust.GetDomainData().Groups.map((group) => ({
|
|
77
|
+
groupId: group.CustomGroupId.trim(),
|
|
78
|
+
}));
|
|
79
|
+
};
|
|
80
|
+
/**
|
|
81
|
+
* Get normalized consent categories.
|
|
82
|
+
* Reads FRESH from OneTrust globals each time - NO CACHING!
|
|
83
|
+
*
|
|
84
|
+
* This ensures if a user changes consent between two track() calls,
|
|
85
|
+
* each event reflects the consent state AT THAT MOMENT.
|
|
86
|
+
*
|
|
87
|
+
* @returns Record of category ID to consent boolean
|
|
88
|
+
*
|
|
89
|
+
* @example
|
|
90
|
+
* getNormalizedCategories()
|
|
91
|
+
* // { "C0001": true, "C0002": false, "C0003": true }
|
|
92
|
+
*/
|
|
93
|
+
export const getNormalizedCategories = () => {
|
|
94
|
+
const activeGroupIds = getNormalizedActiveGroupIds();
|
|
95
|
+
return getAllGroups().reduce((acc, group) => {
|
|
96
|
+
acc[group.groupId] = activeGroupIds.includes(group.groupId);
|
|
97
|
+
return acc;
|
|
98
|
+
}, {});
|
|
99
|
+
};
|
|
100
|
+
/**
|
|
101
|
+
* Coerce OneTrust consent model name to our simplified model.
|
|
102
|
+
*
|
|
103
|
+
* @param model - OneTrust consent model name
|
|
104
|
+
* @returns 'opt-in' or 'opt-out'
|
|
105
|
+
*/
|
|
106
|
+
export const coerceConsentModel = (model) => {
|
|
107
|
+
switch (model) {
|
|
108
|
+
case OtConsentModel.optIn:
|
|
109
|
+
case OtConsentModel.implied:
|
|
110
|
+
case 'opt-in':
|
|
111
|
+
case 'implied consent':
|
|
112
|
+
return 'opt-in';
|
|
113
|
+
case OtConsentModel.optOut:
|
|
114
|
+
case 'opt-out':
|
|
115
|
+
default:
|
|
116
|
+
// Default to opt-out for unknown models (e.g., 'custom', 'notice')
|
|
117
|
+
return 'opt-out';
|
|
118
|
+
}
|
|
119
|
+
};
|
|
@@ -1,22 +1,10 @@
|
|
|
1
1
|
import type { ChordAnalyticsOptions, ConsentConfiguration } from '../../types/index.js';
|
|
2
|
-
declare global {
|
|
3
|
-
interface Window {
|
|
4
|
-
OneTrust?: OneTrustApi;
|
|
5
|
-
OnetrustActiveGroups?: string;
|
|
6
|
-
}
|
|
7
|
-
}
|
|
8
|
-
interface OneTrustApi {
|
|
9
|
-
OnConsentChanged: (callback: () => void) => void;
|
|
10
|
-
GetDomainData: () => {
|
|
11
|
-
Groups: OneTrustGroup[];
|
|
12
|
-
};
|
|
13
|
-
}
|
|
14
|
-
interface OneTrustGroup {
|
|
15
|
-
CustomGroupId: string;
|
|
16
|
-
}
|
|
17
2
|
export declare const onetrustConsentAdapter: (options: ChordAnalyticsOptions) => {
|
|
18
3
|
getCurrentConsent: () => ConsentConfiguration;
|
|
19
4
|
handleConsentUpdates: (updateCdpConsent: (consent: ConsentConfiguration) => void) => void;
|
|
20
5
|
userCanBeTracked: () => boolean;
|
|
6
|
+
getCategories: () => Record<string, boolean>;
|
|
7
|
+
isConsentReady: () => boolean;
|
|
8
|
+
getConsentModel: () => 'opt-in' | 'opt-out';
|
|
9
|
+
getApi: () => import("./onetrust-api.js").OneTrustGlobal;
|
|
21
10
|
};
|
|
22
|
-
export {};
|
|
@@ -1,80 +1,72 @@
|
|
|
1
|
-
|
|
2
|
-
* Returns OneTrust's API.
|
|
3
|
-
*/
|
|
4
|
-
const getApi = () => {
|
|
5
|
-
const oneTrust = window.OneTrust;
|
|
6
|
-
if (oneTrust &&
|
|
7
|
-
typeof oneTrust === 'object' &&
|
|
8
|
-
'OnConsentChanged' in oneTrust &&
|
|
9
|
-
'GetDomainData' in oneTrust) {
|
|
10
|
-
return oneTrust;
|
|
11
|
-
}
|
|
12
|
-
return undefined;
|
|
13
|
-
};
|
|
1
|
+
import { getOneTrustGlobal, getNormalizedCategories, coerceConsentModel, } from './onetrust-api.js';
|
|
14
2
|
/**
|
|
15
3
|
* Returns whether the user can currently be tracked.
|
|
4
|
+
* Uses duck typing to check if OneTrust is fully loaded.
|
|
16
5
|
*/
|
|
17
6
|
const userCanBeTracked = () => {
|
|
18
|
-
// Always loads, a la `consentModel: 'opt-out'`
|
|
19
|
-
|
|
20
|
-
return !!api;
|
|
7
|
+
// Always loads when OneTrust is ready, a la `consentModel: 'opt-out'`
|
|
8
|
+
return !!getOneTrustGlobal();
|
|
21
9
|
};
|
|
22
10
|
/**
|
|
23
11
|
* Returns the user's current consent configuration.
|
|
12
|
+
* Reads FRESH from OneTrust - no caching!
|
|
24
13
|
*/
|
|
25
14
|
const getCurrentConsent = () => {
|
|
26
|
-
const api =
|
|
27
|
-
|
|
28
|
-
if (!api || !activeGroups || typeof activeGroups !== 'string')
|
|
15
|
+
const api = getOneTrustGlobal();
|
|
16
|
+
if (!api)
|
|
29
17
|
return {};
|
|
30
|
-
const activeGroupIds = new Set(activeGroups.trim().split(',').filter(Boolean));
|
|
31
18
|
return {
|
|
32
|
-
consentCategories:
|
|
33
|
-
.GetDomainData()
|
|
34
|
-
.Groups.reduce((acc, { CustomGroupId }) => {
|
|
35
|
-
const groupId = CustomGroupId.trim();
|
|
36
|
-
acc[groupId] = activeGroupIds.has(groupId);
|
|
37
|
-
return acc;
|
|
38
|
-
}, {}),
|
|
39
|
-
dontSend: !userCanBeTracked(),
|
|
19
|
+
consentCategories: getNormalizedCategories(),
|
|
40
20
|
};
|
|
41
21
|
};
|
|
42
22
|
/**
|
|
43
23
|
* Handles subsequent changes to consent configuration.
|
|
24
|
+
* Sets up OnConsentChanged listener with retry logic.
|
|
44
25
|
*/
|
|
45
26
|
const handleConsentUpdates = (updateCdpConsent, options) => {
|
|
46
27
|
const maxRetries = 100;
|
|
47
28
|
const retryInterval = 50;
|
|
48
29
|
let attempts = 0;
|
|
49
30
|
const setupHandler = () => {
|
|
50
|
-
const api =
|
|
31
|
+
const api = getOneTrustGlobal();
|
|
51
32
|
if (!api) {
|
|
52
33
|
attempts++;
|
|
53
34
|
if (attempts < maxRetries) {
|
|
54
35
|
if (options.debug && options.enableLogging) {
|
|
55
|
-
|
|
56
|
-
console.log('OneTrust API not found, retrying...');
|
|
36
|
+
console.log('[Chord Analytics DEBUG]: OneTrust API not found, retrying...');
|
|
57
37
|
}
|
|
58
38
|
setTimeout(setupHandler, retryInterval);
|
|
59
39
|
}
|
|
60
40
|
return;
|
|
61
41
|
}
|
|
62
42
|
if (options.debug && options.enableLogging) {
|
|
63
|
-
|
|
64
|
-
console.log('OneTrust API found, setting up consent changed handler');
|
|
43
|
+
console.log('[Chord Analytics DEBUG]: OneTrust API found, setting up consent changed handler');
|
|
65
44
|
}
|
|
66
45
|
api.OnConsentChanged(() => {
|
|
67
46
|
updateCdpConsent(getCurrentConsent());
|
|
47
|
+
if (options.debug && options.enableLogging) {
|
|
48
|
+
console.log('[Chord Analytics DEBUG]: OneTrust consent changed, sending Consent Preferences Updated track event');
|
|
49
|
+
}
|
|
68
50
|
});
|
|
69
51
|
};
|
|
70
52
|
setupHandler();
|
|
71
53
|
};
|
|
72
54
|
export const onetrustConsentAdapter = (options) => {
|
|
73
55
|
return {
|
|
56
|
+
// Existing methods for backwards compatibility
|
|
74
57
|
getCurrentConsent,
|
|
75
58
|
handleConsentUpdates: (updateCdpConsent) => {
|
|
76
59
|
handleConsentUpdates(updateCdpConsent, options);
|
|
77
60
|
},
|
|
78
61
|
userCanBeTracked,
|
|
62
|
+
getCategories: () => getNormalizedCategories(),
|
|
63
|
+
isConsentReady: () => !!getOneTrustGlobal(),
|
|
64
|
+
getConsentModel: () => {
|
|
65
|
+
const api = getOneTrustGlobal();
|
|
66
|
+
if (!api)
|
|
67
|
+
return 'opt-out';
|
|
68
|
+
return coerceConsentModel(api.GetDomainData().ConsentModel.Name);
|
|
69
|
+
},
|
|
70
|
+
getApi: getOneTrustGlobal,
|
|
79
71
|
};
|
|
80
72
|
};
|
|
@@ -13,5 +13,7 @@ export declare const segmentConsentAdapter: () => {
|
|
|
13
13
|
getCurrentConsent: () => Promise<ConsentConfiguration>;
|
|
14
14
|
handleConsentUpdates: (updateCdpConsent: (consent: ConsentConfiguration) => void) => void;
|
|
15
15
|
userCanBeTracked: () => Promise<boolean>;
|
|
16
|
+
getCategories: () => Record<string, boolean>;
|
|
17
|
+
isConsentReady: () => boolean;
|
|
16
18
|
};
|
|
17
19
|
export {};
|
|
@@ -33,8 +33,6 @@ const waitForApi = (maxRetries = 10, baseDelay = 100) => __awaiter(void 0, void
|
|
|
33
33
|
return null;
|
|
34
34
|
});
|
|
35
35
|
return checkAndWait();
|
|
36
|
-
console.warn('Segment API failed to load after all retries');
|
|
37
|
-
return null;
|
|
38
36
|
});
|
|
39
37
|
/**
|
|
40
38
|
* Returns whether the user can currently be tracked.
|
|
@@ -54,7 +52,6 @@ const getCurrentConsent = () => __awaiter(void 0, void 0, void 0, function* () {
|
|
|
54
52
|
return {};
|
|
55
53
|
return {
|
|
56
54
|
consentCategories: Object.assign(Object.assign({}, api.customPreferences), api.destinationPreferences),
|
|
57
|
-
dontSend: !(yield userCanBeTracked()),
|
|
58
55
|
};
|
|
59
56
|
});
|
|
60
57
|
/**
|
|
@@ -70,5 +67,15 @@ export const segmentConsentAdapter = () => {
|
|
|
70
67
|
getCurrentConsent,
|
|
71
68
|
handleConsentUpdates,
|
|
72
69
|
userCanBeTracked,
|
|
70
|
+
getCategories: () => {
|
|
71
|
+
const api = getApi();
|
|
72
|
+
if (!(api === null || api === void 0 ? void 0 : api.customPreferences))
|
|
73
|
+
return {};
|
|
74
|
+
return Object.assign(Object.assign({}, api.customPreferences), api.destinationPreferences);
|
|
75
|
+
},
|
|
76
|
+
isConsentReady: () => {
|
|
77
|
+
const api = getApi();
|
|
78
|
+
return !!((api === null || api === void 0 ? void 0 : api.customPreferences) && Object.keys(api.customPreferences).length > 0);
|
|
79
|
+
},
|
|
73
80
|
};
|
|
74
81
|
};
|
|
@@ -17,5 +17,7 @@ export declare const shopifyConsentAdapter: () => {
|
|
|
17
17
|
getCurrentConsent: () => ConsentConfiguration;
|
|
18
18
|
handleConsentUpdates: (updateCdpConsent: (consent: ConsentConfiguration) => void) => void;
|
|
19
19
|
userCanBeTracked: () => boolean;
|
|
20
|
+
getCategories: () => Record<string, boolean>;
|
|
21
|
+
isConsentReady: () => boolean;
|
|
20
22
|
};
|
|
21
23
|
export {};
|
|
@@ -27,7 +27,6 @@ const getCurrentConsent = () => {
|
|
|
27
27
|
preferences: api.preferencesProcessingAllowed(),
|
|
28
28
|
sale_of_data: api.saleOfDataAllowed(),
|
|
29
29
|
},
|
|
30
|
-
dontSend: !userCanBeTracked(),
|
|
31
30
|
};
|
|
32
31
|
};
|
|
33
32
|
/**
|
|
@@ -43,5 +42,17 @@ export const shopifyConsentAdapter = () => {
|
|
|
43
42
|
getCurrentConsent,
|
|
44
43
|
handleConsentUpdates,
|
|
45
44
|
userCanBeTracked,
|
|
45
|
+
getCategories: () => {
|
|
46
|
+
const api = getApi();
|
|
47
|
+
if (!api)
|
|
48
|
+
return {};
|
|
49
|
+
return {
|
|
50
|
+
analytics: api.analyticsProcessingAllowed(),
|
|
51
|
+
marketing: api.marketingAllowed(),
|
|
52
|
+
preferences: api.preferencesProcessingAllowed(),
|
|
53
|
+
sale_of_data: api.saleOfDataAllowed(),
|
|
54
|
+
};
|
|
55
|
+
},
|
|
56
|
+
isConsentReady: () => !!getApi(),
|
|
46
57
|
};
|
|
47
58
|
};
|