Skip to content

Commit

Permalink
GPP serving TCF strings (#4433)
Browse files Browse the repository at this point in the history
  • Loading branch information
allisonking authored Nov 22, 2023
1 parent 15b79ee commit e40b6aa
Show file tree
Hide file tree
Showing 8 changed files with 99 additions and 36 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ The types of changes are:
- Stub for initial GPP support [#4431](https://github.com/ethyca/fides/pull/4431)
- Added confirmation modal on deleting a data use declaration [#4439](https://github.com/ethyca/fides/pull/4439)
- Added feature flag for separating system name and Compass vendor selector [#4437](https://github.com/ethyca/fides/pull/4437)
- Fire GPP events per spec [#4433](https://github.com/ethyca/fides/pull/4433)

### Changed
- Improved bulk vendor adding table UX [#4425](https://github.com/ethyca/fides/pull/4425)
Expand Down
19 changes: 11 additions & 8 deletions clients/fides-js/src/components/Overlay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,15 +49,18 @@ const Overlay: FunctionComponent<Props> = ({
const hasMounted = useHasMounted();
const [bannerIsOpen, setBannerIsOpen] = useState(false);

const dispatchCloseEvent = useCallback(() => {
dispatchFidesEvent("FidesModalClosed", cookie, options.debug);
}, [cookie, options.debug]);
const dispatchCloseEvent = useCallback(
({ saved = false }: { saved?: boolean }) => {
dispatchFidesEvent("FidesModalClosed", cookie, options.debug, { saved });
},
[cookie, options.debug]
);

const { instance, attributes } = useA11yDialog({
id: "fides-modal",
role: "alertdialog",
title: experience?.experience_config?.title || "",
onClose: dispatchCloseEvent,
onClose: () => dispatchCloseEvent({ saved: false }),
});

const handleOpenModal = useCallback(() => {
Expand All @@ -67,10 +70,10 @@ const Overlay: FunctionComponent<Props> = ({
}
}, [instance, onOpen]);

const handleCloseModal = useCallback(() => {
const handleCloseModalAfterSave = useCallback(() => {
if (instance && !options.fidesEmbed) {
instance.hide();
dispatchCloseEvent();
dispatchCloseEvent({ saved: true });
}
}, [instance, dispatchCloseEvent, options.fidesEmbed]);

Expand Down Expand Up @@ -156,7 +159,7 @@ const Overlay: FunctionComponent<Props> = ({
experience={experience.experience_config}
renderModalFooter={() =>
renderModalFooter({
onClose: handleCloseModal,
onClose: handleCloseModalAfterSave,
isMobile: false,
})
}
Expand All @@ -170,7 +173,7 @@ const Overlay: FunctionComponent<Props> = ({
onVendorPageClick={onVendorPageClick}
renderModalFooter={() =>
renderModalFooter({
onClose: handleCloseModal,
onClose: handleCloseModalAfterSave,
isMobile: false,
})
}
Expand Down
57 changes: 56 additions & 1 deletion clients/fides-js/src/extensions/gpp.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,71 @@
/* eslint-disable no-underscore-dangle */
/**
* Extension for GPP
*
* Usage:
* Include as a script tag as early as possible (even before fides.js)
*/

import {
CmpApi,
CmpDisplayStatus,
CmpStatus,
SignalStatus,
} from "@iabgpp/cmpapi";
import { makeStub } from "../lib/gpp/stub";
import { fidesEventToTcString } from "../lib/tcf/events";
import {
isPrivacyExperience,
shouldResurfaceConsent,
} from "../lib/consent-utils";
import { ETHYCA_CMP_ID } from "../lib/tcf/constants";

const CMP_VERSION = 1;

const TCF_SECTION_ID = 2;

export const initializeGppCmpApi = () => {
makeStub();

// TODO: instantiate a real (non-stubbed) GPP CMP API and set up listeners
const cmpApi = new CmpApi(ETHYCA_CMP_ID, CMP_VERSION);
cmpApi.setApplicableSections([TCF_SECTION_ID]);
cmpApi.setCmpStatus(CmpStatus.LOADED);

// If consent does not need to be resurfaced, then we can set the signal to Ready here
window.addEventListener("FidesInitialized", (event) => {
const { experience } = window.Fides;
if (
isPrivacyExperience(experience) &&
!shouldResurfaceConsent(experience, event.detail)
) {
cmpApi.setSignalStatus(SignalStatus.READY);
}
});

window.addEventListener("FidesUIShown", () => {
cmpApi.setSignalStatus(SignalStatus.NOT_READY);
cmpApi.setCmpDisplayStatus(CmpDisplayStatus.VISIBLE);
});

window.addEventListener("FidesModalClosed", (event) => {
cmpApi.setCmpDisplayStatus(CmpDisplayStatus.HIDDEN);
// If the modal was closed without the user saving, set signal status back to Ready
if (
event.detail.extraDetails &&
event.detail.extraDetails.saved === false
) {
cmpApi.setSignalStatus(SignalStatus.READY);
}
});

window.addEventListener("FidesUpdated", (event) => {
const tcString = fidesEventToTcString(event);
// Workaround for bug in base library https://github.com/IABTechLab/iabgpp-es/issues/35
cmpApi.setFieldValueBySectionId(TCF_SECTION_ID, "CmpId", CMP_ID);
cmpApi.setSectionStringById(TCF_SECTION_ID, tcString ?? "");
cmpApi.fireSectionChange("tcfeuv2");
cmpApi.setSignalStatus(SignalStatus.READY);
});
};

initializeGppCmpApi();
4 changes: 2 additions & 2 deletions clients/fides-js/src/lib/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ declare global {
*/
export type FidesEventDetail = FidesCookie & {
debug?: boolean;
extraDetails?: Record<string, string>;
extraDetails?: Record<string, string | boolean>;
};

export type FidesEvent = CustomEvent<FidesEventDetail>;
Expand All @@ -59,7 +59,7 @@ export const dispatchFidesEvent = (
type: FidesEventType,
cookie: FidesCookie,
debug: boolean,
extraDetails?: Record<string, string>
extraDetails?: Record<string, string | boolean>
) => {
if (typeof window !== "undefined" && typeof CustomEvent !== "undefined") {
const event = new CustomEvent(type, {
Expand Down
2 changes: 1 addition & 1 deletion clients/fides-js/src/lib/initialize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -368,7 +368,7 @@ export const initialize = async ({
identity: cookie.identity,
fides_string: cookie.fides_string,
tcf_consent: cookie.tcf_consent,
experience,
experience: effectiveExperience,
geolocation,
options,
initialized: true,
Expand Down
28 changes: 4 additions & 24 deletions clients/fides-js/src/lib/tcf.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,10 @@ import {
uniqueGvlVendorIds,
} from "./tcf/vendors";
import { PrivacyExperience } from "./consent-types";
import { FIDES_SEPARATOR } from "./tcf/constants";
import { FidesEvent } from "./events";
import { ETHYCA_CMP_ID, FIDES_SEPARATOR } from "./tcf/constants";
import { fidesEventToTcString } from "./tcf/events";

// TCF
const CMP_ID = 407;
const CMP_VERSION = 1;
const FORBIDDEN_LEGITIMATE_INTEREST_PURPOSE_IDS = [1, 3, 4, 5, 6];

Expand Down Expand Up @@ -70,7 +69,7 @@ export const generateFidesString = async ({
// Some fields will not be populated until a GVL is loaded
await tcModel.gvl.readyPromise;

tcModel.cmpId = CMP_ID;
tcModel.cmpId = ETHYCA_CMP_ID;
tcModel.cmpVersion = CMP_VERSION;
tcModel.consentScreen = 1; // todo- On which 'screen' consent was captured; this is a CMP proprietary number encoded into the TC string
tcModel.isServiceSpecific = true;
Expand Down Expand Up @@ -149,33 +148,14 @@ export const generateFidesString = async ({
return Promise.resolve(encodedString);
};

/**
* Extract just the TC string from a FidesEvent. This will also remove parts of the
* TC string that we do not want to surface with our CMP API events, such as
* `vendors_disclosed` and our own AC string addition.
*/
const fidesEventToTcString = (event: FidesEvent) => {
const { fides_string: cookieString } = event.detail;
if (cookieString) {
// Remove the AC portion which is separated by FIDES_SEPARATOR
const [tcString] = cookieString.split(FIDES_SEPARATOR);
// We only want to return the first part of the tcString, which is separated by '.'
// This means Publisher TC is not sent either, which is okay for now since we do not set it.
// However, if we do one day set it, we would have to decode the string and encode it again
// without vendorsDisclosed
return tcString.split(".")[0];
}
return cookieString;
};

/**
* Initializes the CMP API, including setting up listeners on FidesEvents to update
* the CMP API accordingly.
*/
export const initializeTcfCmpApi = () => {
makeStub();
const isServiceSpecific = true; // TODO: determine this from the backend?
const cmpApi = new CmpApi(CMP_ID, CMP_VERSION, isServiceSpecific, {
const cmpApi = new CmpApi(ETHYCA_CMP_ID, CMP_VERSION, isServiceSpecific, {
// Add custom command to support adding `addtlConsent` per AC spec
getTCData: (next, tcData: TCData, status) => {
/*
Expand Down
3 changes: 3 additions & 0 deletions clients/fides-js/src/lib/tcf/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ import {
TcfModelType,
} from "./types";

/** CMP ID assigned to us by the IAB */
export const ETHYCA_CMP_ID = 407;

/**
* We store all of our preference strings (TC, AC, etc.) together as one string so that
* we can have a single-source-of-truth for offline storage & syncing. The code responsible
Expand Down
21 changes: 21 additions & 0 deletions clients/fides-js/src/lib/tcf/events.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { FidesEvent } from "../events";
import { FIDES_SEPARATOR } from "./constants";

/**
* Extract just the TC string from a FidesEvent. This will also remove parts of the
* TC string that we do not want to surface with our CMP API events, such as
* `vendors_disclosed` and our own AC string addition.
*/
export const fidesEventToTcString = (event: FidesEvent) => {
const { fides_string: cookieString } = event.detail;
if (cookieString) {
// Remove the AC portion which is separated by FIDES_SEPARATOR
const [tcString] = cookieString.split(FIDES_SEPARATOR);
// We only want to return the first part of the tcString, which is separated by '.'
// This means Publisher TC is not sent either, which is okay for now since we do not set it.
// However, if we do one day set it, we would have to decode the string and encode it again
// without vendorsDisclosed
return tcString.split(".")[0];
}
return cookieString;
};

0 comments on commit e40b6aa

Please sign in to comment.