From 8aa3257fa23e668f54c9fa76c805b3674fd181cf Mon Sep 17 00:00:00 2001 From: HomeOS Bootstrap Date: Sat, 18 Jul 2026 01:25:24 +0200 Subject: [PATCH] =?UTF-8?q?feat(pwa):=20installable=20app=20=E2=80=94=20ma?= =?UTF-8?q?nifest,=20service=20worker,=20offline=20shell,=20icons?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Makes HomeOS installable on the tablet/phone (the tablet-control use case): - manifest.webmanifest: standalone display, brand colors, 192/512 "any" + maskable icons, /panel + /rooms shortcuts, start_url /dashboard. - sw.js: conservative for an authenticated Livewire app — never intercepts non-GET (login/Livewire/broadcasting stay online), cache-first only for immutable /build/ + /icons/, network-first navigations with an offline fallback so authenticated HTML is never served stale. - offline.html: self-contained branded offline page (no Vite dependency). - Rendered PNG icons (any + maskable, glyph inside the safe zone) + apple-touch-icon; manifest/theme-color/apple meta in both layouts; SW registered from app.js. - nginx: application/manifest+json MIME, no-cache + Service-Worker-Allowed for sw.js (compose mount now active on the app container). Verified: manifest 200/valid, SW registers+activates (scope /), 11/11 tabs clean, zero console errors. Co-Authored-By: Claude Opus 4.8 --- docker/nginx/default.conf | 13 +++++ public/apple-touch-icon.png | Bin 0 -> 1697 bytes public/icons/icon-192.png | Bin 0 -> 3087 bytes public/icons/icon-512.png | Bin 0 -> 9868 bytes public/icons/icon-maskable-192.png | Bin 0 -> 1316 bytes public/icons/icon-maskable-512.png | Bin 0 -> 4879 bytes public/manifest.webmanifest | 56 ++++++++++++++++++ public/offline.html | 50 ++++++++++++++++ public/sw.js | 74 ++++++++++++++++++++++++ resources/js/app.js | 8 +++ resources/views/layouts/app.blade.php | 11 ++++ resources/views/layouts/guest.blade.php | 5 ++ 12 files changed, 217 insertions(+) create mode 100644 public/apple-touch-icon.png create mode 100644 public/icons/icon-192.png create mode 100644 public/icons/icon-512.png create mode 100644 public/icons/icon-maskable-192.png create mode 100644 public/icons/icon-maskable-512.png create mode 100644 public/manifest.webmanifest create mode 100644 public/offline.html create mode 100644 public/sw.js diff --git a/docker/nginx/default.conf b/docker/nginx/default.conf index 8739d1a..7501cf3 100644 --- a/docker/nginx/default.conf +++ b/docker/nginx/default.conf @@ -36,6 +36,19 @@ server { try_files $uri $uri/ /index.php?$query_string; } + # PWA: correct manifest MIME, and keep the service worker un-cached so updates ship. + location = /manifest.webmanifest { + types { } + default_type application/manifest+json; + add_header Cache-Control "public, max-age=3600"; + access_log off; + } + + location = /sw.js { + add_header Cache-Control "no-cache"; + add_header Service-Worker-Allowed "/"; + } + location = /favicon.ico { access_log off; log_not_found off; } location = /robots.txt { access_log off; log_not_found off; } diff --git a/public/apple-touch-icon.png b/public/apple-touch-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..5fcda223c42582f423c6cdb060c542f32cf199a5 GIT binary patch literal 1697 zcmd5-=Tj2~9F0;&MZz&XmUsq5*~iH$8xjJtBZjgPWhLyT5C^2F2*?OShC)ciBBulp zN!Xwv6ofEDnI_Sbl!Uzk#SD^S`=xjO1Ah3u-`)H4e&gQD_3?tr9aBFB0089NT*0uz zPWo0dM-TB;b8!R!AO&{=JNjX&*O*xJ)OF3F?fw1KZe~WcV3HB+)!rE(LaX|@KK%YD!p}Vj7ggFPTU%WB!f&w0I_b=f zE=4f9c|Ak!V2(>UAPYXB!ff^rGWL0*o$P4(@yeVDK&Is>1!x z14nXy6jI2x6z4zgOtVFhG0XWk^3wmz^sp=O(5d~RRXpw~@W+~)V{}S!Kdq{x5Gg@C zhWo`;j)xiM_8-PJjMmkq&Cj|nwQN&kKYHYNbjO~EhwEK2Hf*oX9|~n6d%Ce>s8VBd z&W5g0*G}XF*PxpMl^4Af&asiB0gy8hX6$EbT;l}n_TAbN7_Dq#v$<2qe_|WtBESD4 zA`)>+K&&c{2pU|bILEC|Hfe1u4UKsMH&aMM0C~ zGz-WOQj)Qdy3XNgF&wsCsZtndtD_WFZZUz3jSsE)|JAIpu@QlIo0qdYN;sE=;v*?a zF@?heQb4(NK6NKC`~b%`Sg3T`T=D*p{mwv_YTGR@ZX2wIXkKy!ON;rtd&Pa}<#iM- z4XXYpu0h0jC?PDm9=NwA`bvHQ@%O(FqF!x9KWpnyrRiP0B3my%2+Xz~OByJC^cN;v zaDSI3@}47p_d%7Mw>ic>uLXN16D1B9WKZ)Ir%uk3?^_*=oO5UH=$49@=V}wQ^?GFhhhNrCC0G)tEl4uPylk z^|&>-AZ%@*#m_WKQZY{xzqNXPBIxT{P&lSUvceT?T>O)}@X zd)X@?$Rxz9Qw`iN$8r4UiZs{KVf)XR+Sj1{ON~EIdyA>_VdE z6SgE=MrBME#^!gv`OrUY`JVo8!KX~c$=A)~S8LjqcGE_Y>fdc|>`)1&HjR~>Z>|ws z06M9b0|milSYctH1A*@C81%j>%{w;n%I=b5N+~s_uvmo9zc)$kU1^J7&Yc=qI!~i+ z_2JnD*(^&&OV_Dcv%O{kymzq<*4tp$5?qJvN!Pc(fU*+iw_}^YJ3T(`h>G&vG@@Nw z)VhxB@zi)Cf=w| z_qu-c=FL>1c(zPe&_&~n%8$NXN-Ogi09jikqzjfa<$$02=7O=;meiJ`aPI>=5zn9z zO7;9(ux%ot|6$Dy{e-S-JGzjwL!pz2DBxW}wo#F#iPnG9|A3zPuOz+(03a5-pSl`+ f7&KDf5=H{RXvvzIH^19($N=0RUf?FDD|h|@Sn(c8 literal 0 HcmV?d00001 diff --git a/public/icons/icon-192.png b/public/icons/icon-192.png new file mode 100644 index 0000000000000000000000000000000000000000..1c1adbfdc30dc87c1cdfd47f92017763d4ad2578 GIT binary patch literal 3087 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 literal 0 HcmV?d00001 diff --git a/public/icons/icon-512.png b/public/icons/icon-512.png new file mode 100644 index 0000000000000000000000000000000000000000..5b39a4905fdc432ad8cfddeaa29fedbf1b3c28e6 GIT binary patch literal 9868 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 literal 0 HcmV?d00001 diff --git a/public/icons/icon-maskable-192.png b/public/icons/icon-maskable-192.png new file mode 100644 index 0000000000000000000000000000000000000000..e2e49ec478561a8f686282965c5204ea2efbd820 GIT binary patch literal 1316 zcmeAS@N?(olHy`uVBq!ia0vp^2SAvE2}s`E_d9@rf#s&Bi(^Q|oHuuEb3#)k4tx|} zs=0E@y=h!iSroShZbKU}HGfLSPM@Cj}*J8HD8`u&UK^L%$VXWu=s?A!g)(ueDp zUOxW3?#Hh`3(w!Gzy9IM1{UuKfM*^Uve(is<`OeXZ|p;=fND? zzwvMRcYm{%&)&*}l;UXGLyJUc<=(QEyC$9OvziF{I{qp7N+c)RM>HgtZo%HGJcP;&g;`wuG-rf~)ozO5% z|J>4=Q_DY^zM6LX@@kI>4gY?=4FUOon(3=OyKlsbs4TGG_e( zU!Az=ockTkqr3S-L=sp^KiOeR%`Uqq-nh80D)`FLuTn=iJ7gMyu3Vc@v*m}m&DV-0 zlLN8@o-60v-WR_5YVqbICqwm>xsFVa*cX_%T5&FAb7VSIUFJChr0R*3#`~M9iGEHi z9Vaw=ninhdOl68xg*+%TaS8?8W8XjB|MaHjY2_2wb~mw1`F`;I)61LQEO;85@_La1 zM_*le<@4niqSnn@t5m4B=;YE=FyKd!bn7|4Y OVeoYIb6Mw<&;$S{%Q7_p literal 0 HcmV?d00001 diff --git a/public/icons/icon-maskable-512.png b/public/icons/icon-maskable-512.png new file mode 100644 index 0000000000000000000000000000000000000000..613fe07ae9a46eefdaecb5fff03690b14c8ab8f0 GIT binary patch literal 4879 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 + + + + + Offline · HomeOS + + + +
+
+ + + + + + +
+

Keine Verbindung

+

HomeOS ist gerade nicht erreichbar. Prüfe deine Netzwerkverbindung zum Server.

+ +
+ + diff --git a/public/sw.js b/public/sw.js new file mode 100644 index 0000000..edc756a --- /dev/null +++ b/public/sw.js @@ -0,0 +1,74 @@ +/** + * HomeOS service worker. Deliberately conservative for an authenticated Livewire app: + * - never intercepts non-GET (login, Livewire updates, broadcasting/auth stay online-only) + * - cache-first only for fingerprinted, immutable assets under /build/ and static icons + * - navigations are network-first with an offline fallback, so authenticated HTML is never + * served stale from cache + * Bump VERSION to invalidate old caches on the next activate. + */ +const VERSION = 'homeos-v1'; +const STATIC_CACHE = `${VERSION}-static`; +const OFFLINE_URL = '/offline.html'; +const PRECACHE = [OFFLINE_URL, '/icons/icon-192.png', '/icons/icon-512.png']; + +self.addEventListener('install', (event) => { + event.waitUntil( + (async () => { + const cache = await caches.open(STATIC_CACHE); + await cache.addAll(PRECACHE); + await self.skipWaiting(); + })(), + ); +}); + +self.addEventListener('activate', (event) => { + event.waitUntil( + (async () => { + const keys = await caches.keys(); + await Promise.all(keys.filter((k) => !k.startsWith(VERSION)).map((k) => caches.delete(k))); + await self.clients.claim(); + })(), + ); +}); + +self.addEventListener('message', (event) => { + if (event.data === 'skipWaiting') self.skipWaiting(); +}); + +self.addEventListener('fetch', (event) => { + const req = event.request; + if (req.method !== 'GET') return; // login / Livewire / broadcasting are POST — stay online + + const url = new URL(req.url); + if (url.origin !== self.location.origin) return; // don't touch cross-origin + + // Immutable, fingerprinted build output + static icons → cache-first. + if (url.pathname.startsWith('/build/') || url.pathname.startsWith('/icons/')) { + event.respondWith(cacheFirst(req)); + return; + } + + // Page loads → network-first, fall back to the offline shell only when truly offline. + if (req.mode === 'navigate') { + event.respondWith(networkFirst(req)); + } +}); + +async function cacheFirst(req) { + const cache = await caches.open(STATIC_CACHE); + const hit = await cache.match(req); + if (hit) return hit; + const res = await fetch(req); + if (res && res.ok) cache.put(req, res.clone()); + return res; +} + +async function networkFirst(req) { + try { + return await fetch(req); + } catch (e) { + const cache = await caches.open(STATIC_CACHE); + const offline = await cache.match(OFFLINE_URL); + return offline || Response.error(); + } +} diff --git a/resources/js/app.js b/resources/js/app.js index 8337e44..f02bd0f 100644 --- a/resources/js/app.js +++ b/resources/js/app.js @@ -85,3 +85,11 @@ window.Echo = new Echo({ forceTLS: isHttps, enabledTransports: ['ws', 'wss'], }); + +// Register the PWA service worker so HomeOS installs on tablets/phones and survives +// brief server blips (offline shell). sw.js lives at the web root for a "/" scope. +if ('serviceWorker' in navigator) { + window.addEventListener('load', () => { + navigator.serviceWorker.register('/sw.js').catch(() => {}); + }); +} diff --git a/resources/views/layouts/app.blade.php b/resources/views/layouts/app.blade.php index d0c3329..bab9047 100644 --- a/resources/views/layouts/app.blade.php +++ b/resources/views/layouts/app.blade.php @@ -5,6 +5,17 @@ + + + {{-- PWA: installable on tablet/phone (§ tablet control) --}} + + + + + + + + {{ $title ?? __('common.app_name') }} · {{ __('common.app_name') }} @vite(['resources/css/app.css', 'resources/js/app.js']) @livewireStyles diff --git a/resources/views/layouts/guest.blade.php b/resources/views/layouts/guest.blade.php index 0f1251f..e7b80df 100644 --- a/resources/views/layouts/guest.blade.php +++ b/resources/views/layouts/guest.blade.php @@ -5,6 +5,11 @@ + + + + + {{ __('auth.title') }} · {{ __('common.app_name') }} @vite(['resources/css/app.css', 'resources/js/app.js']) @livewireStyles