サービスワーカーの落とし穴

この短いエッセイでは、サービスワーカーについて、1年または少なくとも6か月前に読みたいと思うものについて説明します。これにより、アプリケーションのデバッグの非常に長く苦痛な期間を避けることができます。



「サービスワーカーが実稼働環境で作業していないのはどういうことですか」などのリクエストでここに来た場合は、catへようこそ。



これが何であるかを知らない人にとっては、非常に簡単です-サービスワーカーは、Webページとは別に、バックグラウンドでブラウザによって実行されるスクリプトであり、ページまたはユーザーとの対話を必要としない機能を実行できます。



完全なドキュメントを見つけるのは難しくありませんが、ここにリンクがあります。



この資料は私にとっても非常に有用でした。ServiceWorkerAPIに最初に精通し始めたときに注意深く読んでいなかったことを非常に残念に思います。



実際には、Service Worker APIを使用すると、オンラインWebアプリケーションのファイルをユーザーのローカルデバイスにキャッシュし、必要に応じて完全にオフラインで作業するといった魔法のようなことができます。



将来的には、バックグラウンドでキャッシュ同期などのクールな機能を追加する予定です。つまり、ユーザーが現在サイトにいなくても、サービスワーカーは更新を開始したりダウンロードしたりできます。 また、バックグラウンドからPushApiに再度アクセスします(つまり、更新を受け取ったら、プッシュ通知を送信します)。



どのように機能しますか?





Channel Messaging APIを使用すると、 WebページでService Workerメッセージを送受信できます(逆も同様です)。

Service Workerは、メッセージを介さない限り、DOMおよびWebページのデータにアクセスできません。



私が最近まで知らなかったのは、ユーザーがあなたのサイトにいる場合でも、サービスワーカーが常に働くことを保証するものではないということです。 また、どのような場合でも、ワーカーのグローバルスコープに依存することはできません。



つまり、このような作業スキームは、非常に悲惨な結果とアプリケーションのデバッグの長いプロセスにつながりました( 注意、悪いコード、いかなる場合も使用しないでください ):



サービスワーカーの登録/インストール

Index.html



var regSW = require("./register-worker.js"); var sharedData = {filesDir: localDir}; regSW.registerServiceWorker(sharedData);
      
      





register-worker.js



 var registerServiceWorker = function(sharedData){ navigator.serviceWorker.register('service-worker.js', { scope: './' }) .then(navigator.serviceWorker.ready) .then(function () { console.log('service worker registered'); sendMessageToServiceWorker(sharedData).then(function(data) { console.log('service worker respond with message:', data); }) .catch(function(error) { console.error('send message fails with error:', error); }); }) .catch(function (error) { console.error('error when registering service worker', error, arguments) }); }; var sendMessageToServiceWorker = function(data){ return new Promise(function(resolve, reject) { var messageChannel = new MessageChannel(); messageChannel.port1.onmessage = function(event) { if (event.data.error) { reject(event.data.error); } else { resolve(event.data); } }; navigator.serviceWorker.controller.postMessage(data, [messageChannel.port2]); }); };
      
      





フェッチ盗聴およびなりすましの応答を伴うワーカーコード

service-worker.js



 self.addEventListener('message', function(event) { self.filesDir = event.data.filesDir; event.ports[0].postMessage({'received': event.data}); }); self.addEventListener('fetch', function fetcher(event) { let url = event.request.url; if (url.indexOf("s3") > -1) { //redirect to local stored file url = "file://" + self.filesDir + self.getPath(url); let responseInit = { status: 302, statusText: 'Found', headers: { Location: url } }; let redirectResponse = new Response('', responseInit); event.respondWith(redirectResponse); } });
      
      





ここで何が起こった:





コードを大幅に簡略化することを予約します(もちろん、s3を含むすべてを置き換えるわけではありません。怠け者ではありません)が、それは主なものを示しています。



そして、アプリケーションがランダムな時間(3〜10分)実行された後、サービスワーカーがリクエストをどこにもリダイレクトせず、「file:// undefined / images /image1.png»

つまり、しばらくすると、変数self.filesDirが削除され、写真の代わりに大量の404ファイルが見つかりません。



当然、自尊心のあるプログラマーは5分間アプリケーションをテストしません。 したがって、テスターはせいぜいバグを検出します。 そして通常はクライアントです。 あなたはこれらのテスターを自分で知っているので...そして一般に、誰もテストにお金を払っていません。最初にクラッシュしないことを感謝します。



一般に、長い間引き出さないために、問題は、サービスワーカーが(しばらく)使用されない場合、ブラウザーがそれを強制終了し(申し訳ありませんが、終了単語のより適切な翻訳を思い付かなかった)、次にアクセスされたときに再び開始することです。 したがって、労働者の新しいコピーは、彼の死んだ前任者がWebページに何について話していたかを知らず、ファイルをどこから入手するかについての情報はありません。



したがって、何かを保存する必要がある場合は、永続ストレージ、つまりIndexedDBで保存してください。



また、ワーカーは同期APIを使用できないため、localStorage APIは使用できないと言っています。 しかし、私は個人的には試していません。



ちなみに、バグを再現するために長い間(約7分)テストしても、開発者ツールウィンドウが開いていると、潜んでいるchromeがワーカーを殺さないため、デバッグが遅れました。 ログに簡潔なメッセージでこれを報告しますが、「DevToolsが接続されているためタイムアウトによるService Workerの終了はキャンセルされました」



実際、ServiceWorkerが本番クライアントとは異なる動作をする理由を見つけようと何度も試みたが失敗した理由は、私に明らかでした...



画像



一般に、変数のパス設定を削除してindexedDBに転送した後、不幸が終わり、ServiceWorker APIが再び好きになりました。



しかし、前のコードとは異なり、実際に使用できるコードの例は、取得して使用できます。



サービスワーカーの登録/インストール

index.html



 var regSW = require("./register-worker.js"); idxDB.setObject('filesDir', filesDir); regSW.registerServiceWorker();
      
      





register-worker.js



 var registerServiceWorker = function(){ navigator.serviceWorker.register('service-worker.js', { scope: './' }) .then(navigator.serviceWorker.ready) .then(function () { console.log('service worker registered'); }) .catch(function (error) { console.error('error when registering service worker', error, arguments) }); };
      
      





フェッチ盗聴およびなりすましの応答を伴うワーカーコード

service-worker.js



 self.getLocalDir = function() { let DB_NAME = 'localCache'; let DB_VERSION = 1; let STORE = 'cache'; let KEY_NAME = 'filesDir'; return new Promise(function(resolve, reject) { var open = indexedDB.open(DB_NAME, DB_VERSION); open.onerror = function(event) { reject('error while opening indexdb cache'); }; open.onsuccess = function(event) { let db = event.target.result, result; result = db.transaction([STORE]) .objectStore(STORE) .get(KEY_NAME); result.onsuccess = function(event) { if (!event.target.result) { reject('filesDir not set'); } else { resolve(JSON.parse(event.target.result.value)); } }; result.onerror = function(event) { reject('error while getting playthroughDir'); }; } }); }; self.addEventListener('fetch', function fetcher(event) { let url = event.request.url; if (url.indexOf("s3") > -1) { //redirect to local stored file event.respondWith(getLocalDir().then(function(filesDir){ url = "file://" + filesDir + self.getPath(url); var responseInit = { status: 302, statusText: 'Found', headers: { Location: url } }; return new Response('', responseInit); })); });
      
      





PS著者は独創的であるふりをしませんが、この記事が見つかり、読んで、少なくとも一人の不幸な人を助けるなら、それは価値があると信じています。



All Articles