0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

[悪用禁止] 外部サイトのDOMを自前SPAで操る(前編):Cross-Domain通信の仕組み

0
Last updated at Posted at 2026-03-29

はじめに

ブラウザの「同一生成元ポリシー(Same-Origin Policy)」は、ウェブの安全を守る鉄の掟です。
しかし、社内ツールや独自の開発支援ツールを作っていると思い至ります。「コンテキストは外部サイト(Parent)に置きつつ、UIや高度な処理は自作のSPA(Child)でやりたい!」という場面に遭遇します。

ブラウザ拡張機能を作るのも手ですが、配布や権限の管理が意外と面倒だったりします(サボりたいわけではありませんが)。

そこで今回は自前の Sub Window (SPA) を開き、postMessage API を活用して安全にドメインの壁を超える手法の第一歩として、「PostMessageによる疎通」「Heartbeatによる生存確認」 について解説します。

シリーズ記事

記事 内容
1 [悪用禁止] 外部サイトのDOMを自前SPAで操る(前編):Cross-Domain通信の仕組み 本編
2 [悪用禁止] 外部サイトのDOMを自前SPAで操る(中編):RPCによる関数の遠隔実行 関数シリアライズによるドメイン跨ぎのRPCメカニズム
3 [悪用禁止] 外部サイトのDOMを自前SPAで操る(後編):Node.jsとTerserによる自動化と配備 Terserを利用したビルド自動化とBookmarkletによる配備

🚀 技術の全体像

通常、異なるドメイン間では直接的なDOM操作やメモリへのアクセスは禁止されています。
本手法では、Vanilla JS(基本的なJavaScript)のみを用いて以下のステップで安全な通信を確立します。

  1. Browser Console: 外部サイト上でスクリプトを実行し、SPA (http://localhost:3000) を子画面として開く。
  2. Origin Passing: 開く際にParent側のオリジン情報をSPAに伝える。
  3. Heartbeat: 双方が接続状態を定期的に確認し、通信を維持する。

方法

1. Originの特定によるセキュアな通信路の確立

postMessage を使用する際、宛先オリジン(targetOrigin)に *(ワイルドカード)を指定することは推奨されません。
通信相手のウィンドウが意図せぬドメインへ遷移した場合、機密情報を含むメッセージが第三者へ渡るリスクがあるためです。

また、近年のブラウザセキュリティ(Cross-Origin Isolationなど)の仕様において、一部のリソース(SharedArrayBuffer等)を扱うサイトでは * による指定自体が制限され、通信が成立しないケースも存在します。

正確な targetOrigin を指定することで、ブラウザは「現在のウィンドウのオリジンが指定したものと一致する場合のみ」メッセージを配信するようになり、情報の誤送信を物理的に防ぐことが可能です。
本手法では、コンソールからSPAを開く、Parent側の location.origin を明示的に受け渡すことで、このセキュリティ要件に対応します。

サンプルコード (Parent: Browser Console)

const parentDomain = window.location.origin;
// SPAのURLにハッシュやパラメータとしてParentのオリジンを付与
const spaUrl = `http://localhost:3000/#parentDomain=${encodeURIComponent(parentDomain)}`;
const windowName = "Cross-Domain-RPC-Demo";

// 同じ名前を指定することで、常に一つのSub Windowを再利用・フォーカス
const subWin = window.open(spaUrl, windowName);
if (subWin) {
    subWin.focus();
}

なぜこれが重要か?

SPA側は window.location.hash などから「自分がどのサイトから呼ばれたか」を一意に特定できます。
これにより、メッセージを送り返す際に event.source.postMessage(data, targetOrigin)targetOrigin を厳密に指定でき、情報の漏洩を防げます。


2. Heartbeatによる接続状況の監視

ウィンドウが閉じられたりページが遷移したりした場合、通信は一方的に途絶えてしまいます。
SPA(Child)からParentへ定期的に HEARTBEAT_PING を送り、Parentからの HEARTBEAT_PONG を受け取ることで、双方が「接続中(Connected)」であることを正確に把握します。

SPA側 (Child) の受信・送信ロジック

Child側では、Parentからの返信を受け取って初めて isConnected を真(true)と判断します。

index.js
// index.js (Child)
let isConnected = false;
let lastPongTime = Date.now(); // 最後にPONGを受け取った時刻
const targetOrigin = new URLSearchParams(window.location.hash.slice(1)).get('parentDomain');

// Parentからの返信(PONG)を待機
window.addEventListener("message", (event) => {
    if (event.data.type === 'HEARTBEAT_PONG') {
        isConnected = true; 
        lastPongTime = Date.now(); // 受信時刻を更新
        console.log("Status: Connected to Parent");
    }
});

// 1秒ごとに送信および死活監視
setInterval(() => {
    // タイムアウト判定 (5秒以上PONGがない場合は切断とみなす)
    if (Date.now() - lastPongTime > 5000) {
        isConnected = false;
        console.warn("Status: Disconnected (Timeout)");
    }

    if (window.opener && targetOrigin) {
        window.opener.postMessage({ type: 'HEARTBEAT_PING' }, targetOrigin);
    }
}, 1000);

Parent側 (Console) の応答ロジック

Parent側は、ChildからのPINGを受け取った直後に、同一の通信路(event.source)に対して返信を行います。

// ConsoleSnippet (Parent)
window.addEventListener("message", (event) => {
    // セキュリティチェック:許可したオリジン(localhost:3000)以外は無視
    if (event.origin !== "http://localhost:3000") return;

    if (event.data.type === 'HEARTBEAT_PING') {
        // 受け取った相手(Child)に即座にPONGを返す
        event.source.postMessage({ type: 'HEARTBEAT_PONG' }, event.origin);
    }
});

おわりに(前編)

ここまでの手順で、JSを用いて「Originの特定」と「双方向の生存確認(Heartbeat)」を備えた安定的な通信路が確立できました。

次回の「中編」では、この通信路を拡張し、「Parent側の関数をChildから遠隔実行する(RPC)」 高度な実装について詳しく見ていきます。


参考

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?