← シリーズに戻る

postMessage の契約設計 — Vimeo・YouTube・Safie で3世代を比較する

Part 3 / 6 script 1行で配るJS SDKの設計 — 実在5製品を分解する

前記事で Safie の iframe 隔離を分解しました。iframe 内には自社の CSP と自社の JS が動いていて、親子は postMessage で会話しています。今回はその「会話の中身」を設計する話です。5製品のうち Vimeo・YouTube・Safie の3つが iframe 型なので、この3つのプロトコルを並べて読みます。

プロトコル早見表

2026-08-08 時点で各バンドルから抽出した実装です。

製品メッセージ形式型検証Origin検証ハンドシェイク暗号化
Vimeo{method, value, event, data}実装内 === "ready"first-message trust (* → 実 origin)なし (直接 method 呼び出し)なし
YouTube{event, id, channel, data}内部 switch 分岐.origin === k.O 等値比較"listening""initialDelivery"なし
Safie{type, data, guid} + wrapped payloadJSON Schema (safie.link/schemas/...)origin !== this._targetOrigin return"syn""ack" + 鍵交換AES-GCM

3世代あります。Vimeo が最小構成、YouTube が状態同期を加えたもの、Safie がハンドシェイク + 暗号化まで含む最重量。SDK が扱うデータの機密性と、親子で共有する状態の量で必要な世代が決まります。

順に見ていきます。

Vimeo: 最小構成

Vimeo のバンドル(24 KB / gzip 8 KB)から postMessage まわりを抜き出すと、驚くほど短いです。

// 抜粋 (整形済み)
postMessage(e, t.origin)

// 受信側
event.data.event === "ready"
event.data.event === "spacechange"
event.data.event === "drminitfailed"

{method, value, event, data} の JSON を投げ合うだけの構造で、method 呼び出しがそのままメッセージになります。ハンドシェイクは無く、iframe が読み込まれたら親から即座に method を投げていい。もし iframe 側の準備がまだなら、iframe 側が event: "ready" を送るまで親側で待つ実装になっています。

Origin 検証は少し変わっています。

"*" === this.origin && (this.origin = event.origin)

「初回メッセージで受け取った origin を正解とみなす」パターンです。これは iframe の実際のオリジンが構築時点でわからない場合(sandbox 属性、動的サブドメイン等)への実用的な妥協で、first-message trust と呼ばれます。理論的には初回に攻撃側 iframe が先に喋ると乗っ取られますが、iframe の onload 直後に親から ping を投げて子から応答が来るまで待つ実装なので、実質的にはあまり露出しません。それでも AES-GCM 級の防御ではありません。

YouTube: 状態同期を足したもの

YouTube widgetapi (27 KB) は Vimeo より一世代新しいプロトコルです。

// 語彙
"listening"      // 子から親へ「準備できた」
"initialDelivery" // 親から子へ「初期状態はこれ」
"command"        // 親から子へメソッド呼び出し
"event"          // 子から親へ状態変化通知

"listening" が実質的なハンドシェイクの片側で、iframe 側の JS 初期化が終わったタイミングで親に送られます。親はそれを見てから "initialDelivery" で動画 ID・自動再生・字幕設定を子に流し、以後 "command" で再生・停止を、"event" で状態変化を受け取ります。

Origin 検証は等値比較で厳密です。

event.origin === k.O

k.O は iframe を作った時点で覚えておいた自社オリジン(通常 https://www.youtube.com)で、リクエスト毎に検証されます。Vimeo の first-message trust と比べて、コードは短いのに硬い設計です。ハンドシェイクが片側だけなのは、YouTube が扱うデータ(動画メタデータ、再生位置)がそもそも公開情報だから、盗聴対策より状態同期の正確さが優先だからと読めます。

Safie: 最重量、機密データ前提

Safie は前記事で見たとおりです。復習:

postMessage("syn", { privateKey: a.privateKey, wrappedAesKey: c.wrappedAesKey })
// 受信側
event.data.type === "ack"
event.data.type === "connected"
// origin検証
if (event.origin !== this._targetOrigin) return
// 以後のペイロードは AES-GCM で暗号化

TCP 3-way handshake の postMessage 版に、非対称鍵経由の AES-GCM 鍵交換を接ぎ木した形。親子間を流れる認証トークン・監視カメラ ID を、同一オリジン内の別 iframe に傍受されても解読できないようにしています。

Safie がここまで組む理由は、扱うデータが「録画映像へのアクセス権を含む」からです。動画プレイヤーの currentTime が漏れても実害は小さい。カメラ映像 SDK の認証トークンが漏れると、契約カメラを他人がライブ視聴できる状態が発生します。プロトコル世代の選択は、SDK が扱うデータの経済的・法的価値で決まります。

メソッド定義を JSON Schema で公開する

Safie のバンドルには safie.link/schemas/invoke-method/ の URL が並んでいます。

safie.link/schemas/invoke-method/auth/set-token
safie.link/schemas/invoke-method/devices/query-devices
safie.link/schemas/invoke-method/streaming/start-ptz

これらは JSON Schema の URI で、postMessage で送るメッセージの構造を機械可読で定義しています。ランタイムに検証されるので、想定外の型が来たら即座に弾けます。TypeScript の .d.ts はエディタ支援に効きますが、親子で別ドメインの JS が動く SDK では両側で同じ型定義を確実に共有するのが難しく、ランタイム検証の JSON Schema が保険として効きます。

Vimeo や YouTube はここまでやっていません。代わりに実装内の event === "ready" のような文字列判定と、内部 TypeScript 型で通しています。5製品を並べると、機密度が上がるほどランタイム検証まで持ち込む傾向が見えます。

自作するときの決断ポイント

3世代のどこに位置を取るかは、以下で決められます。

  1. 親子で状態を共有するか
    共有しない (Vimeo 型: method 呼び出しと event 通知だけ) → ハンドシェイク不要。
    共有する (YouTube 型: 初期状態を親から流す) → "listening" 相当の準備完了通知が要ります。

  2. ペイロードの機密性はどの水準か
    公開情報のみ → 暗号化不要。
    同一オリジン内の他 iframe に見られたら困る → AES-GCM 鍵交換を組む。

  3. メッセージ構造を機械可読で公開するか
    .d.ts 配布で十分な閉じたクライアント → TypeScript 型のみ。
    サードパーティが独自クライアントを書く可能性がある → JSON Schema をランタイム検証込みで。

  4. Origin 検証の厳密性
    iframe オリジンが構築時に確定 → 等値比較 (YouTube 型)。
    sandbox やサブドメイン切替が絡む → first-message trust (Vimeo 型) を選ぶ場合あり。ただしトレードオフあり。

次: 版と cache-control

プロトコルを固定したら、次はバージョン戦略です。5製品で cache-control ヘッダーと URL 構造をそろえて並べると、iframe 型と DOM 直挿し型で戦略が真っ二つに割れます。SDK 側で版を切らせる YouTube・Video.js・Mux Player と、切らせない Safie・Vimeo。この分岐の理由を次で扱います。

実測日は 2026-08-08。プロトコルの語彙とハンドシェイク形状は年単位で安定します。ペイロード仕様は動きます。