Your HTML page (the host page) has an Iframe. The Iframe source is on other domain, so you will have to use the window.postMessage()
for cross-origin communication.
But this communication is only in 1 direction (The Iframe can inform the Host or the Host can inform the Iframe)
This article shows you how to make the Request/Reply communication using postMessage()
and MessageChannel
combination.
The article did not show you how to add a timeout to the communication, so the Iframe might wait forever if the Host did not response.
I will resume the technique by some codes snippets and at the same time add the missing timeout implementation.
/**
* Send the request Cross-Origin and wait for a response
* @param targetWindow: the window we want to broadcast the request to (usually `parent` or `iframe.contentWindow`)
* @param request
* @param timeoutMs: default 10000
* @param targetOrigin: default '*'
* @returns promise on response
*/
export function requestCrossOrigin<TRequest, TResponse>(
targetWindow: Window,
request: TRequest,
timeoutMs?: number,
targetOrigin?: string
): Promise<TResponse> {
//apply this https://advancedweb.hu/how-to-use-async-await-with-postmessage/
const mainPromise = new Promise<TResponse>((res, rej) => {
const channel = new MessageChannel();
//setup listener to the response
channel.port1.onmessage = ({ data }) => {
channel.port1.close();
const response = data as TResponse | Error;
if (response instanceof Error) {
rej(response);
} else {
res(response);
}
};
targetOrigin = targetOrigin ?? '*';
//send the request
targetWindow.postMessage(request, targetOrigin, [channel.port2]);
});
if (!timeoutMs || timeoutMs < 0) timeoutMs = 10000;
return Promise.race([
mainPromise,
new Promise<TResponse>((_, reject) =>
setTimeout(
() =>
reject(
new Error(
`Timeout: Unable to get response within ${timeoutMs} ms`
)
),
timeoutMs
)
),
]);
}
/**
* Send response for requests coming from the requestCrossOrigin() function.
* It is recommended to verify the `event.origin` before calling this function to make sure that the origin of the request is authorized.
* For eg: `window.addEventListener('message', (event) => { if (event.origin==="https://myapp.com") replyCrossOrigin(event, myResponse) })`;
* @param event the event object when the request is coming
* @param response the response you want to send back
*/
export function replyCrossOrigin<TRequest, TResponse>(event: MessageEvent<TRequest>, response: TResponse | Error) {
event.ports[0].postMessage(response);
}