paneful 0.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,103 @@
1
+ import { WebSocket, WebSocketServer } from 'ws';
2
+ import { newProject } from './project-store.js';
3
+ export class WsHandler {
4
+ wss;
5
+ client = null;
6
+ ptyManager;
7
+ projectStore;
8
+ constructor(server, ptyManager, projectStore) {
9
+ this.ptyManager = ptyManager;
10
+ this.projectStore = projectStore;
11
+ this.wss = new WebSocketServer({ noServer: true });
12
+ server.on('upgrade', (req, socket, head) => {
13
+ if (req.url === '/ws') {
14
+ this.wss.handleUpgrade(req, socket, head, (ws) => {
15
+ this.wss.emit('connection', ws, req);
16
+ });
17
+ }
18
+ else {
19
+ socket.destroy();
20
+ }
21
+ });
22
+ this.wss.on('connection', (ws) => {
23
+ this.client = ws;
24
+ console.log('WebSocket client connected');
25
+ ws.on('message', (raw) => {
26
+ try {
27
+ const msg = JSON.parse(raw.toString());
28
+ this.handleMessage(msg);
29
+ }
30
+ catch (e) {
31
+ this.send({ type: 'error', message: `Invalid message: ${e}` });
32
+ }
33
+ });
34
+ ws.on('close', () => {
35
+ console.log('WebSocket client disconnected');
36
+ this.client = null;
37
+ });
38
+ ws.on('error', (err) => {
39
+ console.error('WebSocket error:', err.message);
40
+ this.client = null;
41
+ });
42
+ });
43
+ }
44
+ send(msg) {
45
+ if (this.client && this.client.readyState === WebSocket.OPEN) {
46
+ this.client.send(JSON.stringify(msg));
47
+ }
48
+ }
49
+ handleMessage(msg) {
50
+ switch (msg.type) {
51
+ case 'pty:spawn':
52
+ this.handlePtySpawn(msg.terminalId, msg.projectId, msg.cwd);
53
+ break;
54
+ case 'pty:input':
55
+ this.ptyManager.write(msg.terminalId, msg.data);
56
+ break;
57
+ case 'pty:resize':
58
+ this.ptyManager.resize(msg.terminalId, msg.cols, msg.rows);
59
+ break;
60
+ case 'pty:kill': {
61
+ const projectId = this.ptyManager.kill(msg.terminalId);
62
+ if (projectId) {
63
+ this.projectStore.removeTerminal(projectId, msg.terminalId);
64
+ this.send({ type: 'pty:exit', terminalId: msg.terminalId, exitCode: 0 });
65
+ }
66
+ break;
67
+ }
68
+ case 'project:kill': {
69
+ const killed = this.ptyManager.killProject(msg.projectId);
70
+ for (const tid of killed) {
71
+ this.send({ type: 'pty:exit', terminalId: tid, exitCode: 0 });
72
+ }
73
+ break;
74
+ }
75
+ case 'project:create': {
76
+ const project = newProject(msg.projectId, msg.name, msg.cwd);
77
+ this.projectStore.create(project);
78
+ break;
79
+ }
80
+ case 'project:remove': {
81
+ const killed = this.ptyManager.killProject(msg.projectId);
82
+ for (const tid of killed) {
83
+ this.send({ type: 'pty:exit', terminalId: tid, exitCode: 0 });
84
+ }
85
+ this.projectStore.remove(msg.projectId);
86
+ break;
87
+ }
88
+ }
89
+ }
90
+ handlePtySpawn(terminalId, projectId, cwd) {
91
+ try {
92
+ this.ptyManager.spawn(terminalId, projectId, cwd, (tid, data) => {
93
+ this.send({ type: 'pty:output', terminalId: tid, data });
94
+ }, (tid, exitCode) => {
95
+ this.send({ type: 'pty:exit', terminalId: tid, exitCode });
96
+ });
97
+ this.projectStore.addTerminal(projectId, terminalId);
98
+ }
99
+ catch (e) {
100
+ this.send({ type: 'error', message: `Failed to spawn terminal: ${e}` });
101
+ }
102
+ }
103
+ }