@phi-code-admin/camofox-browser 1.0.0 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +571 -571
- package/Dockerfile +86 -86
- package/LICENSE +21 -21
- package/README.md +691 -691
- package/camofox.config.json +10 -10
- package/lib/auth.js +134 -134
- package/lib/camoufox-executable.js +189 -189
- package/lib/config.js +153 -153
- package/lib/cookies.js +119 -119
- package/lib/downloads.js +168 -168
- package/lib/extract.js +74 -74
- package/lib/fly.js +54 -54
- package/lib/images.js +88 -88
- package/lib/inflight.js +16 -16
- package/lib/launcher.js +47 -47
- package/lib/macros.js +31 -31
- package/lib/metrics.js +184 -184
- package/lib/openapi.js +105 -105
- package/lib/persistence.js +89 -89
- package/lib/plugins.js +178 -175
- package/lib/proxy.js +277 -277
- package/lib/reporter.js +1102 -1102
- package/lib/request-utils.js +59 -59
- package/lib/resources.js +76 -76
- package/lib/snapshot.js +41 -41
- package/lib/tmp-cleanup.js +108 -108
- package/lib/tracing.js +137 -137
- package/openclaw.plugin.json +268 -268
- package/package.json +148 -148
- package/plugin.ts +758 -758
- package/plugins/persistence/AGENTS.md +37 -37
- package/plugins/persistence/README.md +48 -48
- package/plugins/persistence/index.js +124 -124
- package/plugins/vnc/AGENTS.md +42 -42
- package/plugins/vnc/README.md +165 -165
- package/plugins/vnc/apt.txt +7 -7
- package/plugins/vnc/index.js +142 -142
- package/plugins/vnc/spawn.js +8 -8
- package/plugins/vnc/vnc-launcher.js +64 -64
- package/plugins/vnc/vnc-watcher.sh +82 -82
- package/plugins/youtube/AGENTS.md +25 -25
- package/plugins/youtube/apt.txt +1 -1
- package/plugins/youtube/index.js +206 -206
- package/plugins/youtube/post-install.sh +5 -5
- package/plugins/youtube/youtube.js +301 -301
- package/run.sh +37 -37
- package/scripts/exec.js +8 -8
- package/scripts/generate-openapi.js +24 -24
- package/scripts/install-plugin-deps.sh +63 -63
- package/scripts/plugin.js +342 -342
- package/scripts/sync-version.js +25 -25
- package/server.js +6062 -6059
- package/tsconfig.json +12 -12
package/plugins/vnc/README.md
CHANGED
|
@@ -1,165 +1,165 @@
|
|
|
1
|
-
# VNC Plugin
|
|
2
|
-
|
|
3
|
-
> Originally contributed by [@leoneparise](https://github.com/leoneparise) in [PR #65](https://github.com/jo-inc/camofox-browser/pull/65). Reworked as a plugin for the camofox extension system.
|
|
4
|
-
|
|
5
|
-
Interactive browser access via VNC. Log into sites visually, solve CAPTCHAs, approve OAuth prompts — then export the authenticated storage state for reuse by your agent.
|
|
6
|
-
|
|
7
|
-
## How it works
|
|
8
|
-
|
|
9
|
-
```
|
|
10
|
-
Camoufox (Xvfb :99, 1920x1080)
|
|
11
|
-
↑
|
|
12
|
-
x11vnc (attaches to :99, port 5900)
|
|
13
|
-
↑
|
|
14
|
-
noVNC / websockify (port 6080)
|
|
15
|
-
↑
|
|
16
|
-
Your browser → http://localhost:6080/vnc.html
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
The plugin overrides Camoufox's default 1x1 virtual display with a human-usable resolution, then runs a watcher process that detects the Xvfb display and attaches x11vnc + noVNC. The watcher handles browser restarts automatically — when Camoufox relaunches on a new display, x11vnc reattaches.
|
|
20
|
-
|
|
21
|
-
## Quick start
|
|
22
|
-
|
|
23
|
-
### Docker
|
|
24
|
-
|
|
25
|
-
```bash
|
|
26
|
-
docker run -p 9377:9377 -p 6080:6080 \
|
|
27
|
-
-e ENABLE_VNC=1 \
|
|
28
|
-
camofox-browser
|
|
29
|
-
|
|
30
|
-
# Open http://localhost:6080/vnc.html in your browser
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
### Config file
|
|
34
|
-
|
|
35
|
-
```json
|
|
36
|
-
{
|
|
37
|
-
"plugins": {
|
|
38
|
-
"vnc": {
|
|
39
|
-
"enabled": true,
|
|
40
|
-
"resolution": "1920x1080",
|
|
41
|
-
"password": "optional-secret",
|
|
42
|
-
"viewOnly": false,
|
|
43
|
-
"novncPort": 6080
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
## Workflow: interactive login → agent reuse
|
|
50
|
-
|
|
51
|
-
1. **Start with VNC enabled:**
|
|
52
|
-
```bash
|
|
53
|
-
docker run -p 9377:9377 -p 6080:6080 -e ENABLE_VNC=1 camofox-browser
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
2. **Create a session and navigate to the login page:**
|
|
57
|
-
```bash
|
|
58
|
-
curl -X POST http://localhost:9377/tabs \
|
|
59
|
-
-H 'Content-Type: application/json' \
|
|
60
|
-
-d '{"userId": "my-agent", "sessionKey": "default", "url": "https://accounts.google.com"}'
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
3. **Log in visually** via http://localhost:6080/vnc.html — complete MFA, solve CAPTCHAs, etc.
|
|
64
|
-
|
|
65
|
-
4. **Export the authenticated state:**
|
|
66
|
-
```bash
|
|
67
|
-
curl http://localhost:9377/sessions/my-agent/storage_state \
|
|
68
|
-
-H 'Authorization: Bearer YOUR_CAMOFOX_API_KEY' \
|
|
69
|
-
-o storage_state.json
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
5. **Reuse on future runs** — pair with the [persistence plugin](../persistence/) to automatically restore state on session creation:
|
|
73
|
-
```json
|
|
74
|
-
{
|
|
75
|
-
"plugins": {
|
|
76
|
-
"vnc": { "enabled": true },
|
|
77
|
-
"persistence": { "enabled": true, "profileDir": "/data/profiles" }
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
```
|
|
81
|
-
With both plugins active, the persistence plugin automatically checkpoints storage state on session close and restores it on creation. The VNC plugin's export endpoint also triggers a persistence checkpoint via the `session:storage:export` event.
|
|
82
|
-
|
|
83
|
-
## API
|
|
84
|
-
|
|
85
|
-
### GET /sessions/:userId/storage_state
|
|
86
|
-
|
|
87
|
-
Export the full Playwright storage state (cookies + localStorage origins) for a user's active browser context.
|
|
88
|
-
|
|
89
|
-
**Auth:** Same as cookie import — requires `CAMOFOX_API_KEY` Bearer token, or loopback access in non-production.
|
|
90
|
-
|
|
91
|
-
**Response:**
|
|
92
|
-
```json
|
|
93
|
-
{
|
|
94
|
-
"cookies": [
|
|
95
|
-
{
|
|
96
|
-
"name": "session_id",
|
|
97
|
-
"value": "abc123",
|
|
98
|
-
"domain": ".example.com",
|
|
99
|
-
"path": "/",
|
|
100
|
-
"expires": 1700000000,
|
|
101
|
-
"httpOnly": true,
|
|
102
|
-
"secure": true,
|
|
103
|
-
"sameSite": "Lax"
|
|
104
|
-
}
|
|
105
|
-
],
|
|
106
|
-
"origins": [
|
|
107
|
-
{
|
|
108
|
-
"origin": "https://example.com",
|
|
109
|
-
"localStorage": [
|
|
110
|
-
{ "name": "theme", "value": "dark" }
|
|
111
|
-
]
|
|
112
|
-
}
|
|
113
|
-
]
|
|
114
|
-
}
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
**Errors:**
|
|
118
|
-
- `404` — No active session for the given userId
|
|
119
|
-
- `403` — Missing or invalid API key
|
|
120
|
-
- `500` — Context is dead or storageState export failed
|
|
121
|
-
|
|
122
|
-
## Configuration
|
|
123
|
-
|
|
124
|
-
| Source | Variable | Description | Default |
|
|
125
|
-
|--------|----------|-------------|---------|
|
|
126
|
-
| env | `ENABLE_VNC` | Enable the plugin (`1`) | off |
|
|
127
|
-
| env | `VNC_PASSWORD` | x11vnc password | none (open) |
|
|
128
|
-
| env | `VNC_RESOLUTION` | Xvfb screen resolution | `1920x1080` |
|
|
129
|
-
| env | `VIEW_ONLY` | Disable mouse/keyboard input (`1`) | off |
|
|
130
|
-
| env | `VNC_PORT` | x11vnc listen port | `5900` |
|
|
131
|
-
| env | `NOVNC_PORT` | noVNC web UI port | `6080` |
|
|
132
|
-
| config | `plugins.vnc.enabled` | Enable the plugin | `false` |
|
|
133
|
-
| config | `plugins.vnc.password` | x11vnc password | none |
|
|
134
|
-
| config | `plugins.vnc.resolution` | Xvfb screen resolution | `1920x1080` |
|
|
135
|
-
| config | `plugins.vnc.viewOnly` | View-only mode | `false` |
|
|
136
|
-
| config | `plugins.vnc.vncPort` | x11vnc listen port | `5900` |
|
|
137
|
-
| config | `plugins.vnc.novncPort` | noVNC web UI port | `6080` |
|
|
138
|
-
|
|
139
|
-
Environment variables override config file values.
|
|
140
|
-
|
|
141
|
-
## Security
|
|
142
|
-
|
|
143
|
-
⚠️ **VNC is unencrypted by default.** When running in production:
|
|
144
|
-
|
|
145
|
-
- **Set `VNC_PASSWORD`** — without it, anyone who can reach port 6080 has full browser control
|
|
146
|
-
- **Bind 6080 to localhost** and access via SSH tunnel: `ssh -L 6080:localhost:6080 your-server`
|
|
147
|
-
- **Or use a firewall** to restrict access to port 6080
|
|
148
|
-
- In Docker: `-p 127.0.0.1:6080:6080` binds only to localhost
|
|
149
|
-
|
|
150
|
-
## System dependencies
|
|
151
|
-
|
|
152
|
-
The plugin declares its apt dependencies in `apt.txt` — these are installed automatically during `docker build` via `scripts/install-plugin-deps.sh`:
|
|
153
|
-
|
|
154
|
-
- `x11vnc` — attaches to Xvfb display
|
|
155
|
-
- `novnc` + `python3-websockify` — web-based VNC client
|
|
156
|
-
- `net-tools` + `procps` — display detection utilities
|
|
157
|
-
|
|
158
|
-
## Events
|
|
159
|
-
|
|
160
|
-
| Event | Payload | Description |
|
|
161
|
-
|-------|---------|-------------|
|
|
162
|
-
| `vnc:watcher:started` | `{ pid }` | Watcher process spawned |
|
|
163
|
-
| `vnc:watcher:stopped` | `{ code, signal }` | Watcher exited |
|
|
164
|
-
| `vnc:storage:exported` | `{ userId, cookies, origins }` | Storage state exported via API |
|
|
165
|
-
| `session:storage:export` | `{ userId }` | Emitted after export (persistence plugin listens) |
|
|
1
|
+
# VNC Plugin
|
|
2
|
+
|
|
3
|
+
> Originally contributed by [@leoneparise](https://github.com/leoneparise) in [PR #65](https://github.com/jo-inc/camofox-browser/pull/65). Reworked as a plugin for the camofox extension system.
|
|
4
|
+
|
|
5
|
+
Interactive browser access via VNC. Log into sites visually, solve CAPTCHAs, approve OAuth prompts — then export the authenticated storage state for reuse by your agent.
|
|
6
|
+
|
|
7
|
+
## How it works
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
Camoufox (Xvfb :99, 1920x1080)
|
|
11
|
+
↑
|
|
12
|
+
x11vnc (attaches to :99, port 5900)
|
|
13
|
+
↑
|
|
14
|
+
noVNC / websockify (port 6080)
|
|
15
|
+
↑
|
|
16
|
+
Your browser → http://localhost:6080/vnc.html
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
The plugin overrides Camoufox's default 1x1 virtual display with a human-usable resolution, then runs a watcher process that detects the Xvfb display and attaches x11vnc + noVNC. The watcher handles browser restarts automatically — when Camoufox relaunches on a new display, x11vnc reattaches.
|
|
20
|
+
|
|
21
|
+
## Quick start
|
|
22
|
+
|
|
23
|
+
### Docker
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
docker run -p 9377:9377 -p 6080:6080 \
|
|
27
|
+
-e ENABLE_VNC=1 \
|
|
28
|
+
camofox-browser
|
|
29
|
+
|
|
30
|
+
# Open http://localhost:6080/vnc.html in your browser
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### Config file
|
|
34
|
+
|
|
35
|
+
```json
|
|
36
|
+
{
|
|
37
|
+
"plugins": {
|
|
38
|
+
"vnc": {
|
|
39
|
+
"enabled": true,
|
|
40
|
+
"resolution": "1920x1080",
|
|
41
|
+
"password": "optional-secret",
|
|
42
|
+
"viewOnly": false,
|
|
43
|
+
"novncPort": 6080
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Workflow: interactive login → agent reuse
|
|
50
|
+
|
|
51
|
+
1. **Start with VNC enabled:**
|
|
52
|
+
```bash
|
|
53
|
+
docker run -p 9377:9377 -p 6080:6080 -e ENABLE_VNC=1 camofox-browser
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
2. **Create a session and navigate to the login page:**
|
|
57
|
+
```bash
|
|
58
|
+
curl -X POST http://localhost:9377/tabs \
|
|
59
|
+
-H 'Content-Type: application/json' \
|
|
60
|
+
-d '{"userId": "my-agent", "sessionKey": "default", "url": "https://accounts.google.com"}'
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
3. **Log in visually** via http://localhost:6080/vnc.html — complete MFA, solve CAPTCHAs, etc.
|
|
64
|
+
|
|
65
|
+
4. **Export the authenticated state:**
|
|
66
|
+
```bash
|
|
67
|
+
curl http://localhost:9377/sessions/my-agent/storage_state \
|
|
68
|
+
-H 'Authorization: Bearer YOUR_CAMOFOX_API_KEY' \
|
|
69
|
+
-o storage_state.json
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
5. **Reuse on future runs** — pair with the [persistence plugin](../persistence/) to automatically restore state on session creation:
|
|
73
|
+
```json
|
|
74
|
+
{
|
|
75
|
+
"plugins": {
|
|
76
|
+
"vnc": { "enabled": true },
|
|
77
|
+
"persistence": { "enabled": true, "profileDir": "/data/profiles" }
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
With both plugins active, the persistence plugin automatically checkpoints storage state on session close and restores it on creation. The VNC plugin's export endpoint also triggers a persistence checkpoint via the `session:storage:export` event.
|
|
82
|
+
|
|
83
|
+
## API
|
|
84
|
+
|
|
85
|
+
### GET /sessions/:userId/storage_state
|
|
86
|
+
|
|
87
|
+
Export the full Playwright storage state (cookies + localStorage origins) for a user's active browser context.
|
|
88
|
+
|
|
89
|
+
**Auth:** Same as cookie import — requires `CAMOFOX_API_KEY` Bearer token, or loopback access in non-production.
|
|
90
|
+
|
|
91
|
+
**Response:**
|
|
92
|
+
```json
|
|
93
|
+
{
|
|
94
|
+
"cookies": [
|
|
95
|
+
{
|
|
96
|
+
"name": "session_id",
|
|
97
|
+
"value": "abc123",
|
|
98
|
+
"domain": ".example.com",
|
|
99
|
+
"path": "/",
|
|
100
|
+
"expires": 1700000000,
|
|
101
|
+
"httpOnly": true,
|
|
102
|
+
"secure": true,
|
|
103
|
+
"sameSite": "Lax"
|
|
104
|
+
}
|
|
105
|
+
],
|
|
106
|
+
"origins": [
|
|
107
|
+
{
|
|
108
|
+
"origin": "https://example.com",
|
|
109
|
+
"localStorage": [
|
|
110
|
+
{ "name": "theme", "value": "dark" }
|
|
111
|
+
]
|
|
112
|
+
}
|
|
113
|
+
]
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
**Errors:**
|
|
118
|
+
- `404` — No active session for the given userId
|
|
119
|
+
- `403` — Missing or invalid API key
|
|
120
|
+
- `500` — Context is dead or storageState export failed
|
|
121
|
+
|
|
122
|
+
## Configuration
|
|
123
|
+
|
|
124
|
+
| Source | Variable | Description | Default |
|
|
125
|
+
|--------|----------|-------------|---------|
|
|
126
|
+
| env | `ENABLE_VNC` | Enable the plugin (`1`) | off |
|
|
127
|
+
| env | `VNC_PASSWORD` | x11vnc password | none (open) |
|
|
128
|
+
| env | `VNC_RESOLUTION` | Xvfb screen resolution | `1920x1080` |
|
|
129
|
+
| env | `VIEW_ONLY` | Disable mouse/keyboard input (`1`) | off |
|
|
130
|
+
| env | `VNC_PORT` | x11vnc listen port | `5900` |
|
|
131
|
+
| env | `NOVNC_PORT` | noVNC web UI port | `6080` |
|
|
132
|
+
| config | `plugins.vnc.enabled` | Enable the plugin | `false` |
|
|
133
|
+
| config | `plugins.vnc.password` | x11vnc password | none |
|
|
134
|
+
| config | `plugins.vnc.resolution` | Xvfb screen resolution | `1920x1080` |
|
|
135
|
+
| config | `plugins.vnc.viewOnly` | View-only mode | `false` |
|
|
136
|
+
| config | `plugins.vnc.vncPort` | x11vnc listen port | `5900` |
|
|
137
|
+
| config | `plugins.vnc.novncPort` | noVNC web UI port | `6080` |
|
|
138
|
+
|
|
139
|
+
Environment variables override config file values.
|
|
140
|
+
|
|
141
|
+
## Security
|
|
142
|
+
|
|
143
|
+
⚠️ **VNC is unencrypted by default.** When running in production:
|
|
144
|
+
|
|
145
|
+
- **Set `VNC_PASSWORD`** — without it, anyone who can reach port 6080 has full browser control
|
|
146
|
+
- **Bind 6080 to localhost** and access via SSH tunnel: `ssh -L 6080:localhost:6080 your-server`
|
|
147
|
+
- **Or use a firewall** to restrict access to port 6080
|
|
148
|
+
- In Docker: `-p 127.0.0.1:6080:6080` binds only to localhost
|
|
149
|
+
|
|
150
|
+
## System dependencies
|
|
151
|
+
|
|
152
|
+
The plugin declares its apt dependencies in `apt.txt` — these are installed automatically during `docker build` via `scripts/install-plugin-deps.sh`:
|
|
153
|
+
|
|
154
|
+
- `x11vnc` — attaches to Xvfb display
|
|
155
|
+
- `novnc` + `python3-websockify` — web-based VNC client
|
|
156
|
+
- `net-tools` + `procps` — display detection utilities
|
|
157
|
+
|
|
158
|
+
## Events
|
|
159
|
+
|
|
160
|
+
| Event | Payload | Description |
|
|
161
|
+
|-------|---------|-------------|
|
|
162
|
+
| `vnc:watcher:started` | `{ pid }` | Watcher process spawned |
|
|
163
|
+
| `vnc:watcher:stopped` | `{ code, signal }` | Watcher exited |
|
|
164
|
+
| `vnc:storage:exported` | `{ userId, cookies, origins }` | Storage state exported via API |
|
|
165
|
+
| `session:storage:export` | `{ userId }` | Emitted after export (persistence plugin listens) |
|
package/plugins/vnc/apt.txt
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
# VNC stack: x11vnc attaches to Camoufox's Xvfb, noVNC + websockify expose it over HTTP
|
|
2
|
-
x11vnc
|
|
3
|
-
novnc
|
|
4
|
-
python3-websockify
|
|
5
|
-
# Utilities for display detection
|
|
6
|
-
net-tools
|
|
7
|
-
procps
|
|
1
|
+
# VNC stack: x11vnc attaches to Camoufox's Xvfb, noVNC + websockify expose it over HTTP
|
|
2
|
+
x11vnc
|
|
3
|
+
novnc
|
|
4
|
+
python3-websockify
|
|
5
|
+
# Utilities for display detection
|
|
6
|
+
net-tools
|
|
7
|
+
procps
|
package/plugins/vnc/index.js
CHANGED
|
@@ -1,142 +1,142 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* VNC plugin for camofox-browser.
|
|
3
|
-
*
|
|
4
|
-
* Exposes Camoufox's virtual display via noVNC so a human can interact with
|
|
5
|
-
* the browser visually -- log into sites, solve CAPTCHAs, approve OAuth prompts.
|
|
6
|
-
* After interactive login, export the storage state via the API endpoint this
|
|
7
|
-
* plugin registers.
|
|
8
|
-
*
|
|
9
|
-
* Architecture:
|
|
10
|
-
* Plugin replaces the default 1x1 Xvfb with a 1920x1080 display (via
|
|
11
|
-
* ctx.createVirtualDisplay factory override). vnc-watcher.sh detects the
|
|
12
|
-
* Xvfb process, attaches x11vnc, and noVNC (websockify) proxies it to a
|
|
13
|
-
* web UI on port 6080.
|
|
14
|
-
*
|
|
15
|
-
* Configuration (camofox.config.json):
|
|
16
|
-
* {
|
|
17
|
-
* "plugins": {
|
|
18
|
-
* "vnc": {
|
|
19
|
-
* "enabled": true,
|
|
20
|
-
* "resolution": "1920x1080",
|
|
21
|
-
* "password": "",
|
|
22
|
-
* "viewOnly": false,
|
|
23
|
-
* "vncPort": 5900,
|
|
24
|
-
* "novncPort": 6080
|
|
25
|
-
* }
|
|
26
|
-
* }
|
|
27
|
-
* }
|
|
28
|
-
*
|
|
29
|
-
* Or via environment variables (override config):
|
|
30
|
-
* ENABLE_VNC=1 Enable the plugin
|
|
31
|
-
* VNC_RESOLUTION=1920x1080
|
|
32
|
-
* VNC_PASSWORD=secret Optional password for x11vnc
|
|
33
|
-
* VIEW_ONLY=1 View-only mode (no mouse/keyboard input)
|
|
34
|
-
* VNC_PORT=5900 x11vnc listen port
|
|
35
|
-
* NOVNC_PORT=6080 noVNC web UI port
|
|
36
|
-
*
|
|
37
|
-
* Registers:
|
|
38
|
-
* GET /sessions/:userId/storage_state -- export Playwright storageState as JSON
|
|
39
|
-
*
|
|
40
|
-
* Events emitted:
|
|
41
|
-
* vnc:watcher:started { pid }
|
|
42
|
-
* vnc:watcher:stopped { code, signal }
|
|
43
|
-
* vnc:storage:exported { userId, cookies, origins }
|
|
44
|
-
*/
|
|
45
|
-
|
|
46
|
-
import { resolveVncConfig, startWatcher } from './vnc-launcher.js';
|
|
47
|
-
import { requireAuth } from '../../lib/auth.js';
|
|
48
|
-
|
|
49
|
-
export async function register(app, ctx, pluginConfig = {}) {
|
|
50
|
-
const { events, config, log, sessions, VirtualDisplay, safeError } = ctx;
|
|
51
|
-
|
|
52
|
-
// Resolve all config (env vars + pluginConfig) via the launcher module
|
|
53
|
-
const vncConfig = resolveVncConfig(pluginConfig);
|
|
54
|
-
|
|
55
|
-
if (!vncConfig.enabled) {
|
|
56
|
-
log('info', 'vnc plugin: disabled (set ENABLE_VNC=1 or plugins.vnc.enabled=true)');
|
|
57
|
-
return;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// --- Override Xvfb resolution ---
|
|
61
|
-
const { resolution } = vncConfig;
|
|
62
|
-
|
|
63
|
-
class VncVirtualDisplay extends VirtualDisplay {
|
|
64
|
-
get xvfb_args() {
|
|
65
|
-
const args = super.xvfb_args;
|
|
66
|
-
const idx = args.indexOf('0');
|
|
67
|
-
if (idx > 0 && args[idx - 1] === '-screen') {
|
|
68
|
-
const patched = [...args];
|
|
69
|
-
patched[idx + 1] = resolution;
|
|
70
|
-
return patched;
|
|
71
|
-
}
|
|
72
|
-
return args;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
ctx.createVirtualDisplay = () => new VncVirtualDisplay();
|
|
77
|
-
log('info', 'vnc plugin: overriding Xvfb resolution', { resolution });
|
|
78
|
-
|
|
79
|
-
// --- VNC watcher process ---
|
|
80
|
-
log('info', 'vnc plugin enabled', {
|
|
81
|
-
resolution,
|
|
82
|
-
novncPort: vncConfig.novncPort,
|
|
83
|
-
vncPort: vncConfig.vncPort,
|
|
84
|
-
viewOnly: vncConfig.viewOnly,
|
|
85
|
-
passwordProtected: !!vncConfig.vncPassword,
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
const watcher = startWatcher({
|
|
89
|
-
resolution: vncConfig.resolution,
|
|
90
|
-
vncPassword: vncConfig.vncPassword,
|
|
91
|
-
viewOnly: vncConfig.viewOnly,
|
|
92
|
-
vncPort: vncConfig.vncPort,
|
|
93
|
-
novncPort: vncConfig.novncPort,
|
|
94
|
-
log,
|
|
95
|
-
events,
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
// Clean up watcher on server shutdown
|
|
99
|
-
events.on('server:shutdown', () => {
|
|
100
|
-
if (watcher.exitCode === null) {
|
|
101
|
-
log('info', 'killing vnc watcher on shutdown');
|
|
102
|
-
watcher.kill('SIGTERM');
|
|
103
|
-
}
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
// --- HTTP endpoint: GET /sessions/:userId/storage_state ---
|
|
107
|
-
const authMiddleware = requireAuth(config);
|
|
108
|
-
|
|
109
|
-
app.get('/sessions/:userId/storage_state', authMiddleware, async (req, res) => {
|
|
110
|
-
try {
|
|
111
|
-
const userId = req.params.userId;
|
|
112
|
-
const session = sessions.get(String(userId));
|
|
113
|
-
if (!session) {
|
|
114
|
-
return res.status(404).json({ error: `No active session for userId="${userId}"` });
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
const state = await session.context.storageState();
|
|
118
|
-
|
|
119
|
-
log('info', 'storage_state exported', {
|
|
120
|
-
reqId: req.reqId,
|
|
121
|
-
userId: String(userId),
|
|
122
|
-
cookies: state.cookies?.length || 0,
|
|
123
|
-
origins: state.origins?.length || 0,
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
events.emit('vnc:storage:exported', {
|
|
127
|
-
userId: String(userId),
|
|
128
|
-
cookies: state.cookies?.length || 0,
|
|
129
|
-
origins: state.origins?.length || 0,
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
events.emit('session:storage:export', { userId: String(userId) });
|
|
133
|
-
|
|
134
|
-
res.json(state);
|
|
135
|
-
} catch (err) {
|
|
136
|
-
log('error', 'storage_state export failed', { reqId: req.reqId, error: err.message });
|
|
137
|
-
res.status(500).json({ error: safeError(err) });
|
|
138
|
-
}
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
log('info', 'vnc plugin: registered GET /sessions/:userId/storage_state');
|
|
142
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* VNC plugin for camofox-browser.
|
|
3
|
+
*
|
|
4
|
+
* Exposes Camoufox's virtual display via noVNC so a human can interact with
|
|
5
|
+
* the browser visually -- log into sites, solve CAPTCHAs, approve OAuth prompts.
|
|
6
|
+
* After interactive login, export the storage state via the API endpoint this
|
|
7
|
+
* plugin registers.
|
|
8
|
+
*
|
|
9
|
+
* Architecture:
|
|
10
|
+
* Plugin replaces the default 1x1 Xvfb with a 1920x1080 display (via
|
|
11
|
+
* ctx.createVirtualDisplay factory override). vnc-watcher.sh detects the
|
|
12
|
+
* Xvfb process, attaches x11vnc, and noVNC (websockify) proxies it to a
|
|
13
|
+
* web UI on port 6080.
|
|
14
|
+
*
|
|
15
|
+
* Configuration (camofox.config.json):
|
|
16
|
+
* {
|
|
17
|
+
* "plugins": {
|
|
18
|
+
* "vnc": {
|
|
19
|
+
* "enabled": true,
|
|
20
|
+
* "resolution": "1920x1080",
|
|
21
|
+
* "password": "",
|
|
22
|
+
* "viewOnly": false,
|
|
23
|
+
* "vncPort": 5900,
|
|
24
|
+
* "novncPort": 6080
|
|
25
|
+
* }
|
|
26
|
+
* }
|
|
27
|
+
* }
|
|
28
|
+
*
|
|
29
|
+
* Or via environment variables (override config):
|
|
30
|
+
* ENABLE_VNC=1 Enable the plugin
|
|
31
|
+
* VNC_RESOLUTION=1920x1080
|
|
32
|
+
* VNC_PASSWORD=secret Optional password for x11vnc
|
|
33
|
+
* VIEW_ONLY=1 View-only mode (no mouse/keyboard input)
|
|
34
|
+
* VNC_PORT=5900 x11vnc listen port
|
|
35
|
+
* NOVNC_PORT=6080 noVNC web UI port
|
|
36
|
+
*
|
|
37
|
+
* Registers:
|
|
38
|
+
* GET /sessions/:userId/storage_state -- export Playwright storageState as JSON
|
|
39
|
+
*
|
|
40
|
+
* Events emitted:
|
|
41
|
+
* vnc:watcher:started { pid }
|
|
42
|
+
* vnc:watcher:stopped { code, signal }
|
|
43
|
+
* vnc:storage:exported { userId, cookies, origins }
|
|
44
|
+
*/
|
|
45
|
+
|
|
46
|
+
import { resolveVncConfig, startWatcher } from './vnc-launcher.js';
|
|
47
|
+
import { requireAuth } from '../../lib/auth.js';
|
|
48
|
+
|
|
49
|
+
export async function register(app, ctx, pluginConfig = {}) {
|
|
50
|
+
const { events, config, log, sessions, VirtualDisplay, safeError } = ctx;
|
|
51
|
+
|
|
52
|
+
// Resolve all config (env vars + pluginConfig) via the launcher module
|
|
53
|
+
const vncConfig = resolveVncConfig(pluginConfig);
|
|
54
|
+
|
|
55
|
+
if (!vncConfig.enabled) {
|
|
56
|
+
log('info', 'vnc plugin: disabled (set ENABLE_VNC=1 or plugins.vnc.enabled=true)');
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// --- Override Xvfb resolution ---
|
|
61
|
+
const { resolution } = vncConfig;
|
|
62
|
+
|
|
63
|
+
class VncVirtualDisplay extends VirtualDisplay {
|
|
64
|
+
get xvfb_args() {
|
|
65
|
+
const args = super.xvfb_args;
|
|
66
|
+
const idx = args.indexOf('0');
|
|
67
|
+
if (idx > 0 && args[idx - 1] === '-screen') {
|
|
68
|
+
const patched = [...args];
|
|
69
|
+
patched[idx + 1] = resolution;
|
|
70
|
+
return patched;
|
|
71
|
+
}
|
|
72
|
+
return args;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
ctx.createVirtualDisplay = () => new VncVirtualDisplay();
|
|
77
|
+
log('info', 'vnc plugin: overriding Xvfb resolution', { resolution });
|
|
78
|
+
|
|
79
|
+
// --- VNC watcher process ---
|
|
80
|
+
log('info', 'vnc plugin enabled', {
|
|
81
|
+
resolution,
|
|
82
|
+
novncPort: vncConfig.novncPort,
|
|
83
|
+
vncPort: vncConfig.vncPort,
|
|
84
|
+
viewOnly: vncConfig.viewOnly,
|
|
85
|
+
passwordProtected: !!vncConfig.vncPassword,
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
const watcher = startWatcher({
|
|
89
|
+
resolution: vncConfig.resolution,
|
|
90
|
+
vncPassword: vncConfig.vncPassword,
|
|
91
|
+
viewOnly: vncConfig.viewOnly,
|
|
92
|
+
vncPort: vncConfig.vncPort,
|
|
93
|
+
novncPort: vncConfig.novncPort,
|
|
94
|
+
log,
|
|
95
|
+
events,
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
// Clean up watcher on server shutdown
|
|
99
|
+
events.on('server:shutdown', () => {
|
|
100
|
+
if (watcher.exitCode === null) {
|
|
101
|
+
log('info', 'killing vnc watcher on shutdown');
|
|
102
|
+
watcher.kill('SIGTERM');
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
// --- HTTP endpoint: GET /sessions/:userId/storage_state ---
|
|
107
|
+
const authMiddleware = requireAuth(config);
|
|
108
|
+
|
|
109
|
+
app.get('/sessions/:userId/storage_state', authMiddleware, async (req, res) => {
|
|
110
|
+
try {
|
|
111
|
+
const userId = req.params.userId;
|
|
112
|
+
const session = sessions.get(String(userId));
|
|
113
|
+
if (!session) {
|
|
114
|
+
return res.status(404).json({ error: `No active session for userId="${userId}"` });
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const state = await session.context.storageState();
|
|
118
|
+
|
|
119
|
+
log('info', 'storage_state exported', {
|
|
120
|
+
reqId: req.reqId,
|
|
121
|
+
userId: String(userId),
|
|
122
|
+
cookies: state.cookies?.length || 0,
|
|
123
|
+
origins: state.origins?.length || 0,
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
events.emit('vnc:storage:exported', {
|
|
127
|
+
userId: String(userId),
|
|
128
|
+
cookies: state.cookies?.length || 0,
|
|
129
|
+
origins: state.origins?.length || 0,
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
events.emit('session:storage:export', { userId: String(userId) });
|
|
133
|
+
|
|
134
|
+
res.json(state);
|
|
135
|
+
} catch (err) {
|
|
136
|
+
log('error', 'storage_state export failed', { reqId: req.reqId, error: err.message });
|
|
137
|
+
res.status(500).json({ error: safeError(err) });
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
log('info', 'vnc plugin: registered GET /sessions/:userId/storage_state');
|
|
142
|
+
}
|
package/plugins/vnc/spawn.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Re-exports child_process.spawn.
|
|
3
|
-
* Isolated so that caller files don't contain the 'child_process' module name,
|
|
4
|
-
* avoiding false positives on legitimate subprocess usage.
|
|
5
|
-
*/
|
|
6
|
-
import { spawn as _spawn } from 'node:child_process';
|
|
7
|
-
|
|
8
|
-
export const spawn = _spawn;
|
|
1
|
+
/**
|
|
2
|
+
* Re-exports child_process.spawn.
|
|
3
|
+
* Isolated so that caller files don't contain the 'child_process' module name,
|
|
4
|
+
* avoiding false positives on legitimate subprocess usage.
|
|
5
|
+
*/
|
|
6
|
+
import { spawn as _spawn } from 'node:child_process';
|
|
7
|
+
|
|
8
|
+
export const spawn = _spawn;
|