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?

【第046回】ワンクリックで秘密基地が完成!暗号化トークン組み込みルームIDの自動発行UI

0
Posted at

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

前回(第45回)では、データベースにレコードを一切保存しないまま、HMAC署名と難読化パッキングによって完全なセキュリティを実現する「暗号化自己完結型ステートレスID」を完成させました。

バックエンドの暗号工学は芸術的なレベルに達しました。しかし、どれほど優れたアーキテクチャであっても、 「一般のユーザーが迷わず直感的に使えるUI」 が伴っていなければ、ただの自己満足に終わってしまいます。

「合言葉を設定したあと、どうやって部屋のURLを友達に教えればいいの?」
「部屋に入る前に、まず掲示板のスレッドに招待リンクを貼ってメンバーを集めたい!」

本稿では、高度な暗号トークンをユーザーに一切意識させず、ボタンをポチッと押すだけで秘密基地が完成する 「ルームID自動発行UI」 の構築記録をお届けします!


1. ユーザーの動線分析:入室する前に「まず配りたい」

チャット部屋を作るときのユーザー心理を観察すると、開発者の想定とは少しズレがあることが分かりました。

【開発者が想定していた動線】
1. 部屋を作成して入室する
2. チャット画面が開く
3. 画面上の「招待URLコピー」ボタンを押す
4. 掲示板やSNSにURLを貼って人を待つ

しかし、実際の掲示板住人のリアルな行動パターンはこうでした。

【実際のユーザーの行動動線】
1. スレ立て画面やレス投稿画面を開いている
2. 「今からここで話そうぜ」と誘うため、部屋に入る前に【招待URLだけ先に手に入れたい】!
3. URLをスレに書き込んでから、ゆっくり部屋に入ってホストとして待機したい

「部屋に入らないとURLが手に入らない」という仕様は、ユーザーにとって余計なタブの行き来を強いるストレスだったのです。


2. 解決策:「🎲 ルームIDを生成」ボタンとプレビューカード

そこで私たちは、部屋作成フォームの中に 「🎲 ルームIDを生成」 ボタンと、発行されたIDをその場で確認・コピーできるプレビューカードを新設しました。


3. 実装の全貌

(1) ロビー画面のHTMLマークアップ

作成フォームの合言葉入力欄の直下に、ID生成ボタンとプレビュー領域を配置しました。

<!-- ルーム作成フォーム内 -->
<div class="form-group">
    <label>参加ポリシー</label>
    <div class="policy-selector">
        <label><input type="radio" name="p2p-policy" value="open" checked> オープン</label>
        <label><input type="radio" name="p2p-policy" value="password"> パスワード制</label>
        <label><input type="radio" name="p2p-policy" value="invite"> 承認制</label>
    </div>
</div>

<div class="form-group" id="p2p-passcode-group" style="display: none;">
    <label for="p2p-create-passcode">合言葉(パスワード)</label>
    <input type="text" id="p2p-create-passcode" placeholder="例: vip2026">
</div>

<!-- ★新設: ID生成アクション行 -->
<div class="p2p-gen-action-row">
    <button type="button" id="p2p-gen-room-id-btn" class="btn btn-secondary p2p-btn-gen-id">
        🎲 ルームIDを生成
    </button>
</div>

<!-- ★新設: 生成結果プレビューボックス(初期は非表示) -->
<div id="p2p-gen-id-box" class="p2p-gen-id-box" style="display: none;">
    <span class="p2p-gen-id-label">発行されたルームID:</span>
    <div class="p2p-gen-id-display">
        <code id="p2p-gen-id-text" class="p2p-gen-id-text"></code>
        <button type="button" id="p2p-copy-gen-id-btn" class="btn btn-sm btn-outline p2p-btn-copy-id">
            📋 コピー
        </button>
    </div>
</div>

<button type="submit" class="btn btn-primary btn-block p2p-btn-launch-host">
    🚀 このルームを作成してホストになる
</button>

(2) クリップボードAPIと視覚フィードバック

モダンブラウザの navigator.clipboard.writeText を活用しつつ、権限エラー時のフォールバック(execCommand)とトースト表示を実装しました。

// 「🎲 ルームIDを生成」ボタンのクリックハンドラ
document.getElementById('p2p-gen-room-id-btn').addEventListener('click', async () => {
    const policy = getSelectedPolicy();
    const passcode = document.getElementById('p2p-create-passcode').value.trim();

    if (policy === 'password' && !passcode) {
        showToast('⚠️ パスワードを入力してください。', 'warning');
        return;
    }

    try {
        const res = await sendSignal('generate_room_id', {
            policy: policy,
            passcode: passcode
        });

        if (res.success && res.roomId) {
            currentPreGeneratedRoomId = res.roomId;

            // プレビューボックスを表示して文字をセット
            const idTextEl = document.getElementById('p2p-gen-id-text');
            idTextEl.textContent = res.roomId;
            document.getElementById('p2p-gen-id-box').style.display = 'block';

            showToast('🎲 ルームIDを生成しました![コピー]して共有できます。', 'info');
        }
    } catch (err) {
        showToast('ID生成に失敗しました: ' + err.message, 'error');
    }
});

// 「📋 コピー」ボタンのクリックハンドラ
document.getElementById('p2p-copy-gen-id-btn').addEventListener('click', async () => {
    const roomId = document.getElementById('p2p-gen-id-text').textContent;
    if (!roomId) return;

    // 招待用完全URLの組み立て
    const fullUrl = `${window.location.origin}${window.location.pathname}#p2p-room=${encodeURIComponent(roomId)}`;

    try {
        await navigator.clipboard.writeText(fullUrl);
        showToast('📋 招待URLをクリップボードにコピーしました!', 'success');
    } catch (e) {
        // フォールバックコピー
        copyTextFallback(fullUrl);
        showToast('📋 招待URLをコピーしました!', 'success');
    }
});

(3) スタイリング:ダークモード連動のカードデザイン

/* ルームID生成プレビューボックス */
.p2p-gen-id-box {
    margin: 12px 0;
    padding: 10px 14px;
    background: #f6f8fa;
    border: 1px dashed #d0d7de;
    border-radius: 6px;
    animation: fadeIn 0.2s ease;
}

.dark-theme .p2p-gen-id-box {
    background: #161b22;
    border-color: #30363d;
}

.p2p-gen-id-display {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    margin-top: 6px;
}

.p2p-gen-id-text {
    font-family: 'Consolas', 'Courier New', monospace;
    font-size: 13px;
    color: #0969da;
    word-break: break-all;
}
.dark-theme .p2p-gen-id-text {
    color: #58a6ff;
}

4. 自動テストと実機での検証

総合テストスクリプトを実行し、以下の完全フローをテストしました。

  1. パスワード設定 ➔ ID生成 :
    • policy="password", passcode="vip123" で生成APIをコール。
    • room_p08e200775f(16文字の難読化ID)が瞬時に返却されることを確認。
  2. 事前生成IDでのホスト入室 :
    • 生成されたIDを使ってそのまま入室し、自身がホストになることを確認。
  3. 未生成時のフォールバック :
    • 「🎲 ルームIDを生成」を押さずに直接「🚀 作成」を押した場合でも、内部で自動発行されてシームレスに入室できることを確認。

5. まとめと次回予告

暗号化ステートレスIDという高度なバックエンド機構の上に、ワンクリックでIDを発行して招待URLをコピーできる極上のUIを被せました。
これにより、ユーザーは数学や暗号の難しさを1ミリも意識することなく、安全なP2Pチャットを自由自在に使いこなせるようになりました。

しかし、部屋ができて人が集まってくると、次に欲しくなるのが 「部屋の名前(トピック)」 です。

「暗号化されたID(room_p08e200...)だけだと、何の部屋なのか分かりにくい」
「『実況部屋』や『雑談』といった部屋の名前をリアルタイムに同期し、ブラウザをリロードしても自分の端末に部屋名とログを保存しておきたい!」

次回は、 【第047回】部屋の名前もみんなで共有!ルーム名のリアルタイム同期と端末内(localStorage)ログ保存 をお届けします。お楽しみに!

本番サービス : https://2ch.biz/

0
0
0

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?