cq

Distributed social media platform
git clone git://git.finwo.net/app/cq
Log | Files | Refs

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:
Mpackages/app/build.js | 21+++++++++++++++++++--
Mpackages/app/src/main.ts | 4++++
Apackages/app/src/service-worker.ts | 154+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
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); + }) + ); +});