# 機能設計書 102-プッシュ通知

## 概要

本ドキュメントは、Chatwootにおけるプッシュ通知機能について記述する。FCM（Firebase Cloud Messaging）やWebプッシュを使用して、ユーザーのデバイスにリアルタイム通知を配信する機能である。

### 本機能の処理概要

**業務上の目的・背景**：カスタマーサポート業務において、エージェントは新しい会話や重要なメッセージに迅速に対応する必要がある。プッシュ通知により、アプリケーションがバックグラウンドにある場合や別のタブを開いている場合でも、重要なイベントをリアルタイムで受け取ることができる。これにより応答時間の短縮と顧客満足度の向上に貢献する。

**機能の利用シーン**：
- 新しい会話が作成された時
- 会話がエージェントに割り当てられた時
- 担当会話に新しいメッセージが到着した時
- メンションされた時
- SLA違反が発生した時

**主要な処理内容**：
1. 通知イベント発生時のジョブキューへの登録
2. ユーザーの通知設定（プッシュフラグ）の確認
3. 登録済みサブスクリプションへの通知配信（ブラウザプッシュ、FCM）
4. Chatwoot Hub経由のプッシュ通知（FCM認証情報がない場合）
5. 配信エラー時のサブスクリプション削除

**関連システム・外部連携**：
- Firebase Cloud Messaging (FCM): モバイルアプリ向けプッシュ通知
- Web Push API: ブラウザ向けプッシュ通知（VAPID認証）
- Chatwoot Hub: FCM認証情報がない場合の中継サーバー

**権限による制御**：プッシュ通知は各ユーザーの通知設定（push_flags）に基づいて配信される。ユーザーが該当通知タイプを無効にしている場合、通知は配信されない。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 89 | プロフィール設定画面 | 参照画面 | プッシュ通知の有効/無効設定 |

## 機能種別

非同期処理 / 外部連携

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| notification | Notification | Yes | 通知オブジェクト | - |

### 入力データソース

- DBテーブル: notifications テーブルから通知情報を取得
- DBテーブル: notification_settings テーブルから配信設定を取得
- DBテーブル: notification_subscriptions テーブルから配信先を取得

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| title | String | 通知タイトル（例：「新しい会話 #123 が作成されました」） |
| tag | String | 通知タグ（重複防止用識別子） |
| url | String | 通知クリック時の遷移先URL |
| body | String | 通知本文（FCM用） |

### 出力先

- ブラウザ: Web Push API経由
- モバイルアプリ: FCM経由
- 中継サーバー: Chatwoot Hub経由

## 処理フロー

### 処理シーケンス

```
1. 通知作成（Notificationモデル）
   └─ after_create_commit で process_notification_delivery 実行

2. プッシュ通知ジョブのキューイング
   └─ Notification::PushNotificationJob.perform_later(notification)

3. ジョブ実行（非同期）
   └─ Notification::PushNotificationService.new(notification:).perform

4. 通知設定の確認
   └─ user_subscribed_to_notification? でプッシュ設定を確認

5. サブスクリプション毎に配信
   ├─ ブラウザプッシュ（VAPIDキーがある場合）
   ├─ FCM（Firebase認証情報がある場合）
   └─ Chatwoot Hub（Firebase認証情報がない場合）

6. エラー処理
   └─ 無効なサブスクリプションは自動削除
```

### フローチャート

```mermaid
flowchart TD
    A[通知作成] --> B{プッシュ設定有効?}
    B -->|No| C[処理終了]
    B -->|Yes| D[ジョブキューイング]
    D --> E[サブスクリプション取得]
    E --> F{サブスクリプションあり?}
    F -->|No| C
    F -->|Yes| G[各サブスクリプションに配信]
    G --> H{タイプ判定}
    H -->|browser_push| I[Web Push送信]
    H -->|fcm| J{Firebase認証情報?}
    J -->|Yes| K[FCM送信]
    J -->|No| L{Hub有効?}
    L -->|Yes| M[Chatwoot Hub送信]
    L -->|No| C
    I --> N{エラー?}
    K --> N
    M --> N
    N -->|Yes| O[エラー処理]
    N -->|No| C
    O --> P{無効なサブスクリプション?}
    P -->|Yes| Q[サブスクリプション削除]
    P -->|No| C
    Q --> C
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-102-01 | 購読確認 | プッシュ通知は該当タイプがpush_flagsで有効な場合のみ配信 | 常時 |
| BR-102-02 | 複数配信 | 1つの通知は複数のサブスクリプションに配信可能 | 複数デバイス登録時 |
| BR-102-03 | 無効サブスクリプション削除 | 期限切れや無効なサブスクリプションは自動削除 | 配信エラー時 |
| BR-102-04 | Hub利用条件 | Firebase認証情報がない場合のみChatwoot Hubを利用 | ENABLE_PUSH_RELAY_SERVER=true |

### 計算ロジック

通知タグの生成:
```ruby
"#{notification.notification_type}_#{conversation.display_id}_#{notification.id}"
```

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| 設定確認 | notification_settings | SELECT | プッシュフラグの確認 |
| 配信先取得 | notification_subscriptions | SELECT | 登録済みサブスクリプション取得 |
| サブスクリプション削除 | notification_subscriptions | DELETE | 無効なサブスクリプション削除 |

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

#### notification_subscriptions

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| SELECT | * | user_id = notification.user_id | 全サブスクリプション取得 |
| DELETE | - | 無効なサブスクリプション | エラー時に削除 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| WebPush::ExpiredSubscription | 期限切れ | サブスクリプションの有効期限切れ | サブスクリプション削除 |
| WebPush::InvalidSubscription | 無効 | 無効なサブスクリプション | サブスクリプション削除 |
| WebPush::Unauthorized | 認証エラー | VAPID認証失敗 | サブスクリプション削除 |
| WebPush::TooManyRequests | レート制限 | 送信頻度超過 | ログ出力のみ |
| Errno::ECONNRESET | 接続エラー | ネットワーク障害 | ログ出力のみ |
| Net::OpenTimeout | タイムアウト | 接続タイムアウト | ログ出力のみ |
| Net::ReadTimeout | タイムアウト | 読み取りタイムアウト | ログ出力のみ |

### リトライ仕様

- 基本的にリトライは行わない
- Active Jobのデフォルトリトライ機構に依存

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

- サブスクリプション削除は個別トランザクションで実行
- 配信処理自体はトランザクション外で実行

## パフォーマンス要件

- タイムアウト設定: ssl_timeout=5秒、open_timeout=5秒、read_timeout=5秒
- 非同期処理のためUIブロッキングなし

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

- VAPID: Web Push用の公開鍵/秘密鍵認証
- Firebase認証: サービスアカウント認証情報（JSON形式）
- トークン管理: FCMトークンは有効期限付きで自動更新

## 備考

- FCMトークンは自動更新され、キャッシュされる
- Chatwoot Hubは自己ホスト環境でFirebase設定なしでもプッシュ通知を実現するための中継サービス
- 環境変数 `ENABLE_PUSH_RELAY_SERVER` でHub利用を制御（デフォルト: true）

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | notification.rb | `app/models/notification.rb` | 通知タイプとプッシュメッセージ生成 |
| 1-2 | notification_subscription.rb | `app/models/notification_subscription.rb` | サブスクリプションタイプ（browser_push, fcm） |

**読解のコツ**:
- `NOTIFICATION_TYPES`で通知タイプを確認
- `push_message_title`、`push_message_body`でプッシュ通知の内容生成ロジックを確認

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | notification.rb | `app/models/notification.rb` | 通知作成後のコールバック |

**主要処理フロー**:
- **52行目**: `after_create_commit :process_notification_delivery` - 通知作成後の配信処理
- **155-163行目**: `process_notification_delivery` - ジョブのキューイング

#### Step 3: ジョブ層を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | push_notification_job.rb | `app/jobs/notification/push_notification_job.rb` | ジョブの実行フロー |

**主要処理フロー**:
- **4-6行目**: サービスの呼び出し

#### Step 4: サービス層を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | push_notification_service.rb | `app/services/notification/push_notification_service.rb` | プッシュ通知の配信ロジック |
| 4-2 | fcm_service.rb | `app/services/notification/fcm_service.rb` | FCMクライアントの管理 |

**主要処理フロー**:
- **6-13行目**: `perform` - メイン処理（購読確認、各サブスクリプションへの配信）
- **22-27行目**: `user_subscribed_to_notification?` - 通知設定の確認
- **66-73行目**: `send_browser_push` - ブラウザプッシュ送信
- **90-100行目**: `send_fcm_push` - FCM送信
- **102-108行目**: `send_push_via_chatwoot_hub` - Hub経由送信

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

```
Notification (モデル)
    │
    └─ after_create_commit
           │
           └─ process_notification_delivery
                  │
                  └─ Notification::PushNotificationJob.perform_later
                         │
                         └─ Notification::PushNotificationService.perform
                                │
                                ├─ user_subscribed_to_notification?
                                │      └─ notification_settings.push_#{type}?
                                │
                                └─ notification_subscriptions.each
                                       │
                                       ├─ send_browser_push
                                       │      └─ WebPush.payload_send
                                       │
                                       ├─ send_fcm_push
                                       │      └─ Notification::FcmService
                                       │             └─ FCM.send_v1
                                       │
                                       └─ send_push_via_chatwoot_hub
                                              └─ ChatwootHub.send_push
```

### データフロー図

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

Notification ─────▶ PushNotificationService ─────▶ 外部サービス
(通知データ)              │                              │
                         │                              ├─ Web Push API
                         ▼                              ├─ Firebase FCM
              notification_settings ─────────────▶      └─ Chatwoot Hub
              (配信可否判定)
                         │
                         ▼
              notification_subscriptions
              (配信先情報)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| notification.rb | `app/models/notification.rb` | モデル | 通知データモデル、配信トリガー |
| notification_subscription.rb | `app/models/notification_subscription.rb` | モデル | サブスクリプション管理 |
| push_notification_job.rb | `app/jobs/notification/push_notification_job.rb` | ジョブ | 非同期実行のジョブクラス |
| push_notification_service.rb | `app/services/notification/push_notification_service.rb` | サービス | プッシュ通知の配信ロジック |
| fcm_service.rb | `app/services/notification/fcm_service.rb` | サービス | FCMクライアント管理 |
| vapid_service.rb | `app/services/vapid_service.rb` | サービス | VAPID鍵管理 |
| notification_setting.rb | `app/models/notification_setting.rb` | モデル | 通知設定（push_flags） |
