WebRTC DataChannel と OPFS と sqlite-wasm を組み合わせた P2P SNS 試作 その2
前回の記事

WebRTC DataChannel と OPFS と sqlite-wasm を組み合わせた P2P SNS 試作 その1
概要 P2P で動くSNS Webアプリが作ってみたくなって、ChatGPT と相談しながら作ってみた。 WebRTC DataChannel で P2P っぽい部分を実現し、データは完全にローカル環境の OPFS にしかない。 ストレージには SQLite を使った。WASM 版があるのでそれを OPFS に組...
合同会社うみがめ
https://www.umigame.tech/post/203
WebRTC DataChannel を使った通信の実装
WebSocket のシグナリングサーバーができたら、 WebRTC を使って実際にピア同士をつなぐ。
ピア同士をつなぐには、SDPなるプロトコルとICEフレームワークなるものを使って互いに送信し、コネクションを作る。
具体的には、MDN にある以下のようなコードを書く必要がある。
localConnection .createOffer() .then((offer) => localConnection.setLocalDescription(offer)) .then(() => remoteConnection.setRemoteDescription(localConnection.localDescription), ) .then(() => remoteConnection.createAnswer()) .then((answer) => remoteConnection.setLocalDescription(answer)) .then(() => localConnection.setRemoteDescription(remoteConnection.localDescription), ) .catch(handleCreateDescriptionError);

A simple RTCDataChannel sample - Web APIs | MDN
The RTCDataChannel interface is a feature of the WebRTC API which lets you open a channel between two peers over which you may send and receive arbitrary data. The API is intentionally similar to the WebSocket API, so that the same programming model can be used for each.
MDN Web Docs
https://developer.mozilla.org/en-US/docs/Web/API/WebRTC_API/Simple_RTCDataChannel_sample
このサンプルはローカルでのコネクションだけを想定しており、STUN や TURN サーバーを使わない例になっている。
実際には、開発中は Google の無料の STUN サーバー stun:stun.l.google.com:19302 とかを使うことが多いと思う。
const peerConnection = new RTCPeerConnection({ iceServers: [ { urls: 'stun:stun.l.google.com:19302' } ] });
また、シグナリングサーバーを経由して、 offer, answer, iceで接続、みたいな感じでフェーズを分けてデータを送り合うという実装もよくあるようだ。
TURN サーバーの組み込み
開発中は STUN サーバーを利用する方法でたいてい問題ないと思うが、実際の運用では WiFi ネットワーク外のモバイルネットワークとかで接続しようとした場合は TURN サーバーが必要になる。(詳しく説明したサイトが無数に存在するので説明は省略)
TURN サーバーは OSS を使って自分で立てたりもできるようだが、お金がかかるのは嫌なので、 Cloudflare の無料サーバーを使わせてもらうことにした。1000GB/月 までの転送量を無料で使える。

Cloudflare Realtime | リアルタイム音声・動画アプリを構築
開発者はCloudflare Realtimeを使って、リアルタイムの音声・動画アプリをグローバル規模で構築できます。今すぐCloudflare Realtimeで構築を始めましょう。
https://www.cloudflare.com/ja-jp/developer-platform/products/cloudflare-realtime/
コードとしては以下のようになる。Cloudflare に TURN サーバーのアドレスや認証情報を動的に問い合わせる形式。
// サーバー側 async function getTurnCredentials(env: Env) { if (!env.CLOUDFLARE_TURN_REQUEST_URL || !env.CLOUDFLARE_TURN_API_TOKEN) { return new Response( JSON.stringify({ error: "Missing TURN configuration: CLOUDFLARE_TURN_REQUEST_URL or CLOUDFLARE_TURN_API_TOKEN is not set.", }), { status: 500, headers: { "Content-Type": "application/json", ...coopCoepHeaders, }, } ); } const response = await fetch(env.CLOUDFLARE_TURN_REQUEST_URL, { method: "POST", headers: { "Authorization": `Bearer ${env.CLOUDFLARE_TURN_API_TOKEN}`, "Content-Type": "application/json", }, body: JSON.stringify({ ttl: 86400, }), }); return response.json(); }
// クライアント側 // useCloudflareTurn.tsx import { useEffect, useState } from "react"; export type TurnIceServer = { urls: string[]; username: string; credential: string; }; export type TurnCredentials = { iceServers: TurnIceServer[]; }; export default function useCloudflareTurn() { const [turnCredentials, setTurnCredentials] = useState<TurnCredentials | null>(null); const [turnError, setTurnError] = useState<string | null>(null); const [turnLoading, setTurnLoading] = useState(false); useEffect(() => { const fetchTurnCredentials = async () => { setTurnLoading(true); try { const response = await fetch('/turn'); if (!response.ok) { const errorText = await response.text(); setTurnError(errorText || `Failed to fetch TURN credentials: ${response.status}`); setTurnCredentials(null); return; } const data = await response.json(); setTurnCredentials(data as TurnCredentials); } finally { setTurnLoading(false); } }; fetchTurnCredentials(); }, []); return { turnCredentials, turnError, turnLoading }; } // home.tsx export default function Home() { // 省略 const { turnCredentials, turnError, turnLoading } = useCloudflareTurn(); const peerConnection = new RTCPeerConnection({ iceServers: turnCredentials.iceServers, }); // 省略 }
MPがつきたので、OPFS と sqlite-wasm については次回。。
<!-- # 感想 P2Pのサービスは接続している人がいなければ成り立たない。そのため、いかに繋がりっぱなしにしてもらうか、という課題が発生する。 既存のP2Pサービスは、アップロードした人が優先的にダウンロードを受け取れる仕組みだとか、IPFSみたいにファイルをホストし続ければ報酬がもらえるとかの仕組みでインセンティブを設計している。 しかしそういう仕組がなければ、接続しっぱなしにするための動機が生まれず、データを非同期的にネットワーク上に維持することが難しくなる。 P2Pを使ったら何か面白いSNSが生まれるのではと思ったが、完全に浅はかだった。自分の頭の悪さを再確認するだけで終わった。 -->