diff --git a/src/web/app.py b/src/web/app.py index 7777299..7244ec7 100644 --- a/src/web/app.py +++ b/src/web/app.py @@ -4,7 +4,7 @@ from pathlib import Path from fastapi import FastAPI from fastapi.staticfiles import StaticFiles -from fastapi.responses import RedirectResponse +from fastapi.responses import FileResponse, RedirectResponse from starlette.middleware.base import BaseHTTPMiddleware from starlette.middleware.gzip import GZipMiddleware from loguru import logger @@ -104,6 +104,15 @@ app.add_middleware(GZipMiddleware, minimum_size=500) # Static files app.mount("/static", StaticFiles(directory=Path(__file__).parent / "static"), name="static") + +@app.get("/sw.js", include_in_schema=False) +async def service_worker(): + """Serve Service Worker from root scope so it can intercept all page requests.""" + response = FileResponse(Path(__file__).parent / "static/sw.js", media_type="application/javascript") + response.headers["Service-Worker-Allowed"] = "/" + response.headers["Cache-Control"] = "no-cache" + return response + # Include admin router (always available) app.include_router(admin_router) diff --git a/src/web/static/sw.js b/src/web/static/sw.js new file mode 100644 index 0000000..c213d09 --- /dev/null +++ b/src/web/static/sw.js @@ -0,0 +1,48 @@ +const CACHE_NAME = 'linkedin-shell-v1'; +const PRECACHE_URLS = [ + '/static/tailwind.css', + '/static/tailwind-employee.css', + '/static/logo.png', + '/static/favicon.png', +]; + +self.addEventListener('install', function(event) { + event.waitUntil( + caches.open(CACHE_NAME).then(function(cache) { + return cache.addAll(PRECACHE_URLS); + }).then(function() { + return self.skipWaiting(); + }) + ); +}); + +self.addEventListener('activate', function(event) { + event.waitUntil( + caches.keys().then(function(cacheNames) { + return Promise.all( + cacheNames + .filter(function(name) { return name !== CACHE_NAME; }) + .map(function(name) { return caches.delete(name); }) + ); + }).then(function() { + return self.clients.claim(); + }) + ); +}); + +self.addEventListener('fetch', function(event) { + if (!event.request.url.includes('/static/')) { + return; + } + event.respondWith( + caches.match(event.request).then(function(cached) { + return cached || fetch(event.request).then(function(response) { + var clone = response.clone(); + caches.open(CACHE_NAME).then(function(cache) { + cache.put(event.request, clone); + }); + return response; + }); + }) + ); +}); diff --git a/src/web/templates/user/base.html b/src/web/templates/user/base.html index 63ddf93..8267506 100644 --- a/src/web/templates/user/base.html +++ b/src/web/templates/user/base.html @@ -5,6 +5,7 @@ {% block title %}LinkedIn Posts{% endblock %} +