diff --git a/README.md b/README.md index e3a6c9b..ddb9bf8 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# OTAMan — APDU Helper & Secured Packet Builder +# OTAMan — APDU Helper & Secured Packet Builder, SIM OTA in PWA Standalone offline HTML/JS tool for building APDU commands for SIM, USIM, and GlobalPlatform RAM, assembling secure packets per ETSI TS 102 225, and constructing BER-TLV command scripts per ETSI TS 102 226. @@ -371,6 +371,13 @@ Swaps nibble pairs of an even-length hex string. --- +## PWA + +OTAMan is a Progressive Web App and can be installed for offline use. Use the **INSTALL PWA** button in the header, or use the browser's install prompt. + +- Service worker pre-caches all assets on first visit +- App icons at 192×192 and 512×512 + ## Theme Dark theme is supported. The app follows the OS preference on first visit, and a manual toggle button (🌙/☀️) at the top-right corner persists the choice in `localStorage`. diff --git a/icon-192.png b/icon-192.png new file mode 100644 index 0000000..84dd3fd Binary files /dev/null and b/icon-192.png differ diff --git a/icon-512.png b/icon-512.png new file mode 100644 index 0000000..41add80 Binary files /dev/null and b/icon-512.png differ diff --git a/index.html b/index.html index 950e238..58b0514 100644 --- a/index.html +++ b/index.html @@ -6,6 +6,9 @@ OTAMan + + + @@ -14,7 +17,8 @@

OTAMan

-
+
+ github
@@ -2369,6 +2373,26 @@ toggleSimOverride('usim'); updateP1P2Display('sim'); updateP1P2Display('usim'); updateRamFields(); +// PWA +let deferredPrompt; +const installBtn = document.getElementById('install-btn'); +if (!window.matchMedia('(display-mode: standalone)').matches) { + window.addEventListener('beforeinstallprompt', e => { + e.preventDefault(); + deferredPrompt = e; + installBtn.style.display = ''; + }); + installBtn.addEventListener('click', async () => { + if (!deferredPrompt) return; + deferredPrompt.prompt(); + const { outcome } = await deferredPrompt.userChoice; + if (outcome === 'accepted') deferredPrompt = null; + }); + window.addEventListener('appinstalled', () => { installBtn.style.display = 'none'; }); +} +if ('serviceWorker' in navigator) { + navigator.serviceWorker.register('sw.js'); +} diff --git a/manifest.json b/manifest.json new file mode 100644 index 0000000..8919da7 --- /dev/null +++ b/manifest.json @@ -0,0 +1,13 @@ +{ + "name": "OTAMan", + "short_name": "OTAMan", + "description": "Offline APDU helper for SIM/USIM/RAM commands", + "start_url": "index.html", + "display": "standalone", + "background_color": "#fafafa", + "theme_color": "#2563eb", + "icons": [ + { "src": "icon-192.png", "sizes": "192x192", "type": "image/png" }, + { "src": "icon-512.png", "sizes": "512x512", "type": "image/png" } + ] +} \ No newline at end of file diff --git a/style.css b/style.css index 847cb34..f8f1a9a 100644 --- a/style.css +++ b/style.css @@ -615,10 +615,6 @@ video { display: none; } -.min-h-\[100px\] { - min-height: 100px; -} - .w-1\/3 { width: 33.333333%; } diff --git a/sw.js b/sw.js new file mode 100644 index 0000000..7612dae --- /dev/null +++ b/sw.js @@ -0,0 +1,35 @@ +const CACHE = 'otaman-v1'; +const URLS = [ + 'index.html', + 'style.css', + 'sim.png', + 'icon-192.png', + 'icon-512.png', + 'manifest.json', + 'des-bundle.js', +]; + +self.addEventListener('install', e => { + e.waitUntil( + caches.open(CACHE).then(c => c.addAll(URLS)) + ); + self.skipWaiting(); +}); + +self.addEventListener('activate', e => { + e.waitUntil( + caches.keys().then(keys => + Promise.all(keys.filter(k => k !== CACHE).map(k => caches.delete(k))) + ) + ); +}); + +self.addEventListener('fetch', e => { + e.respondWith( + caches.match(e.request).then(r => r || fetch(e.request).then(res => { + const clone = res.clone(); + caches.open(CACHE).then(c => c.put(e.request, clone)); + return res; + })) + ); +}); \ No newline at end of file