まとめなど
- iframe タグの src 属性に指定した URL に対してデータを送信し、その URL のページから送信されるデータを受信する。送受信、ピンポンするようなイメージ。
 - 呼び出されるオリジン側の認証状態の操作を行う (ことで呼び出されるオリジンのCookieを操作する) のに便利
 - 前回の 【TypeScript】iframe からの postMessage を受け取る汎用関数を作った – oki2a24 の仲間。
 
iframe と送受信する汎用関数のコード
src/utils/postReceiveMessage.ts
/**
 * 受信した MessageEvent.data の型
 * @interface
 */
interface MessageEventData {
    /**
     * レスポンスオブジェクト
     * @type {Object}
     */
    response: {
        /**
         * ステータスコード
         * @type {number}
         */
        status: number;
        // 他のプロパティ
    };
    // 他のプロパティ
}
/**
 * データが MessageEventData 型であるかどうかを検証します。
 * @param {unknown} data - 検証するデータ
 * @returns {data is MessageEventData} - データが MessageEventData 型である場合は true、それ以外の場合は false
 */
const isValidMessageEventData = (data: unknown): data is MessageEventData => {
    return (
        typeof data === "object" &&
        data !== null &&
        "response" in data &&
        typeof (data as MessageEventData).response.status === "number"
    );
};
/**
 * 対象ウィンドウにメッセージを送信し、応答を待ちます。
 * @template T - 送信するメッセージの型。
 * @param {Window} targetWindow - メッセージを送信するウィンドウ。
 * @param {string} targetOrigin - 対象ウィンドウのオリジン。
 * @param {T} message - 送信するメッセージ。
 * @param {number} [timeoutInSeconds=10] - 応答を待つ秒数。タイムアウトするまでの時間。
 * @returns {Promise<MessageEvent<MessageEventData> | Error>} 応答メッセージイベントまたはエラー(タイムアウトした場合)を解決するプロミス。
 */
export const postReceiveMessage = async <T>(
    targetWindow: Window,
    targetOrigin: string,
    message: T,
    timeoutInSeconds: number = 10
): Promise<MessageEvent | Error> => {
    return new Promise((resolve, reject) => {
        const timeoutId = setTimeout(() => {
            window.removeEventListener('message', messageHandler);
            reject(new Error(`${targetOrigin} から ${timeoutInSeconds} 秒応答無く、タイムアウトしました。`));
        }, timeoutInSeconds * 1000);
        const messageHandler = (event: MessageEvent) => {
            if (event.origin !== targetOrigin) {
                return;
            }
            if (!isValidMessageEventData(event.data)) {
                return;
            }
            if (event.data.response.status === 200) {
                resolve(event);
            } else {
                reject(event);
            }
            window.removeEventListener('message', messageHandler);
            clearTimeout(timeoutId);
        };
        window.addEventListener('message', messageHandler);
        targetWindow.postMessage(message, targetOrigin);
    });
}
- 受信するときのデータ、 
MessageEvent.dataの構造をisValidMessageEventData関数でチェックしている。構造の定義はinterface MessageEventDataで行っている。本関数は汎用関数と謳っているが、プロジェクト等に応じて内容を変更した方が良いだろう。 e.data.response.status === 200を行うことで iframe からの postMessage の内容を精査している。これも本関数は汎用関数と謳っているが、プロジェクト等に応じて内容を変更した方が良いだろう。
使い方の例。 Vue.js を例に。
オリジンは http://localhost:5174 。 汎用関数を使う側。
src/views/PostReceiveMessage.vue
<script setup>
import { onMounted, ref } from "vue";
import { postReceiveMessage } from "../utils/postReceiveMessage";
const authMessageEventHandlerIframe = ref(null);
const idpOrigin = "http://localhost:5173";
const message = "hoge-fuga";
onMounted( () => {
});
const test = async () => {
  const targetIframeWindow = authMessageEventHandlerIframe.value.contentWindow;
  const result = await postReceiveMessage(targetIframeWindow, idpOrigin, message);
  console.log({ result });
};
</script>
<template>
  <button @click="test" />
  <iframe ref="authMessageEventHandlerIframe" src="http://localhost:5173/test1.html"
    style="display: none;"></iframe>
</template>
続いて、オリジンは http://localhost:5173 。 汎用関数から呼び出される側。静的 HTML としている。 Vue.js のページでも機能すると思ったのだが、できなかったので静的な HTML としている。
test1.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document aap1 test1.html</title>
</head>
<body>
    <p>Document aap1 test1.html</p>
</body>
<script>
    console.log("app1 test1.html start");
    window.addEventListener("message", receiveMessage, false);
    function receiveMessage(event) {
        const targetOrigin = "http://localhost:5174";
        if (event.origin !== targetOrigin) {
            return;
        }
        // 色々な処理
        // メッセージ送信
        const message = {
            response: {
                status: 200,
                message: "OK",
            }
        };
        event.source.postMessage(message, targetOrigin);
    }
</script>
</html>
おわりに
前回と比べて、想定するユースケースはより複雑になっています。つまり、前回はただ受け取るだけでしたけれども、今回は、送信して、相手がそれを処理し、結果を受け取る、というようになっています。
複雑にはなっていますけれども、実現する汎用関数としてはコードはあまり違いがありません。むしろ呼び出される側でデータを処理する必要がでてくる、つまり動的になるので呼び出される側が複雑になるかと思います。しかしながら呼び出される側で処理を行うことによって、例えば呼び出されるオリジンでCookieを持たせたり呼び出されるオリジンの localStorage を操作できたりするので、応用例は多くありそうです。
以上です。
