@@ -102,7 +102,8 @@ as an `Authorization` header to that forge's API.
102102
103103The options page is forge-agnostic: it renders one token field per forge that
104104declares authentication support (see ` authForges() ` in ` forges.js ` ), so a new
105- authenticated forge needs no options-page change.
105+ authenticated forge needs no options-page change. Self-hosted instances (see
106+ [ Forges] ( #forges ) ) get their own token field too, keyed per instance.
106107
107108For GitHub, create a [ personal access token] [ gh-token ] (classic or
108109fine-grained) — no scope is required for public repositories; grant repository
@@ -121,15 +122,18 @@ in your account settings with repository read scope. For GitLab, create a
121122
122123Forge endpoints are configured in ` forges.js ` , which defines one ** adapter**
123124per forge that normalizes its API and URL quirks behind a common interface,
124- plus a ` forgeForHostname() ` registry lookup.
125+ plus a ` forgeForHostname() ` registry lookup. Single-host forges (GitHub,
126+ Bitbucket) are plain adapter objects; multi-host forges (GitLab, Forgejo/Gitea)
127+ are * families* whose instances — public and self-hosted alike — are built with
128+ ` instanceOf() ` (see [ Families and self-hosted forges] ( #families-and-self-hosted-forges ) ).
125129
126130Here is the interface for each forge:
127131
128132| Member | Purpose |
129133| ------------ | ------------------------------------------------------------------ |
130134| ` label ` | human-readable forge name |
131- | ` hostnames ` | array of hostnames (static forges only) |
132- | ` base_url ` | API URL (static forges only) |
135+ | ` hostnames ` | array of hostnames matched against the page (one per family instance) |
136+ | ` base_url ` | API root URL the endpoint builders are hung off of |
133137| ` parseUrl ` | ` (URL) → { projectKey, repoSlug, filepath, origin } ` or ` null ` |
134138| ` listPRsUrl ` | API endpoint listing the repo's open PRs |
135139| ` pullRequests ` | ` (listResponse) → array of PR objects ` (unwraps paginated envelopes) |
@@ -157,6 +161,43 @@ Supported forges:
157161- ** Codeberg** (Forgejo, Gitea-compatible API): https://forgejo.org/docs/latest/user/api-usage/
158162- ** GitLab** : https://docs.gitlab.com/ee/api/merge_requests.html .
159163
164+ ### Families and self-hosted forges
165+
166+ ** GitLab (CE/EE)** and ** Forgejo/Gitea** can each run on many hosts. Rather than
167+ hardcode one adapter per host, ` forges.js ` defines a * family* holding the shared
168+ API/URL logic (` gitlabFamily ` , ` forgejoFamily ` ) and builds concrete adapters
169+ from it with ` instanceOf(family, { hostname, label, tokenStorageKey }) ` , which
170+ sets the instance-specific fields (` hostnames ` , ` base_url ` = ` https://<hostname> `
171+ + the family ` apiPath ` , ` tokenStorageKey ` ) and inherits everything else through
172+ the prototype chain.
173+
174+ The public instances are built this exact way — ` codeberg ` is
175+ ` instanceOf(forgejoFamily, …) ` and ` gitlab ` is ` instanceOf(gitlabFamily, …) ` —
176+ so a public forge is ** not special** : it is just a built-in instance. A
177+ self-hosted forge is the same ` instanceOf ` , only built at runtime:
178+
179+ - the user adds an instance (its hostname + which software it runs); the entry
180+ is stored under the ` selfHostedInstances ` key in ` browser.storage.local ` ;
181+ - ` buildSelfHostedForge({ type, hostname }) ` picks the family from
182+ ` selfHostedFamilies ` (` { gitlab: gitlabFamily, forgejo: forgejoFamily } ` ),
183+ builds the adapter with the same ` instanceOf ` , and tags it ` selfHosted ` ;
184+ - ` forgeForHostname(hostname, instances) ` matches a page against the built-in
185+ instances first, then the configured self-hosted instances (by exact
186+ hostname).
187+
188+ Because Manifest V3 cannot declare arbitrary hosts up front, the manifest
189+ declares ` optional_host_permissions ` and the options page ** requests the host
190+ permission** for an instance origin when it is added (a user gesture), and
191+ gives it back with ` permissions.remove() ` when the instance is removed. The
192+ background script skips an instance page until its permission is granted.
193+
194+ Bitbucket Data Center is ** not** covered: it speaks a different REST API than
195+ Bitbucket Cloud and would need its own adapter.
196+
197+ To support a new self-hosted software, define its family and add it to
198+ ` selfHostedFamilies ` ; the public instance (if any) is then just another
199+ ` instanceOf ` of that family.
200+
160201## Internationalization
161202
162203Translations live in ` _locales/<lang>/messages.json ` . The default locale is
0 commit comments