【Chrome】拡張機能を作って API レスポンスを自動保存する

 API とのやり取りをドキュメントに起こす時、API のレスポンスの形式を取得する必要があります。自動テストが入っている様なプログラムならば、そのテストにドキュメントの書き出しを入れたりすることで楽をできますが、その様にできない時もあります。そういった時、もし API との通信を行っているのが web ページならば Google Chrome の拡張機能を利用して API とのやり取りを自動で保存できます。
 Chrome 拡張機能を手軽に作るのに便利なテンプレートである chrome-extension-typescript-starter が GitHub で公開されています。
chibat/chrome-extension-typescript-starter: Chrome Extension TypeScript Starter
 Google Chrome の更新とコードの更新がかみ合わないことが多く、よく package.json のバージョンを変更することになりますが、それを差し引いても大変便利です。
[json] // 省略
“devDependencies”: {
“@types/chrome”: “^0.0.124” // ←この chrome バージョンがよく変わるので手打ちや npm update での最新への書き換え推奨
// 省略
[/json]  これを元にすることで Chrome 拡張機能を簡単に作れます。作り方は次です。

 詳しくは Google 謹製の Chrome 拡張機能開発用チュートリアルです。
Getting Started Tutorial – Google Chrome

 API との通信を傍受、保存する拡張機能は次の様に作れます。

// /public/manifest.json にトップレベルプロパティに以下を追記
// 開発者ツール用の機能を使うという宣言です
  "devtools_page": "dev_tools.html",
// 追記すると例えば次の様になります
{
  "manifest_version": 2,

  "name": "Chrome Extension TypeScript Starter",
  "description": "Chrome Extension, TypeScript, Visual Studio Code",
  "version": "1.0",
  "devtools_page": "dev_tools.html",
  "options_ui": {
    "page": "options.html",
    "chrome_style": true
  },

  "browser_action": {
    "default_icon": "icon.png",
    "default_popup": "popup.html"
  },

  "content_scripts": [
      {
          "matches": ["<all_urls>"],
          "js": ["js/vendor.js", "js/content_script.js"]
      }
  ],

  "background": {
    "scripts": ["js/vendor.js", "js/background.js"]
  },

  "permissions": [
    "storage",
    "<all_urls>"
  ]
}
// /public/dev_tools.html を作成
// ↑のmanifest.jsonと合わせると開発者ツールを開いた時にこのコードが実行されます
<!DOCTYPE html>
<html>
<head>
  <script src="js/dev_tools.js"></script>
</head>
<body>
</body>
</html>
// /src/dev_tools.ts を作成。
// 今回作成する拡張機能のメインです。これの content をいじります
chrome.devtools.network.onRequestFinished.addListener(
  (request) => {
    request.getContent((content: string) => {
        // content にレスポンス内容が格納されているのでそれを処理
    });
  },
);
// /webpack/webpacl.common.js にビルドする開発者ツール用コードを追記
module.exports = {
    entry: {
        popup: path.join(__dirname, srcDir + 'popup.ts'),
        options: path.join(__dirname, srcDir + 'options.ts'),
        background: path.join(__dirname, srcDir + 'background.ts'),
        content_script: path.join(__dirname, srcDir + 'content_script.ts'),
        dev_tools: path.join(__dirname, srcDir + 'dev_tools.ts') // ←この dev_tools 用の部分を追加
    },

 これで開発者ツールを開いた時、通信にフックして通信内容を傍受してなんやかんやする準備が整います。
 余談ですが、Chrome 拡張機能の開発者ツールに関する部分は次にまとまっています。かなり詳しく、これと型ファイルがあれば大体実装に困りません。
Extending DevTools – Google Chrome

 実際に API のレスポンスを保存するコードは次です。

/**
 * URL をページ部のみ切り出す様に整形
 * @see https://github.com/TohkenSenran/TohkenBrowser/blob/master/src/devtools/devtools.ts
 * @param url
 */
const getPage = (url: string): string => {
  let page: string = url.split('?')[0];
  page = page.slice(page.indexOf('/', page.indexOf('/', page.indexOf('/') + 1) + 1) + 1);
  return page;
};

/**
 * Blob にできるデータをダウンロード
 * @param data
 * @param fileName
 * @param mimeType
 */
function downloadData(data: BlobPart, fileName: string, mimeType: string): void {
  const blob = new Blob([data], {
    type: mimeType,
  });
  const url = window.URL.createObjectURL(blob);
  downloadURL(url, fileName);
  setTimeout(function () {
    return window.URL.revokeObjectURL(url);
  }, 1000);
}

/**
 * URL のファイルを fileName でダウンロード.
 * @param url ダウンロード先
 * @param fileName a タグ先の header でファイル名が指定されているとそちらを優先
 */
function downloadURL(url: string, fileName?: string): void {
  const a = document.createElement('a');
  a.href = url;
  a.download = fileName || url.replace(/^.*\//, '');
  document.body.appendChild(a);
  a.style.display = 'none';
  a.click();
  a.remove();
}

chrome.devtools.network.onRequestFinished.addListener(
  (request) => {

    if (request.request && request.request.url) {
      const {url} = request.request;
      // この辺りでURLを絞り込むと必要なAPIだけ操作できます
      request.getContent((content: string) => {
        const json = JSON.parse(content);
        if (json) {
          const saveObject = {
            page: getPage(url), // URL をセット
            request: request.request.postData?.params, // リクエストもセットできる気がするのですがうまくいきません
            response: json // レスポンス内容をセット
          }
          // ↑の保存したい内容のオブジェクトをJSON形式のテキストファイルとしてダウンロード
          downloadData(JSON.stringify(saveObject, null, 2), `${saveObject.page}.json`, 'text/plain')
        }
      });
    }
  },
);

 こんな感じで拡張機能を作ると自動で次の様な API レスポンスの JSON ファイルを保存できます。これと JSON to TypeScript • quicktype を利用すると API レスポンスの TypeScript 用の型ファイルを用意することが楽になります。

{
  "page": "admin/1",
  "response": {
    "success": true,
    "body": {
      "id": 1,
      "email": "admin@example.com",
      "phoneNumber": "053-478-71114",
      "name": "開発用管理者",
      "authMode": 1,
      "createdAt": "2020-09-29 16:52:53",
      "updatedAt": "2020-09-29 18:14:00",
      "deletedAt": null
    },
    "message": "管理者の更新に成功しました。"
  }
}
>株式会社シーポイントラボ

株式会社シーポイントラボ

TEL:053-543-9889
営業時間:9:00~18:00(月〜金)
住所:〒432-8003
   静岡県浜松市中央区和地山3-1-7
   浜松イノベーションキューブ 315
※ご来社の際はインターホンで「316」をお呼びください

CTR IMG