ccwebtty 1.0.3 → 1.0.4
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/README.md +102 -24
- package/dist/index.js +48 -13
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,60 +2,138 @@
|
|
|
2
2
|
|
|
3
3
|
A CLI tool that exposes an interactive web terminal in the browser, similar to [ttyd](https://github.com/tsl0922/ttyd).
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
一个在浏览器中提供交互式 Web 终端的命令行工具,类似于 [ttyd](https://github.com/tsl0922/ttyd)。
|
|
6
|
+
|
|
7
|
+
- **node-pty** — 在服务端创建伪终端 / Spawns pseudo-terminals on the server
|
|
8
|
+
- **xterm.js** — 在浏览器中渲染终端 / Renders the terminal in the browser
|
|
9
|
+
- **WebSocket** — 实时双向通信 / Real-time bidirectional communication
|
|
10
|
+
- 每个浏览器标签页拥有独立的 Shell 会话 / Each browser tab gets its own independent shell session
|
|
11
|
+
- 内置用户名/密码认证 / Built-in authentication (username/password)
|
|
12
|
+
- 可选 [Cloudflare Tunnel](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/) 支持,无需账号即可公网访问 / Optional Cloudflare Tunnel support for public access
|
|
13
|
+
- 自动防止系统休眠,保持后台长时间运行 / Automatic sleep prevention to keep the server running in the background
|
|
14
|
+
|
|
15
|
+
## 环境要求 / Prerequisites
|
|
6
16
|
|
|
7
17
|
- [Node.js](https://nodejs.org/) >= 18
|
|
8
18
|
|
|
9
|
-
## Install
|
|
19
|
+
## 安装 / Install
|
|
10
20
|
|
|
11
21
|
```bash
|
|
12
22
|
npm install -g ccwebtty
|
|
13
23
|
```
|
|
14
24
|
|
|
15
|
-
## Usage
|
|
25
|
+
## 使用 / Usage
|
|
16
26
|
|
|
17
27
|
```bash
|
|
18
|
-
#
|
|
28
|
+
# 使用默认设置启动(端口 1989,监听所有网络接口)
|
|
29
|
+
# Start with default settings (port 1989, all interfaces)
|
|
19
30
|
ccweb
|
|
20
31
|
|
|
32
|
+
# 自定义端口和主机地址
|
|
21
33
|
# Custom port and host
|
|
22
34
|
ccweb --port 3000 --host 127.0.0.1
|
|
23
35
|
|
|
36
|
+
# 指定不同的 Shell
|
|
24
37
|
# Specify a different shell
|
|
25
38
|
ccweb --shell /bin/bash
|
|
39
|
+
|
|
40
|
+
# 通过 Cloudflare Tunnel 暴露到公网(无需账号)
|
|
41
|
+
# Expose via Cloudflare Tunnel (publicly accessible, no account required)
|
|
42
|
+
ccweb --tunnel
|
|
43
|
+
|
|
44
|
+
# 使用自定义域名(需要 Cloudflare 账号,在 Zero Trust 后台获取 Token)
|
|
45
|
+
# Use a custom domain (requires Cloudflare account, get token from Zero Trust dashboard)
|
|
46
|
+
ccweb --tunnel-token eyJhIjoiNjM...
|
|
47
|
+
|
|
48
|
+
# 组合使用
|
|
49
|
+
# Combine options
|
|
50
|
+
ccweb --port 3000 --shell /bin/zsh --tunnel
|
|
26
51
|
```
|
|
27
52
|
|
|
28
|
-
|
|
53
|
+
然后在浏览器中打开 `http://localhost:<port>`(默认:`http://localhost:1989`)。
|
|
54
|
+
|
|
55
|
+
Then open `http://localhost:<port>` in your browser (default: `http://localhost:1989`).
|
|
29
56
|
|
|
30
|
-
## Options
|
|
57
|
+
## 参数 / Options
|
|
31
58
|
|
|
32
|
-
| Option | Default | Description |
|
|
59
|
+
| 参数 Option | 默认值 Default | 说明 Description |
|
|
33
60
|
|--------|---------|-------------|
|
|
34
|
-
| `-p, --port <number>` | `
|
|
35
|
-
| `-H, --host <address>` | `0.0.0.0` | Host to bind to |
|
|
36
|
-
| `-s, --shell <path>` | `$SHELL` or `/bin/bash` | Shell to spawn |
|
|
37
|
-
| `-u, --username <name>` | `cc` | Username for authentication |
|
|
38
|
-
| `--password <password>` | random | Password for authentication |
|
|
61
|
+
| `-p, --port <number>` | `1989` | 监听端口 / Port to listen on |
|
|
62
|
+
| `-H, --host <address>` | `0.0.0.0` | 绑定地址 / Host to bind to |
|
|
63
|
+
| `-s, --shell <path>` | `$SHELL` or `/bin/bash` | 要启动的 Shell / Shell to spawn |
|
|
64
|
+
| `-u, --username <name>` | `cc` | 认证用户名 / Username for authentication |
|
|
65
|
+
| `--password <password>` | 随机生成 random | 认证密码(未设置则自动生成)/ Password for authentication |
|
|
66
|
+
| `--tunnel` | 禁用 disabled | 通过 Cloudflare Tunnel 暴露到公网(无需账号)/ Expose via Cloudflare Tunnel |
|
|
67
|
+
| `--tunnel-token <token>` | — | 使用 Cloudflare Tunnel Token 绑定自定义域名 / Use a Cloudflare Tunnel token for custom domain |
|
|
68
|
+
|
|
69
|
+
## 命令 / Commands
|
|
39
70
|
|
|
40
|
-
|
|
71
|
+
| 命令 Command | 说明 Description |
|
|
72
|
+
|---------|-------------|
|
|
73
|
+
| `ccweb start` | 启动 Web 终端服务(默认命令)/ Start the web terminal server (default) |
|
|
74
|
+
| `ccweb update` | 更新 ccweb 到最新版本 / Update ccweb to the latest version |
|
|
75
|
+
|
|
76
|
+
## 自定义域名(Cloudflare Tunnel Token)/ Custom Domain
|
|
77
|
+
|
|
78
|
+
除了使用 `--tunnel` 获取随机域名外,你还可以通过 Cloudflare Tunnel Token 绑定自己的域名。
|
|
79
|
+
|
|
80
|
+
In addition to using `--tunnel` for a random domain, you can bind your own domain via a Cloudflare Tunnel Token.
|
|
81
|
+
|
|
82
|
+
### 获取 Token / How to get a Token
|
|
83
|
+
|
|
84
|
+
1. 登录 [Cloudflare Zero Trust](https://one.dash.cloudflare.com/) 后台 / Log in to the Cloudflare Zero Trust dashboard
|
|
85
|
+
2. 进入 **Networks → Tunnels**,点击 **Create a tunnel** / Go to **Networks → Tunnels**, click **Create a tunnel**
|
|
86
|
+
3. 选择 **Cloudflared** 类型,为隧道命名(如 `ccweb`)/ Select **Cloudflared**, name your tunnel (e.g. `ccweb`)
|
|
87
|
+
4. 在安装页面复制 Token(`eyJ` 开头的字符串)/ Copy the Token from the install page (starts with `eyJ`)
|
|
88
|
+
5. 配置 **Public Hostname** / Configure **Public Hostname**:
|
|
89
|
+
- **Subdomain**: 填入子域名,如 `terminal` / Enter a subdomain, e.g. `terminal`
|
|
90
|
+
- **Domain**: 选择你在 Cloudflare 的域名 / Select your domain in Cloudflare
|
|
91
|
+
- **Service Type**: `HTTP`
|
|
92
|
+
- **URL**: `localhost:1989`(与 ccweb 端口一致)/ (must match ccweb port)
|
|
93
|
+
|
|
94
|
+
### 使用 / Usage
|
|
41
95
|
|
|
42
96
|
```bash
|
|
43
|
-
|
|
97
|
+
ccweb --tunnel-token eyJhIjoiNjM...
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
配置完成后,即可通过自定义域名(如 `https://terminal.example.com`)访问终端。
|
|
101
|
+
|
|
102
|
+
Once configured, you can access the terminal via your custom domain (e.g. `https://terminal.example.com`).
|
|
103
|
+
|
|
104
|
+
### 对比 / Comparison
|
|
105
|
+
|
|
106
|
+
| | `--tunnel` | `--tunnel-token <token>` |
|
|
107
|
+
|---|---|---|
|
|
108
|
+
| 域名 Domain | 随机 `*.trycloudflare.com` / Random | 自定义域名 / Custom domain |
|
|
109
|
+
| 需要账号 Account | 不需要 / No | 需要 Cloudflare 账号 / Yes |
|
|
110
|
+
| 持久性 Persistence | 每次启动域名不同 / Changes on restart | 固定域名 / Persistent |
|
|
111
|
+
| 适用场景 Use case | 临时分享 / Temporary sharing | 长期使用 / Long-term use |
|
|
112
|
+
|
|
113
|
+
## 防止休眠 / Sleep Prevention
|
|
114
|
+
|
|
115
|
+
ccweb 启动后会自动阻止系统进入休眠状态,确保终端服务在后台持续运行。
|
|
116
|
+
|
|
117
|
+
When ccweb starts, it automatically prevents the system from sleeping, ensuring the terminal server keeps running in the background.
|
|
118
|
+
|
|
119
|
+
| 平台 Platform | 实现方式 Implementation | 条件 Requirement |
|
|
120
|
+
|---------|---------|---------|
|
|
121
|
+
| macOS | `caffeinate -s -w <pid>` | 系统自带,无需额外安装 / Built-in, no extra install needed |
|
|
122
|
+
| Windows | PowerShell `SetThreadExecutionState` | 系统自带,无需额外安装 / Built-in, no extra install needed |
|
|
123
|
+
| Linux | 不支持 / Not supported | — |
|
|
124
|
+
|
|
125
|
+
## 开发 / Development
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
# 安装依赖 / Install dependencies
|
|
44
129
|
npm install
|
|
45
130
|
|
|
46
|
-
# Build
|
|
131
|
+
# 构建 / Build
|
|
47
132
|
npm run build
|
|
48
133
|
|
|
49
|
-
# Run
|
|
134
|
+
# 运行 / Run
|
|
50
135
|
npm start
|
|
51
136
|
|
|
52
|
-
# Watch mode
|
|
137
|
+
# 监听模式 / Watch mode
|
|
53
138
|
npm run dev
|
|
54
139
|
```
|
|
55
|
-
|
|
56
|
-
## Architecture
|
|
57
|
-
|
|
58
|
-
- **node-pty** spawns pseudo-terminals on the server
|
|
59
|
-
- **xterm.js** renders the terminal in the browser
|
|
60
|
-
- **WebSocket** provides real-time bidirectional communication
|
|
61
|
-
- Each browser tab gets its own independent shell session
|
package/dist/index.js
CHANGED
|
@@ -201,7 +201,7 @@ function parseCookies(header) {
|
|
|
201
201
|
return cookies;
|
|
202
202
|
}
|
|
203
203
|
function startServer(options) {
|
|
204
|
-
const { port, host, shell, username, password, tunnel: enableTunnel } = options;
|
|
204
|
+
const { port, host, shell, username, password, tunnel: enableTunnel, tunnelToken } = options;
|
|
205
205
|
const sessionManager = new SessionManager();
|
|
206
206
|
const authSecret = generateSecret();
|
|
207
207
|
const app = (0, import_express.default)();
|
|
@@ -223,8 +223,20 @@ function startServer(options) {
|
|
|
223
223
|
if (!token) return false;
|
|
224
224
|
return verifyToken(token, authSecret);
|
|
225
225
|
}
|
|
226
|
+
const LOGIN_MAX_ATTEMPTS = 3;
|
|
227
|
+
const LOGIN_LOCKOUT_MS = 30 * 60 * 1e3;
|
|
228
|
+
const loginAttempts = /* @__PURE__ */ new Map();
|
|
229
|
+
function getClientIp(req) {
|
|
230
|
+
return req.ip || req.socket.remoteAddress || "unknown";
|
|
231
|
+
}
|
|
226
232
|
app.use(import_express.default.urlencoded({ extended: false }));
|
|
227
233
|
app.post("/login", (req, res) => {
|
|
234
|
+
const ip = getClientIp(req);
|
|
235
|
+
const record = loginAttempts.get(ip);
|
|
236
|
+
if (record && record.lockedUntil > Date.now()) {
|
|
237
|
+
const remaining = Math.ceil((record.lockedUntil - Date.now()) / 6e4);
|
|
238
|
+
return res.status(429).send(`Too many failed attempts. Try again in ${remaining} minute(s).`);
|
|
239
|
+
}
|
|
228
240
|
const { username: u, password: p } = req.body;
|
|
229
241
|
if (!u || !p) {
|
|
230
242
|
return res.status(400).send("Missing credentials");
|
|
@@ -232,8 +244,16 @@ function startServer(options) {
|
|
|
232
244
|
const uMatch = u.length === username.length && import_crypto2.default.timingSafeEqual(Buffer.from(u), Buffer.from(username));
|
|
233
245
|
const pMatch = p.length === password.length && import_crypto2.default.timingSafeEqual(Buffer.from(p), Buffer.from(password));
|
|
234
246
|
if (!uMatch || !pMatch) {
|
|
235
|
-
|
|
247
|
+
const attempts = (record?.count || 0) + 1;
|
|
248
|
+
if (attempts >= LOGIN_MAX_ATTEMPTS) {
|
|
249
|
+
loginAttempts.set(ip, { count: attempts, lockedUntil: Date.now() + LOGIN_LOCKOUT_MS });
|
|
250
|
+
console.log(`[ccweb] IP ${ip} locked out for 30 minutes after ${attempts} failed attempts`);
|
|
251
|
+
return res.status(429).send(`Too many failed attempts. Try again in 30 minute(s).`);
|
|
252
|
+
}
|
|
253
|
+
loginAttempts.set(ip, { count: attempts, lockedUntil: 0 });
|
|
254
|
+
return res.status(401).send(`Invalid credentials (${LOGIN_MAX_ATTEMPTS - attempts} attempt(s) remaining)`);
|
|
236
255
|
}
|
|
256
|
+
loginAttempts.delete(ip);
|
|
237
257
|
const token = signToken("auth", authSecret);
|
|
238
258
|
const secure = req.protocol === "https" || req.headers["x-forwarded-proto"] === "https";
|
|
239
259
|
const cookieFlags = `Path=/; HttpOnly; SameSite=Lax${secure ? "; Secure" : ""}`;
|
|
@@ -374,15 +394,29 @@ function startServer(options) {
|
|
|
374
394
|
console.log(`[ccweb] Username: ${username}`);
|
|
375
395
|
console.log(`[ccweb] Password: ${password}`);
|
|
376
396
|
console.log(`[ccweb] Press Ctrl+C to stop`);
|
|
377
|
-
if (enableTunnel) {
|
|
397
|
+
if (enableTunnel || tunnelToken) {
|
|
398
|
+
console.log("[ccweb] Starting Cloudflare Tunnel...");
|
|
378
399
|
import("cloudflared").then(({ Tunnel }) => {
|
|
379
|
-
|
|
400
|
+
console.log("[ccweb] cloudflared module loaded, creating tunnel...");
|
|
401
|
+
const t = tunnelToken ? Tunnel.withToken(tunnelToken) : Tunnel.quick(`http://localhost:${port}`, { "--no-autoupdate": true });
|
|
402
|
+
console.log("[ccweb] Tunnel process PID:", t.process.pid);
|
|
380
403
|
t.on("url", (url) => {
|
|
381
404
|
console.log(`[ccweb] Tunnel: ${url}`);
|
|
382
405
|
});
|
|
406
|
+
t.on("stdout", (output) => {
|
|
407
|
+
const line = output.trim();
|
|
408
|
+
if (line) console.log(`[ccweb] Tunnel stdout: ${line}`);
|
|
409
|
+
});
|
|
410
|
+
t.on("stderr", (output) => {
|
|
411
|
+
const line = output.trim();
|
|
412
|
+
if (line) console.log(`[ccweb] Tunnel stderr: ${line}`);
|
|
413
|
+
});
|
|
383
414
|
t.on("error", (err) => {
|
|
384
415
|
console.error(`[ccweb] Tunnel error: ${err.message}`);
|
|
385
416
|
});
|
|
417
|
+
t.on("exit", (code, signal) => {
|
|
418
|
+
console.log(`[ccweb] Tunnel exited with code=${code} signal=${signal}`);
|
|
419
|
+
});
|
|
386
420
|
const stopTunnel = () => t.stop();
|
|
387
421
|
process.on("SIGINT", stopTunnel);
|
|
388
422
|
process.on("SIGTERM", stopTunnel);
|
|
@@ -450,10 +484,10 @@ function fetchLatestVersion() {
|
|
|
450
484
|
async function checkUpdate() {
|
|
451
485
|
try {
|
|
452
486
|
const latest = await fetchLatestVersion();
|
|
453
|
-
if (latest !== "1.0.
|
|
487
|
+
if (latest !== "1.0.4") {
|
|
454
488
|
console.log(
|
|
455
489
|
`
|
|
456
|
-
New version available: ${"1.0.
|
|
490
|
+
New version available: ${"1.0.4"} -> \x1B[32m${latest}\x1B[0m`
|
|
457
491
|
);
|
|
458
492
|
console.log(` Run \x1B[36mccweb update\x1B[0m to update
|
|
459
493
|
`);
|
|
@@ -464,11 +498,11 @@ async function checkUpdate() {
|
|
|
464
498
|
async function selfUpdate() {
|
|
465
499
|
try {
|
|
466
500
|
const latest = await fetchLatestVersion();
|
|
467
|
-
if (latest === "1.0.
|
|
468
|
-
console.log(`Already up to date (v${"1.0.
|
|
501
|
+
if (latest === "1.0.4") {
|
|
502
|
+
console.log(`Already up to date (v${"1.0.4"})`);
|
|
469
503
|
return;
|
|
470
504
|
}
|
|
471
|
-
console.log(`Updating ${PKG_NAME}: ${"1.0.
|
|
505
|
+
console.log(`Updating ${PKG_NAME}: ${"1.0.4"} -> ${latest} ...`);
|
|
472
506
|
(0, import_child_process2.execSync)(`npm install -g ${PKG_NAME}@latest`, { stdio: "inherit" });
|
|
473
507
|
console.log(`Successfully updated to v${latest}`);
|
|
474
508
|
} catch (err) {
|
|
@@ -479,15 +513,15 @@ async function selfUpdate() {
|
|
|
479
513
|
|
|
480
514
|
// src/index.ts
|
|
481
515
|
var program = new import_commander.Command();
|
|
482
|
-
program.name("ccweb").description("A CLI tool that exposes an interactive web terminal in the browser").version("1.0.
|
|
516
|
+
program.name("ccweb").description("A CLI tool that exposes an interactive web terminal in the browser").version("1.0.4");
|
|
483
517
|
program.command("update").description("Update ccweb to the latest version").action(async () => {
|
|
484
518
|
await selfUpdate();
|
|
485
519
|
});
|
|
486
|
-
program.command("start", { isDefault: true }).description("Start the web terminal server").option("-p, --port <number>", "port to listen on", "
|
|
520
|
+
program.command("start", { isDefault: true }).description("Start the web terminal server").option("-p, --port <number>", "port to listen on", "1989").option("-H, --host <address>", "host to bind to", "0.0.0.0").option(
|
|
487
521
|
"-s, --shell <path>",
|
|
488
522
|
"shell to spawn",
|
|
489
523
|
process.env.SHELL || "/bin/bash"
|
|
490
|
-
).option("-u, --username <name>", "username for authentication", "cc").option("--password <password>", "password for authentication (random if not set)").option("--tunnel", "expose via Cloudflare Tunnel (no account required)").action(async (opts) => {
|
|
524
|
+
).option("-u, --username <name>", "username for authentication", "cc").option("--password <password>", "password for authentication (random if not set)").option("--tunnel", "expose via Cloudflare Tunnel (no account required)").option("--tunnel-token <token>", "use a Cloudflare Tunnel token for custom domain (from Zero Trust dashboard)").action(async (opts) => {
|
|
491
525
|
const port = parseInt(opts.port, 10);
|
|
492
526
|
if (isNaN(port) || port < 1 || port > 65535) {
|
|
493
527
|
console.error(`Invalid port: ${opts.port}`);
|
|
@@ -501,7 +535,8 @@ program.command("start", { isDefault: true }).description("Start the web termina
|
|
|
501
535
|
shell: opts.shell,
|
|
502
536
|
username: opts.username,
|
|
503
537
|
password,
|
|
504
|
-
tunnel: !!opts.tunnel
|
|
538
|
+
tunnel: !!opts.tunnel,
|
|
539
|
+
tunnelToken: opts.tunnelToken
|
|
505
540
|
});
|
|
506
541
|
});
|
|
507
542
|
program.parse();
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/server.ts","../src/session-manager.ts","../src/terminal.ts","../src/updater.ts"],"sourcesContent":["import crypto from \"crypto\";\nimport { Command } from \"commander\";\nimport { startServer } from \"./server\";\nimport { checkUpdate, selfUpdate } from \"./updater\";\n\ndeclare const __VERSION__: string;\n\nconst program = new Command();\n\nprogram\n .name(\"ccweb\")\n .description(\"A CLI tool that exposes an interactive web terminal in the browser\")\n .version(__VERSION__);\n\nprogram\n .command(\"update\")\n .description(\"Update ccweb to the latest version\")\n .action(async () => {\n await selfUpdate();\n });\n\nprogram\n .command(\"start\", { isDefault: true })\n .description(\"Start the web terminal server\")\n .option(\"-p, --port <number>\", \"port to listen on\", \"8080\")\n .option(\"-H, --host <address>\", \"host to bind to\", \"0.0.0.0\")\n .option(\n \"-s, --shell <path>\",\n \"shell to spawn\",\n process.env.SHELL || \"/bin/bash\"\n )\n .option(\"-u, --username <name>\", \"username for authentication\", \"cc\")\n .option(\"--password <password>\", \"password for authentication (random if not set)\")\n .option(\"--tunnel\", \"expose via Cloudflare Tunnel (no account required)\")\n .action(async (opts) => {\n const port = parseInt(opts.port, 10);\n if (isNaN(port) || port < 1 || port > 65535) {\n console.error(`Invalid port: ${opts.port}`);\n process.exit(1);\n }\n\n const password = opts.password || crypto.randomBytes(12).toString(\"base64url\");\n\n // Check for updates in background (non-blocking)\n checkUpdate();\n\n startServer({\n port,\n host: opts.host,\n shell: opts.shell,\n username: opts.username,\n password,\n tunnel: !!opts.tunnel,\n });\n });\n\nprogram.parse();\n","import crypto from \"crypto\";\nimport { spawn } from \"child_process\";\nimport express from \"express\";\nimport http from \"http\";\nimport os from \"os\";\nimport path from \"path\";\nimport { WebSocketServer, WebSocket } from \"ws\";\nimport { SessionManager } from \"./session-manager\";\n\nexport interface ServerOptions {\n port: number;\n host: string;\n shell: string;\n username: string;\n password: string;\n tunnel?: boolean;\n}\n\ninterface ClientMessage {\n type: \"input\" | \"resize\" | \"kill\" | \"create\";\n data?: string;\n cols?: number;\n rows?: number;\n sessionId?: string;\n}\n\n// Cookie-based auth helpers\n\nfunction generateSecret(): string {\n return crypto.randomBytes(32).toString(\"hex\");\n}\n\nfunction signToken(payload: string, secret: string): string {\n const hmac = crypto.createHmac(\"sha256\", secret);\n hmac.update(payload);\n return payload + \".\" + hmac.digest(\"hex\");\n}\n\nfunction verifyToken(token: string, secret: string): boolean {\n const dotIdx = token.lastIndexOf(\".\");\n if (dotIdx === -1) return false;\n const payload = token.substring(0, dotIdx);\n const sig = token.substring(dotIdx + 1);\n const hmac = crypto.createHmac(\"sha256\", secret);\n hmac.update(payload);\n const expected = hmac.digest(\"hex\");\n if (sig.length !== expected.length) return false;\n return crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected));\n}\n\nfunction parseCookies(header: string | undefined): Record<string, string> {\n const cookies: Record<string, string> = {};\n if (!header) return cookies;\n for (const part of header.split(\";\")) {\n const eq = part.indexOf(\"=\");\n if (eq === -1) continue;\n const key = part.substring(0, eq).trim();\n const val = part.substring(eq + 1).trim();\n cookies[key] = decodeURIComponent(val);\n }\n return cookies;\n}\n\nexport function startServer(options: ServerOptions): void {\n const { port, host, shell, username, password, tunnel: enableTunnel } = options;\n\n const sessionManager = new SessionManager();\n const authSecret = generateSecret();\n\n const app = express();\n app.set(\"trust proxy\", true);\n const server = http.createServer(app);\n\n // Track all connected WebSocket clients for tab-list broadcasting\n const allClients = new Set<WebSocket>();\n\n function broadcastTabs(): void {\n const tabs = sessionManager.listIds();\n const msg = JSON.stringify({ type: \"tabs\", tabs });\n for (const client of allClients) {\n if (client.readyState === WebSocket.OPEN) {\n client.send(msg);\n }\n }\n }\n\n function isAuthenticated(cookieHeader: string | undefined): boolean {\n const cookies = parseCookies(cookieHeader);\n const token = cookies[\"ccweb_token\"];\n if (!token) return false;\n return verifyToken(token, authSecret);\n }\n\n // Login endpoint\n app.use(express.urlencoded({ extended: false }));\n\n app.post(\"/login\", (req, res) => {\n const { username: u, password: p } = req.body as { username?: string; password?: string };\n if (!u || !p) {\n return res.status(400).send(\"Missing credentials\");\n }\n const uMatch = u.length === username.length && crypto.timingSafeEqual(Buffer.from(u), Buffer.from(username));\n const pMatch = p.length === password.length && crypto.timingSafeEqual(Buffer.from(p), Buffer.from(password));\n if (!uMatch || !pMatch) {\n return res.status(401).send(\"Invalid credentials\");\n }\n const token = signToken(\"auth\", authSecret);\n const secure = req.protocol === \"https\" || req.headers[\"x-forwarded-proto\"] === \"https\";\n const cookieFlags = `Path=/; HttpOnly; SameSite=Lax${secure ? \"; Secure\" : \"\"}`;\n res.setHeader(\"Set-Cookie\", `ccweb_token=${encodeURIComponent(token)}; ${cookieFlags}`);\n res.redirect(\"/\");\n });\n\n // Auth middleware - skip login route and static login page\n app.use((req, res, next) => {\n if (isAuthenticated(req.headers.cookie)) {\n return next();\n }\n // Serve login page for GET requests\n if (req.method === \"GET\") {\n return res.sendFile(path.join(__dirname, \"public\", \"login.html\"), { dotfiles: \"allow\" });\n }\n res.status(401).send(\"Unauthorized\");\n });\n\n app.use(express.static(path.join(__dirname, \"public\"), { dotfiles: \"allow\" }));\n\n app.get(\"/\", (_req, res) => {\n res.sendFile(path.join(__dirname, \"public\", \"index.html\"), { dotfiles: \"allow\" });\n });\n\n const wss = new WebSocketServer({ noServer: true });\n\n // WebSocket ping/pong keepalive (Cloudflare Tunnel idle timeout is ~100s)\n const WS_PING_INTERVAL = 30_000;\n const pingInterval = setInterval(() => {\n for (const client of wss.clients) {\n if (client.readyState === WebSocket.OPEN) {\n client.ping();\n }\n }\n }, WS_PING_INTERVAL);\n wss.on(\"close\", () => clearInterval(pingInterval));\n\n server.on(\"upgrade\", (req, socket, head) => {\n if (!isAuthenticated(req.headers.cookie)) {\n socket.write(\"HTTP/1.1 401 Unauthorized\\r\\n\\r\\n\");\n socket.destroy();\n return;\n }\n wss.handleUpgrade(req, socket, head, (ws) => {\n wss.emit(\"connection\", ws, req);\n });\n });\n\n wss.on(\"connection\", (ws: WebSocket, req) => {\n const url = new URL(req.url || \"/\", `http://${req.headers.host}`);\n const sessionId = url.searchParams.get(\"sessionId\");\n const cols = parseInt(url.searchParams.get(\"cols\") || \"80\", 10);\n const rows = parseInt(url.searchParams.get(\"rows\") || \"24\", 10);\n\n allClients.add(ws);\n\n // If no sessionId, this is a control connection - send current tab list\n if (!sessionId) {\n // Auto-create a default session if none exist\n if (sessionManager.listIds().length === 0) {\n const session = sessionManager.create(shell, cols, rows);\n console.log(`[ccweb] Default session: ${session.id}`);\n }\n ws.send(JSON.stringify({ type: \"tabs\", tabs: sessionManager.listIds() }));\n\n ws.on(\"message\", (raw: Buffer) => {\n try {\n const msg: ClientMessage = JSON.parse(raw.toString());\n if (msg.type === \"create\") {\n const c = msg.cols || 80;\n const r = msg.rows || 24;\n const session = sessionManager.create(shell, c, r);\n console.log(`[ccweb] New session: ${session.id}`);\n broadcastTabs();\n } else if (msg.type === \"kill\" && msg.sessionId) {\n console.log(`[ccweb] Session killed: ${msg.sessionId}`);\n sessionManager.remove(msg.sessionId);\n broadcastTabs();\n }\n } catch {\n // Ignore malformed messages\n }\n });\n\n ws.on(\"close\", () => {\n allClients.delete(ws);\n });\n ws.on(\"error\", () => {\n allClients.delete(ws);\n });\n return;\n }\n\n // Session-specific connection\n const session = sessionManager.get(sessionId);\n if (!session) {\n ws.send(JSON.stringify({ type: \"error\", data: \"Session not found\" }));\n ws.close();\n allClients.delete(ws);\n return;\n }\n\n console.log(`[ccweb] Client attached to session: ${sessionId}`);\n\n // Replay buffered output so new clients see existing content\n for (const chunk of session.buffer) {\n ws.send(JSON.stringify({ type: \"output\", data: chunk }));\n }\n\n // Subscribe to live output\n const onOutput = (data: string) => {\n if (ws.readyState === WebSocket.OPEN) {\n ws.send(JSON.stringify({ type: \"output\", data }));\n }\n };\n session.clients.add(onOutput);\n\n const onExit = () => {\n console.log(`[ccweb] Session ended: ${sessionId}`);\n if (ws.readyState === WebSocket.OPEN) {\n ws.send(JSON.stringify({ type: \"exit\" }));\n ws.close();\n }\n sessionManager.remove(sessionId);\n broadcastTabs();\n };\n session.onExit.add(onExit);\n\n // Resize to match this client\n session.terminal.resize(cols, rows);\n\n ws.on(\"message\", (raw: Buffer) => {\n try {\n const msg: ClientMessage = JSON.parse(raw.toString());\n switch (msg.type) {\n case \"input\":\n if (msg.data) {\n session.terminal.write(msg.data);\n }\n break;\n case \"resize\":\n if (msg.cols && msg.rows) {\n session.terminal.resize(msg.cols, msg.rows);\n }\n break;\n }\n } catch {\n // Ignore malformed messages\n }\n });\n\n ws.on(\"close\", () => {\n allClients.delete(ws);\n session.clients.delete(onOutput);\n session.onExit.delete(onExit);\n });\n\n ws.on(\"error\", () => {\n allClients.delete(ws);\n session.clients.delete(onOutput);\n session.onExit.delete(onExit);\n });\n });\n\n server.listen(port, host, () => {\n const addr = host === \"0.0.0.0\" ? \"localhost\" : host;\n console.log(`[ccweb] Terminal server running at http://${addr}:${port}`);\n console.log(`[ccweb] Shell: ${shell}`);\n console.log(`[ccweb] Username: ${username}`);\n console.log(`[ccweb] Password: ${password}`);\n console.log(`[ccweb] Press Ctrl+C to stop`);\n\n // Start Cloudflare Tunnel if requested\n if (enableTunnel) {\n import(\"cloudflared\").then(({ Tunnel }) => {\n const t = new Tunnel([\"tunnel\", \"--url\", `http://localhost:${port}`, \"--no-autoupdate\"]);\n t.on(\"url\", (url: string) => {\n console.log(`[ccweb] Tunnel: ${url}`);\n });\n t.on(\"error\", (err: Error) => {\n console.error(`[ccweb] Tunnel error: ${err.message}`);\n });\n const stopTunnel = () => t.stop();\n process.on(\"SIGINT\", stopTunnel);\n process.on(\"SIGTERM\", stopTunnel);\n }).catch((err) => {\n console.error(\"[ccweb] Failed to start tunnel:\", err.message);\n });\n }\n\n // Prevent system sleep while ccweb is running\n const platform = os.platform();\n if (platform === \"darwin\") {\n const caffeinate = spawn(\"caffeinate\", [\"-s\", \"-w\", process.pid.toString()], {\n stdio: \"ignore\",\n });\n caffeinate.unref();\n console.log(\"[ccweb] Sleep prevention enabled (caffeinate)\");\n } else if (platform === \"win32\") {\n const script = `\nAdd-Type -TypeDefinition @\"\nusing System.Runtime.InteropServices;\npublic class Power {\n [DllImport(\"kernel32.dll\")]\n public static extern uint SetThreadExecutionState(uint esFlags);\n}\n\"@\nwhile($true) {\n [Power]::SetThreadExecutionState(0x80000001)\n Start-Sleep -Seconds 30\n}`;\n const ps = spawn(\"powershell\", [\"-NoProfile\", \"-Command\", script], {\n stdio: \"ignore\",\n });\n ps.unref();\n console.log(\"[ccweb] Sleep prevention enabled (SetThreadExecutionState)\");\n }\n });\n\n const shutdown = () => {\n console.log(\"\\n[ccweb] Shutting down...\");\n wss.clients.forEach((client) => client.close());\n sessionManager.destroyAll();\n server.close(() => process.exit(0));\n setTimeout(() => process.exit(1), 3000);\n };\n\n process.on(\"SIGINT\", shutdown);\n process.on(\"SIGTERM\", shutdown);\n}\n","import crypto from \"crypto\";\nimport { Terminal } from \"./terminal\";\n\nexport interface Session {\n id: string;\n terminal: Terminal;\n buffer: string[];\n clients: Set<(data: string) => void>;\n onExit: Set<() => void>;\n alive: boolean;\n}\n\nconst MAX_BUFFER = 5000;\n\nexport class SessionManager {\n private sessions = new Map<string, Session>();\n private order: string[] = [];\n\n create(shell: string, cols: number, rows: number): Session {\n const id = crypto.randomUUID();\n const terminal = new Terminal({ shell, cols, rows });\n\n const session: Session = {\n id,\n terminal,\n buffer: [],\n clients: new Set(),\n onExit: new Set(),\n alive: true,\n };\n\n terminal.onData((data) => {\n if (session.buffer.length >= MAX_BUFFER) {\n session.buffer.shift();\n }\n session.buffer.push(data);\n for (const cb of session.clients) {\n cb(data);\n }\n });\n\n terminal.onExit(() => {\n session.alive = false;\n for (const cb of session.onExit) {\n cb();\n }\n });\n\n this.sessions.set(id, session);\n this.order.push(id);\n return session;\n }\n\n get(id: string): Session | undefined {\n const session = this.sessions.get(id);\n if (session && !session.alive) {\n this.sessions.delete(id);\n this.order = this.order.filter((x) => x !== id);\n return undefined;\n }\n return session;\n }\n\n listIds(): string[] {\n this.order = this.order.filter((id) => {\n const s = this.sessions.get(id);\n if (s && s.alive) return true;\n this.sessions.delete(id);\n return false;\n });\n return [...this.order];\n }\n\n remove(id: string): void {\n const session = this.sessions.get(id);\n if (session) {\n session.terminal.kill();\n this.sessions.delete(id);\n }\n this.order = this.order.filter((x) => x !== id);\n }\n\n destroyAll(): void {\n for (const session of this.sessions.values()) {\n session.terminal.kill();\n }\n this.sessions.clear();\n this.order = [];\n }\n}\n","import * as pty from \"node-pty\";\nimport os from \"os\";\n\nexport interface TerminalOptions {\n shell: string;\n cols?: number;\n rows?: number;\n cwd?: string;\n}\n\nexport class Terminal {\n private process: pty.IPty;\n private disposed = false;\n\n constructor(options: TerminalOptions) {\n const { shell, cols = 80, rows = 24, cwd } = options;\n const env = Object.assign({}, process.env, {\n TERM: \"xterm-256color\",\n COLORTERM: \"truecolor\",\n FORCE_COLOR: \"3\",\n TERM_PROGRAM: \"xterm-256color\",\n });\n delete (env as Record<string, string | undefined>).NO_COLOR;\n\n this.process = pty.spawn(shell, [], {\n name: \"xterm-256color\",\n cols,\n rows,\n cwd: cwd || os.homedir(),\n env: env as Record<string, string>,\n });\n }\n\n get pid(): number {\n return this.process.pid;\n }\n\n onData(callback: (data: string) => void): void {\n this.process.onData(callback);\n }\n\n onExit(callback: (exitCode: number, signal?: number) => void): void {\n this.process.onExit(({ exitCode, signal }) => {\n callback(exitCode, signal);\n });\n }\n\n write(data: string): void {\n if (!this.disposed) {\n this.process.write(data);\n }\n }\n\n resize(cols: number, rows: number): void {\n if (!this.disposed) {\n try {\n this.process.resize(cols, rows);\n } catch {\n // PTY may already be closed\n }\n }\n }\n\n kill(): void {\n if (!this.disposed) {\n this.disposed = true;\n try {\n this.process.kill();\n } catch {\n // Already dead\n }\n }\n }\n}\n","import https from \"https\";\nimport { execSync } from \"child_process\";\n\ndeclare const __VERSION__: string;\nconst PKG_NAME = \"ccwebtty\";\n\nfunction fetchLatestVersion(): Promise<string> {\n return new Promise((resolve, reject) => {\n https\n .get(`https://registry.npmjs.org/${PKG_NAME}/latest`, (res) => {\n let data = \"\";\n res.on(\"data\", (chunk) => (data += chunk));\n res.on(\"end\", () => {\n try {\n resolve(JSON.parse(data).version);\n } catch {\n reject(new Error(\"Failed to parse registry response\"));\n }\n });\n })\n .on(\"error\", reject);\n });\n}\n\nexport async function checkUpdate(): Promise<void> {\n try {\n const latest = await fetchLatestVersion();\n if (latest !== __VERSION__) {\n console.log(\n `\\n New version available: ${__VERSION__} -> \\x1b[32m${latest}\\x1b[0m`\n );\n console.log(` Run \\x1b[36mccweb update\\x1b[0m to update\\n`);\n }\n } catch {\n // silently ignore network errors\n }\n}\n\nexport async function selfUpdate(): Promise<void> {\n try {\n const latest = await fetchLatestVersion();\n if (latest === __VERSION__) {\n console.log(`Already up to date (v${__VERSION__})`);\n return;\n }\n console.log(`Updating ${PKG_NAME}: ${__VERSION__} -> ${latest} ...`);\n execSync(`npm install -g ${PKG_NAME}@latest`, { stdio: \"inherit\" });\n console.log(`Successfully updated to v${latest}`);\n } catch (err: any) {\n console.error(`Update failed: ${err.message}`);\n process.exit(1);\n }\n}\n"],"mappings":"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","names":["import_crypto","import_crypto","import_os","os","crypto","crypto","express","http","path","session","os","import_child_process","https","crypto"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/server.ts","../src/session-manager.ts","../src/terminal.ts","../src/updater.ts"],"sourcesContent":["import crypto from \"crypto\";\nimport { Command } from \"commander\";\nimport { startServer } from \"./server\";\nimport { checkUpdate, selfUpdate } from \"./updater\";\n\ndeclare const __VERSION__: string;\n\nconst program = new Command();\n\nprogram\n .name(\"ccweb\")\n .description(\"A CLI tool that exposes an interactive web terminal in the browser\")\n .version(__VERSION__);\n\nprogram\n .command(\"update\")\n .description(\"Update ccweb to the latest version\")\n .action(async () => {\n await selfUpdate();\n });\n\nprogram\n .command(\"start\", { isDefault: true })\n .description(\"Start the web terminal server\")\n .option(\"-p, --port <number>\", \"port to listen on\", \"1989\")\n .option(\"-H, --host <address>\", \"host to bind to\", \"0.0.0.0\")\n .option(\n \"-s, --shell <path>\",\n \"shell to spawn\",\n process.env.SHELL || \"/bin/bash\"\n )\n .option(\"-u, --username <name>\", \"username for authentication\", \"cc\")\n .option(\"--password <password>\", \"password for authentication (random if not set)\")\n .option(\"--tunnel\", \"expose via Cloudflare Tunnel (no account required)\")\n .option(\"--tunnel-token <token>\", \"use a Cloudflare Tunnel token for custom domain (from Zero Trust dashboard)\")\n .action(async (opts) => {\n const port = parseInt(opts.port, 10);\n if (isNaN(port) || port < 1 || port > 65535) {\n console.error(`Invalid port: ${opts.port}`);\n process.exit(1);\n }\n\n const password = opts.password || crypto.randomBytes(12).toString(\"base64url\");\n\n // Check for updates in background (non-blocking)\n checkUpdate();\n\n startServer({\n port,\n host: opts.host,\n shell: opts.shell,\n username: opts.username,\n password,\n tunnel: !!opts.tunnel,\n tunnelToken: opts.tunnelToken,\n });\n });\n\nprogram.parse();\n","import crypto from \"crypto\";\nimport { spawn } from \"child_process\";\nimport express from \"express\";\nimport http from \"http\";\nimport os from \"os\";\nimport path from \"path\";\nimport { WebSocketServer, WebSocket } from \"ws\";\nimport { SessionManager } from \"./session-manager\";\n\nexport interface ServerOptions {\n port: number;\n host: string;\n shell: string;\n username: string;\n password: string;\n tunnel?: boolean;\n tunnelToken?: string;\n}\n\ninterface ClientMessage {\n type: \"input\" | \"resize\" | \"kill\" | \"create\";\n data?: string;\n cols?: number;\n rows?: number;\n sessionId?: string;\n}\n\n// Cookie-based auth helpers\n\nfunction generateSecret(): string {\n return crypto.randomBytes(32).toString(\"hex\");\n}\n\nfunction signToken(payload: string, secret: string): string {\n const hmac = crypto.createHmac(\"sha256\", secret);\n hmac.update(payload);\n return payload + \".\" + hmac.digest(\"hex\");\n}\n\nfunction verifyToken(token: string, secret: string): boolean {\n const dotIdx = token.lastIndexOf(\".\");\n if (dotIdx === -1) return false;\n const payload = token.substring(0, dotIdx);\n const sig = token.substring(dotIdx + 1);\n const hmac = crypto.createHmac(\"sha256\", secret);\n hmac.update(payload);\n const expected = hmac.digest(\"hex\");\n if (sig.length !== expected.length) return false;\n return crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected));\n}\n\nfunction parseCookies(header: string | undefined): Record<string, string> {\n const cookies: Record<string, string> = {};\n if (!header) return cookies;\n for (const part of header.split(\";\")) {\n const eq = part.indexOf(\"=\");\n if (eq === -1) continue;\n const key = part.substring(0, eq).trim();\n const val = part.substring(eq + 1).trim();\n cookies[key] = decodeURIComponent(val);\n }\n return cookies;\n}\n\nexport function startServer(options: ServerOptions): void {\n const { port, host, shell, username, password, tunnel: enableTunnel, tunnelToken } = options;\n\n const sessionManager = new SessionManager();\n const authSecret = generateSecret();\n\n const app = express();\n app.set(\"trust proxy\", true);\n const server = http.createServer(app);\n\n // Track all connected WebSocket clients for tab-list broadcasting\n const allClients = new Set<WebSocket>();\n\n function broadcastTabs(): void {\n const tabs = sessionManager.listIds();\n const msg = JSON.stringify({ type: \"tabs\", tabs });\n for (const client of allClients) {\n if (client.readyState === WebSocket.OPEN) {\n client.send(msg);\n }\n }\n }\n\n function isAuthenticated(cookieHeader: string | undefined): boolean {\n const cookies = parseCookies(cookieHeader);\n const token = cookies[\"ccweb_token\"];\n if (!token) return false;\n return verifyToken(token, authSecret);\n }\n\n // Login rate limiting: 3 failures per IP → 30 min lockout\n const LOGIN_MAX_ATTEMPTS = 3;\n const LOGIN_LOCKOUT_MS = 30 * 60 * 1000;\n const loginAttempts = new Map<string, { count: number; lockedUntil: number }>();\n\n function getClientIp(req: express.Request): string {\n return (req.ip || req.socket.remoteAddress || \"unknown\");\n }\n\n // Login endpoint\n app.use(express.urlencoded({ extended: false }));\n\n app.post(\"/login\", (req, res) => {\n const ip = getClientIp(req);\n const record = loginAttempts.get(ip);\n\n if (record && record.lockedUntil > Date.now()) {\n const remaining = Math.ceil((record.lockedUntil - Date.now()) / 60000);\n return res.status(429).send(`Too many failed attempts. Try again in ${remaining} minute(s).`);\n }\n\n const { username: u, password: p } = req.body as { username?: string; password?: string };\n if (!u || !p) {\n return res.status(400).send(\"Missing credentials\");\n }\n const uMatch = u.length === username.length && crypto.timingSafeEqual(Buffer.from(u), Buffer.from(username));\n const pMatch = p.length === password.length && crypto.timingSafeEqual(Buffer.from(p), Buffer.from(password));\n if (!uMatch || !pMatch) {\n const attempts = (record?.count || 0) + 1;\n if (attempts >= LOGIN_MAX_ATTEMPTS) {\n loginAttempts.set(ip, { count: attempts, lockedUntil: Date.now() + LOGIN_LOCKOUT_MS });\n console.log(`[ccweb] IP ${ip} locked out for 30 minutes after ${attempts} failed attempts`);\n return res.status(429).send(`Too many failed attempts. Try again in 30 minute(s).`);\n }\n loginAttempts.set(ip, { count: attempts, lockedUntil: 0 });\n return res.status(401).send(`Invalid credentials (${LOGIN_MAX_ATTEMPTS - attempts} attempt(s) remaining)`);\n }\n\n // Successful login - clear failed attempts\n loginAttempts.delete(ip);\n\n const token = signToken(\"auth\", authSecret);\n const secure = req.protocol === \"https\" || req.headers[\"x-forwarded-proto\"] === \"https\";\n const cookieFlags = `Path=/; HttpOnly; SameSite=Lax${secure ? \"; Secure\" : \"\"}`;\n res.setHeader(\"Set-Cookie\", `ccweb_token=${encodeURIComponent(token)}; ${cookieFlags}`);\n res.redirect(\"/\");\n });\n\n // Auth middleware - skip login route and static login page\n app.use((req, res, next) => {\n if (isAuthenticated(req.headers.cookie)) {\n return next();\n }\n // Serve login page for GET requests\n if (req.method === \"GET\") {\n return res.sendFile(path.join(__dirname, \"public\", \"login.html\"), { dotfiles: \"allow\" });\n }\n res.status(401).send(\"Unauthorized\");\n });\n\n app.use(express.static(path.join(__dirname, \"public\"), { dotfiles: \"allow\" }));\n\n app.get(\"/\", (_req, res) => {\n res.sendFile(path.join(__dirname, \"public\", \"index.html\"), { dotfiles: \"allow\" });\n });\n\n const wss = new WebSocketServer({ noServer: true });\n\n // WebSocket ping/pong keepalive (Cloudflare Tunnel idle timeout is ~100s)\n const WS_PING_INTERVAL = 30_000;\n const pingInterval = setInterval(() => {\n for (const client of wss.clients) {\n if (client.readyState === WebSocket.OPEN) {\n client.ping();\n }\n }\n }, WS_PING_INTERVAL);\n wss.on(\"close\", () => clearInterval(pingInterval));\n\n server.on(\"upgrade\", (req, socket, head) => {\n if (!isAuthenticated(req.headers.cookie)) {\n socket.write(\"HTTP/1.1 401 Unauthorized\\r\\n\\r\\n\");\n socket.destroy();\n return;\n }\n wss.handleUpgrade(req, socket, head, (ws) => {\n wss.emit(\"connection\", ws, req);\n });\n });\n\n wss.on(\"connection\", (ws: WebSocket, req) => {\n const url = new URL(req.url || \"/\", `http://${req.headers.host}`);\n const sessionId = url.searchParams.get(\"sessionId\");\n const cols = parseInt(url.searchParams.get(\"cols\") || \"80\", 10);\n const rows = parseInt(url.searchParams.get(\"rows\") || \"24\", 10);\n\n allClients.add(ws);\n\n // If no sessionId, this is a control connection - send current tab list\n if (!sessionId) {\n // Auto-create a default session if none exist\n if (sessionManager.listIds().length === 0) {\n const session = sessionManager.create(shell, cols, rows);\n console.log(`[ccweb] Default session: ${session.id}`);\n }\n ws.send(JSON.stringify({ type: \"tabs\", tabs: sessionManager.listIds() }));\n\n ws.on(\"message\", (raw: Buffer) => {\n try {\n const msg: ClientMessage = JSON.parse(raw.toString());\n if (msg.type === \"create\") {\n const c = msg.cols || 80;\n const r = msg.rows || 24;\n const session = sessionManager.create(shell, c, r);\n console.log(`[ccweb] New session: ${session.id}`);\n broadcastTabs();\n } else if (msg.type === \"kill\" && msg.sessionId) {\n console.log(`[ccweb] Session killed: ${msg.sessionId}`);\n sessionManager.remove(msg.sessionId);\n broadcastTabs();\n }\n } catch {\n // Ignore malformed messages\n }\n });\n\n ws.on(\"close\", () => {\n allClients.delete(ws);\n });\n ws.on(\"error\", () => {\n allClients.delete(ws);\n });\n return;\n }\n\n // Session-specific connection\n const session = sessionManager.get(sessionId);\n if (!session) {\n ws.send(JSON.stringify({ type: \"error\", data: \"Session not found\" }));\n ws.close();\n allClients.delete(ws);\n return;\n }\n\n console.log(`[ccweb] Client attached to session: ${sessionId}`);\n\n // Replay buffered output so new clients see existing content\n for (const chunk of session.buffer) {\n ws.send(JSON.stringify({ type: \"output\", data: chunk }));\n }\n\n // Subscribe to live output\n const onOutput = (data: string) => {\n if (ws.readyState === WebSocket.OPEN) {\n ws.send(JSON.stringify({ type: \"output\", data }));\n }\n };\n session.clients.add(onOutput);\n\n const onExit = () => {\n console.log(`[ccweb] Session ended: ${sessionId}`);\n if (ws.readyState === WebSocket.OPEN) {\n ws.send(JSON.stringify({ type: \"exit\" }));\n ws.close();\n }\n sessionManager.remove(sessionId);\n broadcastTabs();\n };\n session.onExit.add(onExit);\n\n // Resize to match this client\n session.terminal.resize(cols, rows);\n\n ws.on(\"message\", (raw: Buffer) => {\n try {\n const msg: ClientMessage = JSON.parse(raw.toString());\n switch (msg.type) {\n case \"input\":\n if (msg.data) {\n session.terminal.write(msg.data);\n }\n break;\n case \"resize\":\n if (msg.cols && msg.rows) {\n session.terminal.resize(msg.cols, msg.rows);\n }\n break;\n }\n } catch {\n // Ignore malformed messages\n }\n });\n\n ws.on(\"close\", () => {\n allClients.delete(ws);\n session.clients.delete(onOutput);\n session.onExit.delete(onExit);\n });\n\n ws.on(\"error\", () => {\n allClients.delete(ws);\n session.clients.delete(onOutput);\n session.onExit.delete(onExit);\n });\n });\n\n server.listen(port, host, () => {\n const addr = host === \"0.0.0.0\" ? \"localhost\" : host;\n console.log(`[ccweb] Terminal server running at http://${addr}:${port}`);\n console.log(`[ccweb] Shell: ${shell}`);\n console.log(`[ccweb] Username: ${username}`);\n console.log(`[ccweb] Password: ${password}`);\n console.log(`[ccweb] Press Ctrl+C to stop`);\n\n // Start Cloudflare Tunnel if requested\n if (enableTunnel || tunnelToken) {\n console.log(\"[ccweb] Starting Cloudflare Tunnel...\");\n import(\"cloudflared\").then(({ Tunnel }) => {\n console.log(\"[ccweb] cloudflared module loaded, creating tunnel...\");\n const t = tunnelToken\n ? Tunnel.withToken(tunnelToken)\n : Tunnel.quick(`http://localhost:${port}`, { \"--no-autoupdate\": true });\n\n console.log(\"[ccweb] Tunnel process PID:\", t.process.pid);\n t.on(\"url\", (url: string) => {\n console.log(`[ccweb] Tunnel: ${url}`);\n });\n t.on(\"stdout\", (output: string) => {\n const line = output.trim();\n if (line) console.log(`[ccweb] Tunnel stdout: ${line}`);\n });\n t.on(\"stderr\", (output: string) => {\n const line = output.trim();\n if (line) console.log(`[ccweb] Tunnel stderr: ${line}`);\n });\n t.on(\"error\", (err: Error) => {\n console.error(`[ccweb] Tunnel error: ${err.message}`);\n });\n t.on(\"exit\", (code: number | null, signal: string | null) => {\n console.log(`[ccweb] Tunnel exited with code=${code} signal=${signal}`);\n });\n const stopTunnel = () => t.stop();\n process.on(\"SIGINT\", stopTunnel);\n process.on(\"SIGTERM\", stopTunnel);\n }).catch((err) => {\n console.error(\"[ccweb] Failed to start tunnel:\", err.message);\n });\n }\n\n // Prevent system sleep while ccweb is running\n const platform = os.platform();\n if (platform === \"darwin\") {\n const caffeinate = spawn(\"caffeinate\", [\"-s\", \"-w\", process.pid.toString()], {\n stdio: \"ignore\",\n });\n caffeinate.unref();\n console.log(\"[ccweb] Sleep prevention enabled (caffeinate)\");\n } else if (platform === \"win32\") {\n const script = `\nAdd-Type -TypeDefinition @\"\nusing System.Runtime.InteropServices;\npublic class Power {\n [DllImport(\"kernel32.dll\")]\n public static extern uint SetThreadExecutionState(uint esFlags);\n}\n\"@\nwhile($true) {\n [Power]::SetThreadExecutionState(0x80000001)\n Start-Sleep -Seconds 30\n}`;\n const ps = spawn(\"powershell\", [\"-NoProfile\", \"-Command\", script], {\n stdio: \"ignore\",\n });\n ps.unref();\n console.log(\"[ccweb] Sleep prevention enabled (SetThreadExecutionState)\");\n }\n });\n\n const shutdown = () => {\n console.log(\"\\n[ccweb] Shutting down...\");\n wss.clients.forEach((client) => client.close());\n sessionManager.destroyAll();\n server.close(() => process.exit(0));\n setTimeout(() => process.exit(1), 3000);\n };\n\n process.on(\"SIGINT\", shutdown);\n process.on(\"SIGTERM\", shutdown);\n}\n","import crypto from \"crypto\";\nimport { Terminal } from \"./terminal\";\n\nexport interface Session {\n id: string;\n terminal: Terminal;\n buffer: string[];\n clients: Set<(data: string) => void>;\n onExit: Set<() => void>;\n alive: boolean;\n}\n\nconst MAX_BUFFER = 5000;\n\nexport class SessionManager {\n private sessions = new Map<string, Session>();\n private order: string[] = [];\n\n create(shell: string, cols: number, rows: number): Session {\n const id = crypto.randomUUID();\n const terminal = new Terminal({ shell, cols, rows });\n\n const session: Session = {\n id,\n terminal,\n buffer: [],\n clients: new Set(),\n onExit: new Set(),\n alive: true,\n };\n\n terminal.onData((data) => {\n if (session.buffer.length >= MAX_BUFFER) {\n session.buffer.shift();\n }\n session.buffer.push(data);\n for (const cb of session.clients) {\n cb(data);\n }\n });\n\n terminal.onExit(() => {\n session.alive = false;\n for (const cb of session.onExit) {\n cb();\n }\n });\n\n this.sessions.set(id, session);\n this.order.push(id);\n return session;\n }\n\n get(id: string): Session | undefined {\n const session = this.sessions.get(id);\n if (session && !session.alive) {\n this.sessions.delete(id);\n this.order = this.order.filter((x) => x !== id);\n return undefined;\n }\n return session;\n }\n\n listIds(): string[] {\n this.order = this.order.filter((id) => {\n const s = this.sessions.get(id);\n if (s && s.alive) return true;\n this.sessions.delete(id);\n return false;\n });\n return [...this.order];\n }\n\n remove(id: string): void {\n const session = this.sessions.get(id);\n if (session) {\n session.terminal.kill();\n this.sessions.delete(id);\n }\n this.order = this.order.filter((x) => x !== id);\n }\n\n destroyAll(): void {\n for (const session of this.sessions.values()) {\n session.terminal.kill();\n }\n this.sessions.clear();\n this.order = [];\n }\n}\n","import * as pty from \"node-pty\";\nimport os from \"os\";\n\nexport interface TerminalOptions {\n shell: string;\n cols?: number;\n rows?: number;\n cwd?: string;\n}\n\nexport class Terminal {\n private process: pty.IPty;\n private disposed = false;\n\n constructor(options: TerminalOptions) {\n const { shell, cols = 80, rows = 24, cwd } = options;\n const env = Object.assign({}, process.env, {\n TERM: \"xterm-256color\",\n COLORTERM: \"truecolor\",\n FORCE_COLOR: \"3\",\n TERM_PROGRAM: \"xterm-256color\",\n });\n delete (env as Record<string, string | undefined>).NO_COLOR;\n\n this.process = pty.spawn(shell, [], {\n name: \"xterm-256color\",\n cols,\n rows,\n cwd: cwd || os.homedir(),\n env: env as Record<string, string>,\n });\n }\n\n get pid(): number {\n return this.process.pid;\n }\n\n onData(callback: (data: string) => void): void {\n this.process.onData(callback);\n }\n\n onExit(callback: (exitCode: number, signal?: number) => void): void {\n this.process.onExit(({ exitCode, signal }) => {\n callback(exitCode, signal);\n });\n }\n\n write(data: string): void {\n if (!this.disposed) {\n this.process.write(data);\n }\n }\n\n resize(cols: number, rows: number): void {\n if (!this.disposed) {\n try {\n this.process.resize(cols, rows);\n } catch {\n // PTY may already be closed\n }\n }\n }\n\n kill(): void {\n if (!this.disposed) {\n this.disposed = true;\n try {\n this.process.kill();\n } catch {\n // Already dead\n }\n }\n }\n}\n","import https from \"https\";\nimport { execSync } from \"child_process\";\n\ndeclare const __VERSION__: string;\nconst PKG_NAME = \"ccwebtty\";\n\nfunction fetchLatestVersion(): Promise<string> {\n return new Promise((resolve, reject) => {\n https\n .get(`https://registry.npmjs.org/${PKG_NAME}/latest`, (res) => {\n let data = \"\";\n res.on(\"data\", (chunk) => (data += chunk));\n res.on(\"end\", () => {\n try {\n resolve(JSON.parse(data).version);\n } catch {\n reject(new Error(\"Failed to parse registry response\"));\n }\n });\n })\n .on(\"error\", reject);\n });\n}\n\nexport async function checkUpdate(): Promise<void> {\n try {\n const latest = await fetchLatestVersion();\n if (latest !== __VERSION__) {\n console.log(\n `\\n New version available: ${__VERSION__} -> \\x1b[32m${latest}\\x1b[0m`\n );\n console.log(` Run \\x1b[36mccweb update\\x1b[0m to update\\n`);\n }\n } catch {\n // silently ignore network errors\n }\n}\n\nexport async function selfUpdate(): Promise<void> {\n try {\n const latest = await fetchLatestVersion();\n if (latest === __VERSION__) {\n console.log(`Already up to date (v${__VERSION__})`);\n return;\n }\n console.log(`Updating ${PKG_NAME}: ${__VERSION__} -> ${latest} ...`);\n execSync(`npm install -g ${PKG_NAME}@latest`, { stdio: \"inherit\" });\n console.log(`Successfully updated to v${latest}`);\n } catch (err: any) {\n console.error(`Update failed: ${err.message}`);\n process.exit(1);\n }\n}\n"],"mappings":"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","names":["import_crypto","import_crypto","import_os","os","crypto","crypto","express","http","path","session","os","import_child_process","https","crypto"]}
|