1
Architecture
Sulthan Zaki edited this page 2026-07-26 20:39:31 +07:00

Architecture

Bromite userscript (isolated world, per-site adapters, localStorage cache)
   -- fetch() HTTPS -->  reverse proxy (TLS + CORS)  -->  Go net/http  -->  SQLite (volume)

Why this shape

Bromite runs Chromium's native userscript engine, not Tampermonkey. That rules out GM_* APIs entirely:

  • No GM_setValue/GM_getValue → page localStorage instead.
  • No GM_registerMenuCommand → on-page UI (floating button + slide-in panel) instead of a browser menu.
  • No GM_xmlhttpRequest for cross-origin → plain fetch(), which only works against a CORS-enabled backend.

Manga sites are https://, so the backend must be HTTPS or the browser blocks the fetch() as mixed content.

Asura and Demonic are separate origins with separate localStorage — a shared remote store (the backend) is the only way to unify bookmarks across them. Cloud sync is required, not a nice-to-have.

The userscript runs in an isolated world, so the embedded API token is invisible to the site's own JS.

Components

  • Backend (backend/) — stdlib net/http, no framework, modernc.org/sqlite (pure Go, CGO_ENABLED=0 → static binary → distroless image). Listens plain :8080; the reverse proxy terminates TLS.
  • Store — one bookmarks table, keyed <site>:<series_id>. Single-user. Sync is last-write-wins.
  • Web UI — same binary, second hostname, password-gated. See Web-UI.
  • Latest-chapter poller — background goroutine that re-checks each bookmarked series' newest chapter on its own schedule, independent of the userscript. Details in Backend-API and plans/2026-07-26-server-latest-chapter-polling.md.
  • Userscript — one file, per-site adapters, Shadow DOM UI. See Userscript.

The updated_at rule

updated_at drives list order, so it must move only on real reading progress — not on favouriting, not on the poller learning a new chapter.

The server applies its own timestamp when a row is new or last_chapter_num changed; otherwise it keeps the stored value. PUT returns the row as stored, and every client (userscript, web UI) adopts that response instead of its own payload. This is the single place that rule lives — see plans/2026-07-25-bookmark-list-favorites-design.md §4.

Known accepted limitation

The poller's read-modify-write (Store.Get + Store.Upsert) is not wrapped in a transaction. A userscript PUT that lands between the poller's read and write can be overwritten by the poller's stale re-read — reverting progress and reordering the list. Accepted for a single-user deployment; not scheduled to be fixed.

Skills used for infra work

multi-stage-dockerfile and docker-compose-orchestration (see repo CLAUDE.md) cover the container patterns behind backend/Dockerfile and the compose files.