こんにちは!2ちゃんねる互換掲示板システム「 つゔぁいちゃんねる(2ch.biz) 」の開発録、第47回です。
前回(第46回)では、暗号化自己完結型ステートレスIDをワンクリックで生成・コピーできるプレビューUIを実装しました。
部屋の作成と共有が劇的にスムーズになった一方で、チャットを使い始めたユーザーたちから、とても素朴で、かつ切実な要望が寄せられました。
「部屋を作るときに『今夜の野球実況』って名前を付けたのに、 参加してくれた友達の画面には
room_p08e200...としか出てこない! 」
「後から入ってきた人が『ここ何の話をする部屋?』と迷ってしまう!」
「翌朝、端末に保存されたログを見返したとき、部屋名が残っていないと何のログだか分からない…」
本稿では、サーバーに永続的なログテーブルを作らないというゼロストレージ原則を死守したまま、 ルーム名を参加者全員にリアルタイム同期し、各端末のIndexedDBに安全に復元・永続化するハイブリッドアーキテクチャ を解説します!
1. 「無機質なID」から「血の通ったコミュニティ」へ
これまで画面の最上部には、#room_p08e200775f のような暗号化IDが表示されていました。
技術的には美しくても、人間にとっては「何の部屋か分からない無機質な文字列」でしかありません。
IRC(インターネット・リレー・チャット)でも、チャンネル名(#news や #lounge)とトピック(話題)が常に画面上部に掲げられているからこそ、参加者は会話の文脈を瞬時に把握できます。
私たちが目指したのは、以下の理想的な体験でした:
- ホストが付けた部屋名が、参加者全員の画面上部に即座に表示される 。
- 会話の最中に部屋名が変更されても、P2Pで全員にリアルタイム反映される 。
- ブラウザを閉じても、各自の端末(ブラウザ)に部屋名が過去ログとともに保存され、次に開いたときもタイトル付きで復元される 。
2. 二重同期パイプライン(シグナリング + P2P直接補完)
サーバーのデータベースに部屋名の履歴テーブルを作らないため、私たちは 「シグナリングサーバーのインメモリ中継」 と 「WebRTC DataChannelによるP2P直接補完」 の二重パイプラインを構築しました。
- 第1ルート(シグナリング応答) : 入室した最初の1フレーム目で、画面が白い段階から正確な部屋名を描画する。
- 第2ルート(P2P DataChannel) : 万が一シグナリングサーバーで部屋名が取得できなかった場合や、途中で部屋名が変更された場合でも、ピア同士が直接メタデータを同期する。
3. 端末内ストレージ(IndexedDB v2)への永続化
「サーバーには部屋名を残さないが、ユーザーの端末内には残す」。
このプライバシー分離を実現するため、私たちはブラウザの IndexedDB スキーマをバージョン2へとマイグレーション しました。
/**
* IndexedDB のスキーマ拡張 (p2p_chat.js)
*/
initDB() {
return new Promise((resolve, reject) => {
// バージョンを 2 にアップグレード
const req = indexedDB.open(this.dbName, 2);
req.onupgradeneeded = (e) => {
const db = e.target.result;
// v1: メッセージ保存用オブジェクトストア
if (!db.objectStoreNames.contains('messages')) {
const msgStore = db.createObjectStore('messages', { keyPath: 'id' });
msgStore.createIndex('roomId', 'roomId', { unique: false });
msgStore.createIndex('timestamp', 'timestamp', { unique: false });
}
// ★v2新設: 部屋メタデータ保存用オブジェクトストア
if (!db.objectStoreNames.contains('rooms')) {
const roomStore = db.createObjectStore('rooms', { keyPath: 'roomId' });
roomStore.createIndex('updatedAt', 'updatedAt', { unique: false });
}
};
req.onsuccess = (e) => {
this.db = e.target.result;
resolve();
};
req.onerror = reject;
});
}
部屋メタデータの保存と復元API
// 部屋名と最終アクセス日時の保存
async saveRoomMeta(roomId, roomName) {
await this.readyPromise;
return new Promise((resolve, reject) => {
const tx = this.db.transaction('rooms', 'readwrite');
const store = tx.objectStore('rooms');
store.put({
roomId: roomId,
roomName: roomName || '名称未設定ルーム',
updatedAt: Date.now()
});
tx.oncomplete = () => resolve();
tx.onerror = reject;
});
}
// 保存された部屋名の取得
async getRoomMeta(roomId) {
await this.readyPromise;
return new Promise((resolve, reject) => {
const tx = this.db.transaction('rooms', 'readonly');
const store = tx.objectStore('rooms');
const req = store.get(roomId);
req.onsuccess = () => resolve(req.result || null);
req.onerror = reject;
});
}
4. UIへの反映:トピックバーとウィンドウタイトルの調和
取得・同期されたルーム名は、チャット画面のあらゆる場所に心地よく反映されます。
-
アプリ上部ヘッダー :
💬 つゔぁいちゃんねる P2Pチャット - ひみつの作戦会議
-
トピックバー(看板) :
# ひみつの作戦会議 | 👤 4人 | 💾 保存: 許可
-
ブラウザのタブタイトル(
<title>) :- タブを切り替えてスレッドを見ていても、どのチャットルームを開いているかが一目で判別可能。
// UI反映ロジック
function updateRoomTitleUI(roomName, roomId) {
const displayName = roomName || roomId;
// ヘッダータイトルの更新
const titleEl = document.getElementById('p2p-header-title');
if (titleEl) {
titleEl.textContent = `💬 つゔぁいちゃんねる P2Pチャット - ${displayName}`;
}
// トピックバーの更新
const topicEl = document.getElementById('p2p-topic-text');
if (topicEl) {
topicEl.textContent = displayName;
}
// ドキュメントタイトルの更新
document.title = `💬 ${displayName} - P2Pチャット`;
}
5. 自動テストによる同期検証
テストスクリプトを実行し、以下のシナリオをテストしました。
| テストシナリオ | 動作確認項目 | 実行結果 |
|---|---|---|
| ホストの部屋名設定 |
roomName="ひみつの作戦会議" で作成 |
✅ PASS (作成成功) |
| 参加者への初期同期 |
join レスポンスに同名の部屋名が含まれる |
✅ PASS (同期完了) |
| P2P DataChannel同期 | 接続完了後に room_meta パケットを受信 |
✅ PASS (フォールバック成功) |
| 端末内(IndexedDB)復元 | 一度ブラウザを閉じ、再入室時に部屋名が自動復元 | ✅ PASS (完全復元) |
6. まとめと次回予告
ルーム名の二重同期パイプラインとIndexedDB v2へのローカル永続化により、つゔぁいちゃんねるのP2Pチャットは、無機質な通信ツールから「仲間が集まる温かい居場所」へと進化しました。
しかし、デスクトップでの快適な操作性が極まる一方で、スマートフォンの実機テストをしていたメンバーから、またしても画面領域に関する悩みが寄せられました。
「スマホの狭い画面でチャットしているとき、 右側の参加メンバー一覧(ニックネームリスト)が画面の半分近くを占領していて邪魔! 」
「普段はメンバー一覧を隠しておいて、見たいときだけスッと横から引き出せるドロワーメニューにできないか?」
次回は、 【第048回】狭いスマホ画面を有効活用!参加メンバー一覧のドロワー式トグルボタン実装 をお届けします。お楽しみに!
本番サービス : https://2ch.biz/