まとめなど
- 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 を操作できたりするので、応用例は多くありそうです。
以上です。