前提
service workerを最近触るようになったので、その備忘録も兼ねて投稿します。
使用環境 | バージョン | 備考 |
---|---|---|
Laravel | 9 | 本題とあんまり関係ない |
Service Worker | 重要 |
事象
service workerを実装して確認でいろんなページを開いてたらエラー発生
具体的には、
- ログイン
- ホーム画面でservice workerが登録完了
- ログアウト
- ログイン画面に戻った状態でURLにホーム画面に飛ぼうとするとエラー
コード
indexedDBを使ったPOSTメソッドを入れたかったので、下記のページを参考に作ってました。
かつ、Network Firstで実装。
下記ファイルをpublic直下に置いとく。
const addResourcesToCache = async (resources) => {
const cache = await caches.open("キャッシュテーブルの名前");
await cache.addAll(resources);
};
self.addEventListener("install", (event) => {
event.waitUntil(
addResourcesToCache([
...
(キャッシュするファイル)
...
])
);
});
self.addEventListener('fetch', (event) => {
// GETはネットワーク優先、ネットがなければキャッシュから取得
if (event.request.clone().method === 'GET') {
event.respondWith(caches.open(cacheName).then((cache) => {
return fetch(event.request.url).then((fetchedResponse) => {
cache.put(event.request, fetchedResponse.clone());
return fetchedResponse;
}).catch(() => {
return cache.match(event.request.url);
});
}));
// POSTはネットがなければindexedDBにpostMessageとかいろいろ。。。
} else if (event.request.clone().method === 'POST') {
event.respondWith(fetch(event.request.clone()).catch(function
(error) {
...
(POST処理の保存など)
...
}))
}
});
ログイン後のホーム画面でservice workerを登録できるように、ホーム画面に書きを書いとく。
...
<script>
const registerServiceWorker = async () => {
if ('serviceWorker' in navigator) {
try {
const registration = await navigator.serviceWorker.register(
'sw.js', {scope: './',}
);
if (registration.installing) {
console.log('Service worker installing');
} else if (registration.waiting) {
console.log('Service worker installed');
} else if (registration.active) {
console.log('Service worker active');
}
} catch (error) {
console.error(`Registration failed with ${error}`);
}
}
};
</script>
...
Laravelではbreezeでかんたんなログイン機能を追加しておく。
そして本題。エラーを実際に起こす。
上記の通り、service workerが登録している状態でリダイレクトを起こすと、、、
詳細
本来の動きは下記の通り。
- アクセス先 ... ホーム画面
- リダイレクト先 ... ログイン画面
SWをデバッグしながら確認していくと、
|http|url|status|
|:--|:--|:--|
|request|ホーム画面|なし|
|response|ログイン画面|200|
そして、ネットワークを確認すると、
- ホーム画面 ステータス:302
- ログイン画面 ステータス:200
なので、ホーム画面の302でSWが悪さしているのか?と思ってた。
原因
そもそもセキュリティ上の問題で、service workerの機能でリダイレクトをしないようにしてるらしい。
リダイレクトの時はキャッシュしないで、そのままレスポンスを返せば問題ないとのこと。
解決策
event.response.redirectedがtrueになっているとエラーを返すらしい。
なので、request.redirect以外をそのまま返すリクエストを返す
新しくリクエストを作成してそのままレスポンスとして返す
self.addEventListener('fetch', (event) => {
// GETはネットワーク優先、ネットがなければキャッシュから取得
if (event.request.clone().method === 'GET') {
event.respondWith(caches.open(cacheName).then((cache) => {
return fetch(event.request.url).then((fetchedResponse) => {
// 追加
if (fetchedResponse.redirected === true) {
response = cleanResponse(fetchedResponse);
return response;
} else {
cache.put(event.request, fetchedResponse.clone());
return fetchedResponse;
}
// 追加終了
}).catch(() => {
return cache.match(event.request.url);
});
}));
// POSTもネットワーク優先、ネットがなければindexedDBにpostMessageとかいろいろ。。。
} else if (event.request.clone().method === 'POST') {
event.respondWith(fetch(event.request.clone()).catch(function
(error) {
...
(POST処理の保存など)
...
}))
}
});
// 追加
function cleanResponse(response) {
const clonedResponse = response.clone();
const bodyPromise = 'body' in clonedResponse ?
Promise.resolve(clonedResponse.body) :
clonedResponse.blob();
return bodyPromise.then((body) => {
return new Response(body, {
headers: clonedResponse.headers,
status: clonedResponse.status,
statusText: clonedResponse.statusText,
});
});
}
反省
解決策の
const bodyPromise = 'body' in clonedResponse ?
Promise.resolve(clonedResponse.body) :
clonedResponse.blob();
の書き方とか、
return new Response (body, {headers:...});
とか全然知らなかったので、そもそもjavascriptの学習足りないなとも思いました。
そしてswがトラウマになりそうです。
→Workboxを使おう。