# 通知設計書 25-JavaScriptエラー通知

## 概要

本ドキュメントは、Etherpadにおいてクライアントサイドで発生したJavaScriptエラー（未捕捉例外やPromise拒否）を検出し、ユーザーに通知する機能の設計について記載する。

### 本通知の処理概要

この通知は、ブラウザで発生した未捕捉のJavaScriptエラーやPromise拒否をグローバルエラーハンドラで検出し、Gritterライブラリを使用したスティッキー通知として画面に表示するとともに、エラー情報をサーバーに送信するエラーレポーティング機能である。

**業務上の目的・背景**：Etherpadはリアルタイム協調編集アプリケーションであり、クライアントサイドのJavaScriptエラーは編集作業の中断やデータ損失につながる可能性がある。この通知により、ユーザーに対してエラーの発生を明確に伝え、ページのリロードを促すとともに、エラー情報をサーバーに送信することで開発者やサイト管理者が問題を把握・対処できるようにする。

**通知の送信タイミング**：ブラウザでwindow.errorイベント（未捕捉例外）またはunhandledrejectionイベント（未処理Promise拒否）が発生した時点で即座に表示される。同一エラーメッセージの重複通知は抑制される。

**通知の受信者**：エラーが発生したブラウザセッションのユーザー。クライアントサイドのJavaScriptによって表示されるため、当該ブラウザのユーザーのみが受信する。

**通知内容の概要**：「エラーが発生しました。Ctrl+F5を押してこのページをリロードしてください。問題が解決しない場合は、このエラーメッセージをWebマスターに送信してください。」という趣旨の警告メッセージとエラー詳細（エラーメッセージ、発生場所、エラーID、URL、UserAgent）が表示される。

**期待されるアクション**：ユーザーはCtrl+F5でページをリロードすることが期待される。問題が繰り返し発生する場合は、表示されたエラー情報をWebマスターに報告することが推奨される。

## 通知種別

アプリ内通知（Gritter スティッキー通知）

## 送信仕様

### 基本情報

| 項目 | 内容 |
|-----|------|
| 送信方式 | 同期（クライアントサイド処理） |
| 優先度 | 高（スティッキー表示） |
| リトライ | なし |

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

エラーが発生したブラウザに対して表示する。同一エラーメッセージが既に表示されている場合は重複表示を抑制する。完全にクライアントサイドで処理される（エラーログのサーバー送信は別途実行）。

## 通知テンプレート

### Gritter通知

| 項目 | 内容 |
|-----|------|
| タイトル | An error occurred |
| スタイルクラス | error |
| 表示位置 | bottom |
| スティッキー | true（自動消去なし） |

### 本文テンプレート

```html
<p><b>Please press and hold Ctrl and press F5 to reload this page</b></p>
<p>If the problem persists, please send this error message to your webmaster:</p>
<div style="text-align: left; font-size: .8em; margin-top: 1em">
  <b class="error-msg">{errorMessage}</b><br>
  at {sourceUrl} at line {lineNumber}<br>
  ErrorId: {errorId}<br>
  {errorType}<br>
  URL: {currentUrl}<br>
  UserAgent: {userAgent}<br>
</div>
```

### 添付ファイル

該当なし

## テンプレート変数

| 変数名 | 説明 | データ取得元 | 必須 |
|--------|------|-------------|-----|
| errorMessage | エラーメッセージ | ErrorEvent.message / PromiseRejectionEvent.reason.message | Yes |
| sourceUrl | エラー発生ファイルURL | ErrorEvent.filename / Error.fileName | Yes |
| lineNumber | エラー発生行番号 | ErrorEvent.lineno / Error.lineNumber | Yes |
| errorId | ランダム生成されたエラー識別子 | randomString(20) | Yes |
| errorType | エラー種別 | 'Uncaught exception' / 'Unhandled Promise rejection' | Yes |
| currentUrl | 現在のページURL | window.location.href | Yes |
| userAgent | ブラウザのUserAgent | navigator.userAgent | Yes |

## 送信トリガー・条件

### トリガー一覧

| トリガー種別 | トリガーイベント | 送信条件 | 説明 |
|------------|----------------|---------|------|
| イベント | window.error | 未捕捉例外が発生 | ErrorEventを受信 |
| イベント | window.unhandledrejection | 未処理Promise拒否 | PromiseRejectionEventを受信 |

### 送信抑止条件

| 条件 | 説明 |
|-----|------|
| 同一メッセージが表示中 | .gritter-item .error-msg内に同じテキストが存在する場合 |

## 処理フロー

### 送信フロー

```mermaid
flowchart TD
    A[JavaScriptエラー発生] --> B{イベント種別}
    B -->|ErrorEvent| C[未捕捉例外として処理]
    B -->|PromiseRejectionEvent| D[未処理Promise拒否として処理]
    C --> E[エラー情報を抽出]
    D --> E
    E --> F[エラーIDをランダム生成]
    F --> G{同一メッセージが表示中?}
    G -->|Yes| H[通知表示をスキップ]
    G -->|No| I[Gritter通知を表示]
    I --> J[サーバーにエラーログを送信]
    J --> K[終了]
    H --> K
```

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

### 参照テーブル一覧

該当なし（クライアントサイドのみの処理）

### 更新テーブル一覧

該当なし（サーバーへのエラーログ送信は/jserrorエンドポイントへのPOST）

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 対処方法 |
|----------|---------|---------|
| 未捕捉例外 | try-catchで捕捉されない例外 | スティッキー通知を表示、サーバーにログ送信 |
| 未処理Promise拒否 | .catchがないPromise拒否 | スティッキー通知を表示、サーバーにログ送信 |

### リトライ仕様

| 項目 | 内容 |
|-----|------|
| リトライ回数 | 0（通知表示のリトライなし） |
| サーバーログ送信リトライ | 0（失敗時は無視） |

## 配信設定

### レート制限

| 項目 | 内容 |
|-----|------|
| 重複抑制 | 同一エラーメッセージは1回のみ表示 |

### 配信時間帯

制限なし（24時間対応）

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

- エラーメッセージには機密情報が含まれる可能性があるため、サーバーへの送信は/jserrorエンドポイントのみ
- スタックトレースには実行コンテキストの情報が含まれる
- エラーIDはランダム文字列であり、追跡用途に使用可能
- XSS対策としてエラーメッセージはdocument.createTextNodeでエスケープされる

## 備考

- グローバルエラーハンドラはpad.init()実行時にpadutils.setupGlobalExceptionHandler()で設定される
- window.onerrorはnullにクリアされ、addEventListenerで新しいハンドラが設定される
- エラーログは/jserrorエンドポイントにJSON形式でPOSTされる
- Error.captureStackTraceが利用可能な場合はスタックトレースも含まれる

---

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

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

### 推奨読解順序

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

ErrorEventとPromiseRejectionEventの構造、エラー情報の抽出方法を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | pad_utils.ts | `src/static/js/pad_utils.ts` | setupGlobalExceptionHandler関数（398-471行目） |

**読解のコツ**: ErrorEventとPromiseRejectionEventで異なるプロパティからエラー情報を抽出している点に注意。

#### Step 2: エントリーポイントを理解する

グローバルエラーハンドラの設定と初期化を特定する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | pad.ts | `src/static/js/pad.ts` | padutils.setupGlobalExceptionHandler()の呼び出し（411行目） |
| 2-2 | pad_utils.ts | `src/static/js/pad_utils.ts` | setupGlobalExceptionHandler関数（398-471行目） |

**主要処理フロー**:
1. **398行目**: setupGlobalExceptionHandler関数定義開始
2. **399行目**: globalExceptionHandlerがnullかチェック
3. **400-466行目**: エラーハンドラ関数の定義
4. **467行目**: window.onerrorをnullにクリア
5. **468-469行目**: errorとunhandledrejectionイベントにハンドラを設定

#### Step 3: 通知表示とサーバーログ送信を理解する

Gritter通知の表示とエラーログのサーバー送信を追跡する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | pad_utils.ts | `src/static/js/pad_utils.ts` | 重複チェック（421-425行目） |
| 3-2 | pad_utils.ts | `src/static/js/pad_utils.ts` | Gritter通知生成（427-451行目） |
| 3-3 | pad_utils.ts | `src/static/js/pad_utils.ts` | サーバーログ送信（453-465行目） |

**主要処理フロー**:
- **421-425行目**: 既に同じエラーメッセージが表示中かチェック
- **427行目**: msgAlreadyVisibleがfalseの場合のみ通知を生成
- **428行目**: txt = document.createTextNodeのショートカット定義
- **429-441行目**: エラー詳細を含むDOM要素を構築
- **443-450行目**: $.gritter.addで通知を表示
- **453-465行目**: $.postでサーバーにエラー情報を送信

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

```
[Client] pad.ts
    │
    └─ pad.init() [410行目]
           │
           └─ padutils.setupGlobalExceptionHandler() [411行目]
                  │
                  └─ [Client] pad_utils.ts
                         │
                         ├─ globalExceptionHandler定義 [400行目]
                         │      │
                         │      ├─ ErrorEvent / PromiseRejectionEvent判定 [404-412行目]
                         │      │
                         │      ├─ エラー情報抽出 [405-416行目]
                         │      │
                         │      ├─ errorId生成（randomString(20)）[418行目]
                         │      │
                         │      ├─ 重複チェック [421-425行目]
                         │      │
                         │      ├─ $.gritter.add() [443行目]
                         │      │
                         │      └─ $.post('/jserror') [454行目]
                         │
                         ├─ window.onerror = null [467行目]
                         │
                         └─ window.addEventListener('error', handler) [468行目]
                              window.addEventListener('unhandledrejection', handler) [469行目]
```

### データフロー図

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

ErrorEvent ────────────┐
                       ├───▶ globalExceptionHandler() ───▶ エラー情報抽出
PromiseRejectionEvent ─┘              │
                                      ├─ 重複チェック
                                      │
                                      ├─ Gritter通知生成
                                      │
                                      ├─ $.gritter.add()
                                      │         │
                                      │         ▼
                                      │   スティッキー通知
                                      │
                                      └─ $.post('/jserror')
                                                │
                                                ▼
                                          サーバーログ
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| pad_utils.ts | `src/static/js/pad_utils.ts` | ソース | グローバルエラーハンドラの実装 |
| pad.ts | `src/static/js/pad.ts` | ソース | パッド初期化でエラーハンドラを設定 |
| gritter.ts | `src/static/js/vendors/gritter.ts` | ソース | 通知表示ライブラリ |
