3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

KongAdvent Calendar 2024

Day 15

InsomniaのPluginを自作する

Last updated at Posted at 2024-12-14

APIクライアントのInsomniaプラグインによって機能拡張することが出来る。
出来ることは色々あるが例えば以下のような事が出来る。

  • 見た目のカスタマイズ(テーマの追加)
  • テスト用データの生成
  • リクエスト発行時にヘッダの自動追加
  • XMLからJSONへの変換

Plugin Hubに公開されているプラグインは自由にインストールして機能拡張出来るが、自作することも出来る。
試しに自作してみる。

プラグインの基礎

プラグインはNode.jsで記述することが出来、最低2つのファイルから構成される。

  • package.json:nodeモジュールのメタデータ
  • main.js:JavaScriptのファイル

JavaScriptのファイル名は固定ではないので、別名にしても問題ない。
プラグインを含むフォルダを以下のディレクトリに展開することで利用可能となる。

  • MacOS: ~/Library/Application\ Support/Insomnia/plugins/
  • Windows: %APPDATA%\Insomnia\plugins\
  • Linux: ~/.config/Insomnia/plugins/

PluginHubなどを使ってインストールした場合にはこのディレクトリにファイル・フォルダが展開される。
アンインストールする場合はこのディレクトリ内に展開されたディレクトリを削除する。

プラグインの作成

Insomniaにプラグインの雛形を生成する機能があるので、これを使ってプラグインを作成してみる。
Insomniaの設定のPluginsの下の方にあるGenerate New Pluginをクリックすると雛形が生成される。
20241129150715.png

クリックするとプラグイン名を聞かれるので、デフォルトのdemo-exampleのままGenerateをクリックする。
するとプラグイン一覧の中にたった今作成したプラグインinsomnia-plugin-demo-exampleが表示される。
※ Insomniaのプラグイン名は必ず先頭にinsomnia-plugin-がつく
20241129151931.png

プラグイン名の横のReveal FolderをクリックするとPluginのインストール先ディレクトリに移動出来るので、これをクリックして移動先のディレクトリでファイル編集してプラグインの実装を行っていく。
構成としては以下のようになっている。

insomnia-plugin-demo-example/
├── main.js
└── package.json

package.jsonには最小限のPluginの情報が記載されており、main.jsはコメントのみで空になっている。
これをいじって色々試してみる。
なお、どういうものが呼び出したり操作したり出来るかはContent Object Referenceが参考になるが、詳細は書かれてないので実際に動かしたり既存のプラグインのコードを読んだほうが理解は進みそう。

リクエスト送信時に処理を追加

最初に、リクエスト送信時にフックする機能を試してみる。
何かのイベントを発生させた際に処理を入れ込みたい時はHookを利用することで、リクエストとレスポンスに対してフックすることが出来る。
Insomniaのプラグインでは以下の2つを使うことでフックが利用できる。

module.exports.requestHooks = Array<(context: RequestHook) => void>;
module.exports.responseHooks = Array<(context: ResponseHook) => void>;

ということで実際に書いてみる。
最初に、リクエスト送信時にHello Worldを画面出力してみる。

main.js
module.exports = {
  requestHooks: [
    async (context) => {
      context.app.alert("Hello World");
    }
  ]
};

requestHooksでリクエスト送信時のフックを指定し、context.app.alertを呼ぶことでメッセージウィンドウが表示できる。
実際にCollectionからリクエストを送った結果がこちら。
20241129172230.png

意図したものが表示された。
少し工夫すれば任意のコマンドなんかも実行できる。
試しにpingを実行してみる。

main.js
const { exec } = require('child_process');

module.exports = {
  requestHooks: [
    async (context) => {
      Command = 'ping localhost -c 1'
      exec(Command, (error, stdout, stderr) => {
        context.app.alert('Command Output', `Output:\n${stdout}`);
      });
    },
  ],
};

実行結果はこちらで、pingの結果が確認できる。
20241129173021.png

もう少し役立つケースとして、リクエストの内容をログ保存してみる。

const fs = require('fs');

module.exports = {
  requestHooks: [
    async (context) => {
      const url = context.request.getUrl();
      const method = context.request.getMethod();
      const headers = context.request.getHeaders();
      const body = context.request.getBody();

      // ログ内容を作成
      const logContent = `
Request URL: ${url}
Method: ${method}
Headers: ${JSON.stringify(headers, null, 2)}
Body: ${body.text || '<No Body>'}
---
`;

      // ファイルに書き込む
      const logFilePath = '/tmp/request_log.txt';
      fs.appendFile(logFilePath, logContent, (err) => {
        if (err) {
          context.app.alert('Error', `Failed to write to file: ${err.message}`);
        }
      });
    },
  ],
};

プラグインをリロード後、GETとPOSTを実行した結果はこちら。

$ cat /tmp/request_log.txt

Request URL: http://httpbin.org/get
Method: GET
Headers: []
Body: <No Body>
---

Request URL: http://httpbin.org/post
Method: POST
Headers: [
  {
    "name": "Content-Type",
    "value": "application/json"
  }
]
Body: {
  "name": "John Doe",
  "email": "john.doe@example.com",
  "password": "secret123"
}
---

ログが残せていることが分かる。

操作方法そのものを追加

処理にフックするのではなく、処理そのもの(アクション)を足すことも出来る。
用意されているものは以下。

  • requestActions:Collection内のリクエストにアクションを追加
  • requestGroupActions:Collection内のリクエストのフォルダにアクションを追加
  • workspaceActions:SpecとCollectionのドキュメント名にアクションを追加
  • documentActions:ドキュメント(SpecとかCollectionとかの画面より前の画面)にアクションを追加

ここではフォルダ内のリクエスト全てを実行する公式のサンプルを使って挙動を確認する。
公式サンプルのコードは以下になる。アイコンだけ変えてみた。

main.js
module.exports.requestGroupActions = [
  {
    label: 'Send Requests',
    icon: 'fa-star',
    action: async (context, data) => {
      const { requests } = data;

      let results = [];
      for (const request of requests) {
        const response = await context.network.sendRequest(request);
        results.push(`<li>${request.name}: ${response.statusCode}</li>`);
      }

      const html = `<ul>${results.join('\n')}</ul>`;

      context.app.showGenericModalDialog('Results', { html });
    },
  },
];

dataにフォルダ内のリクエスト一覧が入っており、for文内のcontext.network.sendRequestでリクエストを送信し、その結果をリクエスト名:レスポンスコードとしてresultsの中に格納している。
全リクエスト送信後、HTMLリスト形式でウィンドウに表示する、となっている。
実際に実行してみる。
追加されたアクションの場所が非常に分かりづらいが、フォルダの横の▼をクリックすると一番下にプラグイン名(今回はSend Requests)が表示されるので、これをクリックすると追加したアクションが実行できる。
20241129182328.png
実行した結果はこちら。
20241129182413.png

実装した通りに表示された。

もう1つ試してみる。
API SpecのLintingは利用すると思うが、プロジェクト固有のルールを適用したい場合、独自のルールを組み込みたくなる。

以下、バージョンが1.0.0以上の場合にエラーとするカスタムプラグインの例となる。

main.js (クリックして表示)
main.js
module.exports.workspaceActions = [{
  label: 'Validate API Spec Version',
  icon: 'fa-exclamation-triangle',
  action: async (context, models) => {
    // Insomniaのデータを取得
    const insomniaDataString = await context.data.export.insomnia({
      includePrivate: false,
      format: 'json',
      workspace: models.workspace,
    });

    let insomniaData;
    try {
      // insomniaDataをJSONにパース
      insomniaData = JSON.parse(insomniaDataString);
    } catch (err) {
      context.app.alert('Error', 'Failed to parse Insomnia Data: ' + err.message);
      return;
    }

    // 必要なモジュールをロード
    const yaml = require('js-yaml');
    const semver = require('semver');

    try {
      // API Specリソースをフィルタリング
      const apiSpecs = insomniaData.resources.filter(resource => resource._type === 'api_spec');

      // バージョンを検証
      for (const apiSpec of apiSpecs) {
        if (apiSpec.contents) {
          let specContent;
          try {
            // YAMLをパース
            specContent = yaml.load(apiSpec.contents);
          } catch (err) {
            context.app.alert('Error', `Failed to parse API Spec "${apiSpec.fileName}": ${err.message}`);
            return;
          }

          // バージョンを確認
          const version = specContent.info?.version;
          if (!version) {
            context.app.alert('Error', `API Spec "${apiSpec.fileName}" does not have a valid version.`);
            return;
          }

          // バージョンが1.0.0以上かチェック
          if (semver.gte(version, '1.0.0')) {
            context.app.alert(
              'Error',
              `API Spec "${apiSpec.fileName}" has an unsupported version (${version}). Version must be less than 1.0.0.`
            );
            return;
          }
        }
      }

      // 成功メッセージ
      context.app.alert('Success', 'All API Specs have valid versions.');
    } catch (err) {
      console.error('Error validating API Spec version:', err);
      context.app.alert('Error', `An error occurred: ${err.message}`);
    }
  },
}];

js-yamlモジュールとsemverモジュールの取り込みのために必要に応じてインストールも行う。

npm install js-yaml
npm install semver

動作確認を行う。
以下のようなversionが1.0.0以上のAPI SpecをInsomnia上で作成する。

openapi: 3.1.0
info:
  contact:
    name: hoge
  version: 1.1.0
:(以下省略)

バージョンが1.0.0以上なのでエラーが出るはずだ。
ファイル名の右の▼から作成したプラグインを指定する。
picture 7

結果は以下のようになった。

20241202085631.png

問題が適切に検出できていることが分かる。
バージョンを0.0.1にして試してみる。
20241202085751.png
今度はエラーが出なくなった。
ということで、API Specのチェックをするようなプラグインも作成できた。

所感

Insomniaのプラグインはまだベータ版らしく、ドキュメント等は揃っていない。
そのため、自作しようにも参考文献が少なくかなりハードルが高いように見える。
ただ、機能そのものはそこそこ揃っており、色んな箇所に任意の機能を差し込めるので、使い込めば使い込むほどカスタマイズの幅が広がりそうだ。
細かい機能が足りない場合は実装にチャレンジしてみるのも良いかも知れない。
あと、既にかなり多数のプラグインが公開されているので、それらも積極活用すると車輪の再発明も避けれてよいかと思う。
(プラグインのページが見づらいが。。。)

おまけ:トラブルシュート

プラグイン作成後、Tools->Reload Pluginsからプラグインをリロードすると、実装が適切ではない場合は以下のエラーが出る。

Failed to load plugin insomnia-plugin-demo-example. Please contact the plugin author sharing the below stack trace to help them to ensure compatibility with the latest Insomnia.

合わせてスタックトレースも確認できるので、ここから問題箇所を特定することが出来るはず。

/Users/hogehoge/Library/Application Support/Insomnia/plugins/insomnia-plugin-demo-example/main.js:3
    {
    ^

SyntaxError: Unexpected token '{'
    at makeContextifyScript (node:internal/vm:185:14)
    at wrapSafe (node:internal/modules/cjs/loader:1267:20)
    at Module._compile (node:internal/modules/cjs/loader:1328:27)
    at Module._extensions..js (node:internal/modules/cjs/loader:1432:10)

あと、Insomniaのデバッグモードも有効。
View->Toggle DevToolsを選択するとブラウザの開発者モードのような画面が出てくる。
コード内にconsole.log("message")を入れておけば、この開発者モードのConsoleから確認できる。
所謂printfデバッグが出来るので、これでトライアンドエラーするのもあり。

3
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?