diff --git a/head.html b/head.html
index 14aab57a2..3359cb012 100644
--- a/head.html
+++ b/head.html
@@ -3,4 +3,4 @@
-
\ No newline at end of file
+
diff --git a/solutions/scripts/scripts.js b/solutions/scripts/scripts.js
index 37ab4299f..83efc000a 100644
--- a/solutions/scripts/scripts.js
+++ b/solutions/scripts/scripts.js
@@ -32,6 +32,8 @@ export const DEFAULT_COUNTRY = 'au';
export const METADATA_ANALYTICS_TAGS = 'analytics-tags';
+const TARGET_TENANT = 'bitdefender';
+
const HREFLANG_MAP = [
['en-ro', { baseUrl: 'https://www.bitdefender.ro', pageType: '.html' }],
['de', { baseUrl: 'https://www.bitdefender.de', pageType: '.html' }],
@@ -459,7 +461,7 @@ function getExperimentDetails() {
return { experimentId, experimentVariant };
}
-function pushPageLoadToDataLayer() {
+function pushPageLoadToDataLayer(targetExperimentDetails) {
const { hostname } = window.location;
if (!hostname) {
return;
@@ -469,7 +471,9 @@ function pushPageLoadToDataLayer() {
const environment = getEnvironment(hostname, languageCountry.country);
const tags = getTags(getMetadata(METADATA_ANALYTICS_TAGS));
- const experimentDetails = getExperimentDetails();
+ const experimentDetails = targetExperimentDetails ?? getExperimentDetails();
+ // eslint-disable-next-line no-console
+ console.debug(`Experiment details: ${JSON.stringify(experimentDetails)}`);
pushToDataLayer('page load started', {
pageInstanceID: environment,
@@ -511,7 +515,13 @@ async function loadEager(doc) {
await window.hlx.plugins.run('loadEager');
- pushPageLoadToDataLayer();
+ let targetExperimentDetails = null;
+ if (getMetadata('target-experiment') !== '') {
+ const { runTargetExperiment } = await import('./target.js');
+ targetExperimentDetails = await runTargetExperiment(TARGET_TENANT);
+ }
+
+ pushPageLoadToDataLayer(targetExperimentDetails);
if (getMetadata('template') !== '') {
loadCSS(`${window.hlx.codeBasePath}/styles/${getMetadata('template')}.css`);
diff --git a/solutions/scripts/target.js b/solutions/scripts/target.js
new file mode 100644
index 000000000..f85d55906
--- /dev/null
+++ b/solutions/scripts/target.js
@@ -0,0 +1,148 @@
+import { getMetadata, sampleRUM } from './lib-franklin.js';
+
+const ADOBE_TARGET_SESSION_ID_PARAM = 'adobeTargetSessionId';
+
+/**
+ * Convert a URL to a relative URL.
+ * @param url
+ * @returns {*|string}
+ */
+function getPlainPageUrl(url) {
+ const { pathname, search, hash } = new URL(url, window.location.href);
+ const plainPagePathname = pathname.endsWith('/') ? `${pathname}index.plain.html` : `${pathname}.plain.html`;
+ return `${plainPagePathname}${search}${hash}`;
+}
+
+/**
+ * Generate a random session id.
+ * @param length
+ * @returns {string}
+ */
+function generateSessionID(length = 16) {
+ const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
+ let sessionID = '';
+ for (let i = 0; i < length; i += 1) {
+ const randomIndex = Math.floor(Math.random() * characters.length);
+ sessionID += characters.charAt(randomIndex);
+ }
+ return sessionID;
+}
+
+/**
+ * Get or create a session id for the current user.
+ * @returns {string}
+ */
+function getOrCreateSessionId() {
+ let sessionId = sessionStorage.getItem(ADOBE_TARGET_SESSION_ID_PARAM);
+ // eslint-disable-next-line no-console
+ console.debug(`Session id: ${sessionId}`);
+ if (!sessionId) {
+ sessionId = generateSessionID();
+ // eslint-disable-next-line no-console
+ console.debug(`Generated new session id: ${sessionId}`);
+ sessionStorage.setItem(ADOBE_TARGET_SESSION_ID_PARAM, sessionId);
+ }
+ return sessionId;
+}
+
+/**
+ * Fetch the target offers for the current location.
+ * @returns {Promise}
+ */
+async function fetchChallengerPageUrl(tenant, targetLocation) {
+ // eslint-disable-next-line no-console
+ console.debug(`Fetching target offers for location: ${targetLocation}`);
+ const res = await fetch(`https://${tenant}.tt.omtrdc.net/rest/v1/delivery?client=${tenant}&sessionId=${getOrCreateSessionId()}`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({
+ context: {
+ channel: 'web',
+ },
+ execute: {
+ pageLoad: {},
+ mboxes: [
+ {
+ name: targetLocation,
+ index: 0,
+ },
+ ],
+ },
+ }),
+ });
+
+ const payload = await res.json();
+ const mbox = payload.execute.mboxes.find((m) => m.name === targetLocation);
+ const { url } = mbox?.options[0].content ?? { url: null };
+ if (!url) {
+ // eslint-disable-next-line no-console
+ console.error('No challenger url found');
+ throw new Error('No challenger url found');
+ }
+
+ // eslint-disable-next-line no-console
+ console.debug(`Resolved challenger url: ${url}`);
+ return url;
+}
+
+/**
+ * Replace the current page with the challenger page.
+ * @param url The challenger page url.
+ * @returns {Promise}
+ */
+async function navigateToChallengerPage(url) {
+ const plainPath = getPlainPageUrl(url);
+
+ // eslint-disable-next-line no-console
+ console.debug(`Navigating to challenger page: ${plainPath}`);
+
+ const resp = await fetch(plainPath);
+ if (!resp.ok) {
+ throw new Error(`Failed to fetch challenger page: ${resp.status}`);
+ }
+
+ const mainElement = document.querySelector('main');
+ if (!mainElement) {
+ throw new Error('Main element not found');
+ }
+
+ mainElement.innerHTML = await resp.text();
+}
+
+// eslint-disable-next-line import/prefer-default-export
+export async function runTargetExperiment(clientId) {
+ try {
+ const experimentId = getMetadata('target-experiment');
+ const targetLocation = getMetadata('target-experiment-location');
+ if (!experimentId || !targetLocation) {
+ // eslint-disable-next-line no-console
+ console.log('Experiment id or target location not found');
+ return null;
+ }
+
+ // eslint-disable-next-line no-console
+ console.debug(`Running Target experiment ${experimentId} at location ${targetLocation}`);
+
+ const pageUrl = await fetchChallengerPageUrl(clientId, targetLocation);
+ // eslint-disable-next-line no-console
+ console.debug(`Challenger page url: ${pageUrl}`);
+
+ await navigateToChallengerPage(pageUrl);
+
+ sampleRUM('target-experiment', {
+ source: `target:${experimentId}`,
+ target: pageUrl,
+ });
+
+ return {
+ experimentId,
+ experimentVariant: pageUrl,
+ };
+ } catch (e) {
+ // eslint-disable-next-line no-console
+ console.error('Error running target experiment:', e);
+ return null;
+ }
+}