index ↓
publishing apps
apps.bridgething.com is the default source. anyone can host another.
start with the scaffold
bun create bridgething does all of this for you. it asks for a source name, a github repo and a first
app, and writes a repo that holds your webapps and publishes them. run bun run new <app> to add
another, then push to main. github actions builds each app, hosts the bundles on github pages with the right
headers, and publishes the catalog.
a source is a url
publish a catalog.v1 document at an https url. users add it by pasting the url.
{
"schema": "catalog.v1",
"updated_at": "2026-07-24T00:00:00Z",
"repo": {
"name": "my apps",
"description": "webapps I publish for bridgething.",
"homepage": "https://example.com",
"icon": "https://example.com/icon.svg"
},
"apps": [ /* AppEntry… */ ],
"recommended_sources": []
} app entries
id is a uuidv7. keep it stable across versions, and give every app in your catalog a different one.
{
"id": "019e6701-13f8-71b5-ba04-85d326630e98",
"name": "Calendar",
"description": "Upcoming events from an iCalendar feed.",
"author": "you",
"icon": "https://example.com/icons/calendar.svg",
"screenshots": ["https://example.com/shots/calendar-1.png"],
"homepage": "https://example.com",
"source": "https://github.com/you/calendar",
"versions": [
{
"version": "0.1.0",
"released_at": "2026-07-24T00:00:00Z",
"download": {
"url": "https://example.com/r/<id>/0.1.0.zip",
"size": 402118,
"sha256": "…"
},
"settings": {
"url": "https://example.com/s/<id>/0.1.0.html",
"size": 26909,
"sha256": "…"
},
"permissions": ["net.fetch"],
"role": "launcher",
"provides_overlay": true,
"extension": { "desktop": true, "permissions": ["all"] },
"min_libbridgething_version": "0.12.0",
"changelog": "Initial release."
}
]
} the store checks download.sha256 before installing.
role, provides_overlay, and extension are optional and mirror the bundle's manifest.
see launchers and overlays.
icon can be any image format.
screenshots is up to six https urls of the app running on a device, 800x480. the store card uses the first
one, and the listing shows the rest. with the thing plugged in, chromium answers CDP at
bridgething.local:9222, so Page.captureScreenshot against the kiosk target gives you an
exact 800x480 png. the scaffold wraps that as bun run shot <app>.
min_libbridgething_version is dotted semver, no leading v. the store installs the newest
version the connected device can run.
hosting the settings page
settings is optional and points at the same settings page the bundle
ships, hosted on its own. a companion that has it fetches the page over the internet instead of pulling it off the device,
which matters most on a slow bluetooth link.
sha256 must be the digest of the bytes in the bundle. the device reports that digest itself, and the companion
only uses your hosted copy when the two agree, so a mismatch is not a trust problem: it just falls back to the device.
that also means a bundle pushed straight to a device during development always uses the link, which is what you want
while iterating.
native extensions
a bundle with an extension declares it in manifest.json:
{
"extension": {
"entry": "extension/desktop.mjs",
"permissions": ["all"],
"api": 1
}
} and the catalog entry carries a copy:
{
"extension": {
"desktop": true,
"permissions": ["all"]
}
} desktop is always true. permissions here are the deno descriptors from the bundle
manifest.
an app with any version that declares extension must set source to a
github.com repo url people can open. a catalog that breaks the rule fails to load.
permissions
app permissions go in versions[].permissions. declare every one you use. geo gates
geo.watch and geo.getOnce, and net.proxy gates the socks proxy. the store shows
the rest on the install screen.
the desktop app shows the catalog copy at install and refuses a bundle that asks for anything outside the set the
user confirmed. all and a bare kind widen, so net covers net:discord.com.
keep the catalog list a superset of the bundle manifest's. the desktop app launches deno with the descriptors from the bundle manifest.
serve it with cors
your catalog url and every download.url must send
Access-Control-Allow-Origin: *.
Access-Control-Allow-Origin: *
Accept-Ranges: bytes
Access-Control-Expose-Headers: content-length, content-range, etag, accept-ranges github raw and github pages already send it.
if you are writing a client
emit versions[] newest-first by released_at. sort before you pick.
// correct: sort, then pick
const newest = entry.versions
.filter(v => compatible(v.min_libbridgething_version, device.libbridgethingVersion))
.sort((a, b) => Date.parse(b.released_at) - Date.parse(a.released_at))[0];
// wrong: trusts the order the source emitted
const newest = entry.versions[0]; getting listed
the directory is how people find you without the url.
submit yours on the apps page. the url must be reachable over https, parse as
catalog.v1, and send Access-Control-Allow-Origin.
a new source is unreviewed: named under a collapsed section, apps hidden. a reviewer moves it to listed, which shows its apps under community apps and offers it in the phone app. vouched for puts your apps next to the official ones.
a source that goes down is reported and stays in the directory. apps installed from it keep working.
two sources listing the same app
an installed app takes updates from the source it came from. another catalog listing the same id shows as "also available from".