diff --git a/clients/fides-js/docs/interfaces/FidesOptions.md b/clients/fides-js/docs/interfaces/FidesOptions.md index f0e60f371f..37e84e9a1f 100644 --- a/clients/fides-js/docs/interfaces/FidesOptions.md +++ b/clients/fides-js/docs/interfaces/FidesOptions.md @@ -174,3 +174,16 @@ context (e.g. a native app, another website, etc.) and you want to ensure that those preferences are respected. Defaults to `false`. + +*** + +### fides\_accept\_all + +> **fides\_accept\_all**: `boolean` + +Similar to `fides_reject_all`, when `true`, FidesJS will automatically opt +in to all consent and only show the consent modal upon user request. These 2 +options are mutually exclusive, and `fides_accept_all` will take precedence +if both are set to `true`. + +Defaults to `false`. diff --git a/clients/fides-js/rollup.config.mjs b/clients/fides-js/rollup.config.mjs index eb8de09257..5e52b4d1d4 100644 --- a/clients/fides-js/rollup.config.mjs +++ b/clients/fides-js/rollup.config.mjs @@ -16,7 +16,7 @@ const GZIP_SIZE_ERROR_KB = 45; // fail build if bundle size exceeds this const GZIP_SIZE_WARN_KB = 35; // log a warning if bundle size exceeds this // TCF -const GZIP_SIZE_TCF_ERROR_KB = 85; +const GZIP_SIZE_TCF_ERROR_KB = 85.5; const GZIP_SIZE_TCF_WARN_KB = 75; const preactAliases = { diff --git a/clients/fides-js/src/components/ConsentButtons.tsx b/clients/fides-js/src/components/ConsentButtons.tsx index ea19b10cee..7bc6b00f2f 100644 --- a/clients/fides-js/src/components/ConsentButtons.tsx +++ b/clients/fides-js/src/components/ConsentButtons.tsx @@ -3,7 +3,6 @@ import { useEffect, useState } from "preact/hooks"; import { ButtonType, - ConsentMechanism, ConsentMethod, FidesInitOptions, PrivacyExperience, @@ -139,6 +138,8 @@ type NoticeKeys = Array; interface NoticeConsentButtonProps { experience: PrivacyExperience; + onAcceptAll: () => void; + onRejectAll: () => void; onSave: (consentMethod: ConsentMethod, noticeKeys: NoticeKeys) => void; onManagePreferencesClick?: () => void; enabledKeys: NoticeKeys; @@ -150,6 +151,8 @@ interface NoticeConsentButtonProps { export const NoticeConsentButtons = ({ experience, + onAcceptAll, + onRejectAll, onSave, onManagePreferencesClick, enabledKeys, @@ -164,13 +167,6 @@ export const NoticeConsentButtons = ({ } const { privacy_notices: notices } = experience; - const handleAcceptAll = () => { - onSave( - ConsentMethod.ACCEPT, - notices.map((n) => n.notice_key), - ); - }; - const handleAcknowledgeNotices = () => { onSave( ConsentMethod.ACKNOWLEDGE, @@ -178,15 +174,6 @@ export const NoticeConsentButtons = ({ ); }; - const handleRejectAll = () => { - onSave( - ConsentMethod.REJECT, - notices - .filter((n) => n.consent_mechanism === ConsentMechanism.NOTICE_ONLY) - .map((n) => n.notice_key), - ); - }; - const handleSave = () => { onSave(ConsentMethod.SAVE, enabledKeys); }; @@ -219,8 +206,8 @@ export const NoticeConsentButtons = ({ void; - onSave: () => void; onManagePreferencesClick: () => void; } @@ -72,14 +71,15 @@ const Overlay: FunctionComponent = ({ const delayBannerMilliseconds = 100; const delayModalLinkMilliseconds = 200; const hasMounted = useHasMounted(); + const isAutoConsented = options.fidesAcceptAll || options.fidesRejectAll; const showBanner = useMemo( () => - !options.fidesRejectAll && + !isAutoConsented && !options.fidesDisableBanner && experience.experience_config?.component !== ComponentType.MODAL && shouldResurfaceConsent(experience, cookie, savedConsent), - [cookie, savedConsent, experience, options], + [cookie, savedConsent, experience, options, isAutoConsented], ); const [bannerIsOpen, setBannerIsOpen] = useState( @@ -238,9 +238,6 @@ const Overlay: FunctionComponent = ({ onClose: () => { setBannerIsOpen(false); }, - onSave: () => { - setBannerIsOpen(false); - }, onManagePreferencesClick: handleManagePreferencesClick, }) : null} diff --git a/clients/fides-js/src/components/notices/NoticeOverlay.tsx b/clients/fides-js/src/components/notices/NoticeOverlay.tsx index 34231bbc36..f78b2524d6 100644 --- a/clients/fides-js/src/components/notices/NoticeOverlay.tsx +++ b/clients/fides-js/src/components/notices/NoticeOverlay.tsx @@ -229,26 +229,50 @@ const NoticeOverlay: FunctionComponent = ({ ], ); + const handleAcceptAll = useCallback(() => { + handleUpdatePreferences( + ConsentMethod.ACCEPT, + privacyNoticeItems.map((n) => n.notice.notice_key), + ); + }, [handleUpdatePreferences, privacyNoticeItems]); + + const handleRejectAll = useCallback(() => { + handleUpdatePreferences( + ConsentMethod.REJECT, + privacyNoticeItems + .filter( + (n) => n.notice.consent_mechanism === ConsentMechanism.NOTICE_ONLY, + ) + .map((n) => n.notice.notice_key), + ); + }, [handleUpdatePreferences, privacyNoticeItems]); + useEffect(() => { if ( - handleUpdatePreferences && - options.fidesRejectAll && + ((handleAcceptAll && options.fidesRejectAll) || + (handleRejectAll && options.fidesAcceptAll)) && experience.privacy_notices ) { - fidesDebugger( - "Consent automatically rejected by fides_reject_all override!", - ); - handleUpdatePreferences( - ConsentMethod.REJECT, - experience.privacy_notices - .filter((n) => n.consent_mechanism === ConsentMechanism.NOTICE_ONLY) - .map((n) => n.notice_key), - ); + if (options.fidesAcceptAll) { + // fidesAcceptAll takes precedence over fidesRejectAll + fidesDebugger( + "Consent automatically accepted by fides_accept_all override!", + ); + handleAcceptAll(); + } else if (options.fidesRejectAll) { + fidesDebugger( + "Consent automatically rejected by fides_reject_all override!", + ); + handleRejectAll(); + } } }, [ experience.privacy_notices, handleUpdatePreferences, options.fidesRejectAll, + options.fidesAcceptAll, + handleAcceptAll, + handleRejectAll, ]); const dispatchOpenBannerEvent = useCallback(() => { @@ -290,7 +314,6 @@ const NoticeOverlay: FunctionComponent = ({ isEmbedded, isOpen, onClose, - onSave, onManagePreferencesClick, }) => { const isAcknowledge = @@ -312,12 +335,20 @@ const NoticeOverlay: FunctionComponent = ({ experience={experience} onManagePreferencesClick={onManagePreferencesClick} enabledKeys={draftEnabledNoticeKeys} + onAcceptAll={() => { + handleAcceptAll(); + onClose(); + }} + onRejectAll={() => { + handleRejectAll(); + onClose(); + }} onSave={( consentMethod: ConsentMethod, keys: Array, ) => { handleUpdatePreferences(consentMethod, keys); - onSave(); + onClose(); }} isAcknowledge={isAcknowledge} hideOptInOut={isAcknowledge} @@ -345,6 +376,14 @@ const NoticeOverlay: FunctionComponent = ({ { + handleAcceptAll(); + onClose(); + }} + onRejectAll={() => { + handleRejectAll(); + onClose(); + }} onSave={( consentMethod: ConsentMethod, keys: Array, diff --git a/clients/fides-js/src/components/tcf/TcfConsentButtons.tsx b/clients/fides-js/src/components/tcf/TcfConsentButtons.tsx index d58a21b7fb..5329d2c0ac 100644 --- a/clients/fides-js/src/components/tcf/TcfConsentButtons.tsx +++ b/clients/fides-js/src/components/tcf/TcfConsentButtons.tsx @@ -1,35 +1,27 @@ import { h, VNode } from "preact"; import { - ConsentMethod, FidesInitOptions, PrivacyExperience, PrivacyExperienceMinimal, } from "../../lib/consent-types"; -import { EMPTY_ENABLED_IDS } from "../../lib/tcf/constants"; -import type { EnabledIds, TcfModels } from "../../lib/tcf/types"; import { ConsentButtons } from "../ConsentButtons"; interface TcfConsentButtonProps { experience: PrivacyExperience | PrivacyExperienceMinimal; options: FidesInitOptions; onManagePreferencesClick?: () => void; - onSave: (consentMethod: ConsentMethod, keys: EnabledIds) => void; + onRejectAll: () => void; + onAcceptAll: () => void; renderFirstButton?: () => VNode; isInModal?: boolean; } -const getAllIds = (modelList: TcfModels) => { - if (!modelList) { - return []; - } - return modelList.map((m) => `${m.id}`); -}; - export const TcfConsentButtons = ({ experience, onManagePreferencesClick, - onSave, + onRejectAll, + onAcceptAll, renderFirstButton, isInModal, options, @@ -40,64 +32,12 @@ export const TcfConsentButtons = ({ const isGVLLoading = Object.keys(experience.gvl || {}).length === 0; - const handleAcceptAll = () => { - let allIds: EnabledIds; - if (!experience.minimal_tcf) { - // eslint-disable-next-line no-param-reassign - experience = experience as PrivacyExperience; - allIds = { - purposesConsent: getAllIds(experience.tcf_purpose_consents), - purposesLegint: getAllIds(experience.tcf_purpose_legitimate_interests), - specialPurposes: getAllIds(experience.tcf_special_purposes), - features: getAllIds(experience.tcf_features), - specialFeatures: getAllIds(experience.tcf_special_features), - vendorsConsent: getAllIds([ - ...(experience.tcf_vendor_consents || []), - ...(experience.tcf_system_consents || []), - ]), - vendorsLegint: getAllIds([ - ...(experience.tcf_vendor_legitimate_interests || []), - ...(experience.tcf_system_legitimate_interests || []), - ]), - }; - } else { - // eslint-disable-next-line no-param-reassign - experience = experience as PrivacyExperienceMinimal; - allIds = { - purposesConsent: - experience.tcf_purpose_consent_ids?.map((id) => `${id}`) || [], - purposesLegint: - experience.tcf_purpose_legitimate_interest_ids?.map( - (id) => `${id}`, - ) || [], - specialPurposes: - experience.tcf_special_purpose_ids?.map((id) => `${id}`) || [], - features: experience.tcf_feature_ids?.map((id) => `${id}`) || [], - specialFeatures: - experience.tcf_special_feature_ids?.map((id) => `${id}`) || [], - vendorsConsent: [ - ...(experience.tcf_vendor_consent_ids || []), - ...(experience.tcf_system_consent_ids || []), - ], - vendorsLegint: [ - ...(experience.tcf_vendor_legitimate_interest_ids || []), - ...(experience.tcf_system_legitimate_interest_ids || []), - ], - }; - } - onSave(ConsentMethod.ACCEPT, allIds); - }; - const handleRejectAll = () => { - const emptyIds: EnabledIds = EMPTY_ENABLED_IDS; - onSave(ConsentMethod.REJECT, emptyIds); - }; - return ( { + if (!modelList) { + return []; + } + return modelList.map((m) => `${m.id}`); +}; + interface TcfOverlayProps extends Omit { experienceMinimal: PrivacyExperienceMinimal; } @@ -160,7 +171,7 @@ export const TcfOverlay = ({ const { setVendorCount } = useVendorButton(); - const [draftIds, setDraftIds] = useState(); + const [draftIds, setDraftIds] = useState(EMPTY_ENABLED_IDS); useEffect(() => { if (!experience) { @@ -286,14 +297,80 @@ export const TcfOverlay = ({ ], ); + const handleAcceptAll = useCallback(() => { + let allIds: EnabledIds; + let exp = experience || experienceMinimal; + if (!exp.minimal_tcf) { + exp = experience as PrivacyExperience; + allIds = { + purposesConsent: getAllIds(exp.tcf_purpose_consents), + purposesLegint: getAllIds(exp.tcf_purpose_legitimate_interests), + specialPurposes: getAllIds(exp.tcf_special_purposes), + features: getAllIds(exp.tcf_features), + specialFeatures: getAllIds(exp.tcf_special_features), + vendorsConsent: getAllIds([ + ...(exp.tcf_vendor_consents || []), + ...(exp.tcf_system_consents || []), + ]), + vendorsLegint: getAllIds([ + ...(exp.tcf_vendor_legitimate_interests || []), + ...(exp.tcf_system_legitimate_interests || []), + ]), + }; + } else { + // eslint-disable-next-line no-param-reassign + exp = experienceMinimal as PrivacyExperienceMinimal; + allIds = { + purposesConsent: + exp.tcf_purpose_consent_ids?.map((id) => `${id}`) || [], + purposesLegint: + exp.tcf_purpose_legitimate_interest_ids?.map((id) => `${id}`) || [], + specialPurposes: + exp.tcf_special_purpose_ids?.map((id) => `${id}`) || [], + features: exp.tcf_feature_ids?.map((id) => `${id}`) || [], + specialFeatures: + exp.tcf_special_feature_ids?.map((id) => `${id}`) || [], + vendorsConsent: [ + ...(exp.tcf_vendor_consent_ids || []), + ...(exp.tcf_system_consent_ids || []), + ], + vendorsLegint: [ + ...(exp.tcf_vendor_legitimate_interest_ids || []), + ...(exp.tcf_system_legitimate_interest_ids || []), + ], + }; + } + handleUpdateAllPreferences(ConsentMethod.ACCEPT, allIds); + }, [experience, experienceMinimal, handleUpdateAllPreferences]); + + const handleRejectAll = useCallback(() => { + handleUpdateAllPreferences(ConsentMethod.REJECT, EMPTY_ENABLED_IDS); + }, [handleUpdateAllPreferences]); + useEffect(() => { - if (options.fidesRejectAll) { - fidesDebugger( - "Consent automatically rejected by fides_reject_all override!", - ); - handleUpdateAllPreferences(ConsentMethod.REJECT, EMPTY_ENABLED_IDS); + if ( + (handleAcceptAll && options.fidesRejectAll) || + (handleRejectAll && options.fidesAcceptAll) + ) { + if (options.fidesAcceptAll) { + // fidesAcceptAll takes precedence over fidesRejectAll + fidesDebugger( + "Consent automatically accepted by fides_accept_all override!", + ); + handleAcceptAll(); + } else if (options.fidesRejectAll) { + fidesDebugger( + "Consent automatically rejected by fides_reject_all override!", + ); + handleRejectAll(); + } } - }, [handleUpdateAllPreferences, options.fidesRejectAll]); + }, [ + options.fidesRejectAll, + options.fidesAcceptAll, + handleAcceptAll, + handleRejectAll, + ]); const [activeTabIndex, setActiveTabIndex] = useState(0); @@ -338,7 +415,6 @@ export const TcfOverlay = ({ isEmbedded, isOpen, onClose, - onSave, onManagePreferencesClick, }) => { const goToVendorTab = () => { @@ -352,17 +428,21 @@ export const TcfOverlay = ({ isEmbedded={isEmbedded} onOpen={dispatchOpenBannerEvent} onClose={() => { - onClose(); handleDismiss(); + onClose(); }} onVendorPageClick={goToVendorTab} renderButtonGroup={() => ( { - handleUpdateAllPreferences(consentMethod, keys); - onSave(); + onAcceptAll={() => { + handleAcceptAll(); + onClose(); + }} + onRejectAll={() => { + handleRejectAll(); + onClose(); }} options={options} /> @@ -403,12 +483,19 @@ export const TcfOverlay = ({ return ( { + handleAcceptAll(); + onClose(); + }} + onRejectAll={() => { + handleRejectAll(); + onClose(); + }} renderFirstButton={() => (