はじめに
ブラウザ同士でリアルタイム通信をしたい――そう思った瞬間に立ちはだかるのが「シグナリングサーバー」の壁です。WebRTCは強力な技術ですが、ピア同士が接続情報を交換するためのサーバーを自分で用意しなければならず、簡単なマルチプレイヤーゲームやコラボツールを作りたいだけなのに、インフラ構築だけで一日が終わってしまうこともあります。
そんな悩みを解決してくれるのがTrysteroです。BitTorrentやNostrといった既存の分散型ネットワークを「シグナリングの場」として間借りすることで、専用サーバーを一切用意せずにWebRTCのピア接続を確立できます。この記事では、Trysteroの特徴から実際のコード例、実践的な使いどころまでを紹介します。
Trysteroとは
TrysteroはDan Motzenbecker氏が開発しているJavaScriptライブラリで、「サーバーレスなWebRTCマッチメイキング」を謳っています。BitTorrent・Nostr・MQTT・IPFS・Firebase・Supabaseといった既存のネットワークやサービスをシグナリングチャネルとして利用し、ピア同士のWebRTC接続を裏側で自動的に確立してくれます。開発者はルームIDを指定してjoinRoomするだけで、あとはP2Pのデータ・音声・映像のやり取りに集中できます。
主な特徴
- シグナリングサーバー不要 - BitTorrentトラッカーやNostrリレーなど既存の公開インフラを間借りするため、自前のサーバーを構築・運用する必要がありません
- 複数のストラテジーを選択可能 - Nostr(デフォルト)、BitTorrent、MQTT、IPFS、Firebase、Supabaseなど用途や環境に応じて接続方式を切り替えられます
- エンドツーエンド暗号化 - ピア間のデータはWebRTCの暗号化に加え、Trystero自体が鍵交換を行い安全にやり取りされます
- 音声・映像・バイナリ対応 - チャットのようなメッセージだけでなく、ストリームや大きなバイナリデータの自動チャンキング送信にも対応しています
- React Hooks・サーバーサイド実行対応 - React向けのフックが用意されているほか、Node.js/Bun/Denoでも動作します
インストール
npm経由でインストールできます。
npm install trystero
yarnやpnpmを使っている場合も同様です。
yarn add trystero
# または
pnpm add trystero
サーバーを用意せずすぐに試したい場合は、CDN経由でも読み込めます。
<script type="module">
import { joinRoom } from 'https://esm.run/trystero'
</script>
基本的な使い方
まずはルームに参加し、他のピアとメッセージをやり取りする最小構成から見てみましょう。
import { joinRoom } from 'trystero'
// appIdはアプリごとに一意な文字列を指定する
const config = { appId: 'my-realtime-app' }
// 'room-id'を共有するユーザー同士が同じP2Pネットワークに参加する
const room = joinRoom(config, 'room-id')
// ピアの参加・離脱を監視
room.onPeerJoin(peerId => console.log(`${peerId} が参加しました`))
room.onPeerLeave(peerId => console.log(`${peerId} が退出しました`))
// カスタムアクションを定義してメッセージを送受信する
const cursor = room.makeAction('cursor')
// マウス座標を全ピアに送信
window.addEventListener('mousemove', e => {
cursor.send({ x: e.clientX, y: e.clientY })
})
// 他のピアから届いたデータを受け取る
cursor.onMessage((pos, { peerId }) => {
console.log(`${peerId} のカーソル位置:`, pos)
})
joinRoomに渡すappIdは、自分のアプリを他の利用者と区別するための識別子です。同じappIdとroomIdを指定したブラウザ同士が自動的に接続され、シグナリングサーバーなしでWebRTCのハンドシェイクが完了します。
makeActionで作られたオブジェクトのsendでデータを送信し、onMessageにコールバックを登録することで受信を処理できます。文字列・数値・オブジェクト・バイナリなど、JSONでシリアライズ可能なデータはそのまま送受信可能です。
実践的なユースケース
リアルタイムお絵かきボード
複数人が同じキャンバスに同時に描画するようなアプリは、Trysteroが得意とする典型例です。
import { joinRoom } from 'trystero'
const room = joinRoom({ appId: 'shared-canvas' }, 'canvas-room')
const draw = room.makeAction('draw')
const canvas = document.querySelector('canvas')
const ctx = canvas.getContext('2d')
function drawLine({ x0, y0, x1, y1, color }) {
ctx.strokeStyle = color
ctx.beginPath()
ctx.moveTo(x0, y0)
ctx.lineTo(x1, y1)
ctx.stroke()
}
// 自分の描画を全ピアへ送信しつつローカルにも反映
canvas.addEventListener('pointermove', e => {
if (e.buttons !== 1) return
const line = {
x0: e.clientX - e.movementX,
y0: e.clientY - e.movementY,
x1: e.clientX,
y1: e.clientY,
color: '#333'
}
drawLine(line)
draw.send(line)
})
// 他のピアの描画イベントを受け取って自分のキャンバスにも描画
draw.onMessage(line => drawLine(line))
ビデオチャットの映像共有
マイクやカメラのストリームを他のピアに送ることも簡単です。
import { joinRoom } from 'trystero'
const room = joinRoom({ appId: 'video-chat' }, 'meeting-room')
const stream = await navigator.mediaDevices.getUserMedia({
video: true,
audio: true
})
// 自分のストリームをルーム内の全ピアに送信
room.addStream(stream)
// 他のピアから届いたストリームを画面に表示
room.onPeerStream((remoteStream, peerId) => {
const video = document.createElement('video')
video.srcObject = remoteStream
video.autoplay = true
document.body.appendChild(video)
})
このように、シグナリング用のバックエンドを一切書かずに、ブラウザ間の映像共有まで実現できるのがTrysteroの強みです。オンラインボードゲームやコラボレーション付箋ツール、簡易ビデオ会議など、「サーバーを持ちたくないけどリアルタイム性は欲しい」というプロダクトと非常に相性が良いといえます。
まとめ
Trysteroは、WebRTC接続の最大の障壁であるシグナリングサーバーを、BitTorrentやNostrといった既存の分散ネットワークで肩代わりさせるという発想が光るライブラリです。joinRoomとmakeActionというシンプルなAPIだけで、テキストメッセージからカーソル共有、映像ストリームまで幅広いリアルタイム通信を実装できます。
サーバーレスでマルチプレイヤー機能やコラボレーション機能を試作したい場合は、ぜひ一度Trysteroを触ってみてください。ストラテジーの切り替えも容易なので、まずはデフォルトのNostrで動かしてみて、要件に応じてBitTorrentやMQTTなど別の経路へ切り替えていくと良いでしょう。
