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

feat: adds prop update listening to modal browser zoid polyfill #1161

Open
wants to merge 7 commits into
base: develop
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
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()
},
Fixed Show fixed Hide fixed
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