こんにちは!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. 自動テストと実機での検証
総合テストスクリプトを実行し、以下の完全フローをテストしました。
-
パスワード設定 ➔ ID生成 :
-
policy="password",passcode="vip123"で生成APIをコール。 -
room_p08e200775f(16文字の難読化ID)が瞬時に返却されることを確認。
-
-
事前生成IDでのホスト入室 :
- 生成されたIDを使ってそのまま入室し、自身がホストになることを確認。
-
未生成時のフォールバック :
- 「🎲 ルームIDを生成」を押さずに直接「🚀 作成」を押した場合でも、内部で自動発行されてシームレスに入室できることを確認。
5. まとめと次回予告
暗号化ステートレスIDという高度なバックエンド機構の上に、ワンクリックでIDを発行して招待URLをコピーできる極上のUIを被せました。
これにより、ユーザーは数学や暗号の難しさを1ミリも意識することなく、安全なP2Pチャットを自由自在に使いこなせるようになりました。
しかし、部屋ができて人が集まってくると、次に欲しくなるのが 「部屋の名前(トピック)」 です。
「暗号化されたID(room_p08e200...)だけだと、何の部屋なのか分かりにくい」
「『実況部屋』や『雑談』といった部屋の名前をリアルタイムに同期し、ブラウザをリロードしても自分の端末に部屋名とログを保存しておきたい!」
次回は、 【第047回】部屋の名前もみんなで共有!ルーム名のリアルタイム同期と端末内(localStorage)ログ保存 をお届けします。お楽しみに!
本番サービス : https://2ch.biz/