diff --git a/CHANGELOG.md b/CHANGELOG.md index 1b34498383..9cf08fd55c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,7 +21,8 @@ The types of changes are: - Added carets to collapsible sections in the overlay modal [#4793](https://github.com/ethyca/fides/pull/4793) - Added erasure support for OpenWeb [#4735](https://github.com/ethyca/fides/pull/4735) - Added support for configuration of pre-approval webhooks [#4795](https://github.com/ethyca/fides/pull/4795) -- Added CMP API fidesClearCookie to load CMP without preferences on refresh [#4810](https://github.com/ethyca/fides/pull/4810) +- Added fides_clear_cookie option to FidesJS SDK to load CMP without preferences on refresh [#4810](https://github.com/ethyca/fides/pull/4810) +- Added FidesUpdating event to FidesJS SDK [#4816](https://github.com/ethyca/fides/pull/4816) ### Changed - Removed the Celery startup banner from the Fides worker logs [#4814](https://github.com/ethyca/fides/pull/4814) @@ -30,6 +31,9 @@ The types of changes are: - Fixed bug prevented adding new privacy center translations [#4786](https://github.com/ethyca/fides/pull/4786) - Fixed bug where Privacy Policy links would be shown without a configured URL [#4801](https://github.com/ethyca/fides/pull/4801) +### Changed +- Removed the Celery startup banner from the Fides worker logs [#4814](https://github.com/ethyca/fides/pull/4814) + ## [2.34.0](https://github.com/ethyca/fides/compare/2.33.1...2.34.0) ### Added diff --git a/clients/fides-js/docs/interfaces/FidesEvent.md b/clients/fides-js/docs/interfaces/FidesEvent.md index 3a9eabb6ab..46c5f6332c 100644 --- a/clients/fides-js/docs/interfaces/FidesEvent.md +++ b/clients/fides-js/docs/interfaces/FidesEvent.md @@ -29,9 +29,19 @@ the browser, see the MDN docs: current user's consent preferences - either previously saved or applicable defaults - have been set on the `Fides` global object. -- `FidesUpdated`: Dispatched whenever the current user's consent preferences -are updated on the `Fides` global object due to a user action (e.g. accepting -all, applying GPC). The +- `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 +they are saved to the `Fides` object, `fides_consent` cookie on the user's +device, and the Fides API. To wait until the changes are fully +applied, use the `FidesUpdated` event instead. + +- `FidesUpdated`: Dispatched when a user action (e.g. accepting all, saving +changes, applying GPC) has finished updating the user's consent preferences. +This event is dispatched once the changes are fully saved to the `Fides` +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 diff --git a/clients/fides-js/docs/interfaces/FidesOptions.md b/clients/fides-js/docs/interfaces/FidesOptions.md index 4110302582..cfd59d877b 100644 --- a/clients/fides-js/docs/interfaces/FidesOptions.md +++ b/clients/fides-js/docs/interfaces/FidesOptions.md @@ -40,16 +40,28 @@ order of precedence: ### Properties +- [fides\_clear\_cookie](FidesOptions.md#fides_clear_cookie) - [fides\_disable\_banner](FidesOptions.md#fides_disable_banner) - [fides\_disable\_save\_api](FidesOptions.md#fides_disable_save_api) - [fides\_embed](FidesOptions.md#fides_embed) - [fides\_locale](FidesOptions.md#fides_locale) - [fides\_string](FidesOptions.md#fides_string) - [fides\_tcf\_gdpr\_applies](FidesOptions.md#fides_tcf_gdpr_applies) -- [fides\_clear\_cookie](FidesOptions.md#fides_clear_cookie) ## Properties +### fides\_clear\_cookie + +• **fides\_clear\_cookie**: `boolean` + +When `true`, deletes the `fides_consent` cookie when FidesJS is +initialized, to clear any previously saved consent preferences from the +user's device. + +Defaults to `false`. + +___ + ### fides\_disable\_banner • **fides\_disable\_banner**: `boolean` @@ -150,13 +162,3 @@ overriden at the page-level as needed. Only applicable to a TCF experience. For more details, see the [TCF CMP API technical specification](https://github.com/InteractiveAdvertisingBureau/GDPR-Transparency-and-Consent-Framework/blob/master/TCFv2/IAB%20Tech%20Lab%20-%20CMP%20API%20v2.md#what-does-the-gdprapplies-value-mean) * Defaults to `true`. - -___ - -### fides\_clear\_cookie - -• **fides\_clear\_cookie**: `boolean` - -When `true`, shows fides.js overlay UI on load. This deletes the fides_consent cookie as if no preferences have been saved on reload. - -Defaults to `false`. diff --git a/clients/fides-js/src/docs/fides-event.ts b/clients/fides-js/src/docs/fides-event.ts index 99cb3c08a8..8965004993 100644 --- a/clients/fides-js/src/docs/fides-event.ts +++ b/clients/fides-js/src/docs/fides-event.ts @@ -10,6 +10,7 @@ */ export type FidesEventType = | "FidesInitialized" + | "FidesUpdating" | "FidesUpdated" | "FidesUIShown" | "FidesUIChanged" @@ -43,11 +44,21 @@ export type FidesEventType = * - `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 + * they are saved to the `Fides` object, `fides_consent` cookie on the user's + * device, and the Fides API. To wait until the changes are fully + * applied, use the `FidesUpdated` event instead. * - * - `FidesUpdated`: Dispatched whenever the current user's consent preferences - * are updated on the `Fides` global object due to a user action (e.g. accepting - * all, applying GPC). The - * + * - `FidesUpdated`: Dispatched when a user action (e.g. accepting all, saving + * changes, applying GPC) has finished updating the user's consent preferences. + * This event is dispatched once the changes are fully saved to the `Fides` + * 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 diff --git a/clients/fides-js/src/docs/fides-options.ts b/clients/fides-js/src/docs/fides-options.ts index be825ea1c9..f76a36e02a 100644 --- a/clients/fides-js/src/docs/fides-options.ts +++ b/clients/fides-js/src/docs/fides-options.ts @@ -35,6 +35,15 @@ * ``` */ export interface FidesOptions { + /** + * When `true`, deletes the `fides_consent` cookie when FidesJS is + * initialized, to clear any previously saved consent preferences from the + * user's device. + * + * Defaults to `false`. + */ + fides_clear_cookie: boolean; + /** * When `true`, disable the FidesJS banner from being shown. * @@ -127,12 +136,4 @@ export interface FidesOptions { * Defaults to `true`. */ fides_tcf_gdpr_applies: boolean; - - /** - * When `true`, shows fides.js overlay UI on load. This deletes the fides_consent cookie as if no preferences have been saved on reload. - * - * Defaults to `false`. - */ - - fides_clear_cookie: boolean; }; diff --git a/clients/fides-js/src/integrations/gtm.ts b/clients/fides-js/src/integrations/gtm.ts index 04c870e9cb..a337fb61ae 100644 --- a/clients/fides-js/src/integrations/gtm.ts +++ b/clients/fides-js/src/integrations/gtm.ts @@ -42,6 +42,9 @@ export const gtm = () => { window.addEventListener("FidesInitialized", (event) => pushFidesVariableToGTM(event) ); + window.addEventListener("FidesUpdating", (event) => + pushFidesVariableToGTM(event) + ); window.addEventListener("FidesUpdated", (event) => pushFidesVariableToGTM(event) ); diff --git a/clients/fides-js/src/lib/events.ts b/clients/fides-js/src/lib/events.ts index a7375f3862..55d2568fff 100644 --- a/clients/fides-js/src/lib/events.ts +++ b/clients/fides-js/src/lib/events.ts @@ -4,13 +4,7 @@ import type { FidesEventType } from "../docs"; // Bonus points: update the WindowEventMap interface with our custom event types declare global { - interface WindowEventMap { - FidesInitialized: FidesEvent; - FidesUpdated: FidesEvent; - FidesUIShown: FidesEvent; - FidesUIChanged: FidesEvent; - FidesModalClosed: FidesEvent; - } + interface WindowEventMap extends Record {} } /** diff --git a/clients/fides-js/src/lib/preferences.ts b/clients/fides-js/src/lib/preferences.ts index f4874e85d6..b27a6048d3 100644 --- a/clients/fides-js/src/lib/preferences.ts +++ b/clients/fides-js/src/lib/preferences.ts @@ -58,11 +58,12 @@ async function savePreferencesApi( /** * Updates the user's consent preferences, going through the following steps: * 1. Update the cookie object based on new preferences - * 2. Update the window.Fides object - * 3. Save preferences to the `fides_consent` cookie in the browser - * 4. Save preferences to Fides API or a custom function (`savePreferencesFn`) - * 5. Remove any cookies from notices that were opted-out from the browser - * 6. Dispatch a "FidesUpdated" event + * 2. Dispatch a "FidesUpdating" event with the new preferences + * 3. Update the window.Fides object + * 4. Save preferences to the `fides_consent` cookie in the browser + * 5. Save preferences to Fides API or a custom function (`savePreferencesFn`) + * 6. Remove any cookies from notices that were opted-out from the browser + * 7. Dispatch a "FidesUpdated" event */ export const updateConsentPreferences = async ({ consentPreferencesToSave, @@ -96,18 +97,21 @@ export const updateConsentPreferences = async ({ Object.assign(cookie, updatedCookie); Object.assign(cookie.fides_meta, extraDetails); // save extra details to meta (i.e. consentMethod) - // 2. Update the window.Fides object + // 2. Dispatch a "FidesUpdating" event with the new preferences + dispatchFidesEvent("FidesUpdating", cookie, options.debug, extraDetails); + + // 3. Update the window.Fides object debugLog(options.debug, "Updating window.Fides"); window.Fides.consent = cookie.consent; window.Fides.fides_string = cookie.fides_string; window.Fides.tcf_consent = cookie.tcf_consent; - // 3. Save preferences to the cookie in the browser + // 4. Save preferences to the cookie in the browser debugLog(options.debug, "Saving preferences to cookie"); saveFidesCookie(cookie, options.base64Cookie); window.Fides.saved_consent = cookie.consent; - // 4. Save preferences to API (if not disabled) + // 5. Save preferences to API (if not disabled) if (!options.fidesDisableSaveApi) { try { await savePreferencesApi( @@ -130,7 +134,7 @@ export const updateConsentPreferences = async ({ } } - // 5. Remove cookies associated with notices that were opted-out from the browser + // 6. Remove cookies associated with notices that were opted-out from the browser if (consentPreferencesToSave) { consentPreferencesToSave .filter( @@ -142,6 +146,6 @@ export const updateConsentPreferences = async ({ }); } - // 6. Dispatch a "FidesUpdated" event + // 7. Dispatch a "FidesUpdated" event dispatchFidesEvent("FidesUpdated", cookie, options.debug, extraDetails); }; 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 f5382eb37d..d193c9fea6 100644 --- a/clients/privacy-center/cypress/e2e/consent-banner-tcf.cy.ts +++ b/clients/privacy-center/cypress/e2e/consent-banner-tcf.cy.ts @@ -2893,6 +2893,15 @@ describe("Fides-js TCF", () => { cy.wait("@patchPrivacyPreference").then((interception) => { expect(interception.request.body.method).to.eql(ConsentMethod.ACCEPT); }); + // Both FidesUpdating & FidesUpdated should include the fides_string + cy.get("@FidesUpdating") + .should("have.been.calledOnce") + .its("lastCall.args.0.detail.fides_string") + .then((fidesString) => { + const parts = fidesString.split(","); + expect(parts.length).to.eql(2); + expect(parts[1]).to.eql(acceptAllAcString); + }); cy.get("@FidesUpdated") .should("have.been.calledOnce") .its("lastCall.args.0.detail.fides_string") diff --git a/clients/privacy-center/cypress/e2e/consent-banner.cy.ts b/clients/privacy-center/cypress/e2e/consent-banner.cy.ts index ceba7e02fb..76fbade721 100644 --- a/clients/privacy-center/cypress/e2e/consent-banner.cy.ts +++ b/clients/privacy-center/cypress/e2e/consent-banner.cy.ts @@ -1629,7 +1629,7 @@ describe("Consent overlay", () => { // NOTE: See definition of cy.visitConsentDemo in commands.ts for where we // register listeners for these window events - it("emits a FidesInitialized but not a FidesUpdated event when initialized", () => { + it("emits a FidesInitialized but not any other events when initialized", () => { cy.window() .its("Fides") .its("consent") @@ -1646,12 +1646,14 @@ describe("Consent overlay", () => { [PRIVACY_NOTICE_KEY_2]: true, [PRIVACY_NOTICE_KEY_3]: true, }); + cy.get("@FidesUpdating").should("not.have.been.called"); cy.get("@FidesUpdated").should("not.have.been.called"); + cy.get("@FidesUIShown").should("not.have.been.called"); cy.get("@FidesUIChanged").should("not.have.been.called"); }); describe("when preferences are changed / saved", () => { - it("emits a FidesUpdated event when reject all is clicked", () => { + it("emits FidesUpdating -> FidesUpdated events when reject all is clicked", () => { cy.contains("button", "Opt out of all").should("be.visible").click(); cy.get("@FidesUIChanged").should("not.have.been.called"); cy.get("@FidesInitialized") @@ -1663,8 +1665,17 @@ describe("Consent overlay", () => { [PRIVACY_NOTICE_KEY_2]: true, [PRIVACY_NOTICE_KEY_3]: true, }); + cy.get("@FidesUpdating") + // Updating event, when the user rejects all + .should("have.been.calledOnce") + .its("firstCall.args.0.detail.consent") + .should("deep.equal", { + [PRIVACY_NOTICE_KEY_1]: false, + [PRIVACY_NOTICE_KEY_2]: true, + [PRIVACY_NOTICE_KEY_3]: false, + }); cy.get("@FidesUpdated") - // Update event, when the user rejects all + // Updated event, when the preferences have finished updating .should("have.been.calledOnce") .its("firstCall.args.0.detail.consent") .should("deep.equal", { @@ -1679,7 +1690,7 @@ describe("Consent overlay", () => { }); }); - it("emits a FidesUpdated event when accept all is clicked", () => { + it("emits FidesUpdating -> FidesUpdated events when accept all is clicked", () => { cy.contains("button", "Opt in to all").should("be.visible").click(); cy.get("@FidesUIChanged").should("not.have.been.called"); cy.get("@FidesInitialized") @@ -1691,8 +1702,17 @@ describe("Consent overlay", () => { [PRIVACY_NOTICE_KEY_2]: true, [PRIVACY_NOTICE_KEY_3]: true, }); + cy.get("@FidesUpdating") + // Updating event, when the user accepts all + .should("have.been.calledOnce") + .its("firstCall.args.0.detail.consent") + .should("deep.equal", { + [PRIVACY_NOTICE_KEY_1]: true, + [PRIVACY_NOTICE_KEY_2]: true, + [PRIVACY_NOTICE_KEY_3]: true, + }); cy.get("@FidesUpdated") - // Update event, when the user accepts all + // Updated event, when the preferences have finished updating .should("have.been.calledOnce") .its("firstCall.args.0.detail.consent") .should("deep.equal", { @@ -1707,13 +1727,10 @@ describe("Consent overlay", () => { }); }); - it("emits a FidesUIChanged event when preferences are changed and a FidesUpdated event when preferences are saved", () => { + it("emits a FidesUIChanged event when preferences are changed and FidesUpdating -> FidesUpdated events when preferences are saved", () => { cy.contains("button", "Manage preferences") .should("be.visible") .click(); - cy.getByTestId("toggle-Advertising").click(); - cy.getByTestId("consent-modal").contains("Save").click(); - cy.get("@FidesUIChanged").should("have.been.calledOnce"); cy.get("@FidesInitialized") // First event, before the user saved preferences .should("have.been.calledOnce") @@ -1724,8 +1741,26 @@ describe("Consent overlay", () => { [PRIVACY_NOTICE_KEY_3]: true, }); + // Toggle the notice, but don't save yet + cy.getByTestId("toggle-Advertising").click(); + cy.get("@FidesUIChanged").should("have.been.calledOnce"); + cy.get("@FidesUpdating").should("not.have.been.called"); + cy.get("@FidesUpdated").should("not.have.been.called"); + + // Save the changes + cy.getByTestId("consent-modal").contains("Save").click(); + cy.get("@FidesUIChanged").should("have.been.calledOnce"); // still only once + cy.get("@FidesUpdating") + // Updating event, when the user saved preferences and opted-in to the first notice + .should("have.been.calledOnce") + .its("firstCall.args.0.detail.consent") + .should("deep.equal", { + [PRIVACY_NOTICE_KEY_1]: true, + [PRIVACY_NOTICE_KEY_2]: true, + [PRIVACY_NOTICE_KEY_3]: true, + }); cy.get("@FidesUpdated") - // Update event, when the user saved preferences and opted-in to the first notice + // Updated event, when the preferences have finished updating .should("have.been.calledOnce") .its("firstCall.args.0.detail.consent") .should("deep.equal", { @@ -1744,7 +1779,7 @@ describe("Consent overlay", () => { it("pushes events to the GTM integration", () => { cy.contains("button", "Opt in to all").should("be.visible").click(); cy.get("@dataLayerPush") - .should("have.been.calledTwice") + .should("have.been.calledThrice") // First call should be from initialization, before the user accepts all .its("firstCall.args.0") .should("deep.equal", { @@ -1760,6 +1795,19 @@ describe("Consent overlay", () => { cy.get("@dataLayerPush") // Second call is when the user accepts all .its("secondCall.args.0") + .should("deep.equal", { + event: "FidesUpdating", + Fides: { + consent: { + [PRIVACY_NOTICE_KEY_1]: true, + [PRIVACY_NOTICE_KEY_2]: true, + [PRIVACY_NOTICE_KEY_3]: true, + }, + }, + }); + cy.get("@dataLayerPush") + // Third call is when the preferences finish updating + .its("thirdCall.args.0") .should("deep.equal", { event: "FidesUpdated", Fides: { diff --git a/clients/privacy-center/cypress/support/commands.ts b/clients/privacy-center/cypress/support/commands.ts index 08397a0a33..1a72e92b31 100644 --- a/clients/privacy-center/cypress/support/commands.ts +++ b/clients/privacy-center/cypress/support/commands.ts @@ -90,6 +90,7 @@ Cypress.Commands.add( "FidesInitialized", cy.stub().as("FidesInitialized") ); + win.addEventListener("FidesUpdating", cy.stub().as("FidesUpdating")); win.addEventListener("FidesUpdated", cy.stub().as("FidesUpdated")); win.addEventListener("FidesUIShown", cy.stub().as("FidesUIShown")); win.addEventListener("FidesUIChanged", cy.stub().as("FidesUIChanged"));