# 機能設計書 61-プラグイン管理

## 概要

本ドキュメントは、Etherpadのプラグイン管理機能の設計を記載する。この機能により、管理者はWebインターフェースを通じてプラグインのインストール、アンインストール、検索、および更新確認を行うことができる。

### 本機能の処理概要

プラグイン管理機能は、Etherpadの拡張性を実現するための中核的な機能である。npmレジストリ（またはカスタム更新サーバー）から利用可能なプラグイン一覧を取得し、管理者がブラウザからプラグインのライフサイクル全体を管理できるようにする。

**業務上の目的・背景**：Etherpadは豊富なプラグインエコシステムを持ち、機能拡張を容易にするためのプラグインシステムが不可欠である。管理者がコマンドラインを使用せずにプラグインを管理できることで、運用効率が向上し、非技術者でもEtherpadのカスタマイズが可能となる。

**機能の利用シーン**：
- 新機能追加のためにプラグインをインストールする場合
- 不要になったプラグインをアンインストールする場合
- 利用可能なプラグインを検索・確認する場合
- インストール済みプラグインの更新があるか確認する場合

**主要な処理内容**：
1. Socket.IOを通じてリアルタイムにプラグイン操作を実行
2. npmレジストリからプラグイン情報を取得しキャッシュ
3. live-plugin-managerを使用してプラグインのインストール・アンインストール
4. シンボリックリンクによる依存関係管理
5. インストール後のサーバー再起動とフック呼び出し

**関連システム・外部連携**：
- npmレジストリ（プラグイン情報取得）
- static.etherpad.org（デフォルト更新サーバー）
- live-plugin-manager（プラグイン管理ライブラリ）

**権限による制御**：管理者権限（is_admin: true）を持つユーザーのみがプラグイン管理機能にアクセスできる。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 7 | プラグイン管理画面 | 主画面 | プラグインのインストール・アンインストール・検索・更新確認 |
| 9 | ヘルプ/情報画面 | 補助画面 | インストール済みプラグイン一覧を取得して表示 |

## 機能種別

CRUD操作 / データ連携

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| pluginName | string | Yes | インストール・アンインストール対象のプラグイン名 | ep_プレフィックスで始まること |
| searchTerm | string | No | プラグイン検索キーワード | 空文字許可 |
| sortBy | string | No | ソート基準（name/version/last-updated） | 指定値のいずれか |
| sortDir | string | No | ソート方向（asc/desc） | 指定値のいずれか |
| offset | number | No | 検索結果のオフセット | 0以上の整数 |
| limit | number | No | 検索結果の最大件数 | 1以上の整数 |

### 入力データソース

- 管理画面からのSocket.IOメッセージ
- 管理者セッション情報

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| installed | InstalledPlugin[] | インストール済みプラグイン一覧 |
| results | PluginDef[] | 検索結果のプラグイン一覧 |
| updatable | string[] | 更新可能なプラグイン名一覧 |
| plugin | string | 操作対象のプラグイン名 |
| error | string | エラーメッセージ（失敗時） |

### 出力先

- Socket.IOによるクライアントへのリアルタイム送信
- var/installed_plugins.json（インストール済みプラグインの永続化）

## 処理フロー

### 処理シーケンス

```
1. クライアントがSocket.IOで接続
   └─ 管理者権限の確認（is_admin: true）
2. プラグイン操作リクエスト受信
   └─ getInstalled/search/install/uninstall/checkUpdates
3. リクエスト種別に応じた処理実行
   └─ インストール: LinkInstaller.installPlugin()
   └─ アンインストール: LinkInstaller.uninstallPlugin()
   └─ 検索: getAvailablePlugins() + フィルタリング
4. 処理結果をSocket.IOで送信
   └─ results:installed/results:search/finished:install等
5. インストール・アンインストール完了後の後処理
   └─ plugins.update() → reloadSettings() → restartServer
```

### フローチャート

```mermaid
flowchart TD
    A[Socket.IO接続] --> B{管理者権限?}
    B -->|No| C[接続拒否]
    B -->|Yes| D[リクエスト待機]
    D --> E{リクエスト種別}
    E -->|getInstalled| F[インストール済み一覧取得]
    E -->|search| G[プラグイン検索]
    E -->|install| H[プラグインインストール]
    E -->|uninstall| I[プラグインアンインストール]
    E -->|checkUpdates| J[更新確認]
    F --> K[結果送信]
    G --> K
    H --> L[後処理実行]
    I --> L
    J --> K
    L --> K
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-61-01 | プラグイン名規則 | プラグイン名はep_プレフィックスで始まる必要がある | インストール・検索時 |
| BR-61-02 | コアプラグイン保護 | ep_etherpad-liteはアンインストール不可 | アンインストール時 |
| BR-61-03 | キャッシュ有効期限 | プラグイン一覧は10分間キャッシュされる | 検索・更新確認時 |
| BR-61-04 | 依存関係管理 | プラグインの依存関係もシンボリックリンクで管理 | インストール時 |

### 計算ロジック

更新可能判定:
```typescript
semver.gt(latestVersion, currentVersion)
```

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| インストール完了 | var/installed_plugins.json | UPDATE | インストール済みプラグイン一覧の更新 |

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

#### installed_plugins.json

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| UPDATE | plugins | プラグイン名と バージョンの配列 | JSON形式で永続化 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | 接続エラー | npmレジストリへの接続失敗 | エラーメッセージをクライアントに送信 |
| - | インストールエラー | パッケージの取得・展開失敗 | 詳細ログを出力しエラーを通知 |
| - | 権限エラー | 非管理者からのアクセス | 接続を拒否 |

### リトライ仕様

npmレジストリへの接続失敗時は自動リトライなし。ユーザーが手動で再試行する。

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

ファイルシステム操作のため、トランザクション制御は行わない。インストール失敗時はロールバックを試みる。

## パフォーマンス要件

- プラグイン検索: 2秒以内（キャッシュヒット時）
- プラグインインストール: プラグインサイズに依存（通常30秒以内）
- キャッシュ有効期限: 600秒（10分）

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

- 管理者認証必須（is_admin: true）
- 信頼できるnpmレジストリからのみプラグインを取得
- プラグインコードはNode.js上で実行されるため、信頼性の確認が必要

## 備考

- プラグインインストール後はサーバー再起動が必要な場合がある
- live-plugin-managerを使用してpnpm/npmに依存しないインストールを実現

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | Plugin.ts | `admin/src/pages/Plugin.ts` | PluginDef、InstalledPlugin、SearchParams、HelpObjの型定義を確認 |
| 1-2 | PackageInfo.ts | `src/node/types/PackageInfo.ts` | PackageData、PackageInfo型の定義を確認 |

**読解のコツ**: TypeScriptの型定義でプラグインデータの構造を把握する。

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | adminplugins.ts | `src/node/hooks/express/adminplugins.ts` | Socket.IOハンドラの実装 |

**主要処理フロー**:
1. **17-18行目**: Socket.IO名前空間`/pluginfw/installer`でのリスナー登録
2. **19-22行目**: 管理者権限チェック
3. **47-59行目**: getInstalledハンドラ - インストール済みプラグイン一覧取得
4. **86-100行目**: searchハンドラ - プラグイン検索処理
5. **102-112行目**: installハンドラ - プラグインインストール
6. **115-121行目**: uninstallハンドラ - プラグインアンインストール

#### Step 3: プラグインインストーラーを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | installer.ts | `src/static/js/pluginfw/installer.ts` | install/uninstall/search関数の実装 |
| 3-2 | LinkInstaller.ts | `src/static/js/pluginfw/LinkInstaller.ts` | live-plugin-managerを使用した実際のインストール処理 |

**主要処理フロー**:
- **installer.ts 146-163行目**: uninstall関数 - アンインストール実行
- **installer.ts 156-163行目**: install関数 - インストール実行
- **installer.ts 183-215行目**: search関数 - プラグイン検索とフィルタリング
- **LinkInstaller.ts 53-63行目**: installPlugin - live-plugin-managerでのインストール
- **LinkInstaller.ts 77-85行目**: uninstallPlugin - アンインストールとシンボリックリンク削除

#### Step 4: プラグイン定義管理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | plugins.ts | `src/static/js/pluginfw/plugins.ts` | プラグインのロード・更新処理 |
| 4-2 | plugin_defs.ts | `src/static/js/pluginfw/plugin_defs.ts` | プラグイン定義のグローバル格納 |

**主要処理フロー**:
- **plugins.ts 104-124行目**: update関数 - 全プラグインの再読み込み

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

```
HomePage.tsx (フロントエンド)
    │
    ├─ pluginsSocket.emit('install', pluginName)
    │      │
    │      └─ adminplugins.ts: socket.on('install')
    │             └─ installer.ts: install()
    │                    └─ LinkInstaller.installPlugin()
    │                           └─ live-plugin-manager.install()
    │                    └─ onAllTasksFinished()
    │                           └─ plugins.update()
    │                           └─ reloadSettings()
    │                           └─ hooks.aCallAll('restartServer')
    │
    └─ pluginsSocket.emit('search', searchParams)
           │
           └─ adminplugins.ts: socket.on('search')
                  └─ installer.ts: search()
                         └─ getAvailablePlugins()
```

### データフロー図

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

管理者操作 ───▶ Socket.IO ───▶ adminplugins.ts ───▶ Socket.IO
     │                              │                    │
     │                              ▼                    ▼
     │                      installer.ts ───▶ クライアント画面
     │                              │
     │                              ▼
     │                      LinkInstaller ───▶ node_modules/
     │                              │         plugin_packages/
     │                              ▼
     │                      plugins.update() ───▶ plugin_defs
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| adminplugins.ts | `src/node/hooks/express/adminplugins.ts` | ソース | Socket.IOハンドラ |
| installer.ts | `src/static/js/pluginfw/installer.ts` | ソース | インストール・検索ロジック |
| LinkInstaller.ts | `src/static/js/pluginfw/LinkInstaller.ts` | ソース | live-plugin-manager統合 |
| plugins.ts | `src/static/js/pluginfw/plugins.ts` | ソース | プラグインロード管理 |
| plugin_defs.ts | `src/static/js/pluginfw/plugin_defs.ts` | ソース | プラグイン定義格納 |
| HomePage.tsx | `admin/src/pages/HomePage.tsx` | ソース | フロントエンドUI |
| Plugin.ts | `admin/src/pages/Plugin.ts` | ソース | 型定義 |
| installed_plugins.json | `var/installed_plugins.json` | データ | インストール済みプラグイン永続化 |
