From 17b1acf0f58102f055ec3f6822e5da886c856c52 Mon Sep 17 00:00:00 2001 From: Vivian A Goodrich Date: Thu, 18 Jun 2026 13:14:48 -0600 Subject: [PATCH 01/15] [MWPW-198900] Refactor event initialization to store event details in config Updated the event initialization function to store fetched event details in the configuration object. This change enhances the management of event data and ensures that registration status can be accessed from the config. Added a corresponding test to verify the new behavior. --- libs/features/mep/addons/event.js | 6 ++++-- test/features/mep/addons/event.test.js | 2 ++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/libs/features/mep/addons/event.js b/libs/features/mep/addons/event.js index 1e1bcd9e2db..87993fa5994 100644 --- a/libs/features/mep/addons/event.js +++ b/libs/features/mep/addons/event.js @@ -44,6 +44,8 @@ export default async function init(eventId) { const consentCookieValue = getCookie('OptanonConsent'); if (consentCookieValue?.includes('C0002:0')) return false; - const eventDetails = await fetchFromRainfocus(eventId); - return eventDetails?.isRegistered === true; + const config = getConfig(); + config.mep ??= {}; + config.mep.eventDetails = await fetchFromRainfocus(eventId); + return config.mep.eventDetails?.isRegistered === true; } diff --git a/test/features/mep/addons/event.test.js b/test/features/mep/addons/event.test.js index 357d4ad05f4..4618c5f3c42 100644 --- a/test/features/mep/addons/event.test.js +++ b/test/features/mep/addons/event.test.js @@ -1,6 +1,7 @@ import { expect } from '@esm-bundle/chai'; import { stub } from 'sinon'; import init from '../../../../libs/features/mep/addons/event.js'; +import { getConfig } from '../../../../libs/utils/utils.js'; const getFetchPromise = (data, type = 'json') => new Promise((resolve) => { resolve({ @@ -72,6 +73,7 @@ describe('event', () => { isRegistered: true, }); const event = await init('adobe-max-2025'); + expect(getConfig().mep.eventDetails.isRegistered).to.equal(true); expect(event).to.equal(true); }); }); From f4b98cd330736bed02a0454926a33fbe95f08d6e Mon Sep 17 00:00:00 2001 From: Vivian A Goodrich Date: Thu, 18 Jun 2026 13:41:46 -0600 Subject: [PATCH 02/15] Enhance personalization configuration by merging existing MEPS properties Updated the personalization initialization function to ensure that existing MEPS properties are preserved when setting new values. This change improves the configuration management for personalization features. --- libs/features/personalization/personalization.js | 1 + 1 file changed, 1 insertion(+) diff --git a/libs/features/personalization/personalization.js b/libs/features/personalization/personalization.js index 328ef42cb8e..c987645dfdb 100644 --- a/libs/features/personalization/personalization.js +++ b/libs/features/personalization/personalization.js @@ -1676,6 +1676,7 @@ export async function init(enablements = {}) { } else { for (const [key, promise] of Object.entries(promises)) promises[key] = await promise; config.mep = { + ...(config.mep || {}), updateFragDataProps, preview: (mepButton !== 'off' && (config.env?.name !== 'prod' || mepParam || mepParam === '' || mepButton)), From 88adc579b073d61876f2886b75471ee11b06fab9 Mon Sep 17 00:00:00 2001 From: Vivian A Goodrich Date: Mon, 22 Jun 2026 10:51:36 -0600 Subject: [PATCH 03/15] Refactor event initialization to return structured registration status Updated the event initialization function to return an object containing the registration status instead of a boolean. This change improves the clarity of the returned data and ensures that event details are consistently managed. Adjusted related personalization logic to utilize the new structure. Updated tests to reflect these changes. --- libs/features/mep/addons/event.js | 8 ++------ libs/features/personalization/personalization.js | 5 ++++- test/features/mep/addons/event.test.js | 2 -- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/libs/features/mep/addons/event.js b/libs/features/mep/addons/event.js index 87993fa5994..3b154f54698 100644 --- a/libs/features/mep/addons/event.js +++ b/libs/features/mep/addons/event.js @@ -39,13 +39,9 @@ async function fetchFromRainfocus(eventId) { } } export default async function init(eventId) { - if (eventId === true) return eventId; + if (eventId === true) return { isRegistered: eventId }; const consentCookieValue = getCookie('OptanonConsent'); if (consentCookieValue?.includes('C0002:0')) return false; - - const config = getConfig(); - config.mep ??= {}; - config.mep.eventDetails = await fetchFromRainfocus(eventId); - return config.mep.eventDetails?.isRegistered === true; + return fetchFromRainfocus(eventId); } diff --git a/libs/features/personalization/personalization.js b/libs/features/personalization/personalization.js index c987645dfdb..0937ff1a317 100644 --- a/libs/features/personalization/personalization.js +++ b/libs/features/personalization/personalization.js @@ -1084,7 +1084,10 @@ async function getPersonalizationVariant( if (userEntitlements?.includes(name)) return true; const { lob, event } = config.mep.promises; if (lob && lob === name.split('lob-')[1]?.toLowerCase()) return true; - if (name === 'registered' && event) return true; + if (name === 'registered' && event) { + config.mep.eventDetails = event; + if (event.isRegistered) return true; + } return PERSONALIZATION_KEYS.includes(name) && PERSONALIZATION_TAGS[name](); }; diff --git a/test/features/mep/addons/event.test.js b/test/features/mep/addons/event.test.js index 4618c5f3c42..357d4ad05f4 100644 --- a/test/features/mep/addons/event.test.js +++ b/test/features/mep/addons/event.test.js @@ -1,7 +1,6 @@ import { expect } from '@esm-bundle/chai'; import { stub } from 'sinon'; import init from '../../../../libs/features/mep/addons/event.js'; -import { getConfig } from '../../../../libs/utils/utils.js'; const getFetchPromise = (data, type = 'json') => new Promise((resolve) => { resolve({ @@ -73,7 +72,6 @@ describe('event', () => { isRegistered: true, }); const event = await init('adobe-max-2025'); - expect(getConfig().mep.eventDetails.isRegistered).to.equal(true); expect(event).to.equal(true); }); }); From 6bc63d561d8b7529661534bb9a41bf3b5503f2d1 Mon Sep 17 00:00:00 2001 From: Vivian A Goodrich Date: Mon, 22 Jun 2026 10:52:35 -0600 Subject: [PATCH 04/15] Remove unnecessary spread operator in MEPS configuration update Eliminated the spread operator from the MEPS configuration update in the personalization initialization function. This change simplifies the code while maintaining the integrity of the existing configuration properties. --- libs/features/personalization/personalization.js | 1 - 1 file changed, 1 deletion(-) diff --git a/libs/features/personalization/personalization.js b/libs/features/personalization/personalization.js index 0937ff1a317..3ef7214c09b 100644 --- a/libs/features/personalization/personalization.js +++ b/libs/features/personalization/personalization.js @@ -1679,7 +1679,6 @@ export async function init(enablements = {}) { } else { for (const [key, promise] of Object.entries(promises)) promises[key] = await promise; config.mep = { - ...(config.mep || {}), updateFragDataProps, preview: (mepButton !== 'off' && (config.env?.name !== 'prod' || mepParam || mepParam === '' || mepButton)), From 1170706b16b1e9f9b64553f395610d2b3038391c Mon Sep 17 00:00:00 2001 From: Vivian A Goodrich Date: Mon, 22 Jun 2026 12:16:43 -0600 Subject: [PATCH 05/15] Update event tests to assert registration status instead of boolean values Refactored the event test cases to check the 'isRegistered' property of the event object instead of directly comparing the event to boolean values. This change aligns the tests with the updated event initialization function that returns a structured object, enhancing clarity and consistency in test assertions. --- test/features/mep/addons/event.test.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/test/features/mep/addons/event.test.js b/test/features/mep/addons/event.test.js index 357d4ad05f4..5e6e2e8615a 100644 --- a/test/features/mep/addons/event.test.js +++ b/test/features/mep/addons/event.test.js @@ -33,28 +33,28 @@ describe('event', () => { }); it('should return true when sending true into init', async () => { const event = await init(true); - expect(event).to.equal(true); + expect(event.isRegistered).to.equal(true); }); it('should return false when consent cookie is set to C0002:0', async () => { setCookie('OptanonConsent', 'C0002:0'); const event = await init('adobe-max-2025'); - expect(event).to.equal(false); + expect(event.isRegistered).to.equal(false); }); it('should return false when the user is signed out', async () => { document.head.innerHTML = ''; const event = await init('adobe-max-2025'); - expect(event).to.equal(false); + expect(event.isRegistered).to.equal(false); }); it('should return false when userId is set to off', async () => { setMetadata('userId', 'off'); const event = await init('adobe-max-2025'); - expect(event).to.equal(false); + expect(event.isRegistered).to.equal(false); }); it('should return false when getAccessToken returns an empty object', async () => { setMetadata('userId', '1234567890'); window.adobeIMS.getAccessToken = () => Promise.resolve({}); const event = await init('adobe-max-2025'); - expect(event).to.equal(false); + expect(event.isRegistered).to.equal(false); }); it('should return false when api returns false', async () => { setMetadata('userId', '1234567890'); @@ -63,7 +63,7 @@ describe('event', () => { isRegistered: false, }); const event = await init('adobe-max-2025'); - expect(event).to.equal(false); + expect(event.isRegistered).to.equal(false); }); it('should return true when api returns true', async () => { setMetadata('userId', '1234567890'); @@ -72,6 +72,6 @@ describe('event', () => { isRegistered: true, }); const event = await init('adobe-max-2025'); - expect(event).to.equal(true); + expect(event.isRegistered).to.equal(true); }); }); From dff659d9bad8f2d02256f48d3fff110e696d7928 Mon Sep 17 00:00:00 2001 From: Vivian A Goodrich Date: Mon, 22 Jun 2026 12:58:11 -0600 Subject: [PATCH 06/15] Refactor event handling to return default object for error cases Updated the event handling functions to return a default object instead of an empty object when user ID or access token is unavailable. This change improves consistency in the returned data structure and enhances error handling. Adjusted related logic to utilize the new default return value. --- libs/features/mep/addons/event.js | 11 ++++++----- test/features/mep/addons/event.test.js | 1 + 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/libs/features/mep/addons/event.js b/libs/features/mep/addons/event.js index 3b154f54698..97b1f22790e 100644 --- a/libs/features/mep/addons/event.js +++ b/libs/features/mep/addons/event.js @@ -1,6 +1,7 @@ import { isSignedOut, getConfig, getMepEnablement, loadIms } from '../../../utils/utils.js'; import { getCookie } from '../../../martech/helpers.js'; +const defaultReturn = { isRegistered: false }; async function getUserId() { if (isSignedOut() && !getMepEnablement('signedIn')) return false; if (getMepEnablement('userId') !== 'undefined') return getMepEnablement('userId'); @@ -11,10 +12,10 @@ async function getUserId() { } async function fetchFromRainfocus(eventId) { const userId = await getUserId(); - if (!userId) return {}; + if (!userId) return defaultReturn; const accessToken = window.adobeIMS.getAccessToken()?.token; - if (!accessToken) return {}; + if (!accessToken) return defaultReturn; const domainSuffix = getConfig()?.env?.name === 'prod' ? '' : '.stage'; const url = `https://www${domainSuffix}.adobe.com/events/api/rf-auth-seq-generic/${eventId}?user_id=${encodeURIComponent(userId)}`; @@ -29,19 +30,19 @@ async function fetchFromRainfocus(eventId) { tags: 'mep-event', severity: 'error', }); - return {}; + return defaultReturn; } catch (e) { window.lana?.log(`Unable to fetch from Rainfocus: ${e.toString()}`, { tags: 'mep-event', severity: 'error', }); - return {}; + return defaultReturn; } } export default async function init(eventId) { if (eventId === true) return { isRegistered: eventId }; const consentCookieValue = getCookie('OptanonConsent'); - if (consentCookieValue?.includes('C0002:0')) return false; + if (consentCookieValue?.includes('C0002:0')) return defaultReturn; return fetchFromRainfocus(eventId); } diff --git a/test/features/mep/addons/event.test.js b/test/features/mep/addons/event.test.js index 5e6e2e8615a..01e74262aea 100644 --- a/test/features/mep/addons/event.test.js +++ b/test/features/mep/addons/event.test.js @@ -1,6 +1,7 @@ import { expect } from '@esm-bundle/chai'; import { stub } from 'sinon'; import init from '../../../../libs/features/mep/addons/event.js'; +import { getConfig } from '../../../../libs/utils/utils.js'; const getFetchPromise = (data, type = 'json') => new Promise((resolve) => { resolve({ From d0ad0cd56b709310185630cf0d3a452e89fb2240 Mon Sep 17 00:00:00 2001 From: Vivian A Goodrich Date: Mon, 22 Jun 2026 14:10:47 -0600 Subject: [PATCH 07/15] Remove unused import from event test file --- test/features/mep/addons/event.test.js | 1 - 1 file changed, 1 deletion(-) diff --git a/test/features/mep/addons/event.test.js b/test/features/mep/addons/event.test.js index 01e74262aea..5e6e2e8615a 100644 --- a/test/features/mep/addons/event.test.js +++ b/test/features/mep/addons/event.test.js @@ -1,7 +1,6 @@ import { expect } from '@esm-bundle/chai'; import { stub } from 'sinon'; import init from '../../../../libs/features/mep/addons/event.js'; -import { getConfig } from '../../../../libs/utils/utils.js'; const getFetchPromise = (data, type = 'json') => new Promise((resolve) => { resolve({ From 787422c448133f6ec9be40c0ddc43dd86aec5918 Mon Sep 17 00:00:00 2001 From: Vivian A Goodrich Date: Mon, 22 Jun 2026 17:08:48 -0600 Subject: [PATCH 08/15] Refactor personalization variant logic to streamline event registration check Updated the getPersonalizationVariant function to simplify the condition for checking user registration status. Removed unnecessary assignment of event details to the config object, enhancing code clarity and efficiency. Adjusted the initialization of event details in the config to ensure proper handling of promises. --- libs/features/personalization/personalization.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/libs/features/personalization/personalization.js b/libs/features/personalization/personalization.js index 3ef7214c09b..e69012bf414 100644 --- a/libs/features/personalization/personalization.js +++ b/libs/features/personalization/personalization.js @@ -1084,10 +1084,7 @@ async function getPersonalizationVariant( if (userEntitlements?.includes(name)) return true; const { lob, event } = config.mep.promises; if (lob && lob === name.split('lob-')[1]?.toLowerCase()) return true; - if (name === 'registered' && event) { - config.mep.eventDetails = event; - if (event.isRegistered) return true; - } + if (name === 'registered' && event.isRegistered) return true; return PERSONALIZATION_KEYS.includes(name) && PERSONALIZATION_TAGS[name](); }; @@ -1679,6 +1676,7 @@ export async function init(enablements = {}) { } else { for (const [key, promise] of Object.entries(promises)) promises[key] = await promise; config.mep = { + ...(promises.event && { eventDetails: promises.event }), updateFragDataProps, preview: (mepButton !== 'off' && (config.env?.name !== 'prod' || mepParam || mepParam === '' || mepButton)), From 9d1523514fdff16102cba764dd0dff42a2bc4465 Mon Sep 17 00:00:00 2001 From: Vivian A Goodrich Date: Tue, 23 Jun 2026 10:13:04 -0600 Subject: [PATCH 09/15] Fix optional chaining for event registration check and clean up config assignment --- libs/features/personalization/personalization.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/libs/features/personalization/personalization.js b/libs/features/personalization/personalization.js index e69012bf414..07039d93b56 100644 --- a/libs/features/personalization/personalization.js +++ b/libs/features/personalization/personalization.js @@ -1084,7 +1084,7 @@ async function getPersonalizationVariant( if (userEntitlements?.includes(name)) return true; const { lob, event } = config.mep.promises; if (lob && lob === name.split('lob-')[1]?.toLowerCase()) return true; - if (name === 'registered' && event.isRegistered) return true; + if (name === 'registered' && event?.isRegistered) return true; return PERSONALIZATION_KEYS.includes(name) && PERSONALIZATION_TAGS[name](); }; @@ -1676,7 +1676,6 @@ export async function init(enablements = {}) { } else { for (const [key, promise] of Object.entries(promises)) promises[key] = await promise; config.mep = { - ...(promises.event && { eventDetails: promises.event }), updateFragDataProps, preview: (mepButton !== 'off' && (config.env?.name !== 'prod' || mepParam || mepParam === '' || mepButton)), From 2eddc4a649495e2c239fa2fe579d5289ef60fb52 Mon Sep 17 00:00:00 2001 From: Vivian A Goodrich Date: Tue, 23 Jun 2026 10:17:25 -0600 Subject: [PATCH 10/15] Remove unneccessary cookie consent check from event initialization --- libs/features/mep/addons/event.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/libs/features/mep/addons/event.js b/libs/features/mep/addons/event.js index 97b1f22790e..52951442973 100644 --- a/libs/features/mep/addons/event.js +++ b/libs/features/mep/addons/event.js @@ -1,5 +1,4 @@ import { isSignedOut, getConfig, getMepEnablement, loadIms } from '../../../utils/utils.js'; -import { getCookie } from '../../../martech/helpers.js'; const defaultReturn = { isRegistered: false }; async function getUserId() { @@ -41,8 +40,5 @@ async function fetchFromRainfocus(eventId) { } export default async function init(eventId) { if (eventId === true) return { isRegistered: eventId }; - - const consentCookieValue = getCookie('OptanonConsent'); - if (consentCookieValue?.includes('C0002:0')) return defaultReturn; return fetchFromRainfocus(eventId); } From 456b25f4bf135eff5d1c6e659d0a8f74758841f4 Mon Sep 17 00:00:00 2001 From: markpadbe Date: Tue, 7 Jul 2026 21:04:27 -0400 Subject: [PATCH 11/15] [MWPW-199051] Use event-code instead of event-id for MEP event addon --- libs/utils/utils.js | 2 +- test/utils/utils-mep.test.js | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/libs/utils/utils.js b/libs/utils/utils.js index 44edf05ecf2..91cdea45066 100644 --- a/libs/utils/utils.js +++ b/libs/utils/utils.js @@ -2118,7 +2118,7 @@ export function enablePersonalizationV2() { } export function loadMepAddons() { - const mepAddons = ['lob', 'event-id']; + const mepAddons = ['lob', 'event-code']; const promises = {}; mepAddons.forEach((addon) => { const enablement = getMepEnablement(addon); diff --git a/test/utils/utils-mep.test.js b/test/utils/utils-mep.test.js index 935fa1c0b6d..7abde7ac304 100644 --- a/test/utils/utils-mep.test.js +++ b/test/utils/utils-mep.test.js @@ -120,6 +120,16 @@ describe('MEP Utils', () => { console.log(promises); expect(promises.lob).to.be.a('promise'); }); + it('loads event from event-code metadata', async () => { + document.head.innerHTML = ''; + const promises = loadMepAddons(); + expect(promises.event).to.be.a('promise'); + }); + it('does not load event from the legacy event-id metadata', async () => { + document.head.innerHTML = ''; + const promises = loadMepAddons(); + expect(promises.event).to.be.undefined; + }); afterEach(() => { document.head.innerHTML = ''; }); From fb8f4b87549c98bf8b5e866ff8395c01b11e1aaf Mon Sep 17 00:00:00 2001 From: markpadbe Date: Thu, 9 Jul 2026 22:47:40 -0400 Subject: [PATCH 12/15] [MWPW-199051] Fix MEP event registration on Milo pages Event registration never triggered on Milo pages for two compounding reasons, plus an unhandled-rejection bug: - Casing: getMepEnablement read camelCase keys (signedIn/userId) but DA emits metadata names lowercase, so authored values never matched. Now falls back to the lowercase key while keeping URL params working. - userId gate assumed a Dexter-only SSR sentinel (userId="undefined") that Milo never emits, so a signed-in user with no userId meta hit `false !== 'undefined'` and returned false, never loading IMS. The gate now fails open to loadIms()/getProfile() to resolve the userId. - getProfile() rejections (e.g. no live IMS session) were unhandled and propagated through init(), blanking the page. Now caught -> not registered, page still renders. --- libs/features/mep/addons/event.js | 19 +++++++++++++------ test/features/mep/addons/event.test.js | 14 ++++++-------- 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/libs/features/mep/addons/event.js b/libs/features/mep/addons/event.js index 52951442973..f1f3c7ef269 100644 --- a/libs/features/mep/addons/event.js +++ b/libs/features/mep/addons/event.js @@ -2,12 +2,19 @@ import { isSignedOut, getConfig, getMepEnablement, loadIms } from '../../../util const defaultReturn = { isRegistered: false }; async function getUserId() { - if (isSignedOut() && !getMepEnablement('signedIn')) return false; - if (getMepEnablement('userId') !== 'undefined') return getMepEnablement('userId'); - /* c8 ignore next 3 */ - await loadIms(); - const { userId } = await window.adobeIMS.getProfile(); - return userId; + const signedInEnable = getMepEnablement('signedIn') || getMepEnablement('signedin'); + if (isSignedOut() && !signedInEnable) return false; + const userIdEnable = getMepEnablement('userId') || getMepEnablement('userid'); + if (userIdEnable && userIdEnable !== 'undefined') return userIdEnable; + /* c8 ignore start */ + try { + await loadIms(); + const { userId } = await window.adobeIMS.getProfile(); + return userId; + } catch { + return false; + } + /* c8 ignore stop */ } async function fetchFromRainfocus(eventId) { const userId = await getUserId(); diff --git a/test/features/mep/addons/event.test.js b/test/features/mep/addons/event.test.js index 5e6e2e8615a..a89705a294c 100644 --- a/test/features/mep/addons/event.test.js +++ b/test/features/mep/addons/event.test.js @@ -35,20 +35,18 @@ describe('event', () => { const event = await init(true); expect(event.isRegistered).to.equal(true); }); - it('should return false when consent cookie is set to C0002:0', async () => { - setCookie('OptanonConsent', 'C0002:0'); - const event = await init('adobe-max-2025'); - expect(event.isRegistered).to.equal(false); - }); it('should return false when the user is signed out', async () => { document.head.innerHTML = ''; const event = await init('adobe-max-2025'); expect(event.isRegistered).to.equal(false); }); - it('should return false when userId is set to off', async () => { - setMetadata('userId', 'off'); + it('should read enablements from lowercase DA metadata', async () => { + document.head.innerHTML = ''; + setMetadata('signedin', 'on'); + setMetadata('userid', '1234567890'); + setFetchResponse({ ok: true, isRegistered: true }); const event = await init('adobe-max-2025'); - expect(event.isRegistered).to.equal(false); + expect(event.isRegistered).to.equal(true); }); it('should return false when getAccessToken returns an empty object', async () => { setMetadata('userId', '1234567890'); From 3d1781d4fb0e703a75930ba4a35ba3b3aa812a31 Mon Sep 17 00:00:00 2001 From: markpadbe Date: Mon, 20 Jul 2026 18:39:41 -0700 Subject: [PATCH 13/15] [MEP] Cache Rainfocus registration across navigations (MWPW-199051) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a persistent localStorage cache to the event addon, keyed by event-code + userId, storing only { isRegistered, inPersonAttendee } — never the RF authToken/userKey. Registered results cache 24h, unregistered 3 min, mirroring the FEDS cacheTime model. Normalize RF's empty {} response to { isRegistered: false }. Redirect-cookie cache invalidation is implemented but gated off behind REDIRECT_INVALIDATION_ENABLED pending VEAL confirmation of the cookie name and domain. Rename eventId -> eventCode throughout for accuracy. --- libs/features/mep/addons/event.js | 71 +++++++++++++++++++++++--- test/features/mep/addons/event.test.js | 30 +++++++++++ 2 files changed, 94 insertions(+), 7 deletions(-) diff --git a/libs/features/mep/addons/event.js b/libs/features/mep/addons/event.js index f1f3c7ef269..d521fa125bd 100644 --- a/libs/features/mep/addons/event.js +++ b/libs/features/mep/addons/event.js @@ -1,6 +1,47 @@ -import { isSignedOut, getConfig, getMepEnablement, loadIms } from '../../../utils/utils.js'; +import { isSignedOut, getConfig, getMepEnablement, loadIms, getCookie } from '../../../utils/utils.js'; const defaultReturn = { isRegistered: false }; + +// Persist only what personalization reads; never the RF authToken/userKey. +const store = window.localStorage; +const TTL_REGISTERED = 24 * 60 * 60 * 1000; +const TTL_UNREGISTERED = 3 * 60 * 1000; +const cacheKey = (eventCode, userId) => `mep-event:${eventCode}:${userId}`; + +// TODO(MWPW-199051): enable once VEAL confirms the redirect-cookie name/domain. +const REDIRECT_INVALIDATION_ENABLED = false; +const justRegistered = (eventCode) => REDIRECT_INVALIDATION_ENABLED + && getCookie(`feds_${eventCode}_registeredByRedirect`) === 'true'; +/* c8 ignore next 3 */ +const clearRegisteredFlag = (eventCode) => { + document.cookie = `feds_${eventCode}_registeredByRedirect=; Max-Age=0; path=/`; +}; + +function readCache(eventCode, userId) { + try { + const raw = store.getItem(cacheKey(eventCode, userId)); + if (!raw) return null; + const { isRegistered, inPersonAttendee, exp } = JSON.parse(raw); + if (!exp || Date.now() > exp) return null; + return { isRegistered, inPersonAttendee }; + } catch { + return null; + } +} + +function writeCache(eventCode, userId, data) { + try { + const ttl = data.isRegistered ? TTL_REGISTERED : TTL_UNREGISTERED; + store.setItem(cacheKey(eventCode, userId), JSON.stringify({ + isRegistered: !!data.isRegistered, + inPersonAttendee: !!data.inPersonAttendee, + exp: Date.now() + ttl, + })); + } catch { + // storage unavailable — non-fatal + } +} + async function getUserId() { const signedInEnable = getMepEnablement('signedIn') || getMepEnablement('signedin'); if (isSignedOut() && !signedInEnable) return false; @@ -16,22 +57,37 @@ async function getUserId() { } /* c8 ignore stop */ } -async function fetchFromRainfocus(eventId) { + +async function fetchFromRainfocus(eventCode) { const userId = await getUserId(); if (!userId) return defaultReturn; + /* c8 ignore next 3 */ + if (justRegistered(eventCode)) { + store.removeItem(cacheKey(eventCode, userId)); + clearRegisteredFlag(eventCode); + } else { + const cached = readCache(eventCode, userId); + if (cached) return cached; + } + const accessToken = window.adobeIMS.getAccessToken()?.token; if (!accessToken) return defaultReturn; const domainSuffix = getConfig()?.env?.name === 'prod' ? '' : '.stage'; - const url = `https://www${domainSuffix}.adobe.com/events/api/rf-auth-seq-generic/${eventId}?user_id=${encodeURIComponent(userId)}`; + const url = `https://www${domainSuffix}.adobe.com/events/api/rf-auth-seq-generic/${eventCode}?user_id=${encodeURIComponent(userId)}`; try { const response = await fetch(url, { method: 'GET', headers: { Authorization: `Bearer ${accessToken}` }, credentials: 'same-origin', }); - if (response.ok) return response.json(); + if (response.ok) { + // RF returns {} (not { isRegistered: false }) when not registered. + const data = { isRegistered: false, ...(await response.json()) }; + writeCache(eventCode, userId, data); + return data; + } window.lana?.log(`Unable to fetch from Rainfocus: ${response.statusText}`, { tags: 'mep-event', severity: 'error', @@ -45,7 +101,8 @@ async function fetchFromRainfocus(eventId) { return defaultReturn; } } -export default async function init(eventId) { - if (eventId === true) return { isRegistered: eventId }; - return fetchFromRainfocus(eventId); + +export default async function init(eventCode) { + if (eventCode === true) return { isRegistered: eventCode }; + return fetchFromRainfocus(eventCode); } diff --git a/test/features/mep/addons/event.test.js b/test/features/mep/addons/event.test.js index a89705a294c..062e5acedd3 100644 --- a/test/features/mep/addons/event.test.js +++ b/test/features/mep/addons/event.test.js @@ -27,10 +27,14 @@ const setMetadata = (name, content) => { describe('event', () => { beforeEach(() => { document.head.innerHTML = ''; + localStorage.clear(); setMetadata('signedIn', 'on'); setCookie('OptanonConsent', 'C0002:1'); window.adobeIMS = { getAccessToken: () => ({ token: '1234567890' }) }; }); + afterEach(() => { + setCookie('feds_adobe-max-2025_registeredByRedirect', '; Max-Age=0'); + }); it('should return true when sending true into init', async () => { const event = await init(true); expect(event.isRegistered).to.equal(true); @@ -72,4 +76,30 @@ describe('event', () => { const event = await init('adobe-max-2025'); expect(event.isRegistered).to.equal(true); }); + it('should serve a cached result without re-calling the API', async () => { + setMetadata('userId', '1234567890'); + setFetchResponse({ ok: true, isRegistered: true }); + const first = await init('adobe-max-2025'); + expect(first.isRegistered).to.equal(true); + // A cache hit must ignore a now-changed API response. + setFetchResponse({ ok: true, isRegistered: false }); + const second = await init('adobe-max-2025'); + expect(second.isRegistered).to.equal(true); + }); + it('should normalize an empty API response to isRegistered false', async () => { + setMetadata('userId', '1234567890'); + setFetchResponse({}); + const event = await init('adobe-max-2025'); + expect(event.isRegistered).to.equal(false); + }); + // Un-skip when REDIRECT_INVALIDATION_ENABLED flips on (MWPW-199051). + it.skip('should bypass the cache when the registration-redirect cookie is set', async () => { + setMetadata('userId', '1234567890'); + setFetchResponse({ ok: true, isRegistered: false }); + await init('adobe-max-2025'); + setCookie('feds_adobe-max-2025_registeredByRedirect', 'true'); + setFetchResponse({ ok: true, isRegistered: true }); + const event = await init('adobe-max-2025'); + expect(event.isRegistered).to.equal(true); + }); }); From 4039e1f0527b6030f98ad3907ed37d6f84301200 Mon Sep 17 00:00:00 2001 From: markpadbe Date: Mon, 20 Jul 2026 19:28:49 -0700 Subject: [PATCH 14/15] [MEP] Note domain-match requirement for redirect-cookie deletion (MWPW-199051) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Document at the code site that clearRegisteredFlag must set a matching domain= when the invalidation branch is enabled — a host-only delete won't clear a .adobe.com cookie, which would leave the flag stuck. --- libs/features/mep/addons/event.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libs/features/mep/addons/event.js b/libs/features/mep/addons/event.js index d521fa125bd..59fda6c29e8 100644 --- a/libs/features/mep/addons/event.js +++ b/libs/features/mep/addons/event.js @@ -9,6 +9,8 @@ const TTL_UNREGISTERED = 3 * 60 * 1000; const cacheKey = (eventCode, userId) => `mep-event:${eventCode}:${userId}`; // TODO(MWPW-199051): enable once VEAL confirms the redirect-cookie name/domain. +// When enabling, clearRegisteredFlag must set domain= to match — a host-only +// delete won't clear a .adobe.com cookie, leaving the flag stuck. const REDIRECT_INVALIDATION_ENABLED = false; const justRegistered = (eventCode) => REDIRECT_INVALIDATION_ENABLED && getCookie(`feds_${eventCode}_registeredByRedirect`) === 'true'; From f530f673a3484e60d10c010c5a0bca75e3d24211 Mon Sep 17 00:00:00 2001 From: markpadbe Date: Thu, 23 Jul 2026 00:00:27 -0700 Subject: [PATCH 15/15] [MEP] Enable FEDS-style redirect-cookie registration signal (MWPW-199051) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When the VEAL post-registration redirect cookie (feds__registeredByRedirect) is present, treat the user as registered for the pre-LCP paint instead of blocking on RainFocus, then clear the one-shot flag and write the result through to the cache. Clear the flag with domain=.adobe.com to match how VEAL sets it — a host-only delete would no-op. The cookie name is event-specific, so a name mismatch degrades to the normal cache/RF path rather than producing a cross-event false positive. Removes the REDIRECT_INVALIDATION_ENABLED gate; the domain is confirmed from the VEAL redirector source, so no VEAL-side confirmation is needed. --- libs/features/mep/addons/event.js | 23 ++++++++++------------- test/features/mep/addons/event.test.js | 9 +++++---- 2 files changed, 15 insertions(+), 17 deletions(-) diff --git a/libs/features/mep/addons/event.js b/libs/features/mep/addons/event.js index 59fda6c29e8..326c51e9a33 100644 --- a/libs/features/mep/addons/event.js +++ b/libs/features/mep/addons/event.js @@ -8,15 +8,10 @@ const TTL_REGISTERED = 24 * 60 * 60 * 1000; const TTL_UNREGISTERED = 3 * 60 * 1000; const cacheKey = (eventCode, userId) => `mep-event:${eventCode}:${userId}`; -// TODO(MWPW-199051): enable once VEAL confirms the redirect-cookie name/domain. -// When enabling, clearRegisteredFlag must set domain= to match — a host-only -// delete won't clear a .adobe.com cookie, leaving the flag stuck. -const REDIRECT_INVALIDATION_ENABLED = false; -const justRegistered = (eventCode) => REDIRECT_INVALIDATION_ENABLED - && getCookie(`feds_${eventCode}_registeredByRedirect`) === 'true'; -/* c8 ignore next 3 */ +const justRegistered = (eventCode) => getCookie(`feds_${eventCode}_registeredByRedirect`) === 'true'; const clearRegisteredFlag = (eventCode) => { - document.cookie = `feds_${eventCode}_registeredByRedirect=; Max-Age=0; path=/`; + // VEAL sets this cookie Domain=.adobe.com; a host-only delete won't clear it. + document.cookie = `feds_${eventCode}_registeredByRedirect=; Max-Age=0; path=/; domain=.adobe.com`; }; function readCache(eventCode, userId) { @@ -64,14 +59,16 @@ async function fetchFromRainfocus(eventCode) { const userId = await getUserId(); if (!userId) return defaultReturn; - /* c8 ignore next 3 */ + // The redirect flag means the user just registered — trust it for the pre-LCP + // paint instead of blocking on RF, then clear the one-shot signal. if (justRegistered(eventCode)) { - store.removeItem(cacheKey(eventCode, userId)); clearRegisteredFlag(eventCode); - } else { - const cached = readCache(eventCode, userId); - if (cached) return cached; + const data = { isRegistered: true }; + writeCache(eventCode, userId, data); + return data; } + const cached = readCache(eventCode, userId); + if (cached) return cached; const accessToken = window.adobeIMS.getAccessToken()?.token; if (!accessToken) return defaultReturn; diff --git a/test/features/mep/addons/event.test.js b/test/features/mep/addons/event.test.js index 062e5acedd3..643a8e7d74c 100644 --- a/test/features/mep/addons/event.test.js +++ b/test/features/mep/addons/event.test.js @@ -92,14 +92,15 @@ describe('event', () => { const event = await init('adobe-max-2025'); expect(event.isRegistered).to.equal(false); }); - // Un-skip when REDIRECT_INVALIDATION_ENABLED flips on (MWPW-199051). - it.skip('should bypass the cache when the registration-redirect cookie is set', async () => { + it('should register from the redirect cookie, overriding a stale cache without calling the API', async () => { setMetadata('userId', '1234567890'); setFetchResponse({ ok: true, isRegistered: false }); - await init('adobe-max-2025'); + const stale = await init('adobe-max-2025'); + expect(stale.isRegistered).to.equal(false); setCookie('feds_adobe-max-2025_registeredByRedirect', 'true'); - setFetchResponse({ ok: true, isRegistered: true }); + window.fetch = stub(); const event = await init('adobe-max-2025'); expect(event.isRegistered).to.equal(true); + expect(window.fetch.called).to.equal(false); }); });