Skip to content

Commit

Permalink
Allows CDN to cache empty experiences responses from fides.js API (#4113
Browse files Browse the repository at this point in the history
)

Co-authored-by: Allison King <[email protected]>
  • Loading branch information
eastandwestwind and allisonking committed Sep 19, 2023
1 parent 3560e6e commit 3c99c62
Show file tree
Hide file tree
Showing 6 changed files with 60 additions and 33 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ The types of changes are:
- Updated to support Fideslang 2.0, including data migrations [#3933](https://github.com/ethyca/fides/pull/3933)
- Disable notices that are not systems applicable to support new UI [#4094](https://github.com/ethyca/fides/issues/4094)
- Added further config options to customize the privacy center [#4090](https://github.com/ethyca/fides/pull/4090)
- Allows CDN to cache empty experience responses from fides.js API [#4113](https://github.com/ethyca/fides/pull/4113)

### Fixed

Expand Down
22 changes: 11 additions & 11 deletions clients/fides-js/src/fides.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,13 +68,15 @@ import {
ConsentMethod,
SaveConsentPreference,
ConsentMechanism,
EmptyExperience,
} from "./lib/consent-types";
import {
constructFidesRegionString,
debugLog,
experienceIsValid,
transformConsentToFidesUserPreference,
validateOptions,
isPrivacyExperience,
} from "./lib/consent-utils";
import { dispatchFidesEvent } from "./lib/events";
import { fetchExperience } from "./services/fides/api";
Expand All @@ -85,7 +87,7 @@ import { resolveConsentValue } from "./lib/consent-value";

export type Fides = {
consent: CookieKeyConsent;
experience?: PrivacyExperience;
experience?: PrivacyExperience | EmptyExperience;
geolocation?: UserGeolocation;
options: FidesOptions;
fides_meta: CookieMeta;
Expand Down Expand Up @@ -133,7 +135,7 @@ const automaticallyApplyGPCPreferences = (
fidesRegionString: string | null,
fidesApiUrl: string,
debug: boolean,
effectiveExperience?: PrivacyExperience | null
effectiveExperience?: PrivacyExperience
) => {
if (!effectiveExperience || !effectiveExperience.privacy_notices) {
return;
Expand Down Expand Up @@ -216,7 +218,7 @@ const init = async ({
_Fides.geolocation = geolocation;
_Fides.options = options;
_Fides.initialized = true;
if (experience) {
if (isPrivacyExperience(experience)) {
// at this point, pre-fetched experience contains no user consent, so we populate with the Fides cookie
updateExperienceFromCookieConsent(experience, cookie, options.debug);
}
Expand All @@ -225,7 +227,7 @@ const init = async ({
}

let shouldInitOverlay: boolean = options.isOverlayEnabled;
let effectiveExperience: PrivacyExperience | undefined | null = experience;
let effectiveExperience = experience;
let fidesRegionString: string | null = null;

if (shouldInitOverlay) {
Expand All @@ -249,7 +251,8 @@ const init = async ({
`User location could not be obtained. Skipping overlay initialization.`
);
shouldInitOverlay = false;
} else if (!effectiveExperience) {
// if the experience object is null or empty from the server, we should fetch client side
} else if (!isPrivacyExperience(experience)) {
effectiveExperience = await fetchExperience(
fidesRegionString,
options.fidesApiUrl,
Expand All @@ -258,13 +261,10 @@ const init = async ({
);
}

if (
effectiveExperience &&
experienceIsValid(effectiveExperience, options)
) {
if (experienceIsValid(effectiveExperience, options)) {
// Overwrite cookie consent with experience-based consent values
cookie.consent = buildCookieConsentForExperiences(
effectiveExperience,
effectiveExperience as PrivacyExperience,
context,
options.debug
);
Expand All @@ -279,7 +279,7 @@ const init = async ({
}
}
}
if (shouldInitOverlay) {
if (shouldInitOverlay && isPrivacyExperience(effectiveExperience)) {
automaticallyApplyGPCPreferences(
cookie,
fidesRegionString,
Expand Down
6 changes: 4 additions & 2 deletions clients/fides-js/src/lib/consent-types.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
export type EmptyExperience = Record<PropertyKey, never>;

export interface FidesConfig {
// Set the consent defaults from a "legacy" Privacy Center config.json.
consent?: LegacyConsentConfig;
// Set the "experience" to be used for this Fides.js instance -- overrides the "legacy" config.
// If set, Fides.js will fetch neither experience config nor user geolocation.
// If not set, Fides.js will fetch its own experience config.
experience?: PrivacyExperience;
// If not set or is empty, Fides.js will attempt to fetch its own experience config.
experience?: PrivacyExperience | EmptyExperience;
// Set the geolocation for this Fides.js instance. If *not* set, Fides.js will fetch its own geolocation.
geolocation?: UserGeolocation;
// Global options for this Fides.js instance. Fides provides defaults for all props except privacyCenterUrl
Expand Down
39 changes: 34 additions & 5 deletions clients/fides-js/src/lib/consent-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { ConsentContext } from "./consent-context";
import {
ComponentType,
ConsentMechanism,
EmptyExperience,
FidesOptions,
GpcStatus,
PrivacyExperience,
Expand All @@ -26,6 +27,35 @@ export const debugLog = (
}
};

/**
* Returns true if privacy experience is null or empty
*/
export const isPrivacyExperience = (
obj: PrivacyExperience | undefined | EmptyExperience
): obj is PrivacyExperience => {
if (!obj) {
return false;
}
if ("id" in obj) {
return true;
}
return false;
};
// typeof obj === "object" && obj != null && Object.keys(obj).length === 0;

// /**
// * Returns true if privacy experience has notices
// */
// export const experienceHasNotices = (
// experience: PrivacyExperience | undefined | EmptyExperience
// ): boolean =>
// Boolean(
// experience &&
// !isEmptyExperience(experience) &&
// experience.privacy_notices &&
// experience.privacy_notices.length > 0
// );

/**
* Construct user location str to be ingested by Fides API
* Returns null if geolocation cannot be constructed by provided params, e.g. us_ca
Expand Down Expand Up @@ -140,13 +170,13 @@ export const validateOptions = (options: FidesOptions): boolean => {
* Determines whether experience is valid and relevant notices exist within the experience
*/
export const experienceIsValid = (
effectiveExperience: PrivacyExperience | undefined | null,
effectiveExperience: PrivacyExperience | undefined | EmptyExperience,
options: FidesOptions
): boolean => {
if (!effectiveExperience) {
if (!isPrivacyExperience(effectiveExperience)) {
debugLog(
options.debug,
`No relevant experience found. Skipping overlay initialization.`
"No relevant experience found. Skipping overlay initialization."
);
return false;
}
Expand All @@ -158,8 +188,7 @@ export const experienceIsValid = (
) {
debugLog(
options.debug,
`No relevant notices in the privacy experience. Skipping overlay initialization.`,
effectiveExperience
`Privacy experience has no notices. Skipping overlay initialization.`
);
return false;
}
Expand Down
23 changes: 9 additions & 14 deletions clients/fides-js/src/services/fides/api.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {
ComponentType,
EmptyExperience,
LastServedNoticeSchema,
NoticesServedRequest,
PrivacyExperience,
Expand All @@ -22,7 +23,7 @@ export const fetchExperience = async (
fidesApiUrl: string,
debug: boolean,
fidesUserDeviceId?: string | null
): Promise<PrivacyExperience | null> => {
): Promise<PrivacyExperience | EmptyExperience> => {
debugLog(
debug,
`Fetching experience for userId: ${fidesUserDeviceId} in location: ${userLocationString}`
Expand Down Expand Up @@ -51,25 +52,19 @@ export const fetchExperience = async (
if (!response.ok) {
debugLog(
debug,
"Error getting experience from Fides API, returning null. Response:",
"Error getting experience from Fides API, returning {}. Response:",
response
);
return null;
return {};
}
try {
const body = await response.json();
const experience = body.items && body.items[0];
if (!experience) {
debugLog(
debug,
"No relevant experience found from Fides API, returning null. Response:",
body
);
return null;
}
// returning empty obj instead of undefined ensures we can properly cache on server-side for locations
// that have no relevant experiences
const experience = (body.items && body.items[0]) ?? {};
debugLog(
debug,
"Got experience response from Fides API, returning:",
"Got experience response from Fides API, returning: ",
experience
);
return experience;
Expand All @@ -79,7 +74,7 @@ export const fetchExperience = async (
"Error parsing experience response body from Fides API, returning {}. Response:",
response
);
return null;
return {};
}
};

Expand Down
2 changes: 1 addition & 1 deletion clients/privacy-center/cypress/e2e/consent-banner.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ const stubConfig = (
: Object.assign(config.consent, consent),
experience:
experience === OVERRIDE.EMPTY
? undefined
? {}
: Object.assign(config.experience, experience),
geolocation:
geolocation === OVERRIDE.EMPTY
Expand Down

0 comments on commit 3c99c62

Please sign in to comment.