Skip to content

Commit

Permalink
HJ-166: Support BlueConic objectives (#5479)
Browse files Browse the repository at this point in the history
Co-authored-by: Neville Samuell <neville@ethyca.com>
  • Loading branch information
2 people authored and Kelsey-Ethyca committed Nov 11, 2024
1 parent 7ff28bf commit e660788
Show file tree
Hide file tree
Showing 8 changed files with 193 additions and 8 deletions.
106 changes: 106 additions & 0 deletions clients/fides-js/__tests__/integrations/blueconic.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import { FidesGlobal } from "../../src/fides";
import { blueconic } from "../../src/integrations/blueconic";
import { MARKETING_CONSENT_KEYS } from "../../src/lib/consent-constants";

const getBlueConicEvent = () =>
({
subscribe: () => {},
}) as const;

const setupBlueConicClient = (
initialized: "initialized" | "uinitialized" = "initialized",
) => {
const client = {
profile: {
setConsentedObjectives: jest.fn(),
setRefusedObjectives: jest.fn(),
updateProfile: jest.fn(),
},
event: initialized === "initialized" ? getBlueConicEvent() : undefined,
} as const satisfies typeof window.blueConicClient;

window.blueConicClient = client;

return client;
};

const setupFidesWithConsent = (key: string, optInStatus: boolean) => {
window.Fides = {
consent: {
[key]: optInStatus,
},
} as any as FidesGlobal;
};

describe("blueconic", () => {
afterEach(() => {
window.blueConicClient = undefined;
window.Fides = undefined as any;
jest.resetAllMocks();
});

test("that other modes are not supported", () => {
expect(() => blueconic({ approach: "other mode" as "onetrust" })).toThrow();
});

test("that nothing happens when blueconic and fides are not initialized", () => {
setupBlueConicClient("uinitialized");

blueconic();

expect(
window.blueConicClient?.profile?.setConsentedObjectives,
).not.toHaveBeenCalled();
expect(
window.blueConicClient?.profile?.setConsentedObjectives,
).not.toHaveBeenCalled();
expect(
window.blueConicClient?.profile?.updateProfile,
).not.toHaveBeenCalled();
});

describe.each(MARKETING_CONSENT_KEYS)(
"when consent is set via the %s key",
(key) => {
test.each([
[
"opted in",
true,
["iab_purpose_1", "iab_purpose_2", "iab_purpose_3", "iab_purpose_4"],
[],
],
[
"opted out",
false,
["iab_purpose_1"],
["iab_purpose_2", "iab_purpose_3", "iab_purpose_4"],
],
])(
"that a user who has %s gets the correct consented and refused objectives",
(_, optInStatus, consented, refused) => {
const blueConicClient = setupBlueConicClient();
setupFidesWithConsent(key, optInStatus);

blueconic();

expect(
blueConicClient.profile.setConsentedObjectives,
).toHaveBeenCalledWith(consented);
expect(
blueConicClient.profile.setRefusedObjectives,
).toHaveBeenCalledWith(refused);
expect(blueConicClient.profile.updateProfile).toHaveBeenCalled();
},
);
},
);

test.each(["FidesInitialized", "FidesUpdated", "onBlueConicLoaded"])(
"that %s event can cause objectives to be set",
(eventName) => {
const spy = jest.spyOn(window, "addEventListener");
blueconic();
expect(spy).toHaveBeenCalledWith(eventName, expect.any(Function));
},
);
});
5 changes: 5 additions & 0 deletions clients/fides-js/src/docs/fides.ts
Original file line number Diff line number Diff line change
Expand Up @@ -328,4 +328,9 @@ export interface Fides {
* @internal
*/
shopify: (options: any) => void;

/**
* @internal
*/
blueconic: (options?: { approach: "onetrust" }) => void;
}
2 changes: 2 additions & 0 deletions clients/fides-js/src/fides-tcf.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
isPrivacyExperience,
shouldResurfaceConsent,
} from "./fides";
import { blueconic } from "./integrations/blueconic";
import { gtm } from "./integrations/gtm";
import { meta } from "./integrations/meta";
import { shopify } from "./integrations/shopify";
Expand Down Expand Up @@ -264,6 +265,7 @@ const _Fides: FidesGlobal = {
identity: {},
tcf_consent: {},
saved_consent: {},
blueconic,
gtm,
init,
config: undefined,
Expand Down
2 changes: 2 additions & 0 deletions clients/fides-js/src/fides.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
*
* See the overall package docs in ./docs/README.md for more!
*/
import { blueconic } from "./integrations/blueconic";
import { gtm } from "./integrations/gtm";
import { meta } from "./integrations/meta";
import { shopify } from "./integrations/shopify";
Expand Down Expand Up @@ -204,6 +205,7 @@ const _Fides: FidesGlobal = {
identity: {},
tcf_consent: {},
saved_consent: {},
blueconic,
gtm,
init,
config: undefined,
Expand Down
64 changes: 64 additions & 0 deletions clients/fides-js/src/integrations/blueconic.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { MARKETING_CONSENT_KEYS } from "../lib/consent-constants";

declare global {
interface Window {
blueConicClient?: {
profile?: {
setConsentedObjectives: (objectives: string[]) => void;
setRefusedObjectives: (objectives: string[]) => void;
updateProfile: () => void;
};
event?: {
subscribe: any;
};
};
}
}

// https://support.blueconic.com/hc/en-us/articles/202605221-JavaScript-front-end-API
const blueConicLoaded = () =>
typeof window.blueConicClient !== "undefined" &&
typeof window.blueConicClient.event !== "undefined" &&
typeof window.blueConicClient.event.subscribe !== "undefined";

const configureObjectives = () => {
if (!blueConicLoaded() || !window.blueConicClient?.profile) {
return;
}

const profile = window.blueConicClient?.profile;
const { consent } = window.Fides;
const optedIn = MARKETING_CONSENT_KEYS.some((key) => consent[key]);
if (optedIn) {
profile.setConsentedObjectives([
"iab_purpose_1",
"iab_purpose_2",
"iab_purpose_3",
"iab_purpose_4",
]);
profile.setRefusedObjectives([]);
} else {
profile.setConsentedObjectives(["iab_purpose_1"]);
profile.setRefusedObjectives([
"iab_purpose_2",
"iab_purpose_3",
"iab_purpose_4",
]);
}

profile.updateProfile();
};

export const blueconic = (
{ approach }: { approach: "onetrust" } = { approach: "onetrust" },
) => {
if (approach !== "onetrust") {
throw new Error("Unsupported approach");
}

window.addEventListener("FidesInitialized", configureObjectives);
window.addEventListener("FidesUpdated", configureObjectives);
window.addEventListener("onBlueConicLoaded", configureObjectives);

configureObjectives();
};
10 changes: 2 additions & 8 deletions clients/fides-js/src/integrations/shopify.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { MARKETING_CONSENT_KEYS } from "../lib/consent-constants";
import { NoticeConsent } from "../lib/consent-types";

declare global {
Expand All @@ -18,14 +19,7 @@ declare global {
}

const CONSENT_MAP = {
marketing: [
"marketing",
"data_sales_and_sharing",
"data_sales_sharing_gpp_us_state",
"data_sharing_gpp_us_state",
"data_sales_gpp_us_state",
"targeted_advertising_gpp_us_state",
],
marketing: MARKETING_CONSENT_KEYS,
sale_of_data: [
"marketing",
"data_sales_and_sharing",
Expand Down
10 changes: 10 additions & 0 deletions clients/fides-js/src/lib/consent-constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,3 +125,13 @@ export const FIDES_OVERRIDE_EXPERIENCE_LANGUAGE_VALIDATOR_MAP: {

export const FIDES_OVERLAY_WRAPPER = "fides-overlay-wrapper";
export const FIDES_I18N_ICON = "fides-i18n-icon";

export const MARKETING_CONSENT_KEYS = [
"marketing",
"data_sales_and_sharing",
"data_sales_sharing_gpp_us_state",
"data_sharing_gpp_us_state",
"data_sales_gpp_us_state",
"targeted_advertising_gpp_us_state",
"sales_sharing_targeted_advertising_gpp_us_national",
];
2 changes: 2 additions & 0 deletions clients/fides-js/src/lib/consent-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type {
FidesExperienceConfig,
FidesOptions,
} from "../docs";
import { blueconic } from "../integrations/blueconic";
import type { gtm } from "../integrations/gtm";
import type { meta } from "../integrations/meta";
import type { shopify } from "../integrations/shopify";
Expand Down Expand Up @@ -161,6 +162,7 @@ export interface FidesGlobal extends Fides {
options: FidesInitOptions;
saved_consent: NoticeConsent;
tcf_consent: TcfOtherConsent;
blueconic: typeof blueconic;
gtm: typeof gtm;
init: (config?: FidesConfig) => Promise<void>;
meta: typeof meta;
Expand Down

0 comments on commit e660788

Please sign in to comment.