こんにちは!2ちゃんねる互換掲示板システム「 つゔぁいちゃんねる(2ch.biz) 」の開発録、第32回です。
前回(第31回)は、サーバーにログがない環境でも途中参加者が自然に会話に加われる「P2P分散過去ログ同期」を実装しました。
チャットが盛り上がり、夜通し語り合うユーザーが増えるにつれて、次にコミュニティ内で頻発するようになったのが、 「部屋主(ホスト)の寝落ち・離脱問題」 です。
「明日仕事だからもう寝るわノシ」と部屋主がブラウザを閉じた瞬間、承認制ルームに新しい人が入れなくなったり、荒らしをキックできる人がいなくなってしまう――。
本稿では、IRC(インターネット・リレー・チャット)の伝統的な /OP(オペレーター権限付与)の思想を現代のWebRTCに輸入し、ホストが退室する前に別の参加者へ管理権限をワンクリックで引き渡す 「ホストなすりつけ(手動権限移譲)機能」 の実装を解説します!
1. P2Pルームにおけるホスト(👑部屋主)の重責
私たちのP2Pチャットにおいて、部屋を建てた初代ホスト(Host)には以下の重要な管理権限が集約されています。
-
入室リクエストの承認・拒否 :
- パスワード制や招待制(許可制)の部屋では、入室希望者が来た際にホストの画面に「承認待ちリクエスト」がポップアップし、ホストが「許可」を押さないと入室できません。
-
迷惑ユーザーの強制キック :
- ルーム内で暴言を吐く荒らしを部屋から放逐する権限を持ちます。
-
トピック(看板)の変更 :
- 部屋のタイトルや議題を更新できます。
しかし、個人同士のP2P通信であるがゆえに、 「ホストがブラウザを閉じると、部屋の管理者がゼロになる」 という致命的な構造的課題を抱えていました。
【これまでの悲劇】
ホストAlice:「みんな盛り上がってるけど、私もう眠いから落ちるね〜」
参加者Bob:「おやすみー!」
── Alice がタブを閉じて退室 ──
参加者Carol:「(あ、新しい人がノックしてるけど、承認ボタン押せる人が誰もいない…!)」
参加者David:「荒らしが入ってきたのにキックできない!詰んだwww」
2. 解決策:IRCの伝統を継ぐ「ホストなすりつけ」機能
この課題を解決するため、ホストが退室する前に、参加者リストの中から信頼できる誰か(あるいはその場にいる誰でも)を選んで、管理権限をワンポチで押し付けることができる 「ホストなすりつけ機能」 を導入しました。
3. UI/UXのこだわり:ユーモアと即時性
技術的な権限の切り替えはもちろん、2ちゃんねるらしい「ノリ」と、一目でわかるUIの即時反映にこだわりました。
① 参加者サイドバーの「👑」移譲ボタン
ホストとしてログインしている場合、右側のニックネームリストの各参加者の右端に「👑(移譲)」と「×(キック)」のアクションボタンが出現します(自分自身には表示されません)。
「👑」ボタンを押すと、あえてちょっとくだけた確認ダイアログが表示されます。
「ユーザー名」に管理権限(ホスト)を移譲(なすりつけ)しますか?
② チャットログへのシステムアナウンス
権限移譲が行われると、部屋の全参加者のログ画面にIRCオマージュのシステムメッセージが流れます。
* Alice が Bob に管理権限を移譲しました (IRC /OP)
③ 新ホスト画面の動的リアクティブ変化
新ホストに選ばれたユーザー(Bob)の画面では、ページをリロードすることなく、即座に以下の変化が起きます:
- トピックバーのバッジが
👤 一般参加者から👑 Operatorに点灯。 - 保留されていた「入室承認待ちセクション」がスルッとスライドイン表示。
- 他の全参加者の横に「👑」と「×」の操作ボタンがアンロック。
4. バックエンド実装:シグナリングによる権限アトミック切り替え
シグナリングサーバー側では、不正なクライアントが勝手にホスト権限を詐称・奪取できないよう厳格な検証を行います。
// server.js: ホスト権限移譲ハンドラ
function handleTransferHost(room, senderId, targetPeerId) {
// 1. 送信者が現在の正式なホストであるかを検証
const sender = room.peers.get(senderId);
if (!sender || !sender.isHost) {
return { error: '管理権限の移譲は現在のホストのみ実行可能です。' };
}
// 2. 移譲先ピアが実在するか検証
const target = room.peers.get(targetPeerId);
if (!target) {
return { error: '指定された参加者が見つかりません。' };
}
// 3. アトミックに権限を反転
sender.isHost = false;
target.isHost = true;
room.currentHostId = targetPeerId;
// 4. 全参加者へイベントをブロードキャスト
const notifyPayload = {
type: 'host_changed',
oldHostId: senderId,
oldHostName: sender.name,
newHostId: targetPeerId,
newHostName: target.name,
timestamp: Date.now()
};
broadcastToRoom(room, notifyPayload);
return { success: true };
}
5. クライアント側実装:権限のリアクティブ反映
クライアント側では、シグナリング経由で host_changed イベントを受信した瞬間に、自分の状態を更新してUIを再描画します。
// イベント受信ハンドラ
function onHostChanged(event) {
const isMeNewHost = (event.newHostId === myPeerId);
const isMeOldHost = (event.oldHostId === myPeerId);
// 自分のホスト状態フラグを更新
isHost = isMeNewHost;
// チャットログにアナウンスを表示
appendSystemMessage(`* ${escapeHtml(event.oldHostName)} が ${escapeHtml(event.newHostName)} に管理権限を移譲しました (IRC /OP)`);
// 自分が新ホストになった場合の特別通知
if (isMeNewHost) {
showToast('👑 あなたに管理権限(ホスト)が移譲(なすりつけ)されました!', 'success');
}
// UIパーツの再描画
updateRoleBadges();
renderNicknameList();
updatePendingApprovalsVisibility();
}
6. 実機検証とコミュニティの反応
自動検証スクリプトおよび実機ブラウザでのマルチウィンドウテストにより、以下の挙動を確認しました。
-
権限奪取攻撃の防止 :
- 一般参加者から
transfer_hostAPI を偽装送信しても、403 Forbiddenで確実に弾かれることを確認。
- 一般参加者から
-
シームレスな体験 :
- Alice が Bob になすりつけた直後に Alice がブラウザを閉じても、部屋は一切解散せず、Bob のもとで平穏にチャットが継続することを確認。
-
ユーザーの反応 :
- 「なすりつけ機能草」「これで安心して寝落ちできる」「急に王冠ついてビビったけど楽しかったw」と、実用性とコミュニティの遊び心が両立した大好評の機能となりました。
7. 次回予告:大容量ファイル共有への挑戦
ホストの引き継ぎができるようになり、P2Pチャットは夜通し語り合えるコミュニティの「溜まり場」へと進化しました。
しかし、長時間のチャットで必ず発生するのが 「画像や大容量ファイルの共有」 です。
WebRTC DataChannelで画像やzipファイルを共有する際、全員のブラウザに無条件でメガバイト級のバイナリを一斉ブロードキャストしてしまうと、 「スマホのギガ(通信量)が死ぬ!」「通信が固まった!」 という悲鳴が上がることになります。
欲しい人だけが、安全に、手動でダウンロードできる「オンデマンド転送」をどう実現したのか?
次回は、 【第033回】勝手な大容量ダウンロードで帯域を圧迫させない!オンデマンド(手動承認)P2Pファイル転送 をお届けします。お楽しみに!
本番サービス : https://2ch.biz/