Files
holesail-browser/docs/NATIVE-HOST.md
T
Raven Scott f5c349a4a3
CI / Build & Test (push) Successful in 2m56s
docs
2026-02-28 22:29:56 -05:00

605 lines
11 KiB
Markdown

# Native Host Protocol
The native host communicates with the browser extension via Chrome's native messaging protocol: each message is a UTF-8 JSON object prefixed with a 4-byte little-endian length. Maximum message size is 1 MB.
The extension sends **requests**; the native host sends **responses** and **events**.
## Message format
### Request (extension → native host)
```json
{ "id": 42, "type": "<command>", "payload": { ... } }
```
### Response (native host → extension)
```json
{ "id": 42, "type": "response", "payload": { ... } }
```
Responses always echo the `id` from the request. If an error occurred, the payload contains an `error` field:
```json
{ "id": 42, "type": "response", "payload": { "error": "Tunnel failed to connect" } }
```
### Event (native host → extension, unsolicited)
```json
{ "type": "event", "event": "<eventName>", "payload": { ... } }
```
---
## Commands
### `getState`
Returns the full current state of the native host. The extension calls this on startup and after reconnecting.
**Response payload:**
```json
{
"ok": true,
"servers": [ { "id": "server_1", "port": 3000, "hsUrl": "hs://...", "state": "ready" } ],
"virtualHosts": [ { "hostname": "myapp.hole.sail", "hsUrl": "hs://...", "state": "ready", "localPort": 19000 } ],
"serviceTunnels": [ { "id": "svc-1", "label": "Postgres", "hsUrl": "hs://...", "localPort": 5432, "state": "ready" } ],
"sshConnections": [ { "id": "ssh-abc", "label": "My Server", "hsUrl": "hs://...", "username": "root" } ],
"rdpConnections": [ { "id": "rdp-abc", "label": "Work PC", "hsUrl": "hs://...", "type": "vnc", "port": 5900 } ],
"settings": { "proxyPort": 8443, "connectProxyPort": 8442, "readyTimeoutMs": 0, "notifyOnDisconnect": true, "debug": false, "disableOnFileUrls": false, "backupRetention": 5 },
"proxyPort": 8443,
"connectProxyPort": 8442,
"caInstalled": true
}
```
---
### `setVirtualHost`
Add or update a virtual host. If the hostname already exists, the old tunnel is closed and a new one is started.
Hostnames can use any private TLD — not just `.hole.sail`. The hostname must have at least 3 labels and must not use a real public TLD. Any depth is supported (e.g. `i.love.hole.sail`, `api.v2.my.internal`).
After a successful response, the background service worker automatically updates the PAC script to include the new TLD and requests host permissions for it.
**Request payload:**
```json
{ "hostname": "myapp.hole.sail", "hsUrl": "hs://abc123..." }
```
Custom TLD examples:
```json
{ "hostname": "api.haha.wooo", "hsUrl": "hs://abc123..." }
{ "hostname": "i.love.hole.sail", "hsUrl": "hs://abc123..." }
```
**Response payload:**
```json
{ "ok": true, "hostname": "myapp.hole.sail", "localHost": "127.0.0.1", "localPort": 19000, "state": "ready" }
```
---
### `removeVirtualHost`
Remove a virtual host and close its tunnel.
**Request payload:**
```json
{ "hostname": "myapp.hole.sail" }
```
**Response payload:**
```json
{ "ok": true }
```
---
### `startServer`
Start a server tunnel (expose a local port as an `hs://` key).
**Request payload:**
```json
{
"port": 3000,
"host": "127.0.0.1",
"secure": true,
"udp": false,
"label": "My Web App"
}
```
`label` is optional. When provided it is shown in the dashboard and persisted in `state.json`.
**Response payload:**
```json
{ "ok": true, "serverId": "server_1", "url": "hs://abc123...", "port": 3000, "host": "127.0.0.1", "secure": true, "udp": false, "label": "My Web App" }
```
---
### `stopServer`
Stop a server tunnel.
**Request payload:**
```json
{ "serverId": "server_1" }
```
**Response payload:**
```json
{ "ok": true }
```
---
### `startServiceTunnel`
Start a service tunnel (forward a remote `hs://` peer to a local TCP port).
**Request payload:**
```json
{
"tunnelId": "svc-1",
"label": "Postgres",
"hsUrl": "hs://abc123...",
"localPort": 5432
}
```
**Response payload:**
```json
{ "ok": true, "tunnelId": "svc-1", "localPort": 5432, "state": "ready" }
```
---
### `stopServiceTunnel`
Stop a service tunnel.
**Request payload:**
```json
{ "tunnelId": "svc-1" }
```
**Response payload:**
```json
{ "ok": true }
```
---
### `updateServiceTunnel`
Stop and restart a service tunnel with the same (or updated) parameters. Used by the dashboard **Reconnect** button for tunnels in error or closed state.
**Request payload:**
```json
{
"tunnelId": "svc-1",
"label": "Postgres",
"hsUrl": "hs://abc123...",
"localPort": 5432
}
```
**Response payload:** Same as `startServiceTunnel`.
```json
{ "ok": true, "tunnelId": "svc-1", "localPort": 5432, "state": "ready" }
```
---
### `lookupTunnel`
Check whether an `hs://` key is reachable on the DHT.
**Request payload:**
```json
{ "hsUrl": "hs://abc123..." }
```
**Response payload:**
```json
{ "ok": true, "reachable": true }
```
---
### `startSshSession`
Start an SSH session.
**Request payload:**
```json
{
"connectionId": "ssh-abc123",
"hsUrl": "hs://abc123...",
"username": "root",
"passwordB64": "cGFzc3dvcmQ=",
"cols": 80,
"rows": 24,
"label": "My Server"
}
```
`passwordB64` is optional. When present it is a base64-encoded UTF-8 password (`btoa(unescape(encodeURIComponent(password)))`). It is delivered to SSH via `SSH_ASKPASS` and a named FIFO — never written to disk separately or passed on the command line. If omitted, SSH tries public key auth first; if that fails, a password prompt appears in the xterm.js terminal.
**Response payload:**
```json
{ "ok": true, "sessionId": "ssh-session-1", "wsPort": 21000, "tunnelPort": 20000 }
```
---
### `stopSshSession`
Stop an SSH session.
**Request payload:**
```json
{ "sessionId": "ssh-session-1" }
```
**Response payload:**
```json
{ "ok": true }
```
---
### `resizeSshSession`
Resize the SSH terminal.
**Request payload:**
```json
{ "sessionId": "ssh-session-1", "cols": 120, "rows": 40 }
```
**Response payload:**
```json
{ "ok": true }
```
---
### `getSshSessions`
List active SSH sessions.
**Response payload:**
```json
{
"ok": true,
"sessions": [ { "sessionId": "ssh-session-1", "connectionId": "ssh-abc123", "wsPort": 21000 } ]
}
```
---
### `setSshConnections`
Save SSH connection definitions.
**Request payload:**
```json
{
"connections": [
{
"id": "ssh-abc123",
"label": "My Server",
"hsUrl": "hs://abc...",
"username": "root",
"passwordB64": "cGFzc3dvcmQ="
}
]
}
```
`passwordB64` is optional. When present it is a base64-encoded UTF-8 password string (`btoa(unescape(encodeURIComponent(password)))`). It is stored in `state.json` and delivered to SSH via `SSH_ASKPASS` + a named FIFO — never via the command line or environment.
**Response payload:**
```json
{ "ok": true }
```
---
### `startRdpSession`
Start a VNC or RDP session.
**Request payload:**
```json
{
"connectionId": "rdp-abc123",
"hsUrl": "hs://abc123...",
"type": "vnc",
"port": 5900,
"width": 1280,
"height": 720,
"username": ""
}
```
**Response payload:**
```json
{ "ok": true, "sessionId": "rdp-session-1", "wsPort": 23000, "tunnelPort": 22000 }
```
---
### `stopRdpSession`
Stop a VNC or RDP session.
**Request payload:**
```json
{ "sessionId": "rdp-session-1" }
```
**Response payload:**
```json
{ "ok": true }
```
---
### `getRdpSessions`
List active VNC/RDP sessions.
**Response payload:**
```json
{
"ok": true,
"sessions": [ { "sessionId": "rdp-session-1", "connectionId": "rdp-abc123", "type": "vnc", "wsPort": 23000 } ]
}
```
---
### `setRdpConnections`
Save RDP/VNC connection definitions.
**Request payload:**
```json
{
"connections": [
{
"id": "rdp-abc123",
"label": "Work PC",
"hsUrl": "hs://abc...",
"type": "vnc",
"port": 5900,
"width": 1280,
"height": 720,
"username": "",
"passwordB64": "cGFzc3dvcmQ="
}
]
}
```
`passwordB64` is optional. When present it is a base64-encoded UTF-8 password string. It is stored in `state.json` and used automatically when starting a session.
**Response payload:**
```json
{ "ok": true }
```
---
### `installRootCA`
Install the root CA into the OS trust store.
**Request payload:** `{}`
**Response payload:**
```json
{ "ok": true }
```
On failure:
```json
{ "ok": false, "error": "Could not install CA: ..." }
```
---
### `updateSettings`
Update one or more settings.
**Request payload:**
```json
{
"proxyPort": 8443,
"connectProxyPort": 8442,
"readyTimeoutMs": 30000,
"notifyOnDisconnect": true,
"debug": false,
"disableOnFileUrls": false,
"backupRetention": 5
}
```
All fields are optional — only the provided fields are updated.
**Response payload:**
```json
{ "ok": true, "settings": { ... }, "requiresRestart": false }
```
`requiresRestart` is `true` if `proxyPort` or `connectProxyPort` were changed — the dashboard displays a warning in this case.
---
### `createBackup`
Create a backup archive.
**Response payload:**
```json
{ "ok": true, "filename": "holesail-browser-backup-2026-02-28T12-00-00.tar.gz" }
```
---
### `listBackups`
List available backups.
**Response payload:**
```json
{
"ok": true,
"backups": [
{ "filename": "holesail-browser-backup-2026-02-28T12-00-00.tar.gz", "size": 12345, "createdAt": "2026-02-28T12:00:00.000Z" }
]
}
```
---
### `restoreBackup`
Restore a backup.
**Request payload:**
```json
{ "filename": "holesail-browser-backup-2026-02-28T12-00-00.tar.gz" }
```
**Response payload:**
```json
{ "ok": true }
```
---
### `deleteBackup`
Delete a backup.
**Request payload:**
```json
{ "filename": "holesail-browser-backup-2026-02-28T12-00-00.tar.gz" }
```
**Response payload:**
```json
{ "ok": true }
```
---
### `getLogs`
Get recent log lines from the native host log file.
**Request payload:**
```json
{ "lines": 100 }
```
**Response payload:**
```json
{ "ok": true, "logs": "...\n[2026-02-28T12:00:00.000Z] ..." }
```
---
## Events
Events are sent by the native host without a corresponding request. The extension broadcasts them to subscribed tabs.
### `tunnelReady`
A tunnel connected successfully.
```json
{
"type": "event",
"event": "tunnelReady",
"payload": { "hostname": "myapp.hole.sail", "hsUrl": "hs://...", "localHost": "127.0.0.1", "localPort": 19000 }
}
```
### `tunnelClosed`
A tunnel disconnected.
```json
{
"type": "event",
"event": "tunnelClosed",
"payload": { "hostname": "myapp.hole.sail" }
}
```
### `tunnelError`
A tunnel failed to connect.
```json
{
"type": "event",
"event": "tunnelError",
"payload": { "hostname": "myapp.hole.sail", "error": "Tunnel ready timeout after 30000ms" }
}
```
### `connection`
A peer connected to a server tunnel.
```json
{
"type": "event",
"event": "connection",
"payload": { "connId": "conn-1", "swarmId": "server_1", "peerInfo": { "publicKey": "abc123..." } }
}
```
### `error`
A connection error occurred.
```json
{
"type": "event",
"event": "error",
"payload": { "connId": "conn-1", "message": "Connection reset" }
}
```
### `end`
A connection ended.
```json
{
"type": "event",
"event": "end",
"payload": { "connId": "conn-1" }
}
```
---
## Tunnel states
| State | Description |
|-------|-------------|
| `connecting` | Tunnel is being established |
| `ready` | Tunnel is connected and serving requests |
| `error` | Tunnel failed to connect (see `tunnelError` event) |
| `closed` | Tunnel was closed (removed or native host restarted) |