diff --git a/.kodiak/config.yaml b/.kodiak/config.yaml index 89cf03ff..645bbf45 100644 --- a/.kodiak/config.yaml +++ b/.kodiak/config.yaml @@ -9,7 +9,7 @@ notifications: project: MWPW # Mandatory filters: include: - risk_rating: R5 + risk_rating: R3 exclude: files: - acrobat/blocks/acom-widget/acom-widget.js @@ -17,13 +17,11 @@ notifications: fields: assignee: name: joaquinrivero - customfield_11800: MWPW-140779 #epic link + customfield_11800: MWPW-164516 #epic link customfield_12900: value: Slytherin watchers: - casalino - - jmichnow - - mauchley - tsay labels: - "OriginatingProcess=Kodiak" diff --git a/acrobat/blocks/rnr/rnr.js b/acrobat/blocks/rnr/rnr.js index 36964089..d0a4427e 100644 --- a/acrobat/blocks/rnr/rnr.js +++ b/acrobat/blocks/rnr/rnr.js @@ -498,7 +498,7 @@ export default async function init(element) { window.lana?.log('Verb not configured for the rnr widget'); } preloadIcons(); - await loadPlaceholders(); + await loadPlaceholders('rnr'); await loadRnrData(); initControls(element); } diff --git a/acrobat/blocks/verb-widget/verb-widget.css b/acrobat/blocks/verb-widget/verb-widget.css index 5f86c16f..6b7721bc 100644 --- a/acrobat/blocks/verb-widget/verb-widget.css +++ b/acrobat/blocks/verb-widget/verb-widget.css @@ -62,6 +62,7 @@ display: flex; margin: unset; margin-right: 15px; + margin-left: 18px; } .verb-footer { @@ -118,7 +119,7 @@ flex-direction: column; justify-content: center; border-radius: 20px; - border: 3px solid #d5d5d5; + border: 3px dashed #d5d5d5; position: relative; background-color: #fff; min-height: 560px; @@ -242,7 +243,7 @@ .verb-errorIcon::after { content: ''; - background-image: url(/acrobat/img/icons/ui/alert.svg); + background-image: url('/acrobat/img/icons/ui/alert.svg'); background-repeat: no-repeat; display: flex; align-items: center; @@ -362,8 +363,6 @@ flex: 1 1 55%; flex-direction: row; padding: 48px; - - /* cursor: pointer; */ } } @@ -398,24 +397,117 @@ border-radius: 10px; } -@media screen and (max-width: 768px) { +@media screen and (min-width: 768px) { + :root { + --verb-widget-padding: 24px; + --verb-wrapper-padding-y: 57px; + --verb-wrapper-padding-x: 39px; + --verb-wrapper-border-width: 3px; + } + + .verb-widget { + padding: var(--verb-widget-padding); + min-height: auto; + } + + .verb-wrapper.tablet { + width: unset; + max-width: unset; + padding: var(--verb-wrapper-padding-y) var(--verb-wrapper-padding-x); + border: var(--verb-wrapper-border-width) dashed #d5d5d5; + border-radius: 8px; + justify-content: flex-start; + min-height: auto; + } + + .tablet .verb-container { + padding: unset; + } + + .tablet .verb-title { + font-size: 27px; + } + + .tablet .verb-row { + gap: 38px; + } + + .tablet .verb-col { + margin: 10px; + margin-bottom: 0; + } + + .tablet .verb-col.right { + margin: 0; + } + + .tablet .verb-heading { + font-size: 36px; + line-height: 45px; + } + + .tablet .verb-copy { + font-size: 18px; + line-height: 27px; + margin-bottom: 32px; + } + + .tablet .verb-cta { + cursor: pointer; + font-size: 23px; + line-height: 29px; + padding: 11px 25px; + } +} +@media screen and (min-width: 1000px) and (max-width: 1024px) { + :root { + --verb-widget-padding: 24px; + --verb-wrapper-padding-y: 57px; + --verb-wrapper-padding-x: 23px; + --verb-wrapper-border-width: 3px; + } + + .verb-widget { + padding: var(--verb-widget-padding); + min-height: auto; + } + + .verb-wrapper.tablet { + width: unset; + max-width: unset; + padding: var(--verb-wrapper-padding-y) var(--verb-wrapper-padding-x); + border: var(--verb-wrapper-border-width) dashed #d5d5d5; + border-radius: 8px; + justify-content: flex-start; + min-height: auto; + } +} + +@media screen and (max-width: 767px) { + :root { + --verb-widget-padding: 16px; + --verb-wrapper-padding-y: 60px; + --verb-wrapper-padding-x: 29px; + --verb-wrapper-border-width: 3px; + } + .verb-widget { background-image: linear-gradient(180deg, #EB1000 0%, #F79B94 50%, #FFF 70%); - padding: 16px; + padding: var(--verb-widget-padding); } .verb-wrapper { width: unset; max-width: unset; - padding: 24px 29px; - border: 3px dashed #d5d5d5; + padding: var(--verb-wrapper-padding-y) var(--verb-wrapper-padding-x); + border: var(--verb-wrapper-border-width) dashed #d5d5d5; border-radius: 8px; - min-height: calc(100vh - (104px + 32px + 115px + 47px + 7px)); + min-height: calc(100vh - (104px + 115px + ((var(--verb-widget-padding) + var(--verb-wrapper-padding-y) + var(--verb-wrapper-border-width)) * 2)) ); justify-content: flex-start; } .verb-wrapper.mobile-app { - min-height: calc(100vh - (104px + 32px + 0px + 47px + 7px)) + min-height: calc(100vh - (104px + ((16px + 3px) * 2) + 36px + 84px)); } .verb-row { diff --git a/acrobat/blocks/verb-widget/verb-widget.js b/acrobat/blocks/verb-widget/verb-widget.js index 23d766d1..a9c5875a 100644 --- a/acrobat/blocks/verb-widget/verb-widget.js +++ b/acrobat/blocks/verb-widget/verb-widget.js @@ -1,6 +1,6 @@ import LIMITS from './limits.js'; import { setLibs, getEnv, isOldBrowser } from '../../scripts/utils.js'; -import verbAnalytics from '../../scripts/alloy/verb-widget.js'; +import verbAnalytics, { reviewAnalytics } from '../../scripts/alloy/verb-widget.js'; import createSvgElement from './icons.js'; const miloLibs = setLibs('/libs'); @@ -82,6 +82,38 @@ function handleExit(event) { event.returnValue = true; } +function isMobileDevice() { + const ua = navigator.userAgent.toLowerCase(); + const isMobileUA = /android|iphone|ipod|blackberry|windows phone/i.test(ua); + return isMobileUA; +} + +function isTabletDevice() { + const ua = navigator.userAgent.toLowerCase(); + const isIPadOS = navigator.userAgent.includes('Mac') && 'ontouchend' in document && !/iphone|ipod/i.test(ua); + const isTabletUA = /ipad|android(?!.*mobile)/i.test(ua); + const largeTouchDevice = (navigator.maxTouchPoints || navigator.msMaxTouchPoints) > 1 + && window.innerWidth >= 768; + return isIPadOS || isTabletUA || largeTouchDevice; +} + +function getStoreType() { + const { ua } = window.browser; + if (/android/i.test(ua)) { + return 'google'; + } + if (/iphone|ipod/i.test(ua)) { + return 'apple'; + } + if (navigator.userAgent.includes('Mac') && 'ontouchend' in document && !/iphone|ipod/i.test(navigator.userAgent)) { + return 'apple'; + } + if (/ipad/i.test(ua)) { + return 'apple'; + } + return 'desktop'; +} + async function showUpSell(verb, element) { const headline = window.mph[`verb-widget-upsell-headline-${verb}`] || window.mph['verb-widget-upsell-headline']; const headlineNopayment = window.mph['verb-widget-upsell-headline-nopayment']; @@ -133,11 +165,10 @@ export default async function init(element) { const children = element.querySelectorAll(':scope > div'); const VERB = element.classList[1]; const widgetHeading = createTag('h1', { class: 'verb-heading' }, children[0].textContent); + const storeType = getStoreType(); let mobileLink = null; - if (/iPad|iPhone|iPod/.test(window.browser?.ua) && !window.MSStream) { - mobileLink = window.mph[`verb-widget-${VERB}-apple`]; - } else if (/android/i.test(window.browser?.ua)) { - mobileLink = window.mph[`verb-widget-${VERB}-google`]; + if (storeType !== 'desktop') { + mobileLink = window.mph[`verb-widget-${VERB}-${storeType}`]; } children.forEach((child) => { @@ -215,29 +246,45 @@ export default async function init(element) { widgetRow.append(widgetLeft, widgetRight); widgetHeader.append(widgetIcon, widgetTitle); errorState.append(errorIcon, errorStateText, errorCloseBtn); + const isMobile = isMobileDevice(); + const isTablet = isTabletDevice(); + + if (isMobile) { + widget.classList.add('mobile'); + } else if (isTablet) { + widget.classList.add('tablet'); + } + if (mobileLink && LIMITS[VERB].mobileApp) { widget.classList.add('mobile-app'); widgetLeft.append(widgetHeader, widgetHeading, widgetMobCopy, errorState, widgetMobileButton); element.append(widget); } else { - widgetLeft.append(widgetHeader, widgetHeading, widgetCopy, errorState, widgetButton, button); + if (isMobile || isTablet) { + widgetLeft.append( + widgetHeader, + widgetHeading, + widgetMobCopy, + errorState, + widgetButton, + button, + ); + } else { + widgetLeft.append( + widgetHeader, + widgetHeading, + widgetCopy, + errorState, + widgetButton, + button, + ); + } legalTwo.innerHTML = legalTwo.outerHTML.replace(window.mph['verb-widget-terms-of-use'], ` ${window.mph['verb-widget-terms-of-use']}`); legalTwo.innerHTML = legalTwo.outerHTML.replace(window.mph['verb-widget-privacy-policy'], ` ${window.mph['verb-widget-privacy-policy']}`); legalWrapper.append(legal, legalTwo); footer.append(iconSecurity, legalWrapper, infoIcon); element.append(widget, footer); - if (window.browser?.isMobile) { - widgetCopy.after(widgetMobCopy); - widgetCopy.remove(); - const infoMobIconSvg = createSvgElement('INFO_ICON_MOBILE'); - infoIconSvg.remove(); - infoIcon.append(infoMobIconSvg); - const verbMobImageSvg = createSvgElement(`${VERB}-mobile`); - if (verbMobImageSvg) { - verbImageSvg.remove(); - verbMobImageSvg.classList.add('icon-verb-image'); - widgetImage.appendChild(verbMobImageSvg); - } + if (isMobile && !isTablet) { widgetImage.after(widgetImage); iconSecurity.remove(iconSecurity); footer.prepend(infoIcon); @@ -271,6 +318,7 @@ export default async function init(element) { // Analytics verbAnalytics('landing:shown', VERB); + reviewAnalytics(VERB); window.prefetchInitiated = false; diff --git a/acrobat/scripts/alloy/verb-widget.js b/acrobat/scripts/alloy/verb-widget.js index d8342fa6..19bac3d9 100644 --- a/acrobat/scripts/alloy/verb-widget.js +++ b/acrobat/scripts/alloy/verb-widget.js @@ -1,3 +1,5 @@ +import frictionless from '../frictionless.js'; + const params = new Proxy( // eslint-disable-next-line compat/compat new URLSearchParams(window.location.search), @@ -23,15 +25,16 @@ if (params.dropzone2) { appTags.push('dropzone2'); } -export default function init(eventName, verb, metaData, documentUnloading = true) { - function ensureSatelliteReady(callback) { - // eslint-disable-next-line no-underscore-dangle - if (window._satellite && typeof window._satellite.track === 'function') { - callback(); - } else { - setTimeout(() => ensureSatelliteReady(callback), 200); - } +function ensureSatelliteReady(callback) { + // eslint-disable-next-line no-underscore-dangle + if (window._satellite?.track instanceof Function) { + callback(); + } else { + setTimeout(() => ensureSatelliteReady(callback), 200); } +} + +export default function init(eventName, verb, metaData, documentUnloading = true) { function getSessionID() { const aToken = window.adobeIMS.getAccessToken(); const arrayToken = aToken?.token.split('.'); @@ -135,3 +138,9 @@ export default function init(eventName, verb, metaData, documentUnloading = true window._satellite.track('event', event); }); } + +export function reviewAnalytics(verb) { + ensureSatelliteReady(() => { + frictionless(verb); + }); +} diff --git a/acrobat/scripts/contentSecurityPolicy/csp.js b/acrobat/scripts/contentSecurityPolicy/csp.js index 8d45df6a..30712a67 100644 --- a/acrobat/scripts/contentSecurityPolicy/csp.js +++ b/acrobat/scripts/contentSecurityPolicy/csp.js @@ -50,6 +50,6 @@ export default async function ContentSecurityPolicy() { // Content Security Policy Logging window.cspErrors = []; document.addEventListener('securitypolicyviolation', (e) => { - window.cspErrors.push(`${e.violatedDirective} violation ¶ Refused to load content from ${e.blockedURI}`); + window.cspErrors.push(`${e.violatedDirective} violation ¶ Refused to load content from ${e.blockedURI}, Script location: ${e.sourceFile} Line: ${e.lineNumber} Column: ${e.columnNumber}`); }); } diff --git a/acrobat/scripts/contentSecurityPolicy/dev.js b/acrobat/scripts/contentSecurityPolicy/dev.js index c21838bc..452f5db7 100644 --- a/acrobat/scripts/contentSecurityPolicy/dev.js +++ b/acrobat/scripts/contentSecurityPolicy/dev.js @@ -121,6 +121,7 @@ const imgSrc = [ 'stage.adobeccstatic.com', '*.clarity.ms', '*.enterprise.adobe.com', + 'www.stage.adobe.com', '*.services.adobe.com', 'alb.reddit.com/rp.gif', 'bat.bing.com/action/', @@ -168,6 +169,7 @@ const scriptSrc = [ '\'self\'', '\'unsafe-inline\'', '\'unsafe-eval\'', + 'CCb6zi09JRQ6b1z1', '*.adobe.com', '*.clarity.ms', 'accounts.google.com/gsi/client', diff --git a/acrobat/scripts/contentSecurityPolicy/prod.js b/acrobat/scripts/contentSecurityPolicy/prod.js index 5e47978f..dc0a74ac 100644 --- a/acrobat/scripts/contentSecurityPolicy/prod.js +++ b/acrobat/scripts/contentSecurityPolicy/prod.js @@ -8,6 +8,7 @@ const connectSrc = [ '\'self\'', 'blob:', '14257-chimera.adobeioruntime.net', + 'www.adobe.com', '*.adobe.com', 'prod.adobeccstatic.com', '*.clicktale.net/', @@ -114,6 +115,7 @@ const frameSrc = [ 'ui.messaging.adobe.com/', 'acrobatservices.adobe.com', 'auth-light.identity.adobe.com', + 'pixel.everesttech.net', ';', ]; @@ -181,6 +183,7 @@ const scriptSrc = [ '\'self\'', '\'unsafe-inline\'', '\'unsafe-eval\'', + 'www.adobe.com', '*.adobe.com', '*.clarity.ms', 'accounts.google.com/gsi/client', diff --git a/acrobat/scripts/contentSecurityPolicy/stage.js b/acrobat/scripts/contentSecurityPolicy/stage.js index 981227bd..dda83a75 100644 --- a/acrobat/scripts/contentSecurityPolicy/stage.js +++ b/acrobat/scripts/contentSecurityPolicy/stage.js @@ -27,6 +27,7 @@ const connectSrc = [ 'adobeioruntime.net', 'adobesearch-stage.adobe.io', 'analytics.tiktok.com', + 'api.company-target.com', 'api.company-target.com/api/v2/', 'api.iperceptions.com', 'bat.bing.com/', @@ -55,6 +56,7 @@ const connectSrc = [ '*.aem.live', 'cdn.linkedin.oribi.io', '*.akstat.io/', + 'www.facebook.com', 'facebook.com', 'px.ads.linkedin.com', 'tr6.snapchat.com', @@ -180,6 +182,7 @@ const scriptSrc = [ '\'self\'', '\'unsafe-inline\'', '\'unsafe-eval\'', + 'www.stage.adobe.com', '*.adobe.com', '*.clarity.ms', 'accounts.google.com/gsi/client', @@ -234,7 +237,7 @@ const scriptSrc = [ 'tr.snapchat.com/', 'universal.iperceptions.com', 'use.typekit.net', - 'www.everestjs.net/static/le/', + 'www.everestjs.net', 'www.facebook.com', 'www.google.com', 'www.googletagmanager.com', diff --git a/acrobat/scripts/utils.js b/acrobat/scripts/utils.js index 6676304e..deb5eb0f 100644 --- a/acrobat/scripts/utils.js +++ b/acrobat/scripts/utils.js @@ -34,42 +34,57 @@ export const [setLibs, getLibs] = (() => { return `https://${branch}${branch.includes('--') ? '' : '--milo--adobecom'}.${env}.live/libs`; })(); return libs; - }, () => libs, + }, + () => libs, ]; })(); export function getEnv() { const { hostname } = window.location; if (['www.adobe.com', 'sign.ing', 'edit.ing'].includes(hostname)) return 'prod'; - if ([ - 'stage--dc--adobecom.hlx.page', 'main--dc--adobecom.hlx.page', - 'stage--dc--adobecom.hlx.live', 'main--dc--adobecom.hlx.live', - 'stage--dc--adobecom.aem.page', 'main--dc--adobecom.aem.page', - 'stage--dc--adobecom.aem.live', 'main--dc--adobecom.aem.live', - 'www.stage.adobe.com', - ].includes(hostname)) return 'stage'; + if ( + [ + 'stage--dc--adobecom.hlx.page', + 'main--dc--adobecom.hlx.page', + 'stage--dc--adobecom.hlx.live', + 'main--dc--adobecom.hlx.live', + 'stage--dc--adobecom.aem.page', + 'main--dc--adobecom.aem.page', + 'stage--dc--adobecom.aem.live', + 'main--dc--adobecom.aem.live', + 'www.stage.adobe.com', + ].includes(hostname) + ) return 'stage'; return 'dev'; } export function isOldBrowser() { const { name, version } = window?.browser || {}; return ( - name === 'Internet Explorer' || (name === 'Microsoft Edge' && (!version || version.split('.')[0] < 86)) || (name === 'Safari' && version.split('.')[0] < 14) + name === 'Internet Explorer' + || (name === 'Microsoft Edge' && (!version || version.split('.')[0] < 86)) + || (name === 'Safari' && version.split('.')[0] < 14) ); } -export async function loadPlaceholders() { +/** + * Loads placeholders, if SOME were not already loaded + * @param {string | undefined} prefix Optional prefix for loading specific placeholders + */ +export async function loadPlaceholders(prefix) { const miloLibs = setLibs('/libs'); const { getConfig } = await import(`${miloLibs}/utils/utils.js`); const config = getConfig(); - if (!Object.keys(window.mph || {}).length) { + const mphKeys = Object.keys(window.mph || {}).filter((key) => !prefix || key.startsWith(prefix)); + if (mphKeys.length === 0) { const placeholdersPath = `${config.locale.contentRoot}/placeholders.json`; try { const response = await fetch(placeholdersPath); if (response.ok) { const placeholderData = await response.json(); placeholderData.data.forEach(({ key, value }) => { + if (prefix && !key.startsWith(prefix)) return; window.mph[key] = value.replace(/\u00A0/g, ' '); }); } diff --git a/test/scripts/contentSecurityPolicy/csp.test.js b/test/scripts/contentSecurityPolicy/csp.test.js index 4271c364..3ea8d79f 100644 --- a/test/scripts/contentSecurityPolicy/csp.test.js +++ b/test/scripts/contentSecurityPolicy/csp.test.js @@ -1,9 +1,6 @@ import { expect } from '@esm-bundle/chai'; -import * as sinon from 'sinon'; -const { default: ContentSecurityPolicy } = await import( - '../../../acrobat/scripts/contentSecurityPolicy/csp' -); +const { default: ContentSecurityPolicy } = await import('../../../acrobat/scripts/contentSecurityPolicy/csp.js'); describe('contentSecurityPolicy csp', () => { it('handles securitypolicyviolation event', async () => { @@ -13,7 +10,7 @@ describe('contentSecurityPolicy csp', () => { event.violatedDirective = 'test'; document.dispatchEvent(event); expect(window.cspErrors[0]).to.eql( - `${event.violatedDirective} violation ¶ Refused to load content from ${event.blockedURI}` + `${event.violatedDirective} violation ¶ Refused to load content from ${event.blockedURI}, Script location: ${event.sourceFile} Line: ${event.lineNumber} Column: ${event.columnNumber}`, ); }); });