ai-lab.org
BeamDrop

BeamDrop ができるまで — trystero + WebRTC でファイルをサーバー経由せずに直送する

trystero (Nostr 公開リレー) と WebRTC DataChannel で、 ファイル本体をサーバー経由せずブラウザ間で直送する P2P 共有ツールの設計記録。 signaling とペイロードの分離、 QRコード / 部屋コードの UX、 NAT越えとリレーの安定性まで解説します。

判断: 改善

BeamDrop ができるまで — trystero + WebRTC でファイルを直送する設計

BeamDrop は、ファイルを サーバーに上げずに 他人のブラウザへ直接届ける Webサービスです。 WebRTC DataChannel + trystero (Nostr 公開リレー) で signaling と payload を分離し、 ファイル本体は中継サーバを一切通りません。 メディア処理 8本柱に続く、 ラボ 9本目。 今回は 「2台のブラウザ間の通信」 という別軸での 「ブラウザ完結」 実証です。

なぜこの形にしたか

WeTransfer / firestorage / Google Drive のリンク共有はどれも 「ファイル全体を一度クラウドにアップロード → 相手がダウンロード」 という構造です。 ファイル本体は中継サーバに乗ります。 これは置く場所の信頼・法管轄・保持期間の議論が常につきまといます。

WebRTC DataChannel が標準化されて久しい今、 仲介サーバ無しで 2台のブラウザを直結できる素地は揃っています。 残っていた最大の障害は signaling サーバの自前運用ですが、 trystero (Nostr / BitTorrent / MQTT 等の公開リレーを signaling に使う) でそこも消せました。 つまり 「ファイルを上げずに直送できる」 を、 サーバ運用ゼロで成立させる時期に来ています。

visual direction

トランスミッション・フレア × エメラルド + テール + ホログラフィック ピンク。

  • 深い緑黒 (#02110d) を背景に、 エメラルド #10b981 とテール #5eead4 のグラデーション
  • ピーアキセント #f472b6 を 「光の終端」 メタファーで配置
  • 「ビームが届く」 メタファー: ヒーローに横方向に走る scan beam、 待機中の radar pulse
  • 既存8本 (mint / amber / violet / cobalt / lime / gold / rose / indigo) と完全に別の色相とトーン

実装の見どころ

1. signaling と payload の分離

WebRTC で 2台を結ぶには、 SDP (Session Description) と ICE candidate を相互交換する 「signaling」 工程が必要です。 ここだけは何らかのチャネルが要ります。 BeamDrop は Nostr の公開リレー (wss://nostr-relay.app/, wss://relay.damus.io 等) を trystero 経由で利用します。 リレーが運ぶのは 「SDP / ICE 数十KB」 だけで、 ファイル本体は通りません。

const room = joinRoom({ appId: "ai-lab-beam-drop-v1" }, roomCode)
const [sendFile, getFile, onFileProgress] = room.makeAction<ArrayBuffer>("file")

接続が成立した後は、 ファイル本体は WebRTC DataChannel を P2P で流れます。

2. 6文字 room_code + QR

部屋コードは衝突しにくく入力しやすい 32文字アルファベット (ABCDEFGHJKMNPQRSTUVWXYZ23456789) から 6文字 ランダム生成 (crypto.getRandomValues)。 容量は ~10億通り。

const ROOM_ALPHABET = "ABCDEFGHJKMNPQRSTUVWXYZ23456789"
export function generateRoomCode(length = 6) {
  const buf = new Uint8Array(length)
  crypto.getRandomValues(buf)
  return Array.from(buf, (b) => ROOM_ALPHABET[b % ROOM_ALPHABET.length]).join("")
}

QRコードは qrcode npm パッケージで生成。 受信側は /services/beam-drop?room=ABC123&mode=recv を開くだけでコード入力なしで接続準備が走ります。

3. ピア待機 → 自動送信

送信側はリンク発行と同時に joinBeamRoom(code) で部屋に入り、 onPeer() で相手到着をリスニング。 ピアが繋がった瞬間に arrayBuffer() でファイル全体を読み、 sendFile() でストリーミング送信。 trystero がチャンク分割と順序保証を担当します。

session.onPeer(async () => {
  if (sentRef.current) return
  sentRef.current = true
  const buf = await file.arrayBuffer()
  await session.sendFile(buf, { name, size, type }, (pct) => setProgress(pct))
})

4. 受信側の自動ダウンロード

ファイルを受け取り終わったら、 Blob を作って <a download> を click でトリガー。 失敗時用に 「もう一度ダウンロード」 ボタンも表示します。

session.onFile((data, meta) => {
  const blob = new Blob([new Uint8Array(data)], { type: meta.type })
  triggerDownload(blob, meta.name)
})

5. 状態機械

送信側: pick → ready → linking → waiting-peer → transferring → done (or error) 受信側: enter-code → linking → waiting-sender → receiving → done (or error)

各 phase を React state で 1つだけ進めて、 UI もそれに 1:1 で対応。 「リレーに繋いでいる最中」 と 「相手を待っている最中」 を別フェーズにして、 ユーザーに何が起きているか正直に伝えています。

苦労したところ

  • メモリ前提: v1 は送受信側どちらもファイル全体をメモリに保持する設計。 500MB クラスでスマホは厳しい。 FAQ で正直に明示しました。 将来はストリーム送信 + Blob チャンク化に拡張予定。
  • NAT 越えの保証: 公開 STUN だけだと相互がシンメトリック NAT の場合に繋がらないことがあります。 TURN サーバーを足せば確実になりますが、 v1 は STUN のみ。 FAQ で 「繋がらないことがある」 を素直に書く方を選びました。
  • signaling リレーの信頼性: 単一の Nostr リレーが落ちると signaling できません。 trystero は複数リレーへ自動接続して冗長性を担保しますが、 公開リレー全部が落ちる可能性はゼロではない。 v1 は許容、 将来 self-hosted リレー導入の余地を残しています。
  • iOS Safari バックグラウンド: タブが裏に回ると WebRTC 接続が切れがち。 「タブを表示したままに」 を FAQ で案内。

今後の拡張

  • ストリーミング送信: ファイル全体を read せず、 64KB 単位で逐次転送して大ファイル対応
  • 複数ピア broadcast: 1人が複数人に同時送信
  • パスワード保護部屋: trystero の password config で部屋単位の暗号化強化
  • TURN サーバー任意追加: NAT 越えの保証
  • 送信履歴の自動破棄: メモリリーク防止 + プライバシー強化

このサービスから言える事

ラボの 9本柱:

  • voice-scribe / clip-cast / bg-snap / text-pluck / pdf-anvil / pixel-lift / pic-flip / mind-cell / beam-drop

「1台のブラウザの中で処理する」 8本に、 「2台のブラウザの間で通信する」 を加えて 9本。 認識系 (voice-scribe / text-pluck)、 変換系 (bg-snap / pic-flip / pixel-lift / pdf-anvil / clip-cast)、 生成系 (mind-cell)、 そして 転送系 (beam-drop)。 「サーバーに上げないと使えない」 という 既成の前提は、 もう一段ずつ崩れていっています。

Next Action

読んだあとに、そのまま使って確かめる。

この開発ログは BeamDrop をどう育てているかの記録です。読んだらそのままサービス本体へ戻って、価値を確かめられるようにしています。

Keep Reading, Keep Trying

読むだけで終わらせない主力サービス

改善ログで方向を理解したあとに、そのまま使って価値を確かめやすい主力を並べています。

hack-sim主力

HackSim

/hack-sim

1分で始められるSNS向けハッキング体験シミュレーター

· エンタメ· 公開中· 48 pv
prompt-stock主力

PromptStock

/prompt-stock

使うだけで終わらせず、改善して育てるためのプロンプト実用品ストック

· 改善· 改善して伸ばす· 20 pv

More Logs

次に読みたい改善ログ

記事一覧へ