# 機能設計書 57-ユーザー参加処理

## 概要

本ドキュメントは、Etherpadにおけるユーザー参加処理機能の設計仕様を記載する。ユーザーがパッドに参加した際の初期化処理とクライアント変数（clientVars）の送信について詳細に解説する。

### 本機能の処理概要

ユーザー参加処理機能は、ユーザーがパッドに接続した際に、CLIENT_READYメッセージを処理してセッションを確立し、パッドの初期データ（clientVars）をクライアントに送信する機能を提供する。また、既存ユーザーへの新規参加者通知も行う。

**業務上の目的・背景**：
リアルタイム共同編集を開始するためには、クライアントがパッドの現在の状態（テキスト内容、編集履歴、参加者情報など）を取得する必要がある。この初期化処理が適切に行われることで、クライアントは他の参加者と同じドキュメント状態から編集を開始でき、シームレスな共同編集体験が実現される。

**機能の利用シーン**：
- ユーザーがパッドURLにアクセスして編集を開始する場合
- ブラウザリフレッシュ後の再接続
- ネットワーク切断後の自動再接続
- 別のパッドから移動してきた場合

**主要な処理内容**：
1. CLIENT_READYメッセージを受信
2. 認証・認可チェック（セッション、トークン）
3. パッドIDの解決（読み取り専用ID対応）
4. セッション情報の初期化
5. パッドデータと著者情報の取得
6. clientVars構築とクライアントへ送信
7. 同一パッドの他ユーザーに参加通知（USER_NEWINFO）
8. 新規ユーザーに既存ユーザー情報を通知
9. userJoinフック呼び出し

**関連システム・外部連携**：
- Socket.IO: WebSocket経由のリアルタイム通信
- プラグインフック: clientVars, userJoin

**権限による制御**：
アクセス権限に基づき、読み取り専用フラグを設定。グループパッドの場合はセッションIDによる認証も実施。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 2 | パッド編集画面 | 主機能 | CLIENT_READYメッセージを処理してユーザーをパッドに参加させる |

## 機能種別

リアルタイム通信 / セッション管理 / 初期化処理

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| type | string | Yes | メッセージタイプ（CLIENT_READY） | 固定値 |
| padId | string | Yes | 参加するパッドID | 存在するパッドID |
| sessionID | string | No | セッションID（グループパッド用） | createSession APIで作成されたID |
| token | string | Yes | ユーザートークン | クッキーから取得 |
| reconnect | boolean | No | 再接続フラグ | |
| client_rev | number | No | クライアント側の最終リビジョン（再接続時） | 数値 |
| userInfo.name | string | No | ユーザー名 | |
| userInfo.colorId | string | No | ユーザーカラー | #RRGGBB形式 |

### 入力データソース

- Socket.IOメッセージ（クライアントからのWebSocket通信）

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| CLIENT_VARS | object | パッドの初期データ（詳細下記） |
| USER_NEWINFO（他ユーザーへ） | object | 新規参加者情報 |
| USER_NEWINFO（新規ユーザーへ） | object | 既存参加者情報 |

#### clientVars主要項目

| 項目名 | 説明 |
|--------|------|
| collab_client_vars.initialAttributedText | 初期テキスト（属性付き） |
| collab_client_vars.rev | 現在のリビジョン番号 |
| collab_client_vars.historicalAuthorData | 著者履歴データ |
| collab_client_vars.apool | 属性プール |
| padId | パッドID（ユーザー指定のID） |
| readOnlyId | 読み取り専用ID |
| readonly | 読み取り専用フラグ |
| userId | 著者ID |
| userName | ユーザー名 |
| userColor | ユーザーカラー |
| chatHead | 最新チャットメッセージ番号 |
| numConnectedUsers | 接続ユーザー数 |
| savedRevisions | 保存済みリビジョン一覧 |

### 出力先

- Socket.IO経由で各クライアントに送信
- セッション情報（sessioninfos）の更新

## 処理フロー

### 処理シーケンス

```
1. CLIENT_READYメッセージ受信
   └─ handleMessage内で振り分け
2. 認証情報保存
   └─ sessionID, padID, tokenをthisSession.authに保存
3. パッドID解決
   ├─ 存在確認
   └─ 読み取り専用ID解決（readOnlyManager.getIds）
4. セキュリティチェック
   └─ securityManager.checkAccessで権限確認
5. ユーザー情報設定
   └─ 著者名・カラーをAuthorManagerに保存
6. パッドデータ取得
   └─ padManager.getPad
7. 著者データ取得
   └─ getAllAuthors → authorManager.getAuthor（並列）
8. 重複セッション処理
   └─ 同一著者の既存セッションをキック
9. clientVars構築
   └─ 設定値、パッドデータ、プラグイン情報を統合
10. clientVarsフック呼び出し
    └─ プラグインによるカスタマイズ
11. ルームに参加
    └─ socket.join(padId)
12. CLIENT_VARS送信
    └─ socket.emit
13. 他ユーザーに参加通知
    └─ socket.broadcast.to(padId).emit(USER_NEWINFO)
14. 既存ユーザー情報を新規ユーザーに通知
    └─ 各ユーザーのUSER_NEWINFOを送信
15. userJoinフック呼び出し
```

### フローチャート

```mermaid
flowchart TD
    A[CLIENT_READYメッセージ受信] --> B[認証情報保存]
    B --> C{パッド存在確認}
    C -->|存在しない| D[パッドID正規化]
    C -->|存在| E[読み取り専用ID解決]
    D --> E
    E --> F{セキュリティチェック}
    F -->|拒否| Z1[accessStatus送信]
    F -->|許可| G{再接続?}
    G -->|Yes| H[再接続処理]
    G -->|No| I[通常接続処理]
    H --> J[不足リビジョン送信]
    I --> K[パッドデータ取得]
    K --> L[著者データ取得]
    L --> M[重複セッション処理]
    M --> N[clientVars構築]
    N --> O[clientVarsフック]
    O --> P[ルーム参加]
    P --> Q[CLIENT_VARS送信]
    J --> R[他ユーザーに通知]
    Q --> R
    R --> S[既存ユーザー情報送信]
    S --> T[userJoinフック]
    T --> U[完了]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-57-01 | 重複セッション排除 | 同一著者IDの既存セッションはキック（userdup） | 常時 |
| BR-57-02 | カラー形式検証 | colorIdは#RRGGBB形式でなければ無視 | colorId指定時 |
| BR-57-03 | 読み取り専用判定 | 読み取り専用パッドまたは権限不足でreadonlyフラグ設定 | 常時 |
| BR-57-04 | 再接続時リビジョン送信 | client_revから現在headまでのリビジョンを送信 | reconnect=true時 |
| BR-57-05 | セッション情報保持 | padId, author, rev等をsessioninfosに保存 | 常時 |

### 計算ロジック

特になし

## データベース操作仕様

### 操作別データベース影響一覧

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| パッドデータ取得 | pad:{padId} | SELECT | パッドの全データ取得 |
| 著者データ取得 | globalAuthor:{authorId} | SELECT | 著者情報取得 |
| 著者データ更新 | globalAuthor:{authorId} | UPDATE | 名前・カラー更新 |
| リビジョンデータ取得 | pad:{padId}:revs:{n} | SELECT | 再接続時の不足リビジョン |

### テーブル別操作詳細

#### globalAuthor:{authorId}

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| UPDATE | name | userInfo.name | 指定時のみ |
| UPDATE | colorId | userInfo.colorId | 指定時のみ |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| accessStatus | アクセス拒否 | セキュリティチェック失敗 | 適切な認証を行う |
| corruptPad | パッド破損 | パッドデータの読み込み失敗 | 管理者に連絡 |
| userdup | 重複セッション | 同一著者の既存セッションあり | 自動的に既存セッション切断 |
| rejected | 著者ID変更 | セッション中に著者ID変更 | 再認証が必要 |

### リトライ仕様

クライアント側でautomatic reconnection timeoutに基づく自動再接続が行われる。

## トランザクション仕様

読み取り操作が主体のためトランザクション管理は限定的。著者情報の更新はPromise.allで並列実行。

## パフォーマンス要件

- 著者データ取得は並列処理
- 再接続時のリビジョン取得も並列処理
- clientVars送信後に他ユーザー通知（ブロッキングしない）

## セキュリティ考慮事項

- securityManager.checkAccessで認証・認可
- 読み取り専用パッドIDは実パッドIDを隠蔽
- sessionInfosにpadIdを含めるが、クライアントにはauth.padIDのみ送信
- カラーID形式の検証によるインジェクション防止

## 備考

- clientReadyフックは非推奨、userJoinフックを使用推奨
- historicalAuthorDataにはpadIDsは含めない（セキュリティ対策）

---

## コードリーディングガイド

本機能を理解するために参照すべきファイルと、推奨する読み解き順序を以下に示す。

### 推奨読解順序

#### Step 1: セッション情報の構造を理解する

セッション管理の基盤を確認。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | PadMessageHandler.ts | `src/node/handler/PadMessageHandler.ts` | sessioninfosオブジェクトの構造（77-93行目） |

**読解のコツ**: sessioninfosはsocket.idをキーとし、auth、author、padId、readOnlyPadId、readonly、revを持つ。これがユーザーセッションの核心。

#### Step 2: メッセージ受信からCLIENT_READY処理への流れを理解する

メッセージ振り分けとCLIENT_READY特有の処理を確認。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | PadMessageHandler.ts | `src/node/handler/PadMessageHandler.ts` | handleMessage関数内のCLIENT_READY処理 |

**主要処理フロー**:
1. **294-313行目**: CLIENT_READY時の認証情報保存
2. **305-306行目**: パッド存在確認とID正規化
3. **308-310行目**: 読み取り専用ID解決
4. **311-312行目**: 読み取り専用フラグ設定
5. **396行目**: handleClientReady呼び出し

#### Step 3: handleClientReady関数を詳細に理解する

ユーザー参加の核心処理を確認。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | PadMessageHandler.ts | `src/node/handler/PadMessageHandler.ts` | handleClientReady関数（847-1149行目） |

**主要処理フロー**:
- **852行目**: clientReadyフック呼び出し（非推奨）
- **854-864行目**: ユーザー情報設定（名前・カラー）
- **867行目**: パッドデータ取得
- **870行目**: getAllAuthorsで著者一覧取得
- **876-889行目**: historicalAuthorData構築
- **897-909行目**: 重複セッション処理
- **921-982行目**: 再接続処理
- **985-1062行目**: clientVars構築
- **1070-1075行目**: clientVarsフック呼び出し
- **1078行目**: ルーム参加（socket.join）
- **1081行目**: CLIENT_VARS送信
- **1084行目**: セッションrev更新
- **1088-1098行目**: 他ユーザーへの参加通知
- **1100-1139行目**: 既存ユーザー情報の送信
- **1141-1148行目**: userJoinフック呼び出し

### プログラム呼び出し階層図

```
Socket.IO message受信
    │
    └─ handleMessage (PadMessageHandler.ts:273)
           │
           ├─ [CLIENT_READY処理]
           │      ├─ thisSession.auth設定 (294-302)
           │      ├─ padManager.doesPadExist (305)
           │      ├─ padManager.sanitizePadId (306)
           │      └─ readOnlyManager.getIds (308)
           │
           ├─ securityManager.checkAccess (331-332)
           │
           └─ handleClientReady (847)
                  │
                  ├─ hooks.aCallAll('clientReady') [非推奨]
                  │
                  ├─ authorManager.setAuthorName (861)
                  ├─ authorManager.setAuthorColorId (862)
                  │
                  ├─ padManager.getPad (867)
                  │
                  ├─ pad.getAllAuthors (870)
                  │
                  ├─ [並列] authorManager.getAuthor (880-888)
                  │
                  ├─ [重複チェック] _getRoomSockets (897)
                  │      └─ otherSocket.emit('userdup') (907)
                  │
                  ├─ [再接続時] pad.getRevision系 (953-960)
                  │      └─ socket.emit('CLIENT_RECONNECT')
                  │
                  ├─ clientVars構築 (1001-1062)
                  │
                  ├─ hooks.aCallAll('clientVars') (1070)
                  │
                  ├─ socket.join(padId) (1078)
                  │
                  ├─ socket.emit('CLIENT_VARS') (1081)
                  │
                  ├─ socket.broadcast.emit('USER_NEWINFO') (1088)
                  │
                  ├─ [各既存ユーザー] socket.emit('USER_NEWINFO') (1126-1138)
                  │
                  └─ hooks.aCallAll('userJoin') (1141)
```

### データフロー図

```
[入力]                      [処理]                           [出力]

クライアント         ┌─────────────────┐
CLIENT_READY ──────▶│  handleMessage  │
(padId, token,      │  認証情報保存   │
 sessionID,         └────────┬────────┘
 userInfo)                   │
                             ▼
                    ┌─────────────────┐
                    │  security       │
                    │  Manager        │
                    │  checkAccess    │
                    └────────┬────────┘
                             │
                             ▼
                    ┌─────────────────┐
Database ─────────▶ │  handleClient   │
(pad, authors)      │  Ready          │
                    └────────┬────────┘
                             │
        ┌────────────────────┴────────────────────┐
        │                                         │
        ▼                                         ▼
┌───────────────┐                        ┌───────────────┐
│ clientVars    │                        │ sessioninfos  │
│ 構築・送信    │                        │ 更新          │
└───────┬───────┘                        └───────────────┘
        │
        ▼
┌───────────────────────────────────────┐
│            Socket.IO                   │
│  ┌─────────────┐  ┌─────────────────┐ │
│  │CLIENT_VARS  │  │USER_NEWINFO     │ │
│  │(新規ユーザー)│  │(既存ユーザー全員)│ │
│  └─────────────┘  └─────────────────┘ │
└───────────────────────────────────────┘
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| PadMessageHandler.ts | `src/node/handler/PadMessageHandler.ts` | ソース | メッセージ処理・参加処理 |
| SecurityManager.ts | `src/node/db/SecurityManager.ts` | ソース | アクセス権チェック |
| AuthorManager.ts | `src/node/db/AuthorManager.ts` | ソース | 著者情報管理 |
| ReadOnlyManager.ts | `src/node/db/ReadOnlyManager.ts` | ソース | 読み取り専用ID管理 |
| PadManager.ts | `src/node/db/PadManager.ts` | ソース | パッド管理 |
| Pad.ts | `src/node/db/Pad.ts` | ソース | パッドモデル |
| Settings.ts | `src/node/utils/Settings.ts` | ソース | 設定値（clientVarsに使用） |
| SocketIORouter.ts | `src/node/handler/SocketIORouter.ts` | ソース | Socket.IOルーティング |
