# 通知設計書 27-管理者メッセージ通知

## 概要

本ドキュメントは、Etherpadにおいて管理者が管理画面から送信したブロードキャストメッセージを、パッドを閲覧中の全ユーザーに通知する機能の設計について記載する。

### 本通知の処理概要

この通知は、管理者が管理画面（Communication/Shoutページ）からメッセージを送信すると、WebSocket経由で全てのパッドクライアントにブロードキャストされ、Gritterポップアップとして表示されるリアルタイム通知機能である。

**業務上の目的・背景**：サイト管理者がシステムメンテナンス、緊急連絡、お知らせなどを、現在パッドを使用中の全ユーザーにリアルタイムで伝達する必要がある。この通知により、管理者は即座に全ユーザーに重要な情報を届けることができ、システム運用の円滑化を図る。

**通知の送信タイミング**：管理者が管理画面のCommunicationページでメッセージを入力し、送信ボタンを押した時点で、WebSocketを通じて全てのパッドクライアントに即座に配信される。

**通知の受信者**：パッドを閲覧中の全てのブラウザセッション。WebSocketの'shout'イベントを受信した全クライアントに通知が表示される。

**通知内容の概要**：「Admin message」というタイトルで、「[時刻]: メッセージ内容」という形式で管理者が入力したメッセージが表示される。stickyオプションが有効な場合は、ユーザーが明示的に閉じるまで表示が継続する。

**期待されるアクション**：ユーザーはメッセージの内容に応じて適切な対応（作業の保存、ログアウト、ページの更新など）を行うことが期待される。

## 通知種別

アプリ内通知（Gritter 通知、sticky設定可能）

## 送信仕様

### 基本情報

| 項目 | 内容 |
|-----|------|
| 送信方式 | 非同期（WebSocket ブロードキャスト） |
| 優先度 | 高 |
| リトライ | なし（WebSocket接続が切れている場合は受信不可） |

### 送信先決定ロジック

全てのパッドクライアントに対してブロードキャストする。WebSocketで接続中の全クライアントが受信対象となる。

## 通知テンプレート

### Gritter通知

| 項目 | 内容 |
|-----|------|
| タイトル | Admin message |
| スティッキー | 設定可能（管理者が送信時に選択） |

### 本文テンプレート

```
[{timestamp}]: {message}
```

例：
```
[14:30:25]: システムメンテナンスのため、15:00に一時停止します。
```

### 添付ファイル

該当なし

## テンプレート変数

| 変数名 | 説明 | データ取得元 | 必須 |
|--------|------|-------------|-----|
| timestamp | メッセージ送信時刻 | obj.data.payload.timestamp | Yes |
| message | 管理者が入力したメッセージ | obj.data.payload.message.message | Yes |
| sticky | スティッキー表示フラグ | obj.data.payload.message.sticky | No |

## 送信トリガー・条件

### トリガー一覧

| トリガー種別 | トリガーイベント | 送信条件 | 説明 |
|------------|----------------|---------|------|
| 管理者操作 | Communicationページで送信 | メッセージが入力されている | 管理者がshoutメッセージを送信 |

### 送信抑止条件

| 条件 | 説明 |
|-----|------|
| メッセージが空 | 入力フォームでrequired属性により空メッセージは送信不可 |

## 処理フロー

### 送信フロー（管理画面側）

```mermaid
flowchart TD
    A[管理者がメッセージを入力] --> B[stickyオプションを設定]
    B --> C[送信ボタンをクリック]
    C --> D[socket.emit 'shout' イベント]
    D --> E[サーバーがメッセージを受信]
    E --> F[全クライアントにブロードキャスト]
```

### 受信フロー（パッドクライアント側）

```mermaid
flowchart TD
    A[WebSocketで 'shout' イベントを受信] --> B{obj.type === 'COLLABROOM'?}
    B -->|Yes| C[timestampをDateオブジェクトに変換]
    B -->|No| D[処理なし]
    C --> E[$.gritter.add呼び出し]
    E --> F[通知を表示]
    F --> G{sticky設定?}
    G -->|true| H[ユーザーが閉じるまで表示]
    G -->|false| I[自動消去]
```

## データベース参照・更新仕様

### 参照テーブル一覧

該当なし

### 更新テーブル一覧

該当なし（メッセージ履歴はクライアントサイドのstateで管理）

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 対処方法 |
|----------|---------|---------|
| WebSocket切断 | クライアントが切断状態 | 通知は配信されない（リトライなし） |

### リトライ仕様

該当なし

## 配信設定

### レート制限

| 項目 | 内容 |
|-----|------|
| 制限 | なし（管理者の判断に委ねる） |

### 配信時間帯

制限なし（24時間対応）

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

- shoutメッセージの送信は管理者権限を持つユーザーのみ可能
- 管理画面へのアクセスには認証が必要
- メッセージ内容はエスケープされずに表示されるため、管理者は信頼されたユーザーである前提
- クライアント側ではobj.type === 'COLLABROOM'のチェックを行う

## 備考

- メッセージ履歴は管理画面のShoutPageコンポーネント内でstateとして保持される
- 現在オンラインのユーザー数はstatsソケットから取得可能
- stickyオプションはRadixのSwitchコンポーネントで切り替え可能

---

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

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

### 推奨読解順序

#### Step 1: データ構造を理解する

shoutメッセージのペイロード構造を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | ShoutType.ts | `admin/src/components/ShoutType.ts` | ShoutTypeインターフェースの定義 |
| 1-2 | pad.ts | `src/static/js/pad.ts` | 受信メッセージの構造（266-278行目） |

**読解のコツ**: obj.data.payload.message.messageとobj.data.payload.message.stickyの構造に注意。

#### Step 2: 送信側（管理画面）を理解する

管理者がメッセージを送信する処理を特定する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | ShoutPage.tsx | `admin/src/pages/ShoutPage.tsx` | sendMessage関数（35-41行目） |
| 2-2 | ShoutPage.tsx | `admin/src/pages/ShoutPage.tsx` | UIコンポーネント（65-78行目） |

**主要処理フロー**:
1. **35行目**: sendMessage関数定義
2. **36-39行目**: socket.emit('shout', {message, sticky})
3. **40行目**: メッセージ入力欄をクリア

#### Step 3: 受信側（パッドクライアント）を理解する

パッドクライアントでのメッセージ受信と表示を追跡する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | pad.ts | `src/static/js/pad.ts` | socket.on('shout')ハンドラ（266-278行目） |

**主要処理フロー**:
- **266行目**: socket.on('shout', (obj) => {...})
- **267行目**: obj.type === 'COLLABROOM'のチェック
- **268行目**: タイムスタンプをDateオブジェクトに変換
- **269-276行目**: $.gritter.addで通知を表示

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

```
[Admin] ShoutPage.tsx
    │
    ├─ sendMessage() [35行目]
    │      │
    │      └─ socket.emit('shout', {message, sticky}) [36行目]
    │
    │
    ▼ WebSocket ブロードキャスト
    │
    │
[Client] pad.ts
    │
    └─ socket.on('shout') [266行目]
           │
           ├─ obj.type === 'COLLABROOM' チェック [267行目]
           │
           ├─ new Date(obj.data.payload.timestamp) [268行目]
           │
           └─ $.gritter.add() [269行目]
                  │
                  ├─ title: 'Admin message'
                  │
                  ├─ text: '[時刻]: メッセージ'
                  │
                  └─ sticky: obj.data.payload.message.sticky
```

### データフロー図

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

管理者入力 ───────▶ ShoutPage.tsx ───────▶ socket.emit('shout')
                                                │
                                                ▼
                                          サーバー
                                                │
                                                ▼
                                          ブロードキャスト
                                                │
                                                ▼
全パッドクライアント ◀───── socket.on('shout') ◀─────┘
        │
        └─ $.gritter.add()
                │
                ▼
          Gritter通知
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| pad.ts | `src/static/js/pad.ts` | ソース | パッドクライアントでのshoutメッセージ受信と表示 |
| ShoutPage.tsx | `admin/src/pages/ShoutPage.tsx` | ソース | 管理画面のメッセージ送信UI |
| ShoutType.ts | `admin/src/components/ShoutType.ts` | ソース | Shoutメッセージの型定義 |
| gritter.ts | `src/static/js/vendors/gritter.ts` | ソース | 通知表示ライブラリ |
| store.ts | `admin/src/store/store.ts` | ソース | 管理画面のsettingsSocket管理 |
| adminsettings.ts | `src/node/hooks/express/adminsettings.ts` | ソース | サーバーサイドのshoutイベント処理 |
