Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allows CDN to cache empty experiences responses from fides.js API #4113

Merged
merged 6 commits into from
Sep 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ The types of changes are:
### Changed
- Added further config options to customize the privacy center [#4090](https://github.com/ethyca/fides/pull/4090)

### Fixed
- Allows CDN to cache empty experience responses from fides.js API [#4113](https://github.com/ethyca/fides/pull/4113)

## [2.20.0](https://github.com/ethyca/fides/compare/2.19.1...2.20.0)

### Added
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
eastandwestwind marked this conversation as resolved.
Show resolved Hide resolved
) => {
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
eastandwestwind marked this conversation as resolved.
Show resolved Hide resolved
*/
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