@financial-times/cmp-client 3.2.0 → 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.
Files changed (50) hide show
  1. package/README.md +17 -2
  2. package/dist/cmp-static.js +660 -0
  3. package/dist/index.cjs +205 -2761
  4. package/dist/index.js +205 -2761
  5. package/dist/src/client.d.ts.map +1 -1
  6. package/dist/src/cmp-static.d.ts +2 -0
  7. package/dist/src/cmp-static.d.ts.map +1 -0
  8. package/dist/src/consent-ready/index.d.ts.map +1 -1
  9. package/dist/src/consent-ready/utils/__fixtures__/helpers.d.ts +27 -0
  10. package/dist/src/consent-ready/utils/__fixtures__/helpers.d.ts.map +1 -0
  11. package/dist/src/consent-ready/utils/__fixtures__/strings.d.ts +4 -8
  12. package/dist/src/consent-ready/utils/__fixtures__/strings.d.ts.map +1 -1
  13. package/dist/src/consent-ready/utils/__tests__/consent.test.d.ts +2 -0
  14. package/dist/src/consent-ready/utils/__tests__/consent.test.d.ts.map +1 -0
  15. package/dist/src/consent-ready/utils/consent.d.ts +6 -0
  16. package/dist/src/consent-ready/utils/consent.d.ts.map +1 -0
  17. package/dist/src/consent-ready/utils/get-consent-payload.d.ts +7 -7
  18. package/dist/src/consent-ready/utils/get-consent-payload.d.ts.map +1 -1
  19. package/dist/src/consent-ready/utils/get-parsed-consent.d.ts +6 -5
  20. package/dist/src/consent-ready/utils/get-parsed-consent.d.ts.map +1 -1
  21. package/dist/src/consent-ready/utils/validators.d.ts +0 -1
  22. package/dist/src/consent-ready/utils/validators.d.ts.map +1 -1
  23. package/dist/src/html/cmp-footer-link.d.ts +8 -4
  24. package/dist/src/html/cmp-footer-link.d.ts.map +1 -1
  25. package/dist/src/html/cmp-scripts.d.ts.map +1 -1
  26. package/dist/src/index.d.ts +1 -1
  27. package/dist/src/index.d.ts.map +1 -1
  28. package/dist/src/lib/constants.d.ts +33 -2
  29. package/dist/src/lib/constants.d.ts.map +1 -1
  30. package/dist/src/lib/properties.d.ts +36 -0
  31. package/dist/src/lib/properties.d.ts.map +1 -1
  32. package/dist/src/tracking/index.d.ts +1 -1
  33. package/dist/src/tracking/index.d.ts.map +1 -1
  34. package/dist/src/tracking/state.d.ts +6 -0
  35. package/dist/src/tracking/state.d.ts.map +1 -1
  36. package/dist/src/utils/__tests__/url.test.d.ts +2 -0
  37. package/dist/src/utils/__tests__/url.test.d.ts.map +1 -0
  38. package/dist/src/utils/url.d.ts +126 -0
  39. package/dist/src/utils/url.d.ts.map +1 -0
  40. package/package.json +11 -11
  41. package/typings/globals.d.ts +4 -0
  42. package/typings/types.d.ts +54 -4
  43. package/dist/src/consent-ready/utils/has-consent-changed.d.ts +0 -3
  44. package/dist/src/consent-ready/utils/has-consent-changed.d.ts.map +0 -1
  45. package/dist/src/html/__tests__/cmp-footer-links.test.d.ts +0 -2
  46. package/dist/src/html/__tests__/cmp-footer-links.test.d.ts.map +0 -1
  47. package/dist/src/lib/enableNonEssentialCookiesOption.d.ts +0 -2
  48. package/dist/src/lib/enableNonEssentialCookiesOption.d.ts.map +0 -1
  49. package/dist/src/utils/cookie.d.ts +0 -2
  50. package/dist/src/utils/cookie.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 { initSourcepointCmp, properties, debug } from "@financial-times/cmp-client";
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 { initSourcepointCmp, properties, debug } from "@financial-times/cmp-client";
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
+ })();