@transcend-io/consent-manager-ui
Version:
Transcend Consent Manager reference consent UI
181 lines (175 loc) • 6.36 kB
text/typescript
import {
AirgapAPI,
ConsentManagerAPI,
ViewState,
InitialViewState,
} from '@transcend-io/airgap.js-types';
import { isViewStateClosed } from './hooks';
import { logger } from './logger';
import { PRIVACY_SIGNAL_NAME } from './privacy-signals';
import { LOG_LEVELS, settings } from './settings';
import {
HandleSetAuth,
HandleSetLanguage,
HandleChangePrivacyPolicy,
HandleSetViewState,
HandleChangeUiVariables,
} from './types';
import { VERSION } from './constants';
import type { ConsentManagerLanguageKey } from '@transcend-io/internationalization';
import { getTranscendPolicies } from './utils/getTranscendPolicies';
import { ObjByString } from '@transcend-io/type-utils';
interface MakeConsentManagerAPIInput {
/** The event target, where events as dispatched */
eventTarget: EventTarget;
/** Property for the current view state of the consent manager UI */
viewState: ViewState;
/** The currently selected language */
activeLocale: ConsentManagerLanguageKey;
/** The current dynamic variables */
currentVariables: ObjByString;
/** Method to handle setting auth */
handleSetAuth: HandleSetAuth;
/** Method to handle changes to UI global variables */
handleChangeUiVariables: HandleChangeUiVariables;
/** Method to change language */
handleChangeLanguage: HandleSetLanguage;
/** Method to change view state */
handleSetViewState: HandleSetViewState;
/** Method to change the current privacy policy URL */
handleChangePrivacyPolicy: HandleChangePrivacyPolicy;
/** Method to change the current secondary policy URL */
handleChangeSecondaryPolicy: HandleChangePrivacyPolicy;
/** Airgap.js */
airgap: AirgapAPI;
}
let promptSuppressionNoticeShown = false;
/**
* Create the Consent Manager API object
*
* @param input - the methods the API triggers, and the values it returns
* @returns the Consent Manager API
*/
export function makeConsentManagerAPI({
eventTarget,
viewState,
activeLocale,
currentVariables,
handleSetAuth,
handleChangeLanguage,
handleChangeUiVariables,
handleChangePrivacyPolicy,
handleChangeSecondaryPolicy,
handleSetViewState,
airgap,
}: MakeConsentManagerAPIInput): ConsentManagerAPI {
const consentManagerMethods: Omit<ConsentManagerAPI, keyof EventTarget> = {
setAuth: handleSetAuth,
setUiVariables: (variables) =>
Promise.resolve(handleChangeUiVariables(variables)),
getUiVariables: () => currentVariables,
getPolicies: (input = {}) =>
Promise.resolve(
getTranscendPolicies(
input,
settings.policiesCdnLocation,
activeLocale,
currentVariables,
),
),
setPrivacyPolicy: (privacyPolicyLink) =>
Promise.resolve(handleChangePrivacyPolicy(privacyPolicyLink)),
setSecondaryPolicy: (privacyPolicyLink) =>
Promise.resolve(handleChangeSecondaryPolicy(privacyPolicyLink)),
setActiveLocale: (locale) => Promise.resolve(handleChangeLanguage(locale)),
getActiveLocale: () => activeLocale,
getViewState: () => viewState,
viewStates: new Set(Object.values(ViewState)),
doNotSell: (auth, options) =>
Promise.resolve(
handleSetViewState(options?.viewState || 'DoNotSellDisclosure', auth),
),
optOutNotice: (auth, options) =>
Promise.resolve(
handleSetViewState(options?.viewState || 'OptOutDisclosure', auth),
),
// eslint-disable-next-line require-await
showConsentManager: async (options) => {
if (options?.viewState === ViewState.TCF_EU) {
logger.error(
'TCF_EU view state is not valid for this user experience. ' +
'Please configure your Regional Experience to use this view state and try again.',
);
return;
}
const excludedViewStates: InitialViewState[] = [
InitialViewState.TCF_EU, // not valid without TCF experience
InitialViewState.Hidden, // not 'open'
];
const validViewStates = Object.values(InitialViewState).filter(
(state) => !excludedViewStates.includes(state),
);
if (
options?.viewState &&
!validViewStates.includes(options.viewState as InitialViewState)
) {
logger.error(
`${
options.viewState
} is not a valid view state. Valid view states include ${validViewStates.join(
', ',
)}`,
);
return;
}
handleSetViewState(options?.viewState || 'open', undefined, true);
},
hideConsentManager: () => Promise.resolve(handleSetViewState('close')),
toggleConsentManager: () =>
Promise.resolve(
handleSetViewState(
isViewStateClosed(viewState) ? 'open' : 'close',
undefined,
true,
),
),
autoShowConsentManager: () => {
const privacySignals = airgap.getPrivacySignals();
const regimePurposes = airgap.getRegimePurposes();
const applicablePrivacySignals =
// Prompt was auto-suppressed with DNT+any regime or GPC+regimes with SaleOfInfo
(privacySignals.has('DNT') && regimePurposes.size > 0) ||
(privacySignals.has('GPC') && regimePurposes.has('SaleOfInfo'));
const shouldShowNotice = !airgap.getConsent().confirmed;
if (!shouldShowNotice) {
if (
applicablePrivacySignals &&
!promptSuppressionNoticeShown &&
LOG_LEVELS.has('warn')
) {
logger.warn(
'Tracking consent auto-prompt suppressed due to supported privacy signals:',
[...privacySignals].map((signal) =>
// expand supported privacy signals to their full names
PRIVACY_SIGNAL_NAME.has(signal)
? PRIVACY_SIGNAL_NAME.get(signal)
: signal,
),
// eslint-disable-next-line max-len
'\n\nSee https://docs.transcend.io/docs/consent/configuration/configuring-the-ui#user-privacy-signal-integration for more information.',
);
promptSuppressionNoticeShown = true;
}
return Promise.resolve();
}
handleSetViewState('open', undefined, true);
return Promise.resolve();
},
version: VERSION,
};
const consentManagerAPI: ConsentManagerAPI = Object.assign(
eventTarget,
consentManagerMethods,
);
return consentManagerAPI;
}