mirror of
https://github.com/Such-Software/adaptive-multi-protocol-browser.git
synced 2026-10-17 06:21:26 +02:00
Add IPFS dry-run adapter support
This commit is contained in:
@@ -31,6 +31,7 @@ multi-tenant platform.
|
||||
python3 -m ampbrowser route https://wownero.org/
|
||||
python3 -m ampbrowser route http://example.b32.i2p/
|
||||
python3 -m ampbrowser route gemini://wownero.org/
|
||||
python3 -m ampbrowser route ipfs://bafyexample/
|
||||
python3 -m ampbrowser inspect
|
||||
python3 -m ampbrowser plan gemini://wownero.org/
|
||||
python3 -m ampbrowser open https://wownero.org/
|
||||
|
||||
+15
-1
@@ -106,10 +106,24 @@ ADAPTERS: dict[str, TransportAdapter] = {
|
||||
install_strategy="install Reticulum provider",
|
||||
start_strategy="start configured Reticulum interface",
|
||||
stop_policy="stop only AMPB-owned Reticulum process",
|
||||
note="Reticulum adapter is planned; interface readiness needs explicit config",
|
||||
note="Reticulum adapter is planned for resilient/private routing; it is not an anonymity layer",
|
||||
commands=("rnsd", "rnstatus", "rnodeconf"),
|
||||
manage_supported=False,
|
||||
),
|
||||
"ipfs": TransportAdapter(
|
||||
name="ipfs",
|
||||
profile="ipfs",
|
||||
endpoint="http://127.0.0.1:8080",
|
||||
adopt_check="HTTP gateway on 127.0.0.1:8080",
|
||||
managed_state=".ampb/transports/ipfs",
|
||||
install_strategy="install IPFS/Kubo provider",
|
||||
start_strategy="start managed IPFS gateway",
|
||||
stop_policy="stop only AMPB-owned IPFS process",
|
||||
note="IPFS local gateway for content-addressed distribution; not an anonymity layer",
|
||||
commands=("ipfs",),
|
||||
connect_host="127.0.0.1",
|
||||
connect_port=8080,
|
||||
),
|
||||
"gemini": TransportAdapter(
|
||||
name="gemini",
|
||||
profile="gemini",
|
||||
|
||||
@@ -20,7 +20,7 @@ CANDIDATE_TRANSPORTS = (
|
||||
CandidateTransport(
|
||||
name="ipfs",
|
||||
role="content-addressed web",
|
||||
status="next-evaluate",
|
||||
status="active-dry-run",
|
||||
route_examples=("ipfs://<cid>", "/ipfs/<cid>", "ipns://<name>"),
|
||||
browser_fit="native fetch or local gateway",
|
||||
publisher_fit="static site export and pinning",
|
||||
|
||||
+10
-2
@@ -23,8 +23,9 @@ class TransportDefinition:
|
||||
ROUTE_RULES = (
|
||||
RouteRule("*.onion", "tor", "tor", "route through Tor SOCKS"),
|
||||
RouteRule("*.i2p", "i2p", "i2p", "route through I2P HTTP proxy"),
|
||||
RouteRule("ipfs://*, ipns://*, /ipfs/*, /ipns/*", "ipfs", "ipfs", "route through local IPFS gateway"),
|
||||
RouteRule("gemini://*", "gemini", "gemini", "fetch and render Gemtext"),
|
||||
RouteRule("rns://*, lxmf://*, nomad://*", "reticulum", "reticulum", "route through Reticulum adapter"),
|
||||
RouteRule("rns://*, lxmf://*, nomad://*", "reticulum", "reticulum", "route to Reticulum adapter"),
|
||||
RouteRule("http://*, https://*", "clearnet", "clearnet", "ordinary web profile"),
|
||||
)
|
||||
|
||||
@@ -43,6 +44,13 @@ TRANSPORT_DEFINITIONS = (
|
||||
"i2p",
|
||||
"Adopt existing i2pd/I2P router when healthy; otherwise prompt before managed setup.",
|
||||
),
|
||||
TransportDefinition(
|
||||
"ipfs",
|
||||
"HTTP gateway on 127.0.0.1:8080",
|
||||
".ampb/transports/ipfs",
|
||||
"ipfs",
|
||||
"Adopt existing IPFS gateway when healthy; otherwise prompt before managed setup.",
|
||||
),
|
||||
TransportDefinition(
|
||||
"gemini",
|
||||
"built-in renderer available",
|
||||
@@ -55,6 +63,6 @@ TRANSPORT_DEFINITIONS = (
|
||||
"RNS tools and configured interfaces",
|
||||
".ampb/transports/reticulum",
|
||||
"reticulum",
|
||||
"Adapter planned; physical/link-layer setup may require operator config.",
|
||||
"Adapter planned for resilient/private routing; physical/link-layer setup may require operator config.",
|
||||
),
|
||||
)
|
||||
|
||||
@@ -33,11 +33,14 @@ PLATFORM_CAPABILITIES = (
|
||||
PlatformCapability("i2p", "desktop", "ready", "ready", "planned", "planned", "Adopt local I2P proxy or prompt before managed desktop daemon setup."),
|
||||
PlatformCapability("i2p", "android", "planned", "planned", "planned", "planned", "Use an app-owned foreground service or compatible installed I2P provider."),
|
||||
PlatformCapability("i2p", "ios", "constrained", "planned", "foreground-only", "constrained", "Treat iOS I2P support as foreground-only until native adapter constraints are proven."),
|
||||
PlatformCapability("ipfs", "desktop", "ready", "ready", "planned", "planned", "Adopt local IPFS gateway or prompt before managed gateway setup."),
|
||||
PlatformCapability("ipfs", "android", "planned", "planned", "planned", "planned", "Use gateway-first IPFS browsing; embedded node support needs adapter proof."),
|
||||
PlatformCapability("ipfs", "ios", "constrained", "planned", "foreground-only", "constrained", "Use gateway-first IPFS browsing; embedded node support is constrained."),
|
||||
PlatformCapability("gemini", "desktop", "ready", "ready", "ready", "unsupported", "Built-in fetch/render path can be shared by desktop shell."),
|
||||
PlatformCapability("gemini", "android", "ready", "ready", "ready", "unsupported", "Built-in fetch/render path can be shared by Android shell."),
|
||||
PlatformCapability("gemini", "ios", "ready", "ready", "ready", "unsupported", "Built-in fetch/render path can be shared by iOS shell."),
|
||||
PlatformCapability("reticulum", "desktop", "planned", "planned", "planned", "planned", "Adapter and local interface readiness checks are planned."),
|
||||
PlatformCapability("reticulum", "android", "planned", "planned", "planned", "planned", "Mobile adapter should use explicit foreground service consent."),
|
||||
PlatformCapability("reticulum", "desktop", "planned", "planned", "planned", "planned", "Resilient/private networking adapter and interface checks are planned."),
|
||||
PlatformCapability("reticulum", "android", "planned", "planned", "planned", "planned", "Mobile resilient-network adapter should use explicit foreground service consent."),
|
||||
PlatformCapability("reticulum", "ios", "constrained", "planned", "foreground-only", "constrained", "Do not assume background Reticulum operation on iOS."),
|
||||
PlatformCapability("unknown", "desktop", "unsupported", "unsupported", "unsupported", "unsupported", "No platform route is available."),
|
||||
PlatformCapability("unknown", "android", "unsupported", "unsupported", "unsupported", "unsupported", "No platform route is available."),
|
||||
|
||||
+20
-1
@@ -14,6 +14,10 @@ class Route:
|
||||
|
||||
|
||||
def route_url(raw_url: str) -> Route:
|
||||
path_route = _route_path_address(raw_url)
|
||||
if path_route:
|
||||
return path_route
|
||||
|
||||
normalized = _normalize_url(raw_url)
|
||||
parsed = urlparse(normalized)
|
||||
scheme = parsed.scheme.lower()
|
||||
@@ -21,6 +25,8 @@ def route_url(raw_url: str) -> Route:
|
||||
|
||||
if scheme == "gemini":
|
||||
return Route(raw_url, normalized, "gemini", "gemini", "gemini scheme")
|
||||
if scheme in {"ipfs", "ipns"}:
|
||||
return Route(raw_url, normalized, "ipfs", "ipfs", f"{scheme} scheme")
|
||||
if scheme in {"rns", "lxmf", "nomad"}:
|
||||
return Route(raw_url, normalized, "reticulum", "reticulum", f"{scheme} scheme")
|
||||
if host.endswith(".onion"):
|
||||
@@ -34,6 +40,19 @@ def route_url(raw_url: str) -> Route:
|
||||
|
||||
def _normalize_url(raw_url: str) -> str:
|
||||
value = raw_url.strip()
|
||||
if "://" not in value and not value.startswith(("rns:", "lxmf:")):
|
||||
if "://" not in value and not value.startswith(("rns:", "lxmf:", "ipfs:", "ipns:")):
|
||||
value = "https://" + value
|
||||
return value
|
||||
|
||||
|
||||
def _route_path_address(raw_url: str) -> Route | None:
|
||||
value = raw_url.strip()
|
||||
if value.startswith("/ipfs/"):
|
||||
cid = value.removeprefix("/ipfs/").strip("/")
|
||||
normalized = f"ipfs://{cid}" if cid else "ipfs://"
|
||||
return Route(raw_url, normalized, "ipfs", "ipfs", "/ipfs path")
|
||||
if value.startswith("/ipns/"):
|
||||
name = value.removeprefix("/ipns/").strip("/")
|
||||
normalized = f"ipns://{name}" if name else "ipns://"
|
||||
return Route(raw_url, normalized, "ipfs", "ipfs", "/ipns path")
|
||||
return None
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
|
||||
> Status: draft | Updated 2026-07-05 | Applies to: AMPB contributors and integrators
|
||||
|
||||
AMPB is a browser orchestration layer for privacy and alternate-network transports. It
|
||||
keeps the routing, transport management, and profile isolation decisions outside the
|
||||
browser engine until those contracts are stable.
|
||||
AMPB is a browser orchestration layer for privacy, resilient-network, content-addressed,
|
||||
and alternate-web transports. It keeps the routing, transport management, and profile
|
||||
isolation decisions outside the browser engine until those contracts are stable.
|
||||
|
||||
## Operating Model
|
||||
|
||||
@@ -20,7 +20,7 @@ browser engine until those contracts are stable.
|
||||
|
||||
- `ampbrowser.routing`: deterministic URL-to-transport rules.
|
||||
- `ampbrowser.adapters`: transport ownership, setup, inspection, and health contracts.
|
||||
- `ampbrowser.transports`: local readiness checks for Tor, I2P, Reticulum, and Gemini.
|
||||
- `ampbrowser.transports`: local readiness checks for Tor, I2P, IPFS, Reticulum, and Gemini.
|
||||
- `ampbrowser.plan`: dry-run action planner for browser launches.
|
||||
- `ampbrowser.launch`: side-effect-free launch specs and first-use consent state.
|
||||
- `ampbrowser.platforms`: platform capability matrix for desktop, Android, and iOS.
|
||||
@@ -31,7 +31,7 @@ browser engine until those contracts are stable.
|
||||
|
||||
The first implementation should be a launcher and profile manager, not a browser-engine
|
||||
fork. Tor Browser, Mullvad Browser, Firefox profiles, Chromium profiles, terminal Gemini
|
||||
clients, and future Reticulum-native viewers can all sit behind the same route and
|
||||
clients, local IPFS gateways, and future Reticulum-native viewers can all sit behind the same route and
|
||||
transport contracts.
|
||||
|
||||
The long-term browser can become more integrated once the orchestration surface is boring:
|
||||
|
||||
@@ -2,15 +2,16 @@
|
||||
|
||||
> Status: draft | Updated 2026-07-06 | Applies to: AMPB and AMPG transport planning
|
||||
|
||||
Candidate transports are tracked separately from active adapters. A candidate should not
|
||||
be routed by default until AMPB has an adapter contract, fixture behavior, mobile posture,
|
||||
and publishing story for it.
|
||||
Candidate transports are tracked from evaluation through active dry-run support. A
|
||||
candidate should not become an enabled-by-default route until AMPB has an adapter
|
||||
contract, fixture behavior, mobile posture, and publishing story for it.
|
||||
|
||||
## Selection Rules
|
||||
|
||||
- Prefer transports that can open AMPG static output or serve normal web/Gemini content.
|
||||
- Treat discovery/social protocols as metadata layers, not page transports.
|
||||
- Treat permanent archives as publishing targets with explicit user warnings.
|
||||
- Treat resilient overlays separately from anonymity networks.
|
||||
- Keep mobile constraints visible before a candidate becomes active.
|
||||
|
||||
## Current Matrix
|
||||
|
||||
@@ -14,8 +14,9 @@ user opens a URL that needs it and approves the first-use prompt.
|
||||
- Clearnet URLs use a clearnet profile.
|
||||
- `.onion` hosts use Tor through `127.0.0.1:9050` when available.
|
||||
- `.i2p` hosts use I2P through `127.0.0.1:4444` when available.
|
||||
- `ipfs://`, `ipns://`, `/ipfs/`, and `/ipns/` addresses use a local IPFS gateway when available.
|
||||
- `gemini://` URLs use the Gemini profile.
|
||||
- Reticulum-family URLs are routed to the Reticulum adapter once configured.
|
||||
- Reticulum-family URLs use the Reticulum adapter for resilient/private routing once configured.
|
||||
|
||||
## Config File
|
||||
|
||||
@@ -39,6 +40,11 @@ enabled = true
|
||||
mode = "adopt-or-prompt-manage"
|
||||
http_proxy = "127.0.0.1:4444"
|
||||
|
||||
[transports.ipfs]
|
||||
enabled = true
|
||||
mode = "adopt-or-prompt-manage"
|
||||
gateway = "127.0.0.1:8080"
|
||||
|
||||
[transports.reticulum]
|
||||
enabled = false
|
||||
mode = "adopt"
|
||||
|
||||
@@ -8,5 +8,6 @@ This file is generated from code. Do not edit it by hand.
|
||||
| --- | --- | --- | --- | --- | --- | --- |
|
||||
| `tor` | `socks5://127.0.0.1:9050` | SOCKS on 127.0.0.1:9050 | install Tor provider | start managed Tor daemon | stop only AMPB-owned Tor daemon | Tor SOCKS proxy |
|
||||
| `i2p` | `http://127.0.0.1:4444` | HTTP proxy on 127.0.0.1:4444 | install I2P provider | start managed I2P router | stop only AMPB-owned I2P router | I2P HTTP proxy |
|
||||
| `reticulum` | `rns://local` | RNS tools and configured interfaces | install Reticulum provider | start configured Reticulum interface | stop only AMPB-owned Reticulum process | Reticulum adapter is planned; interface readiness needs explicit config |
|
||||
| `reticulum` | `rns://local` | RNS tools and configured interfaces | install Reticulum provider | start configured Reticulum interface | stop only AMPB-owned Reticulum process | Reticulum adapter is planned for resilient/private routing; it is not an anonymity layer |
|
||||
| `ipfs` | `http://127.0.0.1:8080` | HTTP gateway on 127.0.0.1:8080 | install IPFS/Kubo provider | start managed IPFS gateway | stop only AMPB-owned IPFS process | IPFS local gateway for content-addressed distribution; not an anonymity layer |
|
||||
| `gemini` | `builtin://gemtext-renderer` | built-in renderer available | unsupported | use built-in Gemtext fetch/render path | no daemon to stop | Built-in Gemtext fetch/render path |
|
||||
|
||||
@@ -6,7 +6,7 @@ This file is generated from code. Do not edit it by hand.
|
||||
|
||||
| Candidate | Role | Status | Routes | Browser Fit | Publisher Fit | Mobile Fit | Source | Note |
|
||||
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
|
||||
| `ipfs` | content-addressed web | `next-evaluate` | `ipfs://<cid>`, `/ipfs/<cid>`, `ipns://<name>` | native fetch or local gateway | static site export and pinning | gateway first; embedded node later | [source](https://docs.ipfs.tech/concepts/what-is-ipfs/) | Strong fit for AMPG static output; not an anonymity layer by itself. |
|
||||
| `ipfs` | content-addressed web | `active-dry-run` | `ipfs://<cid>`, `/ipfs/<cid>`, `ipns://<name>` | native fetch or local gateway | static site export and pinning | gateway first; embedded node later | [source](https://docs.ipfs.tech/concepts/what-is-ipfs/) | Strong fit for AMPG static output; not an anonymity layer by itself. |
|
||||
| `yggdrasil` | encrypted IPv6 overlay | `next-evaluate` | `http://[0200::]/`, `https://<yggdrasil-host>/` | ordinary HTTP over overlay IPv6 | serve normal HTTP/Gemini over overlay address | cross-platform, but mobile lifecycle needs adapter proof | [source](https://yggdrasil-network.github.io/) | Good fit for old-phone nodes and local/community mesh scenarios. |
|
||||
| `lokinet` | low-latency onion-routed overlay | `evaluate` | `http://<name>.loki/`, `https://<name>.loki/` | ordinary IP traffic over overlay | SNApp-style hidden service publishing | Android likely first; iOS constrained | [source](https://docs.oxen.io/oxen-docs/products-built-on-oxen/lokinet) | Interesting Tor/I2P-adjacent candidate; depends on project health and packaging. |
|
||||
| `hyphanet` | privacy-preserving publishing datastore | `explore` | `USK@...`, `CHK@...`, `SSK@...` | local proxy/freesite viewer | censorship-resistant site publishing | possible on Android; iOS unlikely for full node | [source](https://www.hyphanet.org/) | Strong ideological fit; UX and key/address model need careful design. |
|
||||
|
||||
@@ -15,11 +15,14 @@ This file is generated from code. Do not edit it by hand.
|
||||
| `i2p` | `desktop` | `ready` | `ready` | `planned` | `planned` | Adopt local I2P proxy or prompt before managed desktop daemon setup. |
|
||||
| `i2p` | `android` | `planned` | `planned` | `planned` | `planned` | Use an app-owned foreground service or compatible installed I2P provider. |
|
||||
| `i2p` | `ios` | `constrained` | `planned` | `foreground-only` | `constrained` | Treat iOS I2P support as foreground-only until native adapter constraints are proven. |
|
||||
| `ipfs` | `desktop` | `ready` | `ready` | `planned` | `planned` | Adopt local IPFS gateway or prompt before managed gateway setup. |
|
||||
| `ipfs` | `android` | `planned` | `planned` | `planned` | `planned` | Use gateway-first IPFS browsing; embedded node support needs adapter proof. |
|
||||
| `ipfs` | `ios` | `constrained` | `planned` | `foreground-only` | `constrained` | Use gateway-first IPFS browsing; embedded node support is constrained. |
|
||||
| `gemini` | `desktop` | `ready` | `ready` | `ready` | `unsupported` | Built-in fetch/render path can be shared by desktop shell. |
|
||||
| `gemini` | `android` | `ready` | `ready` | `ready` | `unsupported` | Built-in fetch/render path can be shared by Android shell. |
|
||||
| `gemini` | `ios` | `ready` | `ready` | `ready` | `unsupported` | Built-in fetch/render path can be shared by iOS shell. |
|
||||
| `reticulum` | `desktop` | `planned` | `planned` | `planned` | `planned` | Adapter and local interface readiness checks are planned. |
|
||||
| `reticulum` | `android` | `planned` | `planned` | `planned` | `planned` | Mobile adapter should use explicit foreground service consent. |
|
||||
| `reticulum` | `desktop` | `planned` | `planned` | `planned` | `planned` | Resilient/private networking adapter and interface checks are planned. |
|
||||
| `reticulum` | `android` | `planned` | `planned` | `planned` | `planned` | Mobile resilient-network adapter should use explicit foreground service consent. |
|
||||
| `reticulum` | `ios` | `constrained` | `planned` | `foreground-only` | `constrained` | Do not assume background Reticulum operation on iOS. |
|
||||
| `unknown` | `desktop` | `unsupported` | `unsupported` | `unsupported` | `unsupported` | No platform route is available. |
|
||||
| `unknown` | `android` | `unsupported` | `unsupported` | `unsupported` | `unsupported` | No platform route is available. |
|
||||
|
||||
@@ -8,6 +8,7 @@ This file is generated from code. Do not edit it by hand.
|
||||
| --- | --- | --- | --- |
|
||||
| `*.onion` | `tor` | `tor` | route through Tor SOCKS |
|
||||
| `*.i2p` | `i2p` | `i2p` | route through I2P HTTP proxy |
|
||||
| `ipfs://*, ipns://*, /ipfs/*, /ipns/*` | `ipfs` | `ipfs` | route through local IPFS gateway |
|
||||
| `gemini://*` | `gemini` | `gemini` | fetch and render Gemtext |
|
||||
| `rns://*, lxmf://*, nomad://*` | `reticulum` | `reticulum` | route through Reticulum adapter |
|
||||
| `rns://*, lxmf://*, nomad://*` | `reticulum` | `reticulum` | route to Reticulum adapter |
|
||||
| `http://*, https://*` | `clearnet` | `clearnet` | ordinary web profile |
|
||||
|
||||
@@ -8,5 +8,6 @@ This file is generated from code. Do not edit it by hand.
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `tor` | SOCKS on 127.0.0.1:9050 | `.ampb/transports/tor` | `tor` | Adopt existing Tor when healthy; otherwise prompt before managed setup. |
|
||||
| `i2p` | HTTP proxy on 127.0.0.1:4444 | `.ampb/transports/i2p` | `i2p` | Adopt existing i2pd/I2P router when healthy; otherwise prompt before managed setup. |
|
||||
| `ipfs` | HTTP gateway on 127.0.0.1:8080 | `.ampb/transports/ipfs` | `ipfs` | Adopt existing IPFS gateway when healthy; otherwise prompt before managed setup. |
|
||||
| `gemini` | built-in renderer available | `.ampb/transports/gemini` | `gemini` | No daemon required for static Gemtext browsing. |
|
||||
| `reticulum` | RNS tools and configured interfaces | `.ampb/transports/reticulum` | `reticulum` | Adapter planned; physical/link-layer setup may require operator config. |
|
||||
| `reticulum` | RNS tools and configured interfaces | `.ampb/transports/reticulum` | `reticulum` | Adapter planned for resilient/private routing; physical/link-layer setup may require operator config. |
|
||||
|
||||
@@ -8,16 +8,17 @@ mobile shells should implement only the platform-specific browser and transport
|
||||
|
||||
## Android
|
||||
|
||||
Android is the primary mobile target for full AMPB browsing. Tor, I2P, and Reticulum
|
||||
adapters should run only after a URL selects that transport and the user approves setup.
|
||||
Managed adapters must use visible, stoppable foreground services and app-owned state.
|
||||
Android is the primary mobile target for full AMPB browsing. Tor, I2P, IPFS, and
|
||||
Reticulum adapters should run only after a URL selects that transport and the user
|
||||
approves setup. Managed adapters must use visible, stoppable foreground services and
|
||||
app-owned state.
|
||||
|
||||
## iOS
|
||||
|
||||
iOS is a supported design target, but transport management is constrained. AMPB should
|
||||
assume foreground-only sessions unless a reviewed platform capability proves otherwise.
|
||||
The iOS shell should still share routing, policy, consent, fixture, Gemini, and clearnet
|
||||
behavior with the rest of AMPB.
|
||||
assume foreground-only sessions for managed transports unless a reviewed platform
|
||||
capability proves otherwise. The iOS shell should still share routing, policy, consent,
|
||||
fixture, Gemini, IPFS gateway, and clearnet behavior with the rest of AMPB.
|
||||
|
||||
## Capability Matrix
|
||||
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@ first-use setup prompt.
|
||||
|
||||
## Consent Model
|
||||
|
||||
AMPB does not install or start Tor, I2P, Reticulum, or other optional transports when the
|
||||
AMPB does not install or start Tor, I2P, IPFS, Reticulum, or other optional transports when the
|
||||
application starts. Setup is lazy:
|
||||
|
||||
1. The user opens a URL.
|
||||
|
||||
@@ -32,8 +32,9 @@ leases, tunnels, and daemon logs in that tree are local state and must not be co
|
||||
|
||||
- Tor: detect SOCKS on `127.0.0.1:9050`; managed start planned.
|
||||
- I2P: detect HTTP proxy on `127.0.0.1:4444`; managed start planned.
|
||||
- IPFS: detect HTTP gateway on `127.0.0.1:8080`; managed gateway start planned.
|
||||
- Gemini: built-in route and render path; no daemon required for static browsing.
|
||||
- Reticulum: route contract exists; adapter and readiness model are planned.
|
||||
- Reticulum: resilient/private route contract exists; adapter and readiness model are planned.
|
||||
|
||||
Generated transport metadata lives in [generated transports](generated/transports.md).
|
||||
Adapter ownership and setup contracts live in [generated adapters](generated/adapters.md).
|
||||
|
||||
@@ -10,6 +10,10 @@ mode = "adopt-or-prompt-manage"
|
||||
enabled = true
|
||||
mode = "adopt-or-prompt-manage"
|
||||
|
||||
[transports.ipfs]
|
||||
enabled = true
|
||||
mode = "adopt-or-prompt-manage"
|
||||
|
||||
[transports.reticulum]
|
||||
enabled = false
|
||||
|
||||
|
||||
+12
-2
@@ -8,7 +8,7 @@ from ampbrowser.transports import inspect_transport
|
||||
|
||||
class AdaptersTest(unittest.TestCase):
|
||||
def test_known_adapters_are_registered(self) -> None:
|
||||
self.assertEqual({"tor", "i2p", "reticulum", "gemini"}, set(ADAPTERS))
|
||||
self.assertEqual({"tor", "i2p", "ipfs", "reticulum", "gemini"}, set(ADAPTERS))
|
||||
|
||||
def test_gemini_adapter_is_builtin_ready(self) -> None:
|
||||
status = inspect_transport("gemini")
|
||||
@@ -23,7 +23,17 @@ class AdaptersTest(unittest.TestCase):
|
||||
|
||||
self.assertIsNotNone(adapter)
|
||||
self.assertEqual("stop only AMPB-owned Tor daemon", adapter.stop_policy)
|
||||
self.assertEqual(("install Tor provider", "start managed Tor daemon", "wait for socks5://127.0.0.1:9050"), adapter.setup_steps(installed=False, platform="desktop"))
|
||||
self.assertEqual(
|
||||
("install Tor provider", "start managed Tor daemon", "wait for socks5://127.0.0.1:9050"),
|
||||
adapter.setup_steps(installed=False, platform="desktop"),
|
||||
)
|
||||
|
||||
def test_ipfs_adapter_declares_gateway_not_anonymity(self) -> None:
|
||||
adapter = adapter_for("ipfs")
|
||||
|
||||
self.assertIsNotNone(adapter)
|
||||
self.assertEqual("http://127.0.0.1:8080", adapter.endpoint)
|
||||
self.assertIn("not an anonymity layer", adapter.note)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -11,7 +11,7 @@ class CandidateTransportsTest(unittest.TestCase):
|
||||
|
||||
self.assertIn("ipfs", by_name)
|
||||
self.assertIn("yggdrasil", by_name)
|
||||
self.assertEqual("next-evaluate", by_name["ipfs"].status)
|
||||
self.assertEqual("active-dry-run", by_name["ipfs"].status)
|
||||
self.assertEqual("next-evaluate", by_name["yggdrasil"].status)
|
||||
|
||||
def test_nostr_is_not_classified_as_page_transport(self) -> None:
|
||||
|
||||
@@ -90,6 +90,26 @@ class PrepareOpenTest(unittest.TestCase):
|
||||
plan.setup_steps,
|
||||
)
|
||||
|
||||
def test_ipfs_desktop_setup_uses_gateway_adapter(self) -> None:
|
||||
status = TransportStatus(
|
||||
transport="ipfs",
|
||||
installed=False,
|
||||
running=False,
|
||||
endpoint="http://127.0.0.1:8080",
|
||||
adoptable=False,
|
||||
manage_supported=True,
|
||||
note="IPFS local gateway",
|
||||
)
|
||||
|
||||
with patch("ampbrowser.plan.inspect_transport", return_value=status):
|
||||
plan = prepare_open("ipfs://bafyexample", consent=True)
|
||||
|
||||
self.assertEqual("setup-approved", plan.status)
|
||||
self.assertEqual(
|
||||
("install IPFS/Kubo provider", "start managed IPFS gateway", "wait for http://127.0.0.1:8080"),
|
||||
plan.setup_steps,
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
@@ -107,6 +107,24 @@ class PlanUrlTest(unittest.TestCase):
|
||||
self.assertEqual("planned", plan.platform_capability.manage)
|
||||
self.assertTrue(plan.requires_consent)
|
||||
|
||||
def test_ipfs_missing_gateway_prompts_before_setup(self) -> None:
|
||||
status = TransportStatus(
|
||||
transport="ipfs",
|
||||
installed=False,
|
||||
running=False,
|
||||
endpoint="http://127.0.0.1:8080",
|
||||
adoptable=False,
|
||||
manage_supported=True,
|
||||
note="IPFS local gateway",
|
||||
)
|
||||
|
||||
with patch("ampbrowser.plan.inspect_transport", return_value=status):
|
||||
plan = plan_url("ipfs://bafyexample")
|
||||
|
||||
self.assertEqual("ipfs", plan.route.transport)
|
||||
self.assertEqual("prompt to install and start managed transport", plan.action)
|
||||
self.assertTrue(plan.requires_consent)
|
||||
|
||||
def test_ios_foreground_only_transport_prompt(self) -> None:
|
||||
status = TransportStatus(
|
||||
transport="tor",
|
||||
|
||||
@@ -20,6 +20,12 @@ class PlatformsTest(unittest.TestCase):
|
||||
self.assertTrue(capability.can_manage_setup)
|
||||
self.assertIn("foreground", capability.note)
|
||||
|
||||
def test_ipfs_android_is_gateway_first_planned(self) -> None:
|
||||
capability = capability_for("ipfs", "android")
|
||||
|
||||
self.assertEqual("planned", capability.manage)
|
||||
self.assertIn("gateway-first", capability.note)
|
||||
|
||||
def test_unknown_platform_is_rejected(self) -> None:
|
||||
with self.assertRaises(ValueError):
|
||||
normalize_platform("watch")
|
||||
|
||||
@@ -31,6 +31,24 @@ class RouteUrlTest(unittest.TestCase):
|
||||
self.assertEqual("gemini", route.transport)
|
||||
self.assertEqual("gemini", route.profile)
|
||||
|
||||
def test_routes_ipfs_scheme(self) -> None:
|
||||
route = route_url("ipfs://bafyexample")
|
||||
|
||||
self.assertEqual("ipfs", route.transport)
|
||||
self.assertEqual("ipfs", route.profile)
|
||||
|
||||
def test_routes_ipns_scheme(self) -> None:
|
||||
route = route_url("ipns://wownero.example")
|
||||
|
||||
self.assertEqual("ipfs", route.transport)
|
||||
self.assertEqual("ipfs", route.profile)
|
||||
|
||||
def test_routes_ipfs_gateway_path(self) -> None:
|
||||
route = route_url("/ipfs/bafyexample")
|
||||
|
||||
self.assertEqual("ipfs://bafyexample", route.normalized)
|
||||
self.assertEqual("ipfs", route.transport)
|
||||
|
||||
def test_routes_reticulum_family_scheme(self) -> None:
|
||||
route = route_url("rns://wownero")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user