Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 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
57 changes: 57 additions & 0 deletions src/components/modal/v2/lib/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import objectEntries from 'core-js-pure/stable/object/entries';
import arrayFrom from 'core-js-pure/stable/array/from';
import { isIosWebview, isAndroidWebview } from '@krakenjs/belter/src';
import { request, memoize, ppDebug } from '../../../../utils';
import validate from '../../../../library/zoid/message/validation';

export const getContent = memoize(
({
Expand Down Expand Up @@ -112,3 +113,59 @@ export function formatDateByCountry(country) {
}
return currentDate.toLocaleDateString('en-GB', options);
}

export function createUUID() {
// crypto.randomUUID() is only available in HTTPS secure environments and modern browsers
if (typeof crypto !== 'undefined' && crypto && crypto.randomUUID instanceof Function) {
return crypto.randomUUID();
}

const validChars = '0123456789abcdefghijklmnopqrstuvwxyz';
const stringLength = 32;
let randomId = '';
for (let index = 0; index < stringLength; index++) {
const randomIndex = Math.floor(Math.random() * validChars.length);
randomId += validChars.charAt(randomIndex);
}
return randomId;
}

export function validateProps(updatedProps) {
const validatedProps = {};
Object.entries(updatedProps).forEach(entry => {
const [k, v] = entry;
if (k === 'offerTypes') {
validatedProps.offer = validate.offer({ props: { offer: v } });
} else {
validatedProps[k] = validate[k]({ props: { [k]: v } });
}
});
return validatedProps;
}

export function sendEventAck(eventId, trustedOrigin) {
// skip this step if running in test env because jest's target windows don't support postMessage
if (process.env.NODE_ENV === 'test') {
return;
}

// target window selection depends on if checkout window is in popup or modal iframe
let targetWindow;
const popupCheck = window.parent === window;
if (popupCheck) {
targetWindow = window.opener;
} else {
targetWindow = window.parent;
}

targetWindow.postMessage(
{
// PostMessenger stops reposting an event when it receives an eventName which matches the id in the message it sent and type 'ack'
eventName: eventId,
type: 'ack',
eventPayload: { ok: true },
id: createUUID()
},
trustedOrigin
);
}
43 changes: 35 additions & 8 deletions src/components/modal/v2/lib/zoid-polyfill.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,46 @@
/* global Android */
import { isAndroidWebview, isIosWebview, getPerformance } from '@krakenjs/belter/src';
import { getOrCreateDeviceID, logger } from '../../../../utils';
import { isIframe } from './utils';
import { isIframe, validateProps, sendEventAck } from './utils';

const IOS_INTERFACE_NAME = 'paypalMessageModalCallbackHandler';
const ANDROID_INTERFACE_NAME = 'paypalMessageModalCallbackHandler';

function listenAndAssignProps(newProps, propListeners) {
Array.from(propListeners.values()).forEach(listener => {
listener({ ...window.xprops, ...newProps });
});
Object.assign(window.xprops, newProps);
}

export function validateAndUpdateBrowserProps(initialProps, propListeners, updatedPropsEvent) {
const {
origin: eventOrigin,
data: { eventName, id, eventPayload: newProps }
} = updatedPropsEvent;
const clientOrigin = decodeURIComponent(initialProps.origin);

if (eventOrigin === clientOrigin && eventName === 'PROPS_UPDATE' && newProps && typeof newProps === 'object') {
// send event ack so PostMessenger will stop reposting event
sendEventAck(id, clientOrigin);
const validProps = validateProps(newProps);
listenAndAssignProps(validProps, propListeners);
}
}

const setupBrowser = props => {
const propListeners = new Set();

window.addEventListener(
'message',
event => {
validateAndUpdateBrowserProps(props, propListeners, event);
},
false
);

window.xprops = {
// We will never recieve new props via this integration style
onProps: () => {},
onProps: listener => propListeners.add(listener),
// TODO: Verify these callbacks are instrumented correctly
onReady: ({ products, meta }) => {
const { clientId, payerId, merchantId, offer, partnerAttributionId } = props;
Expand Down Expand Up @@ -126,11 +157,7 @@ const setupWebview = props => {
window.actions = {
updateProps: newProps => {
if (newProps && typeof newProps === 'object') {
Array.from(propListeners.values()).forEach(listener => {
listener({ ...window.xprops, ...newProps });
});

Object.assign(window.xprops, newProps);
listenAndAssignProps(newProps, propListeners);
}
}
};
Expand Down
Loading
Loading