From 2153bc536ca432c696c8d18d3b301afeddcfca15 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 13 May 2020 13:17:47 -0700 Subject: [PATCH] Clean up service worker code and fix 404 (#5855) --- build-scripts/gulp/service-worker.js | 34 +++++------- cast/src/launcher/layout/hc-connect.ts | 2 +- src/auth/ha-authorize.ts | 2 +- src/entrypoints/service_worker.ts | 10 +++- src/layouts/home-assistant.ts | 2 +- src/onboarding/ha-onboarding.ts | 2 +- src/util/register-service-worker.ts | 76 ++++++++++++++------------ 7 files changed, 70 insertions(+), 58 deletions(-) diff --git a/build-scripts/gulp/service-worker.js b/build-scripts/gulp/service-worker.js index dd46b6f1d2..70ede98efb 100644 --- a/build-scripts/gulp/service-worker.js +++ b/build-scripts/gulp/service-worker.js @@ -19,6 +19,8 @@ gulp.task("gen-service-worker-app-dev", (done) => { console.debug('Service worker disabled in development'); self.addEventListener('install', (event) => { + // This will activate the dev service worker, + // removing any prod service worker the dev might have running self.skipWaiting(); }); ` @@ -27,6 +29,17 @@ self.addEventListener('install', (event) => { }); gulp.task("gen-service-worker-app-prod", async () => { + // Read bundled source file + const bundleManifest = require(path.resolve(paths.output, "manifest.json")); + let serviceWorkerContent = fs.readFileSync( + paths.root + bundleManifest["service_worker.js"], + "utf-8" + ); + + // Delete old file from frontend_latest so manifest won't pick it up + fs.removeSync(paths.root + bundleManifest["service_worker.js"]); + fs.removeSync(paths.root + bundleManifest["service_worker.js.map"]); + const workboxManifest = await workboxBuild.getManifest({ // Files that mach this pattern will be considered unique and skip revision check // ignore JS files + translation files @@ -37,7 +50,8 @@ gulp.task("gen-service-worker-app-prod", async () => { "frontend_latest/*.js", // Cache all English translations because we catch them as fallback // Using pattern to match hash instead of * to avoid caching en-GB - "static/translations/**/en-+([a-f0-9]).json", + // 'v' added as valid hash letter because in dev we hash with 'dev' + "static/translations/**/en-+([a-fv0-9]).json", // Icon shown on splash screen "static/icons/favicon-192x192.png", "static/icons/favicon.ico", @@ -53,20 +67,6 @@ gulp.task("gen-service-worker-app-prod", async () => { console.warn(warning); } - // Replace `null` with 0 for better compression - for (const entry of workboxManifest.manifestEntries) { - if (entry.revision === null) { - entry.revision = 0; - } - } - - const manifest = require(path.resolve(paths.output, "manifest.json")); - - // Write bundled source file - let serviceWorkerContent = fs.readFileSync( - paths.root + manifest["service_worker.js"], - "utf-8" - ); // remove source map and add WB manifest serviceWorkerContent = sourceMapUrl.removeFrom(serviceWorkerContent); serviceWorkerContent = serviceWorkerContent.replace( @@ -76,8 +76,4 @@ gulp.task("gen-service-worker-app-prod", async () => { // Write new file to root fs.writeFileSync(swDest, serviceWorkerContent); - - // Delete old file from frontend_latest - fs.removeSync(paths.root + manifest["service_worker.js"]); - fs.removeSync(paths.root + manifest["service_worker.js.map"]); }); diff --git a/cast/src/launcher/layout/hc-connect.ts b/cast/src/launcher/layout/hc-connect.ts index 9660f4755a..048a69f403 100644 --- a/cast/src/launcher/layout/hc-connect.ts +++ b/cast/src/launcher/layout/hc-connect.ts @@ -184,7 +184,7 @@ export class HcConnect extends LitElement { this.castManager = null; } ); - registerServiceWorker(false); + registerServiceWorker(this, false); } private async _handleDemo() { diff --git a/src/auth/ha-authorize.ts b/src/auth/ha-authorize.ts index f811a21186..db9023efab 100644 --- a/src/auth/ha-authorize.ts +++ b/src/auth/ha-authorize.ts @@ -121,7 +121,7 @@ class HaAuthorize extends litLocalizeLiteMixin(LitElement) { const tempA = document.createElement("a"); tempA.href = this.redirectUri!; if (tempA.host === location.host) { - registerServiceWorker(false); + registerServiceWorker(this, false); } } diff --git a/src/entrypoints/service_worker.ts b/src/entrypoints/service_worker.ts index 3b637c5401..5883aadca4 100644 --- a/src/entrypoints/service_worker.ts +++ b/src/entrypoints/service_worker.ts @@ -164,10 +164,18 @@ self.addEventListener("install", (event) => { event.waitUntil(caches.delete(cacheName)); }); +self.addEventListener("activate", () => { + // Attach the service worker to any page of the app + // that didn't have a service worker loaded. + // Happens the first time they open the app without any + // service worker registered. + // This will serve code splitted bundles from SW. + clients.claim(); +}); + self.addEventListener("message", (message) => { if (message.data.type === "skipWaiting") { self.skipWaiting(); - clients.claim(); } }); diff --git a/src/layouts/home-assistant.ts b/src/layouts/home-assistant.ts index c8e0f87f90..c9594d1d54 100644 --- a/src/layouts/home-assistant.ts +++ b/src/layouts/home-assistant.ts @@ -46,7 +46,7 @@ export class HomeAssistantAppEl extends HassElement { protected firstUpdated(changedProps) { super.firstUpdated(changedProps); this._initialize(); - setTimeout(registerServiceWorker, 1000); + setTimeout(() => registerServiceWorker(this), 1000); /* polyfill for paper-dropdown */ import( /* webpackChunkName: "polyfill-web-animations-next" */ "web-animations-js/web-animations-next-lite.min" diff --git a/src/onboarding/ha-onboarding.ts b/src/onboarding/ha-onboarding.ts index 414731adf7..fee79c3a78 100644 --- a/src/onboarding/ha-onboarding.ts +++ b/src/onboarding/ha-onboarding.ts @@ -96,7 +96,7 @@ class HaOnboarding extends litLocalizeLiteMixin(HassElement) { import( /* webpackChunkName: "onboarding-core-config" */ "./onboarding-core-config" ); - registerServiceWorker(false); + registerServiceWorker(this, false); this.addEventListener("onboarding-step", (ev) => this._handleStepDone(ev)); } diff --git a/src/util/register-service-worker.ts b/src/util/register-service-worker.ts index 6014b0e55a..709c8f2a6c 100644 --- a/src/util/register-service-worker.ts +++ b/src/util/register-service-worker.ts @@ -1,49 +1,57 @@ -import { HassElement } from "../state/hass-element"; import { showToast } from "./toast"; export const supportsServiceWorker = () => "serviceWorker" in navigator && (location.protocol === "https:" || location.hostname === "localhost"); -export const registerServiceWorker = (notifyUpdate = true) => { +export const registerServiceWorker = async ( + rootEl: HTMLElement, + notifyUpdate = true +) => { if (!supportsServiceWorker()) { return; } - navigator.serviceWorker.register("/service_worker.js").then((reg) => { - reg.addEventListener("updatefound", () => { - const installingWorker = reg.installing; - if (!installingWorker || !notifyUpdate) { - return; - } - installingWorker.addEventListener("statechange", () => { - if ( - installingWorker.state === "installed" && - navigator.serviceWorker.controller && - !__DEV__ && - !__DEMO__ - ) { - // Notify users here of a new frontend being available. - const haElement = window.document.querySelector( - "home-assistant, ha-onboarding" - )! as HassElement; - showToast(haElement, { - message: "A new version of the frontend is available.", - action: { - action: () => - installingWorker.postMessage({ type: "skipWaiting" }), - text: "reload", - }, - duration: 0, - dismissable: false, - }); - } - }); - }); - }); - // If the active service worker changes, refresh the page because the cache has changed navigator.serviceWorker.addEventListener("controllerchange", () => { location.reload(); }); + + const reg = await navigator.serviceWorker.register("/service_worker.js"); + + if (!notifyUpdate || __DEV__ || __DEMO__) { + return; + } + + reg.addEventListener("updatefound", () => { + const installingWorker = reg.installing; + + if (!installingWorker) { + return; + } + + installingWorker.addEventListener("statechange", () => { + if ( + installingWorker.state !== "installed" || + !navigator.serviceWorker.controller + ) { + return; + } + + // Notify users a new frontend is available. + // When + showToast(rootEl, { + message: "A new version of the frontend is available.", + action: { + // We tell the service worker to call skipWaiting, which activates + // the new service worker. Above we listen for `controllerchange` + // so we reload the page once a new servic worker activates. + action: () => installingWorker.postMessage({ type: "skipWaiting" }), + text: "reload", + }, + duration: 0, + dismissable: false, + }); + }); + }); };