Skip to content

Commit

Permalink
support new query string initialize=false (#4900)
Browse files Browse the repository at this point in the history
  • Loading branch information
gilluminate authored May 21, 2024
1 parent 587eda6 commit 8a9b9b6
Show file tree
Hide file tree
Showing 10 changed files with 131 additions and 33 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ The types of changes are:
### Added
- Added initial version for Helios: Data Discovery and Detection [#4839](https://github.com/ethyca/fides/pull/4839)
- Enhancements to `MonitorConfig` DB model to support new functionality [#4888](https://github.com/ethyca/fides/pull/4888)
- Added developer option to disable auto-initialization on FidesJS bundles. [#4900](https://github.com/ethyca/fides/pull/4900)
- Adding property ID to served notice history and privacy preference history [#4886](https://github.com/ethyca/fides/pull/4886)
- Adding privacy_center_config and stylesheet fields to the Property model [#4879](https://github.com/ethyca/fides/pull/4879)

Expand Down
31 changes: 21 additions & 10 deletions clients/fides-js/docs/interfaces/Fides.md
Original file line number Diff line number Diff line change
Expand Up @@ -250,38 +250,49 @@ Enabling the GTM integration in your site's `<head>`:

### init()

> **init**: (`config`) => `Promise`\<`void`\>
> **init**: (`config`?) => `Promise`\<`void`\>
Initializes FidesJS with an initial configuration object.

NOTE: In most cases, you should never have to call this directly, since
In most cases, you should never have to call this directly, since
Fides Cloud will automatically bundle a `Fides.init(...)` call server-side
with the appropriate configuration options for the user's session based on
their location, property ID, and the matching experience config from Fides.

However, initialization can be called manually if needed - for example to delay
initialization until after your own custom JavaScript has run to set up some
config options. In this case, you can disable the automatic initialization
by including the query param `initialize=false` in the Fides script URL
(see /docs/dev-docs/js/privacy-center-fidesjs-hosting for details).
You will then need to call `Fides.init()` manually at the appropriate time.

This function can also be used to reinitialize FidesJS. This is useful when
you're working on a single page application (SPA) and you want to modify any
FidesJS options after initialization - for example, switching between
regular/embedded mode with `fides_embed`, overriding the user's language with
`fides_locale`, etc. Doing so without passing a config will reinitialize
FidesJS with the initial configuration, but taking into account any new overrides
such as the `fides_overrides` global or the query params.

#### Parameters

| Parameter | Type |
| :------ | :------ |
| `config` | `any` |
| `config`? | `any` |

#### Returns

`Promise`\<`void`\>

***

### reinitialize()
### ~~reinitialize()~~

> **reinitialize**: () => `Promise`\<`void`\>
Reinitialize FidesJS with the initial configuration, but taking into account
any new overrides such as the `fides_overrides` global or the query params.
#### Deprecated

This is useful when you're working on a single page application (SPA) and you
want to modify any FidesJS options after initialization - for example,
switching between regular/embedded mode with `fides_embed`, overriding the
user's language with `fides_locale`, etc.
`Fides.init()` can now be used directly instead of `Fides.reinitialize()`.

#### Returns

Expand Down
28 changes: 19 additions & 9 deletions clients/fides-js/src/docs/fides.ts
Original file line number Diff line number Diff line change
Expand Up @@ -206,21 +206,31 @@ export interface Fides {
/**
* Initializes FidesJS with an initial configuration object.
*
* NOTE: In most cases, you should never have to call this directly, since
* In most cases, you should never have to call this directly, since
* Fides Cloud will automatically bundle a `Fides.init(...)` call server-side
* with the appropriate configuration options for the user's session based on
* their location, property ID, and the matching experience config from Fides.
*
* However, initialization can be called manually if needed - for example to delay
* initialization until after your own custom JavaScript has run to set up some
* config options. In this case, you can disable the automatic initialization
* by including the query param `initialize=false` in the Fides script URL
* (see {@link /docs/dev-docs/js/privacy-center-fidesjs-hosting} for details).
* You will then need to call `Fides.init()` manually at the appropriate time.
*
* This function can also be used to reinitialize FidesJS. This is useful when
* you're working on a single page application (SPA) and you want to modify any
* FidesJS options after initialization - for example, switching between
* regular/embedded mode with `fides_embed`, overriding the user's language with
* `fides_locale`, etc. Doing so without passing a config will reinitialize
* FidesJS with the initial configuration, but taking into account any new overrides
* such as the `fides_overrides` global or the query params.
*/
init: (config: any) => Promise<void>;
init: (config?: any) => Promise<void>;

/**
* Reinitialize FidesJS with the initial configuration, but taking into account
* any new overrides such as the `fides_overrides` global or the query params.
*
* This is useful when you're working on a single page application (SPA) and you
* want to modify any FidesJS options after initialization - for example,
* switching between regular/embedded mode with `fides_embed`, overriding the
* user's language with `fides_locale`, etc.
* @deprecated
* `Fides.init()` can now be used directly instead of `Fides.reinitialize()`.
*/
reinitialize: () => Promise<void>;

Expand Down
16 changes: 13 additions & 3 deletions clients/fides-js/src/fides-tcf.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import {
updateExperienceFromCookieConsentTcf,
} from "./lib/tcf/utils";
import { DEFAULT_MODAL_LINK_LABEL } from "./lib/i18n";
import { raise } from "./lib/common-utils";

declare global {
interface Window {
Expand Down Expand Up @@ -100,8 +101,17 @@ const updateExperience = ({
/**
* Initialize the global Fides object with the given configuration values
*/
async function init(this: FidesGlobal, config: FidesConfig) {
this.config = config;
async function init(this: FidesGlobal, providedConfig?: FidesConfig) {
// confused by the "this"? see https://www.typescriptlang.org/docs/handbook/2/functions.html#declaring-this-in-a-function

// Initialize Fides with the global configuration object if it exists, or the provided one. If neither exists, raise an error.
let config =
providedConfig ??
(this.config as FidesConfig) ??
raise("Fides must be initialized with a configuration object");

this.config = config; // no matter how the config is set, we want to store it on the global object

const optionsOverrides: Partial<FidesInitOptionsOverrides> =
getOverridesByType<Partial<FidesInitOptionsOverrides>>(
OverrideType.OPTIONS,
Expand Down Expand Up @@ -234,7 +244,7 @@ const _Fides: FidesGlobal = {
if (!this.config || !this.initialized) {
throw new Error("Fides must be initialized before reinitializing");
}
return this.init(this.config);
return this.init();
},
initialized: false,
meta,
Expand Down
16 changes: 12 additions & 4 deletions clients/fides-js/src/fides.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import { renderOverlay } from "./lib/renderOverlay";
import { customGetConsentPreferences } from "./services/external/preferences";
import { defaultShowModal } from "./lib/consent-utils";
import { DEFAULT_MODAL_LINK_LABEL } from "./lib/i18n";
import { raise } from "./lib/common-utils";

declare global {
interface Window {
Expand Down Expand Up @@ -78,8 +79,16 @@ const updateExperience: UpdateExperienceFn = ({
/**
* Initialize the global Fides object with the given configuration values
*/
async function init(this: FidesGlobal, config: FidesConfig) {
this.config = config;
async function init(this: FidesGlobal, providedConfig?: FidesConfig) {
// confused by the "this"? see https://www.typescriptlang.org/docs/handbook/2/functions.html#declaring-this-in-a-function

// Initialize Fides with the global configuration object if it exists, or the provided one. If neither exists, raise an error.
let config =
providedConfig ??
(this.config as FidesConfig) ??
raise("Fides must be initialized with a configuration object");

this.config = config; // no matter how the config is set, we want to store it on the global object

const optionsOverrides: Partial<FidesInitOptionsOverrides> =
getOverridesByType<Partial<FidesInitOptionsOverrides>>(
Expand All @@ -99,7 +108,6 @@ async function init(this: FidesGlobal, config: FidesConfig) {
consentPrefsOverrides,
experienceTranslationOverrides,
};
// eslint-disable-next-line no-param-reassign
config = {
...config,
options: { ...config.options, ...overrides.optionsOverrides },
Expand Down Expand Up @@ -185,7 +193,7 @@ const _Fides: FidesGlobal = {
if (!this.config || !this.initialized) {
throw new Error("Fides must be initialized before reinitializing");
}
return this.init(this.config);
return this.init();
},
initialized: false,
meta,
Expand Down
3 changes: 3 additions & 0 deletions clients/fides-js/src/lib/common-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const raise = (message: string) => {
throw new Error(message);
};
2 changes: 1 addition & 1 deletion clients/fides-js/src/lib/consent-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ export interface FidesGlobal extends Fides {
saved_consent: NoticeConsent;
tcf_consent: TcfOtherConsent;
gtm: typeof gtm;
init: (config: FidesConfig) => Promise<void>;
init: (config?: FidesConfig) => Promise<void>;
meta: typeof meta;
reinitialize: () => Promise<void>;
shopify: typeof shopify;
Expand Down
37 changes: 34 additions & 3 deletions clients/privacy-center/cypress/e2e/consent-banner.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2479,7 +2479,7 @@ describe("Consent overlay", () => {

// Call reinitialize() without making any changes
cy.window().then((win) => {
win.Fides.reinitialize();
win.Fides.init();
});

// FidesJS should re-initialize and re-show the banner
Expand Down Expand Up @@ -2508,7 +2508,7 @@ describe("Consent overlay", () => {
fides_embed: true,
fides_disable_banner: false,
};
win.Fides.reinitialize();
win.Fides.init();
});

// FidesJS should initialize again, in embedded mode this time
Expand All @@ -2527,7 +2527,7 @@ describe("Consent overlay", () => {
fides_embed: true,
fides_disable_banner: true,
};
win.Fides.reinitialize();
win.Fides.init();
});

// FidesJS should initialize once again, without any banners
Expand All @@ -2539,4 +2539,35 @@ describe("Consent overlay", () => {
});
});
});

describe("when initialization has been disabled by the developer", () => {
beforeEach(() => {
cy.getCookie(CONSENT_COOKIE_NAME).should("not.exist");
cy.visit({
url: "/fides-js-demo.html",
qs: { initialize: "false" },
});
cy.window().then((win) => {
win.addEventListener(
"FidesInitialized",
cy.stub().as("FidesInitialized")
);
});
});
it("does not trigger any side-effects (like banners displaying, events firing, etc.)", () => {
cy.window().then((win) => {
assert.isTrue(!!win.Fides.config);
assert.isFalse(win.Fides.initialized);
});
cy.get("@FidesInitialized").should("not.have.been.called");
cy.get("#fides-overlay .fides-banner").should("not.exist");
});
it("can still be initialized manually by the developer after adjusting settings", () => {
cy.window().then((win) => {
win.Fides.init().then(() => {
assert.isTrue(win.Fides.initialized);
});
});
});
});
});
8 changes: 8 additions & 0 deletions clients/privacy-center/cypress/e2e/fides-js.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,14 @@ describe("fides.js API route", () => {
});
});
});

describe("when disabling initialization", () => {
it("does not call widnow.Fides.init", () => {
cy.request("/fides.js?initialize=false").then((response) => {
expect(response.body).not.to.match(/window.Fides.init(fidesConfig)/);
});
});
});
});

// Convert this to a module instead of script (allows import/export)
Expand Down
22 changes: 19 additions & 3 deletions clients/privacy-center/pages/api/fides-js.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,12 @@ let autoRefresh: boolean = true;
* description: Forces the GPP extension to be included in the bundle, even if the experience does not have GPP enabled
* schema:
* type: boolean
* - in: query
* name: initialize
* required: false
* description: When set to "false" fides.js will not be initialized automatically; use `window.Fides.init()` to initialize manually
* schema:
* type: boolean
* - in: header
* name: CloudFront-Viewer-Country
* required: false
Expand Down Expand Up @@ -165,6 +171,7 @@ export default async function handler(
// in production. They allow for the config to be injected by the test framework
// and delay the initialization of fides.js until the test framework is ready.
const { e2e: e2eQuery, tcf: tcfQuery } = req.query;
const isTestMode = e2eQuery === "true";

// We determine server-side whether or not to send the TCF bundle, which is based
// on whether or not the experience is marked as TCF. This means for TCF, we *must*
Expand Down Expand Up @@ -257,6 +264,10 @@ export default async function handler(
/* eslint-disable @typescript-eslint/no-use-before-define */
const customFidesCss = await fetchCustomFidesCss(req);

// Check if the client wants to skip initialization of fides.js to allow for manual initialization
const { initialize: initializeQuery } = req.query;
const skipInitialization = initializeQuery === "false";

const script = `
(function () {
// This polyfill service adds a fetch polyfill only when needed, depending on browser making the request
Expand All @@ -277,12 +288,17 @@ export default async function handler(
`
: ""
}${
e2eQuery === "true"
isTestMode // let end-to-end tests set the config and initialize as needed
? ""
: `
// Initialize fides.js with custom config
var fidesConfig = ${fidesConfigJSON};
window.Fides.init(fidesConfig);`
window.Fides.config = ${fidesConfigJSON};
${skipInitialization ? "" : `window.Fides.init(fidesConfig);`}
${
environment.settings.DEBUG && skipInitialization
? `console.log("fides.js initialization skipped. Call window.Fides.init() manually.");`
: ""
}`
}
})();
`;
Expand Down

0 comments on commit 8a9b9b6

Please sign in to comment.