Skip to content

Commit

Permalink
Dispatch Fides.js lifecycle events on window (FidesInitialized, Fides…
Browse files Browse the repository at this point in the history
…Updated) and cross-publish to Fides.gtm() integration (#3454)
  • Loading branch information
NevilleS authored Jun 6, 2023
1 parent 67db31e commit 384301e
Show file tree
Hide file tree
Showing 14 changed files with 394 additions and 113 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ The types of changes are:
- Add `notice_key` field to Privacy Notice UI form [#3403](https://github.com/ethyca/fides/pull/3403)
- Add `identity` query param to the consent reporting API view [#3418](https://github.com/ethyca/fides/pull/3418)
- Use `rollup-plugin-postcss` to bundle and optimize the `fides.js` components CSS [#3431](https://github.com/ethyca/fides/pull/3431)
- Dispatch Fides.js lifecycle events on window (FidesInitialized, FidesUpdated) and cross-publish to Fides.gtm() integration [#3454](https://github.com/ethyca/fides/pull/3454)


### Fixed

Expand Down
12 changes: 10 additions & 2 deletions clients/fides-js/src/fides.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,10 @@ import {
transformConsentToFidesUserPreference,
validateOptions,
} from "./lib/consent-utils";
import { dispatchFidesEvent } from "./lib/events";
import { fetchExperience } from "./services/fides/api";
import { getGeolocation } from "./services/external/geolocation";
import { OverlayProps } from "~/components/Overlay";
import { OverlayProps } from "./components/Overlay";
import { updateConsentPreferences } from "./lib/preferences";

export type Fides = {
Expand Down Expand Up @@ -250,6 +251,12 @@ const init = async ({
_Fides.options = options;
_Fides.initialized = true;

// Dispatch the "FidesInitialized" event to update listeners with the initial
// state. For convenience, also dispatch the "FidesUpdated" event; this allows
// listeners to ignore the initialization event if they prefer
dispatchFidesEvent("FidesInitialized", cookie);
dispatchFidesEvent("FidesUpdated", cookie);

automaticallyApplyGPCPreferences(
cookie,
fidesRegionString,
Expand Down Expand Up @@ -287,14 +294,15 @@ if (typeof window !== "undefined") {

// Export everything from ./lib/* to use when importing fides.mjs as a module
// TODO: pretty sure we need ./services/* too?
export * from "./lib/consent";
export * from "./components";
export * from "./lib/consent";
export * from "./lib/consent-context";
export * from "./lib/consent-types";
export * from "./lib/consent-links";
export * from "./lib/consent-utils";
export * from "./lib/consent-value";
export * from "./lib/cookie";
export * from "./lib/events";

// DEFER: this default export isn't very useful, it's just the Fides type
export default Fides;
53 changes: 45 additions & 8 deletions clients/fides-js/src/integrations/gtm.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,56 @@
import { CookieKeyConsent } from "../lib/cookie";
import { FidesEventDetail } from "../lib/events";

declare global {
interface Window {
dataLayer?: any[];
}
}

/**
* Call Fides.gtm to configure Google Tag Manager. The user's consent choices will be
* pushed into GTM's `dataLayer` under `Fides.consent`.
* Defines the structure of the Fides variable pushed to the GTM data layer
*/
export const gtm = () => {
interface FidesVariable {
consent: CookieKeyConsent;
}

// Helper function to push the Fides variable to the GTM data layer from a FidesEvent
const pushFidesVariableToGTM = (fidesEvent: {
type: string;
detail: FidesEventDetail;
}) => {
// Initialize the dataLayer object, just in case we run before GTM is initialized
const dataLayer = window.dataLayer ?? [];
window.dataLayer = dataLayer;
dataLayer.push({
Fides: {
consent: window.Fides.consent,
},
});

// Construct the Fides variable that will be pushed to GTM
const Fides: FidesVariable = {
consent: fidesEvent.detail.consent,
};

// Push to the GTM dataLayer
dataLayer.push({ event: fidesEvent.type, Fides });
};

/**
* Call Fides.gtm() to configure the Fides <> Google Tag Manager integration.
* The user's consent choices will automatically be pushed into GTM's
* `dataLayer` under `Fides.consent` variable.
*/
export const gtm = () => {
// Listen for Fides events and cross-publish them to GTM
window.addEventListener("FidesInitialized", (event) =>
pushFidesVariableToGTM(event)
);
window.addEventListener("FidesUpdated", (event) =>
pushFidesVariableToGTM(event)
);

// If Fides was already initialized, publish a synthetic event immediately
if (window.Fides?.initialized) {
pushFidesVariableToGTM({
type: "FidesInitialized",
detail: { consent: window.Fides.consent },
});
}
};
2 changes: 2 additions & 0 deletions clients/fides-js/src/integrations/meta.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ type MetaOptions = {
/**
* Call Fides.meta to configure Meta Pixel tracking.
*
* DEFER: Update this integration to support async Fides events
*
* @example
* Fides.meta({
* consent: Fides.consent.data_sales,
Expand Down
2 changes: 2 additions & 0 deletions clients/fides-js/src/integrations/shopify.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ const applyOptions = (options: ShopifyOptions) => {
* Call Fides.shopify to configure Shopify customer privacy. Currently the only consent option
* Shopify allows to be configured is user tracking.
*
* DEFER: Update this integration to support async Fides events
*
* @example
* Fides.shopify({ tracking: Fides.consent.data_sales })
*/
Expand Down
52 changes: 52 additions & 0 deletions clients/fides-js/src/lib/events.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { FidesCookie } from "./cookie";

/**
* Defines the available event names:
* - FidesInitialized: dispatched when initialization is complete, from Fides.init()
* - FidesUpdated: dispatched when preferences are updated, from updateConsentPreferences() or Fides.init()
*/
export type FidesEventType = "FidesInitialized" | "FidesUpdated";

// Bonus points: update the WindowEventMap interface with our custom event types
declare global {
interface WindowEventMap {
FidesInitialized: FidesEvent;
FidesUpdated: FidesEvent;
}
}

/**
* Defines the properties available on event.detail. As of now, these are an
* explicit subset of properties from the Fides cookie
* TODO: add identity and meta?
*/
export type FidesEventDetail = Pick<FidesCookie, "consent">;

export type FidesEvent = CustomEvent<FidesEventDetail>;

/**
* Dispatch a custom event on the window object, providing the current Fides
* state on the "detail" property of the event.
*
* Example usage:
* ```
* window.addEventListener("FidesUpdated", (evt) => console.log("Fides.consent updated:", evt.detail.consent));
* ```
*
* The snippet above will print a console log whenever consent preferences are initialized/updated, like:
* ```
* Fides.consent updated: { data_sales_and_sharing: true }
* ```
*/
export const dispatchFidesEvent = (
type: FidesEventType,
cookie: FidesCookie
) => {
if (typeof window !== "undefined" && typeof CustomEvent !== "undefined") {
// Pick a subset of the Fides cookie properties to provide as event detail
const { consent }: FidesEventDetail = cookie;
const detail: FidesEventDetail = { consent };
const event = new CustomEvent(type, { detail });
window.dispatchEvent(event);
}
};
12 changes: 10 additions & 2 deletions clients/fides-js/src/lib/preferences.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
} from "./consent-types";
import { debugLog, transformUserPreferenceToBoolean } from "./consent-utils";
import { CookieKeyConsent, FidesCookie, saveFidesCookie } from "./cookie";
import { dispatchFidesEvent } from "./events";
import { patchUserPreferenceToFidesServer } from "../services/fides/api";

/**
Expand Down Expand Up @@ -49,6 +50,10 @@ export const updateConsentPreferences = ({
});
});

// Update the cookie object
// eslint-disable-next-line no-param-reassign
cookie.consent = consentCookieKey;

// 1. Save preferences to Fides API
debugLog(debug, "Saving preferences to Fides API");
const privacyPreferenceCreate: PrivacyPreferencesRequest = {
Expand All @@ -67,9 +72,12 @@ export const updateConsentPreferences = ({

// 2. Update the window.Fides.consent object
debugLog(debug, "Updating window.Fides");
window.Fides.consent = consentCookieKey;
window.Fides.consent = cookie.consent;

// 3. Save preferences to the cookie
debugLog(debug, "Saving preferences to cookie");
saveFidesCookie({ ...cookie, consent: consentCookieKey });
saveFidesCookie(cookie);

// 4. Dispatch a "FidesUpdated" event
dispatchFidesEvent("FidesUpdated", cookie);
};
Loading

0 comments on commit 384301e

Please sign in to comment.