flora is a fast and secure runtime that lets you write discord bots for your servers, with a rich TypeScript SDK, without worrying about running infrastructure. [mirror]
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: cache frontend assets and discord images

+165 -5
+97
apps/frontend/public/sw.js
··· 1 + const STATIC_CACHE = 'flora-static-v1' 2 + const IMAGE_CACHE = 'flora-images-v1' 3 + 4 + self.addEventListener('install', (event) => { 5 + event.waitUntil(self.skipWaiting()) 6 + }) 7 + 8 + self.addEventListener('activate', (event) => { 9 + event.waitUntil( 10 + (async () => { 11 + const cacheNames = await caches.keys() 12 + for (const name of cacheNames) { 13 + if (name !== STATIC_CACHE && name !== IMAGE_CACHE) { 14 + await caches.delete(name) 15 + } 16 + } 17 + await self.clients.claim() 18 + })() 19 + ) 20 + }) 21 + 22 + self.addEventListener('fetch', (event) => { 23 + const { request } = event 24 + if (request.method !== 'GET') return 25 + 26 + const url = new URL(request.url) 27 + 28 + if (url.hostname === 'cdn.discordapp.com') { 29 + event.respondWith(staleWhileRevalidate(event, request, IMAGE_CACHE)) 30 + return 31 + } 32 + 33 + if (url.origin !== self.location.origin) return 34 + if (url.pathname.startsWith('/api')) return 35 + 36 + if (url.pathname.startsWith('/assets/')) { 37 + event.respondWith(cacheFirst(request, STATIC_CACHE)) 38 + return 39 + } 40 + 41 + if (isHtmlRequest(request, url)) { 42 + event.respondWith(networkFirst(request, STATIC_CACHE)) 43 + } 44 + }) 45 + 46 + function isHtmlRequest(request, url) { 47 + const acceptHeader = request.headers.get('accept') || '' 48 + if (acceptHeader.includes('text/html')) return true 49 + if (url.pathname === '/') return true 50 + return !url.pathname.includes('.') 51 + } 52 + 53 + async function cacheFirst(request, cacheName) { 54 + const cache = await caches.open(cacheName) 55 + const cached = await cache.match(request) 56 + if (cached) return cached 57 + const response = await fetch(request) 58 + if (response && (response.ok || response.type === 'opaque')) { 59 + await cache.put(request, response.clone()) 60 + } 61 + return response 62 + } 63 + 64 + async function networkFirst(request, cacheName) { 65 + const cache = await caches.open(cacheName) 66 + try { 67 + const response = await fetch(request) 68 + if (response && (response.ok || response.type === 'opaque')) { 69 + await cache.put(request, response.clone()) 70 + } 71 + return response 72 + } catch (error) { 73 + const cached = await cache.match(request) 74 + if (cached) return cached 75 + return new Response('Offline', { status: 503 }) 76 + } 77 + } 78 + 79 + async function staleWhileRevalidate(event, request, cacheName) { 80 + const cache = await caches.open(cacheName) 81 + const cached = await cache.match(request) 82 + const fetchPromise = fetch(request) 83 + .then((response) => { 84 + if (response && (response.ok || response.type === 'opaque')) { 85 + cache.put(request, response.clone()) 86 + } 87 + return response 88 + }) 89 + .catch(() => null) 90 + 91 + event.waitUntil(fetchPromise) 92 + 93 + if (cached) return cached 94 + const response = await fetchPromise 95 + if (response) return response 96 + return new Response('', { status: 504 }) 97 + }
+8
apps/frontend/src/main.tsx
··· 20 20 </HelmetProvider> 21 21 </StrictMode> 22 22 ) 23 + 24 + if (import.meta.env.PROD && 'serviceWorker' in navigator) { 25 + window.addEventListener('load', () => { 26 + navigator.serviceWorker.register('/sw.js').catch((error) => { 27 + console.warn('Service worker registration failed', error) 28 + }) 29 + }) 30 + }
+60 -5
apps/runtime/src/handlers/mod.rs
··· 1 - use axum::{Json, Router, extract::DefaultBodyLimit, routing::get}; 1 + use axum::{ 2 + Json, Router, 3 + extract::{DefaultBodyLimit, Request}, 4 + routing::get, 5 + }; 6 + #[cfg(not(debug_assertions))] 7 + use axum::{ 8 + http::{HeaderValue, header}, 9 + middleware::{self, Next}, 10 + response::Response, 11 + }; 2 12 use tower_http::compression::CompressionLayer; 3 13 #[cfg(not(debug_assertions))] 4 14 use tower_http::services::{ServeDir, ServeFile}; ··· 20 30 pub mod secrets; 21 31 pub mod tokens; 22 32 33 + #[cfg(not(debug_assertions))] 34 + const ASSET_CACHE_CONTROL: &str = "public, max-age=31536000, immutable"; 35 + #[cfg(not(debug_assertions))] 36 + const FRONTEND_CACHE_CONTROL: &str = "public, max-age=3600"; 37 + #[cfg(not(debug_assertions))] 38 + const HTML_CACHE_CONTROL: &str = "no-cache"; 39 + 40 + #[cfg(not(debug_assertions))] 41 + async fn set_asset_cache_headers(req: Request, next: Next) -> Response { 42 + let mut response = next.run(req).await; 43 + response.headers_mut().insert( 44 + header::CACHE_CONTROL, 45 + HeaderValue::from_static(ASSET_CACHE_CONTROL), 46 + ); 47 + response 48 + } 49 + 50 + #[cfg(not(debug_assertions))] 51 + async fn set_frontend_cache_headers(req: Request, next: Next) -> Response { 52 + let path = req.uri().path(); 53 + let is_html = path == "/" || path.ends_with(".html") || !path.contains('.'); 54 + let is_service_worker = path == "/sw.js"; 55 + let mut response = next.run(req).await; 56 + let header_value = if is_html || is_service_worker { 57 + HTML_CACHE_CONTROL 58 + } else { 59 + FRONTEND_CACHE_CONTROL 60 + }; 61 + response.headers_mut().insert( 62 + header::CACHE_CONTROL, 63 + HeaderValue::from_static(header_value), 64 + ); 65 + response 66 + } 67 + 23 68 /// Build the top-level router with API routes and interactive docs. 24 69 pub fn create_router() -> Router<AppState> { 25 70 #[derive(OpenApi)] ··· 77 122 78 123 #[cfg(not(debug_assertions))] 79 124 { 80 - return router.fallback_service( 81 - ServeDir::new("apps/frontend/dist") 82 - .fallback(ServeFile::new("apps/frontend/dist/index.html")), 83 - ); 125 + let assets_router = Router::new() 126 + .fallback_service(ServeDir::new("apps/frontend/dist/assets")) 127 + .layer(middleware::from_fn(set_asset_cache_headers)); 128 + 129 + let frontend_router = Router::new() 130 + .fallback_service( 131 + ServeDir::new("apps/frontend/dist") 132 + .fallback(ServeFile::new("apps/frontend/dist/index.html")), 133 + ) 134 + .layer(middleware::from_fn(set_frontend_cache_headers)); 135 + 136 + return router 137 + .nest("/assets", assets_router) 138 + .fallback_service(frontend_router); 84 139 } 85 140 86 141 #[cfg(debug_assertions)]