605 lines
11 KiB
Markdown
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) |
|