162 lines
7.2 KiB
Markdown
162 lines
7.2 KiB
Markdown
|
|
# One extension directory
|
||
|
|
|
||
|
|
Extensions are currently presented as two kinds. They should be one kind with
|
||
|
|
different provenance. This is what that costs and where the seams actually
|
||
|
|
are.
|
||
|
|
|
||
|
|
The goal, stated plainly: **one catalogue, one lifecycle.** Every extension can
|
||
|
|
be installed, removed, turned on and turned off by the same actions. Some
|
||
|
|
arrive already installed. Who wrote it and who signed it is a fact *about* an
|
||
|
|
extension — searchable, shown on its card — not a wall between two lists.
|
||
|
|
|
||
|
|
## The good news: the runtime is already unified
|
||
|
|
|
||
|
|
None of the split is in the engine.
|
||
|
|
|
||
|
|
- Every extension lives in one directory, `<userData>/extensions/`. A
|
||
|
|
pre-installed one and a downloaded one land in the same place, side by side.
|
||
|
|
`seedBundledAddons()` even says so: *"the framework treats bundled and user
|
||
|
|
add-ons identically, no special path"*.
|
||
|
|
- One discovery pass activates them all — `AddonHost.discoverAndActivate()`
|
||
|
|
scans that single directory.
|
||
|
|
- Enable and disable are already uniform: `settings.disabledAddons` is a list
|
||
|
|
of ids, and `addons-set-enabled` does not care where an extension came from.
|
||
|
|
|
||
|
|
So this is not a rewrite. It is removing four specific things that impose a
|
||
|
|
division the engine never had.
|
||
|
|
|
||
|
|
## Seam 1 — removal does not stick, so the UI hides it
|
||
|
|
|
||
|
|
`seedBundledAddons()` copies any bundled folder whose target is missing. Delete
|
||
|
|
a pre-installed extension and it is back on the next launch. That single fact
|
||
|
|
is why the UI marks them unremovable, and the comment on `addons-list` admits
|
||
|
|
the motive: *"Mark bundled add-ons so the extensions UI can render them
|
||
|
|
differently… look built-in rather than removable extensions."*
|
||
|
|
|
||
|
|
Worse, there is no uninstall at all — for anything. The only removal route is
|
||
|
|
`addons-reveal`, opening the folder in the file manager, described in the
|
||
|
|
source as *"the primary way users edit / uninstall add-ons"*.
|
||
|
|
|
||
|
|
**Fix.** A tombstone list, `settings.removedExtensions: string[]`.
|
||
|
|
|
||
|
|
- Seeding skips any id on it.
|
||
|
|
- Uninstall adds the id; installing again clears it.
|
||
|
|
|
||
|
|
Then removal means something for every extension, and a user who removes a
|
||
|
|
pre-installed one does not have it resurrected by the next update.
|
||
|
|
|
||
|
|
**Fix.** An `extensions-remove` handler that deactivates the extension, moves
|
||
|
|
its folder into the existing `extensions-backups/<id>-<version>-<stamp>/`
|
||
|
|
rather than deleting outright, tombstones the id and re-runs discovery. Its
|
||
|
|
stored data in `extensions-data/<id>.json` survives by default so a reinstall
|
||
|
|
keeps settings, with an explicit "also delete its data" option for people who
|
||
|
|
mean it.
|
||
|
|
|
||
|
|
## Seam 2 — two catalogues, and one of them cannot install
|
||
|
|
|
||
|
|
There are two publishing shapes today:
|
||
|
|
|
||
|
|
| | Where | Signed by | Indexed? | Installable? |
|
||
|
|
|---|---|---|---|---|
|
||
|
|
| Operator channel | `bns/theseus/extensions/<id>/` | operator Ed25519 key | no index, per-id only | **no** |
|
||
|
|
| Community | `bns/theseus/extensions/community/<id>/` | publisher's BCH key, checked against the name's on-chain owner | `/api/ext/catalog` | yes |
|
||
|
|
|
||
|
|
Every install route — the Settings button and the
|
||
|
|
`theseus://extensions/install/<id>` deep link — goes through
|
||
|
|
`installCommunityById`, which resolves the id against the community catalogue.
|
||
|
|
`main.js` has no operator-channel install path at all.
|
||
|
|
|
||
|
|
Two consequences, both live right now:
|
||
|
|
|
||
|
|
- The **Install in Theseus** buttons added to the Screenshot and PDF Editor
|
||
|
|
cards do not work. Those ids are not in the community catalogue. It goes
|
||
|
|
unnoticed only because both already ship in the build.
|
||
|
|
- Moving an extension from the community channel to the operator channel makes
|
||
|
|
it **uninstallable** unless it also ships in the build. This is exactly the
|
||
|
|
trap the Word editor migration is walking into.
|
||
|
|
|
||
|
|
**Fix.** One catalogue endpoint listing everything, with the signing scheme as
|
||
|
|
a field rather than as a separate world. `/api/ext/catalog` keeps its URL and
|
||
|
|
merges both prefixes, each entry carrying:
|
||
|
|
|
||
|
|
```jsonc
|
||
|
|
{
|
||
|
|
"id": "pdf-editor",
|
||
|
|
"publisher": "Silent Mode", // display name of the developer
|
||
|
|
"signer": { "kind": "operator" }, // or { kind: "name", name: "theseus.x", owner: "bchtest:…" }
|
||
|
|
"channel": "https://…/extensions/pdf-editor/updates.json",
|
||
|
|
"latest": "0.2.0",
|
||
|
|
"sha256": "…"
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
and one `installExtension(id)` in main.js that resolves from it and verifies
|
||
|
|
with whichever key the entry declares. That is the smallest change that makes
|
||
|
|
installation uniform, and it repairs the dead buttons on the way past.
|
||
|
|
|
||
|
|
## Seam 3 — two lists in Settings
|
||
|
|
|
||
|
|
Settings has an Extensions section and a separate Plug-ins section, divided by
|
||
|
|
`manifest.category === "plugin"` so that Aegis and friends *"look built-in"*.
|
||
|
|
That is the same division again, by a different field.
|
||
|
|
|
||
|
|
**Fix.** One list. Category becomes a filter value, not a page. Alongside it:
|
||
|
|
|
||
|
|
- free-text search over name, description and developer
|
||
|
|
- filter chips: All · Enabled · Disabled · Pre-installed · by developer
|
||
|
|
- each card shows developer, version, where it updates from, and the same four
|
||
|
|
actions for every row
|
||
|
|
|
||
|
|
## Seam 4 — the website mirrors the split
|
||
|
|
|
||
|
|
`site-theseus-x/extensions/index.html` hardcodes a card per shipped extension
|
||
|
|
and renders community ones from `catalog.json` into a separate section below.
|
||
|
|
Adding an extension means hand-writing HTML.
|
||
|
|
|
||
|
|
**Fix.** Render every card from the one catalogue. The page already has the
|
||
|
|
machinery — it fills version, SHA-256 and tarball URL live from each
|
||
|
|
`updates.json` — it just needs one loop over one list instead of two.
|
||
|
|
|
||
|
|
## The record an extension carries
|
||
|
|
|
||
|
|
`AddonHost.snapshot()` reports id, name, version, description, author, icon,
|
||
|
|
capabilities, category, folder, enabled and error. `main.js` then bolts on
|
||
|
|
`bundled` by looking for a same-named folder in `resources/bundled-addons/`.
|
||
|
|
|
||
|
|
Replace that flag and add the provenance the new UI wants to search on:
|
||
|
|
|
||
|
|
- `publisher` — who signed the channel
|
||
|
|
- `origin` — `"preinstalled"` or `"installed"`, a badge and nothing more
|
||
|
|
- `channel` — its `updates.json`, so a card can say where updates come from
|
||
|
|
- `installedAt`, `updatedAt`
|
||
|
|
- `removable` — true for everything, once tombstones exist
|
||
|
|
|
||
|
|
`bundled` goes away. Nothing downstream should branch on where an extension
|
||
|
|
came from.
|
||
|
|
|
||
|
|
## One thing not to flatten
|
||
|
|
|
||
|
|
"Who the developer is" should not split the catalogue, and it should not be
|
||
|
|
hidden either. An extension signed by the Silent Mode operator key and one
|
||
|
|
signed by a stranger's wallet carry the same permissions once installed, and
|
||
|
|
they are not equally safe. The consent dialog already gets this right — it
|
||
|
|
names the publisher and says the extension *"runs with the same access as any
|
||
|
|
add-on"*. One list, one lifecycle, and the signer stated plainly on the card
|
||
|
|
and in the prompt.
|
||
|
|
|
||
|
|
## Order of work
|
||
|
|
|
||
|
|
Each step stands on its own and leaves the product working.
|
||
|
|
|
||
|
|
1. **Tombstones and `extensions-remove`.** Removal becomes real for every
|
||
|
|
extension. No UI change yet.
|
||
|
|
2. **One catalogue and one `installExtension`.** Fixes the dead Install
|
||
|
|
buttons and unblocks the Word editor migration.
|
||
|
|
3. **One Settings list** with search and filters; Plug-ins becomes a filter.
|
||
|
|
4. **Site page renders from the catalogue** instead of hardcoded cards.
|
||
|
|
5. **Drop `bundled`** from `addons-list` and the last branches on it.
|
||
|
|
|
||
|
|
Step 2 is the one with a deadline attached: until it lands, nothing should be
|
||
|
|
removed from the community catalogue, because that is the only route by which
|
||
|
|
an extension can be installed.
|