@ghostty-web/demo 0.3.0-next.9.g59bb045 → 0.4.0-next.0.g9e4e126

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.
Files changed (3) hide show
  1. package/README.md +45 -1
  2. package/bin/demo.js +62 -23
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -14,9 +14,10 @@ Works on **Linux** and **macOS** (no Windows support yet).
14
14
  ## What it does
15
15
 
16
16
  - Starts an HTTP server on port 8080 (configurable via `PORT` env var)
17
- - Starts a WebSocket server on port 3001 for PTY communication
17
+ - Serves WebSocket PTY on the same port at `/ws` endpoint
18
18
  - Opens a real shell session (bash, zsh, etc.)
19
19
  - Provides full PTY support (colors, cursor positioning, resize, etc.)
20
+ - Supports reverse proxies (ngrok, nginx, etc.) via X-Forwarded-\* headers
20
21
 
21
22
  ## Usage
22
23
 
@@ -30,6 +31,49 @@ PORT=3000 npx @ghostty-web/demo@next
30
31
 
31
32
  Then open http://localhost:8080 in your browser.
32
33
 
34
+ ## Reverse Proxy Support
35
+
36
+ The server now supports reverse proxies like ngrok, nginx, and others by:
37
+
38
+ - Serving WebSocket on the same HTTP port (no separate port needed)
39
+ - Using relative WebSocket URLs on the client side
40
+ - Automatic protocol detection (HTTP/HTTPS, WS/WSS)
41
+
42
+ This means the WebSocket connection automatically adapts to use the same protocol and host as the HTTP connection, making it work seamlessly through any reverse proxy.
43
+
44
+ ### Example with ngrok
45
+
46
+ ```bash
47
+ # Start the demo server
48
+ npx @ghostty-web/demo@next
49
+
50
+ # In another terminal, expose it via ngrok
51
+ ngrok http 8080
52
+ ```
53
+
54
+ The terminal will work seamlessly through the ngrok URL! Both HTTP and WebSocket traffic will be properly proxied.
55
+
56
+ ### Example with nginx
57
+
58
+ ```nginx
59
+ server {
60
+ listen 80;
61
+ server_name example.com;
62
+
63
+ location / {
64
+ proxy_pass http://localhost:8080;
65
+ proxy_http_version 1.1;
66
+ proxy_set_header Upgrade $http_upgrade;
67
+ proxy_set_header Connection "upgrade";
68
+ proxy_set_header Host $host;
69
+ proxy_set_header X-Forwarded-Host $host;
70
+ proxy_set_header X-Forwarded-Proto $scheme;
71
+ proxy_set_header X-Real-IP $remote_addr;
72
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
73
+ }
74
+ }
75
+ ```
76
+
33
77
  ## Security Warning
34
78
 
35
79
  ⚠️ **This server provides full shell access.**
package/bin/demo.js CHANGED
@@ -23,7 +23,6 @@ const __dirname = path.dirname(__filename);
23
23
 
24
24
  const DEV_MODE = process.argv.includes('--dev');
25
25
  const HTTP_PORT = process.env.PORT || (DEV_MODE ? 8000 : 8080);
26
- const WS_PORT = 3001;
27
26
 
28
27
  // ============================================================================
29
28
  // Locate ghostty-web assets
@@ -239,8 +238,9 @@ const HTML_TEMPLATE = `<!doctype html>
239
238
  statusText.textContent = text;
240
239
  }
241
240
 
242
- // Connect to WebSocket PTY server
243
- const wsUrl = 'ws://' + window.location.hostname + ':${WS_PORT}/ws?cols=' + term.cols + '&rows=' + term.rows;
241
+ // Connect to WebSocket PTY server (use same origin as HTTP server)
242
+ const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
243
+ const wsUrl = protocol + '//' + window.location.host + '/ws?cols=' + term.cols + '&rows=' + term.rows;
244
244
  let ws;
245
245
 
246
246
  function connect() {
@@ -413,8 +413,23 @@ function createPtySession(cols, rows) {
413
413
  return ptyProcess;
414
414
  }
415
415
 
416
- // WebSocket server using ws package
417
- const wss = new WebSocketServer({ port: WS_PORT, path: '/ws' });
416
+ // WebSocket server attached to HTTP server (same port)
417
+ const wss = new WebSocketServer({ noServer: true });
418
+
419
+ // Handle HTTP upgrade for WebSocket connections
420
+ httpServer.on('upgrade', (req, socket, head) => {
421
+ const url = new URL(req.url, `http://${req.headers.host}`);
422
+
423
+ if (url.pathname === '/ws') {
424
+ // In production, consider validating req.headers.origin to prevent CSRF
425
+ // For development/demo purposes, we allow all origins
426
+ wss.handleUpgrade(req, socket, head, (ws) => {
427
+ wss.emit('connection', ws, req);
428
+ });
429
+ } else {
430
+ socket.destroy();
431
+ }
432
+ });
418
433
 
419
434
  wss.on('connection', (ws, req) => {
420
435
  const url = new URL(req.url, `http://${req.headers.host}`);
@@ -473,23 +488,20 @@ wss.on('connection', (ws, req) => {
473
488
  });
474
489
 
475
490
  // Send welcome message
476
- setTimeout(() => {
477
- if (ws.readyState !== ws.OPEN) return;
478
- const C = '\x1b[1;36m'; // Cyan
479
- const G = '\x1b[1;32m'; // Green
480
- const Y = '\x1b[1;33m'; // Yellow
481
- const R = '\x1b[0m'; // Reset
482
- ws.send(`${C}╔══════════════════════════════════════════════════════════════╗${R}\r\n`);
483
- ws.send(
484
- `${C}║${R} ${G}Welcome to ghostty-web!${R} ${C}║${R}\r\n`
485
- );
486
- ws.send(`${C}║${R} ${C}║${R}\r\n`);
487
- ws.send(`${C}║${R} You have a real shell session with full PTY support. ${C}║${R}\r\n`);
488
- ws.send(
489
- `${C}║${R} Try: ${Y}ls${R}, ${Y}cd${R}, ${Y}top${R}, ${Y}vim${R}, or any command! ${C}║${R}\r\n`
490
- );
491
- ws.send(`${C}╚══════════════════════════════════════════════════════════════╝${R}\r\n\r\n`);
492
- }, 100);
491
+ const C = '\x1b[1;36m'; // Cyan
492
+ const G = '\x1b[1;32m'; // Green
493
+ const Y = '\x1b[1;33m'; // Yellow
494
+ const R = '\x1b[0m'; // Reset
495
+ ws.send(`${C}╔══════════════════════════════════════════════════════════════╗${R}\r\n`);
496
+ ws.send(
497
+ `${C}║${R} ${G}Welcome to ghostty-web!${R} ${C}║${R}\r\n`
498
+ );
499
+ ws.send(`${C}║${R} ${C}║${R}\r\n`);
500
+ ws.send(`${C}║${R} You have a real shell session with full PTY support. ${C}║${R}\r\n`);
501
+ ws.send(
502
+ `${C}║${R} Try: ${Y}ls${R}, ${Y}cd${R}, ${Y}top${R}, ${Y}vim${R}, or any command! ${C}║${R}\r\n`
503
+ );
504
+ ws.send(`${C}╚══════════════════════════════════════════════════════════════╝${R}\r\n\r\n`);
493
505
  });
494
506
 
495
507
  // ============================================================================
@@ -501,7 +513,7 @@ function printBanner(url) {
501
513
  console.log(' 🚀 ghostty-web demo server' + (DEV_MODE ? ' (dev mode)' : ''));
502
514
  console.log('═'.repeat(60));
503
515
  console.log(`\n 📺 Open: ${url}`);
504
- console.log(` 📡 WebSocket PTY: ws://localhost:${WS_PORT}/ws`);
516
+ console.log(` 📡 WebSocket PTY: same endpoint /ws`);
505
517
  console.log(` 🐚 Shell: ${getShell()}`);
506
518
  console.log(` 📁 Home: ${homedir()}`);
507
519
  if (DEV_MODE) {
@@ -537,7 +549,34 @@ if (DEV_MODE) {
537
549
  strictPort: true,
538
550
  },
539
551
  });
552
+
540
553
  await vite.listen();
554
+
555
+ // Attach WebSocket handler AFTER Vite has fully initialized
556
+ // Use prependListener (not prependOnceListener) so it runs for every request
557
+ // This ensures our handler runs BEFORE Vite's handlers
558
+ if (vite.httpServer) {
559
+ vite.httpServer.prependListener('upgrade', (req, socket, head) => {
560
+ const pathname = req.url?.split('?')[0] || req.url || '';
561
+
562
+ // ONLY handle /ws - everything else passes through unchanged to Vite
563
+ if (pathname === '/ws') {
564
+ if (!socket.destroyed && !socket.readableEnded) {
565
+ wss.handleUpgrade(req, socket, head, (ws) => {
566
+ wss.emit('connection', ws, req);
567
+ });
568
+ }
569
+ // Stop here - we handled it, socket is consumed
570
+ // Don't call other listeners
571
+ return;
572
+ }
573
+
574
+ // For non-/ws paths, explicitly do nothing and let the event propagate
575
+ // The key is: don't return, don't touch the socket, just let it pass through
576
+ // Vite's handlers (which were added before ours via prependListener) will process it
577
+ });
578
+ }
579
+
541
580
  printBanner(`http://localhost:${HTTP_PORT}/demo/`);
542
581
  } else {
543
582
  // Production mode: static file server
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ghostty-web/demo",
3
- "version": "0.3.0-next.9.g59bb045",
3
+ "version": "0.4.0-next.0.g9e4e126",
4
4
  "description": "Cross-platform demo server for ghostty-web terminal emulator",
5
5
  "type": "module",
6
6
  "bin": {
@@ -12,7 +12,7 @@
12
12
  },
13
13
  "dependencies": {
14
14
  "@lydell/node-pty": "^1.0.1",
15
- "ghostty-web": "0.3.0-next.9.g59bb045",
15
+ "ghostty-web": "0.4.0-next.0.g9e4e126",
16
16
  "ws": "^8.18.0"
17
17
  },
18
18
  "files": [