Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ENB-7652] Refactor flags and methods #3498

Open
wants to merge 19 commits into
base: stage
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 32 additions & 19 deletions libs/features/personalization/personalization.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
/* eslint-disable default-param-last */
/* eslint-disable no-underscore-dangle */
/* eslint-disable no-console */

import {
createTag, getConfig, loadLink, loadScript, localizeLink, enablePersonalizationV2,
} from '../../utils/utils.js';
import { createTag, getConfig, loadLink, loadScript, localizeLink } from '../../utils/utils.js';
import { getFederatedUrl } from '../../utils/federated.js';

/* c8 ignore start */
Expand Down Expand Up @@ -763,7 +762,11 @@ export const getEntitlements = async (data) => {
});
};

async function getPersonalizationVariant(manifestPath, variantNames = [], variantLabel = null) {
async function getPersonalizationVariant(
manifestPath,
variantNames = [],
variantLabel = null,
) {
const config = getConfig();
if (config.mep?.variantOverride?.[manifestPath]) {
return config.mep.variantOverride[manifestPath];
Expand All @@ -776,7 +779,7 @@ async function getPersonalizationVariant(manifestPath, variantNames = [], varian

let userEntitlements = [];
if (hasEntitlementTag) {
if (enablePersonalizationV2()) {
if (config?.mep?.enablePersV2) {
userEntitlements = [];
} else {
userEntitlements = await config.entitlements();
Expand Down Expand Up @@ -1040,12 +1043,15 @@ export function parseNestedPlaceholders({ placeholders }) {
});
}

export async function applyPers(manifests) {
export async function applyPers({ manifests }) {
if (!manifests?.length) return;
let experiments = manifests;
const config = getConfig();
for (let i = 0; i < experiments.length; i += 1) {
experiments[i] = await getManifestConfig(experiments[i], config.mep?.variantOverride);
experiments[i] = await getManifestConfig(
experiments[i],
config.mep?.variantOverride,
);
}

experiments = cleanAndSortManifestList(experiments);
Expand Down Expand Up @@ -1121,12 +1127,14 @@ export const combineMepSources = async (persEnabled, promoEnabled, mepParam) =>
return persManifests;
};

async function updateManifestsAndPropositions({ config, targetManifests, targetPropositions }) {
async function updateManifestsAndPropositions(
{ config, targetManifests, targetPropositions },
) {
targetManifests.forEach((manifest) => {
manifest.source = ['target'];
});
config.mep.targetManifests = targetManifests;
if (enablePersonalizationV2()) {
if (config?.mep?.enablePersV2) {
window.addEventListener('alloy_sendEvent', () => {
if (targetPropositions?.length && window._satellite) {
window._satellite.track('propositionDisplay', targetPropositions);
Expand Down Expand Up @@ -1199,7 +1207,7 @@ async function handleMartechTargetInteraction(
) {
let targetManifests = [];
let targetPropositions = [];
if (enablePersonalizationV2() && targetInteractionPromise) {
if (config?.mep?.enablePersV2 && targetInteractionPromise) {
const { targetInteractionData, respTime, respStartTime } = await targetInteractionPromise;
sendTargetResponseAnalytics(false, respStartTime, calculatedTimeout);
if (targetInteractionData.result) {
Expand All @@ -1217,7 +1225,9 @@ async function handleMartechTargetInteraction(
}
}

return updateManifestsAndPropositions({ config, targetManifests, targetPropositions });
return updateManifestsAndPropositions(
{ config, targetManifests, targetPropositions },
);
}

async function callMartech(config) {
Expand All @@ -1226,7 +1236,9 @@ async function callMartech(config) {
targetManifests,
targetPropositions,
} = await getTargetPersonalization({ handleAlloyResponse, sendTargetResponseAnalytics });
return updateManifestsAndPropositions({ config, targetManifests, targetPropositions });
return updateManifestsAndPropositions(
{ config, targetManifests, targetPropositions },
);
}

const awaitMartech = () => new Promise((resolve) => {
Expand All @@ -1237,7 +1249,7 @@ const awaitMartech = () => new Promise((resolve) => {
export async function init(enablements = {}) {
let manifests = [];
const {
mepParam, mepHighlight, mepButton, pzn, promo,
mepParam, mepHighlight, mepButton, pzn, promo, enablePersV2,
target, targetInteractionPromise, calculatedTimeout, postLCP,
} = enablements;
const config = getConfig();
Expand All @@ -1253,6 +1265,7 @@ export async function init(enablements = {}) {
targetEnabled: target,
experiments: [],
prefix: config.locale?.prefix.split('/')[1]?.toLowerCase() || US_GEO,
enablePersV2,
};
manifests = manifests.concat(await combineMepSources(pzn, promo, mepParam));
manifests?.forEach((manifest) => {
Expand All @@ -1263,20 +1276,20 @@ export async function init(enablements = {}) {
if (pzn) loadLink(getXLGListURL(config), { as: 'fetch', crossorigin: 'anonymous', rel: 'preload' });
}

if (enablePersonalizationV2()) {
if (enablePersV2 && target === true) {
manifests = manifests.concat(await handleMartechTargetInteraction(
{ config, targetInteractionPromise, calculatedTimeout },
));
} else {
if (target === true) manifests = manifests.concat(await callMartech(config));
if (target === 'postlcp') callMartech(config);
if (postLCP) {
if (!config.mep.targetManifests) await awaitMartech();
manifests = config.mep.targetManifests;
}
}
if (postLCP) {
if (!config.mep.targetManifests) await awaitMartech();
manifests = config.mep.targetManifests;
}
try {
if (manifests?.length) await applyPers(manifests);
if (manifests?.length) await applyPers({ manifests });
if (config.mep?.preview) await import('./preview.js').then(({ saveToMmm }) => saveToMmm());
} catch (e) {
log(`MEP Error: ${e.toString()}`);
Expand Down
70 changes: 45 additions & 25 deletions libs/martech/helpers.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
const AMCV_COOKIE = 'AMCV_9E1005A551ED61CA0A490D45@AdobeOrg';
const KNDCTR_COOKIE_KEYS = [
'kndctr_9E1005A551ED61CA0A490D45_AdobeOrg_identity',
'kndctr_9E1005A551ED61CA0A490D45_AdobeOrg_cluster',
];

function getDomainWithoutWWW() {
const domain = window?.location?.hostname;
return domain.replace(/^www\./, '');
}

/**
* Generates a random UUIDv4 using cryptographically secure random values.
Expand Down Expand Up @@ -117,7 +126,7 @@ function setCookie(key, value, options = {}) {
date.setTime(date.getTime() + expires * 24 * 60 * 60 * 1000);
const expiresString = `expires=${date.toUTCString()}`;

document.cookie = `${key}=${value}; ${expiresString}; path=/`;
document.cookie = `${key}=${value}; ${expiresString}; path=/ ; domain=.${getDomainWithoutWWW()};`;
}

/**
Expand Down Expand Up @@ -211,16 +220,10 @@ function getUpdatedContext({
* @returns {Array<Object>} List of MarTech cookies with each
* object containing 'key' and 'value' properties.
*/
const getMarctechCookies = () => {
const KNDCTR_COOKIE_KEYS = [
'kndctr_9E1005A551ED61CA0A490D45_AdobeOrg_identity',
'kndctr_9E1005A551ED61CA0A490D45_AdobeOrg_cluster',
];
return document.cookie.split(';')
.map((x) => x.trim().split('='))
.filter(([key]) => KNDCTR_COOKIE_KEYS.includes(key))
.map(([key, value]) => ({ key, value }));
};
const getMartechCookies = () => document.cookie.split(';')
.map((x) => x.trim().split('='))
.filter(([key]) => KNDCTR_COOKIE_KEYS.includes(key))
.map(([key, value]) => ({ key, value }));

/**
* Creates the request payload for Adobe Analytics and Target.
Expand Down Expand Up @@ -300,24 +303,28 @@ function createRequestPayload({ updatedContext, pageName, locale, env }) {
com_adobe_target: { propertyToken: AT_PROPERTY_VAL },
},
state: {
domain: 'localhost',
domain: getDomainWithoutWWW(),
cookiesEnabled: true,
entries: getMarctechCookies(),
entries: getMartechCookies(),
},
},
};
}

/**
* Extracts the ECID (Experience Cloud ID) from the API response data.
* Updates the specified cookies with new values if they don't already exist.
*
* @param {Array<Object>} cookieData - An array of objects containing
* `key` and `value` pairs for the cookies.
*
* @param {Object} data - The response data from the API.
* @returns {string|null} The ECID value, or null if not found.
*/
function extractECIDFromResp(data) {
return data.handle
.flatMap((item) => item.payload)
.find((p) => p.namespace?.code === 'ECID')?.id || null;
function updateMartechCookies(cookieData) {
cookieData?.forEach(({ key, value }) => {
const currentCookie = getCookie(key);
if (!currentCookie) {
setCookie(encodeURIComponent(key), value);
}
});
}

/**
Expand Down Expand Up @@ -374,11 +381,11 @@ export const loadAnalyticsAndInteractionData = async ({ locale, env, calculatedT
const value = getCookie('kndctr_9E1005A551ED61CA0A490D45_AdobeOrg_consent');

if (value === 'general=out') {
return Promise.reject(new Error('Consent Cookie doesnt allow interact'));
return {};
}

// Define constants based on environment
const DATA_STREAM_ID = env === 'prod' ? '5856abb0-95d8-4f9a-bb92-37f99d2bd492' : '87f9b644-5fd3-4015-81d5-f68ad81c3561';
const DATA_STREAM_ID = env === 'prod' ? '913eac4d-900b-45e8-9ee7-306216765cd2' : 'e065836d-be57-47ef-b8d1-999e1657e8fd';
const TARGET_API_URL = getUrl();

// Device and viewport information
Expand Down Expand Up @@ -425,11 +432,24 @@ export const loadAnalyticsAndInteractionData = async ({ locale, env, calculatedT
throw new Error('Failed to fetch interact call');
}
const targetRespJson = await targetResp.json();
const ECID = extractECIDFromResp(targetRespJson);

// Update the AMCV cookie with ECID
const ECID = targetRespJson.handle
.flatMap((item) => item.payload)
.find((p) => p.namespace?.code === 'ECID')?.id || null;
updateAMCVCookie(ECID);

const extractedData = [];
targetRespJson?.handle?.forEach((item) => {
if (item?.type === 'state:store') {
item?.payload?.forEach((payload) => {
if (payload?.key === KNDCTR_COOKIE_KEYS[0] || payload?.key === KNDCTR_COOKIE_KEYS[1]) {
extractedData.push({ ...payload });
}
});
}
});

updateMartechCookies(extractedData);

// Resolve or reject based on propositions
const resultPayload = targetRespJson?.handle?.find((d) => d.type === 'personalization:decisions')?.payload;
if (resultPayload.length === 0) throw new Error('No propositions found');
Expand Down
50 changes: 19 additions & 31 deletions libs/utils/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ const PAGE_URL = new URL(window.location.href);
export const SLD = PAGE_URL.hostname.includes('.aem.') ? 'aem' : 'hlx';

const PROMO_PARAM = 'promo';
let isMartechLoaded = false;

export function getEnv(conf) {
const { host } = window.location;
Expand Down Expand Up @@ -1079,6 +1080,7 @@ export async function loadMartech({

const { default: initMartech } = await import('../martech/martech.js');
await initMartech({ persEnabled, persManifests, postLCP });
isMartechLoaded = true;

return true;
}
Expand All @@ -1103,7 +1105,7 @@ function isSignedOut() {
* @returns {boolean} True if personalization is enabled, otherwise false.
*/
export function enablePersonalizationV2() {
const enablePersV2 = document.head.querySelector('meta[name="personalization-v2"]');
const enablePersV2 = getMepEnablement('personalization-v2');
return !!enablePersV2 && isSignedOut();
}

Expand All @@ -1115,6 +1117,7 @@ async function checkForPageMods() {
martech,
} = Object.fromEntries(PAGE_URL.searchParams);
let targetInteractionPromise = null;
let calculatedTimeout = null;
if (mepParam === 'off') return;
const pzn = getMepEnablement('personalization');
const promo = getMepEnablement('manifestnames', PROMO_PARAM);
Expand All @@ -1127,7 +1130,7 @@ async function checkForPageMods() {
const enablePersV2 = enablePersonalizationV2();
if ((target || xlg) && enablePersV2) {
const params = new URL(window.location.href).searchParams;
const calculatedTimeout = parseInt(params.get('target-timeout'), 10)
calculatedTimeout = parseInt(params.get('target-timeout'), 10)
|| parseInt(getMetadata('target-timeout'), 10)
|| TARGET_TIMEOUT_MS;

Expand All @@ -1145,34 +1148,27 @@ async function checkForPageMods() {

return { targetInteractionData: data, respTime, respStartTime: now };
})();

const { init } = await import('../features/personalization/personalization.js');
await init({
mepParam,
mepHighlight,
mepButton,
pzn,
promo,
target,
targetInteractionPromise,
calculatedTimeout,
});
return;
}
if (target || xlg) {
loadMartech();
} else if (pzn && martech !== 'off') {
} else if ((target || xlg) && !isMartechLoaded) loadMartech();
else if (pzn && martech !== 'off') {
loadIms()
.then(() => {
/* c8 ignore next */
if (window.adobeIMS?.isSignedInUser()) loadMartech();
if (window.adobeIMS?.isSignedInUser() && !isMartechLoaded) loadMartech();
})
.catch((e) => { console.log('Unable to load IMS:', e); });
}

const { init } = await import('../features/personalization/personalization.js');
await init({
mepParam, mepHighlight, mepButton, pzn, promo, target, targetInteractionPromise,
mepParam,
mepHighlight,
mepButton,
pzn,
promo,
target,
targetInteractionPromise,
calculatedTimeout,
enablePersV2,
});
}

Expand All @@ -1184,12 +1180,8 @@ async function loadPostLCP(config) {
/* c8 ignore next 2 */
const { init } = await import('../features/personalization/personalization.js');
await init({ postLCP: true });
if (enablePersonalizationV2()) {
loadMartech();
}
} else {
loadMartech();
}
if (enablePersonalizationV2() && !isMartechLoaded) loadMartech();
} else if (!isMartechLoaded) loadMartech();

const georouting = getMetadata('georouting') || config.geoRouting;
if (georouting === 'on') {
Expand Down Expand Up @@ -1439,10 +1431,6 @@ export async function loadArea(area = document) {
await loadDeferred(area, areaBlocks, config);
}

export function loadDelayed() {
// TODO: remove after all consumers have stopped calling this method
}

export const utf8ToB64 = (str) => window.btoa(unescape(encodeURIComponent(str)));
export const b64ToUtf8 = (str) => decodeURIComponent(escape(window.atob(str)));

Expand Down
Loading
Loading