DEV Community

hammi
hammi

Posted on

How my 1.17 MB PWA updates itself without an app store (7-endpoint config fan-out + a strict service worker)

I build RKMovies, a small 4K media player that ships as a 1.17 MB Android APK and as a PWA on our own site. No Play Store means no update channel. So when we want to change a notice, a download link, or bump the version prompt, the app has to fetch config at runtime.

Here is what we landed on in updater.js and sw.js.

The config fan-out

fetchConfig() fires 7 GET requests concurrently: raw.githubusercontent.com, cdn.jsdelivr.net, our own domain, a Cloudflare Pages mirror, rentry, paste.ee, and a local /config.json. Every request gets an AbortController with a 4-second timeout and cache: "no-store". A response only counts if it parses as JSON and has at least one of platforms, version, or notice.

The point of 7 endpoints is that any single host can be blocked, slow, or wrong. If our domain is unreachable from some ISP, jsdelivr still answers. If GitHub raw is slow, the Pages mirror answers. We then compare all valid configs, take the highest latest_version, and prefer the platform-specific version (android vs desktop) over the top-level one. The winner gets written to localStorage, and if all 7 fail we boot from that cache instead of nothing.

The service worker side

sw.js precaches an 8-file shell (index, styles, app.js, adgate.js, updater.js, the ArtPlayer vendor bundle, the manifest) under a versioned cache name like rkmovies-shell-v43, then skipWaiting() + clients.claim() so the new shell takes over on next load.

Fetch handling is network-first for same-origin static files, with one hard rule: code assets (app.js, style.css, vendor/, parts/) are only ever answered from an exact cache match. A new ?v= must never be served an old build, so a missed exact match returns a hard 503 instead of stale JS. Only non-code assets fall back to the cached shell with ignoreSearch.

What this buys

A 1.17 MB APK that can change its own download links, notices, and update prompts without a store release. The whole updater is one dependency-free file, and the config fetch is a plain GET of a static JSON file. No device ID, no analytics, no fingerprint. Update checks that don't track you are possible; we just had to make the "phone home" a dumb static file read.

Play with it: the tiny native player I build, sideload the Android APK, or grab the Windows setup exe.

Disclosure: I'm the developer of RKMovies. Size numbers come from the landing page's comparison table, not from marketing.

Top comments (4)

Collapse
 
alexcodebytes profile image
Oleksandr •

I love how Zoyous breaks the Zurulian mold by choosing hands-on disability support over a high-prestige medical career. Her maternal instinct and those striking emerald green eyes make her such a warm, grounding character in this universe.

Collapse
 
hammiwor profile image
hammi •

this one reads like it landed on the wrong article, but thanks for dropping by anyway

Collapse
 
alexcodebytes profile image
Oleksandr •

Guilty as charged — that's what I get for having too many tabs open and mixing up alien fanfic with PWA architecture. Thanks for the graceful reply instead of just deleting it!

For what it's worth, the 7-endpoint config fan-out piece is genuinely interesting. A service worker handling self-updates without an app store is a nice piece of engineering.

Collapse
 
alexcodebytes profile image
Oleksandr •

Haha, guilty as charged! Definitely got my browser tabs mixed up—mixing up alien fanfic lore with PWA architecture is a new one for me. Thanks for the graceful reply!