Skip to content

Commit

Permalink
adds 3 new config vars to env
Browse files Browse the repository at this point in the history
  • Loading branch information
eastandwestwind committed Oct 12, 2023
1 parent 08f5f9d commit 8d23a56
Show file tree
Hide file tree
Showing 25 changed files with 1,111 additions and 108 deletions.
2 changes: 1 addition & 1 deletion clients/fides-js/rollup.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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 = 40;
const GZIP_SIZE_TCF_ERROR_KB = 41;
const GZIP_SIZE_TCF_WARN_KB = 35;

const preactAliases = {
Expand Down
2 changes: 1 addition & 1 deletion clients/fides-js/src/components/ConsentModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ const ConsentModal = ({
<div
data-testid="consent-modal"
{...container}
className="fides-modal-container"
className={`fides-modal-container ${attributes.container.clazz || ""}`}
>
<div {...overlay} className="fides-modal-overlay" />
<div
Expand Down
22 changes: 20 additions & 2 deletions clients/fides-js/src/components/Overlay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ const Overlay: FunctionComponent<Props> = ({
const { instance, attributes } = useA11yDialog({
id: "fides-modal",
role: "alertdialog",
clazz: options.fidesEmbed ? "fides-embed" : "",
title: experience?.experience_config?.title || "",
onClose: dispatchCloseEvent,
});
Expand All @@ -75,6 +76,20 @@ const Overlay: FunctionComponent<Props> = ({
}
}, [instance, dispatchCloseEvent]);

const fidesEmbed = useMemo(
() =>
experience.show_banner &&
hasActionNeededNotices(experience) &&
options.fidesEmbed,
[experience, options]
);

useEffect(() => {
if (fidesEmbed && instance) {
handleOpenModal();
}
}, [fidesEmbed, instance, handleOpenModal]);

useEffect(() => {
const delayBanner = setTimeout(() => {
setBannerIsOpen(true);
Expand Down Expand Up @@ -108,8 +123,11 @@ const Overlay: FunctionComponent<Props> = ({
}, [options.modalLinkId, options.debug, handleOpenModal]);

const showBanner = useMemo(
() => experience.show_banner && hasActionNeededNotices(experience),
[experience]
() =>
experience.show_banner &&
hasActionNeededNotices(experience) &&
!options.fidesEmbed,
[experience, options]
);

useEffect(() => {
Expand Down
16 changes: 16 additions & 0 deletions clients/fides-js/src/components/fides.css
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,22 @@ div.fides-modal-content {
background-color: rgba(0, 0, 0, 0.25);
}

/*Fides Embed*/
div#fides-embed-container .fides-modal-container,
div#fides-embed-container .fides-modal-overlay {
position: initial;
display: inline;
}

div#fides-embed-container .fides-modal-content {
position: initial;
transform: none;
}

div#fides-embed-container .fides-close-button {
display: none;
}

.fides-modal-container {
z-index: 2;
display: flex;
Expand Down
1 change: 1 addition & 0 deletions clients/fides-js/src/components/notices/NoticeOverlay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ const NoticeOverlay: FunctionComponent<OverlayProps> = ({
experienceId: experience.id,
fidesApiUrl: options.fidesApiUrl,
consentMethod: ConsentMethod.button,
fidesDisableSaveApi: options.fidesDisableSaveApi,
userLocationString: fidesRegionString,
cookie,
servedNotices,
Expand Down
31 changes: 31 additions & 0 deletions clients/fides-js/src/components/tcf/TcfOverlay.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { h, FunctionComponent, Fragment } from "preact";
import { useState, useCallback, useMemo } from "preact/hooks";
import { TCModel, TCString, Vector } from "@iabtechlabtcf/core";
import ConsentBanner from "../ConsentBanner";
import PrivacyPolicyLink from "../PrivacyPolicyLink";

Expand All @@ -17,6 +18,8 @@ import { OverlayProps } from "../types";

import type {
EnabledIds,
TcfCookieConsent,
TcfCookieKeyConsent,
TCFFeatureRecord,
TCFFeatureSave,
TCFPurposeConsentRecord,
Expand Down Expand Up @@ -46,6 +49,7 @@ import InitialLayer from "./InitialLayer";
import TcfTabs from "./TcfTabs";
import Button from "../Button";
import VendorInfoBanner from "./VendorInfoBanner";
import { TCF_KEY_MAP } from "../../lib/tcf/constants";

const resolveConsentValueFromTcfModel = (
model:
Expand Down Expand Up @@ -82,6 +86,32 @@ const getEnabledIds = (modelList: TcfModels) => {
.map((model) => `${model.id}`);
};

export const transformTcStringToCookieKeys = (
tcString: string,
debug: boolean
): TcfCookieConsent => {
const tcModel: TCModel = TCString.decode(tcString || "");

const cookieKeys: TcfCookieConsent = {};

// map tc model key to cookie key
TCF_KEY_MAP.forEach(({ tcfModelKey, cookieKey }) => {
if (tcfModelKey) {
const items: TcfCookieKeyConsent = {};
(tcModel[tcfModelKey] as Vector).forEach((consented, id) => {
items[id] = consented;
});
cookieKeys[cookieKey] = items;
}
});
debugLog(
debug,
`Generated cookie.tcf_consent from explicit tc string.`,
cookieKeys
);
return cookieKeys;
};

export interface UpdateEnabledIds {
newEnabledIds: string[];
modelType: keyof EnabledIds;
Expand Down Expand Up @@ -243,6 +273,7 @@ const TcfOverlay: FunctionComponent<OverlayProps> = ({
experienceId: experience.id,
fidesApiUrl: options.fidesApiUrl,
consentMethod: ConsentMethod.button,
fidesDisableSaveApi: options.fidesDisableSaveApi,
userLocationString: fidesRegionString,
cookie,
debug: options.debug,
Expand Down
129 changes: 118 additions & 11 deletions clients/fides-js/src/fides-tcf.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,18 +50,41 @@ import { gtm } from "./integrations/gtm";
import { meta } from "./integrations/meta";
import { shopify } from "./integrations/shopify";

import { FidesConfig, PrivacyExperience } from "./lib/consent-types";
import {
FidesConfig,
PrivacyExperience,
UserConsentPreference,
} from "./lib/consent-types";

import { tcf } from "./lib/tcf";
import { generateTcString, tcf } from "./lib/tcf";
import {
getInitialCookie,
getInitialFides,
initialize,
} from "./lib/initialize";
import type { Fides } from "./lib/initialize";
import { dispatchFidesEvent } from "./lib/events";
import { FidesCookie, hasSavedTcfPreferences, isNewFidesCookie } from "./fides";
import {
debugLog,
experienceIsValid,
FidesCookie,
hasSavedTcfPreferences,
isNewFidesCookie,
isPrivacyExperience,
transformTcfPreferencesToCookieKeys,
transformUserPreferenceToBoolean,
} from "./fides";
import { renderOverlay } from "./lib/tcf/renderOverlay";
import {
EnabledIds,
TCFFeatureRecord,
TcfSavePreferences,
TCFVendorConsentRecord,
TCFVendorLegitimateInterestsRecord,
} from "./lib/tcf/types";
import { transformTcStringToCookieKeys } from "./components/tcf/TcfOverlay";
import { TCF_KEY_MAP } from "./lib/tcf/constants";
import { generateTcStringFromCookieTcfConsent } from "./lib/tcf/utils";

declare global {
interface Window {
Expand All @@ -80,29 +103,110 @@ declare global {
// eslint-disable-next-line no-underscore-dangle,@typescript-eslint/naming-convention
let _Fides: Fides;

/** Helper function to determine the initial value of a TCF object */
const getInitialPreference = (
tcfObject:
| TCFFeatureRecord
| TCFVendorConsentRecord
| TCFVendorLegitimateInterestsRecord
): UserConsentPreference => {
if (tcfObject.current_preference) {
return tcfObject.current_preference;
}
return tcfObject.default_preference ?? UserConsentPreference.OPT_OUT;
};

const updateCookie = async (
oldCookie: FidesCookie,
experience: PrivacyExperience
experience: PrivacyExperience,
debug?: boolean,
tc_str_override_set?: boolean
): Promise<FidesCookie> => {
// First check if the user has never consented before
// ignore server-side prefs if either user has no prefs to override, or TC str override is set
if (tc_str_override_set) {
return oldCookie;
}
if (!hasSavedTcfPreferences(experience)) {
return { ...oldCookie, tc_string: "" };
}

// Usually at this point, we'd look at the Experience from the backend and update
// the user's browser cookie to match the preferences in the Experience. However,
// TCF requires pre-fetching an experience. A prefetch'd experience will never have user
// specific consents. We rely on the cookie to fill those in. Therefore, we do nothing
// here, since the cookie is the source of truth, not the backend Experience.
return oldCookie;
const tcSavePrefs: TcfSavePreferences = {};
const enabledIds: any | EnabledIds = {
purposesConsent: [],
purposesLegint: [],
specialPurposes: [],
features: [],
specialFeatures: [],
vendorsConsent: [],
vendorsLegint: [],
};

TCF_KEY_MAP.forEach(({ experienceKey, cookieKey, enabledIdsKey }) => {
if (experienceKey) {
tcSavePrefs[cookieKey] = [];
experience[experienceKey]?.forEach((record) => {
const pref: UserConsentPreference = getInitialPreference(record);
// map experience to tcSavePrefs (same as cookie keys)
tcSavePrefs[cookieKey]?.push({
// @ts-ignore
id: record.id,
preference: getInitialPreference(record),
});
// add to enabledIds only if user consent is True
if (transformUserPreferenceToBoolean(pref)) {
if (enabledIdsKey) {
enabledIds[enabledIdsKey].push(record.id.toString());
}
}
});
}
});

const tcString = await generateTcString({
experience,
tcStringPreferences: enabledIds,
});
const tcfConsent = transformTcfPreferencesToCookieKeys(tcSavePrefs);
return { ...oldCookie, tc_string: tcString, tcf_consent: tcfConsent };
};

/**
* Initialize the global Fides object with the given configuration values
*/
const init = async (config: FidesConfig) => {
const cookie = getInitialCookie(config);
if (config.options.fidesTcString) {
// If a TC str is explicitly passed in, we override the associated cookie props, which are then used to
// override associated props in experience
debugLog(
config.options.debug,
"Explicit TC string detected. Proceeding to override all TCF preferences with given TC string"
);
cookie.tc_string = config.options.fidesTcString;
cookie.tcf_consent = transformTcStringToCookieKeys(
config.options.fidesTcString,
config.options.debug
);
} else if (
cookie.tcf_consent &&
!cookie.tc_string &&
isPrivacyExperience(config.experience) &&
experienceIsValid(config.experience, config.options)
) {
// This state should not be hit, but just in case: if tc string is missing on cookie but we have tcf consent,
// we should generate tc string so that our CMP API accurately reflects user preference
cookie.tc_string = await generateTcStringFromCookieTcfConsent(
config.experience,
cookie.tcf_consent
);
debugLog(
config.options.debug,
"tc_string was missing from cookie, so it has been generated based on tcf_consent",
cookie.tc_string
);
}
const initialFides = getInitialFides({ ...config, cookie });

if (initialFides) {
Object.assign(_Fides, initialFides);
dispatchFidesEvent("FidesInitialized", cookie, config.options.debug);
Expand Down Expand Up @@ -147,6 +251,9 @@ _Fides = {
fidesApiUrl: "",
serverSideFidesApiUrl: "",
tcfEnabled: true,
fidesEmbed: false,
fidesDisableSaveApi: false,
fidesTcString: null,
},
fides_meta: {},
identity: {},
Expand Down
3 changes: 3 additions & 0 deletions clients/fides-js/src/fides.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,9 @@ _Fides = {
fidesApiUrl: "",
serverSideFidesApiUrl: "",
tcfEnabled: false,
fidesEmbed: false,
fidesDisableSaveApi: false,
fidesTcString: null,
},
fides_meta: {},
identity: {},
Expand Down
4 changes: 3 additions & 1 deletion clients/fides-js/src/lib/a11y-dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,12 @@ const useA11yDialogInstance = () => {

interface Props {
role: "dialog" | "alertdialog";
clazz: string;
id: string;
title: string;
onClose?: () => void;
}
export const useA11yDialog = ({ role, id, onClose }: Props) => {
export const useA11yDialog = ({ role, clazz, id, onClose }: Props) => {
const { instance, container: ref } = useA11yDialogInstance();
const isAlertDialog = role === "alertdialog";
const titleId = `${id}-title`;
Expand Down Expand Up @@ -59,6 +60,7 @@ export const useA11yDialog = ({ role, id, onClose }: Props) => {
attributes: {
container: {
id,
clazz,
ref,
role,
tabIndex: -1,
Expand Down
13 changes: 12 additions & 1 deletion clients/fides-js/src/lib/consent-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,18 @@ export type FidesOptions = {
serverSideFidesApiUrl: string;

// Whether we should show the TCF modal
tcfEnabled?: boolean;
tcfEnabled: 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;

// Whether we should disable saving consent preferences to the Fides API.
fidesDisableSaveApi: boolean;

// An explicitly passed-in TC string that supersedes the cookie, and prevents any API calls to fetch
// experiences / preferences. Only available when TCF is enabled. Optional.
fidesTcString: string | null;
};

export class SaveConsentPreference {
Expand Down
Loading

0 comments on commit 8d23a56

Please sign in to comment.