Skip to content

Architecture

┌────────────────────────────────────────────────────────────┐
│ Panel (side panel / sidebar / extension tab) │
│ reads the cache with Dexie useLiveQuery │
└───────────────────────────┬────────────────────────────────┘
│ read (extension origin)
┌───────────────────────────▼────────────────────────────────┐
│ Dexie (IndexedDB, extension origin) │
│ ◄── the one source of truth for entity data │
├────────────────────────────────────────────────────────────┤
│ Cache API (extension origin) │
│ ◄── media bytes, with the stable media ID as the key │
├────────────────────────────────────────────────────────────┤
│ browser.storage.session │
│ ◄── temporary worker state (request limits) │
└───────────────────────────▲────────────────────────────────┘
│ write entities (extension origin)
┌───────────────────────────┴────────────────────────────────┐
│ Service worker (extension origin) │
│ routes messages, limits requests, writes the cache │
└───────────────────────────▲────────────────────────────────┘
│ reply with normalized data
┌───────────────────────────┴────────────────────────────────┐
│ Runner (content script, patreon.com origin) │
│ same-origin fetch → normalize → reply │
└────────────────────────────────────────────────────────────┘

Four rules control all decisions:

  1. The runner must not write the cache. A content script runs in the patreon.com origin. Its IndexedDB is a different database from the database of the extension. Thus the runner normalizes the data and sends it in a message. The service worker writes the cache.
  2. The service worker must not keep state between messages. Chrome stops an MV3 service worker after approximately 30 seconds without activity. Each new event starts the timer again. The worker writes its state to browser.storage.session and reads it again for each message. You can do each operation again safely.
  3. Gopher must not open a hidden tab. tabs.create({ active: false, pinned: true }) makes a tab that the user can see. Gopher therefore uses a tab that is already open.
  4. Gopher must stay below the request limits it sets for itself. See request limits.

Read the service worker, the runner, the panel, and options and lifecycle for each component in detail. Read permissions and configuration for the manifest that assembles them.

Platforms

Sidebar APIStoreDeveloper feePriority
Chromechrome.sidePanelChrome Web Store$5 one timePhase 2
Firefoxbrowser.sidebarActionAMOFreePhase 3
Safarinone (use the tab)Mac App Store$99 each yearDeferred
  • The Chrome Web Store stopped the sale of paid extensions and closed its Licensing API. The $5 is the fee to register as a developer. It is not a way to sell the extension. Gopher does not make money (ADR-011).
  • Safari: you can test an unpackaged extension with the Developer menu. To release it, you must make an Xcode wrapper and join the Apple Developer Program. Safari does not have sidePanel, offscreen, identity, or a webRequest that can block. The extension tab operates on all browsers, thus it is the primary surface for Safari.
  • Manifest version: the Chrome build is MV3 and uses a service worker. The Firefox build of WXT is MV2 and uses a background script. Each statement about MV3 in this reference is correct for Chrome. The Firefox background script has the same requirement: do not keep state.