# 機能設計書 161-Widget会話

## 概要

本ドキュメントは、Chatwoot WebウィジェットにおけるWidget会話管理機能の設計を記載する。この機能はエンドユーザー（訪問者）がWebサイト上のチャットウィジェットを通じて会話を開始・管理するためのAPIを提供する。

### 本機能の処理概要

Widget会話機能は、Webサイト訪問者がチャットウィジェット経由でサポートチームと会話を行うための基盤機能を提供する。訪問者は会話の作成、メッセージ履歴の取得、会話ステータスの変更、入力状態の通知などを行うことができる。

**業務上の目的・背景**：Webサイト訪問者とサポートエージェント間のリアルタイムコミュニケーションを実現するため、ウィジェット側から会話を管理するAPIが必要である。これにより、訪問者は自身の問い合わせ状況を把握し、スムーズなカスタマーサポート体験を得ることができる。

**機能の利用シーン**：
- 訪問者がWebサイトでチャットウィジェットを開き、新規会話を開始する場面
- 既存の会話履歴を閲覧し、過去のやり取りを確認する場面
- 会話中に入力状態を相手に通知する場面
- 問題解決後に会話をクローズする場面
- 会話のトランスクリプトをメールで受け取る場面

**主要な処理内容**：
1. 会話の作成（連絡先情報の更新を含む）
2. 会話情報の取得（既存会話の参照）
3. 最終閲覧時刻の更新
4. 入力状態（タイピング中）のリアルタイム通知
5. 会話ステータスの変更（解決済みへの変更）
6. カスタム属性の設定・削除
7. 会話トランスクリプトのメール送信

**関連システム・外部連携**：
- WebSocket（ActionCable）によるリアルタイム通知
- メール配信システム（ConversationReplyMailer）
- イベントディスパッチャー（タイピングイベント）

**権限による制御**：
- website_tokenによるウィジェット認証が必要
- HMACによる連絡先識別子の検証（オプション）
- end_conversationフラグによる会話終了権限の制御

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 116 | メッセージ画面 | 主画面 | 会話の管理・メッセージ表示 |
| 114 | ホーム画面 | 参照画面 | ウィジェットホーム画面からの会話開始 |
| 115 | プリチャットフォーム画面 | 遷移元画面 | チャット前の情報入力後の会話作成 |

## 機能種別

CRUD操作 / リアルタイム通知

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| website_token | String | Yes | ウェブウィジェットの識別トークン | 有効なトークンである必要がある |
| contact.name | String | No | 連絡先の名前 | - |
| contact.email | String | No | 連絡先のメールアドレス | メール形式 |
| contact.phone_number | String | No | 連絡先の電話番号 | - |
| message.content | String | No | 初期メッセージ内容 | - |
| message.referer_url | String | No | 参照元URL | - |
| message.timestamp | Integer | No | メッセージのタイムスタンプ | - |
| message.echo_id | String | No | クライアント側のエコーID | - |
| typing_status | String | No | 入力状態（on/off） | 'on' または 'off' |
| custom_attributes | Hash | No | カスタム属性 | - |
| custom_attribute | String | No | 削除するカスタム属性のキー | - |

### 入力データソース

- Webウィジェット（JavaScript SDK）からのHTTPリクエスト
- ブラウザ情報（User-Agent、言語設定等）

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| id | Integer | 会話の表示ID（display_id） |
| inbox_id | Integer | インボックスID |
| contact_last_seen_at | Integer | 連絡先の最終閲覧時刻（UNIX時間） |
| status | String | 会話ステータス（open/resolved等） |
| messages | Array | メッセージ一覧（create時） |
| custom_attributes | Hash | カスタム属性 |
| contact | Object | 連絡先情報 |

### 出力先

- JSON形式のHTTPレスポンス
- WebSocket経由のリアルタイム通知

## 処理フロー

### 処理シーケンス

```
1. 会話作成（create）
   └─ 連絡先情報の更新（ContactIdentifyAction）
   └─ 会話の作成（Conversation.create!）
   └─ 初期メッセージの作成（messages.create!）
   └─ 会話情報のリロードと返却

2. 会話取得（index）
   └─ HMAC検証状態に基づく会話検索
   └─ 最新の会話情報を返却

3. 最終閲覧更新（update_last_seen）
   └─ contact_last_seen_atの更新
   └─ メッセージステータス更新ジョブのキュー投入

4. 入力状態切替（toggle_typing）
   └─ イベントディスパッチャーへの通知

5. 会話クローズ（toggle_status）
   └─ end_conversation権限の確認
   └─ ステータスをresolvedに変更

6. カスタム属性設定（set_custom_attributes）
   └─ 会話のcustom_attributesを更新

7. カスタム属性削除（destroy_custom_attributes）
   └─ 指定キーをcustom_attributesから除外

8. トランスクリプト送信（transcript）
   └─ メールアドレス存在確認
   └─ ConversationReplyMailerでメール送信
```

### フローチャート

```mermaid
flowchart TD
    A[リクエスト受信] --> B{アクション判定}
    B -->|create| C[連絡先更新]
    C --> D[会話作成]
    D --> E[メッセージ作成]
    E --> F[レスポンス返却]

    B -->|index| G[会話取得]
    G --> F

    B -->|update_last_seen| H[最終閲覧更新]
    H --> I[ステータス更新Job投入]
    I --> F

    B -->|toggle_typing| J{on/off判定}
    J -->|on| K[TYPING_ONイベント]
    J -->|off| L[TYPING_OFFイベント]
    K --> F
    L --> F

    B -->|toggle_status| M{end_conversation許可?}
    M -->|No| N[403 Forbidden]
    M -->|Yes| O{resolved済み?}
    O -->|No| P[ステータス更新]
    P --> F
    O -->|Yes| F
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-161-01 | 会話終了権限 | end_conversationフラグが有効な場合のみ会話クローズ可能 | toggle_status実行時 |
| BR-161-02 | HMAC検証 | hmac_verifiedがtrueの連絡先は全会話にアクセス可能 | 会話検索時 |
| BR-161-03 | 連絡先名自動設定 | メール・電話・識別子がない場合のみ名前を更新 | 会話作成時 |
| BR-161-04 | 重複会話防止 | lock_to_single_conversationが有効な場合は既存会話を返却 | 会話作成時 |

### 計算ロジック

特になし

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| create | conversations | INSERT | 新規会話レコード作成 |
| create | messages | INSERT | 初期メッセージ作成 |
| create | contacts | UPDATE | 連絡先情報更新 |
| update_last_seen | conversations | UPDATE | contact_last_seen_at更新 |
| toggle_status | conversations | UPDATE | statusをresolvedに更新 |
| set_custom_attributes | conversations | UPDATE | custom_attributes更新 |
| destroy_custom_attributes | conversations | UPDATE | custom_attributesから指定キー削除 |

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

#### conversations

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | account_id | inbox.account_id | インボックスのアカウントID |
| INSERT | inbox_id | inbox.id | インボックスID |
| INSERT | contact_id | contact.id | 連絡先ID |
| INSERT | contact_inbox_id | contact_inbox.id | コンタクトインボックスID |
| INSERT | additional_attributes | ブラウザ情報等 | JSON形式 |
| INSERT | custom_attributes | リクエストパラメータ | JSON形式 |
| UPDATE | contact_last_seen_at | DateTime.now.utc | 最終閲覧時刻 |
| UPDATE | status | :resolved | 会話ステータス |
| UPDATE | custom_attributes | マージまたは除外後の値 | JSON形式 |

#### messages

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | account_id | conversation.account_id | アカウントID |
| INSERT | inbox_id | conversation.inbox_id | インボックスID |
| INSERT | sender_id | contact.id | 送信者（連絡先） |
| INSERT | sender_type | 'Contact' | 送信者タイプ |
| INSERT | content | message.content | メッセージ内容 |
| INSERT | message_type | :incoming | 受信メッセージ |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| 401 | Unauthorized | website_tokenが無効 | 正しいトークンを使用 |
| 403 | Forbidden | end_conversation権限なし | 管理者に権限設定を依頼 |
| 404 | Not Found | 会話が存在しない | 先に会話を作成 |

### リトライ仕様

特になし（同期処理のため）

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

- create: 連絡先更新・会話作成・メッセージ作成をトランザクションで実行
- その他: 単一レコードの更新のため暗黙的トランザクション

## パフォーマンス要件

- レスポンス時間: 500ms以内
- リアルタイム通知: 100ms以内

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

- website_tokenによるウィジェット認証
- HMAC検証による連絡先識別子の改ざん防止
- プライベートメッセージ・アクティビティメッセージのフィルタリング

## 備考

- メッセージ取得時はプライベートメッセージ（private=true）とアクティビティメッセージ（message_type=2）は除外される
- HMAC検証が有効な場合、同一連絡先の全会話にアクセス可能

---

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

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

### 推奨読解順序

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

まず、会話に関連するデータモデルを理解することが重要である。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | web_widget.rb | `app/models/channel/web_widget.rb` | ウェブウィジェットチャネルの設定項目（website_token, hmac_token, feature_flags等） |
| 1-2 | conversation.rb | `app/models/conversation.rb` | 会話モデルの構造、ステータス、関連付け |

**読解のコツ**: Channel::WebWidgetのhas_flagsで定義されている機能フラグ（attachments, emoji_picker, end_conversation等）を確認すること。

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

処理の起点となるコントローラーを確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | base_controller.rb | `app/controllers/api/v1/widget/base_controller.rb` | 認証処理、共通メソッド（conversation, create_conversation等） |
| 2-2 | conversations_controller.rb | `app/controllers/api/v1/widget/conversations_controller.rb` | 各アクションの実装 |

**主要処理フロー**:
1. **1-6行目**: BaseControllerの継承とEvents::Typesのinclude
2. **9-17行目**: create処理（トランザクション内で連絡先更新→会話作成→メッセージ作成）
3. **28-35行目**: update_last_seen処理（最終閲覧時刻更新とジョブ投入）
4. **47-56行目**: toggle_typing処理（タイピングイベントのディスパッチ）
5. **58-66行目**: toggle_status処理（end_conversation権限確認とステータス更新）

#### Step 3: 連絡先識別処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | contact_identify_action.rb | `app/actions/contact_identify_action.rb` | 連絡先のマージ・更新ロジック |

**主要処理フロー**:
- **12-22行目**: performメソッド（識別子・メール・電話番号による既存連絡先マージ）
- **100-109行目**: update_contact（属性更新とアバター取得ジョブ投入）

#### Step 4: ビュー（レスポンス形式）を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | index.json.jbuilder | `app/views/api/v1/widget/conversations/index.json.jbuilder` | 会話取得時のレスポンス形式 |
| 4-2 | create.json.jbuilder | `app/views/api/v1/widget/conversations/create.json.jbuilder` | 会話作成時のレスポンス形式（メッセージ一覧含む） |

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

```
Api::V1::Widget::ConversationsController
    │
    ├─ BaseController (認証・共通処理)
    │      ├─ set_web_widget
    │      ├─ set_contact
    │      ├─ conversations (会話検索)
    │      └─ create_conversation
    │
    ├─ ContactIdentifyAction (連絡先識別)
    │      ├─ merge_if_existing_identified_contact
    │      ├─ merge_if_existing_email_contact
    │      ├─ merge_if_existing_phone_number_contact
    │      └─ update_contact
    │
    ├─ Rails.configuration.dispatcher (イベント通知)
    │      └─ dispatch (TYPING_ON/TYPING_OFF)
    │
    └─ ConversationReplyMailer (メール送信)
           └─ conversation_transcript
```

### データフロー図

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

website_token ────────▶ BaseController#set_web_widget ────▶ @web_widget
                              │
contact_params ───────▶ ContactIdentifyAction#perform ────▶ @contact (更新済み)
                              │
message_params ───────▶ Conversation.create! ─────────────▶ @conversation
                              │
                        messages.create! ─────────────────▶ Message
                              │
                        JSON Response ────────────────────▶ クライアント
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| conversations_controller.rb | `app/controllers/api/v1/widget/conversations_controller.rb` | ソース | Widget会話のメインコントローラー |
| base_controller.rb | `app/controllers/api/v1/widget/base_controller.rb` | ソース | Widget APIの基底コントローラー |
| contact_identify_action.rb | `app/actions/contact_identify_action.rb` | ソース | 連絡先識別・マージ処理 |
| web_widget.rb | `app/models/channel/web_widget.rb` | ソース | ウェブウィジェットチャネルモデル |
| conversation.rb | `app/models/conversation.rb` | ソース | 会話モデル |
| message.rb | `app/models/message.rb` | ソース | メッセージモデル |
| index.json.jbuilder | `app/views/api/v1/widget/conversations/index.json.jbuilder` | テンプレート | 会話取得レスポンス |
| create.json.jbuilder | `app/views/api/v1/widget/conversations/create.json.jbuilder` | テンプレート | 会話作成レスポンス |
| routes.rb | `config/routes.rb` | 設定 | ルーティング定義（389-404行目） |
