Skip to content
Merged
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
2 changes: 1 addition & 1 deletion .github/workflows/servicenow.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ jobs:
python-version: "3.x"
- name: Install dependencies
run: |
python -m pip install --upgrade pip requests timedelta
python -m pip install --upgrade pip requests

- name: Retrieve transaction ID from PR Comments
id: retrieve-transactionId-step
Expand Down
1 change: 0 additions & 1 deletion .kodiak/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ notifications:
fields:
assignee:
name: sghimpos
customfield_11800: MWPW-166942 # Jira epic security tickets will be assigned to.
watchers: # Everyone who is an admin on your repository should be a watcher.
- casalino
- radu
Expand Down
8 changes: 8 additions & 0 deletions libs/blocks/marketo/marketo.css
Original file line number Diff line number Diff line change
Expand Up @@ -550,6 +550,8 @@

.marketo .marketo-overlay {
position: absolute;
max-width: 600px;
margin: 0 auto;
inset: 0;
background: var(--marketo-form-overlay-background);
display: flex;
Expand Down Expand Up @@ -578,6 +580,12 @@
margin-bottom: 12px;
}

.marketo .marketo-overlay .debug-info {
font-size: 14px;
text-align: center;
color: var(--marketo-form-text);
}

@media screen and (min-width: 600px) {
.marketo .marketo-form-wrapper {
padding: var(--spacing-xxl);
Expand Down
228 changes: 139 additions & 89 deletions libs/blocks/marketo/marketo.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,22 @@ import {
SLD,
MILO_EVENTS,
} from '../../utils/utils.js';
import { replaceKey } from '../../features/placeholders.js';
import { replaceKeyArray } from '../../features/placeholders.js';

const ROOT_MARGIN = 50;
const IFRAME_TIMEOUT = 3000;
const FAILURE_TIMEOUT = 10000;
export const LANA_MESSAGE = {
RENDER_FAILED: 'Marketo form did not render',
HANDSHAKE_FAILED: 'Marketo form handshake failed',
RENDER_RECOVERED: 'Marketo form rendered after timeout',
SUBMIT_FAILED: 'Marketo form submit failed',
MARKETO_FORMS_JS: 'Marketo form failed to load forms2.min.js',
};
const FORM_ID = 'form id';
const BASE_URL = 'marketo host';
const MUNCHKIN_ID = 'marketo munckin';
const FORM_STATUS = 'form.status';
const FORM_XDFRAME = 'form.xdframe';
const SUCCESS_TYPE = 'form.success.type';
const SUCCESS_CONTENT = 'form.success.content';
const SUCCESS_SECTION = 'form.success.section';
Expand All @@ -50,6 +59,30 @@ const FORM_MAP = {
};
export const FORM_PARAM = 'form';

const isVisible = (el) => !!el && (typeof el.checkVisibility === 'function'
? el.checkVisibility({ checkOpacity: true, checkVisibilityCSS: true })
: !!(el.offsetWidth || el.offsetHeight || el.getClientRects().length));

export const setDataLayer = (key = '', value = '') => {
window.mcz_marketoForm_pref = window.mcz_marketoForm_pref || {};
if (!value || !key.includes('.')) return;
const keyParts = key.split('.');
const lastKey = keyParts.pop();
const formDataObject = keyParts.reduce((obj, part) => {
obj[part] = obj[part] || {};
return obj[part];
}, window.mcz_marketoForm_pref);
formDataObject[lastKey] = value;
};

export const setDataLayerObj = (formData) => {
Object.entries(formData).forEach(([key, value]) => setDataLayer(key, value));
};

export const getDataLayer = (key = '') => key
.split('.')
.reduce((obj, part) => obj?.[part], window.mcz_marketoForm_pref);

export const formValidate = (formEl) => {
formEl.classList.remove('hide-errors');
formEl.classList.add('show-warnings');
Expand Down Expand Up @@ -91,23 +124,6 @@ export const decorateURL = async (destination, baseURL = window.location) => {
return null;
};

const setPreference = (key = '', value = '') => {
window.mcz_marketoForm_pref = window.mcz_marketoForm_pref || {};
if (!value || !key.includes('.')) return;
const keyParts = key.split('.');
const lastKey = keyParts.pop();
const formDataObject = keyParts.reduce((obj, part) => {
obj[part] = obj[part] || {};
return obj[part];
}, window.mcz_marketoForm_pref);
formDataObject[lastKey] = value;
};

export const setPreferences = (formData) => {
setPreference('form.status', 'pending');
Object.entries(formData).forEach(([key, value]) => setPreference(key, value));
};

const showSuccessSection = (formData) => {
const show = async (sections) => {
sections.forEach((section) => section.classList.remove('hide-block'));
Expand Down Expand Up @@ -167,11 +183,90 @@ const hideSuccessSection = (formData) => {
);
};

export const debugTags = () => {
const len = document.cookie.length;
const tags = ['marketo'];
const signedIn = window.adobeIMS?.isSignedInUser();
const frameStatus = getDataLayer(FORM_XDFRAME);
if (getDataLayer('form.id')) tags.push(`form-${getDataLayer('form.id')}`);
if (getDataLayer('program.id')) tags.push(`program-${getDataLayer('program.id')}`);
if (getDataLayer(FORM_STATUS)) tags.push(`status-${getDataLayer(FORM_STATUS)}`);
if (len >= 8192) tags.push('cookie-8k');
else if (len >= 6144) tags.push('cookie-6k');
else if (len >= 4096) tags.push('cookie-4k');
tags.push(`ims-${signedIn ? 'signed-in' : 'signed-out'}`);
tags.push(frameStatus === 'ready' ? 'sync-ok' : 'sync-error');
if (getDataLayer('form.progressive')) tags.push('progressive');
if (getDataLayer('profile.known_visitor') === true) tags.push('known-visitor');
tags.push(`pref-lang-${getDataLayer('profile.prefLanguage') ?? ''}`);
return tags;
};

const decorateOverlay = async (el, message, callback) => {
if (el.querySelector('.marketo-overlay')) return;
const [errorRefresh, tryAgain] = await replaceKeyArray(['marketo-load-error', 'marketo-try-again'], getConfig());
const formEl = el.querySelector('form');
if (formEl) formEl.inert = true;
const searchParams = new URLSearchParams(window.location.search);
const debugMsg = searchParams.get('preview') === '1' ? message : '';
const errorMessage = createTag('p', { class: 'error', id: 'marketo-error-message' }, errorRefresh);
const formError = createTag('div', { class: 'error-container' }, errorMessage);
if (debugMsg) {
const debugInfo = createTag('p', { class: 'debug-info' });
debugInfo.textContent = debugMsg;
formError.appendChild(debugInfo);
}
const retryButton = createTag('button', { class: 'retry-button' }, tryAgain);
formError.appendChild(retryButton);
const errorOverlay = createTag(
'div',
{
class: 'marketo-overlay',
role: 'alertdialog',
'aria-modal': 'true',
'aria-labelledby': 'marketo-error-message',
},
formError,
);

retryButton.addEventListener('click', () => {
/* c8 ignore next 3 */
if (formEl) formEl.inert = false;
errorOverlay.remove();
if (callback) callback();
});

el.appendChild(errorOverlay);
};

export const logFailure = (el, msg) => {
if (el.dataset.mktoFailed) return;
const tags = debugTags();
el.dataset.mktoFailed = 'true';
window.lana?.log(msg, { tags: tags.join(','), severity: 'e', sampleRate: 100 });
decorateOverlay(el, `${msg}: ${tags.join(', ')}`, () => { window.location.reload(); });
};

export const formTimeout = (el, condition, message, timeout = FAILURE_TIMEOUT) => {
setTimeout(() => {
if (condition()) {
logFailure(el, message);
}
}, timeout);
};

const toggleSuccessSection = (formData) => {
showSuccessSection(formData);
hideSuccessSection(formData);
};

export const formSubmit = (formEl) => {
const el = formEl.closest('.marketo');
const testRecord = window.mkto_isTestRecord?.();
if (testRecord && testRecord !== 'not_test') return;
formTimeout(el, () => !el.classList.contains('success'), LANA_MESSAGE.SUBMIT_FAILED);
};

export const formSuccess = (formEl, formData) => {
const el = formEl.closest('.marketo');
const parentModal = formEl?.closest('.dialog-modal');
Expand Down Expand Up @@ -210,83 +305,33 @@ export const formSuccess = (formEl, formData) => {

if (formData?.[SUCCESS_TYPE] !== 'section') return true;
toggleSuccessSection(formData);
setPreference(SUCCESS_TYPE, 'message');
setDataLayer(SUCCESS_TYPE, 'message');
return false;
};

export const handleIframeTimeout = (el) => {
const searchParams = new URLSearchParams(window.location.search);
const config = getConfig();
const iframe = document.querySelector('iframe[src*="/index.php/form/XDFrame"]');
const formEl = el.querySelector('form');
let iframeTimeout = null;
const readyForm = (form, formData) => {
const formEl = form.getFormElem().get(0);
const el = formEl.closest('.marketo');
const isDesktop = matchMedia('(min-width: 900px)');
el.classList.remove('loading');

const handleIframeReady = (event) => {
if (event.origin !== 'https://engage.adobe.com') return;
const message = JSON.parse(event.data);
if (!message.mktoReady) return;
setPreference('form.status', 'ready');
if (iframeTimeout) clearTimeout(iframeTimeout);
const errorOverlay = el.querySelector('.marketo-overlay');
if (formEl) formEl.inert = false;
if (errorOverlay) errorOverlay.remove();
const hadFailed = el.dataset.mktoFailed === 'true';
setDataLayer(FORM_XDFRAME, 'ready');
const formVisible = isVisible(formEl);
if (hadFailed && formVisible) {
window.lana?.log(LANA_MESSAGE.RENDER_RECOVERED, { tags: 'marketo,render-recovered', severity: 'i', sampleRate: 100 });
delete el.dataset.mktoFailed;
el.querySelector('.marketo-overlay')?.remove();
formEl.inert = false;
}
window.removeEventListener('message', handleIframeReady);
};

const decorateOverlay = async () => {
const cookieSize = document.cookie.length > 4096 ? '> 4k' : '< 4k';
window.lana?.log(`Marketo iframe timeout - Cookie Size ${cookieSize}`, { tags: 'marketo', severity: 'e' });
setPreference('form.status', 'error');
if (el.querySelector('.marketo-overlay')) return;
if (!config.marketo?.showError && searchParams.get('marketoOverlay') !== 'error') return;

const marketoErrorText = await replaceKey('marketo-load-error', config);
const marketoTryAgainText = await replaceKey('marketo-try-again', config);
if (formEl) formEl.inert = true;

const errorMessage = createTag('p', { class: 'error', id: 'marketo-error-message' }, marketoErrorText);
const retryButton = createTag('button', { class: 'retry-button' }, marketoTryAgainText);
const formError = createTag('div', { class: 'error-container' }, [errorMessage, retryButton]);
const errorOverlay = createTag(
'div',
{
class: 'marketo-overlay',
role: 'alertdialog',
'aria-modal': 'true',
'aria-labelledby': 'marketo-error-message',
},
formError,
);

retryButton.addEventListener('click', () => {
const iframeSrc = iframe.src;
errorOverlay.remove();
if (formEl) formEl.inert = false;
iframe.src = '';
iframe.src = iframeSrc;
clearTimeout(iframeTimeout);
iframeTimeout = setTimeout(decorateOverlay, config.marketo?.iframeTimeout ?? IFRAME_TIMEOUT);
});

el.appendChild(errorOverlay);
};

/* c8 ignore next 4 */
if (searchParams.get('marketoOverlay') === 'error') {
decorateOverlay();
return;
}

iframeTimeout = setTimeout(decorateOverlay, config.marketo?.iframeTimeout ?? IFRAME_TIMEOUT);
window.addEventListener('message', handleIframeReady);
};

const readyForm = (form, formData) => {
const formEl = form.getFormElem().get(0);
const el = formEl.closest('.marketo');
const isDesktop = matchMedia('(min-width: 900px)');
el.classList.remove('loading');
handleIframeTimeout(el);
formTimeout(el, () => getDataLayer(FORM_XDFRAME) !== 'ready', LANA_MESSAGE.HANDSHAKE_FAILED);

formEl.addEventListener('focus', ({ target }) => {
/* c8 ignore next 9 */
Expand All @@ -301,21 +346,24 @@ const readyForm = (form, formData) => {
window.scrollTo(0, offsetPosition);
}, true);
form.onValidate(() => formValidate(formEl));
form.onSubmit(() => formSubmit(formEl));
form.onSuccess(() => formSuccess(formEl, formData));
};

export const loadMarketo = (el, formData) => {
setDataLayer(FORM_STATUS, 'loading');
const baseURL = formData[BASE_URL];
const munchkinID = formData[MUNCHKIN_ID];
const formID = formData[FORM_ID];
const { base } = getConfig();

loadScript(`${base}/deps/forms2.min.js`)
return loadScript(`${base}/deps/forms2.min.js`)
.then(() => {
const { MktoForms2 } = window;
if (!MktoForms2) throw new Error('Marketo forms not loaded');

MktoForms2.loadForm(`//${baseURL}`, munchkinID, formID);
formTimeout(el, () => !isVisible(el.querySelector('form')), LANA_MESSAGE.RENDER_FAILED);
MktoForms2.loadForm(`//${baseURL}`, munchkinID, formID, () => { setDataLayer(FORM_STATUS, 'loaded'); });
MktoForms2.whenReady((form) => { readyForm(form, formData); });

/* c8 ignore next 3 */
Expand All @@ -326,7 +374,7 @@ export const loadMarketo = (el, formData) => {
.catch(() => {
/* c8 ignore next 2 */
el.style.display = 'none';
window.lana?.log(`Error loading Marketo form for ${munchkinID}_${formID}`, { tags: 'marketo', severity: 'e' });
logFailure(el, LANA_MESSAGE.MARKETO_FORMS_JS);
});
};

Expand Down Expand Up @@ -367,6 +415,7 @@ function decorateForm(el, formData) {
}

export default async function init(el) {
setDataLayer(FORM_STATUS, 'init');
const children = Array.from(el.querySelectorAll(':scope > div'));
const encodedConfigDiv = children.shift();
const link = encodedConfigDiv.querySelector('a');
Expand Down Expand Up @@ -423,7 +472,8 @@ export default async function init(el) {
if (destinationUrl) formData[SUCCESS_CONTENT] = destinationUrl;
}

setPreferences(formData);
formData[FORM_STATUS] = 'decorated';
setDataLayerObj(formData);
decorateForm(el, formData);

loadLink(`https://${baseURL}`, { rel: 'dns-prefetch' });
Expand Down
Loading
Loading