The app
Bytestream is one Expo SDK 57 universal app (managed workflow) with Expo Router for file-based navigation, targeting web, iOS, and Android from a single TypeScript codebase.Storage keys never rename
Every persisted browser-storage key still carries the product’s originalatlas. prefix — atlas.settings, atlas.saved, atlas.watch,
atlas.theater, atlas.stills.v1, atlas.catalog.v*, atlas.epg.v2, and
the atlas IndexedDB database name itself (src/state/storage.ts).
Auth: Clerk + Supabase
Clerk handles sign-in; Supabase holds the data, gated by row-level security keyed onclerk_sub() — a
Postgres function that reads the Clerk subject out of the request, so a
signed-in viewer can only read or write their own rows (see the RLS policies
in supabase/migrations/20260920090000_recommendations.sql, e.g. (select public.clerk_sub()) = user_id).
Sign-in is fully optional. Without EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY set to
a real pk_ key, no ClerkProvider mounts at all, and the app renders
exactly as it always has — anonymous watch, no auth context.
The stream relay
Roughly 868 channels in the catalog serve every one of their streams fromjmp2.uk (Pluto TV’s edge), which answers with a CORS header that allows only
http://pluto.tv — a browser refuses to fetch it, even though the native app
and VLC play it fine.
worker/ is a small Cloudflare Worker that
fetches such a stream server-side and returns it with a header a browser will
accept. It runs on Cloudflare’s free plan by design — no KV, R2, D1,
Durable Objects, Queues, cron, smart placement, or custom domain — and is
deployed at atlas-stream-relay.brainpowerux.workers.dev.
It is not an open proxy: it relays only hosts on an explicit allowlist and
refuses everything else with a 403. It is not a way around geo-blocking —
Cloudflare answers from a data centre near the viewer, so a geo-blocked
upstream still refuses that viewer. Nothing of the viewer’s goes upstream:
no cookies, no
Authorization, no client User-Agent, no Referer.worker/wrangler.toml, [vars] ALLOWED_HOSTS) is five hosts:
jmp2.uk, pluto.tv, plutotv.net (Pluto’s redirect chain), plus
channel-assembly.mediatailor.us-east-1.amazonaws.com and
nasaplus.akamaized.net (NASA+‘s manifest and segment hosts). HLS is
recursive — a master playlist points at media playlists, which point at
segments and encryption keys — so every URL inside a playlist is rewritten to
route back through the relay, resolved against wherever the playlist was
finally served from after redirects.
The relay is enabled only when EXPO_PUBLIC_ATLAS_RELAY_URL is set at Expo
export time, and only for web — native never routes through it. See
Quickstart for the variable and the
--clear caveat when changing it.
Deploying
The web build deploys to Vercel as a static site (vercel.json: framework: null, buildCommand: pnpm build:web,
outputDirectory: dist). The Vercel project is connected to the GitHub
repository (sachio222/atlas): every push to main builds and promotes
production, and every other branch gets a preview URL. Nothing in the repo
deploys itself; the EPG workflow publishes data to Supabase Storage and the
docs workflow deploys the separate bytestream-docs project.
Two project settings keep deployments small. VERCEL_FORCE_NO_BUILD_CACHE=1
is set on Production and Preview: Vercel stores a build-cache snapshot (about
216 MB here) with every deployment, and at eight deployments a day that
reached 10 GB in eleven days; a cold pnpm install costs about a minute per
build instead. .vercelignore also drops api/*.test.ts, which Vercel would
otherwise build into functions of their own.
A manual deploy still works when you need one:
The GitHub connection depends on the Vercel GitHub App having access to the
repository. If a push does not produce a deployment, check the app’s
repository list under GitHub → Settings → Applications → Vercel, then
vercel git connect from the repo.