UNPKG

@transcend-io/consent-manager-ui

Version:
217 lines (198 loc) 6.13 kB
/** * getMergedConfig() returns the final config for the UI: { ...baseConfig, ...bundleConfig, ...scriptConfig } */ import type { ConsentManagerConfig, ConsentManagerConfigInput, ExperienceToInitialViewState, } from '@transcend-io/airgap.js-types'; import { ViewState } from '@transcend-io/airgap.js-types/build/enums/viewState'; import { CONSENT_MANAGER_SUPPORTED_LANGUAGES } from './i18n'; import { logger } from './logger'; import { settings, LOG_LEVELS, extraConfig } from './settings'; import { jsonParseSafe } from './utils/safe-json-parse'; import { MergedConsentManagerConfig } from './types'; const { privacyCenter, privacyPolicy = privacyCenter || '/privacy', secondaryPolicy, languages, dismissedViewState = 'Hidden', nonce, inlineCss, } = settings; /** * This enum is copied to avoid airgap.js-types * being a production dependency for this package. * TODO: https://transcend.height.app/T-20982 - consider * a simpler option, such as a dedicated package for constants */ export const DEFAULT_VIEW_STATE_BY_PRIVACY_REGIME_COPIED: ExperienceToInitialViewState = { // EU GDPR: 'TCF_EU', // Brazil LGPD: 'QuickOptions', // US: California CPRA: 'Hidden', // US: Virginia CDPA: 'Hidden', // US: Colorado CPA: 'Hidden', // US nFADP: 'Hidden', // US: Nevada NEVADA_SB220: 'Hidden', // Other Unknown: 'Hidden', }; // Base configuration const baseConfig: Omit< ConsentManagerConfig, 'privacyPolicy' | 'dismissedViewState' | 'secondaryPolicy' > = { css: '', messages: '', theme: { primaryColor: '#3333FF', fontColor: '#010101', }, breakpoints: { tablet: '640px', desktop: '1024px', }, initialViewStateByPrivacyRegime: DEFAULT_VIEW_STATE_BY_PRIVACY_REGIME_COPIED, }; /** * Merges config from defaults and settings. JSON is automatically decoded. * * @returns the consent manager config to use in the UI */ export function getMergedConfig(): MergedConsentManagerConfig { const settingsConfig: ConsentManagerConfigInput = typeof settings === 'string' ? jsonParseSafe(settings, () => ({})) : settings || {}; const settingsConfigInitialViewStateByPrivacyRegime = settingsConfig?.initialViewStateByPrivacyRegime; // Skip initialViewStateByPrivacyRegime config in settings if empty if ( settingsConfigInitialViewStateByPrivacyRegime && Object.keys(settingsConfigInitialViewStateByPrivacyRegime).length === 0 ) { delete settingsConfig?.initialViewStateByPrivacyRegime; } // These consent manager settings can be configured through our backend or ag-bundler/config/{site}.json const config: ConsentManagerConfig = { ...baseConfig, ...settingsConfig, ...extraConfig, } as ConsentManagerConfig; config.privacyPolicy ??= privacyPolicy; config.secondaryPolicy ??= secondaryPolicy; config.dismissedViewState ??= dismissedViewState; config.languages ??= languages; if (typeof config.initialViewStateByPrivacyRegime === 'string') { config.initialViewStateByPrivacyRegime = JSON.parse( config.initialViewStateByPrivacyRegime, ); } // Determine the language settings to use const existingLanguages = config.languages ? config.languages.trim().split(/\s*,\s*/) : []; const supportedLanguages = !config.languages ? CONSENT_MANAGER_SUPPORTED_LANGUAGES : CONSENT_MANAGER_SUPPORTED_LANGUAGES.filter((lang) => existingLanguages.includes(lang), ); const safeToContinue = validateConfig(config); if (!safeToContinue) { throw new Error('Invalid consent manager config'); } return { config, supportedLanguages }; } /** * Validate recognized view states. * * @param viewState - the view state to validate * @param param - Parameter name to use in error messages * @param errors - list of errors to append to * @returns whether viewState is a recognized view state */ const validateViewState = ( viewState: ViewState, param: string, errors: string[], ): boolean => { const valid = Object.values(ViewState).some( (knownViewState) => knownViewState === viewState, ); if (!valid) { errors.push(`Unrecognized ${param}: ${viewState}`); } return valid; }; /** * Validates the configuration * * @param config - the provided config to validate * @returns a boolean on whether this is valid config */ function validateConfig(config: ConsentManagerConfig): boolean { const errors: string[] = []; const warnings: string[] = []; /* Validators */ // Check that config is not empty if (Object.keys(config).length === 0) { errors.push('Consent manager UI config missing!'); } const dismissedViewStateParam = 'dismissedViewState'; const initialViewStateByPrivacyRegimeParam = 'initialViewStateByPrivacyRegime'; const { [dismissedViewStateParam]: dismissedViewState, [initialViewStateByPrivacyRegimeParam]: initialViewStateByPrivacyRegime, } = config; if (dismissedViewState) { validateViewState(dismissedViewState, dismissedViewStateParam, errors); } if (initialViewStateByPrivacyRegime) { Object.values(initialViewStateByPrivacyRegime).forEach((viewState) => { if (viewState) { validateViewState( viewState, `${initialViewStateByPrivacyRegimeParam} map value`, errors, ); } }); } /* Logging */ if (warnings.length > 0 && LOG_LEVELS.has('warn')) { // Log warnings warnings.forEach((warning) => { logger.warn(warning, config); }); } if (errors.length > 0) { // Log errors and return false if (LOG_LEVELS.has('error')) { errors.forEach((error) => { logger.error.styled('color: #686868', error, config); }); } return false; } return true; } export const CSP_NONCE = nonce; if (CSP_NONCE) { const currentScriptDataset = document.currentScript?.dataset; if (currentScriptDataset) { // hide nonce from other scripts delete currentScriptDataset.nonce; } } export const ALLOW_INLINE_CSS = inlineCss !== 'off'; export const EXTERNALIZE_INLINE_CSS = inlineCss === 'data:';