commit 298281b23ad15b471839c4acb05c599acdf15ab4
parent be93da63ee7baa4654da6640b2bbdaf048bf016d
Author: finwo <finwo@pm.me>
Date: Mon, 15 Sep 2025 19:47:26 +0200
Added service worker to allow offline usage
Diffstat:
3 files changed, 177 insertions(+), 2 deletions(-)
diff --git a/packages/app/build.js b/packages/app/build.js
@@ -26,7 +26,10 @@ const config = {
mainFields: ['browser','module','main'],
bundle: true,
outdir: __dirname + '/dist',
- entryPoints: Object.values(entryPoints),
+ entryPoints: [
+ ...Object.values(entryPoints),
+ __dirname+'/src/service-worker.ts',
+ ],
minify: false,
define,
@@ -61,7 +64,8 @@ esbuild
.build(config)
.then(async () => {
try { fs.mkdirSync('./dist/assets'); } catch { /* empty */ }
- const r = await cpy(__dirname + '/src/assets/*', __dirname + '/dist/assets');
+ await cpy(__dirname + '/src/assets/*', __dirname + '/dist/assets');
+ await cpy(__dirname + '/src/assets/**/*', __dirname + '/dist/assets');
// try { fs.mkdirSync('./dist/AppModule'); } catch { /* empty */ }
fs.copyFileSync(`./src/global.css`, `./dist/global.css`);
for(const name of Object.keys(entryPoints)) {
@@ -84,6 +88,7 @@ esbuild
<link rel="icon" type="image/png" sizes="192x192" href="/assets/icon-192x192.png">
<link rel="icon" type="image/png" sizes="512x512" href="/assets/icon-512x512.png">
<link rel="manifest" href="/assets/manifest.json"/>
+ <link rel="stylesheet" href="/assets/fonts/karla/karla.css"/>
${styles.map(name => `<link rel="preload" as="style" href="${name}" onload="this.onload=null;this.rel='stylesheet'"/>`).join('\n ')}
</head>
<body>
@@ -109,4 +114,16 @@ esbuild
});
}
+ const files = await new Promise(done => {
+ exec(`find ${config.outdir} -type f`, (err,stdout, stderr) => {
+ done(stdout
+ .split('\n')
+ .map(file => file.slice(config.outdir.length))
+ .filter(file => file)
+ );
+ });
+ });
+ let sw = fs.readFileSync(`${config.outdir}/service-worker.js`, 'utf-8')
+ .split('"--FILES--"').join(JSON.stringify(['/',...files]));
+ fs.writeFileSync(`${config.outdir}/service-worker.js`, sw);
})
diff --git a/packages/app/src/main.ts b/packages/app/src/main.ts
@@ -10,6 +10,10 @@ import {DeviceListScreen} from './component/screen-device-list.js';
import { HomeScreen } from './component/screen-home.js';
import {MenuScreen} from './component/screen-menu.js';
+if ('serviceWorker' in navigator) {
+ navigator.serviceWorker.register("/service-worker.js");
+}
+
m.route(document.body, "/", {
[HomeScreen.routePath ]: HomeScreen,
[AccountSelectScreen.routePath]: AccountSelectScreen,
diff --git a/packages/app/src/service-worker.ts b/packages/app/src/service-worker.ts
@@ -0,0 +1,154 @@
+const CACHE_NAME = 'pwa-cache-v1';
+const urlsToCache = '--FILES--';
+
+ // '/',
+ // '/index.html',
+ // '/global.css',
+ // '/main.js',
+ // '/service-worker.js',
+ // '/assets/apple-touch-icon.png',
+ // '/assets/favicon.ico',
+ // '/assets/fonts/karla/karla.css',
+ // '/assets/fonts/karla/karla-italic-latin-ext.woff2',
+ // '/assets/fonts/karla/karla-italic-latin.woff2',
+ // '/assets/fonts/karla/karla-normal-latin-ext.woff2',
+ // '/assets/fonts/karla/karla-normal-latin.woff2',
+ // '/assets/icon.png',
+ // '/assets/icon-48.png',
+ // '/assets/icon-57.png',
+ // '/assets/icon-60.png',
+ // '/assets/icon-72.png',
+ // '/assets/icon-76.png',
+ // '/assets/icon-96.png',
+ // '/assets/icon-114.png',
+ // '/assets/icon-120.png',
+ // '/assets/icon-144.png',
+ // '/assets/icon-152.png',
+ // '/assets/icon-180.png',
+ // '/assets/icon-192.png',
+ // '/assets/icon-256.png',
+ // '/assets/icon-384.png',
+ // '/assets/icon-512.png',
+ // '/assets/manifest.json',
+ // ]);
+
+// // Install event - Cache essential resources
+// self.addEventListener('install', event => {
+// event.waitUntil(
+// caches.open(CACHE_NAME)
+// .then(cache => {
+// console.log('Caching app shell');
+// return cache.addAll(urlsToCache);
+// })
+// .then(() => {
+// // Force activation of new service worker
+// return self.skipWaiting();
+// })
+// );
+// });
+
+// // Activate event - Clean up old caches
+// self.addEventListener('activate', event => {
+// event.waitUntil(
+// caches.keys().then(cacheNames => {
+// return Promise.all(
+// cacheNames.map(cacheName => {
+// if (cacheName !== CACHE_NAME) {
+// console.log('Deleting old cache:', cacheName);
+// return caches.delete(cacheName);
+// }
+// })
+// );
+// }).then(() => {
+// // Take control of all pages
+// return self.clients.claim();
+// })
+// );
+// });
+
+// // Cache-first strategy for static assets
+// self.addEventListener('fetch', event => {
+// // Only handle GET requests
+// if (event.request.method !== 'GET') return;
+
+// const url = new URL(event.request.url);
+
+// console.log('Fetch', event.request.url, event.request.mode);
+
+// // Cache-first for static assets
+// if (url.pathname.match(/\.(css|js|png|jpg|jpeg|gif|svg|ico)$/)) {
+// event.respondWith(
+// caches.match(event.request)
+// .then(response => {
+// if (response) {
+// console.log('Serving from cache:', event.request.url);
+// return response;
+// }
+
+// // Fetch and cache new resources
+// return fetch(event.request)
+// .then(response => {
+// // Don't cache non-successful responses
+// if (!response || response.status !== 200 || response.type !== 'basic') {
+// return response;
+// }
+
+// const responseToCache = response.clone();
+// caches.open(CACHE_NAME)
+// .then(cache => {
+// cache.put(event.request, responseToCache);
+// });
+
+// return response;
+// });
+// })
+// .catch(() => {
+// // Return fallback for images
+// if (event.request.destination === 'image') {
+// return caches.match('/images/fallback.png');
+// }
+// })
+// );
+// }
+// });
+
+self.addEventListener("install", event => {
+ console.log("Service worker installed");
+});
+self.addEventListener("activate", event => {
+ console.log("Service worker activated");
+});
+self.addEventListener('fetch', event => {
+ console.log('Fetching:', event.request.url);
+ // Handle network requests
+});
+
+self.addEventListener('install', event => {
+ event.waitUntil(
+ caches.open('v1').then(cache => {
+ return cache.addAll(urlsToCache);
+
+
+ })
+ );
+});
+
+self.addEventListener('activate', event => {
+ event.waitUntil(
+ caches.keys().then(keyList => {
+ return Promise.all(keyList.map(key => {
+ if (key !== 'v1') {
+ return caches.delete(key);
+ }
+ }));
+ })
+ );
+});
+
+self.addEventListener('fetch', event => {
+ event.respondWith(
+ caches.match(event.request).then(response => {
+ return response || fetch(event.request);
+ })
+ );
+});