0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【第047回】部屋の名前もみんなで共有!ルーム名のリアルタイム同期と端末内(localStorage)ログ保存

0
Posted at

こんにちは!2ちゃんねる互換掲示板システム「 つゔぁいちゃんねる(2ch.biz) 」の開発録、第47回です。

前回(第46回)では、暗号化自己完結型ステートレスIDをワンクリックで生成・コピーできるプレビューUIを実装しました。

部屋の作成と共有が劇的にスムーズになった一方で、チャットを使い始めたユーザーたちから、とても素朴で、かつ切実な要望が寄せられました。

「部屋を作るときに『今夜の野球実況』って名前を付けたのに、 参加してくれた友達の画面には room_p08e200... としか出てこない! 」
「後から入ってきた人が『ここ何の話をする部屋?』と迷ってしまう!」
「翌朝、端末に保存されたログを見返したとき、部屋名が残っていないと何のログだか分からない…」

本稿では、サーバーに永続的なログテーブルを作らないというゼロストレージ原則を死守したまま、 ルーム名を参加者全員にリアルタイム同期し、各端末のIndexedDBに安全に復元・永続化するハイブリッドアーキテクチャ を解説します!


1. 「無機質なID」から「血の通ったコミュニティ」へ

これまで画面の最上部には、#room_p08e200775f のような暗号化IDが表示されていました。
技術的には美しくても、人間にとっては「何の部屋か分からない無機質な文字列」でしかありません。

IRC(インターネット・リレー・チャット)でも、チャンネル名(#news や #lounge)とトピック(話題)が常に画面上部に掲げられているからこそ、参加者は会話の文脈を瞬時に把握できます。

私たちが目指したのは、以下の理想的な体験でした:

  1. ホストが付けた部屋名が、参加者全員の画面上部に即座に表示される 。
  2. 会話の最中に部屋名が変更されても、P2Pで全員にリアルタイム反映される 。
  3. ブラウザを閉じても、各自の端末(ブラウザ)に部屋名が過去ログとともに保存され、次に開いたときもタイトル付きで復元される 。

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への反映:トピックバーとウィンドウタイトルの調和

取得・同期されたルーム名は、チャット画面のあらゆる場所に心地よく反映されます。

  1. アプリ上部ヘッダー :
    • 💬 つゔぁいちゃんねる P2Pチャット - ひみつの作戦会議
  2. トピックバー(看板) :
    • # ひみつの作戦会議 | 👤 4人 | 💾 保存: 許可
  3. ブラウザのタブタイトル(<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/

0
0
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?