Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save duongphuhiep/a3ed34c7bbd49199337a234e7b48a0d5 to your computer and use it in GitHub Desktop.

Select an option

Save duongphuhiep/a3ed34c7bbd49199337a234e7b48a0d5 to your computer and use it in GitHub Desktop.

Revisions

  1. duongphuhiep revised this gist Feb 25, 2023. 1 changed file with 40 additions and 38 deletions.
    78 changes: 40 additions & 38 deletions How to perform Request Reply communication between Iframe & Host.md
    Original file line number Diff line number Diff line change
    @@ -14,52 +14,54 @@ I will resume the technique by some codes snippets and at the same time add the
    ```typescript
    /**
    * 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 '*'
    * @param timeoutMs: default 10000
    * @param targetOrigin: default '*'
    * @returns promise on response
    */
    export function requestCrossOrigin<TRequest, TResponse>(
    request: TRequest,
    timeoutMs?: number,
    targetOrigin?: string
    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();
    //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);
    }
    };
    //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 ?? '*';
    targetOrigin = targetOrigin ?? '*';

    //send the request
    parent.postMessage(request, targetOrigin, [channel.port2]);
    });
    //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
    )
    ),
    ]);
    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
    )
    ),
    ]);
    }

    /**
    @@ -70,6 +72,6 @@ export function requestCrossOrigin<TRequest, TResponse>(
    * @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);
    event.ports[0].postMessage(response);
    }
    ```
  2. duongphuhiep revised this gist Feb 25, 2023. 1 changed file with 2 additions and 0 deletions.
    Original file line number Diff line number Diff line change
    @@ -11,6 +11,7 @@ I will resume the technique by some codes snippets and at the same time add the

    # Codes snippets

    ```typescript
    /**
    * Send the request Cross-Origin and wait for a response
    * @param request
    @@ -71,3 +72,4 @@ export function requestCrossOrigin<TRequest, TResponse>(
    export function replyCrossOrigin<TRequest, TResponse>(event: MessageEvent<TRequest>, response: TResponse | Error) {
    event.ports[0].postMessage(response);
    }
    ```
  3. duongphuhiep revised this gist Feb 25, 2023. 1 changed file with 54 additions and 55 deletions.
    109 changes: 54 additions & 55 deletions How to perform Request Reply communication between Iframe & Host.md
    Original file line number Diff line number Diff line change
    @@ -11,64 +11,63 @@ I will resume the technique by some codes snippets and at the same time add the

    # Codes snippets

    In this example:
    * the Iframe want to ask the Host a `IframeConfig` object,
    * the Iframe open a new `MessageChannel` to post a `IframeConfigRequest` object to the Host.
    * when the Host received this `IframeConfigRequest`, it will reply the `IframeConfig` object on the `MessageChannel`
    * we want the Promise<IframeConfig> to be terminated in timeout if the host did not response, so we make it race with a "timeout" promise.
    => what ever terminate first will solve the promise.
    /**
    * Send the request Cross-Origin and wait for a response
    * @param request
    * @param timeoutMs default 10000
    * @param targetOrigin default '*'
    * @returns promise on response
    */
    export function requestCrossOrigin<TRequest, TResponse>(
    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();

    ### Iframe codes
    //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);
    }
    };

    ```typescript
    function requestIframeConfig(uuid: string): Promise<IframeConfig> {
    const mainPromise = new Promise<IframeConfig>((res, rej) => {
    const channel = new MessageChannel();
    targetOrigin = targetOrigin ?? '*';

    //setup listener to the response
    channel.port1.onmessage = ({ data }) => {
    channel.port1.close();
    const response = data as IframeConfig|Error
    if (response instanceof Error) {
    rej(response);
    }
    else if (!response.type) {
    rej(new Error(`Unable to get IframeConfig from the sdk. Invalid response: missing type`))
    }
    else {
    res(response);
    }
    };
    //send the request
    parent.postMessage(request, targetOrigin, [channel.port2]);
    });

    //send the request
    const request: IframeConfigRequest = {
    type: MessageType.IframeConfigRequest,
    uuid
    }

    parent.postMessage(request, "*", [channel.port2])
    });

    return Promise.race([
    mainPromise,
    new Promise<IframeConfig>((_, reject) =>
    setTimeout(() => reject(new Error('Timeout: Unable to get IframeConfig from the sdk within 10 seconds')), 10000)
    )
    ]);
    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
    )
    ),
    ]);
    }
    ```

    ### Host codes

    ```typescript
    let iframeConfig: IframeConfig;

    window.addEventListener(
    "message",
    function (event: MessageEvent<IframeConfigRequest>) {
    if (event.data.type != MessageType.IframeConfigRequest) return; //received non-concerned event
    //response to the iframe
    event.ports[0].postMessage(iframeConfig);
    }
    );
    ```
    /**
    * 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);
    }
  4. duongphuhiep created this gist Feb 11, 2023.
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,74 @@
    # 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](https://advancedweb.hu/how-to-use-async-await-with-postmessage/) 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

    In this example:
    * the Iframe want to ask the Host a `IframeConfig` object,
    * the Iframe open a new `MessageChannel` to post a `IframeConfigRequest` object to the Host.
    * when the Host received this `IframeConfigRequest`, it will reply the `IframeConfig` object on the `MessageChannel`
    * we want the Promise<IframeConfig> to be terminated in timeout if the host did not response, so we make it race with a "timeout" promise.
    => what ever terminate first will solve the promise.

    ### Iframe codes

    ```typescript
    function requestIframeConfig(uuid: string): Promise<IframeConfig> {
    const mainPromise = new Promise<IframeConfig>((res, rej) => {
    const channel = new MessageChannel();

    //setup listener to the response
    channel.port1.onmessage = ({ data }) => {
    channel.port1.close();
    const response = data as IframeConfig|Error
    if (response instanceof Error) {
    rej(response);
    }
    else if (!response.type) {
    rej(new Error(`Unable to get IframeConfig from the sdk. Invalid response: missing type`))
    }
    else {
    res(response);
    }
    };

    //send the request
    const request: IframeConfigRequest = {
    type: MessageType.IframeConfigRequest,
    uuid
    }

    parent.postMessage(request, "*", [channel.port2])
    });

    return Promise.race([
    mainPromise,
    new Promise<IframeConfig>((_, reject) =>
    setTimeout(() => reject(new Error('Timeout: Unable to get IframeConfig from the sdk within 10 seconds')), 10000)
    )
    ]);
    }
    ```

    ### Host codes

    ```typescript
    let iframeConfig: IframeConfig;

    window.addEventListener(
    "message",
    function (event: MessageEvent<IframeConfigRequest>) {
    if (event.data.type != MessageType.IframeConfigRequest) return; //received non-concerned event
    //response to the iframe
    event.ports[0].postMessage(iframeConfig);
    }
    );
    ```