Files
tonemark/src/service-worker.ts
Giancarmine Salucci 13a96b6efa
Some checks failed
Build & Push Docker Image / build-and-push (push) Failing after 11s
Initial commit: Tonemark PWA
Tonemark is a SvelteKit PWA for transcribing YouTube videos, audio
and video files, and microphone recordings using a local Whisper backend.

Features:
- Dark glassmorphic UI with electric-lime accent (5 switchable themes)
- Rail nav (desktop) / tab bar (mobile) layout
- Drop zone, YouTube URL input, and live audio recording inputs
- Audio mode waveform cards (none / standard / aggressive / auto)
- Real-time transcription progress with animated waveform
- Job queue with SSE streaming updates
- Push notifications on job completion
- PWA with native SvelteKit service worker
- SRT / TXT / MD / JSON transcript downloads

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-06 16:41:25 +02:00

96 lines
2.7 KiB
TypeScript

/// <reference no-default-lib="true"/>
/// <reference lib="esnext" />
/// <reference lib="webworker" />
/// <reference types="@sveltejs/kit" />
import { build, files, version } from '$service-worker';
declare const self: ServiceWorkerGlobalScope;
const CACHE = `tonemark-${version}`;
const ASSETS = [...build, ...files];
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(CACHE).then((cache) => cache.addAll(ASSETS))
);
self.skipWaiting();
});
self.addEventListener('activate', (event) => {
event.waitUntil(
caches.keys().then((keys) =>
Promise.all(keys.filter((k) => k !== CACHE).map((k) => caches.delete(k)))
)
);
self.clients.claim();
});
self.addEventListener('fetch', (event) => {
if (event.request.method !== 'GET') return;
event.respondWith(
(async () => {
const url = new URL(event.request.url);
const cache = await caches.open(CACHE);
// Serve cached assets instantly
if (ASSETS.includes(url.pathname)) {
const cached = await cache.match(url.pathname);
if (cached) return cached;
}
// Network-first for everything else
try {
const response = await fetch(event.request);
if (response instanceof Response && response.status === 200) {
cache.put(event.request, response.clone());
}
return response;
} catch {
const cached = await cache.match(event.request);
if (cached) return cached;
throw new Error('Offline and no cached response');
}
})()
);
});
// ── Push notifications ──────────────────────────────────────────────────────
self.addEventListener('push', (event) => {
const data = (event.data?.json() as { jobId?: string; title?: string; body?: string } | null) ?? {};
const title = data.title ?? 'Tonemark';
const body = data.body ?? 'Your transcript is ready.';
const jobId = data.jobId;
event.waitUntil(
self.registration.showNotification(title, {
body,
icon: '/icons/android-icon-192.png',
badge: '/icons/android-icon-192.png',
tag: jobId ? `job-${jobId}` : 'tonemark',
data: { jobId }
})
);
});
self.addEventListener('notificationclick', (event) => {
event.notification.close();
const jobId = (event.notification.data as { jobId?: string } | null)?.jobId;
const url = jobId ? `/jobs/${jobId}` : '/';
event.waitUntil(
self.clients
.matchAll({ type: 'window', includeUncontrolled: true })
.then((clients) => {
const target = clients.find((c) =>
jobId ? c.url.includes(`/jobs/${jobId}`) : c.url === self.location.origin + '/'
);
if (target) return target.focus();
return self.clients.openWindow(url);
})
);
});