Skip to content

Commit

Permalink
feat: use postMessage between iFrame and hosting website for redempti…
Browse files Browse the repository at this point in the history
…onInfo/redemptionSubmitted/redemptionConfirmed
  • Loading branch information
levalleux-ludo committed Nov 16, 2023
1 parent 1801efa commit d947eb2
Show file tree
Hide file tree
Showing 3 changed files with 257 additions and 9 deletions.
82 changes: 80 additions & 2 deletions public/example.html
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ <h2>Redemption Widget</h2>
<input type="checkbox" id="input-redeem-show-overview" checked onchange="updateRedeemButton('data-show-redemption-overview', this.checked)">
</td>
<td>
<button onclick="clearRedeemInputs(); setValue('input-redeem-show-overview', false, 'checked')" >Example</button>
<button onclick="setValue('input-redeem-show-overview', !getValue('input-redeem-show-overview', 'checked'), 'checked')" >Example</button>
</td>
</tr>
<tr>
Expand Down Expand Up @@ -109,14 +109,23 @@ <h2>Redemption Widget</h2>
<button onclick="clearRedeemInputs(); setValue('input-redeem-exchange-id', '80'); setValue('select-redeem-widget-action', 'CANCEL_FORM')" >Example</button>
</td>
</tr>
<tr>
<td>Send DeliveryInfo Through XMTP</td>
<td>
<input type="checkbox" id="input-send-delivery-info-XMTP" checked onchange="updateRedeemButton('data-send-delivery-info-XMTP', this.checked)">
</td>
<td>
<button onclick="setValue('input-send-delivery-info-XMTP', !getValue('input-send-delivery-info-XMTP', 'checked'), 'checked')" >Example</button>
</td>
</tr>
<tr>
<td>DeliveryInfo</td>
<td>
<textarea id="input-delivery-info" onchange="updateRedeemButton('data-delivery-info', encodeURI(this.value))" rows="10" cols="30">
</textarea>
</td>
<td>
<button onclick="clearRedeemInputs(); setValue('input-redeem-show-overview', false, 'checked'); setValue('input-post-delivery-info-url', 'http://localhost:3666/deliveryInfo'); setValue('input-redeem-exchange-id', '133'); setValue('select-redeem-widget-action', 'CONFIRM_REDEEM'); setValue('input-delivery-info', fullDecodeUri('%7B%22name%22:%20%22TOTO%22,%20%22streetNameAndNumber%22:%20%221%20grand%20place%22,%20%22city%22:%20%22LILLE%22,%20%22state%22:%20%22NORD%22,%20%22zip%22:%20%2259000%22,%20%22country%22:%20%22FR%22,%20%22email%22:%20%[email protected]%22,%20%22phone%22:%20%22%2B33123456789%22%7D'))" >Example</button>
<button onclick="setValue('input-redeem-show-overview', false, 'checked'); setValue('input-post-delivery-info-url', 'http://localhost:3666/deliveryInfo'); setValue('input-redeem-exchange-id', '133'); setValue('select-redeem-widget-action', 'CONFIRM_REDEEM'); setValue('input-delivery-info', fullDecodeUri('%7B%22name%22:%20%22TOTO%22,%20%22streetNameAndNumber%22:%20%221%20grand%20place%22,%20%22city%22:%20%22LILLE%22,%20%22state%22:%20%22NORD%22,%20%22zip%22:%20%2259000%22,%20%22country%22:%20%22FR%22,%20%22email%22:%20%[email protected]%22,%20%22phone%22:%20%22%2B33123456789%22%7D'))" >Example</button>
</td>
</tr>
<tr>
Expand All @@ -138,6 +147,24 @@ <h2>Redemption Widget</h2>
<button onclick="setValue('input-post-delivery-info-url', 'http://localhost:3666/deliveryInfo'); setValue('input-post-delivery-info-header', fullDecodeUri('%7B%0A%20%20%22authorization%22:%20%22Bearer%20eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9%22,%0A%20%20%22another-header%22:%20%22*****%22%0A%7D'))" >Example</button>
</td>
</tr>
<tr>
<td>TargetOrigin</td>
<td>
<input type="url" id="input-target-origin" onchange="updateRedeemButton('data-target-origin', encodeURI(this.value))" size="30">
</td>
<td>
<button onclick="setValue('input-target-origin', window.location.origin)" >Example</button>
</td>
</tr>
<tr>
<td>Should Wait For Response</td>
<td>
<input type="checkbox" id="input-wait-for-response" autocomplete="off" onchange="updateRedeemButton('data-wait-for-response', this.checked)">
</td>
<td>
<button onclick="setValue('input-wait-for-response', !getValue('input-wait-for-response', 'checked'), 'checked')" >Example</button>
</td>
</tr>
<tr>
<td>PostRedemptionSubmittedURL</td>
<td>
Expand Down Expand Up @@ -202,12 +229,19 @@ <h2>Redemption Widget</h2>
element[attribute] = value
element.onchange()
}
function getValue(elementId, attribute = "value") {
let element = document.getElementById(elementId)
return element[attribute]
}
function clearRedeemInputs() {
setValue('input-redeem-exchange-id', '')
setValue('input-redeem-seller-ids', '')
setValue('select-redeem-widget-action', 'SELECT_EXCHANGE')
setValue('input-redeem-show-overview', true, 'checked')
setValue('select-redeem-exchange-state', 'Committed')
setValue('input-send-delivery-info-XMTP', true, 'checked')
setValue('input-target-origin', '')
setValue('input-wait-for-response', true, 'checked')
setValue('input-post-delivery-info-url', '')
setValue('input-post-delivery-info-header', '')
setValue('input-post-redemption-submitted-url', '')
Expand All @@ -227,6 +261,50 @@ <h2>Redemption Widget</h2>
}
return s2
}
window.addEventListener("message", (event) => {
if (event.data.type === constants.deliveryInfoMessage) {
console.log(
`Received message '${event.data.type}' from '${event.origin}'. Content: '${JSON.stringify(event.data.message)}'`
);
if (getValue('input-wait-for-response', 'checked')) {
// wait for a bit and send a response to the iFrame (only relevant when )
setTimeout(() => {
const el = getIFrame();
if (el) {
const target = event.origin;
console.log(
`Post response message '${constants.deliveryInfoMessageResponse}' to the iFrame '${target}'`
);
// https://stackoverflow.com/questions/40991114/issue-communication-with-postmessage-from-parent-to-child-iframe
el.contentWindow.postMessage(
{
type: constants.deliveryInfoMessageResponse,
message: {
accepted: true,
reason: "",
resume: true
}
},
target
);
} else {
console.error("Unable to retrieve the iFrame");
}
}, 5000);
}
}
if (event.data.type === constants.redemptionSubmittedMessage) {
console.log(
`Received message '${event.data.type}' from '${event.origin}'. Content: '${JSON.stringify(event.data.message)}'`
);
}
if (event.data.type === constants.redemptionConfirmedMessage) {
console.log(
`Received message '${event.data.type}' from '${event.origin}'. Content: '${JSON.stringify(event.data.message)}'`
);
}
});

</script>
</body>
</html>
32 changes: 29 additions & 3 deletions public/scripts/boson-widgets.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,20 @@ const constants = {
deliveryInfoTag: "data-delivery-info",
postDeliveryInfoUrlTag: "data-post-delivery-info-url",
postDeliveryInfoHeadersTag: "data-post-delivery-info-headers",
dataTargetOrigin: "data-target-origin",
dataWaitForResponse: "data-wait-for-response",
dataSendDeliveryInfoXMTP: "data-send-delivery-info-XMTP",
postRedemptionSubmittedUrlTag: "data-post-redemption-submitted-url",
postRedemptionSubmittedHeadersTag: "data-post-redemption-submitted-headers",
postRedemptionConfirmedUrlTag: "data-post-redemption-confirmed-url",
postRedemptionConfirmedHeadersTag: "data-post-redemption-confirmed-headers",
accountTag: "data-account",
hideModalId: "boson-hide-modal",
hideModalMessage: "boson-close-iframe",
deliveryInfoMessage: "boson-delivery-info",
deliveryInfoMessageResponse: "boson-delivery-info-response",
redemptionSubmittedMessage: "boson-redemption-submitted",
redemptionConfirmedMessage: "boson-redemption-confirmed",
financeUrl: (widgetsHost) => `${widgetsHost}/#/finance`,
redeemUrl: (widgetsHost) => `${widgetsHost}/#/redeem`
};
Expand Down Expand Up @@ -81,8 +88,11 @@ const createIFrame = (src, onLoad) => {
bosonModal.onload = onLoad;
document.body.appendChild(bosonModal);
};
const getIFrame = () => {
return document.getElementById(constants.iFrameId);
};
const hideIFrame = () => {
const el = document.getElementById(constants.iFrameId);
const el = getIFrame();
if (el) {
el.remove();
}
Expand Down Expand Up @@ -152,6 +162,13 @@ function bosonWidgetReload() {
?.value;
const configId = showRedeemId.attributes[constants.configIdTag]?.value;
const account = showRedeemId.attributes[constants.accountTag]?.value;
const targetOrigin =
showRedeemId.attributes[constants.dataTargetOrigin]?.value;
const shouldWaitForResponse =
showRedeemId.attributes[constants.dataWaitForResponse]?.value;
const sendDeliveryInfoThroughXMTP =
showRedeemId.attributes[constants.dataSendDeliveryInfoXMTP]?.value;

bosonWidgetShowRedeem({
exchangeId,
sellerId,
Expand All @@ -167,7 +184,10 @@ function bosonWidgetReload() {
postRedemptionConfirmedUrl,
postRedemptionConfirmedHeaders,
configId,
account
account,
targetOrigin,
shouldWaitForResponse,
sendDeliveryInfoThroughXMTP
});
};
}
Expand Down Expand Up @@ -211,7 +231,13 @@ function bosonWidgetShowRedeem(args) {
value: args.postRedemptionConfirmedHeaders
},
{ tag: "configId", value: args.configId },
{ tag: "account", value: args.account }
{ tag: "account", value: args.account },
{ tag: "targetOrigin", value: args.targetOrigin },
{ tag: "shouldWaitForResponse", value: args.shouldWaitForResponse },
{
tag: "sendDeliveryInfoThroughXMTP",
value: args.sendDeliveryInfoThroughXMTP
}
]);
showLoading();
hideIFrame();
Expand Down
152 changes: 148 additions & 4 deletions src/components/widgets/redeem/Redeem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
RedemptionWidgetAction,
subgraph
} from "@bosonprotocol/react-kit";
import { DeliveryInfoCallbackResponse } from "@bosonprotocol/react-kit/dist/cjs/hooks/callbacks/useRedemptionCallbacks";
import { useSearchParams } from "react-router-dom";

import { CONFIG, getMetaTxConfig } from "../../../config";
Expand All @@ -12,10 +13,10 @@ export const redeemPath = "/redeem";
export function Redeem() {
const [searchParams] = useSearchParams();
const exchangeId = searchParams.get("exchangeId") || undefined;
const showRedemptionOverviewStr = searchParams.get("showRedemptionOverview");
const showRedemptionOverview = showRedemptionOverviewStr
? /^true$/i.test(showRedemptionOverviewStr)
: true; // default value
const showRedemptionOverview = extractBooleanParam(
searchParams.get("showRedemptionOverview"),
true
);
const widgetAction: RedemptionWidgetAction = checkWidgetAction(
searchParams.get("widgetAction") || undefined
);
Expand All @@ -36,6 +37,15 @@ export function Redeem() {
);
}
}
const sendDeliveryInfoThroughXMTP = extractBooleanParam(
searchParams.get("sendDeliveryInfoThroughXMTP"),
true
);
const targetOrigin = searchParams.get("targetOrigin") || undefined;
const shouldWaitForResponse = extractBooleanParam(
searchParams.get("shouldWaitForResponse"),
false
);

const postDeliveryInfoUrl =
searchParams.get("postDeliveryInfoUrl") || undefined;
Expand Down Expand Up @@ -96,9 +106,15 @@ export function Redeem() {
? [sellerId]
: undefined;

// In case the deliveryInfo shall be transferred between frontend windows, the targetOrigin
// the deliveryInfo message shall be posted to
// (see https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage#targetorigin)
// deliveryInfoTargetOrigin?: string;

return (
<RedemptionWidget
showRedemptionOverview={showRedemptionOverview}
sendDeliveryInfoThroughXMTP={sendDeliveryInfoThroughXMTP}
exchangeState={exchangeState}
exchangeId={exchangeId}
sellerIds={sellerIds}
Expand Down Expand Up @@ -133,6 +149,105 @@ export function Redeem() {
console.error(`Unable to post message ${e}`);
}
}}
deliveryInfoHandler={
targetOrigin
? async (message) => {
try {
const event = {
type: "boson-delivery-info",
message
};
// precaution: register to the response before posting the message
const responseType = "boson-delivery-info-response";
const _waitForResponse = shouldWaitForResponse
? (waitForResponse(responseType) as Promise<{
message: DeliveryInfoCallbackResponse;
origin: string;
}>)
: undefined;
// post the message
console.log(
`Post '${event.type}' message to '${targetOrigin}'`
);
window.parent.postMessage(event, targetOrigin);
if (_waitForResponse) {
console.log(`Wait for response '${responseType}' message`);
const response = await _waitForResponse;
console.log(
`Received response '${responseType}' from '${
response.origin
}'. Content: '${JSON.stringify(
JSON.stringify(response.message)
)}'`
);
return response.message;
}
return {
accepted: true,
resume: true,
reason: ""
};
} catch (e) {
console.error(`Unable to post message ${e}`);
return {
accepted: false,
reason: "",
resume: false
};
}
}
: undefined
}
redemptionSubmittedHandler={
targetOrigin
? async (message) => {
try {
const event = {
type: "boson-redemption-submitted",
message
};
// post the message
console.log(`Post ${event.type} message to ${targetOrigin}`);
window.parent.postMessage(event, targetOrigin);
return {
accepted: true,
reason: ""
};
} catch (e) {
console.error(`Unable to post message ${e}`);
return {
accepted: false,
reason: ""
};
}
}
: undefined
}
redemptionConfirmedHandler={
targetOrigin
? async (message) => {
try {
const event = {
type: "boson-redemption-confirmed",
message
};
// post the message
console.log(`Post ${event.type} message to ${targetOrigin}`);
window.parent.postMessage(event, targetOrigin);
return {
accepted: true,
reason: ""
};
} catch (e) {
console.error(`Unable to post message ${e}`);
return {
accepted: false,
reason: ""
};
}
}
: undefined
}
modalMargin="2%"
widgetAction={widgetAction}
deliveryInfo={deliveryInfoDecoded}
Expand Down Expand Up @@ -190,3 +305,32 @@ function checkExchangeState(
}
throw new Error(`Not supported exchange state '${exchangeStateStr}'`);
}

function extractBooleanParam(
paramStr: string | null,
defaultValue: boolean
): boolean {
return paramStr ? /^true$/i.test(paramStr) : defaultValue;
}

async function waitForResponse(
response: string
): Promise<{ message: unknown; origin: string }> {
return new Promise<{ message: unknown; origin: string }>(
(resolve, reject) => {
const eventType = "message";
const listener = (event: MessageEvent | undefined) => {
try {
if (event?.data?.type === response) {
// Ensure the listener won't be called again. Do not use "once" option because some other message could be received in the meanwhile
window.removeEventListener(eventType, listener);
resolve({ message: event.data.message, origin: event.origin });
}
} catch (e) {
reject(e);
}
};
window.addEventListener(eventType, listener);
}
);
}

0 comments on commit d947eb2

Please sign in to comment.