@phantomstudios/ft-lib 0.5.2-rc8 → 0.5.2-rc9

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.
@@ -1,25 +1,9 @@
1
- declare global {
2
- interface Window {
3
- __tcfapi?: (command: string, version: number, callback: (tcData: TCFData | null, success: boolean) => void, parameter?: any) => void;
4
- }
5
- }
6
- interface TCFData {
7
- eventStatus: "tcloaded" | "useractioncomplete" | "cmpuishown";
8
- listenerId: number;
9
- purpose?: {
10
- consents?: Record<number, boolean>;
11
- };
12
- vendor?: {
13
- consents?: Record<number, boolean>;
14
- };
15
- gdprApplies?: boolean | undefined;
16
- }
17
1
  export interface VendorConsentResults {
18
2
  brandmetrics: boolean;
19
3
  linkedIn: boolean;
20
4
  purpose1: boolean;
21
5
  gdprApplies: boolean;
6
+ usnatApplies?: boolean;
22
7
  }
23
8
  export declare function initVendorConsentListener(onConsentUpdate: (results: VendorConsentResults) => void): void;
24
- export {};
25
9
  //# sourceMappingURL=vendorConsent.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"vendorConsent.d.ts","sourceRoot":"","sources":["../../src/cmp/vendorConsent.ts"],"names":[],"mappings":"AACA,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,MAAM;QACd,QAAQ,CAAC,EAAE,CACT,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,CAAC,MAAM,EAAE,OAAO,GAAG,IAAI,EAAE,OAAO,EAAE,OAAO,KAAK,IAAI,EAC5D,SAAS,CAAC,EAAE,GAAG,KACZ,IAAI,CAAC;KACX;CACF;AAED,UAAU,OAAO;IACf,WAAW,EAAE,UAAU,GAAG,oBAAoB,GAAG,YAAY,CAAC;IAC9D,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE;QACR,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;KACpC,CAAC;IACF,MAAM,CAAC,EAAE;QACP,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;KACpC,CAAC;IACF,WAAW,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CACnC;AAED,MAAM,WAAW,oBAAoB;IACnC,YAAY,EAAE,OAAO,CAAC;IACtB,QAAQ,EAAE,OAAO,CAAC;IAClB,QAAQ,EAAE,OAAO,CAAC;IAClB,WAAW,EAAE,OAAO,CAAC;CACtB;AAED,wBAAgB,yBAAyB,CACvC,eAAe,EAAE,CAAC,OAAO,EAAE,oBAAoB,KAAK,IAAI,GACvD,IAAI,CA+CN"}
1
+ {"version":3,"file":"vendorConsent.d.ts","sourceRoot":"","sources":["../../src/cmp/vendorConsent.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,oBAAoB;IACnC,YAAY,EAAE,OAAO,CAAC;IACtB,QAAQ,EAAE,OAAO,CAAC;IAClB,QAAQ,EAAE,OAAO,CAAC;IAClB,WAAW,EAAE,OAAO,CAAC;IACrB,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAED,wBAAgB,yBAAyB,CACvC,eAAe,EAAE,CAAC,OAAO,EAAE,oBAAoB,KAAK,IAAI,GACvD,IAAI,CAqDN"}
@@ -2,38 +2,89 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.initVendorConsentListener = initVendorConsentListener;
4
4
  function initVendorConsentListener(onConsentUpdate) {
5
- if (typeof window.__tcfapi !== "function") {
6
- console.warn("[CMP] window.__tcfapi is not defined on this page yet.");
5
+ // --- Path 1: Check TCF API First ---
6
+ if (typeof window.__tcfapi === "function") {
7
+ window.__tcfapi("addEventListener", 2, function (tcData, success) {
8
+ var _a, _b, _c, _d, _e, _f, _g, _h;
9
+ if (!success || !tcData)
10
+ return;
11
+ var isConsentReady = tcData.eventStatus === "tcloaded" ||
12
+ tcData.eventStatus === "useractioncomplete";
13
+ if (isConsentReady) {
14
+ // If GDPR actually applies, process TCF consents (Opt-In model)
15
+ if (tcData.gdprApplies !== false) {
16
+ var results = {
17
+ brandmetrics: ((_b = (_a = tcData.vendor) === null || _a === void 0 ? void 0 : _a.consents) === null || _b === void 0 ? void 0 : _b[422]) === true,
18
+ linkedIn: ((_d = (_c = tcData.vendor) === null || _c === void 0 ? void 0 : _c.consents) === null || _d === void 0 ? void 0 : _d[804]) === true,
19
+ purpose1: ((_f = (_e = tcData.purpose) === null || _e === void 0 ? void 0 : _e.consents) === null || _f === void 0 ? void 0 : _f[1]) === true,
20
+ gdprApplies: true,
21
+ usnatApplies: false,
22
+ };
23
+ onConsentUpdate(results);
24
+ // Clean up TCF listener
25
+ if (typeof tcData.listenerId === "number") {
26
+ (_g = window.__tcfapi) === null || _g === void 0 ? void 0 : _g.call(window, "removeEventListener", 2, function () { }, tcData.listenerId);
27
+ }
28
+ return;
29
+ }
30
+ // --- Path 2: TCF explicitly returned gdprApplies: false -> Fall back to USNat ---
31
+ evaluateUSNatConsent(onConsentUpdate);
32
+ // Clean up TCF listener since GDPR does not apply
33
+ if (typeof tcData.listenerId === "number") {
34
+ (_h = window.__tcfapi) === null || _h === void 0 ? void 0 : _h.call(window, "removeEventListener", 2, function () { }, tcData.listenerId);
35
+ }
36
+ }
37
+ });
38
+ }
39
+ else {
40
+ // If __tcfapi doesn't exist at all, try evaluating USNat directly
41
+ evaluateUSNatConsent(onConsentUpdate);
42
+ }
43
+ }
44
+ /**
45
+ * Helper to evaluate USNat / US Privacy consent (Opt-Out model)
46
+ */
47
+ function evaluateUSNatConsent(onConsentUpdate) {
48
+ var _a, _b;
49
+ // Option A: Preferred Sourcepoint SDK method
50
+ if (typeof ((_b = (_a = window._sp_) === null || _a === void 0 ? void 0 : _a.usnat) === null || _b === void 0 ? void 0 : _b.getUserConsents) === "function") {
51
+ window._sp_.usnat.getUserConsents(function (consents) {
52
+ // US Privacy is OPT-OUT by default.
53
+ // If the user has NOT opted out, consent is considered granted for all vendors/purposes.
54
+ var isOptedOut = (consents === null || consents === void 0 ? void 0 : consents.optedOut) === true ||
55
+ (Array.isArray(consents === null || consents === void 0 ? void 0 : consents.rejectedCategories) &&
56
+ consents.rejectedCategories.length > 0);
57
+ var isConsented = !isOptedOut;
58
+ onConsentUpdate({
59
+ brandmetrics: isConsented,
60
+ linkedIn: isConsented,
61
+ purpose1: isConsented,
62
+ gdprApplies: false,
63
+ usnatApplies: true,
64
+ });
65
+ });
7
66
  return;
8
67
  }
9
- window.__tcfapi("addEventListener", 2, function (tcData, success) {
10
- var _a, _b, _c, _d, _e, _f, _g;
11
- if (!success || !tcData)
12
- return;
13
- // Handles BOTH returning visitors ('tcloaded') AND live banner accepts ('useractioncomplete')
14
- var isConsentReady = tcData.eventStatus === "tcloaded" ||
15
- tcData.eventStatus === "useractioncomplete";
16
- if (isConsentReady) {
17
- // Brandmetrics (IAB Vendor ID: 422)
18
- var brandmetrics = ((_b = (_a = tcData.vendor) === null || _a === void 0 ? void 0 : _a.consents) === null || _b === void 0 ? void 0 : _b[422]) === true;
19
- // LinkedIn (IAB Vendor ID: 804)
20
- var linkedIn = ((_d = (_c = tcData.vendor) === null || _c === void 0 ? void 0 : _c.consents) === null || _d === void 0 ? void 0 : _d[804]) === true;
21
- // Purpose 1: Device storage/access (Cookies)
22
- var purpose1 = ((_f = (_e = tcData.purpose) === null || _e === void 0 ? void 0 : _e.consents) === null || _f === void 0 ? void 0 : _f[1]) === true;
23
- var gdprApplies = tcData.gdprApplies !== false ? true : false;
24
- var results = {
25
- brandmetrics: brandmetrics,
26
- linkedIn: linkedIn,
27
- purpose1: purpose1,
28
- gdprApplies: gdprApplies,
29
- };
30
- // Trigger your callback handler with the updated states
31
- onConsentUpdate(results);
32
- // Clean up listener after handling the event
33
- if (typeof tcData.listenerId === "number") {
34
- (_g = window.__tcfapi) === null || _g === void 0 ? void 0 : _g.call(window, "removeEventListener", 2, function () { }, tcData.listenerId);
35
- }
36
- }
37
- });
68
+ // Option B: Fallback to GPP standard API
69
+ if (typeof window.__gpp === "function") {
70
+ window.__gpp("getGPPData", function (gppData, success) {
71
+ var _a;
72
+ if (!success || !gppData)
73
+ return;
74
+ var usnat = (_a = gppData.parsedSections) === null || _a === void 0 ? void 0 : _a.usnat;
75
+ // GPP USNat spec: 1 = Opted Out / Yes
76
+ var isOptedOut = (usnat === null || usnat === void 0 ? void 0 : usnat.SharingOptOut) === 1 ||
77
+ (usnat === null || usnat === void 0 ? void 0 : usnat.TargetedAdvertisingOptOut) === 1 ||
78
+ (usnat === null || usnat === void 0 ? void 0 : usnat.SaleOptOut) === 1;
79
+ var isConsented = !isOptedOut;
80
+ onConsentUpdate({
81
+ brandmetrics: isConsented,
82
+ linkedIn: isConsented,
83
+ purpose1: isConsented,
84
+ gdprApplies: false,
85
+ usnatApplies: true,
86
+ });
87
+ });
88
+ }
38
89
  }
39
90
  //# sourceMappingURL=vendorConsent.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"vendorConsent.js","sourceRoot":"","sources":["../../src/cmp/vendorConsent.ts"],"names":[],"mappings":";;AA+BA,8DAiDC;AAjDD,SAAgB,yBAAyB,CACvC,eAAwD;IAExD,IAAI,OAAO,MAAM,CAAC,QAAQ,KAAK,UAAU,EAAE,CAAC;QAC1C,OAAO,CAAC,IAAI,CAAC,wDAAwD,CAAC,CAAC;QACvE,OAAO;IACT,CAAC;IAED,MAAM,CAAC,QAAQ,CAAC,kBAAkB,EAAE,CAAC,EAAE,UAAC,MAAM,EAAE,OAAO;;QACrD,IAAI,CAAC,OAAO,IAAI,CAAC,MAAM;YAAE,OAAO;QAEhC,8FAA8F;QAC9F,IAAM,cAAc,GAClB,MAAM,CAAC,WAAW,KAAK,UAAU;YACjC,MAAM,CAAC,WAAW,KAAK,oBAAoB,CAAC;QAE9C,IAAI,cAAc,EAAE,CAAC;YACnB,oCAAoC;YACpC,IAAM,YAAY,GAAG,CAAA,MAAA,MAAA,MAAM,CAAC,MAAM,0CAAE,QAAQ,0CAAG,GAAG,CAAC,MAAK,IAAI,CAAC;YAE7D,gCAAgC;YAChC,IAAM,QAAQ,GAAG,CAAA,MAAA,MAAA,MAAM,CAAC,MAAM,0CAAE,QAAQ,0CAAG,GAAG,CAAC,MAAK,IAAI,CAAC;YAEzD,6CAA6C;YAC7C,IAAM,QAAQ,GAAG,CAAA,MAAA,MAAA,MAAM,CAAC,OAAO,0CAAE,QAAQ,0CAAG,CAAC,CAAC,MAAK,IAAI,CAAC;YAExD,IAAM,WAAW,GAAG,MAAM,CAAC,WAAW,KAAK,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC;YAEhE,IAAM,OAAO,GAAyB;gBACpC,YAAY,cAAA;gBACZ,QAAQ,UAAA;gBACR,QAAQ,UAAA;gBACR,WAAW,aAAA;aACZ,CAAC;YAEF,wDAAwD;YACxD,eAAe,CAAC,OAAO,CAAC,CAAC;YAEzB,6CAA6C;YAC7C,IAAI,OAAO,MAAM,CAAC,UAAU,KAAK,QAAQ,EAAE,CAAC;gBAC1C,MAAA,MAAM,CAAC,QAAQ,uDACb,qBAAqB,EACrB,CAAC,EACD,cAAO,CAAC,EACR,MAAM,CAAC,UAAU,CAClB,CAAC;YACJ,CAAC;QACH,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC"}
1
+ {"version":3,"file":"vendorConsent.js","sourceRoot":"","sources":["../../src/cmp/vendorConsent.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@phantomstudios/ft-lib",
3
3
  "description": "A collection of Javascript UI & tracking utils for FT sites",
4
- "version": "0.5.2-rc8",
4
+ "version": "0.5.2-rc9",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",
7
7
  "homepage": "https://github.com/phantomstudios/ft-lib#readme",
@@ -1,81 +1,119 @@
1
- // 1. Declare Global Window Types for TCF API
2
- declare global {
3
- interface Window {
4
- __tcfapi?: (
5
- command: string,
6
- version: number,
7
- callback: (tcData: TCFData | null, success: boolean) => void,
8
- parameter?: any,
9
- ) => void;
10
- }
11
- }
12
-
13
- interface TCFData {
14
- eventStatus: "tcloaded" | "useractioncomplete" | "cmpuishown";
15
- listenerId: number;
16
- purpose?: {
17
- consents?: Record<number, boolean>;
18
- };
19
- vendor?: {
20
- consents?: Record<number, boolean>;
21
- };
22
- gdprApplies?: boolean | undefined;
23
- }
24
-
25
1
  export interface VendorConsentResults {
26
2
  brandmetrics: boolean;
27
3
  linkedIn: boolean;
28
4
  purpose1: boolean;
29
5
  gdprApplies: boolean;
6
+ usnatApplies?: boolean;
30
7
  }
31
8
 
32
9
  export function initVendorConsentListener(
33
10
  onConsentUpdate: (results: VendorConsentResults) => void,
34
11
  ): void {
35
- if (typeof window.__tcfapi !== "function") {
36
- console.warn("[CMP] window.__tcfapi is not defined on this page yet.");
37
- return;
38
- }
12
+ // --- Path 1: Check TCF API First ---
13
+ if (typeof window.__tcfapi === "function") {
14
+ window.__tcfapi("addEventListener", 2, (tcData, success) => {
15
+ if (!success || !tcData) return;
39
16
 
40
- window.__tcfapi("addEventListener", 2, (tcData, success) => {
41
- if (!success || !tcData) return;
17
+ const isConsentReady =
18
+ tcData.eventStatus === "tcloaded" ||
19
+ tcData.eventStatus === "useractioncomplete";
42
20
 
43
- // Handles BOTH returning visitors ('tcloaded') AND live banner accepts ('useractioncomplete')
44
- const isConsentReady =
45
- tcData.eventStatus === "tcloaded" ||
46
- tcData.eventStatus === "useractioncomplete";
21
+ if (isConsentReady) {
22
+ // If GDPR actually applies, process TCF consents (Opt-In model)
23
+ if (tcData.gdprApplies !== false) {
24
+ const results: VendorConsentResults = {
25
+ brandmetrics: tcData.vendor?.consents?.[422] === true,
26
+ linkedIn: tcData.vendor?.consents?.[804] === true,
27
+ purpose1: tcData.purpose?.consents?.[1] === true,
28
+ gdprApplies: true,
29
+ usnatApplies: false,
30
+ };
47
31
 
48
- if (isConsentReady) {
49
- // Brandmetrics (IAB Vendor ID: 422)
50
- const brandmetrics = tcData.vendor?.consents?.[422] === true;
32
+ onConsentUpdate(results);
51
33
 
52
- // LinkedIn (IAB Vendor ID: 804)
53
- const linkedIn = tcData.vendor?.consents?.[804] === true;
34
+ // Clean up TCF listener
35
+ if (typeof tcData.listenerId === "number") {
36
+ window.__tcfapi?.(
37
+ "removeEventListener",
38
+ 2,
39
+ () => {},
40
+ tcData.listenerId,
41
+ );
42
+ }
43
+ return;
44
+ }
54
45
 
55
- // Purpose 1: Device storage/access (Cookies)
56
- const purpose1 = tcData.purpose?.consents?.[1] === true;
46
+ // --- Path 2: TCF explicitly returned gdprApplies: false -> Fall back to USNat ---
47
+ evaluateUSNatConsent(onConsentUpdate);
57
48
 
58
- const gdprApplies = tcData.gdprApplies !== false ? true : false;
49
+ // Clean up TCF listener since GDPR does not apply
50
+ if (typeof tcData.listenerId === "number") {
51
+ window.__tcfapi?.(
52
+ "removeEventListener",
53
+ 2,
54
+ () => {},
55
+ tcData.listenerId,
56
+ );
57
+ }
58
+ }
59
+ });
60
+ } else {
61
+ // If __tcfapi doesn't exist at all, try evaluating USNat directly
62
+ evaluateUSNatConsent(onConsentUpdate);
63
+ }
64
+ }
59
65
 
60
- const results: VendorConsentResults = {
61
- brandmetrics,
62
- linkedIn,
63
- purpose1,
64
- gdprApplies,
65
- };
66
+ /**
67
+ * Helper to evaluate USNat / US Privacy consent (Opt-Out model)
68
+ */
69
+ function evaluateUSNatConsent(
70
+ onConsentUpdate: (results: VendorConsentResults) => void,
71
+ ): void {
72
+ // Option A: Preferred Sourcepoint SDK method
73
+ if (typeof window._sp_?.usnat?.getUserConsents === "function") {
74
+ window._sp_.usnat.getUserConsents((consents) => {
75
+ // US Privacy is OPT-OUT by default.
76
+ // If the user has NOT opted out, consent is considered granted for all vendors/purposes.
77
+ const isOptedOut =
78
+ consents?.optedOut === true ||
79
+ (Array.isArray(consents?.rejectedCategories) &&
80
+ consents.rejectedCategories.length > 0);
66
81
 
67
- // Trigger your callback handler with the updated states
68
- onConsentUpdate(results);
82
+ const isConsented = !isOptedOut;
69
83
 
70
- // Clean up listener after handling the event
71
- if (typeof tcData.listenerId === "number") {
72
- window.__tcfapi?.(
73
- "removeEventListener",
74
- 2,
75
- () => {},
76
- tcData.listenerId,
77
- );
78
- }
79
- }
80
- });
84
+ onConsentUpdate({
85
+ brandmetrics: isConsented,
86
+ linkedIn: isConsented,
87
+ purpose1: isConsented,
88
+ gdprApplies: false,
89
+ usnatApplies: true,
90
+ });
91
+ });
92
+ return;
93
+ }
94
+
95
+ // Option B: Fallback to GPP standard API
96
+ if (typeof window.__gpp === "function") {
97
+ window.__gpp("getGPPData", (gppData: any, success: boolean) => {
98
+ if (!success || !gppData) return;
99
+
100
+ const usnat = gppData.parsedSections?.usnat;
101
+
102
+ // GPP USNat spec: 1 = Opted Out / Yes
103
+ const isOptedOut =
104
+ usnat?.SharingOptOut === 1 ||
105
+ usnat?.TargetedAdvertisingOptOut === 1 ||
106
+ usnat?.SaleOptOut === 1;
107
+
108
+ const isConsented = !isOptedOut;
109
+
110
+ onConsentUpdate({
111
+ brandmetrics: isConsented,
112
+ linkedIn: isConsented,
113
+ purpose1: isConsented,
114
+ gdprApplies: false,
115
+ usnatApplies: true,
116
+ });
117
+ });
118
+ }
81
119
  }
package/src/types.d.ts CHANGED
@@ -15,17 +15,44 @@ declare module "@financial-times/cmp-client" {
15
15
  propertyConfig?: any;
16
16
  }): Promise<void>;
17
17
  }
18
+
19
+ interface TCFData {
20
+ eventStatus: "tcloaded" | "useractioncomplete" | "cmpuishown";
21
+ listenerId: number;
22
+ purpose?: {
23
+ consents?: Record<number, boolean>;
24
+ };
25
+ vendor?: {
26
+ consents?: Record<number, boolean>;
27
+ };
28
+ gdprApplies?: boolean | undefined;
29
+ }
18
30
  interface SourcepointCmpAPI {
19
31
  queue?: Array<() => void>;
20
32
  addEventListener?: (
21
33
  eventName: string,
22
-
23
34
  callback: (...args: any[]) => void,
24
35
  ) => void;
36
+ usnat?: {
37
+ getUserConsents: (callback: (consents: any) => void) => void;
38
+ };
25
39
  }
40
+
26
41
  interface Window {
27
42
  _sp_: SourcepointCmpAPI;
28
43
  _sp_queue?: Array<() => void>;
44
+ __tcfapi?: (
45
+ command: string,
46
+ version: number,
47
+ callback: (tcData: TCFData | null, success: boolean) => void,
48
+ parameter?: any,
49
+ ) => void;
50
+ __gpp?: (
51
+ command: string,
52
+ callback?: (data: any, success: boolean) => void,
53
+ parameter?: any,
54
+ ) => void;
55
+
29
56
  dataLayer: any;
30
57
  gtag: any;
31
58
  permutive: {