webtty 0.0.0-development → 1.1.0

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.
@@ -0,0 +1,680 @@
1
+ #!/usr/bin/env node
2
+ import { createRequire } from "node:module";
3
+ var __create = Object.create;
4
+ var __getProtoOf = Object.getPrototypeOf;
5
+ var __defProp = Object.defineProperty;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ function __accessProp(key) {
9
+ return this[key];
10
+ }
11
+ var __toESMCache_node;
12
+ var __toESMCache_esm;
13
+ var __toESM = (mod, isNodeMode, target) => {
14
+ var canCache = mod != null && typeof mod === "object";
15
+ if (canCache) {
16
+ var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
17
+ var cached = cache.get(mod);
18
+ if (cached)
19
+ return cached;
20
+ }
21
+ target = mod != null ? __create(__getProtoOf(mod)) : {};
22
+ const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
23
+ for (let key of __getOwnPropNames(mod))
24
+ if (!__hasOwnProp.call(to, key))
25
+ __defProp(to, key, {
26
+ get: __accessProp.bind(mod, key),
27
+ enumerable: true
28
+ });
29
+ if (canCache)
30
+ cache.set(mod, to);
31
+ return to;
32
+ };
33
+ var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
34
+ var __returnValue = (v) => v;
35
+ function __exportSetter(name, newValue) {
36
+ this[name] = __returnValue.bind(null, newValue);
37
+ }
38
+ var __export = (target, all) => {
39
+ for (var name in all)
40
+ __defProp(target, name, {
41
+ get: all[name],
42
+ enumerable: true,
43
+ configurable: true,
44
+ set: __exportSetter.bind(all, name)
45
+ });
46
+ };
47
+ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
48
+ var __require = /* @__PURE__ */ createRequire(import.meta.url);
49
+
50
+ // src/pty/bun.ts
51
+ var exports_bun = {};
52
+ __export(exports_bun, {
53
+ spawn: () => spawn
54
+ });
55
+ import { homedir } from "node:os";
56
+ function spawn(shell, cols, rows) {
57
+ let onDataCb;
58
+ let onExitCb;
59
+ const proc = Bun.spawn([shell], {
60
+ terminal: {
61
+ cols,
62
+ rows,
63
+ data(_term, data) {
64
+ onDataCb?.(Buffer.from(data).toString("utf8"));
65
+ }
66
+ },
67
+ cwd: homedir(),
68
+ env: { ...process.env, TERM: "xterm-256color", COLORTERM: "truecolor" }
69
+ });
70
+ proc.exited.then((exitCode) => {
71
+ onExitCb?.({ exitCode: exitCode ?? 0 });
72
+ });
73
+ return {
74
+ onData(cb) {
75
+ onDataCb = cb;
76
+ },
77
+ onExit(cb) {
78
+ onExitCb = cb;
79
+ },
80
+ write(data) {
81
+ proc.terminal?.write(data);
82
+ },
83
+ resize(cols2, rows2) {
84
+ proc.terminal?.resize(cols2, rows2);
85
+ },
86
+ kill() {
87
+ proc.kill();
88
+ }
89
+ };
90
+ }
91
+ var init_bun = () => {};
92
+
93
+ // src/pty/node.ts
94
+ var exports_node = {};
95
+ __export(exports_node, {
96
+ spawn: () => spawn2
97
+ });
98
+ import { homedir as homedir2 } from "node:os";
99
+ import nodePty from "@lydell/node-pty";
100
+ function spawn2(shell, cols, rows) {
101
+ const ptyProc = nodePty.spawn(shell, [], {
102
+ name: "xterm-256color",
103
+ cols,
104
+ rows,
105
+ cwd: homedir2(),
106
+ env: { ...process.env, TERM: "xterm-256color", COLORTERM: "truecolor" }
107
+ });
108
+ return {
109
+ onData(cb) {
110
+ ptyProc.onData(cb);
111
+ },
112
+ onExit(cb) {
113
+ ptyProc.onExit(cb);
114
+ },
115
+ write(data) {
116
+ ptyProc.write(data);
117
+ },
118
+ resize(cols2, rows2) {
119
+ ptyProc.resize(cols2, rows2);
120
+ },
121
+ kill() {
122
+ ptyProc.kill();
123
+ }
124
+ };
125
+ }
126
+ var init_node = () => {};
127
+
128
+ // src/server/index.ts
129
+ import http from "node:http";
130
+
131
+ // src/server/routes.ts
132
+ import path3 from "node:path";
133
+
134
+ // src/server/client.ts
135
+ function render(sessionId) {
136
+ return `<!doctype html>
137
+ <html lang="en">
138
+ <head>
139
+ <meta charset="UTF-8" />
140
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
141
+ <title>webtty — ${sessionId}</title>
142
+ <style>
143
+ * { margin: 0; padding: 0; box-sizing: border-box; }
144
+
145
+ html, body, #terminal { width: 100%; height: 100%; overflow: hidden; background: #282A36; }
146
+
147
+ #terminal canvas { display: block; }
148
+ </style>
149
+ </head>
150
+ <body>
151
+ <div id="terminal"></div>
152
+
153
+ <script type="module">
154
+ import { init, Terminal, FitAddon } from '/dist/ghostty-web.js';
155
+
156
+ await init();
157
+ const term = new Terminal({
158
+ cols: 80,
159
+ rows: 24,
160
+ cursorBlink: true,
161
+ fontSize: 14,
162
+ fontFamily: "'FiraMono Nerd Font', Menlo, Monaco, 'Courier New', monospace",
163
+ scrollback: 10000,
164
+ theme: {
165
+ background: '#282A36',
166
+ foreground: '#F8F8F2',
167
+ cursor: '#F8F8F2',
168
+ selection: '#44475A',
169
+ black: '#21222C',
170
+ red: '#FF5555',
171
+ green: '#50FA7B',
172
+ yellow: '#F1FA8C',
173
+ blue: '#BD93F9',
174
+ purple: '#FF79C6',
175
+ cyan: '#8BE9FD',
176
+ white: '#F8F8F2',
177
+ brightBlack: '#6272A4',
178
+ brightRed: '#FF6E6E',
179
+ brightGreen: '#69FF94',
180
+ brightYellow: '#FFFFA5',
181
+ brightBlue: '#D6ACFF',
182
+ brightPurple: '#FF92DF',
183
+ brightCyan: '#A4FFFF',
184
+ brightWhite: '#FFFFFF',
185
+ },
186
+ });
187
+
188
+ const fitAddon = new FitAddon();
189
+ term.loadAddon(fitAddon);
190
+
191
+ const container = document.getElementById('terminal');
192
+ await term.open(container);
193
+ fitAddon.fit();
194
+ fitAddon.observeResize();
195
+
196
+ const sessionId = ${JSON.stringify(sessionId)};
197
+ const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
198
+ let ws;
199
+
200
+ function connect() {
201
+ const wsUrl = protocol + '//' + window.location.host + '/ws/' + sessionId + '?cols=' + term.cols + '&rows=' + term.rows;
202
+ ws = new WebSocket(wsUrl);
203
+
204
+ ws.onopen = () => {
205
+ console.log('[webtty] connected to session ' + sessionId);
206
+ ws.send(JSON.stringify({ type: 'resize', cols: term.cols, rows: term.rows }));
207
+ };
208
+
209
+ ws.onmessage = (event) => {
210
+ term.write(event.data);
211
+ };
212
+
213
+ ws.onclose = (event) => {
214
+ if (event.code === 4001) {
215
+ term.write('\\r\\n\\x1b[31mSession removed.\\x1b[0m\\r\\n');
216
+ setTimeout(() => window.close(), 500);
217
+ return;
218
+ }
219
+ console.log('[webtty] disconnected, reconnecting in 2s...');
220
+ term.write('\\r\\n\\x1b[31mConnection closed. Reconnecting in 2s...\\x1b[0m\\r\\n');
221
+ setTimeout(connect, 2000);
222
+ };
223
+
224
+ ws.onerror = () => {
225
+ console.error('[webtty] websocket error');
226
+ };
227
+ }
228
+
229
+ connect();
230
+
231
+ term.onData((data) => {
232
+ if (ws && ws.readyState === WebSocket.OPEN) {
233
+ ws.send(data);
234
+ }
235
+ });
236
+
237
+ term.onResize(({ cols, rows }) => {
238
+ if (ws && ws.readyState === WebSocket.OPEN) {
239
+ ws.send(JSON.stringify({ type: 'resize', cols, rows }));
240
+ }
241
+ });
242
+
243
+ window.addEventListener('resize', () => {
244
+ fitAddon.fit();
245
+ });
246
+ </script>
247
+ </body>
248
+ </html>`;
249
+ }
250
+
251
+ // src/server/session.ts
252
+ var SCROLLBACK_MAX = 256 * 1024;
253
+ var sessionRegistry = new Map;
254
+ var lastUsedId = null;
255
+ function setLastUsedId(id) {
256
+ lastUsedId = id;
257
+ }
258
+ var ID_RE = /^[a-z0-9\-_.]{1,64}$/;
259
+ function isValidId(id) {
260
+ return ID_RE.test(id);
261
+ }
262
+ function generateId() {
263
+ return Math.floor(Math.random() * 4294967295).toString(16).padStart(8, "0");
264
+ }
265
+ function createSession(id) {
266
+ const session = {
267
+ id,
268
+ createdAt: Date.now(),
269
+ pty: null,
270
+ clients: new Set,
271
+ scrollback: ""
272
+ };
273
+ sessionRegistry.set(id, session);
274
+ return session;
275
+ }
276
+ function sessionToJson(s) {
277
+ return { id: s.id, createdAt: s.createdAt, connected: s.clients.size > 0 };
278
+ }
279
+
280
+ // src/server/static.ts
281
+ import fs from "node:fs";
282
+ import { createRequire as createRequire2 } from "node:module";
283
+ import path2 from "node:path";
284
+ import { fileURLToPath } from "node:url";
285
+
286
+ // src/utils.ts
287
+ import path from "node:path";
288
+ var MIME_TYPES = {
289
+ ".html": "text/html",
290
+ ".js": "application/javascript",
291
+ ".mjs": "application/javascript",
292
+ ".css": "text/css",
293
+ ".json": "application/json",
294
+ ".wasm": "application/wasm",
295
+ ".png": "image/png",
296
+ ".svg": "image/svg+xml",
297
+ ".ico": "image/x-icon"
298
+ };
299
+ function mimeType(filePath) {
300
+ const ext = path.extname(filePath);
301
+ return MIME_TYPES[ext] ?? "application/octet-stream";
302
+ }
303
+ function ghosttyWebRootFromMain(mainPath) {
304
+ return mainPath.replace(/[/\\]dist[/\\].*$/, "");
305
+ }
306
+
307
+ // src/server/static.ts
308
+ var __filename2 = fileURLToPath(import.meta.url);
309
+ var __dirname2 = path2.dirname(__filename2);
310
+ var require2 = createRequire2(import.meta.url);
311
+ function findGhosttyWeb() {
312
+ try {
313
+ const ghosttyWebMain = require2.resolve("ghostty-web");
314
+ const ghosttyWebRoot = ghosttyWebRootFromMain(ghosttyWebMain);
315
+ const distPath = path2.join(ghosttyWebRoot, "dist");
316
+ const wasmPath = path2.join(ghosttyWebRoot, "ghostty-vt.wasm");
317
+ if (fs.existsSync(path2.join(distPath, "ghostty-web.js")) && fs.existsSync(wasmPath)) {
318
+ return { distPath, wasmPath };
319
+ }
320
+ } catch {}
321
+ console.error("Error: Could not find ghostty-web package.");
322
+ process.exit(1);
323
+ }
324
+ function serveFile(filePath, res) {
325
+ const contentType = mimeType(filePath);
326
+ fs.readFile(filePath, (err, data) => {
327
+ if (err) {
328
+ res.writeHead(404);
329
+ res.end("Not Found");
330
+ return;
331
+ }
332
+ res.writeHead(200, { "Content-Type": contentType });
333
+ res.end(data);
334
+ });
335
+ }
336
+
337
+ // src/server/routes.ts
338
+ var MAX_BODY = 64 * 1024;
339
+ function decodeId(raw) {
340
+ try {
341
+ return decodeURIComponent(raw);
342
+ } catch {
343
+ return null;
344
+ }
345
+ }
346
+ function readJson(req) {
347
+ return new Promise((resolve, reject) => {
348
+ let body = "";
349
+ req.on("data", (chunk) => {
350
+ body += chunk;
351
+ if (body.length > MAX_BODY) {
352
+ req.destroy();
353
+ reject(Object.assign(new Error("payload too large"), { status: 413 }));
354
+ }
355
+ });
356
+ req.on("end", () => {
357
+ try {
358
+ resolve(body ? JSON.parse(body) : {});
359
+ } catch {
360
+ reject(new Error("invalid JSON"));
361
+ }
362
+ });
363
+ req.on("error", reject);
364
+ });
365
+ }
366
+ async function handleRequest(req, res, distPath, wasmPath, onStop) {
367
+ const url = new URL(req.url ?? "/", `http://${req.headers.host ?? "127.0.0.1"}`);
368
+ const pathname = url.pathname;
369
+ if (req.method === "POST" && pathname === "/api/server/stop") {
370
+ res.writeHead(200, { "Content-Type": "text/plain" });
371
+ res.end("stopping");
372
+ onStop();
373
+ return;
374
+ }
375
+ if (pathname === "/api/sessions") {
376
+ if (req.method === "GET") {
377
+ const list = [...sessionRegistry.values()].map(sessionToJson);
378
+ res.writeHead(200, { "Content-Type": "application/json" });
379
+ res.end(JSON.stringify(list));
380
+ return;
381
+ }
382
+ if (req.method === "POST") {
383
+ let body;
384
+ try {
385
+ body = await readJson(req);
386
+ } catch (err) {
387
+ const status = err.status === 413 ? 413 : 400;
388
+ res.writeHead(status);
389
+ res.end(status === 413 ? "Payload Too Large" : "invalid JSON");
390
+ return;
391
+ }
392
+ const id = body.id ?? generateId();
393
+ if (!isValidId(id)) {
394
+ res.writeHead(400, { "Content-Type": "application/json" });
395
+ res.end(JSON.stringify({ error: `invalid id: ${id}` }));
396
+ return;
397
+ }
398
+ if (sessionRegistry.has(id)) {
399
+ res.writeHead(409, { "Content-Type": "application/json" });
400
+ res.end(JSON.stringify({ error: `session already exists: ${id}` }));
401
+ return;
402
+ }
403
+ const session = createSession(id);
404
+ res.writeHead(201, { "Content-Type": "application/json" });
405
+ res.end(JSON.stringify(sessionToJson(session)));
406
+ return;
407
+ }
408
+ }
409
+ const sessionMatch = pathname.match(/^\/api\/sessions\/([^/]+)$/);
410
+ if (sessionMatch) {
411
+ const id = decodeId(sessionMatch[1]);
412
+ if (!id) {
413
+ res.writeHead(400);
414
+ res.end("Bad Request");
415
+ return;
416
+ }
417
+ if (req.method === "GET") {
418
+ const session = sessionRegistry.get(id);
419
+ if (!session) {
420
+ res.writeHead(404);
421
+ res.end("Not Found");
422
+ return;
423
+ }
424
+ res.writeHead(200, { "Content-Type": "application/json" });
425
+ res.end(JSON.stringify(sessionToJson(session)));
426
+ return;
427
+ }
428
+ if (req.method === "PATCH") {
429
+ let body;
430
+ try {
431
+ body = await readJson(req);
432
+ } catch (err) {
433
+ const status = err.status === 413 ? 413 : 400;
434
+ res.writeHead(status);
435
+ res.end(status === 413 ? "Payload Too Large" : "invalid JSON");
436
+ return;
437
+ }
438
+ const session = sessionRegistry.get(id);
439
+ if (!session) {
440
+ res.writeHead(404);
441
+ res.end("Not Found");
442
+ return;
443
+ }
444
+ const newId = body.id;
445
+ if (!newId) {
446
+ res.writeHead(400);
447
+ res.end("missing id");
448
+ return;
449
+ }
450
+ if (!isValidId(newId)) {
451
+ res.writeHead(400, { "Content-Type": "application/json" });
452
+ res.end(JSON.stringify({ error: `invalid id: ${newId}` }));
453
+ return;
454
+ }
455
+ if (sessionRegistry.has(newId)) {
456
+ res.writeHead(409, { "Content-Type": "application/json" });
457
+ res.end(JSON.stringify({ error: `session already exists: ${newId}` }));
458
+ return;
459
+ }
460
+ sessionRegistry.delete(id);
461
+ session.id = newId;
462
+ sessionRegistry.set(newId, session);
463
+ if (lastUsedId === id)
464
+ setLastUsedId(newId);
465
+ res.writeHead(200, { "Content-Type": "application/json" });
466
+ res.end(JSON.stringify(sessionToJson(session)));
467
+ return;
468
+ }
469
+ if (req.method === "DELETE") {
470
+ const session = sessionRegistry.get(id);
471
+ if (!session) {
472
+ res.writeHead(404);
473
+ res.end("Not Found");
474
+ return;
475
+ }
476
+ sessionRegistry.delete(id);
477
+ if (lastUsedId === id)
478
+ setLastUsedId(null);
479
+ for (const client of session.clients)
480
+ client.close(4001, "session deleted");
481
+ session.pty?.kill();
482
+ res.writeHead(204);
483
+ res.end();
484
+ return;
485
+ }
486
+ }
487
+ if (req.method === "GET" && (pathname === "/" || pathname === "/index.html")) {
488
+ let targetId = lastUsedId ?? null;
489
+ if (!targetId || !sessionRegistry.has(targetId)) {
490
+ if (!sessionRegistry.has("main"))
491
+ createSession("main");
492
+ targetId = "main";
493
+ }
494
+ res.writeHead(302, { Location: `/s/${targetId}` });
495
+ res.end();
496
+ return;
497
+ }
498
+ const clientMatch = pathname.match(/^\/s\/([^/]+)$/);
499
+ if (req.method === "GET" && clientMatch) {
500
+ const id = decodeId(clientMatch[1]);
501
+ if (!id || !isValidId(id) || !sessionRegistry.has(id)) {
502
+ res.writeHead(404);
503
+ res.end("Not Found");
504
+ return;
505
+ }
506
+ res.writeHead(200, { "Content-Type": "text/html" });
507
+ res.end(render(id));
508
+ return;
509
+ }
510
+ if (pathname.startsWith("/dist/")) {
511
+ const filePath = path3.resolve(distPath, pathname.slice(6));
512
+ if (!filePath.startsWith(path3.resolve(distPath) + path3.sep) && filePath !== path3.resolve(distPath)) {
513
+ res.writeHead(404);
514
+ res.end("Not Found");
515
+ return;
516
+ }
517
+ serveFile(filePath, res);
518
+ return;
519
+ }
520
+ if (pathname === "/ghostty-vt.wasm") {
521
+ serveFile(wasmPath, res);
522
+ return;
523
+ }
524
+ res.writeHead(404);
525
+ res.end("Not Found");
526
+ }
527
+
528
+ // src/server/websocket.ts
529
+ import { WebSocketServer } from "ws";
530
+
531
+ // src/pty/index.ts
532
+ var isBun = !!process.versions.bun;
533
+ console.log(`pty: ${isBun ? "Bun.Terminal" : "node-pty"}`);
534
+ var { spawn: _spawn } = await (isBun ? Promise.resolve().then(() => (init_bun(), exports_bun)) : Promise.resolve().then(() => (init_node(), exports_node)));
535
+ function spawnForSession(cols, rows) {
536
+ const shell = process.platform === "win32" ? process.env.COMSPEC ?? "cmd.exe" : process.env.SHELL ?? "/bin/bash";
537
+ return _spawn(shell, cols, rows);
538
+ }
539
+
540
+ // src/server/websocket.ts
541
+ function createWebSocketServer(httpServer) {
542
+ const wss = new WebSocketServer({ noServer: true });
543
+ httpServer.on("upgrade", (req, socket, head) => {
544
+ const url = new URL(req.url ?? "/", `http://${req.headers.host ?? "127.0.0.1"}`);
545
+ if (url.pathname.match(/^\/ws\/([^/]+)$/)) {
546
+ wss.handleUpgrade(req, socket, head, (ws) => wss.emit("connection", ws, req));
547
+ } else {
548
+ socket.destroy();
549
+ }
550
+ });
551
+ wss.on("connection", (ws, req) => {
552
+ const url = new URL(req.url ?? "/", `http://${req.headers.host ?? "127.0.0.1"}`);
553
+ const wsMatch = url.pathname.match(/^\/ws\/([^/]+)$/);
554
+ if (!wsMatch) {
555
+ ws.close();
556
+ return;
557
+ }
558
+ let id;
559
+ try {
560
+ id = decodeURIComponent(wsMatch[1]);
561
+ } catch {
562
+ ws.close(1008, "Bad Request");
563
+ return;
564
+ }
565
+ const cols = Math.max(1, Math.min(1000, Number.parseInt(url.searchParams.get("cols") ?? "80", 10) || 80));
566
+ const rows = Math.max(1, Math.min(500, Number.parseInt(url.searchParams.get("rows") ?? "24", 10) || 24));
567
+ if (!sessionRegistry.has(id)) {
568
+ ws.close(4001, "session deleted");
569
+ return;
570
+ }
571
+ const session = sessionRegistry.get(id);
572
+ if (!session) {
573
+ ws.close(4001, "session deleted");
574
+ return;
575
+ }
576
+ session.clients.add(ws);
577
+ setLastUsedId(id);
578
+ if (!session.pty) {
579
+ session.pty = spawnForSession(cols, rows);
580
+ session.pty.onData((data) => {
581
+ session.scrollback = (session.scrollback + data).slice(-SCROLLBACK_MAX);
582
+ for (const client of session.clients) {
583
+ if (client.readyState === client.OPEN) {
584
+ client.send(data, { binary: false });
585
+ }
586
+ }
587
+ });
588
+ session.pty.onExit(() => {
589
+ sessionRegistry.delete(session.id);
590
+ for (const client of session.clients) {
591
+ if (client.readyState === client.OPEN) {
592
+ client.close(4001, "shell exited");
593
+ }
594
+ }
595
+ session.pty = null;
596
+ });
597
+ const C = "\x1B[1;36m";
598
+ const G = "\x1B[1;32m";
599
+ const Y = "\x1B[1;33m";
600
+ const R = "\x1B[0m";
601
+ const banner = [
602
+ `${C}╔══════════════════════════════════════════════════════════════╗${R}\r
603
+ `,
604
+ `${C}║${R} ${G}Welcome to webtty!${R} ${C}║${R}\r
605
+ `,
606
+ `${C}║${R} ${C}║${R}\r
607
+ `,
608
+ `${C}║${R} You have a real shell session with full PTY support. ${C}║${R}\r
609
+ `,
610
+ `${C}║${R} Try: ${Y}ls${R}, ${Y}cd${R}, ${Y}top${R}, ${Y}vim${R}, or any command! ${C}║${R}\r
611
+ `,
612
+ `${C}╚══════════════════════════════════════════════════════════════╝${R}\r
613
+ \r
614
+ `
615
+ ].join("");
616
+ ws.send(banner);
617
+ session.scrollback = banner;
618
+ session.pty.write(`
619
+ `);
620
+ } else {
621
+ if (session.scrollback) {
622
+ ws.send(session.scrollback, { binary: false });
623
+ }
624
+ session.pty.resize(cols, rows);
625
+ }
626
+ ws.on("message", (data) => {
627
+ const message = data.toString("utf8");
628
+ if (message.startsWith("{")) {
629
+ try {
630
+ const msg = JSON.parse(message);
631
+ if (msg.type === "resize") {
632
+ const c = Math.max(1, Math.min(1000, Math.trunc(msg.cols) || 80));
633
+ const r = Math.max(1, Math.min(500, Math.trunc(msg.rows) || 24));
634
+ session.pty?.resize(c, r);
635
+ return;
636
+ }
637
+ } catch {}
638
+ }
639
+ session.pty?.write(message);
640
+ });
641
+ ws.on("close", () => {
642
+ session.clients.delete(ws);
643
+ });
644
+ ws.on("error", () => {});
645
+ });
646
+ return wss;
647
+ }
648
+
649
+ // src/server/index.ts
650
+ var HTTP_PORT = Number(process.env.PORT) || 2346;
651
+ var { distPath, wasmPath } = findGhosttyWeb();
652
+ var httpServer = http.createServer((req, res) => {
653
+ handleRequest(req, res, distPath, wasmPath, () => {
654
+ for (const session of sessionRegistry.values()) {
655
+ session.pty?.kill();
656
+ for (const client of session.clients)
657
+ client.close();
658
+ }
659
+ wss.close();
660
+ httpServer.close(() => process.exit(0));
661
+ });
662
+ });
663
+ var wss = createWebSocketServer(httpServer);
664
+ process.on("SIGINT", () => {
665
+ console.log(`
666
+
667
+ Shutting down...`);
668
+ for (const session of sessionRegistry.values()) {
669
+ session.pty?.kill();
670
+ for (const client of session.clients)
671
+ client.close();
672
+ }
673
+ wss.close();
674
+ process.exit(0);
675
+ });
676
+ httpServer.listen(HTTP_PORT, "127.0.0.1", () => {
677
+ console.log(`listening on http://127.0.0.1:${HTTP_PORT}`);
678
+ });
679
+
680
+ //# debugId=102CAB57A485E36364756E2164756E21