@financial-times/cmp-client 4.2.0-beta.1 → 5.0.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.
Files changed (39) hide show
  1. package/dist/index.cjs +164 -50
  2. package/dist/index.js +164 -50
  3. package/dist/scripts/cmp-static.js +2 -1
  4. package/dist/src/client.d.ts +1 -1
  5. package/dist/src/client.d.ts.map +1 -1
  6. package/dist/src/consent-parsers/__fixtures__/gpp.d.ts +20 -0
  7. package/dist/src/consent-parsers/__fixtures__/gpp.d.ts.map +1 -0
  8. package/dist/src/consent-parsers/__tests__/index.test.d.ts +2 -0
  9. package/dist/src/consent-parsers/__tests__/index.test.d.ts.map +1 -0
  10. package/dist/src/consent-parsers/gpp/__tests__/parse-gpp-consent.test.d.ts +2 -0
  11. package/dist/src/consent-parsers/gpp/__tests__/parse-gpp-consent.test.d.ts.map +1 -0
  12. package/dist/src/consent-parsers/gpp/index.d.ts +3 -0
  13. package/dist/src/consent-parsers/gpp/index.d.ts.map +1 -0
  14. package/dist/src/consent-parsers/index.d.ts +6 -0
  15. package/dist/src/consent-parsers/index.d.ts.map +1 -0
  16. package/dist/src/consent-parsers/tcfv2/__tests__/check-consent.test.d.ts.map +1 -0
  17. package/dist/src/{consent-ready/utils/get-parsed-consent.d.ts → consent-parsers/tcfv2/index.d.ts} +2 -7
  18. package/dist/src/consent-parsers/tcfv2/index.d.ts.map +1 -0
  19. package/dist/src/consent-ready/index.d.ts.map +1 -1
  20. package/dist/src/html/cmp-scripts.d.ts +2 -2
  21. package/dist/src/html/cmp-scripts.d.ts.map +1 -1
  22. package/dist/src/lib/configurators/ft-dot-com.d.ts +7 -0
  23. package/dist/src/lib/configurators/ft-dot-com.d.ts.map +1 -0
  24. package/dist/src/lib/configurators/index.d.ts +14 -0
  25. package/dist/src/lib/configurators/index.d.ts.map +1 -0
  26. package/dist/src/lib/constants.d.ts +0 -4
  27. package/dist/src/lib/constants.d.ts.map +1 -1
  28. package/dist/src/lib/properties.d.ts +1 -12
  29. package/dist/src/lib/properties.d.ts.map +1 -1
  30. package/dist/src/utils/url.d.ts +1 -12
  31. package/dist/src/utils/url.d.ts.map +1 -1
  32. package/package.json +1 -1
  33. package/typings/globals.d.ts +2 -1
  34. package/typings/types.d.ts +81 -4
  35. package/dist/src/consent-ready/utils/__tests__/check-consent.test.d.ts.map +0 -1
  36. package/dist/src/consent-ready/utils/__tests__/get-parsed-consent.test.d.ts +0 -2
  37. package/dist/src/consent-ready/utils/__tests__/get-parsed-consent.test.d.ts.map +0 -1
  38. package/dist/src/consent-ready/utils/get-parsed-consent.d.ts.map +0 -1
  39. /package/dist/src/{consent-ready/utils → consent-parsers/tcfv2}/__tests__/check-consent.test.d.ts +0 -0
package/dist/index.cjs CHANGED
@@ -46,8 +46,7 @@ const debug = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.definePropert
46
46
  }, Symbol.toStringTag, { value: "Module" }));
47
47
  const defaults = {
48
48
  joinHref: true,
49
- gdpr: {},
50
- ccpa: {}
49
+ gdpr: {}
51
50
  };
52
51
  const FT_DOTCOM_TEST = {
53
52
  ...defaults,
@@ -56,7 +55,8 @@ const FT_DOTCOM_TEST = {
56
55
  propertyHref: "https://local.ft.com",
57
56
  _clientOptions: {
58
57
  privacyManagerId: 827767,
59
- manageCookiesLinkOverride: "ft.com/preferences/manage-cookies"
58
+ manageCookiesLinkOverride: "ft.com/preferences/manage-cookies",
59
+ rootDomain: "ft.com"
60
60
  }
61
61
  };
62
62
  const FT_DOTCOM_PROD = {
@@ -216,7 +216,7 @@ function interceptManageCookiesLinks({ _clientOptions } = FT_DOTCOM_PROD) {
216
216
  }
217
217
  },
218
218
  gdpr: {
219
- text: "Manage cookies",
219
+ text: "Manage Cookies",
220
220
  onClick: (e) => {
221
221
  var _a;
222
222
  e.preventDefault();
@@ -226,7 +226,6 @@ function interceptManageCookiesLinks({ _clientOptions } = FT_DOTCOM_PROD) {
226
226
  };
227
227
  const linkMap = /* @__PURE__ */ new Map();
228
228
  const onConsentReadyHandler = function(message_type, _uuid, _string, info) {
229
- console.log("handler called");
230
229
  findAndUpdateLinks(
231
230
  false,
232
231
  message_type,
@@ -243,6 +242,7 @@ function interceptManageCookiesLinks({ _clientOptions } = FT_DOTCOM_PROD) {
243
242
  }
244
243
  };
245
244
  findAndUpdateLinks(true, "gdpr", false, regionConfig, linkMap, manageCookiesLinkOverride);
245
+ window._sp_queue ?? (window._sp_queue = []);
246
246
  window._sp_queue.push(() => {
247
247
  var _a, _b;
248
248
  (_b = (_a = window._sp_).addEventListener) == null ? void 0 : _b.call(_a, "onConsentReady", onConsentReadyHandler);
@@ -373,20 +373,34 @@ const scriptSources = {
373
373
  };
374
374
  const scriptContent = {
375
375
  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()}();`,
376
- 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) } })();`
376
+ gppStub: `window.__gpp_addFrame=function(e){if(!window.frames[e])if(document.body){var t=document.createElement("iframe");t.style.cssText="display:none",t.name=e,document.body.appendChild(t)}else window.setTimeout(window.__gpp_addFrame,10,e)},window.__gpp_stub=function(){var e=arguments;if(__gpp.queue=__gpp.queue||[],__gpp.events=__gpp.events||[],!e.length||1==e.length&&"queue"==e[0])return __gpp.queue;if(1==e.length&&"events"==e[0])return __gpp.events;var t=e[0],p=e.length>1?e[1]:null,s=e.length>2?e[2]:null;if("ping"===t)p({gppVersion:"1.1",cmpStatus:"stub",cmpDisplayStatus:"hidden",signalStatus:"not ready",supportedAPIs:["2:tcfeuv2","5:tcfcav1","6:uspv1","7:usnatv1","8:uscav1","9:usvav1","10:uscov1","11:usutv1","12:usctv1"],cmpId:0,sectionList:[],applicableSections:[],gppString:"",parsedSections:{}},!0);else if("addEventListener"===t){"lastId"in __gpp||(__gpp.lastId=0),__gpp.lastId++;var n=__gpp.lastId;__gpp.events.push({id:n,callback:p,parameter:s}),p({eventName:"listenerRegistered",listenerId:n,data:!0,pingData:{gppVersion:"1.1",cmpStatus:"stub",cmpDisplayStatus:"hidden",signalStatus:"not ready",supportedAPIs:["2:tcfeuv2","5:tcfcav1","6:uspv1","7:usnatv1","8:uscav1","9:usvav1","10:uscov1","11:usutv1","12:usctv1"],cmpId:0,sectionList:[],applicableSections:[],gppString:"",parsedSections:{}}},!0)}else if("removeEventListener"===t){for(var a=!1,i=0;i<__gpp.events.length;i++)if(__gpp.events[i].id==s){__gpp.events.splice(i,1),a=!0;break}p({eventName:"listenerRemoved",listenerId:s,data:a,pingData:{gppVersion:"1.1",cmpStatus:"stub",cmpDisplayStatus:"hidden",signalStatus:"not ready",supportedAPIs:["2:tcfeuv2","5:tcfcav1","6:uspv1","7:usnatv1","8:uscav1","9:usvav1","10:uscov1","11:usutv1","12:usctv1"],cmpId:0,sectionList:[],applicableSections:[],gppString:"",parsedSections:{}}},!0)}else"hasSection"===t?p(!1,!0):"getSection"===t||"getField"===t?p(null,!0):__gpp.queue.push([].slice.apply(e))},window.__gpp_msghandler=function(e){var t="string"==typeof e.data;try{var p=t?JSON.parse(e.data):e.data}catch(e){p=null}if("object"==typeof p&&null!==p&&"__gppCall"in p){var s=p.__gppCall;window.__gpp(s.command,(function(p,n){var a={__gppReturn:{returnValue:p,success:n,callId:s.callId}};e.source.postMessage(t?JSON.stringify(a):a,"*")}),"parameter"in s?s.parameter:null,"version"in s?s.version:"1.1")}},"__gpp"in window&&"function"==typeof window.__gpp||(window.__gpp=window.__gpp_stub,window.addEventListener("message",window.__gpp_msghandler,!1),window.__gpp_addFrame("__gppLocator"));
377
+ `
377
378
  };
378
- function getCmpScripts() {
379
+ function getCmpScripts(options) {
379
380
  const fragment = document.createDocumentFragment();
380
381
  fragment.appendChild(createContentScript("tcf", scriptContent.tcfStub));
381
- fragment.appendChild(createContentScript("usp", scriptContent.uspStub));
382
+ if (options.includeUsNat) {
383
+ fragment.appendChild(createContentScript("gpp", scriptContent.gppStub));
384
+ }
382
385
  fragment.appendChild(createSourceScript(scriptSources.cmpFrames));
383
386
  return fragment;
384
387
  }
385
- function bootstrapCmp(config) {
388
+ function applyOptionsToConfig(propertyConfig, options) {
389
+ const { userId, includeUsNat } = options;
390
+ if (userId) {
391
+ propertyConfig.authId = userId;
392
+ }
393
+ if (includeUsNat) {
394
+ propertyConfig.usnat = propertyConfig.usnat || {};
395
+ }
396
+ return propertyConfig;
397
+ }
398
+ function bootstrapCmp(config, options) {
386
399
  const { _clientOptions, ...spConfig } = config;
387
- window._sp_ = { config: spConfig };
400
+ const runtimeConfig = applyOptionsToConfig(spConfig, options);
401
+ window._sp_ = { config: runtimeConfig };
388
402
  window._sp_queue ?? (window._sp_queue = []);
389
- document.head.appendChild(getCmpScripts());
403
+ document.head.appendChild(getCmpScripts(options));
390
404
  }
391
405
  function getConsentPayload(parsedConsent, updateConsentStore, { formOfWordsId, cookieDomain }) {
392
406
  const categoryNames = Object.keys(parsedConsent);
@@ -415,6 +429,89 @@ function getConsentPayload(parsedConsent, updateConsentStore, { formOfWordsId, c
415
429
  }
416
430
  return { data, cookieDomain };
417
431
  }
432
+ const sections = {
433
+ CALIFORNIA: "uscav1",
434
+ US_NATIONAL: "usnatv1"
435
+ };
436
+ const sectionConfig = {
437
+ [sections.CALIFORNIA]: {
438
+ excludedCategories: {
439
+ personalisedMarketing: true
440
+ }
441
+ },
442
+ [sections.US_NATIONAL]: {
443
+ excludedCategories: {
444
+ personalisedMarketing: true
445
+ }
446
+ }
447
+ };
448
+ function getApplicableSection(gppData) {
449
+ const { applicableSections, supportedAPIs } = gppData;
450
+ const applicableSection = applicableSections[0];
451
+ if (applicableSection === -1 || typeof applicableSection === "undefined") {
452
+ return;
453
+ }
454
+ const sectionApi = supportedAPIs.find((api) => api.startsWith(`${applicableSection}:`));
455
+ const sectionKey = sectionApi == null ? void 0 : sectionApi.split(":")[1];
456
+ return sectionKey;
457
+ }
458
+ function parseCaliforniaSection(sectionData) {
459
+ const { SaleOptOut, SharingOptOut, Gpc } = sectionData;
460
+ const { excludedCategories } = sectionConfig[sections.CALIFORNIA];
461
+ const userHasNotOptedOut = SaleOptOut !== 1 && SharingOptOut !== 1 && !Gpc;
462
+ const parsedConsent = {};
463
+ for (const categoryName of iabCategoryNames) {
464
+ if (categoryName in excludedCategories) {
465
+ parsedConsent[categoryName] = true;
466
+ continue;
467
+ }
468
+ parsedConsent[categoryName] = userHasNotOptedOut;
469
+ }
470
+ return parsedConsent;
471
+ }
472
+ function parseUsNationalSection(sectionData) {
473
+ const { SaleOptOut, SharingOptOut, TargetedAdvertisingOptOut } = sectionData;
474
+ const { excludedCategories } = sectionConfig[sections.US_NATIONAL];
475
+ const userHasNotOptedOut = SaleOptOut !== 1 && SharingOptOut !== 1 && TargetedAdvertisingOptOut !== 1;
476
+ const parsedConsent = {};
477
+ for (const categoryName of iabCategoryNames) {
478
+ if (categoryName in excludedCategories) {
479
+ parsedConsent[categoryName] = true;
480
+ continue;
481
+ }
482
+ parsedConsent[categoryName] = userHasNotOptedOut;
483
+ }
484
+ return parsedConsent;
485
+ }
486
+ const sectionParsers = {
487
+ [sections.CALIFORNIA]: parseCaliforniaSection,
488
+ [sections.US_NATIONAL]: parseUsNationalSection
489
+ };
490
+ async function parseGPPConsent() {
491
+ const gppData = await new Promise((resolve, reject) => {
492
+ try {
493
+ if (window.__gpp) {
494
+ window.__gpp("ping", resolve);
495
+ } else {
496
+ reject(new Error("GPP API is not available on page"));
497
+ }
498
+ } catch (error) {
499
+ reject(error);
500
+ }
501
+ });
502
+ const { parsedSections } = gppData;
503
+ const applicableSection = getApplicableSection(gppData);
504
+ if (!applicableSection || !parsedSections[applicableSection]) {
505
+ throw new Error("GPP parser was called without an applicable section");
506
+ }
507
+ const sectionData = parsedSections[applicableSection];
508
+ const parseSection = sectionParsers[applicableSection];
509
+ if (!parseSection || typeof parseSection !== "function") {
510
+ throw new Error(`No parser found for applicable GPP section: ${applicableSection}`);
511
+ }
512
+ const parsedConsent = parseSection(sectionData);
513
+ return parsedConsent;
514
+ }
418
515
  function checkConsentFor(categoryName, { purpose, vendor, specialFeatureOptins }) {
419
516
  const customCategory = iabCustomCategories[categoryName];
420
517
  const requiredPurposesConsented = customCategory.purposes.every(
@@ -428,19 +525,14 @@ function checkConsentFor(categoryName, { purpose, vendor, specialFeatureOptins }
428
525
  );
429
526
  return requiredPurposesConsented && requiredIabVendorsConsented && requiredSpecialFeaturesConsented;
430
527
  }
431
- function parseCCPAConsent(ccpa) {
432
- const userHasNotOptedOut = (ccpa == null ? void 0 : ccpa[2]) === "N";
433
- const parsedConsent = {};
434
- for (const categoryName of iabCategoryNames) {
435
- parsedConsent[categoryName] = userHasNotOptedOut;
436
- }
437
- return parsedConsent;
438
- }
439
528
  async function parseGDPRConsent() {
440
529
  const tcData = await new Promise((resolve, reject) => {
441
- var _a;
442
530
  try {
443
- (_a = window.__tcfapi) == null ? void 0 : _a.call(window, "addEventListener", 2, resolve);
531
+ if (window.__tcfapi) {
532
+ window.__tcfapi("addEventListener", 2, resolve);
533
+ } else {
534
+ reject(new Error("TCF API is not available on page"));
535
+ }
444
536
  } catch (error) {
445
537
  reject(error);
446
538
  }
@@ -451,8 +543,16 @@ async function parseGDPRConsent() {
451
543
  }
452
544
  return parsedConsent;
453
545
  }
454
- async function getParsedConsent(activeLegislation, consentString) {
455
- return activeLegislation === "ccpa" ? parseCCPAConsent(consentString) : await parseGDPRConsent();
546
+ async function getParsedConsent(activeLegislation) {
547
+ const legislationParsers = {
548
+ gdpr: parseGDPRConsent,
549
+ usnat: parseGPPConsent
550
+ };
551
+ const consentParser = legislationParsers[activeLegislation];
552
+ if (!consentParser) {
553
+ throw new Error("Unable to update user consent. Unsupported consent legislation.");
554
+ }
555
+ return consentParser();
456
556
  }
457
557
  function getConsentCookieValue() {
458
558
  const cookies = Object.fromEntries(
@@ -508,11 +608,22 @@ function consentReadyHandlerFn(props) {
508
608
  if (props.disableFTCookies) {
509
609
  return;
510
610
  }
611
+ if (!props.includeUsNat && legislation === "usnat") {
612
+ return;
613
+ }
511
614
  const activeLegislation = consentMeta.applies ? legislation : "gdpr";
512
615
  if (activeLegislation !== legislation || !consentString) {
513
616
  return;
514
617
  }
515
- const parsedConsent = await getParsedConsent(activeLegislation, consentString);
618
+ let parsedConsent;
619
+ try {
620
+ parsedConsent = await getParsedConsent(activeLegislation);
621
+ } catch (error) {
622
+ console.error(error);
623
+ }
624
+ if (!parsedConsent) {
625
+ return;
626
+ }
516
627
  const consentHasChanged = hasConsentChanged(parsedConsent, getConsentCookieValue());
517
628
  if (!consentHasChanged) {
518
629
  return;
@@ -687,18 +798,19 @@ function setupPmTracking(trackingProps, cmpBaseEndpoint) {
687
798
  false
688
799
  );
689
800
  }
690
- const version = "4.2.0-beta.1";
691
- async function initSourcepointCmp({
692
- propertyConfig = FT_DOTCOM_PROD,
693
- userId,
694
- useFTSession = true,
695
- consentProxyHost = FT_CONSENT_PROXY_HOST,
696
- cookieDomain = FT_COOKIE_DOMAIN,
697
- formOfWordsId = SOURCEPOINT_FOW_ID,
698
- useConsentStore = true,
699
- trackingContext = {},
700
- disableFTCookies = false
701
- } = {}) {
801
+ const version = "5.0.1";
802
+ async function initSourcepointCmp(options) {
803
+ const {
804
+ useFTSession = true,
805
+ consentProxyHost = FT_CONSENT_PROXY_HOST,
806
+ cookieDomain = FT_COOKIE_DOMAIN,
807
+ formOfWordsId = SOURCEPOINT_FOW_ID,
808
+ useConsentStore = true,
809
+ trackingContext = {},
810
+ disableFTCookies = false,
811
+ includeUsNat = false
812
+ } = options || {};
813
+ let { propertyConfig = FT_DOTCOM_PROD, userId } = options || {};
702
814
  if (typeof window === "undefined") {
703
815
  console.error("The CMP client can only be initialised in a browser context");
704
816
  return;
@@ -712,19 +824,8 @@ async function initSourcepointCmp({
712
824
  console.error(error);
713
825
  }
714
826
  }
715
- if (!(propertyConfig == null ? void 0 : propertyConfig.accountId)) {
716
- throw new Error("Please pass a valid property config");
717
- }
718
- if (userId) {
719
- propertyConfig.authId = userId;
720
- }
721
- if (propertyConfig.events) {
722
- console.warn(
723
- "[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"
724
- );
725
- delete propertyConfig.events;
726
- }
727
- bootstrapCmp(propertyConfig);
827
+ propertyConfig = validateAndCleanConfig(propertyConfig);
828
+ bootstrapCmp(propertyConfig, { userId, includeUsNat });
728
829
  window._sp_queue.push(() => {
729
830
  var _a, _b;
730
831
  (_b = (_a = window._sp_) == null ? void 0 : _a.addEventListener) == null ? void 0 : _b.call(
@@ -736,12 +837,25 @@ async function initSourcepointCmp({
736
837
  cookieDomain,
737
838
  formOfWordsId,
738
839
  useConsentStore,
739
- disableFTCookies
840
+ disableFTCookies,
841
+ includeUsNat
740
842
  })
741
843
  );
742
844
  });
743
845
  initTracking(trackingContext, propertyConfig.baseEndpoint);
744
846
  }
847
+ function validateAndCleanConfig(propertyConfig) {
848
+ if (!(propertyConfig == null ? void 0 : propertyConfig.accountId)) {
849
+ throw new Error("Please pass a valid property config");
850
+ }
851
+ if (propertyConfig.events) {
852
+ console.warn(
853
+ "[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"
854
+ );
855
+ delete propertyConfig.events;
856
+ }
857
+ return propertyConfig;
858
+ }
745
859
  exports.debug = debug;
746
860
  exports.initSourcepointCmp = initSourcepointCmp;
747
861
  exports.interceptManageCookiesLinks = interceptManageCookiesLinks;
package/dist/index.js CHANGED
@@ -44,8 +44,7 @@ const debug = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.definePropert
44
44
  }, Symbol.toStringTag, { value: "Module" }));
45
45
  const defaults = {
46
46
  joinHref: true,
47
- gdpr: {},
48
- ccpa: {}
47
+ gdpr: {}
49
48
  };
50
49
  const FT_DOTCOM_TEST = {
51
50
  ...defaults,
@@ -54,7 +53,8 @@ const FT_DOTCOM_TEST = {
54
53
  propertyHref: "https://local.ft.com",
55
54
  _clientOptions: {
56
55
  privacyManagerId: 827767,
57
- manageCookiesLinkOverride: "ft.com/preferences/manage-cookies"
56
+ manageCookiesLinkOverride: "ft.com/preferences/manage-cookies",
57
+ rootDomain: "ft.com"
58
58
  }
59
59
  };
60
60
  const FT_DOTCOM_PROD = {
@@ -214,7 +214,7 @@ function interceptManageCookiesLinks({ _clientOptions } = FT_DOTCOM_PROD) {
214
214
  }
215
215
  },
216
216
  gdpr: {
217
- text: "Manage cookies",
217
+ text: "Manage Cookies",
218
218
  onClick: (e) => {
219
219
  var _a;
220
220
  e.preventDefault();
@@ -224,7 +224,6 @@ function interceptManageCookiesLinks({ _clientOptions } = FT_DOTCOM_PROD) {
224
224
  };
225
225
  const linkMap = /* @__PURE__ */ new Map();
226
226
  const onConsentReadyHandler = function(message_type, _uuid, _string, info) {
227
- console.log("handler called");
228
227
  findAndUpdateLinks(
229
228
  false,
230
229
  message_type,
@@ -241,6 +240,7 @@ function interceptManageCookiesLinks({ _clientOptions } = FT_DOTCOM_PROD) {
241
240
  }
242
241
  };
243
242
  findAndUpdateLinks(true, "gdpr", false, regionConfig, linkMap, manageCookiesLinkOverride);
243
+ window._sp_queue ?? (window._sp_queue = []);
244
244
  window._sp_queue.push(() => {
245
245
  var _a, _b;
246
246
  (_b = (_a = window._sp_).addEventListener) == null ? void 0 : _b.call(_a, "onConsentReady", onConsentReadyHandler);
@@ -371,20 +371,34 @@ const scriptSources = {
371
371
  };
372
372
  const scriptContent = {
373
373
  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()}();`,
374
- 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) } })();`
374
+ gppStub: `window.__gpp_addFrame=function(e){if(!window.frames[e])if(document.body){var t=document.createElement("iframe");t.style.cssText="display:none",t.name=e,document.body.appendChild(t)}else window.setTimeout(window.__gpp_addFrame,10,e)},window.__gpp_stub=function(){var e=arguments;if(__gpp.queue=__gpp.queue||[],__gpp.events=__gpp.events||[],!e.length||1==e.length&&"queue"==e[0])return __gpp.queue;if(1==e.length&&"events"==e[0])return __gpp.events;var t=e[0],p=e.length>1?e[1]:null,s=e.length>2?e[2]:null;if("ping"===t)p({gppVersion:"1.1",cmpStatus:"stub",cmpDisplayStatus:"hidden",signalStatus:"not ready",supportedAPIs:["2:tcfeuv2","5:tcfcav1","6:uspv1","7:usnatv1","8:uscav1","9:usvav1","10:uscov1","11:usutv1","12:usctv1"],cmpId:0,sectionList:[],applicableSections:[],gppString:"",parsedSections:{}},!0);else if("addEventListener"===t){"lastId"in __gpp||(__gpp.lastId=0),__gpp.lastId++;var n=__gpp.lastId;__gpp.events.push({id:n,callback:p,parameter:s}),p({eventName:"listenerRegistered",listenerId:n,data:!0,pingData:{gppVersion:"1.1",cmpStatus:"stub",cmpDisplayStatus:"hidden",signalStatus:"not ready",supportedAPIs:["2:tcfeuv2","5:tcfcav1","6:uspv1","7:usnatv1","8:uscav1","9:usvav1","10:uscov1","11:usutv1","12:usctv1"],cmpId:0,sectionList:[],applicableSections:[],gppString:"",parsedSections:{}}},!0)}else if("removeEventListener"===t){for(var a=!1,i=0;i<__gpp.events.length;i++)if(__gpp.events[i].id==s){__gpp.events.splice(i,1),a=!0;break}p({eventName:"listenerRemoved",listenerId:s,data:a,pingData:{gppVersion:"1.1",cmpStatus:"stub",cmpDisplayStatus:"hidden",signalStatus:"not ready",supportedAPIs:["2:tcfeuv2","5:tcfcav1","6:uspv1","7:usnatv1","8:uscav1","9:usvav1","10:uscov1","11:usutv1","12:usctv1"],cmpId:0,sectionList:[],applicableSections:[],gppString:"",parsedSections:{}}},!0)}else"hasSection"===t?p(!1,!0):"getSection"===t||"getField"===t?p(null,!0):__gpp.queue.push([].slice.apply(e))},window.__gpp_msghandler=function(e){var t="string"==typeof e.data;try{var p=t?JSON.parse(e.data):e.data}catch(e){p=null}if("object"==typeof p&&null!==p&&"__gppCall"in p){var s=p.__gppCall;window.__gpp(s.command,(function(p,n){var a={__gppReturn:{returnValue:p,success:n,callId:s.callId}};e.source.postMessage(t?JSON.stringify(a):a,"*")}),"parameter"in s?s.parameter:null,"version"in s?s.version:"1.1")}},"__gpp"in window&&"function"==typeof window.__gpp||(window.__gpp=window.__gpp_stub,window.addEventListener("message",window.__gpp_msghandler,!1),window.__gpp_addFrame("__gppLocator"));
375
+ `
375
376
  };
376
- function getCmpScripts() {
377
+ function getCmpScripts(options) {
377
378
  const fragment = document.createDocumentFragment();
378
379
  fragment.appendChild(createContentScript("tcf", scriptContent.tcfStub));
379
- fragment.appendChild(createContentScript("usp", scriptContent.uspStub));
380
+ if (options.includeUsNat) {
381
+ fragment.appendChild(createContentScript("gpp", scriptContent.gppStub));
382
+ }
380
383
  fragment.appendChild(createSourceScript(scriptSources.cmpFrames));
381
384
  return fragment;
382
385
  }
383
- function bootstrapCmp(config) {
386
+ function applyOptionsToConfig(propertyConfig, options) {
387
+ const { userId, includeUsNat } = options;
388
+ if (userId) {
389
+ propertyConfig.authId = userId;
390
+ }
391
+ if (includeUsNat) {
392
+ propertyConfig.usnat = propertyConfig.usnat || {};
393
+ }
394
+ return propertyConfig;
395
+ }
396
+ function bootstrapCmp(config, options) {
384
397
  const { _clientOptions, ...spConfig } = config;
385
- window._sp_ = { config: spConfig };
398
+ const runtimeConfig = applyOptionsToConfig(spConfig, options);
399
+ window._sp_ = { config: runtimeConfig };
386
400
  window._sp_queue ?? (window._sp_queue = []);
387
- document.head.appendChild(getCmpScripts());
401
+ document.head.appendChild(getCmpScripts(options));
388
402
  }
389
403
  function getConsentPayload(parsedConsent, updateConsentStore, { formOfWordsId, cookieDomain }) {
390
404
  const categoryNames = Object.keys(parsedConsent);
@@ -413,6 +427,89 @@ function getConsentPayload(parsedConsent, updateConsentStore, { formOfWordsId, c
413
427
  }
414
428
  return { data, cookieDomain };
415
429
  }
430
+ const sections = {
431
+ CALIFORNIA: "uscav1",
432
+ US_NATIONAL: "usnatv1"
433
+ };
434
+ const sectionConfig = {
435
+ [sections.CALIFORNIA]: {
436
+ excludedCategories: {
437
+ personalisedMarketing: true
438
+ }
439
+ },
440
+ [sections.US_NATIONAL]: {
441
+ excludedCategories: {
442
+ personalisedMarketing: true
443
+ }
444
+ }
445
+ };
446
+ function getApplicableSection(gppData) {
447
+ const { applicableSections, supportedAPIs } = gppData;
448
+ const applicableSection = applicableSections[0];
449
+ if (applicableSection === -1 || typeof applicableSection === "undefined") {
450
+ return;
451
+ }
452
+ const sectionApi = supportedAPIs.find((api) => api.startsWith(`${applicableSection}:`));
453
+ const sectionKey = sectionApi == null ? void 0 : sectionApi.split(":")[1];
454
+ return sectionKey;
455
+ }
456
+ function parseCaliforniaSection(sectionData) {
457
+ const { SaleOptOut, SharingOptOut, Gpc } = sectionData;
458
+ const { excludedCategories } = sectionConfig[sections.CALIFORNIA];
459
+ const userHasNotOptedOut = SaleOptOut !== 1 && SharingOptOut !== 1 && !Gpc;
460
+ const parsedConsent = {};
461
+ for (const categoryName of iabCategoryNames) {
462
+ if (categoryName in excludedCategories) {
463
+ parsedConsent[categoryName] = true;
464
+ continue;
465
+ }
466
+ parsedConsent[categoryName] = userHasNotOptedOut;
467
+ }
468
+ return parsedConsent;
469
+ }
470
+ function parseUsNationalSection(sectionData) {
471
+ const { SaleOptOut, SharingOptOut, TargetedAdvertisingOptOut } = sectionData;
472
+ const { excludedCategories } = sectionConfig[sections.US_NATIONAL];
473
+ const userHasNotOptedOut = SaleOptOut !== 1 && SharingOptOut !== 1 && TargetedAdvertisingOptOut !== 1;
474
+ const parsedConsent = {};
475
+ for (const categoryName of iabCategoryNames) {
476
+ if (categoryName in excludedCategories) {
477
+ parsedConsent[categoryName] = true;
478
+ continue;
479
+ }
480
+ parsedConsent[categoryName] = userHasNotOptedOut;
481
+ }
482
+ return parsedConsent;
483
+ }
484
+ const sectionParsers = {
485
+ [sections.CALIFORNIA]: parseCaliforniaSection,
486
+ [sections.US_NATIONAL]: parseUsNationalSection
487
+ };
488
+ async function parseGPPConsent() {
489
+ const gppData = await new Promise((resolve, reject) => {
490
+ try {
491
+ if (window.__gpp) {
492
+ window.__gpp("ping", resolve);
493
+ } else {
494
+ reject(new Error("GPP API is not available on page"));
495
+ }
496
+ } catch (error) {
497
+ reject(error);
498
+ }
499
+ });
500
+ const { parsedSections } = gppData;
501
+ const applicableSection = getApplicableSection(gppData);
502
+ if (!applicableSection || !parsedSections[applicableSection]) {
503
+ throw new Error("GPP parser was called without an applicable section");
504
+ }
505
+ const sectionData = parsedSections[applicableSection];
506
+ const parseSection = sectionParsers[applicableSection];
507
+ if (!parseSection || typeof parseSection !== "function") {
508
+ throw new Error(`No parser found for applicable GPP section: ${applicableSection}`);
509
+ }
510
+ const parsedConsent = parseSection(sectionData);
511
+ return parsedConsent;
512
+ }
416
513
  function checkConsentFor(categoryName, { purpose, vendor, specialFeatureOptins }) {
417
514
  const customCategory = iabCustomCategories[categoryName];
418
515
  const requiredPurposesConsented = customCategory.purposes.every(
@@ -426,19 +523,14 @@ function checkConsentFor(categoryName, { purpose, vendor, specialFeatureOptins }
426
523
  );
427
524
  return requiredPurposesConsented && requiredIabVendorsConsented && requiredSpecialFeaturesConsented;
428
525
  }
429
- function parseCCPAConsent(ccpa) {
430
- const userHasNotOptedOut = (ccpa == null ? void 0 : ccpa[2]) === "N";
431
- const parsedConsent = {};
432
- for (const categoryName of iabCategoryNames) {
433
- parsedConsent[categoryName] = userHasNotOptedOut;
434
- }
435
- return parsedConsent;
436
- }
437
526
  async function parseGDPRConsent() {
438
527
  const tcData = await new Promise((resolve, reject) => {
439
- var _a;
440
528
  try {
441
- (_a = window.__tcfapi) == null ? void 0 : _a.call(window, "addEventListener", 2, resolve);
529
+ if (window.__tcfapi) {
530
+ window.__tcfapi("addEventListener", 2, resolve);
531
+ } else {
532
+ reject(new Error("TCF API is not available on page"));
533
+ }
442
534
  } catch (error) {
443
535
  reject(error);
444
536
  }
@@ -449,8 +541,16 @@ async function parseGDPRConsent() {
449
541
  }
450
542
  return parsedConsent;
451
543
  }
452
- async function getParsedConsent(activeLegislation, consentString) {
453
- return activeLegislation === "ccpa" ? parseCCPAConsent(consentString) : await parseGDPRConsent();
544
+ async function getParsedConsent(activeLegislation) {
545
+ const legislationParsers = {
546
+ gdpr: parseGDPRConsent,
547
+ usnat: parseGPPConsent
548
+ };
549
+ const consentParser = legislationParsers[activeLegislation];
550
+ if (!consentParser) {
551
+ throw new Error("Unable to update user consent. Unsupported consent legislation.");
552
+ }
553
+ return consentParser();
454
554
  }
455
555
  function getConsentCookieValue() {
456
556
  const cookies = Object.fromEntries(
@@ -506,11 +606,22 @@ function consentReadyHandlerFn(props) {
506
606
  if (props.disableFTCookies) {
507
607
  return;
508
608
  }
609
+ if (!props.includeUsNat && legislation === "usnat") {
610
+ return;
611
+ }
509
612
  const activeLegislation = consentMeta.applies ? legislation : "gdpr";
510
613
  if (activeLegislation !== legislation || !consentString) {
511
614
  return;
512
615
  }
513
- const parsedConsent = await getParsedConsent(activeLegislation, consentString);
616
+ let parsedConsent;
617
+ try {
618
+ parsedConsent = await getParsedConsent(activeLegislation);
619
+ } catch (error) {
620
+ console.error(error);
621
+ }
622
+ if (!parsedConsent) {
623
+ return;
624
+ }
514
625
  const consentHasChanged = hasConsentChanged(parsedConsent, getConsentCookieValue());
515
626
  if (!consentHasChanged) {
516
627
  return;
@@ -685,18 +796,19 @@ function setupPmTracking(trackingProps, cmpBaseEndpoint) {
685
796
  false
686
797
  );
687
798
  }
688
- const version = "4.2.0-beta.1";
689
- async function initSourcepointCmp({
690
- propertyConfig = FT_DOTCOM_PROD,
691
- userId,
692
- useFTSession = true,
693
- consentProxyHost = FT_CONSENT_PROXY_HOST,
694
- cookieDomain = FT_COOKIE_DOMAIN,
695
- formOfWordsId = SOURCEPOINT_FOW_ID,
696
- useConsentStore = true,
697
- trackingContext = {},
698
- disableFTCookies = false
699
- } = {}) {
799
+ const version = "5.0.1";
800
+ async function initSourcepointCmp(options) {
801
+ const {
802
+ useFTSession = true,
803
+ consentProxyHost = FT_CONSENT_PROXY_HOST,
804
+ cookieDomain = FT_COOKIE_DOMAIN,
805
+ formOfWordsId = SOURCEPOINT_FOW_ID,
806
+ useConsentStore = true,
807
+ trackingContext = {},
808
+ disableFTCookies = false,
809
+ includeUsNat = false
810
+ } = options || {};
811
+ let { propertyConfig = FT_DOTCOM_PROD, userId } = options || {};
700
812
  if (typeof window === "undefined") {
701
813
  console.error("The CMP client can only be initialised in a browser context");
702
814
  return;
@@ -710,19 +822,8 @@ async function initSourcepointCmp({
710
822
  console.error(error);
711
823
  }
712
824
  }
713
- if (!(propertyConfig == null ? void 0 : propertyConfig.accountId)) {
714
- throw new Error("Please pass a valid property config");
715
- }
716
- if (userId) {
717
- propertyConfig.authId = userId;
718
- }
719
- if (propertyConfig.events) {
720
- console.warn(
721
- "[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"
722
- );
723
- delete propertyConfig.events;
724
- }
725
- bootstrapCmp(propertyConfig);
825
+ propertyConfig = validateAndCleanConfig(propertyConfig);
826
+ bootstrapCmp(propertyConfig, { userId, includeUsNat });
726
827
  window._sp_queue.push(() => {
727
828
  var _a, _b;
728
829
  (_b = (_a = window._sp_) == null ? void 0 : _a.addEventListener) == null ? void 0 : _b.call(
@@ -734,12 +835,25 @@ async function initSourcepointCmp({
734
835
  cookieDomain,
735
836
  formOfWordsId,
736
837
  useConsentStore,
737
- disableFTCookies
838
+ disableFTCookies,
839
+ includeUsNat
738
840
  })
739
841
  );
740
842
  });
741
843
  initTracking(trackingContext, propertyConfig.baseEndpoint);
742
844
  }
845
+ function validateAndCleanConfig(propertyConfig) {
846
+ if (!(propertyConfig == null ? void 0 : propertyConfig.accountId)) {
847
+ throw new Error("Please pass a valid property config");
848
+ }
849
+ if (propertyConfig.events) {
850
+ console.warn(
851
+ "[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"
852
+ );
853
+ delete propertyConfig.events;
854
+ }
855
+ return propertyConfig;
856
+ }
743
857
  export {
744
858
  debug,
745
859
  initSourcepointCmp,