agent-gear 1.1.2 → 1.1.5

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/src/gateway.ts ADDED
@@ -0,0 +1,195 @@
1
+ import net from 'node:net';
2
+ import type { Socket } from 'socket.io-client';
3
+
4
+ interface Tunnel {
5
+ id: string;
6
+ port: number;
7
+ localSocket: net.Socket | null;
8
+ serverSocket: Socket;
9
+ pendingRequestId?: string | null;
10
+ responseBuffer: Buffer[];
11
+ responseTimer?: ReturnType<typeof setTimeout> | null;
12
+ }
13
+
14
+ const RECONNECT_INTERVAL_MS = 3000;
15
+ const HTTP_RESPONSE_COLLECT_MS = 100;
16
+
17
+ export class GatewayManager {
18
+ private tunnels: Map<string, Tunnel> = new Map();
19
+ private reconnectTimers: Map<string, ReturnType<typeof setTimeout>> = new Map();
20
+
21
+ async createTunnel(tunnelId: string, port: number, serverSocket: Socket): Promise<void> {
22
+ this.tunnels.set(tunnelId, { id: tunnelId, port, localSocket: null, serverSocket, responseBuffer: [] });
23
+ await this.connect(tunnelId, true);
24
+ }
25
+
26
+ private async connect(tunnelId: string, isInitial: boolean): Promise<void> {
27
+ const tunnel = this.tunnels.get(tunnelId);
28
+ if (!tunnel) return;
29
+
30
+ const localSocket = net.connect({ host: '127.0.0.1', port: tunnel.port });
31
+ localSocket.setKeepAlive(true, 5000);
32
+
33
+ return new Promise<void>((resolve) => {
34
+ const onError = (err: Error) => {
35
+ console.error(`Gateway tunnel error: tunnelId=${tunnelId} error=${err.message}`);
36
+ localSocket.destroy();
37
+ this.scheduleReconnect(tunnelId);
38
+ resolve();
39
+ };
40
+
41
+ localSocket.once('error', onError);
42
+
43
+ localSocket.once('connect', () => {
44
+ localSocket.removeListener('error', onError);
45
+
46
+ tunnel.localSocket = localSocket;
47
+ console.log(`Gateway tunnel connected: tunnelId=${tunnelId} port=${tunnel.port}`);
48
+
49
+ if (isInitial) {
50
+ tunnel.serverSocket.emit('port-forward:created', { tunnelId, success: true });
51
+ }
52
+
53
+ localSocket.on('data', (data: Buffer) => {
54
+ if (tunnel.pendingRequestId) {
55
+ tunnel.responseBuffer.push(data);
56
+ if (tunnel.responseTimer) clearTimeout(tunnel.responseTimer);
57
+ tunnel.responseTimer = setTimeout(() => this.flushResponse(tunnel), HTTP_RESPONSE_COLLECT_MS);
58
+ } else {
59
+ tunnel.serverSocket.emit('port-forward:data', {
60
+ tunnelId,
61
+ data: data.toString('base64'),
62
+ });
63
+ }
64
+ });
65
+
66
+ localSocket.on('close', () => {
67
+ console.log(`Gateway local socket closed: tunnelId=${tunnelId}`);
68
+ tunnel.localSocket = null;
69
+ this.scheduleReconnect(tunnelId);
70
+ });
71
+
72
+ localSocket.on('error', (err: Error) => {
73
+ console.error(`Gateway local socket error: tunnelId=${tunnelId} error=${err.message}`);
74
+ tunnel.serverSocket.emit('port-forward:error', { tunnelId, error: err.message });
75
+ });
76
+
77
+ resolve();
78
+ });
79
+ });
80
+ }
81
+
82
+ private flushResponse(tunnel: Tunnel): void {
83
+ if (!tunnel.pendingRequestId) return;
84
+ const combined = Buffer.concat(tunnel.responseBuffer);
85
+ tunnel.responseBuffer = [];
86
+ const requestId = tunnel.pendingRequestId;
87
+ tunnel.pendingRequestId = null;
88
+ tunnel.responseTimer = null;
89
+
90
+ tunnel.serverSocket.emit('port-forward:data', {
91
+ tunnelId: tunnel.id,
92
+ data: Buffer.from(JSON.stringify({
93
+ type: 'http-response',
94
+ requestId,
95
+ body: combined.toString('base64'),
96
+ })).toString('base64'),
97
+ });
98
+ }
99
+
100
+ private scheduleReconnect(tunnelId: string): void {
101
+ if (!this.tunnels.has(tunnelId) || this.reconnectTimers.has(tunnelId)) return;
102
+
103
+ console.log(`Gateway scheduling reconnection: tunnelId=${tunnelId}`);
104
+ const timer = setTimeout(() => {
105
+ this.reconnectTimers.delete(tunnelId);
106
+ this.connect(tunnelId, false);
107
+ }, RECONNECT_INTERVAL_MS);
108
+
109
+ this.reconnectTimers.set(tunnelId, timer);
110
+ }
111
+
112
+ handleTunnelData(tunnelId: string, data: string): void {
113
+ const tunnel = this.tunnels.get(tunnelId);
114
+ if (!tunnel) {
115
+ console.warn(`Gateway data for unknown tunnel: tunnelId=${tunnelId}`);
116
+ return;
117
+ }
118
+
119
+ const buffer = Buffer.from(data, 'base64');
120
+ let parsed: any;
121
+ try {
122
+ parsed = JSON.parse(buffer.toString('utf8'));
123
+ } catch {
124
+ if (tunnel.localSocket) {
125
+ tunnel.localSocket.write(buffer);
126
+ }
127
+ return;
128
+ }
129
+
130
+ if (parsed.type === 'http-request') {
131
+ const { method, path, headers, body } = parsed;
132
+ let rawRequest = `${method} ${path} HTTP/1.1\r\n`;
133
+ for (const [key, value] of Object.entries(headers || {})) {
134
+ rawRequest += `${key}: ${value}\r\n`;
135
+ }
136
+ rawRequest += `Host: 127.0.0.1:${tunnel.port}\r\n`;
137
+ rawRequest += 'Connection: close\r\n';
138
+ rawRequest += '\r\n';
139
+ if (body) rawRequest += Buffer.from(body, 'base64').toString('utf8');
140
+
141
+ tunnel.pendingRequestId = parsed.requestId;
142
+ tunnel.responseBuffer = [];
143
+
144
+ if (tunnel.localSocket) {
145
+ tunnel.localSocket.write(rawRequest);
146
+ }
147
+ return;
148
+ }
149
+
150
+ if (tunnel.localSocket) {
151
+ tunnel.localSocket.write(buffer);
152
+ }
153
+ }
154
+
155
+ closeTunnel(tunnelId: string): void {
156
+ const tunnel = this.tunnels.get(tunnelId);
157
+ if (!tunnel) return;
158
+
159
+ console.log(`Gateway closing tunnel: tunnelId=${tunnelId}`);
160
+
161
+ const timer = this.reconnectTimers.get(tunnelId);
162
+ if (timer) {
163
+ clearTimeout(timer);
164
+ this.reconnectTimers.delete(tunnelId);
165
+ }
166
+
167
+ this.tunnels.delete(tunnelId);
168
+
169
+ if (tunnel.localSocket) {
170
+ tunnel.localSocket.destroy();
171
+ }
172
+ tunnel.serverSocket.emit('port-forward:close', { tunnelId });
173
+ }
174
+
175
+ closeAll(): void {
176
+ console.log(`Gateway closing all tunnels: count=${this.tunnels.size}`);
177
+
178
+ for (const [tunnelId, tunnel] of [...this.tunnels]) {
179
+ const timer = this.reconnectTimers.get(tunnelId);
180
+ if (timer) {
181
+ clearTimeout(timer);
182
+ this.reconnectTimers.delete(tunnelId);
183
+ }
184
+
185
+ this.tunnels.delete(tunnelId);
186
+
187
+ if (tunnel.localSocket) {
188
+ tunnel.localSocket.destroy();
189
+ }
190
+ tunnel.serverSocket.emit('port-forward:close', { tunnelId: tunnel.id });
191
+ }
192
+ }
193
+ }
194
+
195
+ export const gatewayManager = new GatewayManager();
@@ -13,7 +13,7 @@ import { AcpSyncBridge } from './acp/sync-bridge.js';
13
13
  import { gitHandler } from './git-handler.js';
14
14
  import { worktreeHandler } from './worktree-handler.js';
15
15
  import { fsHandler } from './fs-handler.js';
16
- import { portForwardManager } from './port-forward.js';
16
+ import { gatewayManager } from './gateway.js';
17
17
 
18
18
  const CONTROLLER_VERSION = '1.1.1';
19
19
  const acpBridges = new Map<string, AcpSessionBridge>();
@@ -83,12 +83,14 @@ interface AcpResumePayload {
83
83
 
84
84
  interface AcpListSessionsPayload {
85
85
  requestId: string;
86
+ serverType: string;
86
87
  cwd: string;
87
88
  }
88
89
 
89
90
  interface AcpLoadSessionPayload {
90
91
  requestId: string;
91
92
  sessionId: string;
93
+ serverType: string;
92
94
  cwd: string;
93
95
  }
94
96
 
@@ -343,8 +345,10 @@ export function connectToServer(
343
345
  });
344
346
 
345
347
  socket.on('acp:list-sessions', async (data: AcpListSessionsPayload) => {
346
- console.log(`[ACP sync] acp:list-sessions received: requestId=${data.requestId} cwd=${data.cwd}`);
347
- const bridge = new AcpSyncBridge({ cwd: data.cwd });
348
+ const serverType = data.serverType ?? 'opencode';
349
+ console.log(`[ACP sync] acp:list-sessions received: requestId=${data.requestId} serverType=${serverType} cwd=${data.cwd}`);
350
+ const spawner = createSpawner(serverType, `sync_${Date.now()}`, data.cwd);
351
+ const bridge = new AcpSyncBridge({ cwd: data.cwd }, spawner);
348
352
  try {
349
353
  await bridge.connect();
350
354
  const result = await bridge.listSessions(data.cwd);
@@ -371,8 +375,10 @@ export function connectToServer(
371
375
  });
372
376
 
373
377
  socket.on('acp:load-session', async (data: AcpLoadSessionPayload) => {
374
- console.log(`[ACP] load-session received: requestId=${data.requestId} sessionId=${data.sessionId} cwd=${data.cwd}`);
375
- const bridge = new AcpSyncBridge({ cwd: data.cwd });
378
+ const serverType = data.serverType ?? 'opencode';
379
+ console.log(`[ACP] load-session received: requestId=${data.requestId} sessionId=${data.sessionId} serverType=${serverType} cwd=${data.cwd}`);
380
+ const spawner = createSpawner(serverType, `sync_${Date.now()}`, data.cwd);
381
+ const bridge = new AcpSyncBridge({ cwd: data.cwd }, spawner);
376
382
  try {
377
383
  await bridge.connect();
378
384
  const { config, updates } = await bridge.loadSession(data.sessionId, data.cwd);
@@ -585,19 +591,19 @@ export function connectToServer(
585
591
  socket.emit('git:commit:result', { requestId: data.requestId, ...result });
586
592
  });
587
593
 
588
- // Port Forward events
594
+ // Port Forward events (event names kept unchanged for backward compatibility)
589
595
  socket.on('port-forward:create', async (data: { tunnelId: string; port: number }) => {
590
- console.log(`Port forward create requested: tunnelId=${data.tunnelId} port=${data.port}`);
591
- await portForwardManager.createTunnel(data.tunnelId, data.port, socket);
596
+ console.log(`Gateway tunnel create requested: tunnelId=${data.tunnelId} port=${data.port}`);
597
+ await gatewayManager.createTunnel(data.tunnelId, data.port, socket);
592
598
  });
593
599
 
594
600
  socket.on('port-forward:data', (data: { tunnelId: string; data: string }) => {
595
- portForwardManager.handleTunnelData(data.tunnelId, data.data);
601
+ gatewayManager.handleTunnelData(data.tunnelId, data.data);
596
602
  });
597
603
 
598
604
  socket.on('port-forward:close', (data: { tunnelId: string }) => {
599
- console.log(`Port forward close: tunnelId=${data.tunnelId}`);
600
- portForwardManager.closeTunnel(data.tunnelId);
605
+ console.log(`Gateway tunnel close: tunnelId=${data.tunnelId}`);
606
+ gatewayManager.closeTunnel(data.tunnelId);
601
607
  });
602
608
 
603
609
  socket.on('acp:permission:response', (data: {
@@ -614,7 +620,7 @@ export function connectToServer(
614
620
  socket.on('disconnect', (reason) => {
615
621
  console.log(`Disconnected: ${reason}`);
616
622
  heartbeat.stop();
617
- portForwardManager.closeAll();
623
+ gatewayManager.closeAll();
618
624
  permissionRequestBridge.clear();
619
625
  });
620
626
 
@@ -32,71 +32,148 @@ describe('AcpDetector', () => {
32
32
  });
33
33
 
34
34
  describe('detect', () => {
35
- it('should detect opencode and github_copilot when both available', async () => {
35
+ it('should detect all 7 servers when all available', async () => {
36
36
  mockExecSuccessOnce('opencode v1.2.3\n');
37
37
  mockExecSuccessOnce('copilot 1.0.0\n');
38
+ mockExecSuccessOnce('claude-agent-acp 0.1.0\n');
39
+ mockExecSuccessOnce('gemini 2.0.0\n');
40
+ mockExecSuccessOnce('codex-acp 0.5.0\n');
41
+ mockExecSuccessOnce('mimo 0.1.0\n');
42
+ mockExecSuccessOnce('kimi 1.0.0\n');
38
43
 
39
44
  const result = await detector.detect();
40
45
 
41
- expect(result).toHaveLength(2);
46
+ expect(result).toHaveLength(7);
42
47
  expect(result[0]).toEqual({ type: 'opencode', available: true, version: '1.2.3' });
43
48
  expect(result[1]).toEqual({ type: 'github_copilot', available: true, version: '1.0.0' });
49
+ expect(result[2]).toEqual({ type: 'claude_code', available: true, version: '0.1.0' });
50
+ expect(result[3]).toEqual({ type: 'gemini_cli', available: true, version: '2.0.0' });
51
+ expect(result[4]).toEqual({ type: 'codex', available: true, version: '0.5.0' });
52
+ expect(result[5]).toEqual({ type: 'mimo_code', available: true, version: '0.1.0' });
53
+ expect(result[6]).toEqual({ type: 'kimi_code', available: true, version: '1.0.0' });
44
54
  });
45
55
 
46
56
  it('should detect as unavailable when command fails', async () => {
47
57
  mockExecFailureOnce();
48
58
  mockExecFailureOnce();
59
+ mockExecFailureOnce();
60
+ mockExecFailureOnce();
61
+ mockExecFailureOnce();
62
+ mockExecFailureOnce();
63
+ mockExecFailureOnce();
49
64
 
50
65
  const result = await detector.detect();
51
66
 
52
- expect(result).toHaveLength(2);
67
+ expect(result).toHaveLength(7);
53
68
  expect(result[0]).toEqual({ type: 'opencode', available: false });
54
69
  expect(result[1]).toEqual({ type: 'github_copilot', available: false });
70
+ expect(result[2]).toEqual({ type: 'claude_code', available: false });
71
+ expect(result[3]).toEqual({ type: 'gemini_cli', available: false });
72
+ expect(result[4]).toEqual({ type: 'codex', available: false });
73
+ expect(result[5]).toEqual({ type: 'mimo_code', available: false });
74
+ expect(result[6]).toEqual({ type: 'kimi_code', available: false });
55
75
  });
56
76
 
57
- it('should parse version with different formats', async () => {
58
- mockExecSuccessOnce('OpenCode version 2.0.1\n');
59
- mockExecSuccessOnce('copilot 3.0.0\n');
77
+ it('should detect claude_code only when other servers fail', async () => {
78
+ mockExecFailureOnce();
79
+ mockExecFailureOnce();
80
+ mockExecSuccessOnce('claude-agent-acp 0.1.0\n');
81
+ mockExecFailureOnce();
82
+ mockExecFailureOnce();
83
+ mockExecFailureOnce();
84
+ mockExecFailureOnce();
60
85
 
61
86
  const result = await detector.detect();
62
87
 
63
- expect(result[0].version).toBe('2.0.1');
64
- expect(result[1].version).toBe('3.0.0');
88
+ expect(result).toHaveLength(7);
89
+ expect(result[0]).toEqual({ type: 'opencode', available: false });
90
+ expect(result[1]).toEqual({ type: 'github_copilot', available: false });
91
+ expect(result[2]).toEqual({ type: 'claude_code', available: true, version: '0.1.0' });
92
+ expect(result[3]).toEqual({ type: 'gemini_cli', available: false });
93
+ expect(result[4]).toEqual({ type: 'codex', available: false });
94
+ expect(result[5]).toEqual({ type: 'mimo_code', available: false });
95
+ expect(result[6]).toEqual({ type: 'kimi_code', available: false });
65
96
  });
66
97
 
67
- it('should return unknown version when format not recognized', async () => {
68
- mockExecSuccessOnce('some output without version\n');
69
- mockExecSuccessOnce('copilot output\n');
98
+ it('should detect gemini_cli only when other servers fail', async () => {
99
+ mockExecFailureOnce();
100
+ mockExecFailureOnce();
101
+ mockExecFailureOnce();
102
+ mockExecSuccessOnce('gemini 2.0.0\n');
103
+ mockExecFailureOnce();
104
+ mockExecFailureOnce();
105
+ mockExecFailureOnce();
70
106
 
71
107
  const result = await detector.detect();
72
108
 
73
- expect(result[0].version).toBe('unknown');
74
- expect(result[1].version).toBe('unknown');
109
+ expect(result).toHaveLength(7);
110
+ expect(result[3]).toEqual({ type: 'gemini_cli', available: true, version: '2.0.0' });
75
111
  });
76
112
 
77
- it('should detect only opencode when copilot fails', async () => {
78
- mockExecSuccessOnce('opencode v1.2.3\n');
113
+ it('should detect codex only when other servers fail', async () => {
114
+ mockExecFailureOnce();
115
+ mockExecFailureOnce();
116
+ mockExecFailureOnce();
117
+ mockExecFailureOnce();
118
+ mockExecSuccessOnce('codex-acp 0.5.0\n');
119
+ mockExecFailureOnce();
79
120
  mockExecFailureOnce();
80
121
 
81
122
  const result = await detector.detect();
82
123
 
83
- expect(result).toHaveLength(2);
84
- expect(result[0]).toEqual({ type: 'opencode', available: true, version: '1.2.3' });
85
- expect(result[1]).toEqual({ type: 'github_copilot', available: false });
124
+ expect(result).toHaveLength(7);
125
+ expect(result[4]).toEqual({ type: 'codex', available: true, version: '0.5.0' });
86
126
  });
87
127
 
88
- it('should detect only copilot when opencode fails', async () => {
128
+ it('should detect mimo_code only when other servers fail', async () => {
129
+ mockExecFailureOnce();
130
+ mockExecFailureOnce();
131
+ mockExecFailureOnce();
132
+ mockExecFailureOnce();
133
+ mockExecFailureOnce();
134
+ mockExecSuccessOnce('mimo 0.1.0\n');
89
135
  mockExecFailureOnce();
90
- mockExecSuccessOnce('copilot 1.0.0\n');
91
136
 
92
137
  const result = await detector.detect();
93
138
 
94
- expect(result).toHaveLength(2);
95
- expect(result[0]).toEqual({ type: 'opencode', available: false });
96
- expect(result[1]).toEqual({ type: 'github_copilot', available: true, version: '1.0.0' });
139
+ expect(result).toHaveLength(7);
140
+ expect(result[5]).toEqual({ type: 'mimo_code', available: true, version: '0.1.0' });
97
141
  });
98
142
 
143
+ it('should detect kimi_code only when other servers fail', async () => {
144
+ mockExecFailureOnce();
145
+ mockExecFailureOnce();
146
+ mockExecFailureOnce();
147
+ mockExecFailureOnce();
148
+ mockExecFailureOnce();
149
+ mockExecFailureOnce();
150
+ mockExecSuccessOnce('kimi 2.0.0\n');
151
+
152
+ const result = await detector.detect();
153
+
154
+ expect(result).toHaveLength(7);
155
+ expect(result[6]).toEqual({ type: 'kimi_code', available: true, version: '2.0.0' });
156
+ });
157
+
158
+ it('should parse version with different formats', async () => {
159
+ mockExecSuccessOnce('OpenCode version 2.0.1\n');
160
+ mockExecSuccessOnce('copilot 3.0.0\n');
161
+ mockExecSuccessOnce('v0.1.0\n');
162
+ mockExecSuccessOnce('Gemini 2.0.0\n');
163
+ mockExecSuccessOnce('codex-acp version 0.5.0\n');
164
+ mockExecSuccessOnce('MiMo CLI v1.0.0\n');
165
+ mockExecSuccessOnce('kimi 2.0.0\n');
166
+
167
+ const result = await detector.detect();
99
168
 
169
+ expect(result[0].version).toBe('2.0.1');
170
+ expect(result[1].version).toBe('3.0.0');
171
+ expect(result[2].version).toBe('0.1.0');
172
+ expect(result[3].version).toBe('2.0.0');
173
+ expect(result[4].version).toBe('0.5.0');
174
+ expect(result[5].version).toBe('1.0.0');
175
+ expect(result[6].version).toBe('2.0.0');
176
+ });
100
177
  });
101
178
 
102
179
  describe('getSpawnerConfig', () => {
@@ -110,6 +187,31 @@ describe('AcpDetector', () => {
110
187
  expect(config).toEqual({ command: 'copilot', args: ['--acp', '--stdio'] });
111
188
  });
112
189
 
190
+ it('should return claude_code spawner config', () => {
191
+ const config = detector.getSpawnerConfig('claude_code');
192
+ expect(config).toEqual({ command: 'claude-agent-acp', args: [] });
193
+ });
194
+
195
+ it('should return gemini_cli spawner config', () => {
196
+ const config = detector.getSpawnerConfig('gemini_cli');
197
+ expect(config).toEqual({ command: 'gemini', args: ['--acp'] });
198
+ });
199
+
200
+ it('should return codex spawner config', () => {
201
+ const config = detector.getSpawnerConfig('codex');
202
+ expect(config).toEqual({ command: 'codex-acp', args: [] });
203
+ });
204
+
205
+ it('should return mimo_code spawner config', () => {
206
+ const config = detector.getSpawnerConfig('mimo_code');
207
+ expect(config).toEqual({ command: 'mimo', args: ['acp'] });
208
+ });
209
+
210
+ it('should return kimi_code spawner config', () => {
211
+ const config = detector.getSpawnerConfig('kimi_code');
212
+ expect(config).toEqual({ command: 'kimi', args: ['acp'] });
213
+ });
214
+
113
215
  it('should return opencode as default for unknown serverType', () => {
114
216
  const config = detector.getSpawnerConfig('unknown_server');
115
217
  expect(config).toEqual({ command: 'opencode', args: ['acp'] });
@@ -1 +0,0 @@
1
- {"version":3,"file":"port-forward.d.ts","sourceRoot":"","sources":["../src/port-forward.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAQ/C,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,OAAO,CAAkC;IAE3C,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAiDvF,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAStD,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAQnC,QAAQ,IAAI,IAAI;CAOjB;AAED,eAAO,MAAM,kBAAkB,oBAA2B,CAAC"}
@@ -1,68 +0,0 @@
1
- import net from 'node:net';
2
- export class PortForwardManager {
3
- tunnels = new Map();
4
- async createTunnel(tunnelId, port, serverSocket) {
5
- const localSocket = net.connect({ host: '127.0.0.1', port });
6
- return new Promise((resolve) => {
7
- const onError = (err) => {
8
- console.error(`Port forward tunnel error: tunnelId=${tunnelId} error=${err.message}`);
9
- serverSocket.emit('port-forward:created', {
10
- tunnelId,
11
- success: false,
12
- error: err.message,
13
- });
14
- localSocket.destroy();
15
- resolve();
16
- };
17
- localSocket.once('error', onError);
18
- localSocket.once('connect', () => {
19
- localSocket.removeListener('error', onError);
20
- this.tunnels.set(tunnelId, { id: tunnelId, port, localSocket });
21
- console.log(`Port forward tunnel created: tunnelId=${tunnelId} port=${port}`);
22
- serverSocket.emit('port-forward:created', { tunnelId, success: true });
23
- localSocket.on('data', (data) => {
24
- serverSocket.emit('port-forward:data', {
25
- tunnelId,
26
- data: data.toString('base64'),
27
- });
28
- });
29
- localSocket.on('close', () => {
30
- console.log(`Port forward local socket closed: tunnelId=${tunnelId}`);
31
- this.tunnels.delete(tunnelId);
32
- serverSocket.emit('port-forward:close', { tunnelId });
33
- });
34
- localSocket.on('error', (err) => {
35
- console.error(`Port forward local socket error: tunnelId=${tunnelId} error=${err.message}`);
36
- this.tunnels.delete(tunnelId);
37
- serverSocket.emit('port-forward:error', { tunnelId, error: err.message });
38
- });
39
- resolve();
40
- });
41
- });
42
- }
43
- handleTunnelData(tunnelId, data) {
44
- const tunnel = this.tunnels.get(tunnelId);
45
- if (!tunnel) {
46
- console.warn(`Port forward data for unknown tunnel: tunnelId=${tunnelId}`);
47
- return;
48
- }
49
- tunnel.localSocket.write(Buffer.from(data, 'base64'));
50
- }
51
- closeTunnel(tunnelId) {
52
- const tunnel = this.tunnels.get(tunnelId);
53
- if (!tunnel)
54
- return;
55
- console.log(`Port forward closing tunnel: tunnelId=${tunnelId}`);
56
- tunnel.localSocket.destroy();
57
- this.tunnels.delete(tunnelId);
58
- }
59
- closeAll() {
60
- console.log(`Port forward closing all tunnels: count=${this.tunnels.size}`);
61
- for (const tunnel of this.tunnels.values()) {
62
- tunnel.localSocket.destroy();
63
- }
64
- this.tunnels.clear();
65
- }
66
- }
67
- export const portForwardManager = new PortForwardManager();
68
- //# sourceMappingURL=port-forward.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"port-forward.js","sourceRoot":"","sources":["../src/port-forward.ts"],"names":[],"mappings":"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"}
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=port-forward.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"port-forward.test.d.ts","sourceRoot":"","sources":["../src/port-forward.test.ts"],"names":[],"mappings":""}