From a404ff7fe052134dd584d21f7bb2dba56cac2ea2 Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Sun, 12 Jun 2022 21:16:42 +0100 Subject: [PATCH] feat: add auto-update and out-of-date indicator --- README.md | 23 ++++---- external/lang | 2 +- package.json | 5 +- src/components/common/UpdateIndicator.tsx | 2 +- src/context/modals/components/OutOfDate.tsx | 47 +++++++++++++++ src/context/modals/index.tsx | 2 + src/context/modals/types.ts | 4 ++ src/main.tsx | 15 +---- src/updateWorker.ts | 64 +++++++++++++++++++++ yarn.lock | 9 +++ 10 files changed, 145 insertions(+), 28 deletions(-) create mode 100644 src/context/modals/components/OutOfDate.tsx create mode 100644 src/updateWorker.ts diff --git a/README.md b/README.md index 60a1cdf4..0d5cd8c0 100644 --- a/README.md +++ b/README.md @@ -52,17 +52,18 @@ You can now access the client at http://local.revolt.chat:3000. ## CLI Commands -| Command | Description | -| ------------------- | -------------------------------------------- | -| `yarn pull` | Setup assets required for Revite. | -| `yarn dev` | Start the Revolt client in development mode. | -| `yarn build` | Build the Revolt client. | -| `yarn preview` | Start a local server with the built client. | -| `yarn lint` | Run ESLint on the client. | -| `yarn fmt` | Run Prettier on the client. | -| `yarn typecheck` | Run TypeScript type checking on the client. | -| `yarn start` | Start a local sirv server with built client. | -| `yarn start:inject` | Inject a given API URL and start server. | +| Command | Description | +| --------------------------------------- | -------------------------------------------- | +| `yarn pull` | Setup assets required for Revite. | +| `yarn dev` | Start the Revolt client in development mode. | +| `yarn build` | Build the Revolt client. | +| `yarn preview` | Start a local server with the built client. | +| `yarn lint` | Run ESLint on the client. | +| `yarn fmt` | Run Prettier on the client. | +| `yarn typecheck` | Run TypeScript type checking on the client. | +| `yarn start` | Start a local sirv server with built client. | +| `yarn start:inject` | Inject a given API URL and start server. | +| `yarn lint \| egrep "no-literals" -B 1` | Scan for untranslated strings. | ## License diff --git a/external/lang b/external/lang index a38d0dc7..30964859 160000 --- a/external/lang +++ b/external/lang @@ -1 +1 @@ -Subproject commit a38d0dc72a39ea2b5a6f54c1c999f2021b899e50 +Subproject commit 309648592801a3bb5c1fa1702753f8dadde56cae diff --git a/package.json b/package.json index 0f158e83..86dc0c7e 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,8 @@ { "varsIgnorePattern": "^_" } - ] + ], + "react/jsx-no-literals": "warn" } }, "dependencies": { @@ -92,6 +93,7 @@ "@types/react-helmet": "^6.1.1", "@types/react-router-dom": "^5.1.7", "@types/react-scroll": "^1.8.2", + "@types/semver": "^7", "@types/styled-components": "^5.1.10", "@types/twemoji": "^12.1.1", "@typescript-eslint/eslint-plugin": "^4.27.0", @@ -132,6 +134,7 @@ "revolt.js": "6.0.3", "rimraf": "^3.0.2", "sass": "^1.35.1", + "semver": "^7.3.7", "shade-blend-color": "^1.0.0", "stacktrace-js": "^2.0.2", "styled-components": "^5.3.0", diff --git a/src/components/common/UpdateIndicator.tsx b/src/components/common/UpdateIndicator.tsx index 52ef1ebe..cb3093df 100644 --- a/src/components/common/UpdateIndicator.tsx +++ b/src/components/common/UpdateIndicator.tsx @@ -9,7 +9,7 @@ import { internalSubscribe } from "../../lib/eventEmitter"; import { useApplicationState } from "../../mobx/State"; -import { updateSW } from "../../main"; +import { updateSW } from "../../updateWorker"; import Tooltip from "./Tooltip"; let pendingUpdate = false; diff --git a/src/context/modals/components/OutOfDate.tsx b/src/context/modals/components/OutOfDate.tsx new file mode 100644 index 00000000..6368e56e --- /dev/null +++ b/src/context/modals/components/OutOfDate.tsx @@ -0,0 +1,47 @@ +import { Text } from "preact-i18n"; + +import { Modal } from "@revoltchat/ui"; + +import { noop, noopTrue } from "../../../lib/js"; + +import { APP_VERSION } from "../../../version"; +import { ModalProps } from "../types"; + +export default function OutOfDate({ + onClose, + version, +}: ModalProps<"out_of_date">) { + return ( + } + description={ + <> + +
+ + + } + actions={[ + { + palette: "plain", + onClick: noop, + children: ( + + ), + }, + { + palette: "plain-secondary", + onClick: noopTrue, + children: ( + + ), + }, + ]} + onClose={onClose} + nonDismissable + /> + ); +} diff --git a/src/context/modals/index.tsx b/src/context/modals/index.tsx index 852271ec..824c31f2 100644 --- a/src/context/modals/index.tsx +++ b/src/context/modals/index.tsx @@ -11,6 +11,7 @@ import { ulid } from "ulid"; import MFAEnableTOTP from "./components/MFAEnableTOTP"; import MFAFlow from "./components/MFAFlow"; import MFARecovery from "./components/MFARecovery"; +import OutOfDate from "./components/OutOfDate"; import Test from "./components/Test"; import { Modal } from "./types"; @@ -120,5 +121,6 @@ export const modalController = new ModalControllerExtended({ mfa_flow: MFAFlow, mfa_recovery: MFARecovery, mfa_enable_totp: MFAEnableTOTP, + out_of_date: OutOfDate, test: Test, }); diff --git a/src/context/modals/types.ts b/src/context/modals/types.ts index 4f67b9bf..bbe43c37 100644 --- a/src/context/modals/types.ts +++ b/src/context/modals/types.ts @@ -24,6 +24,10 @@ export type Modal = { secret: string; callback: (code?: string) => void; } + | { + type: "out_of_date"; + version: string; + } | { type: "test"; } diff --git a/src/main.tsx b/src/main.tsx index 28081391..2ba0dd7e 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,21 +1,8 @@ -import { registerSW } from "virtual:pwa-register"; - import "./styles/index.scss"; import { render } from "preact"; -import { internalEmit } from "./lib/eventEmitter"; - import { App } from "./pages/app"; - -export const updateSW = registerSW({ - onNeedRefresh() { - internalEmit("PWA", "update"); - }, - onOfflineReady() { - console.info("Ready to work offline."); - // show a ready to work offline to user - }, -}); +import "./updateWorker"; // eslint-disable-next-line @typescript-eslint/no-non-null-assertion render(, document.getElementById("app")!); diff --git a/src/updateWorker.ts b/src/updateWorker.ts new file mode 100644 index 00000000..4976fe0c --- /dev/null +++ b/src/updateWorker.ts @@ -0,0 +1,64 @@ +import semver from "semver"; +import { ulid } from "ulid"; +import { registerSW } from "virtual:pwa-register"; + +import { internalEmit } from "./lib/eventEmitter"; + +import { modalController } from "./context/modals"; + +import { APP_VERSION } from "./version"; + +const INTERVAL_HOUR = 36e5; + +let forceUpdate = false; +let registration: ServiceWorkerRegistration | undefined; + +export const updateSW = registerSW({ + onNeedRefresh() { + if (forceUpdate) { + updateSW(true); + } else { + internalEmit("PWA", "update"); + } + }, + onOfflineReady() { + console.info("Ready to work offline."); + // show a ready to work offline to user + }, + onRegistered(r) { + registration = r; + + // Check for updates every hour + setInterval(() => r!.update(), INTERVAL_HOUR); + }, +}); + +/** + * Check whether the client is out of date + */ +async function checkVersion() { + const { version } = (await fetch("https://api.revolt.chat/release").then( + (res) => res.json(), + )) as { version: string }; + + if (!semver.satisfies(APP_VERSION, version)) { + // Let the worker know we should immediately refresh + forceUpdate = true; + + // Prompt service worker to update + registration?.update(); + + // Push information that the client is out of date + modalController.push({ + key: ulid(), + type: "out_of_date", + version, + }); + } +} + +if (import.meta.env.VITE_API_URL === "https://api.revolt.chat") { + // Check for critical updates hourly + checkVersion(); + setInterval(checkVersion, INTERVAL_HOUR); +} diff --git a/yarn.lock b/yarn.lock index 6e582177..52c11951 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2730,6 +2730,13 @@ __metadata: languageName: node linkType: hard +"@types/semver@npm:^7": + version: 7.3.9 + resolution: "@types/semver@npm:7.3.9" + checksum: 60bfcfdfa7f937be2c6f4b37ddb6714fb0f27b05fe4cbdfdd596a97d35ed95d13ee410efdd88e72a66449d0384220bf20055ab7d6b5df10de4990fbd20e5cbe0 + languageName: node + linkType: hard + "@types/styled-components@npm:^5.1.10": version: 5.1.13 resolution: "@types/styled-components@npm:5.1.13" @@ -3551,6 +3558,7 @@ __metadata: "@types/react-helmet": ^6.1.1 "@types/react-router-dom": ^5.1.7 "@types/react-scroll": ^1.8.2 + "@types/semver": ^7 "@types/styled-components": ^5.1.10 "@types/twemoji": ^12.1.1 "@typescript-eslint/eslint-plugin": ^4.27.0 @@ -3593,6 +3601,7 @@ __metadata: revolt.js: 6.0.3 rimraf: ^3.0.2 sass: ^1.35.1 + semver: ^7.3.7 shade-blend-color: ^1.0.0 sirv-cli: ^1.0.14 stacktrace-js: ^2.0.2