From 9811248b7ca136899cc84c9eb5e790406e268e43 Mon Sep 17 00:00:00 2001 From: Siavash Sameni Date: Fri, 27 Mar 2026 12:32:59 +0400 Subject: [PATCH] v0.0.10: Progressive Web App (PWA) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Web manifest (standalone mode, theme, icon) - Service worker: caches shell (HTML, WASM, icon) for offline - SVG app icon (chat bubble with encryption indicator) - iOS meta tags: apple-mobile-web-app-capable, status bar style - Android: beforeinstallprompt → /install command - Offline fallback: loads cached shell, shows reconnecting state - Cache versioning with automatic old cache cleanup Co-Authored-By: Claude Opus 4.6 (1M context) --- warzone/Cargo.lock | 10 +- warzone/Cargo.toml | 2 +- .../crates/warzone-server/src/routes/web.rs | 94 ++++++++++++++++++- 3 files changed, 99 insertions(+), 7 deletions(-) diff --git a/warzone/Cargo.lock b/warzone/Cargo.lock index 87c37ce..e89b1c2 100644 --- a/warzone/Cargo.lock +++ b/warzone/Cargo.lock @@ -2647,7 +2647,7 @@ dependencies = [ [[package]] name = "warzone-client" -version = "0.0.9" +version = "0.0.10" dependencies = [ "anyhow", "argon2", @@ -2680,7 +2680,7 @@ dependencies = [ [[package]] name = "warzone-mule" -version = "0.0.9" +version = "0.0.10" dependencies = [ "anyhow", "clap", @@ -2689,7 +2689,7 @@ dependencies = [ [[package]] name = "warzone-protocol" -version = "0.0.9" +version = "0.0.10" dependencies = [ "base64", "bincode", @@ -2712,7 +2712,7 @@ dependencies = [ [[package]] name = "warzone-server" -version = "0.0.9" +version = "0.0.10" dependencies = [ "anyhow", "axum", @@ -2739,7 +2739,7 @@ dependencies = [ [[package]] name = "warzone-wasm" -version = "0.0.9" +version = "0.0.10" dependencies = [ "base64", "bincode", diff --git a/warzone/Cargo.toml b/warzone/Cargo.toml index 767a58e..95fb31d 100644 --- a/warzone/Cargo.toml +++ b/warzone/Cargo.toml @@ -9,7 +9,7 @@ members = [ ] [workspace.package] -version = "0.0.9" +version = "0.0.10" edition = "2021" license = "MIT" rust-version = "1.75" diff --git a/warzone/crates/warzone-server/src/routes/web.rs b/warzone/crates/warzone-server/src/routes/web.rs index 486d01d..29c6061 100644 --- a/warzone/crates/warzone-server/src/routes/web.rs +++ b/warzone/crates/warzone-server/src/routes/web.rs @@ -12,6 +12,9 @@ pub fn routes() -> Router { .route("/", get(web_ui)) .route("/wasm/warzone_wasm.js", get(wasm_js)) .route("/wasm/warzone_wasm_bg.wasm", get(wasm_binary)) + .route("/manifest.json", get(pwa_manifest)) + .route("/sw.js", get(service_worker)) + .route("/icon.svg", get(pwa_icon)) } async fn web_ui() -> Html<&'static str> { @@ -32,12 +35,85 @@ async fn wasm_binary() -> impl IntoResponse { ) } +async fn pwa_manifest() -> impl IntoResponse { + ([(header::CONTENT_TYPE, "application/manifest+json")], r##"{ + "name": "Warzone Messenger", + "short_name": "Warzone", + "description": "End-to-end encrypted messenger", + "start_url": "/", + "display": "standalone", + "background_color": "#0a0a1a", + "theme_color": "#0a0a1a", + "icons": [{"src": "/icon.svg", "sizes": "any", "type": "image/svg+xml", "purpose": "any maskable"}] +}"##) +} + +async fn service_worker() -> impl IntoResponse { + ([(header::CONTENT_TYPE, "application/javascript")], r##" +const CACHE = 'wz-v1'; +const SHELL = ['/', '/wasm/warzone_wasm.js', '/wasm/warzone_wasm_bg.wasm', '/icon.svg', '/manifest.json']; + +self.addEventListener('install', e => { + e.waitUntil(caches.open(CACHE).then(c => c.addAll(SHELL))); + self.skipWaiting(); +}); + +self.addEventListener('activate', e => { + e.waitUntil(caches.keys().then(keys => + Promise.all(keys.filter(k => k !== CACHE).map(k => caches.delete(k))) + )); + e.waitUntil(clients.claim()); +}); + +self.addEventListener('fetch', e => { + const url = new URL(e.request.url); + // API calls: network only + if (url.pathname.startsWith('/v1/')) return; + // WS: skip + if (url.protocol === 'ws:' || url.protocol === 'wss:') return; + // Shell: cache first, network fallback + e.respondWith( + caches.match(e.request).then(cached => cached || fetch(e.request).then(resp => { + if (resp.ok && SHELL.includes(url.pathname)) { + const clone = resp.clone(); + caches.open(CACHE).then(c => c.put(e.request, clone)); + } + return resp; + }).catch(() => { + if (e.request.mode === 'navigate') { + return caches.match('/'); + } + })) + ); +}); +"##) +} + +async fn pwa_icon() -> impl IntoResponse { + ([(header::CONTENT_TYPE, "image/svg+xml")], r##" + + + + + + + +"##) +} + const WEB_HTML: &str = r##" + + + + + + + Warzone