Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save duongphuhiep/a3ed34c7bbd49199337a234e7b48a0d5 to your computer and use it in GitHub Desktop.
Save duongphuhiep/a3ed34c7bbd49199337a234e7b48a0d5 to your computer and use it in GitHub Desktop.
How to perform Request Reply communication between Iframe & Host

Problem

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.

Codes snippets

/**
 * 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);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment