From acf4e89849704ee23622e36ed24b138436d313c2 Mon Sep 17 00:00:00 2001 From: Tom Wey Date: Tue, 6 Feb 2024 12:51:40 +0000 Subject: [PATCH] Remove @guardian/braze-components DRR are happy that frontend represents a small enough percentage of pageviews and Braze message impressions that we can stop rendering Braze messages here. There is some Braze code remaining here. If a user changes their consents or logs out we still want to clear any data set locally by either our own code or the Braze web SDK. I've inlined a small amount of code from @guardian/braze-components around clearing the local message cache. --- package.json | 1 - .../javascripts/bootstraps/enhanced/common.js | 63 +----- .../modules/commercial/braze/brazeBanner.tsx | 212 ------------------ .../commercial/braze/brazeMessageInterface.ts | 14 -- .../commercial/braze/buildBrazeMessaging.ts | 101 ++------- .../braze/getMessageFromUrlFragment.ts | 65 ------ yarn.lock | 5 - 7 files changed, 23 insertions(+), 438 deletions(-) delete mode 100644 static/src/javascripts/projects/common/modules/commercial/braze/brazeBanner.tsx delete mode 100644 static/src/javascripts/projects/common/modules/commercial/braze/brazeMessageInterface.ts delete mode 100644 static/src/javascripts/projects/common/modules/commercial/braze/getMessageFromUrlFragment.ts diff --git a/package.json b/package.json index 04a5344266ec..5faf84ca3217 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,6 @@ "@guardian/ab-core": "^5.0.0", "@guardian/atom-renderer": "^2.0.0", "@guardian/automat-modules": "^0.3.8", - "@guardian/braze-components": "^16.3.0", "@guardian/commercial": "14.4.1", "@guardian/consent-management-platform": "13.7.3", "@guardian/core-web-vitals": "^5.0.0", diff --git a/static/src/javascripts/bootstraps/enhanced/common.js b/static/src/javascripts/bootstraps/enhanced/common.js index 862140056636..27845ee1360e 100644 --- a/static/src/javascripts/bootstraps/enhanced/common.js +++ b/static/src/javascripts/bootstraps/enhanced/common.js @@ -40,7 +40,6 @@ import { init as initIdentity } from 'bootstraps/enhanced/identity-common'; import { init as initBannerPicker } from 'common/modules/ui/bannerPicker'; import { trackConsentCookies } from 'common/modules/analytics/send-privacy-prefs'; import { getAllAdConsentsWithState } from 'common/modules/commercial/ad-prefs.lib'; -import ophan from 'ophan/ng'; import { adFreeBanner } from 'common/modules/commercial/ad-free-banner'; import { init as initReaderRevenueDevUtils } from 'common/modules/commercial/reader-revenue-dev-utils'; import { @@ -48,11 +47,9 @@ import { addPrivacySettingsLink, } from 'common/modules/ui/cmp-ui'; import { signInGate } from 'common/modules/identity/sign-in-gate'; -import { buildBrazeBanner } from 'common/modules/commercial/braze/brazeBanner'; -import { buildBrazeMessaging } from 'common/modules/commercial/braze/buildBrazeMessaging'; +import { handleBraze } from 'common/modules/commercial/braze/buildBrazeMessaging'; import { readerRevenueBanner } from 'common/modules/commercial/reader-revenue-banner'; import { init as initGoogleAnalytics } from 'common/modules/tracking/google-analytics'; -import { bufferedNotificationListener } from 'common/modules/bufferedNotificationListener'; import { eitherInOktaExperimentOrElse } from 'common/modules/identity/api'; const initialiseTopNavItems = () => { @@ -262,71 +259,28 @@ const initPublicApi = () => { window.guardian.api = {}; }; -const initialiseHeaderNotifications = (brazeCardsPromise) => { - const isValid = (card) => Boolean( - card.extras.target - && card.extras.message - && card.extras.ophanLabel - ); - - brazeCardsPromise.then(brazeCards => { - return brazeCards.getCardsForProfileBadge(); - }).then(cards => { - const notifications = cards.filter((card) => isValid(card)) - .map((card) => { - return { - id: card.id, - target: card.extras.target, - message: card.extras.message, - ophanLabel: card.extras.ophanLabel, - logImpression: () => { - card.logImpression(); - } - }; - }) - - if (notifications.length > 0) { - bufferedNotificationListener.emit(notifications); - } - }) -}; - -const initialiseBanner = (brazeMessagesPromise) => { - const brazeBanner = buildBrazeBanner(brazeMessagesPromise); - +const initialiseBanner = () => { const isPreview = config.get('page.isPreview', false) // ordered by priority // in preview we don't want to show most banners as they are an unnecessary interruption - // however braze banner does use preview for testing const bannerList = isPreview ? [ cmpBannerCandidate, - brazeBanner, ] : [ cmpBannerCandidate, signInGate, membershipBanner, readerRevenueBanner, adFreeBanner, - brazeBanner, ]; initBannerPicker(bannerList); }; -const initialiseMessageSlots = () => { - const brazeMessagingPromise = buildBrazeMessaging(); - - const brazeMessagesPromise = brazeMessagingPromise.then( - ({ brazeMessages }) => brazeMessages - ); - const brazeCardsPromise = brazeMessagingPromise.then( - ({ brazeCards }) => brazeCards - ); - - initialiseBanner(brazeMessagesPromise); - initialiseHeaderNotifications(brazeCardsPromise); -}; - +const handleBrazeAndReportErrors = () => { + handleBraze().catch((err) => { + reportError(err, { module: 'c-braze' }) + }); +} const initialiseConsentCookieTracking = () => trackConsentCookies(getAllAdConsentsWithState()); @@ -361,7 +315,8 @@ const init = () => { ['c-media-listeners', mediaListener], ['c-accessibility-prefs', initAccessibilityPreferences], ['c-membership', initMembership], - ['c-message-slots', initialiseMessageSlots], + ['c-banner', initialiseBanner], + ['c-braze', handleBrazeAndReportErrors], ['c-reader-revenue-dev-utils', initReaderRevenueDevUtils], ['c-add-privacy-settings-link', addPrivacySettingsLink], ['c-load-google-analytics', loadGoogleAnalytics], diff --git a/static/src/javascripts/projects/common/modules/commercial/braze/brazeBanner.tsx b/static/src/javascripts/projects/common/modules/commercial/braze/brazeBanner.tsx deleted file mode 100644 index 896fae2094b0..000000000000 --- a/static/src/javascripts/projects/common/modules/commercial/braze/brazeBanner.tsx +++ /dev/null @@ -1,212 +0,0 @@ -import type { BrazeMessagesInterface } from '@guardian/braze-components'; -import React from 'react'; -import config from '../../../../../lib/config'; -import { reportError } from '../../../../../lib/report-error'; -import { - getAuthStatus, - getOptionsHeadersWithOkta, - getUserFromApiOrOkta, -} from '../../../modules/identity/api'; -import { submitComponentEvent, submitViewEvent } from '../acquisitions-ophan'; -import type { BrazeMessageInterface } from './brazeMessageInterface'; -import { getMessageFromUrlFragment } from './getMessageFromUrlFragment'; - -let message: BrazeMessageInterface | undefined; - -const buildCanShow = ( - brazeMessagesPromise: Promise, -) => { - return async (): Promise => { - const forcedBrazeMessage = getMessageFromUrlFragment(); - if (forcedBrazeMessage) { - message = forcedBrazeMessage; - return true; - } - - // Suppress all braze banners during this period - if (config.get('switches.brazeTaylorReport')) { - return false; - } - - try { - const section = config.get('page.section'); - const brazeArticleContext = - typeof section === 'string' ? { section } : undefined; - - const brazeMessages = await brazeMessagesPromise; - message = await brazeMessages.getMessageForBanner( - brazeArticleContext, - ); - - return true; - } catch (e) { - return false; - } - }; -}; - -const renderBanner = ( - extras: Record, - message: BrazeMessageInterface, -) => { - Promise.all([ - import('react-dom'), - import('@emotion/react'), - import('@emotion/cache'), - import( - /* webpackChunkName: "guardian-braze-components-banner" */ '@guardian/braze-components/banner' - ), - getAuthStatus(), - ]) - .then((props) => { - const [ - { render }, - { CacheProvider }, - { default: createCache }, - brazeModule, - authStatus, - ] = props; - const container = document.createElement('div'); - container.classList.add('site-message--banner'); - - document.body.appendChild(container); - - const Component = brazeModule.BrazeBannerComponent; - const idApiUrl: string = config.get('page.idApiUrl') ?? ''; - const newsletterUrl = `${idApiUrl}/users/me/newsletters`; - - const optionsHeaders = - authStatus.kind === 'SignedInWithCookies' || - authStatus.kind === 'SignedInWithOkta' - ? getOptionsHeadersWithOkta(authStatus) - : {}; - - const fetchEmail = (): Promise => { - return new Promise((resolve) => { - getUserFromApiOrOkta() - .then((res) => { - if ( - res?.primaryEmailAddress && - res.statusFields.userEmailValidated - ) { - resolve(res.primaryEmailAddress); - } - resolve(null); - }) - .catch(() => resolve(null)); - }); - }; - - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- IE does not support shadow DOM, so instead we just render - if (!container.attachShadow) { - render( - { - message.logButtonClick(buttonId); - }} - submitComponentEvent={submitComponentEvent} - brazeMessageProps={extras} - fetchEmail={fetchEmail} - subscribeToNewsletter={async (newsletterId: string) => { - await fetch(newsletterUrl, { - method: 'PATCH', - body: JSON.stringify({ - id: newsletterId, - subscribed: true, - }), - ...optionsHeaders, - }); - }} - />, - container, - ); - } else { - const shadowRoot = container.attachShadow({ mode: 'open' }); - const inner = shadowRoot.appendChild( - document.createElement('div'), - ); - const renderContainer = inner.appendChild( - document.createElement('div'), - ); - - const emotionCache = createCache({ - key: 'site-message', - container: inner, - }); - - const cached = ( - - { - message.logButtonClick(buttonId); - }} - submitComponentEvent={submitComponentEvent} - brazeMessageProps={extras} - fetchEmail={fetchEmail} - subscribeToNewsletter={async ( - newsletterId: string, - ) => { - await fetch(newsletterUrl, { - method: 'PATCH', - body: JSON.stringify({ - id: newsletterId, - subscribed: true, - }), - ...optionsHeaders, - }); - }} - /> - - ); - - render(cached, renderContainer); - } - - // Log the impression with Braze - message.logImpression(); - - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- in general the Braze extras data should include the ophanComponentId, but it's not guaranteed - const componentId = extras.ophanComponentId ?? extras.componentName; - // Log the impression with Ophan - submitViewEvent({ - component: { - componentType: 'RETENTION_ENGAGEMENT_BANNER', - id: componentId, - }, - }); - - return true; - }) - .catch((error) => { - if (error instanceof Error) { - const msg = `Error with remote Braze component: ${error.message}`; - reportError(new Error(msg), {}, false); - } - - return false; - }); -}; - -const show = (): void => { - if (message?.extras) { - renderBanner(message.extras, message); - } -}; - -interface BannerCandidate { - id: string; - show: () => void; - canShow: () => Promise; -} - -const buildBrazeBanner = ( - brazeMessagesPromise: Promise, -): BannerCandidate => ({ - id: 'brazeBanner', - show, - canShow: buildCanShow(brazeMessagesPromise), -}); - -export { buildBrazeBanner }; diff --git a/static/src/javascripts/projects/common/modules/commercial/braze/brazeMessageInterface.ts b/static/src/javascripts/projects/common/modules/commercial/braze/brazeMessageInterface.ts deleted file mode 100644 index 69e57a1bcfc9..000000000000 --- a/static/src/javascripts/projects/common/modules/commercial/braze/brazeMessageInterface.ts +++ /dev/null @@ -1,14 +0,0 @@ -// Note on BrazeMessageInterface vs BrazeMessage: -// BrazeMessage is the actual class which we wrap messages in returned from the -// Braze SDK (in @guardian/braze-components). BrazeMessageInterface represents -// the public interface supplied by instances of that class. In the message -// forcing logic it's hard to create and return a BrazeMessage from the forced -// json. A better approach would probably be to change the code in -// @guardian/braze-components to return a BrazeMessageInterface (which an -// instance of BrazeMessage would conform to) and at least the type definitions -// would all live in one centralised place. -export interface BrazeMessageInterface { - extras: Record | undefined; - logImpression: () => void; - logButtonClick: (internalButtonId: number) => void; -} diff --git a/static/src/javascripts/projects/common/modules/commercial/braze/buildBrazeMessaging.ts b/static/src/javascripts/projects/common/modules/commercial/braze/buildBrazeMessaging.ts index bf33e433c89a..2fe849ec63fa 100644 --- a/static/src/javascripts/projects/common/modules/commercial/braze/buildBrazeMessaging.ts +++ b/static/src/javascripts/projects/common/modules/commercial/braze/buildBrazeMessaging.ts @@ -1,24 +1,9 @@ -import type { - BrazeCardsInterface, - BrazeMessagesInterface, -} from '@guardian/braze-components'; -import { - BrazeCards, - BrazeMessages, - LocalMessageCache, - NullBrazeCards, - NullBrazeMessages, -} from '@guardian/braze-components/logic'; -import { log } from '@guardian/libs'; -import ophan from 'ophan/ng'; -import config from '../../../../../lib/config'; -import { reportError } from '../../../../../lib/report-error'; -import { measureTiming } from '../../../../commercial/measure-timing'; +import { log, storage } from '@guardian/libs'; +import { reportError } from 'lib/report-error'; import { checkBrazeDependencies } from './checkBrazeDependencies'; import { clearHasCurrentBrazeUser, hasCurrentBrazeUser, - setHasCurrentBrazeUser, } from './hasCurrentBrazeUser'; const SDK_OPTIONS = { @@ -30,6 +15,15 @@ const SDK_OPTIONS = { devicePropertyAllowlist: [], }; +const localStorageKeyBase = 'gu.brazeMessageCache'; +const clearLocalMessageCache = (): void => { + const slotNames = ['Banner', 'EndOfArticle']; + slotNames.forEach((slotName) => { + const key = `${localStorageKeyBase}.${slotName}`; + storage.local.remove(key); + }); +}; + const maybeWipeUserData = async ( apiKey: string | undefined, brazeUuid: string | undefined, @@ -48,7 +42,7 @@ const maybeWipeUserData = async ( importedAppboy.wipeData(); } - LocalMessageCache.clear(); + clearLocalMessageCache(); clearHasCurrentBrazeUser(); @@ -59,83 +53,16 @@ const maybeWipeUserData = async ( } }; -interface BrazeMessaging { - brazeMessages: BrazeMessagesInterface; - brazeCards: BrazeCardsInterface; -} -export const buildBrazeMessaging = async (): Promise => { +export const handleBraze = async (): Promise => { const dependenciesResult = await checkBrazeDependencies(); if (!dependenciesResult.isSuccessful) { - const { failure, data } = dependenciesResult; - - log( - 'tx', - `Not attempting to show Braze messages. Dependency ${ - failure.field - } failed with ${String(failure.data)}.`, - ); + const { data } = dependenciesResult; await maybeWipeUserData( data.apiKey as string | undefined, data.brazeUuid as string | undefined, data.consent as boolean, ); - - return { - brazeMessages: new NullBrazeMessages(), - brazeCards: new NullBrazeCards(), - }; - } - - try { - const sdkLoadTiming = measureTiming('braze-sdk-load'); - sdkLoadTiming.start(); - - const { default: importedAppboy } = await import( - /* webpackChunkName: "braze-web-sdk-core" */ '@braze/web-sdk-core' - ); - - const sdkLoadTimeTaken = sdkLoadTiming.end(); - ophan.record({ - component: 'braze-sdk-load-timing', - value: sdkLoadTimeTaken, - }); - - importedAppboy.initialize( - dependenciesResult.data.apiKey as string, - SDK_OPTIONS, - ); - - const errorHandler = (error: Error) => { - reportError(error, {}, false); - }; - const brazeMessages = new BrazeMessages( - importedAppboy, - LocalMessageCache, - errorHandler, - ); - const brazeCards = config.get('switches.brazeContentCards') - ? new BrazeCards(importedAppboy, errorHandler) - : new NullBrazeCards(); - - setHasCurrentBrazeUser(); - importedAppboy.changeUser(dependenciesResult.data.brazeUuid as string); - importedAppboy.openSession(); - - return { - brazeMessages, - brazeCards, - }; - } catch (error) { - if (error instanceof Error) { - const msg = `Error building Braze Messages: ${error.message}`; - log('tx', msg); - } - - return { - brazeMessages: new NullBrazeMessages(), - brazeCards: new NullBrazeCards(), - }; } }; diff --git a/static/src/javascripts/projects/common/modules/commercial/braze/getMessageFromUrlFragment.ts b/static/src/javascripts/projects/common/modules/commercial/braze/getMessageFromUrlFragment.ts deleted file mode 100644 index 2a502700bfc7..000000000000 --- a/static/src/javascripts/projects/common/modules/commercial/braze/getMessageFromUrlFragment.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { log } from '@guardian/libs'; -import type { BrazeMessageInterface } from './brazeMessageInterface'; - -const FORCE_BRAZE_ALLOWLIST = [ - 'preview.gutools.co.uk', - 'preview.code.dev-gutools.co.uk', - 'localhost', - 'm.thegulocal.com', -]; - -const isExtrasData = (data: unknown): data is Record => { - if (typeof data === 'object' && data != null) { - return Object.values(data).every((value) => typeof value === 'string'); - } - return false; -}; - -export const getMessageFromUrlFragment = (): - | BrazeMessageInterface - | undefined => { - if (window.location.hash) { - // This is intended for use on development domains for preview purposes. - // It won't run in PROD. - const key = 'force-braze-message'; - - const hashString = window.location.hash; - - if (hashString.includes(key)) { - if (!FORCE_BRAZE_ALLOWLIST.includes(window.location.hostname)) { - log('tx', `${key} is not supported on this domain`); - return; - } - - const forcedMessage = hashString.slice( - hashString.indexOf(`${key}=`) + key.length + 1, - hashString.length, - ); - - try { - const dataFromBraze = JSON.parse( - decodeURIComponent(forcedMessage), - ) as unknown; - - if (isExtrasData(dataFromBraze)) { - return { - extras: dataFromBraze, - logImpression: () => { - return; - }, - logButtonClick: () => { - return; - }, - }; - } - } catch (e) { - // Parsing failed. Log a message and fall through. - if (e instanceof Error) { - log('tx', `There was an error with ${key}:`, e.message); - } - } - } - } - - return; -}; diff --git a/yarn.lock b/yarn.lock index 1d5be31e0254..6d1183f549fa 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2423,11 +2423,6 @@ react "^16.13.1" react-dom "^16.13.1" -"@guardian/braze-components@^16.3.0": - version "16.3.0" - resolved "https://registry.yarnpkg.com/@guardian/braze-components/-/braze-components-16.3.0.tgz#77e2c550507d4e5b86029bce716a9a102d7cc136" - integrity sha512-JQ9MkrJx6+7OXxwqPy2lfFNKvvmFGTCbdOedC2KczdLKusKUSWIw3EzLwdoRpgMTXsQSVpqrJ5N/VM3wDsAAxQ== - "@guardian/commercial@14.4.1": version "14.4.1" resolved "https://registry.yarnpkg.com/@guardian/commercial/-/commercial-14.4.1.tgz#3a67d75f4f72786d238833c3a05dc3e94483d6d3"