From ec273bf8cfc8e32e2812de72ad0179bb5d20a4c2 Mon Sep 17 00:00:00 2001 From: Thomas Makin Date: Fri, 22 Nov 2024 11:29:35 -0500 Subject: [PATCH] serviceworker: use serwist Also introduce placeholder temp offline page. TODO: impl bg sync etc --- .gitignore | 6 +- app/sw.ts | 76 ++++++++++++++ app/~offline/page.tsx | 14 +++ next.config.js | 8 -- next.config.mjs | 23 +++++ package-lock.json | 229 ++++++++++++++++++++++++++++++++++++++++++ package.json | 3 + public/sw.js | 63 ------------ 8 files changed, 350 insertions(+), 72 deletions(-) create mode 100644 app/sw.ts create mode 100644 app/~offline/page.tsx delete mode 100644 next.config.js create mode 100644 next.config.mjs delete mode 100644 public/sw.js diff --git a/.gitignore b/.gitignore index 7d263c1..efabe4d 100644 --- a/.gitignore +++ b/.gitignore @@ -36,4 +36,8 @@ next-env.d.ts .env* -/postgres \ No newline at end of file +/postgres + +# Serwist +public/sw* +public/swe-worker* diff --git a/app/sw.ts b/app/sw.ts new file mode 100644 index 0000000..6396c20 --- /dev/null +++ b/app/sw.ts @@ -0,0 +1,76 @@ +import { defaultCache } from "@serwist/next/worker"; +import type { PrecacheEntry, SerwistGlobalConfig } from "serwist"; +import { Serwist } from "serwist"; + +declare global { + interface WorkerGlobalScope extends SerwistGlobalConfig { + // Change this attribute's name to your `injectionPoint`. + // `injectionPoint` is an InjectManifest option. + // See https://serwist.pages.dev/docs/build/configuring + __SW_MANIFEST: (PrecacheEntry | string)[] | undefined; + } +} + +// @ts-expect-error +declare const self: ServiceWorkerGlobalScope; + +const serwist = new Serwist({ + precacheEntries: self.__SW_MANIFEST, + skipWaiting: true, + clientsClaim: true, + navigationPreload: true, + runtimeCaching: defaultCache, + fallbacks: { + entries: [ + { + url: "/~offline", + matcher({ request }) { + return request.destination === "document"; + }, + }, + ], + }, +}); + +serwist.addEventListeners(); + +/* Push Notifs */ +self.addEventListener("push", function (event: any) { + if (event.data) { + let body; + let icon; + let title; + try { + const data = event.data.json(); + body = data.body; + icon = data.icon || "/icon.png"; + title = data.title; + } catch (e) { + console.log("clearly not a JSON notif"); + body = ""; + icon = "/icon.png"; + title = event.data; + } + + const options = { + body: body, + icon: icon, + badge: "/icon.png", + vibrate: [100, 50, 100], + data: { + dateOfArrival: Date.now(), + primaryKey: "2", + }, + }; + event.waitUntil(self.registration.showNotification(title, options)); + } +}); + +self.addEventListener("notificationclick", function (event: any) { + console.log("Notification click received."); + event.notification.close(); + event.waitUntil( + //@ts-ignore + clients.openWindow("https://schedulerv2.sccs.swarthmore.edu/") + ); +}); diff --git a/app/~offline/page.tsx b/app/~offline/page.tsx new file mode 100644 index 0000000..81fef2c --- /dev/null +++ b/app/~offline/page.tsx @@ -0,0 +1,14 @@ +import type { Metadata } from "next"; + +export const metadata: Metadata = { + title: "Offline", +}; + +export default function Page() { + return ( + <> +

This is offline fallback page

+

When offline, any page route will fallback to this page

+ + ); +} diff --git a/next.config.js b/next.config.js deleted file mode 100644 index 73dc253..0000000 --- a/next.config.js +++ /dev/null @@ -1,8 +0,0 @@ -/** @type {import('next').NextConfig} */ -const nextConfig = { - images: { - domains: ["www.swarthmore.edu", "cdn.vectorstock.com"] - } -} - -module.exports = nextConfig diff --git a/next.config.mjs b/next.config.mjs new file mode 100644 index 0000000..c846404 --- /dev/null +++ b/next.config.mjs @@ -0,0 +1,23 @@ +// @ts-check +import withSerwistInit from "@serwist/next"; + +// You may want to use a more robust revision to cache +// files more efficiently. +// A viable option is `git rev-parse HEAD`. +const revision = crypto.randomUUID(); + +const withSerwist = withSerwistInit({ + cacheOnNavigation: true, + swSrc: "app/sw.ts", + swDest: "public/sw.js", + additionalPrecacheEntries: [{ url: "/~offline", revision }], +}); + +/** @type {import('next').NextConfig} */ +const nextConfig = { + images: { + domains: ["www.swarthmore.edu", "cdn.vectorstock.com"], + }, +}; + +export default withSerwist(nextConfig); diff --git a/package-lock.json b/package-lock.json index 9b6347f..edf41ef 100644 --- a/package-lock.json +++ b/package-lock.json @@ -67,10 +67,13 @@ "devDependencies": { "@eslint/js": "^9.14.0", "@next/eslint-plugin-next": "^15.0.3", + "@serwist/build": "^9.0.10", + "@serwist/next": "^9.0.10", "eslint": "^9.14.0", "eslint-config-prettier": "^9.1.0", "eslint-plugin-react": "^7.37.2", "globals": "^15.12.0", + "serwist": "^9.0.10", "turbopack": "^0.0.1", "typescript-eslint": "^8.13.0" } @@ -5063,6 +5066,131 @@ "integrity": "sha512-WJgX9nzTqknM393q1QJDJmoW28kUfEnybeTfVNcNAPnIx210RXm2DiXiHzfNPJNIUUb1tJnz/l4QGtJ30PgWmA==", "license": "MIT" }, + "node_modules/@serwist/build": { + "version": "9.0.10", + "resolved": "https://registry.npmjs.org/@serwist/build/-/build-9.0.10.tgz", + "integrity": "sha512-7txmKhDw65CZZilT4lcV8A47/DD/Tdx4VUBlINSdECCx/pUNSLlRAwmKgb4DLqwQ9+Ert+FGEonPn1j4ZiAFUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "common-tags": "1.8.2", + "glob": "10.4.5", + "pretty-bytes": "6.1.1", + "source-map": "0.8.0-beta.0", + "zod": "3.23.8" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "typescript": ">=5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@serwist/build/node_modules/source-map": { + "version": "0.8.0-beta.0", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.8.0-beta.0.tgz", + "integrity": "sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "whatwg-url": "^7.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@serwist/next": { + "version": "9.0.10", + "resolved": "https://registry.npmjs.org/@serwist/next/-/next-9.0.10.tgz", + "integrity": "sha512-E5gpZTwyQesubn8rASs3oNj+RmQd7whBlcWqEVD5aGxQntgpAJeUVGgALkL1aUfUD/CvgWHKyLJ4Ih6akKKZUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@serwist/build": "9.0.10", + "@serwist/webpack-plugin": "9.0.10", + "@serwist/window": "9.0.10", + "chalk": "5.3.0", + "glob": "10.4.5", + "serwist": "9.0.10", + "zod": "3.23.8" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "next": ">=14.0.0", + "typescript": ">=5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@serwist/next/node_modules/chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@serwist/webpack-plugin": { + "version": "9.0.10", + "resolved": "https://registry.npmjs.org/@serwist/webpack-plugin/-/webpack-plugin-9.0.10.tgz", + "integrity": "sha512-9DGkOjqeIrn6yulyP0VEt/F7EXrYKBSa9jOFFJdmeEXL0SdR5cbzEixTaxTWB1IBHnNduWvFavubxv8dtjyMOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@serwist/build": "9.0.10", + "pretty-bytes": "6.1.1", + "zod": "3.23.8" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "typescript": ">=5.0.0", + "webpack": "4.4.0 || ^5.9.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/@serwist/window": { + "version": "9.0.10", + "resolved": "https://registry.npmjs.org/@serwist/window/-/window-9.0.10.tgz", + "integrity": "sha512-M3VRAyLR8UEZcRYFRZrRF6zvZXJkk1hDtU7MVrDmqtoZ6rvB+PCWWoNacEFi4Rk/7ydmt7Vl8IGx8IanxIakYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/trusted-types": "2.0.7", + "serwist": "9.0.10" + }, + "peerDependencies": { + "typescript": ">=5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/@swc/counter": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", @@ -5161,6 +5289,13 @@ "@types/react": "*" } }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "dev": true, + "license": "MIT" + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "8.14.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.14.0.tgz", @@ -6044,6 +6179,16 @@ "node": ">= 6" } }, + "node_modules/common-tags": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.8.2.tgz", + "integrity": "sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, "node_modules/compute-scroll-into-view": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-3.1.0.tgz", @@ -7637,6 +7782,13 @@ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", "license": "MIT" }, + "node_modules/idb": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/idb/-/idb-8.0.0.tgz", + "integrity": "sha512-l//qvlAKGmQO31Qn7xdzagVPPaHTxXx199MhrAFuVBTPqydcPYBWjkrbv4Y0ktB+GmWOiwHl237UUOrLmQxLvw==", + "dev": true, + "license": "ISC" + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -8345,6 +8497,13 @@ "integrity": "sha512-XeqSp49hNGmlkj2EJlfrQFIzQ6lXdNro9sddtQzcJY8QaoC2GO0DT7xaIokHeyM+mIT0mPMlPvkYzg2xCuHdZg==", "license": "MIT" }, + "node_modules/lodash.sortby": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", + "integrity": "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==", + "dev": true, + "license": "MIT" + }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -9221,6 +9380,19 @@ "node": ">=6.0.0" } }, + "node_modules/pretty-bytes": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-6.1.1.tgz", + "integrity": "sha512-mQUvGU6aUFQ+rNvTIAcZuWGRT9a6f6Yrg9bHs4ImKF+HZCEK+plBvnAZYSIQztknZF2qnzNtr6F8s0+IuptdlQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/pretty-format": { "version": "3.8.0", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-3.8.0.tgz", @@ -9660,6 +9832,24 @@ "node": ">=10" } }, + "node_modules/serwist": { + "version": "9.0.10", + "resolved": "https://registry.npmjs.org/serwist/-/serwist-9.0.10.tgz", + "integrity": "sha512-r6DiPmR99ZqfSo1T0SePgyUnQz0QmbVF6XywjWtCk1Qgk4EBAdrYRr9Y4Ul8Xge/jAnpEMyrmdSX9N1vgxWVgA==", + "dev": true, + "license": "MIT", + "dependencies": { + "idb": "8.0.0" + }, + "peerDependencies": { + "typescript": ">=5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/set-function-length": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", @@ -10280,6 +10470,16 @@ "node": ">=8.0" } }, + "node_modules/tr46": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", + "integrity": "sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.1.0" + } + }, "node_modules/ts-api-utils": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.0.tgz", @@ -10623,6 +10823,25 @@ "uuid": "dist/bin/uuid" } }, + "node_modules/webidl-conversions": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", + "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/whatwg-url": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz", + "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "lodash.sortby": "^4.7.0", + "tr46": "^1.0.1", + "webidl-conversions": "^4.0.2" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -10840,6 +11059,16 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zod": { + "version": "3.23.8", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz", + "integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } } } } diff --git a/package.json b/package.json index 46fab0b..e315eaf 100644 --- a/package.json +++ b/package.json @@ -68,10 +68,13 @@ "devDependencies": { "@eslint/js": "^9.14.0", "@next/eslint-plugin-next": "^15.0.3", + "@serwist/build": "^9.0.10", + "@serwist/next": "^9.0.10", "eslint": "^9.14.0", "eslint-config-prettier": "^9.1.0", "eslint-plugin-react": "^7.37.2", "globals": "^15.12.0", + "serwist": "^9.0.10", "turbopack": "^0.0.1", "typescript-eslint": "^8.13.0" } diff --git a/public/sw.js b/public/sw.js deleted file mode 100644 index 76fe9c8..0000000 --- a/public/sw.js +++ /dev/null @@ -1,63 +0,0 @@ -/** - * SCCS Course Planner PWA service worker - * Adapted from PWABuilder "Offline copy of pages" template - * - * Functions: - * - offline page cache - * - push notif server - * - more to come! - */ - -const CACHE = "sccs-planner"; - -importScripts( - "https://storage.googleapis.com/workbox-cdn/releases/5.1.2/workbox-sw.js" -); - -/* Cache */ -self.addEventListener("message", (event) => { - if (event.data && event.data.type === "SKIP_WAITING") { - self.skipWaiting(); - } -}); - -workbox.routing.registerRoute( - new RegExp("/*"), - new workbox.strategies.StaleWhileRevalidate({ - cacheName: CACHE, - }) -); - -/* Push Notifs */ -self.addEventListener("push", function (event) { - if (event.data) { - let body; - try { - const data = event.data.json(); - body = data.body; - icon = data.icon || "/icon.png"; - } catch (e) { - console.log("clearly not a JSON notif"); - body = event.data; - icon = "/icon.png"; - } - - const options = { - body: body, - icon: icon, - badge: "/icon.png", - vibrate: [100, 50, 100], - data: { - dateOfArrival: Date.now(), - primaryKey: "2", - }, - }; - event.waitUntil(self.registration.showNotification(data.title, options)); - } -}); - -self.addEventListener("notificationclick", function (event) { - console.log("Notification click received."); - event.notification.close(); - event.waitUntil(clients.openWindow("")); -});