diff --git a/src/app/components/ATIAnalytics/atiUrl/index.client.test.ts b/src/app/components/ATIAnalytics/atiUrl/index.client.test.ts index 891d6d2385d..db18d1ed3db 100644 --- a/src/app/components/ATIAnalytics/atiUrl/index.client.test.ts +++ b/src/app/components/ATIAnalytics/atiUrl/index.client.test.ts @@ -162,7 +162,11 @@ describe('atiUrl', () => { x18: 'isLocServeCookieSet', }, }; - const userParams = { isSignedIn: false, hashedId: null }; + const userParams = { + isSignedIn: false, + hashedId: null, + isPersonalisationOn: false, + }; expect(reverbAnalyticsModel.params.page).toEqual(pageParams); expect(reverbAnalyticsModel.params.user).toEqual(userParams); @@ -203,7 +207,11 @@ describe('atiUrl', () => { x18: 'isLocServeCookieSet', }, }; - const userParams = { isSignedIn: false, hashedId: null }; + const userParams = { + isSignedIn: false, + hashedId: null, + isPersonalisationOn: false, + }; expect(reverbAnalyticsModel.params.page).toEqual(pageParams); expect(reverbAnalyticsModel.params.user).toEqual(userParams); diff --git a/src/app/components/ATIAnalytics/atiUrl/index.ts b/src/app/components/ATIAnalytics/atiUrl/index.ts index 98674aadcf6..c203beaef2a 100644 --- a/src/app/components/ATIAnalytics/atiUrl/index.ts +++ b/src/app/components/ATIAnalytics/atiUrl/index.ts @@ -92,6 +92,7 @@ export const buildReverbAnalyticsModel = ({ experimentVariant, isSignedIn = false, hashedId = null, + isPersonalisationOn = false, }: ATIPageTrackingProps): ReverbBeaconConfig => { const href = getHref(platform); const referrer = getReferrer(platform); @@ -140,6 +141,7 @@ export const buildReverbAnalyticsModel = ({ user: { isSignedIn, hashedId, + isPersonalisationOn, }, }, eventDetails, diff --git a/src/app/components/ATIAnalytics/index.client.test.tsx b/src/app/components/ATIAnalytics/index.client.test.tsx index ef26f09953b..6c6e8ad4336 100644 --- a/src/app/components/ATIAnalytics/index.client.test.tsx +++ b/src/app/components/ATIAnalytics/index.client.test.tsx @@ -93,6 +93,7 @@ describe('ATI Analytics Container', () => { }, }, user: { + isPersonalisationOn: false, isSignedIn: false, hashedId: null, }, @@ -213,7 +214,11 @@ describe('ATI Analytics Container', () => { x18: false, }, }, - user: { isSignedIn: false, hashedId: null }, + user: { + isSignedIn: false, + hashedId: null, + isPersonalisationOn: false, + }, }, eventDetails: { eventName: 'pageView' }, }); @@ -379,7 +384,11 @@ describe('ATI Analytics Container', () => { x18: false, }, }, - user: { isSignedIn: false, hashedId: null }, + user: { + isSignedIn: false, + hashedId: null, + isPersonalisationOn: false, + }, }, eventDetails: { eventName: 'pageView' }, }); @@ -483,7 +492,11 @@ describe('ATI Analytics Container', () => { x18: false, }, }, - user: { isSignedIn: false, hashedId: null }, + user: { + isSignedIn: false, + hashedId: null, + isPersonalisationOn: false, + }, }, eventDetails: { eventName: 'pageView' }, }); @@ -640,6 +653,7 @@ describe('ATI Analytics Container', () => { }, }, user: { + isPersonalisationOn: false, isSignedIn: false, hashedId: null, }, @@ -746,6 +760,7 @@ describe('ATI Analytics Container', () => { }, }, user: { + isPersonalisationOn: false, isSignedIn: false, hashedId: null, }, diff --git a/src/app/components/ATIAnalytics/params/buildParams/index.test.ts b/src/app/components/ATIAnalytics/params/buildParams/index.test.ts index fcc952fae17..c83f37fec6a 100644 --- a/src/app/components/ATIAnalytics/params/buildParams/index.test.ts +++ b/src/app/components/ATIAnalytics/params/buildParams/index.test.ts @@ -44,6 +44,7 @@ describe('implementation of buildPageATIParams', () => { categoryName: undefined, contentId: 'urn:bbc:tipo:topic:cm7682qz7v1t', contentType: 'index-home', + isPersonalisationOn: false, hashedId: null, isSignedIn: false, isUk: undefined, @@ -109,6 +110,7 @@ describe('implementation of buildPageATIParams', () => { categoryName: 'Refugees%20and%20asylum%20seekers~Myanmar~Military', contentId: 'urn:bbc:optimo:asset:c9wxnzvwp3mo', contentType: 'article', + isPersonalisationOn: false, hashedId: null, isSignedIn: false, isUK: false, @@ -199,6 +201,7 @@ describe('implementation of buildPageATIParams', () => { categoryName: 'Environment~Narendra+Modi~Nature~India~Severe+weather', contentId: 'urn:bbc:optimo:asset:c4nrpd0d4nro', contentType: 'article-sfv', + isPersonalisationOn: false, hashedId: null, isSignedIn: false, isUK: false, @@ -249,6 +252,7 @@ describe('implementation of buildPageATIParams', () => { categoryName: undefined, contentId: 'urn:bbc:tipo:topic:c95y35941vrt', contentType: 'index-category', + isPersonalisationOn: false, hashedId: null, isSignedIn: false, isUK: undefined, @@ -306,6 +310,7 @@ describe('implementation of buildPageATIParams', () => { categoryName: undefined, contentId: undefined, contentType: 'list-datadriven', + isPersonalisationOn: false, hashedId: null, isSignedIn: false, isUK: undefined, @@ -387,6 +392,7 @@ describe('implementation of buildPageATIParams', () => { contentId: 'urn:bbc:cps:curie:asset:3137d6de-62c2-4637-a002-29d2ab075990', contentType: 'article', + isPersonalisationOn: false, hashedId: null, isSignedIn: false, isUK: undefined, @@ -466,6 +472,7 @@ describe('implementation of buildPageATIParams', () => { contentId: 'urn:bbc:cps:curie:asset:6d745333-c79d-e245-a5b2-f4acb7de35e1', contentType: 'article-media-asset', + isPersonalisationOn: false, hashedId: null, isSignedIn: false, isUK: undefined, @@ -543,6 +550,7 @@ describe('implementation of buildPageATIParams', () => { contentId: 'urn:bbc:cps:curie:asset:08e22e90-7361-cd47-b586-7cb53fc5a012', contentType: 'article-photo-gallery', + isPersonalisationOn: false, hashedId: null, isSignedIn: false, isUK: undefined, @@ -613,6 +621,7 @@ describe('implementation of buildPageATIParams', () => { contentId: 'urn:bbc:cps:curie:asset:c1c8b1bf-4c9c-44e8-be0d-c81a2aa59e46', contentType: 'article-correspondent', + isPersonalisationOn: false, hashedId: null, isSignedIn: false, isUK: undefined, diff --git a/src/app/components/ATIAnalytics/params/buildParams/index.ts b/src/app/components/ATIAnalytics/params/buildParams/index.ts index 6ea83665741..06ed1a8b2d5 100644 --- a/src/app/components/ATIAnalytics/params/buildParams/index.ts +++ b/src/app/components/ATIAnalytics/params/buildParams/index.ts @@ -11,9 +11,11 @@ export const buildPageATIParams = ({ serviceContext, isSignedIn = false, hashedId = null, + isPersonalisationOn = false, }: ATIDataWithContexts & { isSignedIn?: boolean; hashedId?: string | null; + isPersonalisationOn?: boolean; }) => { const { isUK, platform, statsDestination, destinationSiteId } = requestContext; @@ -67,6 +69,7 @@ export const buildPageATIParams = ({ timeUpdated, isSignedIn, hashedId, + isPersonalisationOn, ...(ampExperimentName && { ampExperimentName }), ...(experimentName && { experimentName }), ...(experimentVariant && { experimentVariant }), @@ -76,6 +79,7 @@ export const buildPageATIParams = ({ type BuildPageParamsArgs = ATIDataWithContexts & { isSignedIn?: boolean; hashedId?: string | null; + isPersonalisationOn?: boolean; }; const buildPageReverbParams = ({ @@ -84,6 +88,7 @@ const buildPageReverbParams = ({ serviceContext, isSignedIn, hashedId, + isPersonalisationOn, }: BuildPageParamsArgs) => buildReverbAnalyticsModel( buildPageATIParams({ @@ -92,6 +97,7 @@ const buildPageReverbParams = ({ serviceContext, isSignedIn, hashedId, + isPersonalisationOn, }), ); @@ -118,6 +124,7 @@ export const buildAnalyticsParams = ({ serviceContext, isSignedIn, hashedId, + isPersonalisationOn, }: BuildPageParamsArgs) => { const { resonanceEnabled } = serviceContext; const { platform } = requestContext; @@ -132,6 +139,7 @@ export const buildAnalyticsParams = ({ serviceContext, isSignedIn, hashedId, + isPersonalisationOn, }), resonanceParams: sendResonanceEvents ? buildPageResonanceParams({ diff --git a/src/app/components/ATIAnalytics/params/index.test.ts b/src/app/components/ATIAnalytics/params/index.test.ts index c5b0cd3138c..60e32897cfd 100644 --- a/src/app/components/ATIAnalytics/params/index.test.ts +++ b/src/app/components/ATIAnalytics/params/index.test.ts @@ -153,6 +153,7 @@ describe('ATIAnalytics params', () => { user: { isSignedIn: false, hashedId: null, + isPersonalisationOn: false, }, }, eventDetails: { @@ -191,6 +192,7 @@ describe('ATIAnalytics params', () => { user: { isSignedIn: false, hashedId: null, + isPersonalisationOn: false, }, }, eventDetails: { @@ -235,6 +237,7 @@ describe('ATIAnalytics params', () => { user: { isSignedIn: false, hashedId: null, + isPersonalisationOn: false, }, }, eventDetails: { @@ -278,6 +281,7 @@ describe('ATIAnalytics params', () => { user: { isSignedIn: false, hashedId: null, + isPersonalisationOn: false, }, }, eventDetails: { @@ -322,6 +326,7 @@ describe('ATIAnalytics params', () => { user: { isSignedIn: false, hashedId: null, + isPersonalisationOn: false, }, }, eventDetails: { diff --git a/src/app/components/ATIAnalytics/params/index.ts b/src/app/components/ATIAnalytics/params/index.ts index aafe67ce807..92a1efd68d3 100644 --- a/src/app/components/ATIAnalytics/params/index.ts +++ b/src/app/components/ATIAnalytics/params/index.ts @@ -7,9 +7,11 @@ export default ({ atiData, isSignedIn, hashedId, + isPersonalisationOn, }: ReverbDetailsProviders & { isSignedIn?: boolean; hashedId?: string | null; + isPersonalisationOn?: boolean; }) => buildAnalyticsParams({ atiData, @@ -17,4 +19,5 @@ export default ({ serviceContext, isSignedIn, hashedId, + isPersonalisationOn, }); diff --git a/src/app/components/ATIAnalytics/types.ts b/src/app/components/ATIAnalytics/types.ts index 1365991613c..4f8f2253982 100644 --- a/src/app/components/ATIAnalytics/types.ts +++ b/src/app/components/ATIAnalytics/types.ts @@ -122,6 +122,7 @@ export type ReverbPageVars = { export type ReverbUserVars = { isSignedIn: boolean; hashedId?: string | null; + isPersonalisationOn?: boolean; }; export type ReverbEventDetails = { @@ -238,6 +239,7 @@ export interface ATIPageTrackingProps { experimentVariant?: string | null; isSignedIn?: boolean; hashedId?: string | null; + isPersonalisationOn?: boolean; } export interface ATIProps { diff --git a/src/app/contexts/AccountContext/index.tsx b/src/app/contexts/AccountContext/index.tsx index c9d01ab244b..de49261f22a 100644 --- a/src/app/contexts/AccountContext/index.tsx +++ b/src/app/contexts/AccountContext/index.tsx @@ -16,7 +16,13 @@ import { getIdctaUserOrigin } from '#app/lib/idcta/getIDCTAUserOrigin'; import useToggle from '#app/hooks/useToggle'; import isLocal from '#app/lib/utilities/isLocal'; import { USER_ID_COOKIE_KEY } from '#app/lib/uasApi/uasUtility'; -import { TOKEN_COOKIE_NAME } from '#app/lib/uasApi/tokenRefresh/tokenManager'; +import { + TOKEN_COOKIE_NAME, + getDecodedToken, +} from '#app/lib/uasApi/tokenRefresh/tokenManager'; + +// Claim set by Account in the ckns_id JWT when a signed-in user opts out of personalisation +const ENABLE_PERSONALISATION_CLAIM = 'ep'; export const AccountContext = createContext( {} as AccountContextProps, @@ -88,6 +94,13 @@ export const AccountProvider = ({ const isPersonalizationEnabled = isPersonalizationAvailable && isSignedIn; + const decodedIdToken = signedInToken ? getDecodedToken(signedInToken) : null; + const hasOptedOutOfPersonalisation = + decodedIdToken?.[ENABLE_PERSONALISATION_CLAIM] === false; + + const isPersonalisationOn = + isPersonalizationEnabled && !hasOptedOutOfPersonalisation; + const isRefreshAvailable = isIdctaAvailable && initialConfig?.availability?.refresh === 'GREEN'; @@ -104,6 +117,7 @@ export const AccountProvider = ({ forYouUrl, isPersonalizationAvailable, isPersonalizationEnabled, + isPersonalisationOn, }), [ hashedUserId, @@ -117,6 +131,7 @@ export const AccountProvider = ({ signOutUrl, isPersonalizationAvailable, isPersonalizationEnabled, + isPersonalisationOn, ], ); diff --git a/src/app/contexts/ReverbParamsContext/index.test.tsx b/src/app/contexts/ReverbParamsContext/index.test.tsx index 8cfb72b4d5d..9aba51ac167 100644 --- a/src/app/contexts/ReverbParamsContext/index.test.tsx +++ b/src/app/contexts/ReverbParamsContext/index.test.tsx @@ -97,6 +97,7 @@ describe('ReverbParamsContext', () => { user: { hashedId: null, isSignedIn: false, + isPersonalisationOn: false, }, }, }, @@ -149,6 +150,7 @@ describe('ReverbParamsContext', () => { user: { hashedId: null, isSignedIn: false, + isPersonalisationOn: false, }, }, }, @@ -201,6 +203,7 @@ describe('ReverbParamsContext', () => { user: { hashedId: null, isSignedIn: false, + isPersonalisationOn: false, }, }, }, @@ -253,6 +256,7 @@ describe('ReverbParamsContext', () => { user: { hashedId: null, isSignedIn: false, + isPersonalisationOn: false, }, }, }, @@ -309,6 +313,7 @@ describe('ReverbParamsContext', () => { user: { hashedId: null, isSignedIn: false, + isPersonalisationOn: false, }, }, }, diff --git a/src/app/contexts/ReverbParamsContext/index.tsx b/src/app/contexts/ReverbParamsContext/index.tsx index 23596ee030f..503dee2a0e0 100644 --- a/src/app/contexts/ReverbParamsContext/index.tsx +++ b/src/app/contexts/ReverbParamsContext/index.tsx @@ -80,7 +80,11 @@ const ReverbParamsContextProviderComponent = ({ }: PropsWithChildren) => { const requestContext = use(RequestContext); const serviceContext = use(ServiceContext); - const { isSignedIn, hashedUserId: hashedId } = use(AccountContext); + const { + isSignedIn, + hashedUserId: hashedId, + isPersonalisationOn, + } = use(AccountContext); const enrichedAtiData = getEnrichedATIData({ pageMetadata: metadata, @@ -94,6 +98,7 @@ const ReverbParamsContextProviderComponent = ({ atiData: enrichedAtiData, isSignedIn, hashedId, + isPersonalisationOn, }); const { diff --git a/src/app/lib/analyticsUtils/setBbcPage/index.ts b/src/app/lib/analyticsUtils/setBbcPage/index.ts index db2aa76a8e6..25885cca33c 100644 --- a/src/app/lib/analyticsUtils/setBbcPage/index.ts +++ b/src/app/lib/analyticsUtils/setBbcPage/index.ts @@ -25,6 +25,7 @@ export type BBCPage = { export type BBCUser = { getHashedId: () => Promise; isSignedIn: () => Promise; + isPersonalisationOn: () => Promise; }; const setReverbPageValues = async ({ @@ -77,6 +78,8 @@ const setReverbPageValues = async ({ window.bbcuser = { getHashedId: () => Promise.resolve(userVars.hashedId ?? null), isSignedIn: () => Promise.resolve(userVars.isSignedIn), + isPersonalisationOn: () => + Promise.resolve(userVars.isPersonalisationOn ?? false), }; }; diff --git a/src/app/models/types/account.ts b/src/app/models/types/account.ts index cd65c6fcf34..3d74e7a550c 100644 --- a/src/app/models/types/account.ts +++ b/src/app/models/types/account.ts @@ -30,4 +30,5 @@ export type AccountContextProps = { hashedUserId?: string; isPersonalizationEnabled: boolean; isPersonalizationAvailable: boolean; + isPersonalisationOn: boolean; }; diff --git a/src/app/pages/ArticlePage/index.test.tsx b/src/app/pages/ArticlePage/index.test.tsx index 6ddb1bf3874..e1625ec6ac3 100644 --- a/src/app/pages/ArticlePage/index.test.tsx +++ b/src/app/pages/ArticlePage/index.test.tsx @@ -854,7 +854,11 @@ describe('Article Page', () => { name: null, producer: 'PIDGIN', }, - user: { hashedId: null, isSignedIn: false }, + user: { + hashedId: null, + isSignedIn: false, + isPersonalisationOn: false, + }, }, }, }); @@ -930,7 +934,11 @@ describe('Article Page', () => { name: null, producer: 'PIDGIN', }, - user: { hashedId: null, isSignedIn: false }, + user: { + hashedId: null, + isSignedIn: false, + isPersonalisationOn: false, + }, }, }, }); diff --git a/src/global.d.ts b/src/global.d.ts index 82046ae4fa3..6e01f5dfeb4 100644 --- a/src/global.d.ts +++ b/src/global.d.ts @@ -33,6 +33,7 @@ declare global { bbcuser: { getHashedId: () => Promise; isSignedIn: () => Promise; + isPersonalisationOn: () => Promise; }; __reverb: { __reverbLoadedPromise: Promise;