From 2d141b2d794e99d6b8bd0c9fa0e2885ffb8618f7 Mon Sep 17 00:00:00 2001 From: eastandwestwind Date: Fri, 13 Oct 2023 17:41:31 +0200 Subject: [PATCH 1/8] initial framework for override options, implements query param override --- clients/fides-js/src/fides.ts | 43 ++++++++++++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/clients/fides-js/src/fides.ts b/clients/fides-js/src/fides.ts index 45bd64b4db..969daeacb2 100644 --- a/clients/fides-js/src/fides.ts +++ b/clients/fides-js/src/fides.ts @@ -53,7 +53,11 @@ import { buildCookieConsentForExperiences, isNewFidesCookie, } from "./lib/cookie"; -import { FidesConfig, PrivacyExperience } from "./lib/consent-types"; +import { + FidesConfig, + FidesOptions, + PrivacyExperience, +} from "./lib/consent-types"; import { dispatchFidesEvent } from "./lib/events"; @@ -91,10 +95,47 @@ const updateCookie = async ( return { ...oldCookie, consent }; }; +type OverrideFidesOptions = Pick< + FidesOptions, + "fidesString" | "fidesDisableSaveApi" | "fidesEmbed" +>; + +const overrideOptionsValidatorMap = new Map( + [ + ["fidesString", /(.*)/], // for now allow any characters, but follow-up with more strict validation after implementing AC str + ["fidesDisableSaveApi", /^(?i)(true|false)$/], + ["fidesEmbed", /^(?i)(true|false)$/], + ] +); + +/** + * Gets and validates Fides override options provided through URL query params. + * This function is extensible to further support other methods of override, e.g. cookie or window obj. + */ +const getOverrideFidesOptions = (): Map => { + const overrideOptions: Map = new Map(); + if (typeof window !== "undefined") { + // look for override options on URL query params + const params = new URLSearchParams(document.location.search); + overrideOptionsValidatorMap.forEach( + (regexp: RegExp, optionName: string) => { + const value = params.get(optionName); + if (value && regexp.test(value)) { + overrideOptions.set(optionName, value); + } + } + ); + } + return overrideOptions; +}; + /** * Initialize the global Fides object with the given configuration values */ const init = async (config: FidesConfig) => { + const overrideOptions: Map = getOverrideFidesOptions(); + // eslint-disable-next-line no-param-reassign + config.options = { ...config.options, ...overrideOptions }; const cookie = getInitialCookie(config); const initialFides = getInitialFides({ ...config, cookie }); if (initialFides) { From e3bf3b8367ae79ae139bff8391639557a0ca6b0f Mon Sep 17 00:00:00 2001 From: eastandwestwind Date: Wed, 18 Oct 2023 18:23:16 +0200 Subject: [PATCH 2/8] adds e2e tests, fixes bugs --- clients/fides-js/rollup.config.mjs | 2 +- clients/fides-js/src/fides-tcf.ts | 9 + clients/fides-js/src/fides.ts | 43 +-- clients/fides-js/src/lib/consent-types.ts | 43 +++ clients/fides-js/src/lib/cookie.ts | 8 +- clients/fides-js/src/lib/initialize.ts | 32 ++ .../cypress/e2e/consent-banner-tcf.cy.ts | 339 +++++++++++++++++- .../cypress/support/commands.ts | 70 ++-- .../privacy-center/cypress/support/stubs.ts | 10 +- 9 files changed, 488 insertions(+), 68 deletions(-) diff --git a/clients/fides-js/rollup.config.mjs b/clients/fides-js/rollup.config.mjs index 541cf8c879..14f6aceedd 100644 --- a/clients/fides-js/rollup.config.mjs +++ b/clients/fides-js/rollup.config.mjs @@ -14,7 +14,7 @@ const GZIP_SIZE_ERROR_KB = 22; // fail build if bundle size exceeds this const GZIP_SIZE_WARN_KB = 15; // log a warning if bundle size exceeds this // TCF -const GZIP_SIZE_TCF_ERROR_KB = 41; +const GZIP_SIZE_TCF_ERROR_KB = 42; const GZIP_SIZE_TCF_WARN_KB = 35; const preactAliases = { diff --git a/clients/fides-js/src/fides-tcf.ts b/clients/fides-js/src/fides-tcf.ts index 40cbcea116..955af8f3ec 100644 --- a/clients/fides-js/src/fides-tcf.ts +++ b/clients/fides-js/src/fides-tcf.ts @@ -52,6 +52,8 @@ import { shopify } from "./integrations/shopify"; import { FidesConfig, + FidesOptionOverrides, + OverrideOptions, PrivacyExperience, UserConsentPreference, } from "./lib/consent-types"; @@ -60,6 +62,7 @@ import { generateFidesString, initializeCmpApi } from "./lib/tcf"; import { getInitialCookie, getInitialFides, + getOverrideFidesOptions, initialize, } from "./lib/initialize"; import type { Fides } from "./lib/initialize"; @@ -96,6 +99,9 @@ declare global { callback: (tcData: TCData, success: boolean) => void, parameter?: number | string ) => void; + config: { + fides: OverrideOptions; + }; } } @@ -164,6 +170,9 @@ const updateCookie = async ( * Initialize the global Fides object with the given configuration values */ const init = async (config: FidesConfig) => { + const overrideOptions: FidesOptionOverrides = getOverrideFidesOptions(); + // eslint-disable-next-line no-param-reassign + config.options = { ...config.options, ...overrideOptions }; const cookie = getInitialCookie(config); if (config.options.fidesString) { // If a fidesString is explicitly passed in, we override the associated cookie props, which are then used to diff --git a/clients/fides-js/src/fides.ts b/clients/fides-js/src/fides.ts index 969daeacb2..0ad2b74288 100644 --- a/clients/fides-js/src/fides.ts +++ b/clients/fides-js/src/fides.ts @@ -55,7 +55,8 @@ import { } from "./lib/cookie"; import { FidesConfig, - FidesOptions, + FidesOptionOverrides, + OverrideOptions, PrivacyExperience, } from "./lib/consent-types"; @@ -65,6 +66,7 @@ import { initialize, getInitialCookie, getInitialFides, + getOverrideFidesOptions, } from "./lib/initialize"; import type { Fides } from "./lib/initialize"; @@ -74,6 +76,9 @@ import { getConsentContext } from "./lib/consent-context"; declare global { interface Window { Fides: Fides; + config: { + fides: OverrideOptions; + }; } } @@ -95,45 +100,11 @@ const updateCookie = async ( return { ...oldCookie, consent }; }; -type OverrideFidesOptions = Pick< - FidesOptions, - "fidesString" | "fidesDisableSaveApi" | "fidesEmbed" ->; - -const overrideOptionsValidatorMap = new Map( - [ - ["fidesString", /(.*)/], // for now allow any characters, but follow-up with more strict validation after implementing AC str - ["fidesDisableSaveApi", /^(?i)(true|false)$/], - ["fidesEmbed", /^(?i)(true|false)$/], - ] -); - -/** - * Gets and validates Fides override options provided through URL query params. - * This function is extensible to further support other methods of override, e.g. cookie or window obj. - */ -const getOverrideFidesOptions = (): Map => { - const overrideOptions: Map = new Map(); - if (typeof window !== "undefined") { - // look for override options on URL query params - const params = new URLSearchParams(document.location.search); - overrideOptionsValidatorMap.forEach( - (regexp: RegExp, optionName: string) => { - const value = params.get(optionName); - if (value && regexp.test(value)) { - overrideOptions.set(optionName, value); - } - } - ); - } - return overrideOptions; -}; - /** * Initialize the global Fides object with the given configuration values */ const init = async (config: FidesConfig) => { - const overrideOptions: Map = getOverrideFidesOptions(); + const overrideOptions: FidesOptionOverrides = getOverrideFidesOptions(); // eslint-disable-next-line no-param-reassign config.options = { ...config.options, ...overrideOptions }; const cookie = getInitialCookie(config); diff --git a/clients/fides-js/src/lib/consent-types.ts b/clients/fides-js/src/lib/consent-types.ts index 3b7405b9de..0205c5ef11 100644 --- a/clients/fides-js/src/lib/consent-types.ts +++ b/clients/fides-js/src/lib/consent-types.ts @@ -207,6 +207,49 @@ export type UserGeolocation = { // 3) Separated by a dash (e.g. "US-CA") export const VALID_ISO_3166_LOCATION_REGEX = /^\w{2,3}(-\w{2,3})?$/; +export type OverrideOptions = { + fides_string: string; + fides_disable_save_api: boolean; + fides_embed: boolean; +}; + +export type FidesOptionOverrides = Pick< + FidesOptions, + "fidesString" | "fidesDisableSaveApi" | "fidesEmbed" +>; + +export enum OverrideFidesOption { + FIDES_STRING = "fides_string", + FIDES_DISABLE_SAVE_API = "fides_disable_save_api", + FIDES_EMBED = "fides_embed", +} + +export const FIDES_OVERRIDE_OPTIONS_VALIDATOR_MAP: { + fidesOption: keyof FidesOptionOverrides; + fidesOptionType: "string" | "boolean"; + fidesOverrideKey: keyof OverrideOptions; + validationRegex: RegExp; +}[] = [ + { + fidesOption: "fidesEmbed", + fidesOptionType: "boolean", + fidesOverrideKey: OverrideFidesOption.FIDES_EMBED, + validationRegex: /^(true|false)$/, + }, + { + fidesOption: "fidesDisableSaveApi", + fidesOptionType: "boolean", + fidesOverrideKey: OverrideFidesOption.FIDES_DISABLE_SAVE_API, + validationRegex: /^(true|false)$/, + }, + { + fidesOption: "fidesString", + fidesOptionType: "string", + fidesOverrideKey: OverrideFidesOption.FIDES_STRING, + validationRegex: /(.*)/, + }, +]; + export enum ButtonType { PRIMARY = "primary", SECONDARY = "secondary", diff --git a/clients/fides-js/src/lib/cookie.ts b/clients/fides-js/src/lib/cookie.ts index a5b069fadf..c340ef348d 100644 --- a/clients/fides-js/src/lib/cookie.ts +++ b/clients/fides-js/src/lib/cookie.ts @@ -118,6 +118,12 @@ export const makeFidesCookie = (consent?: CookieKeyConsent): FidesCookie => { }; }; +/** + * Retrieve cookie by name + */ +export const getCookieByName = (cookieName: string): string | undefined => + getCookie(cookieName, CODEC); + /** * Attempt to read, parse, and return the current Fides cookie from the browser. * If one doesn't exist, make a new default cookie (including generating a new @@ -138,7 +144,7 @@ export const getOrMakeFidesCookie = ( } // Check for an existing cookie for this device - const cookieString = getCookie(CONSENT_COOKIE_NAME, CODEC); + const cookieString = getCookieByName(CONSENT_COOKIE_NAME); if (!cookieString) { debugLog( debug, diff --git a/clients/fides-js/src/lib/initialize.ts b/clients/fides-js/src/lib/initialize.ts index a1c003ba73..541223fd85 100644 --- a/clients/fides-js/src/lib/initialize.ts +++ b/clients/fides-js/src/lib/initialize.ts @@ -9,6 +9,7 @@ import { CookieKeyConsent, CookieMeta, FidesCookie, + getCookieByName, getOrMakeFidesCookie, isNewFidesCookie, makeConsentDefaultsLegacy, @@ -19,7 +20,9 @@ import { ConsentMechanism, ConsentMethod, EmptyExperience, + FIDES_OVERRIDE_OPTIONS_VALIDATOR_MAP, FidesConfig, + FidesOptionOverrides, FidesOptions, PrivacyExperience, SaveConsentPreference, @@ -143,6 +146,35 @@ const automaticallyApplyGPCPreferences = ({ } }; +/** + * Gets and validates Fides override options provided through URL query params, cookie or window obj. + */ +export const getOverrideFidesOptions = (): FidesOptionOverrides => { + const overrideOptions: Partial = {}; + if (typeof window !== "undefined") { + const params = new URLSearchParams(document.location.search); + FIDES_OVERRIDE_OPTIONS_VALIDATOR_MAP.forEach( + ({ fidesOption, fidesOptionType, fidesOverrideKey, validationRegex }) => { + // look for override options on URL query params, window obj, and cookie + const queryParamOverride: string | null = params.get(fidesOverrideKey); + const windowObjOverride: string | boolean | undefined = window.config + ?.fides + ? window.config?.fides[fidesOverrideKey] + : undefined; + const cookieOverride: string | undefined = + getCookieByName(fidesOverrideKey); + const value = queryParamOverride || windowObjOverride || cookieOverride; + if (value && validationRegex.test(value.toString())) { + // coerce to expected type in FidesOptions + overrideOptions[fidesOption] = + fidesOptionType === "string" ? value : JSON.parse(value.toString()); + } + } + ); + } + return overrideOptions; +}; + /** * Get the initial Fides cookie based on legacy consent values * as well as any preferences stored in existing cookies diff --git a/clients/privacy-center/cypress/e2e/consent-banner-tcf.cy.ts b/clients/privacy-center/cypress/e2e/consent-banner-tcf.cy.ts index 1f2b3c570f..9919cd4a9e 100644 --- a/clients/privacy-center/cypress/e2e/consent-banner-tcf.cy.ts +++ b/clients/privacy-center/cypress/e2e/consent-banner-tcf.cy.ts @@ -5,6 +5,7 @@ import { PrivacyExperience, UserConsentPreference, } from "fides-js"; +import { OverrideFidesOption } from "fides-js/src/lib/consent-types"; import { OVERRIDE, stubConfig } from "../support/stubs"; const PURPOSE_2 = { @@ -742,11 +743,116 @@ describe("Fides-js TCF", () => { }); }); }); + it("skips saving preferences to API when disable save is set via cookie", () => { + cy.getCookie(CONSENT_COOKIE_NAME).should("not.exist"); + cy.getCookie(OverrideFidesOption.FIDES_DISABLE_SAVE_API).should( + "not.exist" + ); + cy.setCookie(OverrideFidesOption.FIDES_DISABLE_SAVE_API, "true"); + cy.fixture("consent/experience_tcf.json").then((experience) => { + stubConfig({ + options: { + isOverlayEnabled: true, + tcfEnabled: true, + }, + experience: experience.items[0], + }); + }); + cy.waitUntilFidesInitialized().then(() => { + cy.get("#fides-modal-link").click(); + cy.getByTestId("consent-modal").within(() => { + cy.get("button").contains("Opt out of all").click(); + // timeout means API call not made, which is expected + cy.on("fail", (error) => { + if (error.message.indexOf("Timed out retrying") !== 0) { + throw error; + } + }); + // check that preferences aren't sent to Fides API + cy.wait("@patchPrivacyPreference", { + requestTimeout: 500, + }).then((xhr) => { + assert.isNull(xhr?.response?.body); + }); + }); + }); + }); + it("skips saving preferences to API when disable save is set via query param", () => { + cy.getCookie(OverrideFidesOption.FIDES_STRING).should("not.exist"); + cy.fixture("consent/experience_tcf.json").then((experience) => { + stubConfig( + { + options: { + isOverlayEnabled: true, + tcfEnabled: true, + }, + experience: experience.items[0], + }, + null, + null, + { fides_disable_save_api: true } + ); + }); + cy.waitUntilFidesInitialized().then(() => { + cy.get("#fides-modal-link").click(); + cy.getByTestId("consent-modal").within(() => { + cy.get("button").contains("Opt out of all").click(); + // timeout means API call not made, which is expected + cy.on("fail", (error) => { + if (error.message.indexOf("Timed out retrying") !== 0) { + throw error; + } + }); + // check that preferences aren't sent to Fides API + cy.wait("@patchPrivacyPreference", { + requestTimeout: 500, + }).then((xhr) => { + assert.isNull(xhr?.response?.body); + }); + }); + }); + }); + it("skips saving preferences to API when disable save is set via window obj", () => { + cy.getCookie(OverrideFidesOption.FIDES_STRING).should("not.exist"); + cy.fixture("consent/experience_tcf.json").then((experience) => { + stubConfig( + { + options: { + isOverlayEnabled: true, + tcfEnabled: true, + }, + experience: experience.items[0], + }, + null, + null, + null, + { fides_disable_save_api: true } + ); + }); + cy.waitUntilFidesInitialized().then(() => { + cy.get("#fides-modal-link").click(); + cy.getByTestId("consent-modal").within(() => { + cy.get("button").contains("Opt out of all").click(); + // timeout means API call not made, which is expected + cy.on("fail", (error) => { + if (error.message.indexOf("Timed out retrying") !== 0) { + throw error; + } + }); + // check that preferences aren't sent to Fides API + cy.wait("@patchPrivacyPreference", { + requestTimeout: 500, + }).then((xhr) => { + assert.isNull(xhr?.response?.body); + }); + }); + }); + }); }); }); describe("second layer embedded", () => { - beforeEach(() => { + it("automatically renders the second layer and can render tabs", () => { cy.getCookie(CONSENT_COOKIE_NAME).should("not.exist"); cy.fixture("consent/experience_tcf.json").then((experience) => { stubConfig({ @@ -758,8 +864,6 @@ describe("Fides-js TCF", () => { experience: experience.items[0], }); }); - }); - it("automatically renders the second layer and can render tabs", () => { cy.get("#fides-tab-Purposes"); // Purposes cy.getByTestId("toggle-Purposes").within(() => { @@ -796,6 +900,17 @@ describe("Fides-js TCF", () => { }); }); it("can opt in to some and opt out of others", () => { + cy.getCookie(CONSENT_COOKIE_NAME).should("not.exist"); + cy.fixture("consent/experience_tcf.json").then((experience) => { + stubConfig({ + options: { + isOverlayEnabled: true, + tcfEnabled: true, + fidesEmbed: true, + }, + experience: experience.items[0], + }); + }); cy.getByTestId("consent-modal").within(() => { cy.getByTestId(`toggle-${PURPOSE_4.name}-consent`).click(); cy.get("#fides-tab-Features").click(); @@ -867,6 +982,89 @@ describe("Fides-js TCF", () => { ); }); }); + it("automatically renders the second layer when fidesEmbed is set via cookie", () => { + cy.getCookie(CONSENT_COOKIE_NAME).should("not.exist"); + cy.getCookie(OverrideFidesOption.FIDES_EMBED).should("not.exist"); + cy.setCookie(OverrideFidesOption.FIDES_EMBED, "true"); + cy.fixture("consent/experience_tcf.json").then((experience) => { + stubConfig({ + options: { + isOverlayEnabled: true, + tcfEnabled: true, + }, + experience: experience.items[0], + }); + }); + // spot check a couple UI elements + cy.get("#fides-tab-Purposes"); + // Purposes + cy.getByTestId("toggle-Purposes").within(() => { + cy.get("input").should("be.checked"); + }); + // Vendors + cy.get("#fides-tab-Vendors").click(); + cy.getByTestId(`toggle-${SYSTEM_1.name}`).within(() => { + cy.get("input").should("be.checked"); + }); + }); + it("automatically renders the second layer when fidesEmbed is set via query param", () => { + cy.getCookie(CONSENT_COOKIE_NAME).should("not.exist"); + cy.fixture("consent/experience_tcf.json").then((experience) => { + stubConfig( + { + options: { + isOverlayEnabled: true, + tcfEnabled: true, + }, + experience: experience.items[0], + }, + null, + null, + { fides_embed: true } + ); + }); + // spot check a couple UI elements + cy.get("#fides-tab-Purposes"); + // Purposes + cy.getByTestId("toggle-Purposes").within(() => { + cy.get("input").should("be.checked"); + }); + // Vendors + cy.get("#fides-tab-Vendors").click(); + cy.getByTestId(`toggle-${SYSTEM_1.name}`).within(() => { + cy.get("input").should("be.checked"); + }); + }); + it("automatically renders the second layer when fidesEmbed is set via window obj", () => { + cy.getCookie(OverrideFidesOption.FIDES_STRING).should("not.exist"); + cy.getCookie(CONSENT_COOKIE_NAME).should("not.exist"); + cy.fixture("consent/experience_tcf.json").then((experience) => { + stubConfig( + { + options: { + isOverlayEnabled: true, + tcfEnabled: true, + }, + experience: experience.items[0], + }, + null, + null, + null, + { fides_embed: true } + ); + }); + // spot check a couple UI elements + cy.get("#fides-tab-Purposes"); + // Purposes + cy.getByTestId("toggle-Purposes").within(() => { + cy.get("input").should("be.checked"); + }); + // Vendors + cy.get("#fides-tab-Vendors").click(); + cy.getByTestId(`toggle-${SYSTEM_1.name}`).within(() => { + cy.get("input").should("be.checked"); + }); + }); }); describe("cmp api", () => { @@ -1507,6 +1705,141 @@ describe("Fides-js TCF", () => { }); }); + describe("fides string override options", () => { + it("uses TC string when set via cookie", () => { + cy.getCookie(OverrideFidesOption.FIDES_STRING).should("not.exist"); + // this TC string sets purpose 4 to false and purpose 7 to true + cy.setCookie( + OverrideFidesOption.FIDES_STRING, + "CPzevcAPzevcAGXABBENATEIAAIAAAAAAAAAAAAAAAAA.IABE" + ); + cy.fixture("consent/experience_tcf.json").then((experience) => { + stubConfig({ + options: { + isOverlayEnabled: true, + tcfEnabled: true, + }, + experience: experience.items[0], + }); + }); + cy.window().then((win) => { + win.__tcfapi("addEventListener", 2, cy.stub().as("TCFEvent")); + }); + // Open the modal + cy.get("#fides-modal-link").click(); + + // verify CMP API + cy.get("@TCFEvent") + .its("lastCall.args") + .then(([tcData, success]) => { + expect(success).to.eql(true); + expect(tcData.eventStatus).to.eql("cmpuishown"); + expect(tcData.purpose.consents).to.eql({ + [PURPOSE_2.id]: false, + [PURPOSE_4.id]: false, + [PURPOSE_6.id]: false, + [PURPOSE_7.id]: true, + 1: false, + 2: false, + 3: false, + 5: false, + }); + expect(tcData.purpose.legitimateInterests).to.eql({}); + expect(tcData.vendor.consents).to.eql({}); + expect(tcData.vendor.legitimateInterests).to.eql({}); + }); + }); + it("uses TC string when set via query param", () => { + cy.getCookie(OverrideFidesOption.FIDES_STRING).should("not.exist"); + cy.fixture("consent/experience_tcf.json").then((experience) => { + stubConfig( + { + options: { + isOverlayEnabled: true, + tcfEnabled: true, + }, + experience: experience.items[0], + }, + null, + null, + // this TC string sets purpose 4 to false and purpose 7 to true + { fides_string: "CPzevcAPzevcAGXABBENATEIAAIAAAAAAAAAAAAAAAAA.IABE" } + ); + }); + cy.window().then((win) => { + win.__tcfapi("addEventListener", 2, cy.stub().as("TCFEvent")); + }); + // Open the modal + cy.get("#fides-modal-link").click(); + + // verify CMP API + cy.get("@TCFEvent") + .its("lastCall.args") + .then(([tcData, success]) => { + expect(success).to.eql(true); + expect(tcData.eventStatus).to.eql("cmpuishown"); + expect(tcData.purpose.consents).to.eql({ + [PURPOSE_2.id]: false, + [PURPOSE_4.id]: false, + [PURPOSE_6.id]: false, + [PURPOSE_7.id]: true, + 1: false, + 2: false, + 3: false, + 5: false, + }); + expect(tcData.purpose.legitimateInterests).to.eql({}); + expect(tcData.vendor.consents).to.eql({}); + expect(tcData.vendor.legitimateInterests).to.eql({}); + }); + }); + it("uses TC string when set via window obj", () => { + cy.getCookie(OverrideFidesOption.FIDES_STRING).should("not.exist"); + cy.fixture("consent/experience_tcf.json").then((experience) => { + stubConfig( + { + options: { + isOverlayEnabled: true, + tcfEnabled: true, + }, + experience: experience.items[0], + }, + null, + null, + null, + // this TC string sets purpose 4 to false and purpose 7 to true + { fides_string: "CPzevcAPzevcAGXABBENATEIAAIAAAAAAAAAAAAAAAAA.IABE" } + ); + }); + cy.window().then((win) => { + win.__tcfapi("addEventListener", 2, cy.stub().as("TCFEvent")); + }); + // Open the modal + cy.get("#fides-modal-link").click(); + + // verify CMP API + cy.get("@TCFEvent") + .its("lastCall.args") + .then(([tcData, success]) => { + expect(success).to.eql(true); + expect(tcData.eventStatus).to.eql("cmpuishown"); + expect(tcData.purpose.consents).to.eql({ + [PURPOSE_2.id]: false, + [PURPOSE_4.id]: false, + [PURPOSE_6.id]: false, + [PURPOSE_7.id]: true, + 1: false, + 2: false, + 3: false, + 5: false, + }); + expect(tcData.purpose.legitimateInterests).to.eql({}); + expect(tcData.vendor.consents).to.eql({}); + expect(tcData.vendor.legitimateInterests).to.eql({}); + }); + }); + }); + describe("ac string", () => { const AC_IDS = [42, 33, 49]; const acceptAllAcString = `1~${AC_IDS.sort().join(".")}`; diff --git a/clients/privacy-center/cypress/support/commands.ts b/clients/privacy-center/cypress/support/commands.ts index 184b811a49..0f6b0f2433 100644 --- a/clients/privacy-center/cypress/support/commands.ts +++ b/clients/privacy-center/cypress/support/commands.ts @@ -5,6 +5,7 @@ import "cypress-wait-until"; import type { AppDispatch } from "~/app/store"; import type { FidesConfig } from "fides-js"; import type { PrivacyCenterClientSettings } from "~/app/server-environment"; +import VisitOptions = Cypress.VisitOptions; Cypress.Commands.add("getByTestId", (selector, ...args) => cy.get(`[data-testid='${selector}']`, ...args) @@ -55,31 +56,46 @@ Cypress.Commands.add("overrideSettings", (settings) => { ); }); -Cypress.Commands.add("visitConsentDemo", (options?: FidesConfig) => { - cy.visit("/fides-js-components-demo.html", { - onBeforeLoad: (win) => { - // eslint-disable-next-line no-param-reassign - win.fidesConfig = options; +Cypress.Commands.add( + "visitConsentDemo", + (options?: FidesConfig, queryParams?: any, windowParams?: any) => { + const visitOptions: Partial = { + onBeforeLoad: (win) => { + // eslint-disable-next-line no-param-reassign + win.fidesConfig = options; - // Add event listeners for Fides.js events - win.addEventListener( - "FidesInitialized", - cy.stub().as("FidesInitialized") - ); - win.addEventListener("FidesUpdated", cy.stub().as("FidesUpdated")); - win.addEventListener("FidesUIShown", cy.stub().as("FidesUIShown")); - win.addEventListener( - "FidesPreferenceToggled", - cy.stub().as("FidesPreferenceToggled") - ); + if (windowParams) { + // @ts-ignore + // eslint-disable-next-line no-param-reassign + win.config = { + fides: windowParams, + }; + } - // Add GTM stub - // eslint-disable-next-line no-param-reassign - win.dataLayer = []; - cy.stub(win.dataLayer, "push").as("dataLayerPush"); - }, - }); -}); + // Add event listeners for Fides.js events + win.addEventListener( + "FidesInitialized", + cy.stub().as("FidesInitialized") + ); + win.addEventListener("FidesUpdated", cy.stub().as("FidesUpdated")); + win.addEventListener("FidesUIShown", cy.stub().as("FidesUIShown")); + win.addEventListener( + "FidesPreferenceToggled", + cy.stub().as("FidesPreferenceToggled") + ); + + // Add GTM stub + // eslint-disable-next-line no-param-reassign + win.dataLayer = []; + cy.stub(win.dataLayer, "push").as("dataLayerPush"); + }, + }; + if (queryParams) { + visitOptions.qs = queryParams; + } + cy.visit("/fides-js-components-demo.html", visitOptions); + } +); declare global { namespace Cypress { @@ -161,9 +177,13 @@ declare global { ): Chainable; /** * Visit the /fides-js-components-demo page and inject config options - * @example cy.visitConsentDemo(fidesConfig); + * @example cy.visitConsentDemo(fidesConfig, {fidesEmbed: true}); */ - visitConsentDemo(options?: FidesConfig): Chainable; + visitConsentDemo( + options?: FidesConfig, + queryParams?: any, + windowParams?: any + ): Chainable; /** * Custom command to load a Privacy Center settings object into the app * diff --git a/clients/privacy-center/cypress/support/stubs.ts b/clients/privacy-center/cypress/support/stubs.ts index fdfd68f33a..ff25e230b7 100644 --- a/clients/privacy-center/cypress/support/stubs.ts +++ b/clients/privacy-center/cypress/support/stubs.ts @@ -46,7 +46,9 @@ interface FidesConfigTesting { export const stubConfig = ( { consent, experience, geolocation, options }: Partial, mockGeolocationApiResp?: any, - mockExperienceApiResp?: any + mockExperienceApiResp?: any, + demoPageQueryParams?: any, + demoPageWindowParams?: any ) => { cy.fixture("consent/test_banner_options.json").then((config) => { const updatedConfig = { @@ -109,6 +111,10 @@ export const stubConfig = ( `${updatedConfig.options.fidesApiUrl}${FidesEndpointPaths.NOTICES_SERVED}`, { fixture: "consent/notices_served.json" } ).as("patchNoticesServed"); - cy.visitConsentDemo(updatedConfig); + cy.visitConsentDemo( + updatedConfig, + demoPageQueryParams, + demoPageWindowParams + ); }); }; From 3c130823dc96c0887fbe4a4957e67ec2d430bbb4 Mon Sep 17 00:00:00 2001 From: eastandwestwind Date: Wed, 18 Oct 2023 18:35:02 +0200 Subject: [PATCH 3/8] adds changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 66e3a91369..ebf2e53219 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ The types of changes are: ## [Unreleased](https://github.com/ethyca/fides/compare/2.22.0...main) - Added support for 3 additional config variables in Fides.js: fidesEmbed, fidesDisableSaveApi, and fidesTcString [#4262](https://github.com/ethyca/fides/pull/4262) +- Added support for fidesEmbed, fidesDisableSaveApi, and fidesTcString to be passed into Fides.js via query param, cookie, or window object [#4297](https://github.com/ethyca/fides/pull/4297) ### Added - Added a `FidesPreferenceToggled` event to Fides.js to track when user preferences change without being saved [#4253](https://github.com/ethyca/fides/pull/4253) From b1e127897fefeebfaf6cac1d6ea90d6913eec4e2 Mon Sep 17 00:00:00 2001 From: eastandwestwind Date: Thu, 19 Oct 2023 15:39:48 +0200 Subject: [PATCH 4/8] address CR concerns, small refactors --- clients/fides-js/src/lib/consent-constants.ts | 33 ++++++++++++++++ clients/fides-js/src/lib/consent-types.ts | 38 ------------------- clients/fides-js/src/lib/consent-utils.ts | 2 +- clients/fides-js/src/lib/initialize.ts | 6 +-- .../cypress/e2e/consent-banner-tcf.cy.ts | 25 ++++++------ 5 files changed, 48 insertions(+), 56 deletions(-) create mode 100644 clients/fides-js/src/lib/consent-constants.ts diff --git a/clients/fides-js/src/lib/consent-constants.ts b/clients/fides-js/src/lib/consent-constants.ts new file mode 100644 index 0000000000..0b0ea7cfba --- /dev/null +++ b/clients/fides-js/src/lib/consent-constants.ts @@ -0,0 +1,33 @@ +import { FidesOptionOverrides, OverrideOptions } from "./consent-types"; + +// Regex to validate a location string, which must: +// 1) Start with a 2-3 character country code (e.g. "US") +// 2) Optionally end with a 2-3 character region code (e.g. "CA") +// 3) Separated by a dash (e.g. "US-CA") +export const VALID_ISO_3166_LOCATION_REGEX = /^\w{2,3}(-\w{2,3})?$/; + +export const FIDES_OVERRIDE_OPTIONS_VALIDATOR_MAP: { + fidesOption: keyof FidesOptionOverrides; + fidesOptionType: "string" | "boolean"; + fidesOverrideKey: keyof OverrideOptions; + validationRegex: RegExp; +}[] = [ + { + fidesOption: "fidesEmbed", + fidesOptionType: "boolean", + fidesOverrideKey: "fides_embed", + validationRegex: /^(true|false)$/, + }, + { + fidesOption: "fidesDisableSaveApi", + fidesOptionType: "boolean", + fidesOverrideKey: "fides_disable_save_api", + validationRegex: /^(true|false)$/, + }, + { + fidesOption: "fidesString", + fidesOptionType: "string", + fidesOverrideKey: "fides_string", + validationRegex: /(.*)/, + }, +]; diff --git a/clients/fides-js/src/lib/consent-types.ts b/clients/fides-js/src/lib/consent-types.ts index 0205c5ef11..3a72a454d6 100644 --- a/clients/fides-js/src/lib/consent-types.ts +++ b/clients/fides-js/src/lib/consent-types.ts @@ -201,12 +201,6 @@ export type UserGeolocation = { region?: string; // "NY" }; -// Regex to validate a location string, which must: -// 1) Start with a 2-3 character country code (e.g. "US") -// 2) Optionally end with a 2-3 character region code (e.g. "CA") -// 3) Separated by a dash (e.g. "US-CA") -export const VALID_ISO_3166_LOCATION_REGEX = /^\w{2,3}(-\w{2,3})?$/; - export type OverrideOptions = { fides_string: string; fides_disable_save_api: boolean; @@ -218,38 +212,6 @@ export type FidesOptionOverrides = Pick< "fidesString" | "fidesDisableSaveApi" | "fidesEmbed" >; -export enum OverrideFidesOption { - FIDES_STRING = "fides_string", - FIDES_DISABLE_SAVE_API = "fides_disable_save_api", - FIDES_EMBED = "fides_embed", -} - -export const FIDES_OVERRIDE_OPTIONS_VALIDATOR_MAP: { - fidesOption: keyof FidesOptionOverrides; - fidesOptionType: "string" | "boolean"; - fidesOverrideKey: keyof OverrideOptions; - validationRegex: RegExp; -}[] = [ - { - fidesOption: "fidesEmbed", - fidesOptionType: "boolean", - fidesOverrideKey: OverrideFidesOption.FIDES_EMBED, - validationRegex: /^(true|false)$/, - }, - { - fidesOption: "fidesDisableSaveApi", - fidesOptionType: "boolean", - fidesOverrideKey: OverrideFidesOption.FIDES_DISABLE_SAVE_API, - validationRegex: /^(true|false)$/, - }, - { - fidesOption: "fidesString", - fidesOptionType: "string", - fidesOverrideKey: OverrideFidesOption.FIDES_STRING, - validationRegex: /(.*)/, - }, -]; - export enum ButtonType { PRIMARY = "primary", SECONDARY = "secondary", diff --git a/clients/fides-js/src/lib/consent-utils.ts b/clients/fides-js/src/lib/consent-utils.ts index 8a543d30c7..e1ef7d029a 100644 --- a/clients/fides-js/src/lib/consent-utils.ts +++ b/clients/fides-js/src/lib/consent-utils.ts @@ -9,10 +9,10 @@ import { PrivacyNotice, UserConsentPreference, UserGeolocation, - VALID_ISO_3166_LOCATION_REGEX, } from "./consent-types"; import { EXPERIENCE_KEYS_WITH_PREFERENCES } from "./tcf/constants"; import { TCFPurposeConsentRecord } from "./tcf/types"; +import { VALID_ISO_3166_LOCATION_REGEX } from "./consent-constants"; /** * Wrapper around 'console.log' that only logs output when the 'debug' banner diff --git a/clients/fides-js/src/lib/initialize.ts b/clients/fides-js/src/lib/initialize.ts index 541223fd85..fcfdf4c3fb 100644 --- a/clients/fides-js/src/lib/initialize.ts +++ b/clients/fides-js/src/lib/initialize.ts @@ -20,7 +20,6 @@ import { ConsentMechanism, ConsentMethod, EmptyExperience, - FIDES_OVERRIDE_OPTIONS_VALIDATOR_MAP, FidesConfig, FidesOptionOverrides, FidesOptions, @@ -43,6 +42,7 @@ import { updateConsentPreferences } from "./preferences"; import { resolveConsentValue } from "./consent-value"; import { initOverlay } from "./consent"; import { TcfCookieConsent } from "./tcf/types"; +import { FIDES_OVERRIDE_OPTIONS_VALIDATOR_MAP } from "./consent-constants"; export type Fides = { consent: CookieKeyConsent; @@ -149,7 +149,7 @@ const automaticallyApplyGPCPreferences = ({ /** * Gets and validates Fides override options provided through URL query params, cookie or window obj. */ -export const getOverrideFidesOptions = (): FidesOptionOverrides => { +export const getOverrideFidesOptions = (): Partial => { const overrideOptions: Partial = {}; if (typeof window !== "undefined") { const params = new URLSearchParams(document.location.search); @@ -172,7 +172,7 @@ export const getOverrideFidesOptions = (): FidesOptionOverrides => { } ); } - return overrideOptions; + return overrideOptions; }; /** diff --git a/clients/privacy-center/cypress/e2e/consent-banner-tcf.cy.ts b/clients/privacy-center/cypress/e2e/consent-banner-tcf.cy.ts index 9919cd4a9e..d021bd3711 100644 --- a/clients/privacy-center/cypress/e2e/consent-banner-tcf.cy.ts +++ b/clients/privacy-center/cypress/e2e/consent-banner-tcf.cy.ts @@ -5,7 +5,6 @@ import { PrivacyExperience, UserConsentPreference, } from "fides-js"; -import { OverrideFidesOption } from "fides-js/src/lib/consent-types"; import { OVERRIDE, stubConfig } from "../support/stubs"; const PURPOSE_2 = { @@ -745,10 +744,8 @@ describe("Fides-js TCF", () => { }); it("skips saving preferences to API when disable save is set via cookie", () => { cy.getCookie(CONSENT_COOKIE_NAME).should("not.exist"); - cy.getCookie(OverrideFidesOption.FIDES_DISABLE_SAVE_API).should( - "not.exist" - ); - cy.setCookie(OverrideFidesOption.FIDES_DISABLE_SAVE_API, "true"); + cy.getCookie("fides_disable_save_api").should("not.exist"); + cy.setCookie("fides_disable_save_api", "true"); cy.fixture("consent/experience_tcf.json").then((experience) => { stubConfig({ options: { @@ -778,7 +775,7 @@ describe("Fides-js TCF", () => { }); }); it("skips saving preferences to API when disable save is set via query param", () => { - cy.getCookie(OverrideFidesOption.FIDES_STRING).should("not.exist"); + cy.getCookie("fides_string").should("not.exist"); cy.fixture("consent/experience_tcf.json").then((experience) => { stubConfig( { @@ -813,7 +810,7 @@ describe("Fides-js TCF", () => { }); }); it("skips saving preferences to API when disable save is set via window obj", () => { - cy.getCookie(OverrideFidesOption.FIDES_STRING).should("not.exist"); + cy.getCookie("fides_string").should("not.exist"); cy.fixture("consent/experience_tcf.json").then((experience) => { stubConfig( { @@ -984,8 +981,8 @@ describe("Fides-js TCF", () => { }); it("automatically renders the second layer when fidesEmbed is set via cookie", () => { cy.getCookie(CONSENT_COOKIE_NAME).should("not.exist"); - cy.getCookie(OverrideFidesOption.FIDES_EMBED).should("not.exist"); - cy.setCookie(OverrideFidesOption.FIDES_EMBED, "true"); + cy.getCookie("fides_embed").should("not.exist"); + cy.setCookie("fides_embed", "true"); cy.fixture("consent/experience_tcf.json").then((experience) => { stubConfig({ options: { @@ -1036,7 +1033,7 @@ describe("Fides-js TCF", () => { }); }); it("automatically renders the second layer when fidesEmbed is set via window obj", () => { - cy.getCookie(OverrideFidesOption.FIDES_STRING).should("not.exist"); + cy.getCookie("fides_string").should("not.exist"); cy.getCookie(CONSENT_COOKIE_NAME).should("not.exist"); cy.fixture("consent/experience_tcf.json").then((experience) => { stubConfig( @@ -1707,10 +1704,10 @@ describe("Fides-js TCF", () => { describe("fides string override options", () => { it("uses TC string when set via cookie", () => { - cy.getCookie(OverrideFidesOption.FIDES_STRING).should("not.exist"); + cy.getCookie("fides_string").should("not.exist"); // this TC string sets purpose 4 to false and purpose 7 to true cy.setCookie( - OverrideFidesOption.FIDES_STRING, + "fides_string", "CPzevcAPzevcAGXABBENATEIAAIAAAAAAAAAAAAAAAAA.IABE" ); cy.fixture("consent/experience_tcf.json").then((experience) => { @@ -1750,7 +1747,7 @@ describe("Fides-js TCF", () => { }); }); it("uses TC string when set via query param", () => { - cy.getCookie(OverrideFidesOption.FIDES_STRING).should("not.exist"); + cy.getCookie("fides_string").should("not.exist"); cy.fixture("consent/experience_tcf.json").then((experience) => { stubConfig( { @@ -1794,7 +1791,7 @@ describe("Fides-js TCF", () => { }); }); it("uses TC string when set via window obj", () => { - cy.getCookie(OverrideFidesOption.FIDES_STRING).should("not.exist"); + cy.getCookie("fides_string").should("not.exist"); cy.fixture("consent/experience_tcf.json").then((experience) => { stubConfig( { From e0e87ef08b80673fd75c55ade643058a1111e414 Mon Sep 17 00:00:00 2001 From: eastandwestwind Date: Thu, 19 Oct 2023 15:44:09 +0200 Subject: [PATCH 5/8] fix type err --- clients/fides-js/src/fides-tcf.ts | 2 +- clients/fides-js/src/fides.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/clients/fides-js/src/fides-tcf.ts b/clients/fides-js/src/fides-tcf.ts index 955af8f3ec..3168f09683 100644 --- a/clients/fides-js/src/fides-tcf.ts +++ b/clients/fides-js/src/fides-tcf.ts @@ -170,7 +170,7 @@ const updateCookie = async ( * Initialize the global Fides object with the given configuration values */ const init = async (config: FidesConfig) => { - const overrideOptions: FidesOptionOverrides = getOverrideFidesOptions(); + const overrideOptions: Partial = getOverrideFidesOptions(); // eslint-disable-next-line no-param-reassign config.options = { ...config.options, ...overrideOptions }; const cookie = getInitialCookie(config); diff --git a/clients/fides-js/src/fides.ts b/clients/fides-js/src/fides.ts index 0ad2b74288..f9fb21c325 100644 --- a/clients/fides-js/src/fides.ts +++ b/clients/fides-js/src/fides.ts @@ -104,7 +104,7 @@ const updateCookie = async ( * Initialize the global Fides object with the given configuration values */ const init = async (config: FidesConfig) => { - const overrideOptions: FidesOptionOverrides = getOverrideFidesOptions(); + const overrideOptions: Partial = getOverrideFidesOptions(); // eslint-disable-next-line no-param-reassign config.options = { ...config.options, ...overrideOptions }; const cookie = getInitialCookie(config); From 2ed6dfd50254f9a3eb8bca9ca637a2ff4046f5a4 Mon Sep 17 00:00:00 2001 From: eastandwestwind Date: Thu, 19 Oct 2023 15:47:41 +0200 Subject: [PATCH 6/8] format --- clients/fides-js/src/fides-tcf.ts | 3 ++- clients/fides-js/src/fides.ts | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/clients/fides-js/src/fides-tcf.ts b/clients/fides-js/src/fides-tcf.ts index 3168f09683..debd5ecf25 100644 --- a/clients/fides-js/src/fides-tcf.ts +++ b/clients/fides-js/src/fides-tcf.ts @@ -170,7 +170,8 @@ const updateCookie = async ( * Initialize the global Fides object with the given configuration values */ const init = async (config: FidesConfig) => { - const overrideOptions: Partial = getOverrideFidesOptions(); + const overrideOptions: Partial = + getOverrideFidesOptions(); // eslint-disable-next-line no-param-reassign config.options = { ...config.options, ...overrideOptions }; const cookie = getInitialCookie(config); diff --git a/clients/fides-js/src/fides.ts b/clients/fides-js/src/fides.ts index f9fb21c325..5fbbb07b5a 100644 --- a/clients/fides-js/src/fides.ts +++ b/clients/fides-js/src/fides.ts @@ -104,7 +104,8 @@ const updateCookie = async ( * Initialize the global Fides object with the given configuration values */ const init = async (config: FidesConfig) => { - const overrideOptions: Partial = getOverrideFidesOptions(); + const overrideOptions: Partial = + getOverrideFidesOptions(); // eslint-disable-next-line no-param-reassign config.options = { ...config.options, ...overrideOptions }; const cookie = getInitialCookie(config); From 61c2a092a6c7f590531efed93728df2058a37748 Mon Sep 17 00:00:00 2001 From: eastandwestwind Date: Thu, 19 Oct 2023 16:52:10 +0200 Subject: [PATCH 7/8] adds check for consent in cookie.tcf_consent before generating tc str on cookie --- clients/fides-js/src/fides-tcf.ts | 3 ++- clients/fides-js/src/lib/cookie.ts | 9 +++++++++ .../privacy-center/cypress/e2e/consent-banner-tcf.cy.ts | 1 + 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/clients/fides-js/src/fides-tcf.ts b/clients/fides-js/src/fides-tcf.ts index debd5ecf25..a8dc055326 100644 --- a/clients/fides-js/src/fides-tcf.ts +++ b/clients/fides-js/src/fides-tcf.ts @@ -74,6 +74,7 @@ import { hasSavedTcfPreferences, isNewFidesCookie, isPrivacyExperience, + tcfConsentCookieObjHasSomeConsentSet, transformTcfPreferencesToCookieKeys, transformUserPreferenceToBoolean, } from "./fides"; @@ -188,7 +189,7 @@ const init = async (config: FidesConfig) => { config.options.debug ); } else if ( - cookie.tcf_consent && + tcfConsentCookieObjHasSomeConsentSet(cookie.tcf_consent) && !cookie.fides_string && isPrivacyExperience(config.experience) && experienceIsValid(config.experience, config.options) diff --git a/clients/fides-js/src/lib/cookie.ts b/clients/fides-js/src/lib/cookie.ts index c340ef348d..c743789eca 100644 --- a/clients/fides-js/src/lib/cookie.ts +++ b/clients/fides-js/src/lib/cookie.ts @@ -81,6 +81,15 @@ const CODEC: Types.CookieCodecConfig = { encodeValue: encodeURIComponent, }; +export const tcfConsentCookieObjHasSomeConsentSet = ( + tcf_consent: TcfCookieConsent | undefined +): boolean => { + if (!tcf_consent) { + return false; + } + return Object.keys(tcf_consent).some((val) => Object.keys(val).length >= 0); +}; + /** * Each cookie will be assigned an autogenerated user/device ID, to match user's * consent preferences between the browser and the server. This is a randomly diff --git a/clients/privacy-center/cypress/e2e/consent-banner-tcf.cy.ts b/clients/privacy-center/cypress/e2e/consent-banner-tcf.cy.ts index d021bd3711..55e9a782eb 100644 --- a/clients/privacy-center/cypress/e2e/consent-banner-tcf.cy.ts +++ b/clients/privacy-center/cypress/e2e/consent-banner-tcf.cy.ts @@ -1125,6 +1125,7 @@ describe("Fides-js TCF", () => { }); it("can handle inappropriate legint purposes", () => { + cy.getCookie(CONSENT_COOKIE_NAME).should("not.exist"); cy.fixture("consent/experience_tcf.json").then((payload) => { const experience: PrivacyExperience = payload.items[0]; // Set purpose with id 4 to LegInt which is not allowed! From df8d3399f575404f2f3b0c23ceb2c8dc4fec381b Mon Sep 17 00:00:00 2001 From: eastandwestwind Date: Thu, 19 Oct 2023 17:13:25 +0200 Subject: [PATCH 8/8] use obj vals instead of keys --- clients/fides-js/src/lib/cookie.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/clients/fides-js/src/lib/cookie.ts b/clients/fides-js/src/lib/cookie.ts index c743789eca..8a0cf466a0 100644 --- a/clients/fides-js/src/lib/cookie.ts +++ b/clients/fides-js/src/lib/cookie.ts @@ -20,6 +20,7 @@ import { } from "./consent-utils"; import type { TcfCookieConsent, TcfSavePreferences } from "./tcf/types"; import { TCF_KEY_MAP } from "./tcf/constants"; +import { TcfCookieKeyConsent } from "./tcf/types"; /** * Store the user's consent preferences on the cookie, as key -> boolean pairs, e.g. @@ -87,7 +88,9 @@ export const tcfConsentCookieObjHasSomeConsentSet = ( if (!tcf_consent) { return false; } - return Object.keys(tcf_consent).some((val) => Object.keys(val).length >= 0); + return Object.values(tcf_consent).some( + (val: TcfCookieKeyConsent) => Object.keys(val).length >= 0 + ); }; /**