diff options
| author | Adam Soltys <[email protected]> | 2026-06-23 23:05:52 -0700 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2026-06-23 23:05:52 -0700 |
| commit | d5e55c9c577feb6f8000bad3c37045a2cd7c9b4c (patch) | |
| tree | c4260128cae53c949c29e1fe802c4d4a5aca94d0 /build.js | |
| parent | ca7d85e8603ad5ee6a31afbf56777cedfc3931b5 (diff) | |
| download | coinosv3-d5e55c9c577feb6f8000bad3c37045a2cd7c9b4c.tar.gz coinosv3-d5e55c9c577feb6f8000bad3c37045a2cd7c9b4c.zip | |
Add QR scanner to the send page (lazy-loaded decoder)
Scan a Bitcoin address, a BIP21 URI (fills address + amount), or a
signed-transaction QR (opens a broadcast confirmation, then relays it)
from a viewfinder button on the Send form.
Decoder strategy keeps the main bundle small:
- Prefer the native BarcodeDetector (Chrome desktop/Android) — no lib.
- Otherwise lazy-load jsQR from a separate dist/jsqr.js, only when the
scanner is opened. jsQR stays out of index.html (~424 KB vs ~551 KB)
and off the initial load.
- The service worker precaches jsqr.js so offline scanning works for
installed PWAs on browsers without BarcodeDetector.
The scanner needs a camera + secure context, so it never shows on a
standalone file:// page; offline air-gapped signing is unaffected.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Diffstat (limited to 'build.js')
| -rw-r--r-- | build.js | 24 |
1 files changed, 22 insertions, 2 deletions
@@ -42,7 +42,9 @@ const MANIFEST = { // cross-origin explorer/Nostr requests. CACHE carries a build token so each // deploy supersedes the previous cache. {{VERSION}} is filled in at build time. const SW = `const CACHE = 'cold-{{VERSION}}'; -const SHELL = ['./', 'manifest.webmanifest', 'icon-192.png', 'icon-512.png']; +// jsqr.js is precached so the lazy QR decoder is available offline too (for +// installed PWAs on browsers without a native BarcodeDetector). +const SHELL = ['./', 'manifest.webmanifest', 'icon-192.png', 'icon-512.png', 'jsqr.js']; self.addEventListener('install', (e) => { self.skipWaiting(); @@ -91,6 +93,20 @@ const PWA_HEAD = `<link rel="manifest" href="manifest.webmanifest"> const SW_REGISTER = `<script>if('serviceWorker'in navigator)addEventListener('load',function(){navigator.serviceWorker.register('sw.js').catch(function(){})});</script>`; +// Bundle the standalone jsQR decoder (sets window.jsQR) for lazy loading. +export async function buildJsQr({ minify = true } = {}) { + const result = await Bun.build({ + entrypoints: ['./src/jsqr-global.js'], + target: 'browser', + minify, + }); + if (!result.success) { + for (const log of result.logs) console.error(log); + throw new Error('jsqr bundle failed'); + } + return result.outputs[0].text(); +} + export async function buildHtml({ minify = true, pwa = minify } = {}) { const result = await Bun.build({ entrypoints: ['./src/app.js'], @@ -128,6 +144,10 @@ if (import.meta.main) { const html = await buildHtml({ minify: true }); await Bun.write('dist/index.html', html); + // Lazy-loaded QR decoder — kept out of index.html, fetched only when a + // browser without BarcodeDetector opens the scanner. + await Bun.write('dist/jsqr.js', await buildJsQr()); + // PWA sidecars. await Bun.write('dist/manifest.webmanifest', JSON.stringify(MANIFEST, null, 2)); const version = Bun.hash(html).toString(36); @@ -136,5 +156,5 @@ if (import.meta.main) { const kb = (Buffer.byteLength(html) / 1024).toFixed(0); console.log(`✓ dist/index.html written (${kb} KB) — open it offline, no server needed`); - console.log(`✓ PWA: manifest.webmanifest, sw.js (cold-${version}), ${STATIC.length} icons`); + console.log(`✓ PWA: manifest.webmanifest, sw.js (cold-${version}), ${STATIC.length} icons, jsqr.js`); } |
