Skip to content

Commit

Permalink
feat: native modal communication (#851)
Browse files Browse the repository at this point in the history
  • Loading branch information
Seavenly authored Aug 14, 2023
1 parent 7999138 commit 1d30e05
Show file tree
Hide file tree
Showing 4 changed files with 513 additions and 17 deletions.
2 changes: 1 addition & 1 deletion scripts/semantic-release/assets.sh
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ if [ ! -z "$tag" ]; then
version=$version-$(echo $tag | sed "s/_/-/g" | sed -E "s/-([0-9]+)$/.\1/")
# Check if the CDN tag has already been used before spending time on the webpack build
tagStatus=$(web status $tag 2>&1)
if [[ $tagStatus =~ "✔ Complete" ]]; then
if [[ $tagStatus =~ "staged" ]]; then
printf "Stage tag already exists and must be unique ($tag)\n\n"
exit 1
fi
Expand Down
4 changes: 3 additions & 1 deletion src/components/modal/v2/lib/utils.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
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';

export const getContent = memoize(
Expand Down Expand Up @@ -64,7 +65,8 @@ export const getContent = memoize(
* @returns boolean
*/
export const isLander = __MESSAGES__.__TARGET__ === 'LANDER';
export const isIframe = window.top !== window;
const { userAgent } = window.navigator;
export const isIframe = window.top !== window || isIosWebview(userAgent) || isAndroidWebview(userAgent);

export function setupTabTrap() {
const focusableElementsString =
Expand Down
141 changes: 126 additions & 15 deletions src/components/modal/v2/lib/zoid-polyfill.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,11 @@
/* global Android */
import { isAndroidWebview, isIosWebview, getPerformance } from '@krakenjs/belter/src';
import { logger } from '../../../../utils';

export default function polyfillZoid() {
const props = window.location.search
.slice(1)
.split('&')
.reduce((acc, query) => {
const [key, value] = query.split('=');

if (value) {
const propName = key.replace(/_([a-z])/g, (_, p1) => p1.toUpperCase());

acc[propName] = value;
}

return acc;
}, {});
const IOS_INTERFACE_NAME = 'paypalMessageModalCallbackHandler';
const ANDROID_INTERFACE_NAME = 'paypalMessageModalCallbackHandler';

const setupBrowser = props => {
window.xprops = {
// We will never recieve new props via this integration style
onProps: () => {},
Expand Down Expand Up @@ -105,4 +95,125 @@ export default function polyfillZoid() {
// Specified props via query params
...props
};
};

const setupWebview = props => {
const postMessage = (() => {
if (window.webkit?.messageHandlers?.[IOS_INTERFACE_NAME]) {
return window.webkit.messageHandlers[IOS_INTERFACE_NAME].postMessage.bind(
window.webkit.messageHandlers[IOS_INTERFACE_NAME]
);
}

// `Android` is not on the `window` object but rather an adjacent top level object
if (typeof Android !== 'undefined') {
return Android[ANDROID_INTERFACE_NAME].bind(Android);
}

// This scenario should only ever occur when developing locally
return payload => console.warn('postMessage:', JSON.parse(payload));

Check warning on line 114 in src/components/modal/v2/lib/zoid-polyfill.js

View workflow job for this annotation

GitHub Actions / Lint and Unit Tests

Unexpected console statement

Check warning on line 114 in src/components/modal/v2/lib/zoid-polyfill.js

View workflow job for this annotation

GitHub Actions / Lint and Unit Tests

Unexpected console statement
})();

const propListeners = new Set();
const sendCallbackMessage = (name, ...args) => postMessage(JSON.stringify({ name, args }));
// Functions called from the native app
window.actions = {
updateProps: newProps => {
if (newProps && typeof newProps === 'object') {
Array.from(propListeners.values()).forEach(listener => {
listener({ ...window.xprops, ...newProps });
});

Object.assign(window.xprops, newProps);
}
}
};
window.xprops = {
onProps: listener => propListeners.add(listener),

onReady: ({ meta }) => {
const { trackingDetails } = meta;
const timing = getPerformance()?.getEntriesByType('navigation')[0];

sendCallbackMessage('onReady', {
__shared__: {
// Analytic Details
fdata: trackingDetails.fdata,
experimentation_experience_ids: trackingDetails.experimentation_experience_ids,
experimentation_treatment_ids: trackingDetails.experimentation_treatment_ids,
credit_product_identifiers: trackingDetails.credit_product_identifiers,
offer_country_code: trackingDetails.offer_country_code,
merchant_country_code: trackingDetails.merchant_country_code,
views: trackingDetails.views,
qualified_products: trackingDetails.qualified_products,
debug_id: trackingDetails.debug_id
},
event_type: 'modal_render',
request_duration: timing && timing.responseEnd - timing.requestStart,
render_duration: timing && timing.loadEventEnd - timing.responseEnd
});
},

onClick: ({ linkName, src = linkName }) => {
sendCallbackMessage('onClick', {
event_type: 'modal_click',
link_name: linkName,
link_src: src
});
},

onCalculate: ({ value }) => {
sendCallbackMessage('onCalculate', {
event_type: 'modal_click',
link_name: 'Calculator',
link_src: 'Calculator',
data: value
});
},

onShow: () => {
sendCallbackMessage('onShow', {
event_type: 'modal_open',
link_name: 'Show',
link_src: 'Show'
});
},

onClose: ({ linkName, src = linkName }) => {
sendCallbackMessage('onClose', {
event_type: 'modal_close',
link_name: linkName,
link_src: src
});
},
// Overridable defaults
integrationType: __MESSAGES__.__TARGET__,
// Specified props via query params
...props
};
};

export default function polyfillZoid() {
const props = window.location.search
.slice(1)
.split('&')
.reduce((acc, query) => {
const [key, value] = query.split('=');

if (value) {
const propName = key.replace(/_([a-z])/g, (_, p1) => p1.toUpperCase());

acc[propName] = value;
}

return acc;
}, {});

const { userAgent } = window.navigator;

if (isIosWebview(userAgent) || isAndroidWebview(userAgent)) {
setupWebview(props);
} else {
setupBrowser(props);
}
}
Loading

0 comments on commit 1d30e05

Please sign in to comment.