From 08aed0d21a8e7fd5e225140fa1ee8f0e879841c5 Mon Sep 17 00:00:00 2001 From: crupest Date: Tue, 15 Jun 2021 18:14:58 +0800 Subject: ... --- FrontEnd/src/index.tsx | 1 + FrontEnd/src/service-worker.tsx | 104 +++++++++++++++++++++ FrontEnd/src/service-worker.txt | 104 --------------------- FrontEnd/src/sw.ts | 66 +++++++++++++ .../timeline-common/ConnectionStatusBadge.tsx | 2 +- 5 files changed, 172 insertions(+), 105 deletions(-) create mode 100644 FrontEnd/src/service-worker.tsx delete mode 100644 FrontEnd/src/service-worker.txt create mode 100644 FrontEnd/src/sw.ts (limited to 'FrontEnd/src') diff --git a/FrontEnd/src/index.tsx b/FrontEnd/src/index.tsx index 83c25792..28034601 100644 --- a/FrontEnd/src/index.tsx +++ b/FrontEnd/src/index.tsx @@ -13,6 +13,7 @@ import "./index.css"; import "./i18n"; import "./palette"; +import "./service-worker"; import App from "./App"; diff --git a/FrontEnd/src/service-worker.tsx b/FrontEnd/src/service-worker.tsx new file mode 100644 index 00000000..ea8dfc32 --- /dev/null +++ b/FrontEnd/src/service-worker.tsx @@ -0,0 +1,104 @@ +import React from "react"; +import { useTranslation } from "react-i18next"; +import { Button } from "react-bootstrap"; + +import { pushAlert } from "./services/alert"; + +if ("serviceWorker" in navigator) { + let isThisTriggerUpgrade = false; + + const upgradeSuccessLocalStorageKey = "TIMELINE_UPGRADE_SUCCESS"; + + if (window.localStorage.getItem(upgradeSuccessLocalStorageKey)) { + pushAlert({ + message: "serviceWorker.upgradeSuccess", + type: "success", + }); + window.localStorage.removeItem(upgradeSuccessLocalStorageKey); + } + + void import("workbox-window").then(({ Workbox, messageSW }) => { + const wb = new Workbox("/sw.js"); + let registration: ServiceWorkerRegistration | undefined; + + // externalactivated is not usable but I still use its name. + wb.addEventListener("controlling", () => { + const upgradeReload = (): void => { + window.localStorage.setItem(upgradeSuccessLocalStorageKey, "true"); + window.location.reload(); + }; + + if (isThisTriggerUpgrade) { + upgradeReload(); + } else { + const Message: React.FC = () => { + const { t } = useTranslation(); + return ( + <> + {t("serviceWorker.externalActivatedPrompt")} + + + ); + }; + + pushAlert({ + message: Message, + dismissTime: "never", + type: "warning", + }); + } + }); + + wb.addEventListener("activated", (event) => { + if (!event.isUpdate) { + pushAlert({ + message: "serviceWorker.availableOffline", + type: "success", + }); + } + }); + + // Add an event listener to detect when the registered + // service worker has installed but is waiting to activate. + wb.addEventListener("waiting", (): void => { + const upgrade = (): void => { + isThisTriggerUpgrade = true; + if (registration && registration.waiting) { + // Send a message to the waiting service worker, + // instructing it to activate. + // Note: for this to work, you have to add a message + // listener in your service worker. See below. + void messageSW(registration.waiting, { type: "SKIP_WAITING" }); + } + }; + + const UpgradeMessage: React.FC = () => { + const { t } = useTranslation(); + return ( + <> + {t("serviceWorker.upgradePrompt")} + + + ); + }; + + pushAlert({ + message: UpgradeMessage, + dismissTime: "never", + type: "success", + }); + }); + + void wb.register().then((reg) => { + registration = reg; + }); + }); +} diff --git a/FrontEnd/src/service-worker.txt b/FrontEnd/src/service-worker.txt deleted file mode 100644 index ea8dfc32..00000000 --- a/FrontEnd/src/service-worker.txt +++ /dev/null @@ -1,104 +0,0 @@ -import React from "react"; -import { useTranslation } from "react-i18next"; -import { Button } from "react-bootstrap"; - -import { pushAlert } from "./services/alert"; - -if ("serviceWorker" in navigator) { - let isThisTriggerUpgrade = false; - - const upgradeSuccessLocalStorageKey = "TIMELINE_UPGRADE_SUCCESS"; - - if (window.localStorage.getItem(upgradeSuccessLocalStorageKey)) { - pushAlert({ - message: "serviceWorker.upgradeSuccess", - type: "success", - }); - window.localStorage.removeItem(upgradeSuccessLocalStorageKey); - } - - void import("workbox-window").then(({ Workbox, messageSW }) => { - const wb = new Workbox("/sw.js"); - let registration: ServiceWorkerRegistration | undefined; - - // externalactivated is not usable but I still use its name. - wb.addEventListener("controlling", () => { - const upgradeReload = (): void => { - window.localStorage.setItem(upgradeSuccessLocalStorageKey, "true"); - window.location.reload(); - }; - - if (isThisTriggerUpgrade) { - upgradeReload(); - } else { - const Message: React.FC = () => { - const { t } = useTranslation(); - return ( - <> - {t("serviceWorker.externalActivatedPrompt")} - - - ); - }; - - pushAlert({ - message: Message, - dismissTime: "never", - type: "warning", - }); - } - }); - - wb.addEventListener("activated", (event) => { - if (!event.isUpdate) { - pushAlert({ - message: "serviceWorker.availableOffline", - type: "success", - }); - } - }); - - // Add an event listener to detect when the registered - // service worker has installed but is waiting to activate. - wb.addEventListener("waiting", (): void => { - const upgrade = (): void => { - isThisTriggerUpgrade = true; - if (registration && registration.waiting) { - // Send a message to the waiting service worker, - // instructing it to activate. - // Note: for this to work, you have to add a message - // listener in your service worker. See below. - void messageSW(registration.waiting, { type: "SKIP_WAITING" }); - } - }; - - const UpgradeMessage: React.FC = () => { - const { t } = useTranslation(); - return ( - <> - {t("serviceWorker.upgradePrompt")} - - - ); - }; - - pushAlert({ - message: UpgradeMessage, - dismissTime: "never", - type: "success", - }); - }); - - void wb.register().then((reg) => { - registration = reg; - }); - }); -} diff --git a/FrontEnd/src/sw.ts b/FrontEnd/src/sw.ts new file mode 100644 index 00000000..0130e345 --- /dev/null +++ b/FrontEnd/src/sw.ts @@ -0,0 +1,66 @@ +/// +/// +/// + +import { precacheAndRoute, matchPrecache } from "workbox-precaching"; +import { registerRoute, setDefaultHandler } from "workbox-routing"; +import { + NetworkFirst, + NetworkOnly, + StaleWhileRevalidate, +} from "workbox-strategies"; +import { CacheableResponsePlugin } from "workbox-cacheable-response"; +import { ExpirationPlugin } from "workbox-expiration"; + +declare let self: ServiceWorkerGlobalScope; + +self.addEventListener("message", (event) => { + if (event.data && (event.data as { type: string }).type === "SKIP_WAITING") { + void self.skipWaiting(); + } +}); + +precacheAndRoute(self.__WB_MANIFEST); + +const networkOnly = new NetworkOnly(); + +registerRoute(new RegExp("/swagger/?.*"), new NetworkOnly()); + +registerRoute(new RegExp("/api/token/?.*"), new NetworkOnly()); +registerRoute(new RegExp("/api/search/?.*"), new NetworkOnly()); + +registerRoute( + new RegExp("/api/users/.+/avatar"), + new StaleWhileRevalidate({ + cacheName: "avatars", + plugins: [ + new CacheableResponsePlugin({ + statuses: [200], + }), + new ExpirationPlugin({ + maxAgeSeconds: 60 * 60 * 24 * 30 * 3, // 3 months + }), + ], + }) +); + +registerRoute( + new RegExp("/api/?.*"), + new NetworkFirst({ + plugins: [ + new CacheableResponsePlugin({ + statuses: [200], + }), + ], + }) +); + +setDefaultHandler((options) => { + const { request } = options; + + if (request instanceof Request && request.destination === "document") + return matchPrecache("/index.html").then((r) => + r == null ? Response.error() : r + ); + else return networkOnly.handle(options); +}); diff --git a/FrontEnd/src/views/timeline-common/ConnectionStatusBadge.tsx b/FrontEnd/src/views/timeline-common/ConnectionStatusBadge.tsx index 1b9d6d2a..df43d8d2 100644 --- a/FrontEnd/src/views/timeline-common/ConnectionStatusBadge.tsx +++ b/FrontEnd/src/views/timeline-common/ConnectionStatusBadge.tsx @@ -1,6 +1,6 @@ import React from "react"; import classnames from "classnames"; -import { HubConnectionState } from "srcmicrosoft/signalr"; +import { HubConnectionState } from "@microsoft/signalr"; import { useTranslation } from "react-i18next"; export interface ConnectionStatusBadgeProps { -- cgit v1.2.3