@financial-times/cmp-client 3.1.4 → 3.3.0-beta.4
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/README.md +17 -2
- package/dist/cmp-static.js +660 -0
- package/dist/index.cjs +202 -2719
- package/dist/index.js +202 -2719
- package/dist/src/cmp-static.d.ts +2 -0
- package/dist/src/cmp-static.d.ts.map +1 -0
- package/dist/src/consent-ready/index.d.ts.map +1 -1
- package/dist/src/consent-ready/utils/__fixtures__/helpers.d.ts +27 -0
- package/dist/src/consent-ready/utils/__fixtures__/helpers.d.ts.map +1 -0
- package/dist/src/consent-ready/utils/__fixtures__/strings.d.ts +4 -8
- package/dist/src/consent-ready/utils/__fixtures__/strings.d.ts.map +1 -1
- package/dist/src/consent-ready/utils/__tests__/consent.test.d.ts +2 -0
- package/dist/src/consent-ready/utils/__tests__/consent.test.d.ts.map +1 -0
- package/dist/src/consent-ready/utils/consent.d.ts +6 -0
- package/dist/src/consent-ready/utils/consent.d.ts.map +1 -0
- package/dist/src/consent-ready/utils/get-consent-payload.d.ts +7 -7
- package/dist/src/consent-ready/utils/get-consent-payload.d.ts.map +1 -1
- package/dist/src/consent-ready/utils/get-parsed-consent.d.ts +6 -5
- package/dist/src/consent-ready/utils/get-parsed-consent.d.ts.map +1 -1
- package/dist/src/consent-ready/utils/validators.d.ts +0 -1
- package/dist/src/consent-ready/utils/validators.d.ts.map +1 -1
- package/dist/src/html/cmp-footer-link.d.ts +8 -4
- package/dist/src/html/cmp-footer-link.d.ts.map +1 -1
- package/dist/src/html/cmp-scripts.d.ts.map +1 -1
- package/dist/src/index.d.ts +1 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/lib/constants.d.ts +33 -2
- package/dist/src/lib/constants.d.ts.map +1 -1
- package/dist/src/lib/properties.d.ts +36 -0
- package/dist/src/lib/properties.d.ts.map +1 -1
- package/dist/src/tracking/index.d.ts +1 -1
- package/dist/src/tracking/index.d.ts.map +1 -1
- package/dist/src/tracking/state.d.ts +6 -0
- package/dist/src/tracking/state.d.ts.map +1 -1
- package/dist/src/utils/__tests__/url.test.d.ts +2 -0
- package/dist/src/utils/__tests__/url.test.d.ts.map +1 -0
- package/dist/src/utils/url.d.ts +126 -0
- package/dist/src/utils/url.d.ts.map +1 -0
- package/package.json +11 -11
- package/typings/globals.d.ts +4 -0
- package/typings/types.d.ts +54 -3
- package/dist/src/consent-ready/utils/has-consent-changed.d.ts +0 -3
- package/dist/src/consent-ready/utils/has-consent-changed.d.ts.map +0 -1
- package/dist/src/html/__tests__/cmp-footer-links.test.d.ts +0 -2
- package/dist/src/html/__tests__/cmp-footer-links.test.d.ts.map +0 -1
package/README.md
CHANGED
|
@@ -29,7 +29,12 @@ npm install @financial-times/cmp-client
|
|
|
29
29
|
### Usage on non-FT.com properties
|
|
30
30
|
|
|
31
31
|
```js
|
|
32
|
-
import {
|
|
32
|
+
import {
|
|
33
|
+
initSourcepointCmp,
|
|
34
|
+
properties,
|
|
35
|
+
updateFooterLinkCMP,
|
|
36
|
+
debug,
|
|
37
|
+
} from "@financial-times/cmp-client";
|
|
33
38
|
|
|
34
39
|
/**
|
|
35
40
|
* Optionally enable debug mode to see CMP events in the console
|
|
@@ -48,12 +53,21 @@ initSourcepointCmp({
|
|
|
48
53
|
propertyConfig: properties["YOUR_PROPERTY_CONFIG_KEY"],
|
|
49
54
|
useConsentStore: false, // Specialist Titles _must_ opt out of Single Consent Store
|
|
50
55
|
});
|
|
56
|
+
/**
|
|
57
|
+
* Optionally enable "Manage Cookies" footer link util
|
|
58
|
+
*/
|
|
59
|
+
updateFooterLinkCMP(properties["YOUR_PROPERTY_CONFIG_KEY"]);
|
|
51
60
|
```
|
|
52
61
|
|
|
53
62
|
### Usage on FT.com
|
|
54
63
|
|
|
55
64
|
```js
|
|
56
|
-
import {
|
|
65
|
+
import {
|
|
66
|
+
initSourcepointCmp,
|
|
67
|
+
properties,
|
|
68
|
+
updateFooterLinkCMP,
|
|
69
|
+
debug,
|
|
70
|
+
} from "@financial-times/cmp-client";
|
|
57
71
|
|
|
58
72
|
/**
|
|
59
73
|
* Optionally enable debug mode to see CMP events in the console
|
|
@@ -68,6 +82,7 @@ if (flagsClient.get("adsDisableInternalCMP")) {
|
|
|
68
82
|
initSourcepointCmp({
|
|
69
83
|
propertyConfig: FT_DOTCOM_TEST,
|
|
70
84
|
});
|
|
85
|
+
updateFooterLinkCMP();
|
|
71
86
|
}
|
|
72
87
|
```
|
|
73
88
|
|
|
@@ -0,0 +1,660 @@
|
|
|
1
|
+
(function() {
|
|
2
|
+
"use strict";
|
|
3
|
+
const request = (url2, { credentials = "omit" } = {}) => {
|
|
4
|
+
return fetch(`https://session-next.ft.com${url2}`, {
|
|
5
|
+
credentials,
|
|
6
|
+
useCorsProxy: true
|
|
7
|
+
}).then((response) => {
|
|
8
|
+
if (response.ok) {
|
|
9
|
+
return response.json();
|
|
10
|
+
} else {
|
|
11
|
+
return response.text().then((text) => {
|
|
12
|
+
throw new Error(`Next session responded with "${text}" (${response.status})`);
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
}).catch((err) => {
|
|
16
|
+
document.body.dispatchEvent(new CustomEvent("oErrors.log", {
|
|
17
|
+
bubbles: true,
|
|
18
|
+
detail: {
|
|
19
|
+
error: err,
|
|
20
|
+
info: {
|
|
21
|
+
component: "next-session-client"
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}));
|
|
25
|
+
});
|
|
26
|
+
};
|
|
27
|
+
let detailsCache = {};
|
|
28
|
+
const cache = (name, value) => {
|
|
29
|
+
if (typeof name === "object") {
|
|
30
|
+
detailsCache = name;
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
if (typeof name === "string" && typeof value === "string") {
|
|
34
|
+
detailsCache[name] = value;
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
if (typeof name === "string" && typeof value === "undefined") {
|
|
38
|
+
return detailsCache[name] || null;
|
|
39
|
+
}
|
|
40
|
+
if (typeof name === "undefined" && typeof value === "undefined") {
|
|
41
|
+
return detailsCache;
|
|
42
|
+
}
|
|
43
|
+
throw new Error("Invalid arguments");
|
|
44
|
+
};
|
|
45
|
+
cache.clear = () => {
|
|
46
|
+
detailsCache = {};
|
|
47
|
+
};
|
|
48
|
+
const requests = {};
|
|
49
|
+
const getSessionId = () => {
|
|
50
|
+
const [, sessionId] = /FTSession_s=([^;]+)/.exec(document.cookie) || [];
|
|
51
|
+
return sessionId;
|
|
52
|
+
};
|
|
53
|
+
const getUuid = () => {
|
|
54
|
+
const cachedUUID = cache("uuid");
|
|
55
|
+
if (cachedUUID) {
|
|
56
|
+
return Promise.resolve({ uuid: cachedUUID });
|
|
57
|
+
}
|
|
58
|
+
const sessionId = getSessionId();
|
|
59
|
+
if (!sessionId) {
|
|
60
|
+
return Promise.resolve({ uuid: void 0 });
|
|
61
|
+
}
|
|
62
|
+
if (!requests.uuid) {
|
|
63
|
+
requests.uuid = request(`/sessions/s/${sessionId}`).then(({ uuid } = {}) => {
|
|
64
|
+
delete requests.uuid;
|
|
65
|
+
if (uuid) {
|
|
66
|
+
cache("uuid", uuid);
|
|
67
|
+
}
|
|
68
|
+
return { uuid };
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
return requests.uuid;
|
|
72
|
+
};
|
|
73
|
+
const SOURCEPOINT_CONSENT_SOURCE = "sourcepoint-cmp";
|
|
74
|
+
const SOURCEPOINT_FOW_SCOPE = "FTPINK";
|
|
75
|
+
const CONSENT_COOKIE_NAME = "FTConsent";
|
|
76
|
+
const SOURCEPOINT_FOW_ID = "sourcepointCmp/VngD.XycZut.595cp9fWdp5XYP9vlFvk";
|
|
77
|
+
const FT_COOKIE_DOMAIN = ".ft.com";
|
|
78
|
+
const FT_CONSENT_PROXY_HOST = "https://consent.ft.com";
|
|
79
|
+
const iabCustomCategories = {
|
|
80
|
+
permutiveAds: {
|
|
81
|
+
purposes: [2, 4, 8, 9],
|
|
82
|
+
iabVendors: [361],
|
|
83
|
+
customVendors: [],
|
|
84
|
+
specialFeatures: []
|
|
85
|
+
},
|
|
86
|
+
demographicAds: {
|
|
87
|
+
purposes: [3, 4, 7, 9, 10],
|
|
88
|
+
iabVendors: [],
|
|
89
|
+
customVendors: [],
|
|
90
|
+
specialFeatures: []
|
|
91
|
+
},
|
|
92
|
+
behaviouralAds: {
|
|
93
|
+
purposes: [2, 4, 8, 9],
|
|
94
|
+
iabVendors: [],
|
|
95
|
+
customVendors: [],
|
|
96
|
+
specialFeatures: []
|
|
97
|
+
},
|
|
98
|
+
programmaticAds: {
|
|
99
|
+
purposes: [2],
|
|
100
|
+
iabVendors: [],
|
|
101
|
+
customVendors: [],
|
|
102
|
+
specialFeatures: []
|
|
103
|
+
},
|
|
104
|
+
personalisedMarketing: {
|
|
105
|
+
purposes: [1, 4, 8, 9, 10],
|
|
106
|
+
iabVendors: [],
|
|
107
|
+
customVendors: [],
|
|
108
|
+
specialFeatures: []
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
const iabCategoryNames = Object.keys(iabCustomCategories);
|
|
112
|
+
const defaults = {
|
|
113
|
+
joinHref: true,
|
|
114
|
+
gdpr: {},
|
|
115
|
+
ccpa: {}
|
|
116
|
+
};
|
|
117
|
+
const FT_DOTCOM_TEST = {
|
|
118
|
+
...defaults,
|
|
119
|
+
accountId: 1906,
|
|
120
|
+
baseEndpoint: "https://consent-manager.ft.com",
|
|
121
|
+
propertyHref: "https://local.ft.com",
|
|
122
|
+
_clientOptions: {
|
|
123
|
+
privacyManagerId: 827767,
|
|
124
|
+
manageCookiesLinkOverride: "ft.com/preferences/manage-cookies"
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
const FT_DOTCOM_PROD = {
|
|
128
|
+
...defaults,
|
|
129
|
+
accountId: 1906,
|
|
130
|
+
baseEndpoint: "https://consent-manager.ft.com",
|
|
131
|
+
propertyId: 31642,
|
|
132
|
+
_clientOptions: {
|
|
133
|
+
privacyManagerId: 827767,
|
|
134
|
+
manageCookiesLinkOverride: "ft.com/preferences/manage-cookies",
|
|
135
|
+
rootDomain: "ft.com"
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
const SP_PWMNET = {
|
|
139
|
+
...defaults,
|
|
140
|
+
accountId: 1906,
|
|
141
|
+
baseEndpoint: "https://consent-manager.pwmnet.com",
|
|
142
|
+
propertyId: 33414,
|
|
143
|
+
_clientOptions: {
|
|
144
|
+
rootDomain: "pwmnet.com"
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
const SP_FDI_INTELLIGENCE = {
|
|
148
|
+
...defaults,
|
|
149
|
+
accountId: 1906,
|
|
150
|
+
baseEndpoint: "https://consent-manager.fdiintelligence.com",
|
|
151
|
+
propertyId: 34061,
|
|
152
|
+
_clientOptions: {
|
|
153
|
+
rootDomain: "fdiintelligence.com"
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
const SP_THE_BANKER = {
|
|
157
|
+
...defaults,
|
|
158
|
+
accountId: 1906,
|
|
159
|
+
baseEndpoint: "https://consent-manager.thebanker.com",
|
|
160
|
+
propertyId: 34060,
|
|
161
|
+
_clientOptions: {
|
|
162
|
+
rootDomain: "thebanker.com"
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
const SP_BANKING_RR = {
|
|
166
|
+
...defaults,
|
|
167
|
+
accountId: 1906,
|
|
168
|
+
baseEndpoint: "https://consent-manager.bankingriskandregulation.com",
|
|
169
|
+
propertyId: 34059,
|
|
170
|
+
_clientOptions: {
|
|
171
|
+
rootDomain: "bankingriskandregulation.com"
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
const SP_SUSTAINABLE_VIEWS = {
|
|
175
|
+
...defaults,
|
|
176
|
+
accountId: 1906,
|
|
177
|
+
baseEndpoint: "https://consent-manager.sustainableviews.com",
|
|
178
|
+
propertyId: 34058,
|
|
179
|
+
_clientOptions: {
|
|
180
|
+
rootDomain: "sustainableviews.com"
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
const SP_FT_ADVISER = {
|
|
184
|
+
...defaults,
|
|
185
|
+
accountId: 1906,
|
|
186
|
+
baseEndpoint: "https://consent-manager.ftadviser.com",
|
|
187
|
+
propertyId: 33416,
|
|
188
|
+
_clientOptions: {
|
|
189
|
+
rootDomain: "ftadviser.com"
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
const SP_INVESTORS_CHRONICLE = {
|
|
193
|
+
...defaults,
|
|
194
|
+
accountId: 1906,
|
|
195
|
+
baseEndpoint: "https://consent-manager.investorschronicle.co.uk",
|
|
196
|
+
propertyId: 33415,
|
|
197
|
+
_clientOptions: {
|
|
198
|
+
rootDomain: "investorschronicle.co.uk"
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
const MM_IGNITES_ASIA = {
|
|
202
|
+
...defaults,
|
|
203
|
+
accountId: 1906,
|
|
204
|
+
baseEndpoint: "https://cdn.privacy-mgmt.com",
|
|
205
|
+
propertyId: 33947,
|
|
206
|
+
_clientOptions: {
|
|
207
|
+
rootDomain: "ignitesasia.com"
|
|
208
|
+
}
|
|
209
|
+
};
|
|
210
|
+
const MM_IGNITES_EUROPE = {
|
|
211
|
+
...defaults,
|
|
212
|
+
accountId: 1906,
|
|
213
|
+
baseEndpoint: "https://cdn.privacy-mgmt.com",
|
|
214
|
+
propertyId: 33946,
|
|
215
|
+
_clientOptions: {
|
|
216
|
+
rootDomain: "igniteseurope.com"
|
|
217
|
+
}
|
|
218
|
+
};
|
|
219
|
+
const properties = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
220
|
+
__proto__: null,
|
|
221
|
+
FT_DOTCOM_PROD,
|
|
222
|
+
FT_DOTCOM_TEST,
|
|
223
|
+
MM_IGNITES_ASIA,
|
|
224
|
+
MM_IGNITES_EUROPE,
|
|
225
|
+
SP_BANKING_RR,
|
|
226
|
+
SP_FDI_INTELLIGENCE,
|
|
227
|
+
SP_FT_ADVISER,
|
|
228
|
+
SP_INVESTORS_CHRONICLE,
|
|
229
|
+
SP_PWMNET,
|
|
230
|
+
SP_SUSTAINABLE_VIEWS,
|
|
231
|
+
SP_THE_BANKER
|
|
232
|
+
}, Symbol.toStringTag, { value: "Module" }));
|
|
233
|
+
function createContentScript(cmpScript, content) {
|
|
234
|
+
const s = document.createElement("script");
|
|
235
|
+
s.dataset.cmpScript = cmpScript;
|
|
236
|
+
s.innerHTML = content;
|
|
237
|
+
return s;
|
|
238
|
+
}
|
|
239
|
+
function createSourceScript(src) {
|
|
240
|
+
const s = document.createElement("script");
|
|
241
|
+
s.src = src;
|
|
242
|
+
return s;
|
|
243
|
+
}
|
|
244
|
+
const scriptSources = {
|
|
245
|
+
cmpFrames: "https://consent-manager.ft.com/unified/wrapperMessagingWithoutDetection.js"
|
|
246
|
+
};
|
|
247
|
+
const scriptContent = {
|
|
248
|
+
tcfStub: `"use strict";function _typeof(t){return(_typeof="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(t)}!function(){var t=function(){var t,e,o=[],n=window,r=n;for(;r;){try{if(r.frames.__tcfapiLocator){t=r;break}}catch(t){}if(r===n.top)break;r=r.parent}t||(!function t(){var e=n.document,o=!!n.frames.__tcfapiLocator;if(!o)if(e.body){var r=e.createElement("iframe");r.style.cssText="display:none",r.name="__tcfapiLocator",r.title = "__tcfapiLocator",e.body.appendChild(r)}else setTimeout(t,5);return!o}(),n.__tcfapi=function(){for(var t=arguments.length,n=new Array(t),r=0;r<t;r++)n[r]=arguments[r];if(!n.length)return o;"setGdprApplies"===n[0]?n.length>3&&2===parseInt(n[1],10)&&"boolean"==typeof n[3]&&(e=n[3],"function"==typeof n[2]&&n[2]("set",!0)):"ping"===n[0]?"function"==typeof n[2]&&n[2]({gdprApplies:e,cmpLoaded:!1,cmpStatus:"stub"}):o.push(n)},n.addEventListener("message",(function(t){var e="string"==typeof t.data,o={};if(e)try{o=JSON.parse(t.data)}catch(t){}else o=t.data;var n="object"===_typeof(o)&&null!==o?o.__tcfapiCall:null;n&&window.__tcfapi(n.command,n.version,(function(o,r){var a={__tcfapiReturn:{returnValue:o,success:r,callId:n.callId}};t&&t.source&&t.source.postMessage&&t.source.postMessage(e?JSON.stringify(a):a,"*")}),n.parameter)}),!1))};"undefined"!=typeof module?module.exports=t:t()}();`,
|
|
249
|
+
uspStub: `"use strict";(function () { var e = false; var c = window; var t = document; function r() { if (!c.frames["__uspapiLocator"]) { if (t.body) { var a = t.body; var e = t.createElement("iframe"); e.style.cssText = "display:none"; e.name = "__uspapiLocator"; e.title = "__uspapiLocator";a.appendChild(e) } else { setTimeout(r, 5) } } } r(); function p() { var a = arguments; __uspapi.a = __uspapi.a || []; if (!a.length) { return __uspapi.a } else if (a[0] === "ping") { a[2]({ gdprAppliesGlobally: e, cmpLoaded: false }, true) } else { __uspapi.a.push([].slice.apply(a)) } } function l(t) { var r = typeof t.data === "string"; try { var a = r ? JSON.parse(t.data) : t.data; if (a.__cmpCall) { var n = a.__cmpCall; c.__uspapi(n.command, n.parameter, function (a, e) { var c = { __cmpReturn: { returnValue: a, success: e, callId: n.callId } }; t.source.postMessage(r ? JSON.stringify(c) : c, "*") }) } } catch (a) { } } if (typeof __uspapi !== "function") { c.__uspapi = p; __uspapi.msgHandler = l; c.addEventListener("message", l, false) } })();`
|
|
250
|
+
};
|
|
251
|
+
function getCmpScripts() {
|
|
252
|
+
const fragment = document.createDocumentFragment();
|
|
253
|
+
fragment.appendChild(createContentScript("tcf", scriptContent.tcfStub));
|
|
254
|
+
fragment.appendChild(createContentScript("usp", scriptContent.uspStub));
|
|
255
|
+
fragment.appendChild(createSourceScript(scriptSources.cmpFrames));
|
|
256
|
+
return fragment;
|
|
257
|
+
}
|
|
258
|
+
function bootstrapCmp(config) {
|
|
259
|
+
const { _clientOptions, ...spConfig } = config;
|
|
260
|
+
window._sp_ = { config: spConfig };
|
|
261
|
+
window._sp_queue ?? (window._sp_queue = []);
|
|
262
|
+
document.head.appendChild(getCmpScripts());
|
|
263
|
+
}
|
|
264
|
+
function getConsentPayload(parsedConsent, updateConsentStore, { formOfWordsId, cookieDomain }) {
|
|
265
|
+
const categoryNames = Object.keys(parsedConsent);
|
|
266
|
+
const data = categoryNames.reduce(
|
|
267
|
+
(payload, categoryName) => {
|
|
268
|
+
payload[categoryName] = {
|
|
269
|
+
onsite: {
|
|
270
|
+
status: parsedConsent[categoryName],
|
|
271
|
+
lbi: false,
|
|
272
|
+
source: SOURCEPOINT_CONSENT_SOURCE,
|
|
273
|
+
fow: formOfWordsId
|
|
274
|
+
}
|
|
275
|
+
};
|
|
276
|
+
return payload;
|
|
277
|
+
},
|
|
278
|
+
{}
|
|
279
|
+
);
|
|
280
|
+
if (updateConsentStore) {
|
|
281
|
+
return {
|
|
282
|
+
setConsentCookie: true,
|
|
283
|
+
formOfWordsId,
|
|
284
|
+
consentSource: SOURCEPOINT_CONSENT_SOURCE,
|
|
285
|
+
cookieDomain,
|
|
286
|
+
data
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
return { data, cookieDomain };
|
|
290
|
+
}
|
|
291
|
+
function checkConsentFor(categoryName, { purpose, vendor, specialFeatureOptins }) {
|
|
292
|
+
const customCategory = iabCustomCategories[categoryName];
|
|
293
|
+
const requiredPurposesConsented = customCategory.purposes.every(
|
|
294
|
+
(requiredPurpose) => (purpose == null ? void 0 : purpose.consents[requiredPurpose]) === true
|
|
295
|
+
);
|
|
296
|
+
const requiredIabVendorsConsented = customCategory.iabVendors.every(
|
|
297
|
+
(requiredVendor) => (vendor == null ? void 0 : vendor.consents[requiredVendor]) === true
|
|
298
|
+
);
|
|
299
|
+
const requiredSpecialFeaturesConsented = customCategory.specialFeatures.every(
|
|
300
|
+
(requiredFeature) => (specialFeatureOptins == null ? void 0 : specialFeatureOptins[requiredFeature]) === true
|
|
301
|
+
);
|
|
302
|
+
return requiredPurposesConsented && requiredIabVendorsConsented && requiredSpecialFeaturesConsented;
|
|
303
|
+
}
|
|
304
|
+
function parseCCPAConsent(ccpa) {
|
|
305
|
+
const userHasNotOptedOut = (ccpa == null ? void 0 : ccpa[2]) === "N";
|
|
306
|
+
const parsedConsent = {};
|
|
307
|
+
for (const categoryName of iabCategoryNames) {
|
|
308
|
+
parsedConsent[categoryName] = userHasNotOptedOut;
|
|
309
|
+
}
|
|
310
|
+
return parsedConsent;
|
|
311
|
+
}
|
|
312
|
+
async function parseGDPRConsent() {
|
|
313
|
+
const tcData = await new Promise((resolve, reject) => {
|
|
314
|
+
var _a;
|
|
315
|
+
try {
|
|
316
|
+
(_a = window.__tcfapi) == null ? void 0 : _a.call(window, "addEventListener", 2, resolve);
|
|
317
|
+
} catch (error) {
|
|
318
|
+
reject(error);
|
|
319
|
+
}
|
|
320
|
+
});
|
|
321
|
+
const parsedConsent = {};
|
|
322
|
+
for (const categoryName of iabCategoryNames) {
|
|
323
|
+
parsedConsent[categoryName] = checkConsentFor(categoryName, tcData);
|
|
324
|
+
}
|
|
325
|
+
return parsedConsent;
|
|
326
|
+
}
|
|
327
|
+
async function getParsedConsent(activeLegislation, consentString) {
|
|
328
|
+
return activeLegislation === "ccpa" ? parseCCPAConsent(consentString) : await parseGDPRConsent();
|
|
329
|
+
}
|
|
330
|
+
function getConsentCookieValue() {
|
|
331
|
+
const cookies = Object.fromEntries(
|
|
332
|
+
document.cookie.split("; ").map((cookie) => cookie.split("="))
|
|
333
|
+
);
|
|
334
|
+
const encodedValue = cookies[CONSENT_COOKIE_NAME];
|
|
335
|
+
if (!encodedValue)
|
|
336
|
+
return {};
|
|
337
|
+
const decodedValue = decodeURIComponent(encodedValue);
|
|
338
|
+
return Object.fromEntries(
|
|
339
|
+
decodedValue.split(",").map((consent) => {
|
|
340
|
+
const [category, value] = consent.split(":");
|
|
341
|
+
const booleanConsent = [category, value === "on" ? true : false];
|
|
342
|
+
return booleanConsent;
|
|
343
|
+
})
|
|
344
|
+
);
|
|
345
|
+
}
|
|
346
|
+
function hasConsentChanged(parsedConsent, previousConsent) {
|
|
347
|
+
const categories = Object.keys(parsedConsent);
|
|
348
|
+
return categories.some((category) => {
|
|
349
|
+
const adaptedCategoryName = `${category.toLowerCase()}Onsite`;
|
|
350
|
+
return parsedConsent[category] !== previousConsent[adaptedCategoryName];
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
function shouldUpdateConsentStore({ userId, useConsentStore }) {
|
|
354
|
+
return !!userId && useConsentStore === true;
|
|
355
|
+
}
|
|
356
|
+
function getConsentEndpoint(updateConsentStore, props) {
|
|
357
|
+
if (updateConsentStore) {
|
|
358
|
+
return `${props.consentProxyHost}/__consent/consent-record/${SOURCEPOINT_FOW_SCOPE}/${props.userId}`;
|
|
359
|
+
}
|
|
360
|
+
return `${props.consentProxyHost}/__consent/consent-record-cookie?cookieDomain=${props.cookieDomain}`;
|
|
361
|
+
}
|
|
362
|
+
async function saveConsent(consentEndpoint, payload) {
|
|
363
|
+
try {
|
|
364
|
+
const response = await fetch(consentEndpoint, {
|
|
365
|
+
method: "POST",
|
|
366
|
+
headers: {
|
|
367
|
+
"Content-Type": "application/json"
|
|
368
|
+
},
|
|
369
|
+
body: JSON.stringify(payload),
|
|
370
|
+
credentials: "include"
|
|
371
|
+
});
|
|
372
|
+
if (!response.ok) {
|
|
373
|
+
console.error("Unable to save consent preferences", response.status);
|
|
374
|
+
}
|
|
375
|
+
} catch (error) {
|
|
376
|
+
console.error("An error occurred while saving consent", error);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
function consentReadyHandlerFn(props) {
|
|
380
|
+
return async function consentReadyHandler(legislation, _consentUUID, consentString, consentMeta) {
|
|
381
|
+
const activeLegislation = consentMeta.applies ? legislation : "gdpr";
|
|
382
|
+
if (activeLegislation !== legislation || !consentString) {
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
const parsedConsent = await getParsedConsent(activeLegislation, consentString);
|
|
386
|
+
const consentHasChanged = hasConsentChanged(parsedConsent, getConsentCookieValue());
|
|
387
|
+
if (!consentHasChanged) {
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
const updateConsentStore = shouldUpdateConsentStore(props);
|
|
391
|
+
const consentEndpoint = getConsentEndpoint(updateConsentStore, props);
|
|
392
|
+
const payload = getConsentPayload(parsedConsent, updateConsentStore, props);
|
|
393
|
+
await saveConsent(consentEndpoint, payload);
|
|
394
|
+
document.dispatchEvent(new CustomEvent("oCookieMessage.act", { bubbles: true }));
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
const COOKIE_MESSAGE = "cookie-message";
|
|
398
|
+
const PRIVACY_MANAGER = "manage-cookies";
|
|
399
|
+
const INITIAL_STATE = Object.freeze({
|
|
400
|
+
activeComponent: COOKIE_MESSAGE,
|
|
401
|
+
messageId: 0,
|
|
402
|
+
privacyManagerId: 0
|
|
403
|
+
});
|
|
404
|
+
let privateState = INITIAL_STATE;
|
|
405
|
+
function isPlainObject(obj) {
|
|
406
|
+
return typeof obj === "object" && obj !== null && obj.constructor === Object && Object.prototype.toString.call(obj) === "[object Object]";
|
|
407
|
+
}
|
|
408
|
+
const getState = () => ({ ...privateState });
|
|
409
|
+
const setState = (newState) => {
|
|
410
|
+
if (!isPlainObject(newState)) {
|
|
411
|
+
console.error("Invalid state changes");
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
privateState = { ...privateState, ...newState };
|
|
415
|
+
};
|
|
416
|
+
const cookieToggleFlags = ["adsDisableInternalCMP", "pwm.cmp", "messageSlotBottom"];
|
|
417
|
+
function initTracking(context, cmpBaseEndpoint) {
|
|
418
|
+
const flags = extractRelevantFlags(context.flags);
|
|
419
|
+
window._sp_queue = window._sp_queue ?? [];
|
|
420
|
+
window._sp_queue.push(() => {
|
|
421
|
+
var _a, _b;
|
|
422
|
+
for (const [eventId, eventHandler] of Object.entries(trackingEventHandlers)) {
|
|
423
|
+
(_b = (_a = window._sp_).addEventListener) == null ? void 0 : _b.call(_a, eventId, eventHandler({ ...context, flags }));
|
|
424
|
+
}
|
|
425
|
+
});
|
|
426
|
+
setupPmTracking({ ...context, flags }, cmpBaseEndpoint);
|
|
427
|
+
}
|
|
428
|
+
function extractRelevantFlags(flags) {
|
|
429
|
+
const output = {};
|
|
430
|
+
if (typeof flags === "object") {
|
|
431
|
+
cookieToggleFlags.forEach((flagName) => {
|
|
432
|
+
if (Object.prototype.hasOwnProperty.call(flags, flagName)) {
|
|
433
|
+
output[flagName] = flags[flagName];
|
|
434
|
+
}
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
return output;
|
|
438
|
+
}
|
|
439
|
+
function track(payload) {
|
|
440
|
+
if (!payload)
|
|
441
|
+
return;
|
|
442
|
+
const rootEl = document.body;
|
|
443
|
+
const event = new CustomEvent("oTracking.event", {
|
|
444
|
+
bubbles: true,
|
|
445
|
+
cancelable: true,
|
|
446
|
+
detail: payload.detail
|
|
447
|
+
});
|
|
448
|
+
rootEl.dispatchEvent(event);
|
|
449
|
+
}
|
|
450
|
+
function dispatchComponentEvent({
|
|
451
|
+
trackingProps,
|
|
452
|
+
action,
|
|
453
|
+
triggerAction
|
|
454
|
+
}) {
|
|
455
|
+
let componentId;
|
|
456
|
+
const state = getState();
|
|
457
|
+
const { product, app, flags } = trackingProps;
|
|
458
|
+
if (state.activeComponent === COOKIE_MESSAGE) {
|
|
459
|
+
componentId = state.messageId;
|
|
460
|
+
} else {
|
|
461
|
+
componentId = state.privacyManagerId;
|
|
462
|
+
}
|
|
463
|
+
const event = {
|
|
464
|
+
detail: {
|
|
465
|
+
component: {
|
|
466
|
+
id: componentId,
|
|
467
|
+
name: state.activeComponent,
|
|
468
|
+
type: "overlay",
|
|
469
|
+
subtype: "cmp"
|
|
470
|
+
},
|
|
471
|
+
category: "component",
|
|
472
|
+
action,
|
|
473
|
+
...triggerAction && { trigger_action: triggerAction },
|
|
474
|
+
...product && { product },
|
|
475
|
+
...app && { app },
|
|
476
|
+
custom: [
|
|
477
|
+
{
|
|
478
|
+
cookie_toggle_flag: flags
|
|
479
|
+
}
|
|
480
|
+
],
|
|
481
|
+
url: window.document.location.href || null
|
|
482
|
+
}
|
|
483
|
+
};
|
|
484
|
+
track(event);
|
|
485
|
+
}
|
|
486
|
+
const trackingEventHandlers = {
|
|
487
|
+
onMessageChoiceSelect: (trackingProps) => (_messageType, _choiceId, choiceTypeId) => {
|
|
488
|
+
const choiceTypeTriggerMap = {
|
|
489
|
+
11: "accept_all",
|
|
490
|
+
12: "manage_cookies",
|
|
491
|
+
13: "reject_all"
|
|
492
|
+
};
|
|
493
|
+
const triggerAction = choiceTypeTriggerMap[choiceTypeId];
|
|
494
|
+
if (triggerAction) {
|
|
495
|
+
dispatchComponentEvent({
|
|
496
|
+
trackingProps,
|
|
497
|
+
action: "click",
|
|
498
|
+
triggerAction
|
|
499
|
+
});
|
|
500
|
+
}
|
|
501
|
+
},
|
|
502
|
+
onMessageReady: (trackingProps) => () => {
|
|
503
|
+
dispatchComponentEvent({
|
|
504
|
+
trackingProps,
|
|
505
|
+
action: "view"
|
|
506
|
+
});
|
|
507
|
+
},
|
|
508
|
+
onMessageReceiveData: () => (_messageType, data) => {
|
|
509
|
+
const { messageId } = data;
|
|
510
|
+
if (messageId) {
|
|
511
|
+
setState({
|
|
512
|
+
messageId
|
|
513
|
+
});
|
|
514
|
+
}
|
|
515
|
+
},
|
|
516
|
+
onError: (trackingProps) => (_messageType, errorCode) => {
|
|
517
|
+
dispatchComponentEvent({
|
|
518
|
+
trackingProps,
|
|
519
|
+
action: "error",
|
|
520
|
+
triggerAction: errorCode
|
|
521
|
+
});
|
|
522
|
+
},
|
|
523
|
+
onPMCancel: () => () => {
|
|
524
|
+
setState({
|
|
525
|
+
activeComponent: COOKIE_MESSAGE
|
|
526
|
+
});
|
|
527
|
+
}
|
|
528
|
+
};
|
|
529
|
+
function setupPmTracking(trackingProps, cmpBaseEndpoint) {
|
|
530
|
+
window.addEventListener(
|
|
531
|
+
"message",
|
|
532
|
+
function(event) {
|
|
533
|
+
if (event.origin !== cmpBaseEndpoint)
|
|
534
|
+
return;
|
|
535
|
+
const actionTypeMap = {
|
|
536
|
+
1: "save_and_close",
|
|
537
|
+
11: "accept_all",
|
|
538
|
+
13: "reject_all"
|
|
539
|
+
};
|
|
540
|
+
const { data: { fromPM, actionType, messageId = "0" } = {} } = event;
|
|
541
|
+
if (!fromPM)
|
|
542
|
+
return;
|
|
543
|
+
if (+messageId) {
|
|
544
|
+
setState({
|
|
545
|
+
activeComponent: PRIVACY_MANAGER,
|
|
546
|
+
privacyManagerId: +messageId
|
|
547
|
+
});
|
|
548
|
+
}
|
|
549
|
+
if (!actionType || !actionTypeMap[actionType])
|
|
550
|
+
return;
|
|
551
|
+
dispatchComponentEvent({
|
|
552
|
+
trackingProps,
|
|
553
|
+
action: "click",
|
|
554
|
+
triggerAction: actionTypeMap[actionType]
|
|
555
|
+
});
|
|
556
|
+
},
|
|
557
|
+
false
|
|
558
|
+
);
|
|
559
|
+
}
|
|
560
|
+
const version = "3.3.0-beta.4";
|
|
561
|
+
async function initSourcepointCmp({
|
|
562
|
+
propertyConfig = FT_DOTCOM_PROD,
|
|
563
|
+
userId,
|
|
564
|
+
useFTSession = true,
|
|
565
|
+
consentProxyHost = FT_CONSENT_PROXY_HOST,
|
|
566
|
+
cookieDomain = FT_COOKIE_DOMAIN,
|
|
567
|
+
formOfWordsId = SOURCEPOINT_FOW_ID,
|
|
568
|
+
useConsentStore = true,
|
|
569
|
+
trackingContext = {}
|
|
570
|
+
} = {}) {
|
|
571
|
+
if (typeof window === "undefined") {
|
|
572
|
+
console.error("The CMP client can only be initialised in a browser context");
|
|
573
|
+
return;
|
|
574
|
+
}
|
|
575
|
+
window.FT_CMP_CLIENT_VERSION = version;
|
|
576
|
+
if (!userId && useFTSession) {
|
|
577
|
+
try {
|
|
578
|
+
const response = await getUuid();
|
|
579
|
+
userId = response == null ? void 0 : response.uuid;
|
|
580
|
+
} catch (error) {
|
|
581
|
+
console.error(error);
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
if (!(propertyConfig == null ? void 0 : propertyConfig.accountId)) {
|
|
585
|
+
throw new Error("Please pass a valid property config");
|
|
586
|
+
}
|
|
587
|
+
if (userId) {
|
|
588
|
+
propertyConfig.authId = userId;
|
|
589
|
+
}
|
|
590
|
+
if (propertyConfig.events) {
|
|
591
|
+
console.warn(
|
|
592
|
+
"[cmp-client] Passing an events map in the config is not supported and will be ignored. Please use window._sp_.addEventListener() to listen for events"
|
|
593
|
+
);
|
|
594
|
+
delete propertyConfig.events;
|
|
595
|
+
}
|
|
596
|
+
bootstrapCmp(propertyConfig);
|
|
597
|
+
window._sp_queue.push(() => {
|
|
598
|
+
var _a, _b;
|
|
599
|
+
(_b = (_a = window._sp_) == null ? void 0 : _a.addEventListener) == null ? void 0 : _b.call(
|
|
600
|
+
_a,
|
|
601
|
+
"onConsentReady",
|
|
602
|
+
consentReadyHandlerFn({
|
|
603
|
+
userId,
|
|
604
|
+
consentProxyHost,
|
|
605
|
+
cookieDomain,
|
|
606
|
+
formOfWordsId,
|
|
607
|
+
useConsentStore
|
|
608
|
+
})
|
|
609
|
+
);
|
|
610
|
+
});
|
|
611
|
+
initTracking(trackingContext, propertyConfig.baseEndpoint);
|
|
612
|
+
}
|
|
613
|
+
function updateFooterLinkCMP(propertyConfig = FT_DOTCOM_PROD) {
|
|
614
|
+
const { privacyManagerId, manageCookiesLinkOverride, manageCookiesSelector } = propertyConfig._clientOptions || {};
|
|
615
|
+
const footer = document.querySelector(manageCookiesSelector ?? "#site-footer");
|
|
616
|
+
if (footer && manageCookiesLinkOverride) {
|
|
617
|
+
footer.addEventListener("click", (event) => {
|
|
618
|
+
var _a, _b, _c;
|
|
619
|
+
const link = event.target.closest("a");
|
|
620
|
+
if ((_a = link == null ? void 0 : link.getAttribute("href")) == null ? void 0 : _a.endsWith(manageCookiesLinkOverride)) {
|
|
621
|
+
event.preventDefault();
|
|
622
|
+
(_c = (_b = window._sp_) == null ? void 0 : _b.gdpr) == null ? void 0 : _c.loadPrivacyManagerModal(privacyManagerId);
|
|
623
|
+
}
|
|
624
|
+
});
|
|
625
|
+
} else {
|
|
626
|
+
console.warn("No footer found for", manageCookiesSelector);
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
function containsRootDomain(hostname, rootDomain) {
|
|
630
|
+
if (!rootDomain || Number.isFinite(parseFloat(rootDomain))) {
|
|
631
|
+
return false;
|
|
632
|
+
}
|
|
633
|
+
if (!hostname.endsWith(rootDomain)) {
|
|
634
|
+
return false;
|
|
635
|
+
}
|
|
636
|
+
const index = hostname.indexOf(rootDomain);
|
|
637
|
+
const precedingChar = hostname[index - 1];
|
|
638
|
+
return precedingChar === "." || precedingChar === void 0;
|
|
639
|
+
}
|
|
640
|
+
function getPropertyConfigByHostname(hostname) {
|
|
641
|
+
if (!hostname) {
|
|
642
|
+
throw new Error("Invalid hostname provided");
|
|
643
|
+
}
|
|
644
|
+
const propertyValues = Object.values(properties);
|
|
645
|
+
let property = propertyValues.find((property2) => {
|
|
646
|
+
var _a;
|
|
647
|
+
const rootDomain = (_a = property2._clientOptions) == null ? void 0 : _a.rootDomain;
|
|
648
|
+
return rootDomain && containsRootDomain(hostname, rootDomain);
|
|
649
|
+
});
|
|
650
|
+
property ?? (property = FT_DOTCOM_TEST);
|
|
651
|
+
return property;
|
|
652
|
+
}
|
|
653
|
+
const url = new URL(window.location.href);
|
|
654
|
+
const activeHost = url.hostname;
|
|
655
|
+
const activeProperty = getPropertyConfigByHostname(activeHost);
|
|
656
|
+
initSourcepointCmp({
|
|
657
|
+
propertyConfig: activeProperty
|
|
658
|
+
});
|
|
659
|
+
updateFooterLinkCMP(activeProperty);
|
|
660
|
+
})();
|