# 機能設計書 163-Widget連絡先

## 概要

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

### 本機能の処理概要

Widget連絡先機能は、Webサイト訪問者がチャットウィジェット経由で自身の連絡先情報（名前、メールアドレス、電話番号、カスタム属性等）を表示・更新するための機能を提供する。また、setUser機能により、Webサイト側で認証済みのユーザー情報をウィジェットに連携することができる。

**業務上の目的・背景**：Webサイト訪問者の識別とプロファイル管理を実現するため、ウィジェット側から連絡先情報を管理するAPIが必要である。これにより、エージェントは訪問者を識別し、過去の会話履歴や属性情報を参照してパーソナライズされたサポートを提供できる。また、HMAC認証により、なりすましを防止してセキュアなユーザー識別が可能になる。

**機能の利用シーン**：
- 訪問者が自身のプロフィール情報を確認する場面
- プリチャットフォームで連絡先情報を入力する場面
- Webサイト側でログイン済みユーザーの情報をウィジェットに連携する場面
- カスタム属性を削除する場面

**主要な処理内容**：
1. 連絡先情報の表示（show）
2. 連絡先情報の更新（update）
3. ユーザー設定（set_user）- HMAC認証対応
4. カスタム属性の削除（destroy_custom_attributes）

**関連システム・外部連携**：
- HMAC認証（SHA256）によるユーザー識別子の検証
- Avatar::AvatarFromUrlJob（アバター取得）

**権限による制御**：
- website_tokenによるウィジェット認証が必要
- HMAC検証（hmac_mandatory時は必須、それ以外はオプション）
- HMAC検証済みの連絡先は全会話にアクセス可能

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 115 | プリチャットフォーム画面 | 主画面 | チャット前の情報入力 |
| 116 | メッセージ画面 | 参照画面 | 連絡先情報の表示 |

## 機能種別

CRUD操作 / 認証処理

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| website_token | String | Yes | ウェブウィジェットの識別トークン | 有効なトークンである必要がある |
| identifier | String | No | ユーザー識別子（Webサイト側のユーザーID等） | - |
| identifier_hash | String | Conditional | 識別子のHMACハッシュ | hmac_mandatory時は必須 |
| email | String | No | メールアドレス | メール形式 |
| name | String | No | 名前 | - |
| phone_number | String | No | 電話番号 | - |
| avatar_url | String | No | アバター画像URL | 有効なURL形式 |
| custom_attributes | Hash | No | カスタム属性 | - |
| additional_attributes | Hash | No | 追加属性 | - |

### 入力データソース

- Webウィジェット（JavaScript SDK）からのHTTPリクエスト
- Webサイト側のアプリケーションからのsetUser呼び出し

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| id | Integer | 連絡先ID |
| has_email | Boolean | メールアドレスが設定されているか |
| has_name | Boolean | 名前が設定されているか |
| has_phone_number | Boolean | 電話番号が設定されているか |
| identifier | String | ユーザー識別子（show時のみ） |
| widget_auth_token | String | 新規連絡先作成時の認証トークン（set_user時のみ） |

### 出力先

- JSON形式のHTTPレスポンス

## 処理フロー

### 処理シーケンス

```
1. 連絡先表示（show）
   └─ 現在の連絡先情報を返却

2. 連絡先更新（update）
   └─ ContactIdentifyActionによる連絡先識別・更新
   └─ 既存連絡先とのマージ処理
   └─ 更新後の連絡先情報を返却

3. ユーザー設定（set_user）
   └─ HMAC検証（必要時）
   └─ 異なる識別子の場合は新規contact_inbox作成
   └─ HMAC検証済みの場合はhmac_verified=true
   └─ ContactIdentifyActionによる連絡先識別・更新
   └─ 新規トークン発行時はwidget_auth_tokenを返却

4. カスタム属性削除（destroy_custom_attributes）
   └─ 指定キーをcustom_attributesから除外
   └─ 連絡先情報を返却
```

### フローチャート

```mermaid
flowchart TD
    A[リクエスト受信] --> B{アクション判定}

    B -->|show| C[連絡先情報取得]
    C --> D[レスポンス返却]

    B -->|update| E[ContactIdentifyAction実行]
    E --> D

    B -->|set_user| F{HMAC検証必要?}
    F -->|Yes| G{HMAC有効?}
    G -->|No| H[401 Unauthorized]
    G -->|Yes| I{識別子が異なる?}
    F -->|No| I
    I -->|Yes| J[新規contact_inbox作成]
    I -->|No| K[既存連絡先使用]
    J --> L[hmac_verified更新]
    K --> L
    L --> M[ContactIdentifyAction実行]
    M --> D

    B -->|destroy_custom_attributes| N[属性除外]
    N --> O[連絡先保存]
    O --> D
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-163-01 | HMAC必須検証 | hmac_mandatoryがtrueの場合、identifier_hashが必須 | set_user時 |
| BR-163-02 | HMAC検証 | identifier_hash = HMAC-SHA256(hmac_token, identifier) | set_user時 |
| BR-163-03 | 識別子変更時の新規作成 | 既存識別子と異なる場合は新規contact_inboxを作成 | set_user時 |
| BR-163-04 | カスタム属性のみの更新時はHMAC不要 | custom_attributesのみでidentifierがない場合はHMACスキップ | set_user時 |
| BR-163-05 | 連絡先マージ | 同一メール・電話番号・識別子の既存連絡先は自動マージ | update, set_user時 |

### 計算ロジック

**HMAC検証**:
```ruby
expected_hash = OpenSSL::HMAC.hexdigest('sha256', hmac_token, identifier.to_s)
valid = (identifier_hash == expected_hash)
```

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| show | contacts | SELECT | 連絡先情報取得 |
| update | contacts | UPDATE | 連絡先情報更新 |
| set_user | contacts | UPDATE/INSERT | 連絡先の更新または作成 |
| set_user | contact_inboxes | INSERT | 新規contact_inbox作成（識別子変更時） |
| set_user | contact_inboxes | UPDATE | hmac_verified更新 |
| destroy_custom_attributes | contacts | UPDATE | custom_attributes更新 |

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

#### contacts

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| SELECT | - | id = @contact.id | 現在の連絡先 |
| UPDATE | identifier | パラメータ値 | ユーザー識別子 |
| UPDATE | name | パラメータ値 | 名前 |
| UPDATE | email | パラメータ値（小文字） | メールアドレス |
| UPDATE | phone_number | パラメータ値 | 電話番号 |
| UPDATE | custom_attributes | マージ後の値 | JSON形式 |
| UPDATE | additional_attributes | マージ後の値 | JSON形式 |

#### contact_inboxes

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | contact_id | 新規連絡先ID | 識別子変更時 |
| INSERT | inbox_id | @web_widget.inbox.id | インボックスID |
| INSERT | source_id | 生成されたID | ソースID |
| UPDATE | hmac_verified | true | HMAC検証済み時 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| 401 | Unauthorized | HMACが無効（hmac_mandatory時） | 正しいidentifier_hashを使用 |
| 401 | Unauthorized | website_tokenが無効 | 正しいトークンを使用 |

### リトライ仕様

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

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

- ContactIdentifyAction内でActiveRecord::Base.transactionを使用
- 連絡先マージ処理はトランザクション内で実行

## パフォーマンス要件

- レスポンス時間: 300ms以内

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

- website_tokenによるウィジェット認証
- HMAC-SHA256による識別子の改ざん防止
- hmac_mandatoryによるセキュアなユーザー識別の強制
- アバターURLの検証（有効なURL形式かチェック）

## 備考

- HMAC検証が有効な連絡先（hmac_verified=true）は同一連絡先の全会話にアクセス可能
- アバターURLが指定された場合、Avatar::AvatarFromUrlJobで非同期取得
- カスタム属性・追加属性は既存値とマージ（上書きではなく深いマージ）

---

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

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

### 推奨読解順序

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

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | contact.rb | `app/models/contact.rb` | 連絡先モデルの構造、カスタム属性 |
| 1-2 | contact_inbox.rb | `app/models/contact_inbox.rb` | 連絡先とインボックスの関連、hmac_verified |
| 1-3 | web_widget.rb | `app/models/channel/web_widget.rb` | hmac_token, hmac_mandatory設定 |

**読解のコツ**: contact_inboxのhmac_verifiedフラグが会話アクセス範囲に影響することを理解すること。

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

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | contacts_controller.rb | `app/controllers/api/v1/widget/contacts_controller.rb` | 各アクションの実装 |

**主要処理フロー**:
1. **6行目**: show処理（ビューに委譲）
2. **8-10行目**: update処理（identify_contactの呼び出し）
3. **12-25行目**: set_user処理（HMAC検証、新規contact_inbox作成、識別）
4. **28-32行目**: destroy_custom_attributes処理
5. **36-43行目**: identify_contactプライベートメソッド
6. **45-47行目**: a_different_contact?（識別子比較）
7. **49-53行目**: validate_hmac（認証検証）
8. **64-70行目**: valid_hmac?（HMAC計算と比較）

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

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

**主要処理フロー**:
- **12-22行目**: performメソッド（識別子・メール・電話番号による既存連絡先マージ）
- **30-34行目**: merge_if_existing_identified_contact
- **36-39行目**: merge_if_existing_email_contact
- **41-47行目**: merge_if_existing_phone_number_contact
- **128-132行目**: custom_attributes（深いマージ）

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | show.json.jbuilder | `app/views/api/v1/widget/contacts/show.json.jbuilder` | 表示時のレスポンス形式 |
| 4-2 | update.json.jbuilder | `app/views/api/v1/widget/contacts/update.json.jbuilder` | 更新時のレスポンス形式 |
| 4-3 | set_user.json.jbuilder | `app/views/api/v1/widget/contacts/set_user.json.jbuilder` | setUser時のレスポンス形式（widget_auth_token含む） |

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

```
Api::V1::Widget::ContactsController
    │
    ├─ BaseController (認証・共通処理)
    │      ├─ set_web_widget
    │      └─ set_contact
    │
    ├─ validate_hmac (HMAC検証)
    │      ├─ should_verify_hmac?
    │      └─ valid_hmac?
    │
    ├─ a_different_contact? (識別子比較)
    │
    ├─ build_contact_inbox_with_token (新規作成)
    │      └─ WidgetHelper
    │
    └─ identify_contact (連絡先識別)
           └─ ContactIdentifyAction#perform
                  ├─ merge_if_existing_identified_contact
                  ├─ merge_if_existing_email_contact
                  ├─ merge_if_existing_phone_number_contact
                  └─ update_contact
```

### データフロー図

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

website_token ────────▶ BaseController#set_web_widget ────▶ @web_widget
                              │
identifier_hash ──────▶ validate_hmac ────────────────────▶ 認証結果
                              │
identifier ───────────▶ a_different_contact? ─────────────▶ 判定結果
                              │
                        build_contact_inbox_with_token ───▶ @contact_inbox
                              │                              @widget_auth_token
contact_params ───────▶ ContactIdentifyAction#perform ────▶ @contact
                              │
                        JSON Response ────────────────────▶ クライアント
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| contacts_controller.rb | `app/controllers/api/v1/widget/contacts_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` | ソース | 連絡先識別・マージ処理 |
| widget_helper.rb | `app/helpers/widget_helper.rb` | ソース | ウィジェットヘルパー（build_contact_inbox_with_token） |
| contact.rb | `app/models/contact.rb` | ソース | 連絡先モデル |
| contact_inbox.rb | `app/models/contact_inbox.rb` | ソース | 連絡先インボックスモデル |
| web_widget.rb | `app/models/channel/web_widget.rb` | ソース | ウェブウィジェットチャネルモデル |
| show.json.jbuilder | `app/views/api/v1/widget/contacts/show.json.jbuilder` | テンプレート | 連絡先表示レスポンス |
| update.json.jbuilder | `app/views/api/v1/widget/contacts/update.json.jbuilder` | テンプレート | 連絡先更新レスポンス |
| set_user.json.jbuilder | `app/views/api/v1/widget/contacts/set_user.json.jbuilder` | テンプレート | setUserレスポンス |
| routes.rb | `config/routes.rb` | 設定 | ルーティング定義（405-410行目） |
