自分用のChrome拡張機能を作ってみた

# 何を作ったのか（と、簡単な経緯の説明）

日頃、仕事でもプライベートでもブラウザのタブがとんでもない数になることって多いと思います。
自分は都度適当なブックマークフォルダに保存してタブを消すようにしているのですが、まぁ後になっても見ないわけです。登録したことすら忘れたりするので。
そこから、 **登録したブックマークを、時間差で通知してくれる仕組みがあったりしたらいいなぁ** とふと思ったのがきっかけで、そういった機能を持った拡張機能を作ってみました。

# 作ったもの

https://github.com/ryuta-watanabe/readme-later

まだ公開出来るほどの完成度に至っていないですが、とりあえず動作はするはずなので公開しておきます。

# どういった機能がある拡張機能か

1. 拡張機能の有効化時、ブックマークフォルダを一つ作る
2. 対象のWebページを開いた状態で拡張機能のアイコンをクリックすると、1で生成したブックマークフォルダにブックマーク登録される 
3. ブックマーク登録した翌日の10時に、2で登録したWebサイト情報を通知する
4. 通知をクリックすると、Webサイトを新規タブで開く

現状、上手く実現出来ていない機能もありますが、機能としてはシンプルかと思います。
通知と飛ばす対象とするブックマークを絞るため、それ用のブックマークフォルダを生成して、登録されたブックマーク情報を翌日の10時に「読んだ？」と通知するような内容です。

# つまづいたポイント

今回、初めてChrome拡張機能を作ったこともあり、いくつかつまづいたポイントがあったので以下に記載します。

## 1. 通知をクリックしたときに取得出来るパラメータがID一つしかない

通知のクリック時に処理を加えるには、 `chrome.notifications.onClicked.addListener` イベントに処理を紐付ける必要があります。
このイベントの詳細は [こちら](https://developer.chrome.com/docs/extensions/reference/notifications/#event-onClicked) にあるのですが、パラメータが　**notificationId** のみとなります。

つまり、今回のケースで言う「通知をクリックすると、Webサイトを開く」という動作を実現するには、
このnotificationIdに対して、 **URLを直接渡すか、もしくはブックマーク情報を一意に特定出来る情報を渡す** 必要があります。
URLを受け取り、それをクリックしてWebサイトを表示する場合は以下のようにします。

```javascript
chrome.notifications.onClicked.addListener(async (notificationId) => {
  // notificationId = WebサイトのURL
  await chrome.tabs.create({ url: notificationId });
});
```

では、notificationIdがどの時点で決定するのかというと、通知の生成時（ `chrome.notifications.create` )です。
生成するメソッドは[こちら](https://developer.chrome.com/docs/extensions/reference/notifications/#method-create) で説明されており、第一引数にnotificationIdが任意に設定出来る旨が記載されています。
今回のケースでいうと、第一引数へURLやブックマーク情報を一意に特定出来る情報を設定します。
ブックマークのURLを取得して、通知を表示するコードは以下のようなイメージです。

```javascript
const bookmarkUrl = // ここにブックマークのURLを取得する処理を書く
const options = {
  type: 'basic',
  title: '通知のタイトル',
  message: '通知のメッセージ',
  iconUrl: '通知を表示するときに一緒に表示するアイコンのパス'
}
await chrome.notifications.create(bookmarkUrl, options);
```

:::note warn
ブックマークの情報へアクセスする方法については、後々記事を更新しようと思います。（まだ整理出来ておらず…）
APIリファレンスは [こちら](https://developer.chrome.com/docs/extensions/reference/bookmarks/) になります。
:::

## 2. 定期的に処理を実行出来るAPIを探すのに手間取った

これは単純な見落としになってしまいますが、APIリファレンスから「定期的に処理を実行出来るAPIはあるのか」というのを探し、理解するのに手間取りました。
定期的な処理になるので `schedule` や `Timer` というような処理があるんじゃないかと思いこんで探したのですが、Chrome拡張機能のAPIにおいては、 [alarms](https://developer.chrome.com/docs/extensions/reference/alarms/) というAPIを使うことで実現出来そうです。

例えば拡張機能がインストールされるとき、 **「翌日10時に最初のトリガーを発生させて、以降は24時間ごとに定期実行したい」** というアラームを設定するのは以下のようにすれば動くと思います。
この部分の動作検証が甘く、もし違うところがあればご指摘いただけますとありがたいです！

```javascript
// 翌日のAM10時を設定する
const getTimestampForNextAlarm = () => {
	const now = new Date();
	const nextAlarm = new Date();
	nextAlarm.setHours(10, 0, 0, 0);

	if (now > nextAlarm) {
		nextAlarm.setDate(nextAlarm.getDate() + 1);
	}

	return nextAlarm.getTime();
};
chrome.runtime.onInstalled.addListener(async (details) => {
	// 翌日10時にアラームをセットして、それ以降毎日10時にアラームが起動する設定
	await chrome.alarms.create('アラームの名前（任意の文字列）', {
		periodInMinutes: 1440,
		when: getTimestampForNextAlarm(),
	});
});
```

:::note warn
上記は拡張機能のインストール時にバックグラウンドで処理をする場合のサンプルになるので、manifest.jsonでbackgroundプロパティの設定が必要になります。
manifest.jsonの説明は[こちら](https://developer.chrome.com/docs/extensions/mv3/manifest/)にあります。
:::

これに対して、アラームのイベントが実行されたときの処理は以下のように記述します。

```javascript
chrome.alarms.onAlarm.addListener(async (alarm) => {
	if (alarm.name === '↑で設定したアラーム名') {
      // ここに実行したい処理を記述する
    };
});
```

# 今後の課題

- いきあたりばったりでコードを書いてしまったので、リファクタリングを最優先で対応したい
- 通知が複数ある場合、一気に出力されてしまうので処理順を制御したい
- ブックマークのオブジェクトについて、日付に関するプロパティがないと勘違いしてストレージにもデータを保存してしまっているので、ブックマークだけを参照する形にしたい
- サービスワーカーがあまり理解出来ていないので、きちんと理解して制御したい

# まとめ

今回初めてchrome拡張機能を作りましたが、manifest.jsonのプロパティと紐づく要素や処理を理解するのに苦戦しました…
少しずつ理解が進んできている感触があるので、年末年始に課題部分を解消していきたいと思います！