🕵️‍♂️

Chrome拡張機能 Notion Tweaks を作ったときの備忘録

2021/11/10に公開

概要

Notionをより便利に使うためのChrome拡張機能「Notion Tweaks」を作りました。この記事はその際の備忘録です。

↓公開先
https://chrome.google.com/webstore/detail/notion-tweaks/fodemmhibeapiocbkojhfpbbiipojcof

↓紹介記事
https://zenn.dev/eetann/articles/2021-11-08-notion-tweaks-intro

Manifest V3

permissions

V2ではmanifest.jsonpermissionstabs やURLを一緒に書いていましたが,V3では以下のように分けて書くことになりました.

{
  ...
  "host_permissions": [
    "https://script.google.com/",
    "https://script.googleusercontent.com/",
    "https://api.notion.com/"
  ],
  "permissions": [
    "tabs",
    "storage"
  ],
  ...
}

このhost_permissions の内容は,ユーザーがChrome拡張機能を追加するときに以下のように表示されます.ただし,「詳細」ボタンを押さないと対象サイトは表示されません.ユーザーに不安を与えたくない場合は,Web Store 申請時の説明欄に書いておくと良いと思います.

when-add-extension

"https://script.googleusercontent.com/" はGASを使うときにリダイレクトが行われるために書きました.

参考: Migrating to Manifest V3 - Chrome Developers

開発時の自動リロード

コードを変更して保存したときに自動リロードする部分については,以前Chrome拡張機能「Amazing Searcher」を作成したときのものをそのまま利用しました.

キーボードショートカットを追加する

"global": true で,ブラウザにフォーカスが無い時でも有効になります.

{
  ...
  "commands": {
    "feature_name": {
      "suggested_key": {
        "default": "Ctrl+Shift+1"
      },
      "description": "hogehooogeeee",
      "global": true
    }
  },
  ...
}

あとはchrome.commands.onCommand.addListener に書くだけです.

chrome.commands.onCommand.addListener(function(command) {
  if (command == "feature_name") {
    console.log("Heke!");
  }
});

参考:

Google Apps Scriptを実行する

Chrome拡張とGASを連携させる例(改) - Qiita を参考に実装しました.

デプロイのURLは「デプロイをテスト」の方,つまりhttps://script.google.com/macros/s/hoge/dev のように末尾がdevになっているものでも実行できます.

chrome.storage.localのラッパー

chrome.storage.localを使うときに,JSONに直したりパースするのを何回も忘れたり,awaitを使いたかったため,書いてみました.TypeScript使い始めたばかりであるため,今後書き直すかもしれません.

await getStorage("hoge") と書けばstorageから取得できるため楽でした.

export const getStorage = (key: string) => new Promise(resolve => {
  chrome.storage.local.get(key, (data: any) => {
    if (typeof data[key] === "undefined") {
      resolve(null);
      return;
    }
    resolve(JSON.parse(data[key]));
    return;
  });
});

interface storageType {
  [key: string]: any;
}

export const setStorage = (key: string, value: any) => new Promise(resolve => {
  const setObj: storageType = {[key]: JSON.stringify(value)}
  chrome.storage.local.set(setObj, () => {
    resolve(true);
  });
});

export const clearStorage = () => new Promise(resolve => {
  chrome.storage.local.clear(() => resolve(true));
});

参考: 非同期処理 — 仕事ですぐに使えるTypeScript ドキュメント

Chrome拡張機能でnpmのライブラリが読み込まれないトラブル

webpackの設定で,

module.exports = {
 ...
  optimization: {
    splitChunks: {
      name: 'chunk',
      chunks(chunk) {
        return chunk.name !== 'background';
      },
    }
  },
  ...
}

のようにchunk.jsとしてバンドルしているのに,manifest.jsonchunk.jsを書くのを忘れていたことが原因でした.

以下のように,content_scriptsjs などでは複数のファイルが指定できます.

{
  ...
  "content_scripts": [
    {
      "matches": ["<https://www.notion.so/*>"],
      "css": ["content.css"],
      "js": ["content.js", "chunk.js"]
    }
  ],
  ...
}

VivaldiでのChrome拡張機能開発

以前はパッケージ化されていないChrome拡張機能をVivaldiに読み込ませてService Workerを使おうとすると, An unknown error occurred when fetching the script.Service worker registration failed といったエラー出ていましたが,現在(4.3.2439.44以降)では解決しました

この問題でVivaldiでの Chrome拡張機能の開発 や GitHubからcloneして自分でビルドした拡張機能を動かすこと を諦めていた方は,トライしてみてはどうでしょうか.

Notion API

integration

NotionのAPIで扱いたいページは,画面右上からintegrationを許可する必要があります.複数のページを扱っていると許可したつもりになってしまうので注意です(時間を溶かしました).
notion-api-integration

データベースへのアイテムの追加

Update databaseはデータベースそのもののタイトルやプロパティのUpdateです.
データベースにアイテムを追加するためにはCreate a pageを使います.
ドキュメントにも,

Creates a new page in the specified database or as a child of an existing page.

と書いてあるのに読み飛ばしてしまい,時間を溶かしちゃいました.

最後に

初めてReactやTypeScriptを使ってみましたが,公式ドキュメントがわかりやすくて使いやすかったです.また,オプションページについてはMUIとReact Hook Formを使ってすぐに作成することができたため,この記事で書くことはありません(ドキュメントもわかりやすかったです).

リンクのまとめです.

Discussion