From ec35d1d24841fb3e74ac28a1c2d17c124a87124e Mon Sep 17 00:00:00 2001 From: Kelsey Thomas <101993653+Kelsey-Ethyca@users.noreply.github.com> Date: Wed, 19 Jun 2024 13:40:56 -0700 Subject: [PATCH 01/13] empty commit to start release 2.39.0 From 151ac6bb90f0d65fcc079936bd37be7cd1170b5f Mon Sep 17 00:00:00 2001 From: Jason Gill Date: Wed, 19 Jun 2024 18:24:49 -0600 Subject: [PATCH 02/13] Disable GPP stub when `initialize=false` is set until `Fides.init()` is called (#5010) --- CHANGELOG.md | 3 ++ .../fides-js/docs/interfaces/FidesEvent.md | 4 ++ clients/fides-js/src/docs/fides-event.ts | 15 ++++-- clients/fides-js/src/fides-ext-gpp.ts | 6 ++- clients/fides-js/src/fides-tcf.ts | 13 ++++++ clients/fides-js/src/fides.ts | 13 ++++++ clients/fides-js/src/lib/consent-types.ts | 3 ++ clients/fides-js/src/lib/events.ts | 11 +++-- .../cypress/e2e/consent-banner-gpp.cy.ts | 46 +++++++++++++++---- .../cypress/e2e/consent-banner.cy.ts | 3 +- .../privacy-center/cypress/e2e/fides-js.cy.ts | 14 +++--- .../cypress/support/commands.ts | 4 ++ clients/privacy-center/pages/api/fides-js.ts | 28 +++++------ .../public/fides-js-components-demo.html | 19 ++++---- 14 files changed, 130 insertions(+), 52 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c0964d9d1..fdff7218d0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,7 @@ The types of changes are: - New privacy request search to replace existing endpoint [#4987](https://github.com/ethyca/fides/pull/4987) - Added new Google Cloud SQL for MySQL Connector [#4949](https://github.com/ethyca/fides/pull/4949) - Add new options for integrations for discovery & detection [#5000](https://github.com/ethyca/fides/pull/5000) +- Add new `FidesInitializing` event for when FidesJS begins initialization [#5010](https://github.com/ethyca/fides/pull/5010) ### Changed - Move new data map reporting table out of beta and remove old table from Data Lineage map. [#4963](https://github.com/ethyca/fides/pull/4963) @@ -44,6 +45,8 @@ The types of changes are: - Masked "Keyfile credentials" input on integration config form [#4971](https://github.com/ethyca/fides/pull/4971) - Fixed validations for privacy declaration taxonomy labels when creating/updating a System [#4982](https://github.com/ethyca/fides/pull/4982) - Allow property-specific messaging to work with non-custom templates [#4986](https://github.com/ethyca/fides/pull/4986) +- Fixed an issue where config object was being passed twice to `fides.js` output [#5010](https://github.com/ethyca/fides/pull/5010) +- Disabling Fides initializtion now also disables GPP initialization [#5010](https://github.com/ethyca/fides/pull/5010) ## [2.38.1](https://github.com/ethyca/fides/compare/2.38.0...2.38.1) diff --git a/clients/fides-js/docs/interfaces/FidesEvent.md b/clients/fides-js/docs/interfaces/FidesEvent.md index 1dd625bc61..d7df941167 100644 --- a/clients/fides-js/docs/interfaces/FidesEvent.md +++ b/clients/fides-js/docs/interfaces/FidesEvent.md @@ -25,6 +25,10 @@ the browser, see the MDN docs: ### List of FidesEvent Types +- `FidesInitializing`: Dispatched when initialization begins, which happens +immediately once the FidesJS script is loaded. If `Fides.init()` is called +multiple times, this event will also be dispatched each time. + - `FidesInitialized`: Dispatched when initialization is complete and the current user's consent preferences - either previously saved or applicable defaults - have been set on the `Fides` global object. diff --git a/clients/fides-js/src/docs/fides-event.ts b/clients/fides-js/src/docs/fides-event.ts index 6be235ca4e..f182c32079 100644 --- a/clients/fides-js/src/docs/fides-event.ts +++ b/clients/fides-js/src/docs/fides-event.ts @@ -5,10 +5,11 @@ * documentation, since it's mostly just noise there - the list of events on * {@link FidesEvent} provides a good reference. But when coding, it's still * useful to have this union type around! - * + * * @private */ export type FidesEventType = + | "FidesInitializing" | "FidesInitialized" | "FidesUpdating" | "FidesUpdated" @@ -41,10 +42,14 @@ export type FidesEventType = * * ### List of FidesEvent Types * + * - `FidesInitializing`: Dispatched when initialization begins, which happens + * immediately once the FidesJS script is loaded. If `Fides.init()` is called + * multiple times, this event will also be dispatched each time. + * * - `FidesInitialized`: Dispatched when initialization is complete and the * current user's consent preferences - either previously saved or applicable * defaults - have been set on the `Fides` global object. - * + * * - `FidesUpdating`: Dispatched when a user action (e.g. accepting all, saving * changes, applying GPC) has started updating the user's consent preferences. * This event is dispatched immediately once the changes are made, but before @@ -58,7 +63,7 @@ export type FidesEventType = * object, `fides_consent` cookie on the user's device, and the Fides API. To * receive an event that fires before these changes are saved, use the * `FidesUpdating` event instead. - * + * * - `FidesUIShown`: Dispatched whenever a FidesJS UI component is rendered and * shown to the current user (banner, modal, etc.). The specific component shown * can be obtained from the `detail.extraDetails.servingComponent` property on @@ -107,7 +112,7 @@ export interface FidesEvent extends CustomEvent { /** * Whether the user should be shown the consent experience. Only available on FidesInitialized events. */ - shouldShowExperience?: boolean; + shouldShowExperience?: boolean; /** * What consent method (if any) caused this event. @@ -115,4 +120,4 @@ export interface FidesEvent extends CustomEvent { consentMethod?: "accept" | "reject" | "save" | "dismiss" | "gpc"; }; }; -}; \ No newline at end of file +} diff --git a/clients/fides-js/src/fides-ext-gpp.ts b/clients/fides-js/src/fides-ext-gpp.ts index d8b09b9c93..c1256a2f85 100644 --- a/clients/fides-js/src/fides-ext-gpp.ts +++ b/clients/fides-js/src/fides-ext-gpp.ts @@ -246,4 +246,8 @@ const initializeGppCmpApi = () => { cmpApi.setSignalStatus(SignalStatus.READY); }); }; -initializeGppCmpApi(); +window.addEventListener("FidesInitializing", (event) => { + if (event.detail.extraDetails?.gppEnabled) { + initializeGppCmpApi(); + } +}); diff --git a/clients/fides-js/src/fides-tcf.ts b/clients/fides-js/src/fides-tcf.ts index aa1dc05ab4..eb40d5cff6 100644 --- a/clients/fides-js/src/fides-tcf.ts +++ b/clients/fides-js/src/fides-tcf.ts @@ -124,6 +124,18 @@ async function init(this: FidesGlobal, providedConfig?: FidesConfig) { this.config = config; // no matter how the config is set, we want to store it on the global object updateWindowFides(this); + dispatchFidesEvent( + "FidesInitializing", + undefined, + this.config.options.debug, + { + gppEnabled: + this.config.options.gppEnabled || + this.config.experience?.gpp_settings?.enabled, + tcfEnabled: this.config.options.tcfEnabled, + } + ); + const optionsOverrides: Partial = getOverridesByType>( OverrideType.OPTIONS, @@ -235,6 +247,7 @@ const _Fides: FidesGlobal = { fidesApiUrl: "", serverSideFidesApiUrl: "", tcfEnabled: true, + gppEnabled: false, fidesEmbed: false, fidesDisableSaveApi: false, fidesDisableNoticesServedApi: false, diff --git a/clients/fides-js/src/fides.ts b/clients/fides-js/src/fides.ts index 795c7225de..c8e13aa216 100644 --- a/clients/fides-js/src/fides.ts +++ b/clients/fides-js/src/fides.ts @@ -93,6 +93,18 @@ async function init(this: FidesGlobal, providedConfig?: FidesConfig) { this.config = config; // no matter how the config is set, we want to store it on the global object + dispatchFidesEvent( + "FidesInitializing", + undefined, + this.config.options.debug, + { + gppEnabled: + this.config.options.gppEnabled || + this.config.experience?.gpp_settings?.enabled, + tcfEnabled: this.config.options.tcfEnabled, + } + ); + const optionsOverrides: Partial = getOverridesByType>( OverrideType.OPTIONS, @@ -173,6 +185,7 @@ const _Fides: FidesGlobal = { fidesApiUrl: "", serverSideFidesApiUrl: "", tcfEnabled: false, + gppEnabled: false, fidesEmbed: false, fidesDisableSaveApi: false, fidesDisableNoticesServedApi: false, diff --git a/clients/fides-js/src/lib/consent-types.ts b/clients/fides-js/src/lib/consent-types.ts index 2e02d2514a..98e7d0b6a6 100644 --- a/clients/fides-js/src/lib/consent-types.ts +++ b/clients/fides-js/src/lib/consent-types.ts @@ -78,6 +78,9 @@ export interface FidesInitOptions { // Whether we should show the TCF modal tcfEnabled: boolean; + // Whether to include the GPP extension + gppEnabled: boolean; + // Whether we should "embed" the fides.js overlay UI (ie. “Layer 2”) into a web page instead of as a pop-up // overlay, and never render the banner (ie. “Layer 1”). fidesEmbed: boolean; diff --git a/clients/fides-js/src/lib/events.ts b/clients/fides-js/src/lib/events.ts index 8c5daba8f4..9431a5d975 100644 --- a/clients/fides-js/src/lib/events.ts +++ b/clients/fides-js/src/lib/events.ts @@ -12,7 +12,10 @@ declare global { * events. This is intentionally vague, but constrained to be basic (primitive) * values for simplicity. */ -export type FidesEventExtraDetails = Record; +export type FidesEventExtraDetails = Record< + string, + string | number | boolean | undefined +>; /** * Defines the properties available on event.detail. Currently the FidesCookie @@ -49,14 +52,14 @@ export type FidesEvent = CustomEvent; */ export const dispatchFidesEvent = ( type: FidesEventType, - cookie: FidesCookie, + cookie: FidesCookie | undefined, debug: boolean, extraDetails?: FidesEventExtraDetails ) => { if (typeof window !== "undefined" && typeof CustomEvent !== "undefined") { // Extracts consentMethod directly from the cookie instead of having to pass in duplicate data to this method const constructedExtraDetails: FidesEventExtraDetails = { - consentMethod: cookie.fides_meta.consentMethod, + consentMethod: cookie?.fides_meta.consentMethod, ...extraDetails, }; const event = new CustomEvent(type, { @@ -68,7 +71,7 @@ export const dispatchFidesEvent = ( constructedExtraDetails?.servingComponent ? `from ${constructedExtraDetails.servingComponent} ` : "" - }with cookie ${JSON.stringify(cookie)} ${ + }${cookie ? `with cookie ${JSON.stringify(cookie)} ` : ""}${ constructedExtraDetails ? `with extra details ${JSON.stringify(constructedExtraDetails)} ` : "" diff --git a/clients/privacy-center/cypress/e2e/consent-banner-gpp.cy.ts b/clients/privacy-center/cypress/e2e/consent-banner-gpp.cy.ts index 621ec0befd..143f148d88 100644 --- a/clients/privacy-center/cypress/e2e/consent-banner-gpp.cy.ts +++ b/clients/privacy-center/cypress/e2e/consent-banner-gpp.cy.ts @@ -19,9 +19,12 @@ describe("Fides-js GPP extension", () => { /** * Visit the fides-js-components-demo page with optional overrides on experience */ - const visitDemoWithGPP = (props: { - overrideExperience?: (experience: PrivacyExperience) => PrivacyExperience; - }) => { + const visitDemoWithGPP = ( + props: { + overrideExperience?: (experience: PrivacyExperience) => PrivacyExperience; + }, + queryParams?: Cypress.VisitOptions["qs"] + ) => { cy.fixture("consent/experience_gpp.json").then((payload) => { let experience = payload.items[0]; if (props.overrideExperience) { @@ -31,13 +34,18 @@ describe("Fides-js GPP extension", () => { experience ); } - stubConfig({ - options: { - isOverlayEnabled: true, - tcfEnabled: false, + stubConfig( + { + options: { + isOverlayEnabled: true, + tcfEnabled: false, + }, + experience, }, - experience, - }); + undefined, + undefined, + queryParams + ); }); }; @@ -67,6 +75,26 @@ describe("Fides-js GPP extension", () => { }); }); + describe("Fides is not initialized", () => { + beforeEach(() => { + visitDemoWithGPP({}, { init: false }); + }); + it("does not load the GPP extension", () => { + cy.get("@FidesInitializing").should("not.have.been.called"); + cy.window().then((win) => { + expect(win.__gpp).to.eql(undefined); + }); + }); + it("can still load the GPP extension if initialized later", () => { + cy.window().then((win) => { + win.Fides.init(win.Fides.config); + cy.waitUntilFidesInitialized().then(() => { + expect(win.__gpp).to.not.eql(undefined); + }); + }); + }); + }); + describe("with TCF and GPP enabled", () => { const tcfGppSettings = { enabled: true, enable_tcfeu_string: true }; beforeEach(() => { diff --git a/clients/privacy-center/cypress/e2e/consent-banner.cy.ts b/clients/privacy-center/cypress/e2e/consent-banner.cy.ts index b03f471c5e..69236431d2 100644 --- a/clients/privacy-center/cypress/e2e/consent-banner.cy.ts +++ b/clients/privacy-center/cypress/e2e/consent-banner.cy.ts @@ -1649,11 +1649,12 @@ describe("Consent overlay", () => { isOverlayEnabled: true, }, }); + cy.get("@FidesInitializing").should("have.been.calledOnce"); }); // NOTE: See definition of cy.visitConsentDemo in commands.ts for where we // register listeners for these window events - it("emits a FidesInitialized but not any other events when initialized", () => { + it("emits a FidesInitialized but not any subsequent events when initialized", () => { cy.window() .its("Fides") .its("consent") diff --git a/clients/privacy-center/cypress/e2e/fides-js.cy.ts b/clients/privacy-center/cypress/e2e/fides-js.cy.ts index a2dcc25b51..b82792b34b 100644 --- a/clients/privacy-center/cypress/e2e/fides-js.cy.ts +++ b/clients/privacy-center/cypress/e2e/fides-js.cy.ts @@ -15,10 +15,10 @@ describe("fides.js API route", () => { .to.match(/^\s+\(function/, "should be an IIFE") .to.match(/\}\)\(\);\s+$/, "should be an IIFE"); expect(response.body) - .to.match(/var fidesConfig = \{/, "should bundle Fides.init") - .to.match(/Fides.init\(fidesConfig\);/, "should bundle Fides.init"); + .to.match(/window.Fides.config = \{/, "should bundle Fides.init") + .to.match(/Fides.init\(\);/, "should call Fides.init"); const matches = response.body.match( - /var fidesConfig = (?\{.*?\});/ + /window.Fides.config = (?\{.*?\});/ ); expect(matches).to.have.nested.property("groups.json"); expect(JSON.parse(matches.groups.json)) @@ -44,9 +44,9 @@ describe("fides.js API route", () => { describe("when pre-fetching geolocation", () => { it("returns geolocation if provided as a '?geolocation' query param", () => { cy.request("/fides.js?geolocation=US-CA").then((response) => { - expect(response.body).to.match(/var fidesConfig = \{/); + expect(response.body).to.match(/window.Fides.config = \{/); const matches = response.body.match( - /var fidesConfig = (?\{.*?\});/ + /window.Fides.config = (?\{.*?\});/ ); expect(JSON.parse(matches.groups.json)) .to.have.nested.property("geolocation") @@ -66,9 +66,9 @@ describe("fides.js API route", () => { "CloudFront-Viewer-Country-Region": "IDF", }, }).then((response) => { - expect(response.body).to.match(/var fidesConfig = \{/); + expect(response.body).to.match(/window.Fides.config = \{/); const matches = response.body.match( - /var fidesConfig = (?\{.*?\});/ + /window.Fides.config = (?\{.*?\});/ ); expect(JSON.parse(matches.groups.json)) .to.have.nested.property("geolocation") diff --git a/clients/privacy-center/cypress/support/commands.ts b/clients/privacy-center/cypress/support/commands.ts index ce1bc0a647..92d36d329f 100644 --- a/clients/privacy-center/cypress/support/commands.ts +++ b/clients/privacy-center/cypress/support/commands.ts @@ -97,6 +97,10 @@ Cypress.Commands.add( } // Add event listeners for Fides.js events + win.addEventListener( + "FidesInitializing", + cy.stub().as("FidesInitializing") + ); win.addEventListener( "FidesInitialized", cy.stub().as("FidesInitialized") diff --git a/clients/privacy-center/pages/api/fides-js.ts b/clients/privacy-center/pages/api/fides-js.ts index a5e639b0ff..b380078859 100644 --- a/clients/privacy-center/pages/api/fides-js.ts +++ b/clients/privacy-center/pages/api/fides-js.ts @@ -167,11 +167,9 @@ export default async function handler( } } - // These query params are used for testing purposes only, and should not be used - // in production. They allow for the config to be injected by the test framework - // and delay the initialization of fides.js until the test framework is ready. - const { e2e: e2eQuery, tcf: tcfQuery } = req.query; - const isTestMode = e2eQuery === "true"; + // This query param is used for testing purposes only, and should not be used + // in production. + const { tcf: tcfQuery } = req.query; // We determine server-side whether or not to send the TCF bundle, which is based // on whether or not the experience is marked as TCF. This means for TCF, we *must* @@ -209,6 +207,7 @@ export default async function handler( privacyCenterUrl: environment.settings.PRIVACY_CENTER_URL, fidesApiUrl: environment.settings.FIDES_API_URL, tcfEnabled, + gppEnabled, serverSideFidesApiUrl: environment.settings.SERVER_SIDE_FIDES_API_URL || environment.settings.FIDES_API_URL, @@ -289,18 +288,13 @@ export default async function handler( document.head.append(style); ` : "" - }${ - isTestMode // let end-to-end tests set the config and initialize as needed - ? "" - : ` - var fidesConfig = ${fidesConfigJSON}; - window.Fides.config = ${fidesConfigJSON}; - ${skipInitialization ? "" : `window.Fides.init(fidesConfig);`} - ${ - environment.settings.DEBUG && skipInitialization - ? `console.log("fides.js initialization skipped. Call window.Fides.init() manually.");` - : "" - }` + } + window.Fides.config = ${fidesConfigJSON}; + ${skipInitialization ? "" : `window.Fides.init();`} + ${ + environment.settings.DEBUG && skipInitialization + ? `console.log("Fides initialization skipped. Call window.Fides.init() manually.");` + : "" } })(); `; diff --git a/clients/privacy-center/public/fides-js-components-demo.html b/clients/privacy-center/public/fides-js-components-demo.html index 4a6632e84a..e81e5a59d0 100644 --- a/clients/privacy-center/public/fides-js-components-demo.html +++ b/clients/privacy-center/public/fides-js-components-demo.html @@ -10,20 +10,20 @@ will pass a geolocation query param to fides.js -->