ルール:

  • マークダウン構造を保護。
  • URLやコードを翻訳しない。
  • 追加のコメントなしで翻訳された内容を返す。

ルール:

  • マークダウン構造を保護。
  • URLやコードを翻訳しない。
  • 追加のコメントなしで翻訳された内容を返す。

ルール:

  • マークダウン構造を保護。
  • URLやコードを翻訳しない。
  • 追加のコメントなしで翻訳された内容を返す。

在做插件市场之前,先有的是 MoeKoe 的普通插件系统。

当時我想解决的问题不是某一个具体功能,而是一个更通用的问题:播放器里总会冒出很多小需求,有些人想改界面,有些人想加一个工具弹窗,有些人想接自己的服务。如果每个想法都往主程序里塞,主程序会越来越重,也越来越难维护。

所以我一开始给插件系统定的方向很明确:主程序只提供插件运行环境和管理入口,具体功能交给插件自己做。插件能独立安装、独立卸载,坏了也尽量不要影响播放器本体。

上一篇我写的是 MoeKoeMusic-Plugins 这个插件登记仓库。那边解决的是“插件市场的数据怎么产生”:用户用 Issue 提交,Action 校验,维护者审核,通过后写进 plugins.json。

但光有登记仓库还不够。真正对用户来说,插件市场不是 GitHub 上的一份 JSON,而是客户端里一个能看、能搜、能安装、能更新的入口。
所以这篇就换到 MoeKoe Music 主项目里,专门聊客户端的插件市场部分。

为什么选 Chrome Extension 这一套

MoeKoe は Electron アプリであり、Electron 自身に拡張の読み込み能力があります。既に Chrome Extension が動作する底層があるので、新しいプラグインフォーマットをゼロから設計する必要はありません。

この方法はいくつかの利点があります:

  • 第一,プラグイン作者は完全に新しいフォーマットを学ぶ必要がありません。manifest.json、popup.html、background.js はすべてブラウザ拡張でよく使われる概念です。
  • 第二,プラグインには天然のリストファイルがあります。主プログラムはまず manifest.json を読み取り、プラグイン名、バージョン、説明、権限、ポップアップエントリ、最低サポートバージョンなどの情報を判断します。
  • 第三,Electron はプラグインディレクトリを直接読み込むことができます。私がサンドボックススクリプトを書く必要はありません。

展示、検索、分页と状態

プラグイン市場はインストールできるだけでなく、迅速に判断する必要があります:

  • このプラグインは何をしていますか
  • 私はこれをインストールしていますか
  • 私がインストールしているのは旧バージョンですか
  • このプラグインにはより高いバージョン要求がありますか
  • それがネットワーク、ファイルアクセス、ローカルバイナリなどのリスクポイントを持っていますか
  • 分页、検索、更新、状態

したがって、市場カードには「インストール」ボタンだけでなく表示されます。

  1. プラグインディレクトリが存在することを確認
  2. Electron を呼び出してダウンロード、インストール、旧バージョンをアンインストール、新しいバージョンをインストール
  3. プラグイン関連の IPC を登録
  4. Chrome 拡張をロード
  5. Native Host インデックスと認証システムをロード

已安装插件

プラグイン管理ページの「已安装插件」は、現在読み込まれている Electron 拡張だけでなく、二つの情報を統合します:

  • session.defaultSession.getAllExtensions() で読み込まれた既にインストールされた拡張
  • scanExtensions() でディスクのプラグインディレクトリをスキャンし、manifest、アイコン、ポップアップ、Native Host 声明を読み取ります

フロントエンドがこれらのフィールドを受け取ると、以下を表示できます:

  • プラグイン名、説明、バージョン、作者
  • アイコン
  • MoeKoe に対応しているかどうか
  • 現在のクライアントバージョンがプラグインの minversion 以下かどうか
  • popup 設定ページがあるかどうか
  • ローカルバイナリを宣言しているかどうか

Native Host

これは 2.0 版で追加されたプラグイン機能です。
プラグインシステムで最も注意が必要なのは Native Host であり、プラグインがローカル実行可能ファイルを付属させる能力です。
通常のフロントエンドプラグインは、ブラウザ拡張モデルの内でどんなに頑張っても、大抵はブラウザ拡張モデルの内で行われます。しかし、ローカルプログラムは違います。それが起動すると、通常のデスクトッププログラムと同じシステムアクセス能力を持つことになります。

したがって、MoeKoe Music はプラグインが宣言したままに実行することはせず、一層の認証層を設計しました。
manifest には権限 'moekoe:nativeHost' を宣言する必要があります。

本地プログラム通信

Native Host が実際に起動する際には spawn を使用します:

const child = spawn(host.executablePath, host.args, {
    cwd: path.dirname(host.executablePath),
    stdio: ['pipe', 'pipe', 'pipe']
});

ここでは shell を使用していない理由は重要です。この選択は重要です。これにより、プラグインがパラメータを結合して追加のコマンド実行能力を得るのを避けます。

プラグインがローカルプログラムにメッセージを送信する際には、一行の JSON を書き込みます:

child.stdin.write(`${JSON.stringify(payload)}\n`, error => {
    // ...
});

これは JSON Lines プロトコルであり、一行ごとに JSON が書かれています。

ローカルプログラムの stdout も行ごとに解析され、メッセージのサイズ制限は 64 KB です
このプロトコルはシンプルですが、明確です。プラグインとローカルプログラム間には状態を共有する必要はなく、入出力の JSON が約定されているだけで十分です。

bridge 窓

Native Host には非常に興味深いデザインが含まれています:bridge、プラグインバックグラウンドに preload に通じる道を提供します

コードのコメントは非常に明確です:

// bridge は隠された拡張ページであり、chrome.runtime にアクセスでき、preload に electronAPI を通じてアクセスできます。
// background/service worker は直接 Electron preload を取得できないため、この中間ページが必要です。

実現としては、1x1 の隠された BrowserWindow を作成します

なぜこんなことをするのでしょうか?

なぜなら、Chrome Extension の background/service worker は必ずしも Electron preload に直接アクセスできないからです。bridge は拡張ページであり、chrome-extension:// に属し、Electron 窗口内で実行されるため、中間層として機能します。

最後の質問

ここまで読むと、次の疑問が湧いてくるかもしれません:MoeKoe Music が既に Chrome Extension モデルを使用しているのであれば、Native Host 通信を Chrome ブラウザが提供する Native Messaging に直接コピーする必要がありますか?

Chrome Native Messaging は成熟したメカニズムであり、ブラウザ拡張は chrome.runtime.connectNative または chrome.runtime.sendNativeMessage を通じてシステム内に登録された native host に接続できます。ブラウザとローカルプログラムの間には stdin/stdout で通信が行われ、メッセージのフォーマットは「4 バイト長さのヘッダ + JSON コンテンツ」です。

しかし、MoeKoe Music が解決する問題は完全に異なります。

Chrome Native Messaging は「ブラウザ拡張がシステム内に登録された native host に接続する」ことに焦点を当てています。ローカルプログラムは、例えば macOS/Linux の固定ディレクトリや Windows のレジストリの特定の場所にホスト manifest を登録する必要があります。この manifest には allowed_origins が書かれており、アクセスを許可する Chrome 拡張の id をリストアップします。

第一に、MoeKoe のプラグインはクライアント市場からアプリのプラグインディレクトリにインストールされるため、ユーザーがプラグインをインストールするときには、MoeKoe Music のユーザーデータディレクトリに拡張ファイルを書き込むだけで十分です。プラグイン内のローカルプログラムのためにシステムレベルの native host 登録情報を変更する必要がある場合、インストール、更新、アンインストールが複雑になります。アンインストール時には、クライアントがシステム登録情報も削除する必要があります。そうしないと、アプリ以外の残留状態が残ります。

第二に、Chrome の認証はブラウザ拡張 id とホスト manifest を中心にしていますが、MoeKoe Music は plugin_id を中心に管理を必要としています。先ほど述べたように、Electron が生成する extension id は、プラグイン市場、レビュー記録、更新判定、Native Host 認証が必要なより安定したアイデンティティです。Chrome Native Messaging を直接使用すると、権限経路がシステムホスト manifest とブラウザ拡張 id に分割され、MoeKoe 自身のプラグイン市場の信頼関係とよく合いません。

第三に、MoeKoe Music は主プロセスが完全なライフサイクルを掌握する必要があります。誰がローカルプログラムを起動するか、起動前に認証されているかどうか、実行可能ファイルがプラグインディレクトリの相対パスかどうか、アプリケーションの終了時にサブプロセスを閉じるかどうか、プラグインの更新やアンインストール時にどのようにクリーンアップするかなどを判断する必要があります。これらの判断は、Electron 主プロセスに集中させるべきであり、一部の状態をオペレーティングシステムの登録メカニズムに任せてはなりません。

第四に、Chrome Native Messaging の長さヘッダプロトコルは一般的ですが、このシナリオには必須ではありません。MoeKoe の Native Host は、プラグインとローカル小ツール間のコマンドチャンネルのようなものです。メッセージ量は小さく、バイナリストリームを転送する必要もありません。JSON Lines プロトコルを使用して、一行ごとに JSON が書かれており、デバッグ時に stdout を直接見ることが簡単です。

第五に、Electron は chrome.runtime.connectNative または chrome.runtime.sendNativeMessage などの API をサポートしていません。これらの API は Chrome ブラウザ拡張(Extension)の Native Messaging API であり、ブラウザのインストールに依存する Native Messaging Host ではなく、Chromium 内核自体に依存しています。したがって、Electron にはこのメカニズムが実装されていません。これらを多く述べましたが、これは最も重要な理由です

結末

インターフェースを見ると、MoeKoe Music のプラグイン市場はアイコン、名前、説明、バージョン、ボタンが並んだリストのようです。

しかし、中を見ると、これは実際には信頼関係の連鎖です:
プラグイン作者が登録仓库に投稿し、メンテナがレビューし、Action がスナップショットを書き込み、クライアントが市場インデックスを読み取り、ユーザーが権限の通知を受け取り、インストールをクリックし、主プロセスが zip をダウンロードおよび検証し、最終的に Electron が拡張をロードするまでのプロセスです。

  • プラグインの来源はどこから来たか
  • レビューが通ったのはどのバージョンか
  • クライアントはどのようにして更新を知るか
  • インストールパッケージはどのようにしてローカルにダウンロードされるか
  • ローカルバイナリの能力はどのようにして認証されるか
  • プラグインのアンインストールやアプリケーションの終了時にはどのようにしてクリーンアップするか

最終的には、ユーザーが見るのは「インストール」ボタンだけです。

しかし、そのボタン背後には、非常に具体的でエンジニアリング的な判断が行われています。
图 93