@ghostty-web/demo 0.2.1-next.12.g5d6bd7b → 0.2.1-next.13.g7b2dd99

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 (2) hide show
  1. package/bin/demo.js +42 -154
  2. package/package.json +3 -2
package/bin/demo.js CHANGED
@@ -7,7 +7,6 @@
7
7
  * Run with: npx @ghostty-web/demo
8
8
  */
9
9
 
10
- import crypto from 'crypto';
11
10
  import fs from 'fs';
12
11
  import http from 'http';
13
12
  import { homedir } from 'os';
@@ -16,6 +15,8 @@ import { fileURLToPath } from 'url';
16
15
 
17
16
  // Node-pty for cross-platform PTY support
18
17
  import pty from '@lydell/node-pty';
18
+ // WebSocket server
19
+ import { WebSocketServer } from 'ws';
19
20
 
20
21
  const __filename = fileURLToPath(import.meta.url);
21
22
  const __dirname = path.dirname(__filename);
@@ -349,7 +350,7 @@ function serveFile(filePath, res) {
349
350
  }
350
351
 
351
352
  // ============================================================================
352
- // WebSocket Server (using native WebSocket upgrade)
353
+ // WebSocket Server (using ws package)
353
354
  // ============================================================================
354
355
 
355
356
  const sessions = new Map();
@@ -380,196 +381,85 @@ function createPtySession(cols, rows) {
380
381
  return ptyProcess;
381
382
  }
382
383
 
383
- // WebSocket server
384
- const wsServer = http.createServer();
384
+ // WebSocket server using ws package
385
+ const wss = new WebSocketServer({ port: WS_PORT, path: '/ws' });
385
386
 
386
- wsServer.on('upgrade', (req, socket, head) => {
387
+ wss.on('connection', (ws, req) => {
387
388
  const url = new URL(req.url, `http://${req.headers.host}`);
388
-
389
- if (url.pathname !== '/ws') {
390
- socket.destroy();
391
- return;
392
- }
393
-
394
389
  const cols = Number.parseInt(url.searchParams.get('cols') || '80');
395
390
  const rows = Number.parseInt(url.searchParams.get('rows') || '24');
396
391
 
397
- // Parse WebSocket key and create accept key
398
- const key = req.headers['sec-websocket-key'];
399
- const acceptKey = crypto
400
- .createHash('sha1')
401
- .update(key + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11')
402
- .digest('base64');
403
-
404
- // Send WebSocket handshake response
405
- socket.write(
406
- 'HTTP/1.1 101 Switching Protocols\r\n' +
407
- 'Upgrade: websocket\r\n' +
408
- 'Connection: Upgrade\r\n' +
409
- 'Sec-WebSocket-Accept: ' +
410
- acceptKey +
411
- '\r\n\r\n'
412
- );
413
-
414
- const sessionId = crypto.randomUUID().slice(0, 8);
415
-
416
392
  // Create PTY
417
393
  const ptyProcess = createPtySession(cols, rows);
418
- sessions.set(socket, { id: sessionId, pty: ptyProcess });
394
+ sessions.set(ws, { pty: ptyProcess });
419
395
 
420
396
  // PTY -> WebSocket
421
397
  ptyProcess.onData((data) => {
422
- if (socket.writable) {
423
- sendWebSocketFrame(socket, data);
398
+ if (ws.readyState === ws.OPEN) {
399
+ ws.send(data);
424
400
  }
425
401
  });
426
402
 
427
403
  ptyProcess.onExit(({ exitCode }) => {
428
- sendWebSocketFrame(socket, `\r\n\x1b[33mShell exited (code: ${exitCode})\x1b[0m\r\n`);
429
- socket.end();
404
+ if (ws.readyState === ws.OPEN) {
405
+ ws.send(`\r\n\x1b[33mShell exited (code: ${exitCode})\x1b[0m\r\n`);
406
+ ws.close();
407
+ }
430
408
  });
431
409
 
432
410
  // WebSocket -> PTY
433
- let buffer = Buffer.alloc(0);
434
-
435
- socket.on('data', (chunk) => {
436
- buffer = Buffer.concat([buffer, chunk]);
437
-
438
- while (buffer.length >= 2) {
439
- const fin = (buffer[0] & 0x80) !== 0;
440
- const opcode = buffer[0] & 0x0f;
441
- const masked = (buffer[1] & 0x80) !== 0;
442
- let payloadLength = buffer[1] & 0x7f;
443
-
444
- let offset = 2;
445
-
446
- if (payloadLength === 126) {
447
- if (buffer.length < 4) break;
448
- payloadLength = buffer.readUInt16BE(2);
449
- offset = 4;
450
- } else if (payloadLength === 127) {
451
- if (buffer.length < 10) break;
452
- payloadLength = Number(buffer.readBigUInt64BE(2));
453
- offset = 10;
454
- }
455
-
456
- const maskKeyOffset = offset;
457
- if (masked) offset += 4;
458
-
459
- const totalLength = offset + payloadLength;
460
- if (buffer.length < totalLength) break;
461
-
462
- // Handle different opcodes
463
- if (opcode === 0x8) {
464
- // Close frame
465
- socket.end();
466
- break;
467
- }
468
-
469
- if (opcode === 0x1 || opcode === 0x2) {
470
- // Text or binary frame
471
- let payload = buffer.slice(offset, totalLength);
472
-
473
- if (masked) {
474
- const maskKey = buffer.slice(maskKeyOffset, maskKeyOffset + 4);
475
- payload = Buffer.from(payload);
476
- for (let i = 0; i < payload.length; i++) {
477
- payload[i] ^= maskKey[i % 4];
478
- }
411
+ ws.on('message', (data) => {
412
+ const message = data.toString('utf8');
413
+
414
+ // Check for resize message
415
+ if (message.startsWith('{')) {
416
+ try {
417
+ const msg = JSON.parse(message);
418
+ if (msg.type === 'resize') {
419
+ ptyProcess.resize(msg.cols, msg.rows);
420
+ return;
479
421
  }
480
-
481
- const data = payload.toString('utf8');
482
-
483
- // Check for resize message
484
- if (data.startsWith('{')) {
485
- try {
486
- const msg = JSON.parse(data);
487
- if (msg.type === 'resize') {
488
- ptyProcess.resize(msg.cols, msg.rows);
489
- buffer = buffer.slice(totalLength);
490
- continue;
491
- }
492
- } catch (e) {
493
- // Not JSON, treat as input
494
- }
495
- }
496
-
497
- // Send to PTY
498
- ptyProcess.write(data);
422
+ } catch (e) {
423
+ // Not JSON, treat as input
499
424
  }
500
-
501
- buffer = buffer.slice(totalLength);
502
425
  }
426
+
427
+ // Send to PTY
428
+ ptyProcess.write(message);
503
429
  });
504
430
 
505
- socket.on('close', () => {
506
- const session = sessions.get(socket);
431
+ ws.on('close', () => {
432
+ const session = sessions.get(ws);
507
433
  if (session) {
508
434
  session.pty.kill();
509
- sessions.delete(socket);
435
+ sessions.delete(ws);
510
436
  }
511
437
  });
512
438
 
513
- socket.on('error', () => {
439
+ ws.on('error', () => {
514
440
  // Ignore socket errors (connection reset, etc.)
515
441
  });
516
442
 
517
443
  // Send welcome message
518
444
  setTimeout(() => {
445
+ if (ws.readyState !== ws.OPEN) return;
519
446
  const C = '\x1b[1;36m'; // Cyan
520
447
  const G = '\x1b[1;32m'; // Green
521
448
  const Y = '\x1b[1;33m'; // Yellow
522
449
  const R = '\x1b[0m'; // Reset
523
- sendWebSocketFrame(
524
- socket,
525
- `${C}╔══════════════════════════════════════════════════════════════╗${R}\r\n`
526
- );
527
- sendWebSocketFrame(
528
- socket,
450
+ ws.send(`${C}╔══════════════════════════════════════════════════════════════╗${R}\r\n`);
451
+ ws.send(
529
452
  `${C}║${R} ${G}Welcome to ghostty-web!${R} ${C}║${R}\r\n`
530
453
  );
531
- sendWebSocketFrame(
532
- socket,
533
- `${C}║${R} ${C}║${R}\r\n`
534
- );
535
- sendWebSocketFrame(
536
- socket,
537
- `${C}║${R} You have a real shell session with full PTY support. ${C}║${R}\r\n`
538
- );
539
- sendWebSocketFrame(
540
- socket,
454
+ ws.send(`${C}║${R} ${C}║${R}\r\n`);
455
+ ws.send(`${C}║${R} You have a real shell session with full PTY support. ${C}║${R}\r\n`);
456
+ ws.send(
541
457
  `${C}║${R} Try: ${Y}ls${R}, ${Y}cd${R}, ${Y}top${R}, ${Y}vim${R}, or any command! ${C}║${R}\r\n`
542
458
  );
543
- sendWebSocketFrame(
544
- socket,
545
- `${C}╚══════════════════════════════════════════════════════════════╝${R}\r\n\r\n`
546
- );
459
+ ws.send(`${C}╚══════════════════════════════════════════════════════════════╝${R}\r\n\r\n`);
547
460
  }, 100);
548
461
  });
549
462
 
550
- function sendWebSocketFrame(socket, data) {
551
- const payload = Buffer.from(data, 'utf8');
552
- let header;
553
-
554
- if (payload.length < 126) {
555
- header = Buffer.alloc(2);
556
- header[0] = 0x81; // FIN + text frame
557
- header[1] = payload.length;
558
- } else if (payload.length < 65536) {
559
- header = Buffer.alloc(4);
560
- header[0] = 0x81;
561
- header[1] = 126;
562
- header.writeUInt16BE(payload.length, 2);
563
- } else {
564
- header = Buffer.alloc(10);
565
- header[0] = 0x81;
566
- header[1] = 127;
567
- header.writeBigUInt64BE(BigInt(payload.length), 2);
568
- }
569
-
570
- socket.write(Buffer.concat([header, payload]));
571
- }
572
-
573
463
  // ============================================================================
574
464
  // Startup
575
465
  // ============================================================================
@@ -596,16 +486,14 @@ function printBanner(url) {
596
486
  // Graceful shutdown
597
487
  process.on('SIGINT', () => {
598
488
  console.log('\n\nShutting down...');
599
- for (const [socket, session] of sessions.entries()) {
489
+ for (const [ws, session] of sessions.entries()) {
600
490
  session.pty.kill();
601
- socket.destroy();
491
+ ws.close();
602
492
  }
493
+ wss.close();
603
494
  process.exit(0);
604
495
  });
605
496
 
606
- // Start WebSocket PTY server (runs in both modes)
607
- wsServer.listen(WS_PORT);
608
-
609
497
  // Start HTTP/Vite server
610
498
  if (DEV_MODE) {
611
499
  // Dev mode: use Vite for hot reload
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ghostty-web/demo",
3
- "version": "0.2.1-next.12.g5d6bd7b",
3
+ "version": "0.2.1-next.13.g7b2dd99",
4
4
  "description": "Cross-platform demo server for ghostty-web terminal emulator",
5
5
  "type": "module",
6
6
  "bin": {
@@ -12,7 +12,8 @@
12
12
  },
13
13
  "dependencies": {
14
14
  "@lydell/node-pty": "^1.0.1",
15
- "ghostty-web": "next"
15
+ "ghostty-web": "next",
16
+ "ws": "^8.18.0"
16
17
  },
17
18
  "files": [
18
19
  "bin",