📟

Service WorkerのCache APIを利用してキャッシュを削除する

2023/05/25に公開

はじめに

お久しぶりです、表題の通りですが今回は、リハビリに軽めの記事にしてみました。
少し前話題になった TBSのニュースサイトヤバない?(追記)
に対して、キャッシュストレージ周りが異様に多いと取り沙汰され、
https://blog.hinaloe.net/2023/04/27/chrome-too-large-cache-storage/
にて、表示されてる見た目上のキャッシュストレージの容量と実態とはかなり乖離しているという記事が出回るほどに、 すでにService Worker周りは必修科目になってる... とヒシヒシと感じています。

肥大化したキャッシュストレージを削除する方法を学んで、リスクヘッジをしよう!
Angular Service Worker を例に、肥大化したキャッシュを削除する方法について書いていこうと思います。

Service Worker とは

https://developer.mozilla.org/ja/docs/Web/API/Service_Worker_API
サービスワーカーは、あるオリジンとパスに対して登録されたイベント駆動型のワーカーです。 JavaScript ファイルの形を取り、ナビゲーションやリソースへのリクエストを横取りや改変したり細かい粒度でリソースをキャッシュすることで関連付けられたウェブページやサイトを制御し、それぞれの状況(もっとも顕著な例は、ネットワークが利用できないとき)にアプリがどのように振る舞うかを完全に制御することができます。

つまり、サーバーとクライアントの仲を保つ中間サービスですね。
サービスワーカーからデータを取得することを fetch と呼んだりします。

キャッシュストレージ とは

Service Worker の一部です。
このあたりは、web.dev に詳しく書かれているのですが、簡単に説明すると、HTTP経由で取得された、データをサービスワーカーが保持しておくためのストレージになります。

保存できるもの

キャッシュには、それぞれ HTTP のリクエストとレスポンスを意味する Request オブジェクトと Response オブジェクトのペアが格納可能で、リクエストとレスポンスには、HTTP経由で転送できるデータなら種類を問わず何でも含めることができます。詰まるところ、JSONでもPNG,JPG,JavaScript,CSSなどHTTP経由で送ることのできるデータ全てをキーマッピングで保存できます。便利なストレージですね。

Angular Service Worker のキャッシュ削除

以下で、一般的なCahce APIを利用した、サービスワーカーのキャッシュ削除と一緒に、
Angular Service Worker のキャッシュ削除について解説します。

Cahce APIを利用して、特定のキャッシュを削除する

const deleteCacheAll = async (): Promise<void> => {
                // ここでサービスワーカーのキャッシュのキーを取得します
	const cacheKeysAll = ['key'] 
        if (cacheKeysAll.length === 0) {
            return;
        }
        const promiseList = cacheKeysAll.flatMap(cacheKey => async () => {
		//該当のキーのキャッシュストレージを呼び出します
		const cache = await window.caches.open(cacheKey);
		// 呼び出したのち、キャッシュストレージ内のキャッシュデータを削除します。
		(await cache.keys()).map(request => cache.delete(request));
        });
	// たくさんあるので、ついでに並列実行します。
        await Promise.all(promiseList.map(deleteCache => deleteCache()));
}

Angular Service Worker のキャッシュキーを取得する

Angular Service Workerのキャッシュストレージに保存されるデータのキーは、以下の6つみたい(他にもあるかも)。ハッシュキーは、サービスワーカーのバージョンのハッシュと同じで、ngsw:/:db:controlのストレージに保存されている、JSONデータから取得可能でした。

`ngsw:/:${hash}:assets:app:meta`,
`ngsw:/:${hash}:assets:app:cache`,
`ngsw:/:${hash}:assets:assets:meta`,
`ngsw:/:${hash}:assets:assets:cache`,
`ngsw:/:${hash}:assets:fonts:meta`,
`ngsw:/:${hash}:assets:fonts:cache`,

まとめると、以下のようなコードになります。

const SW_CACHE_PREFIX = 'ngsw:/' as const;
const getServiceWorkerCacheKeysAll = async (): Promise<string[]> =>  {
	const dbControlCache = await window.caches.open(`${SW_CACHE_PREFIX}:db:control`);
	const response = await dbControlCache.match('assignments');
	if (response === undefined) {
	    return [];
	}
	// JSONデータからハッシュキーを取得
	const assignments: Record<string, string> | undefined = await response.json();
	// 補足: 最新のバージョンハッシュキーの取得は以下可能
	// const response = await dbControlCache.match('latest');
	// if (response === undefined) {
	//     return [];
	// }
	// const latest: Record<string, string> | undefined = await response.json();
	if (assignments === undefined) {
	    return [];
	}
	const cacheHashList = Object.values(assignments);

	const serviceWorkerCacheKeys = cacheHashList.flatMap(hash => [
	    `${SW_CACHE_PREFIX}:${hash}:assets:app:meta`,
	    `${SW_CACHE_PREFIX}:${hash}:assets:app:cache`,
	    `${SW_CACHE_PREFIX}:${hash}:assets:assets:meta`,
	    `${SW_CACHE_PREFIX}:${hash}:assets:assets:cache`,
	    `${SW_CACHE_PREFIX}:${hash}:assets:fonts:meta`,
	    `${SW_CACHE_PREFIX}:${hash}:assets:fonts:cache`,
	]);
	return serviceWorkerCacheKeys;
	}
}

実行すれば削除完了です。
ちなみに、キャッシュストレージの箱は削除できませんでした。(知ってる方がいれば教えてください。)

おわりに

今回は、使用したのは、Angular12 ということもあり、最新版と多少はずれがあるかもしれませんが、Service Workerのことと、Cache APIに少しでも詳しくなっていただけたら嬉しいです!!

何かご指摘ありましたら、コメントよろしくお願いします( ; ; )

Discussion