# 機能設計書 160-Widget設定

## 概要

本ドキュメントは、Chatwootの「Widget設定」機能の設計仕様を定義する。この機能により、Webサイトに埋め込まれたチャットウィジェットが初期化時に必要な設定情報を取得し、連絡先の作成・認証を行う。

### 本機能の処理概要

**業務上の目的・背景**：
Webサイトに埋め込まれたチャットウィジェットが動作するためには、ウィジェットの設定（色、ウェルカムメッセージ、プリチャットフォームなど）と、訪問者を識別するためのトークンが必要である。Widget設定APIは、ウィジェットの初期化時にこれらの情報を提供し、シームレスなチャット体験を実現する。

**機能の利用シーン**：
- 訪問者がWebサイトにアクセスし、ウィジェットが読み込まれる場面
- 新規訪問者の連絡先が自動作成される場面
- 既存訪問者がウィジェットを再度開く場面
- ウィジェットの設定情報（色、メッセージ、営業時間など）を取得する場面

**主要な処理内容**：
1. website_tokenによるWeb Widget識別
2. 連絡先の作成または既存連絡先の取得
3. JWT認証トークンの生成
4. ウィジェット設定情報の返却
5. グローバル設定（ブランド名、ロゴ等）の提供

**関連システム・外部連携**：
- Web Widget JavaScript SDK：クライアント側のウィジェット
- JWT：認証トークンの生成
- IP Lookup：訪問者のIPアドレスから地理情報を取得（オプション）

**権限による制御**：
- 認証不要（website_tokenによる識別のみ）

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 114 | ホーム画面 | 主画面 | ウィジェットホーム画面表示 |
| 115 | プリチャットフォーム画面 | 補助画面 | チャット前の情報入力 |

## 機能種別

設定取得 / 認証トークン生成 / 連絡先管理

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| website_token | String | Yes | Web Widgetのトークン | Channel::WebWidget存在確認 |

### 入力データソース

- URL パラメータ：website_token
- リクエストヘッダー：X-Auth-Token（既存連絡先の場合）
- API：`POST /api/v1/widget/configs`

## 出力仕様

### 出力データ

#### website_channel_config

| 項目名 | 型 | 説明 |
|--------|-----|------|
| allow_messages_after_resolved | Boolean | 解決後のメッセージ許可 |
| api_host | String | APIホストURL |
| auth_token | String | JWT認証トークン |
| avatar_url | String | インボックスのアバターURL |
| csat_survey_enabled | Boolean | CSAT調査有効フラグ |
| disable_branding | Boolean | ブランディング無効フラグ |
| enabled_features | Array | 有効な機能フラグ |
| enabled_languages | Array | 有効な言語一覧 |
| locale | String | ロケール |
| out_of_office_message | String | 営業時間外メッセージ |
| portal | Object | 連携ポータル情報 |
| pre_chat_form_enabled | Boolean | プリチャットフォーム有効フラグ |
| pre_chat_form_options | JSON | プリチャットフォーム設定 |
| reply_time | String | 返信時間目安 |
| timezone | String | タイムゾーン |
| utc_off_set | String | UTCオフセット |
| website_name | String | Webサイト名 |
| website_token | String | ウィジェットトークン |
| welcome_tagline | String | ウェルカムタグライン |
| welcome_title | String | ウェルカムタイトル |
| widget_color | String | ウィジェットカラー |
| working_hours | Array | 営業時間 |
| working_hours_enabled | Boolean | 営業時間有効フラグ |

#### contact

| 項目名 | 型 | 説明 |
|--------|-----|------|
| email | String | メールアドレス |
| id | Integer | 連絡先ID |
| identifier | String | 識別子 |
| name | String | 名前 |
| phone_number | String | 電話番号 |
| pubsub_token | String | PubSubトークン |

#### global_config

| 項目名 | 型 | 説明 |
|--------|-----|------|
| LOGO_THUMBNAIL | String | ロゴサムネイル |
| BRAND_NAME | String | ブランド名 |
| WIDGET_BRAND_URL | String | ウィジェットブランドURL |
| MAXIMUM_FILE_UPLOAD_SIZE | Integer | 最大ファイルアップロードサイズ |
| INSTALLATION_NAME | String | インストール名 |

### 出力先

- JSON API レスポンス

## 処理フロー

### 処理シーケンス

```
1. ウィジェット設定取得
   └─ リクエスト受信 → website_token検証 → グローバル設定取得 → 連絡先取得または作成 → JWTトークン生成 → レスポンス返却
```

### フローチャート

```mermaid
flowchart TD
    A[POST /widget/configs] --> B[set_web_widget]
    B --> C{website_token有効?}
    C -->|No| D[401 Unauthorized]
    C -->|Yes| E{アカウントアクティブ?}
    E -->|No| F[401 Account suspended]
    E -->|Yes| G[set_global_config]
    G --> H[set_contact]
    H --> I{連絡先存在?}
    I -->|Yes| J[既存連絡先使用]
    I -->|No| K[build_contact]
    K --> L[ContactInboxWithContactBuilder]
    L --> M[新規連絡先作成]
    J --> N[set_token]
    M --> N
    N --> O[JWT生成]
    O --> P[JSON Response]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-160-01 | website_token検証 | 有効なwebsite_tokenが必要 | configs API呼び出し時 |
| BR-160-02 | アカウント状態確認 | アカウントがアクティブである必要がある | configs API呼び出し時 |
| BR-160-03 | 連絡先自動作成 | 連絡先が存在しない場合は自動作成 | 新規訪問者時 |
| BR-160-04 | IPルックアップ | ip_lookup機能が有効な場合、作成時IPアドレスを記録 | 新規連絡先作成時 |
| BR-160-05 | トークン有効期限 | JWTトークンはデフォルト180日で期限切れ | トークン生成時 |

### 計算ロジック

```ruby
# JWTトークン生成
def generate_token
  JWT.encode(token_payload, secret_key, algorithm)
end

def token_payload
  (payload || {}).merge(exp: exp, iat: iat)
end

def exp
  iat + expire_in.days.to_i
end

def expire_in
  token_expiry_value = InstallationConfig.find_by(name: 'WIDGET_TOKEN_EXPIRY')&.value
  (token_expiry_value.presence || DEFAULT_EXPIRY_DAYS).to_i  # DEFAULT_EXPIRY_DAYS = 180
end
```

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| configs | channel_web_widgets | SELECT | ウィジェット取得 |
| configs | contacts | SELECT/INSERT | 連絡先取得または作成 |
| configs | contact_inboxes | SELECT/INSERT | 連絡先インボックス取得または作成 |
| configs | installation_configs | SELECT | トークン有効期限設定取得 |

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

#### channel_web_widgets

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| SELECT | - | website_token | ウィジェット識別 |

#### contacts

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| SELECT | - | source_id（contact_inboxes経由） | 既存連絡先取得 |
| INSERT | additional_attributes.created_at_ip | request.remote_ip | IP記録（オプション） |

#### contact_inboxes

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| SELECT | - | source_id | 連絡先インボックス取得 |
| INSERT | source_id | 自動生成 | 新規作成時 |
| INSERT | pubsub_token | 自動生成 | PubSub用トークン |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| 404 | NotFound | website_tokenが無効 | 正しいトークンを指定 |
| 401 | Unauthorized | アカウントが停止中 | アカウント状態を確認 |

### リトライ仕様

特になし

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

- 連絡先作成はContactInboxWithContactBuilderで原子的に実行
- 暗黙的トランザクション

## パフォーマンス要件

- API レスポンス：100ms以内
- トークン生成：10ms以内

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

- website_tokenによる識別（秘密にする必要あり）
- JWTトークンの有効期限管理
- HMAC検証オプション（hmac_mandatory）
- アカウント停止チェック

## 備考

- FlagShihTzuによる機能フラグ管理（attachments, emoji_picker, end_conversation, use_inbox_avatar_for_bot）
- ポータル連携により、Widget内でヘルプ記事を表示可能

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | web_widget.rb | `app/models/channel/web_widget.rb` | Web Widgetのデータモデル |

**読解のコツ**:
- **1-27行目**: Schema Informationでテーブル構造を把握
- **34-40行目**: EDITABLE_ATTRS - 編集可能な属性一覧
- **47-48行目**: has_secure_token - website_token, hmac_tokenの自動生成
- **50-55行目**: FlagShihTzu - 機能フラグ管理
- **57行目**: reply_time enum
- **102-107行目**: create_contact_inbox - 連絡先インボックス作成

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | configs_controller.rb | `app/controllers/api/v1/widget/configs_controller.rb` | 設定API |

**主要処理フロー**:
- **4-7行目**: create - メインエントリーポイント
- **11-18行目**: set_global_config - グローバル設定取得
- **28-33行目**: build_contact - 連絡先作成
- **35-38行目**: set_token - JWTトークン生成

#### Step 3: ベースコントローラーを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | base_controller.rb | `app/controllers/api/v1/widget/base_controller.rb` | 共通処理 |

**主要処理フロー**:
- **5-6行目**: before_action - web_widget, contact設定

#### Step 4: ヘルパーを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | website_token_helper.rb | `app/controllers/concerns/website_token_helper.rb` | トークン処理 |

**主要処理フロー**:
- **2-4行目**: auth_token_params - JWTトークンのデコード
- **6-11行目**: set_web_widget - website_tokenでウィジェット取得、アカウント状態確認
- **13-21行目**: set_contact - 連絡先取得

#### Step 5: トークンサービスを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | token_service.rb | `app/services/widget/token_service.rb` | JWT生成 |

**主要処理フロー**:
- **4-6行目**: generate_token - JWT生成
- **10-12行目**: token_payload - ペイロード構築
- **22-26行目**: expire_in - 有効期限計算（デフォルト180日）

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

```
Api::V1::Widget::ConfigsController#create
    │
    ├─ before_action :set_global_config
    │      └─ GlobalConfig.get(...)
    │
    ├─ before_action :set_web_widget (from WebsiteTokenHelper)
    │      ├─ Channel::WebWidget.find_by!(website_token:)
    │      └─ account.active? チェック
    │
    ├─ before_action :set_contact (from WebsiteTokenHelper)
    │      └─ contact_inboxes.find_by(source_id:)
    │
    ├─ build_contact
    │      └─ ContactInboxWithContactBuilder.new.perform
    │             ├─ Contact.create
    │             └─ ContactInbox.create
    │
    └─ set_token
           └─ Widget::TokenService.new(payload:).generate_token
                  └─ JWT.encode(payload, secret_key, algorithm)
```

### データフロー図

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

POST Request        →   ConfigsController#create    →   JSON Response
{ website_token }        │                              { website_channel_config,
                        ├─ set_web_widget                 contact,
                        │     └─ WebWidget取得             global_config }
                        │
                        ├─ set_global_config
                        │     └─ GlobalConfig取得
                        │
                        ├─ build_contact
                        │     └─ ContactInboxWithContactBuilder
                        │
                        └─ set_token
                              └─ Widget::TokenService
                                       ↓
                              [JWT生成]
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| web_widget.rb | `app/models/channel/web_widget.rb` | モデル | Web Widgetデータモデル |
| configs_controller.rb | `app/controllers/api/v1/widget/configs_controller.rb` | コントローラー | 設定API |
| base_controller.rb | `app/controllers/api/v1/widget/base_controller.rb` | コントローラー | 共通処理 |
| website_token_helper.rb | `app/controllers/concerns/website_token_helper.rb` | ヘルパー | トークン処理 |
| token_service.rb | `app/services/widget/token_service.rb` | サービス | JWT生成 |
| create.json.jbuilder | `app/views/api/v1/widget/configs/create.json.jbuilder` | ビュー | レスポンス生成 |
| contact_inbox_with_contact_builder.rb | `app/builders/contact_inbox_with_contact_builder.rb` | ビルダー | 連絡先作成 |
