protocol-proxy 1.0.1 → 1.0.2
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/package.json +1 -1
- package/server.js +267 -137
package/package.json
CHANGED
package/server.js
CHANGED
|
@@ -1,175 +1,268 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
const express = require('express');
|
|
3
2
|
const path = require('path');
|
|
4
|
-
const cors = require('cors');
|
|
5
3
|
const { exec } = require('child_process');
|
|
6
4
|
const os = require('os');
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
|
|
7
|
+
// ==================== CLI ====================
|
|
8
|
+
|
|
9
|
+
const PID_FILE = path.join(os.tmpdir(), 'protocol-proxy.pid');
|
|
10
|
+
const pkg = require('./package.json');
|
|
11
|
+
|
|
12
|
+
function writePid() {
|
|
13
|
+
try { fs.writeFileSync(PID_FILE, String(process.pid)); } catch {}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function readPid() {
|
|
17
|
+
try { return parseInt(fs.readFileSync(PID_FILE, 'utf8').trim()); } catch { return null; }
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function removePid() {
|
|
21
|
+
try { fs.unlinkSync(PID_FILE); } catch {}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function isProcessAlive(pid) {
|
|
25
|
+
try { process.kill(pid, 0); return true; } catch { return false; }
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function showHelp() {
|
|
29
|
+
console.log(`
|
|
30
|
+
protocol-proxy - OpenAI / Anthropic 协议转换透明代理
|
|
31
|
+
|
|
32
|
+
用法:
|
|
33
|
+
protocol-proxy 启动服务
|
|
34
|
+
protocol-proxy start 启动服务
|
|
35
|
+
protocol-proxy stop 停止服务
|
|
36
|
+
protocol-proxy status 查看运行状态
|
|
37
|
+
protocol-proxy help 显示帮助信息
|
|
38
|
+
protocol-proxy -v, --version 显示版本号
|
|
39
|
+
protocol-proxy update 更新到最新版本
|
|
40
|
+
`);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function showVersion() {
|
|
44
|
+
console.log(pkg.version);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function showStatus() {
|
|
48
|
+
const pid = readPid();
|
|
49
|
+
if (pid && isProcessAlive(pid)) {
|
|
50
|
+
console.log(`服务正在运行 (PID: ${pid})`);
|
|
51
|
+
const configStore = require('./lib/config-store');
|
|
52
|
+
const proxies = configStore.getProxies();
|
|
53
|
+
if (proxies.length > 0) {
|
|
54
|
+
console.log(`\n已配置的代理 (${proxies.length} 个):`);
|
|
55
|
+
for (const p of proxies) {
|
|
56
|
+
console.log(` - ${p.name}: 端口 ${p.port} → ${p.target?.providerUrl || '未设置'}`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
20
59
|
} else {
|
|
21
|
-
|
|
60
|
+
removePid();
|
|
61
|
+
console.log('服务未运行');
|
|
22
62
|
}
|
|
23
|
-
exec(command, (err) => {
|
|
24
|
-
if (err) console.error('[Browser] 打开浏览器失败:', err.message);
|
|
25
|
-
});
|
|
26
63
|
}
|
|
27
64
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
res.json(proxies);
|
|
45
|
-
});
|
|
65
|
+
function stopService() {
|
|
66
|
+
const pid = readPid();
|
|
67
|
+
if (!pid || !isProcessAlive(pid)) {
|
|
68
|
+
removePid();
|
|
69
|
+
console.log('服务未运行');
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
try {
|
|
73
|
+
process.kill(pid, 'SIGTERM');
|
|
74
|
+
removePid();
|
|
75
|
+
console.log(`服务已停止 (PID: ${pid})`);
|
|
76
|
+
} catch (err) {
|
|
77
|
+
console.error('停止服务失败:', err.message);
|
|
78
|
+
removePid();
|
|
79
|
+
}
|
|
80
|
+
}
|
|
46
81
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
82
|
+
function updateService() {
|
|
83
|
+
console.log('正在更新 protocol-proxy...');
|
|
84
|
+
exec('npm install -g protocol-proxy@latest', (err, stdout, stderr) => {
|
|
85
|
+
if (err) {
|
|
86
|
+
console.error('更新失败:', err.message);
|
|
87
|
+
process.exit(1);
|
|
88
|
+
}
|
|
89
|
+
if (stdout) console.log(stdout);
|
|
90
|
+
if (stderr) console.error(stderr);
|
|
91
|
+
console.log('更新完成');
|
|
92
|
+
});
|
|
93
|
+
}
|
|
53
94
|
|
|
54
|
-
//
|
|
55
|
-
app.post('/api/proxies', async (req, res) => {
|
|
56
|
-
const { name, port, requireAuth, authToken, target } = req.body;
|
|
95
|
+
// ==================== 启动 ====================
|
|
57
96
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
97
|
+
async function init() {
|
|
98
|
+
const express = require('express');
|
|
99
|
+
const cors = require('cors');
|
|
100
|
+
const configStore = require('./lib/config-store');
|
|
101
|
+
const proxyManager = require('./lib/proxy-manager');
|
|
61
102
|
|
|
62
|
-
const
|
|
103
|
+
const app = express();
|
|
104
|
+
const PORT = process.env.ADMIN_PORT || 3000;
|
|
63
105
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
106
|
+
function openBrowser(url) {
|
|
107
|
+
const platform = os.platform();
|
|
108
|
+
let command;
|
|
109
|
+
if (platform === 'win32') {
|
|
110
|
+
command = `start "" "${url}"`;
|
|
111
|
+
} else if (platform === 'darwin') {
|
|
112
|
+
command = `open "${url}"`;
|
|
113
|
+
} else {
|
|
114
|
+
command = `xdg-open "${url}"`;
|
|
115
|
+
}
|
|
116
|
+
exec(command, (err) => {
|
|
117
|
+
if (err) console.error('[Browser] 打开浏览器失败:', err.message);
|
|
69
118
|
});
|
|
70
119
|
}
|
|
71
120
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
121
|
+
app.use(cors());
|
|
122
|
+
app.use(express.json());
|
|
123
|
+
app.use(express.static(path.join(__dirname, 'public')));
|
|
124
|
+
|
|
125
|
+
// ==================== 管理 API ====================
|
|
126
|
+
|
|
127
|
+
// 获取所有代理配置
|
|
128
|
+
app.get('/api/proxies', (req, res) => {
|
|
129
|
+
const proxies = configStore.getProxies().map(p => ({
|
|
130
|
+
...p,
|
|
131
|
+
target: p.target ? {
|
|
132
|
+
...p.target,
|
|
133
|
+
apiKey: p.target.apiKey ? '***' : '',
|
|
134
|
+
} : null,
|
|
135
|
+
running: proxyManager.isRunning(p.id),
|
|
136
|
+
}));
|
|
137
|
+
res.json(proxies);
|
|
78
138
|
});
|
|
79
139
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
140
|
+
// 获取单个代理配置(含敏感信息)
|
|
141
|
+
app.get('/api/proxies/:id', (req, res) => {
|
|
142
|
+
const proxy = configStore.getProxyById(req.params.id);
|
|
143
|
+
if (!proxy) return res.status(404).json({ error: 'Proxy not found' });
|
|
144
|
+
res.json(proxy);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
// 创建代理
|
|
148
|
+
app.post('/api/proxies', async (req, res) => {
|
|
149
|
+
const { name, port, requireAuth, authToken, target } = req.body;
|
|
150
|
+
|
|
151
|
+
if (!name || !port || !target) {
|
|
152
|
+
return res.status(400).json({ error: 'name, port and target are required' });
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const parsedPort = parseInt(port);
|
|
89
156
|
|
|
90
|
-
//
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
const existing = configStore.getProxyById(req.params.id);
|
|
94
|
-
if (!existing) return res.status(404).json({ error: 'Proxy not found' });
|
|
95
|
-
|
|
96
|
-
const updates = {};
|
|
97
|
-
if (name !== undefined) updates.name = name;
|
|
98
|
-
if (port !== undefined) updates.port = parseInt(port);
|
|
99
|
-
if (requireAuth !== undefined) updates.requireAuth = !!requireAuth;
|
|
100
|
-
if (authToken !== undefined) updates.authToken = authToken || null;
|
|
101
|
-
if (target !== undefined) updates.target = target;
|
|
102
|
-
|
|
103
|
-
// 端口变更时校验冲突
|
|
104
|
-
const needRestart = updates.port !== undefined && updates.port !== existing.port;
|
|
105
|
-
if (needRestart) {
|
|
106
|
-
const conflict = configStore.getProxies().find(p => p.id !== req.params.id && p.port === updates.port);
|
|
107
|
-
if (conflict) {
|
|
157
|
+
// 端口冲突校验
|
|
158
|
+
const existing = configStore.getProxies().find(p => p.port === parsedPort);
|
|
159
|
+
if (existing) {
|
|
108
160
|
return res.status(409).json({
|
|
109
|
-
error: `端口 ${
|
|
161
|
+
error: `端口 ${parsedPort} 已被代理「${existing.name}」占用,请更换端口`,
|
|
110
162
|
});
|
|
111
163
|
}
|
|
112
|
-
}
|
|
113
164
|
|
|
114
|
-
|
|
165
|
+
const proxy = configStore.addProxy({
|
|
166
|
+
name,
|
|
167
|
+
port: parsedPort,
|
|
168
|
+
requireAuth: !!requireAuth,
|
|
169
|
+
authToken: authToken || null,
|
|
170
|
+
target,
|
|
171
|
+
});
|
|
115
172
|
|
|
116
|
-
if (needRestart) {
|
|
117
173
|
try {
|
|
118
|
-
await proxyManager.
|
|
174
|
+
await proxyManager.startProxy(proxy);
|
|
175
|
+
res.status(201).json({ ...proxy, running: true });
|
|
119
176
|
} catch (err) {
|
|
120
|
-
|
|
177
|
+
// 启动失败,回滚已保存的配置
|
|
178
|
+
configStore.removeProxy(proxy.id);
|
|
179
|
+
res.status(500).json({ error: `代理启动失败: ${err.message}` });
|
|
121
180
|
}
|
|
122
|
-
}
|
|
123
|
-
proxyManager.updateProxyConfig(updated);
|
|
124
|
-
}
|
|
181
|
+
});
|
|
125
182
|
|
|
126
|
-
|
|
127
|
-
|
|
183
|
+
// 更新代理
|
|
184
|
+
app.put('/api/proxies/:id', async (req, res) => {
|
|
185
|
+
const { name, port, requireAuth, authToken, target } = req.body;
|
|
186
|
+
const existing = configStore.getProxyById(req.params.id);
|
|
187
|
+
if (!existing) return res.status(404).json({ error: 'Proxy not found' });
|
|
128
188
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
189
|
+
const updates = {};
|
|
190
|
+
if (name !== undefined) updates.name = name;
|
|
191
|
+
if (port !== undefined) updates.port = parseInt(port);
|
|
192
|
+
if (requireAuth !== undefined) updates.requireAuth = !!requireAuth;
|
|
193
|
+
if (authToken !== undefined) updates.authToken = authToken || null;
|
|
194
|
+
if (target !== undefined) updates.target = target;
|
|
133
195
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
196
|
+
// 端口变更时校验冲突
|
|
197
|
+
const needRestart = updates.port !== undefined && updates.port !== existing.port;
|
|
198
|
+
if (needRestart) {
|
|
199
|
+
const conflict = configStore.getProxies().find(p => p.id !== req.params.id && p.port === updates.port);
|
|
200
|
+
if (conflict) {
|
|
201
|
+
return res.status(409).json({
|
|
202
|
+
error: `端口 ${updates.port} 已被代理「${conflict.name}」占用,请更换端口`,
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
}
|
|
138
206
|
|
|
139
|
-
|
|
140
|
-
app.post('/api/proxies/:id/start', async (req, res) => {
|
|
141
|
-
const proxy = configStore.getProxyById(req.params.id);
|
|
142
|
-
if (!proxy) return res.status(404).json({ error: 'Proxy not found' });
|
|
207
|
+
const updated = configStore.updateProxy(req.params.id, updates);
|
|
143
208
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
}
|
|
209
|
+
if (needRestart) {
|
|
210
|
+
try {
|
|
211
|
+
await proxyManager.restartProxy(updated);
|
|
212
|
+
} catch (err) {
|
|
213
|
+
return res.status(500).json({ error: `代理重启失败: ${err.message}` });
|
|
214
|
+
}
|
|
215
|
+
} else {
|
|
216
|
+
proxyManager.updateProxyConfig(updated);
|
|
217
|
+
}
|
|
151
218
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
219
|
+
res.json({ ...updated, running: proxyManager.isRunning(updated.id) });
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
// 删除代理
|
|
223
|
+
app.delete('/api/proxies/:id', async (req, res) => {
|
|
224
|
+
const existing = configStore.getProxyById(req.params.id);
|
|
225
|
+
if (!existing) return res.status(404).json({ error: 'Proxy not found' });
|
|
156
226
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
running: proxyManager.getRunningPorts(),
|
|
161
|
-
total: configStore.getProxies().length,
|
|
227
|
+
await proxyManager.stopProxy(req.params.id);
|
|
228
|
+
configStore.removeProxy(req.params.id);
|
|
229
|
+
res.json({ success: true });
|
|
162
230
|
});
|
|
163
|
-
});
|
|
164
231
|
|
|
165
|
-
//
|
|
166
|
-
app.
|
|
167
|
-
|
|
168
|
-
});
|
|
232
|
+
// 启动/停止代理
|
|
233
|
+
app.post('/api/proxies/:id/start', async (req, res) => {
|
|
234
|
+
const proxy = configStore.getProxyById(req.params.id);
|
|
235
|
+
if (!proxy) return res.status(404).json({ error: 'Proxy not found' });
|
|
169
236
|
|
|
170
|
-
|
|
237
|
+
try {
|
|
238
|
+
await proxyManager.startProxy(proxy);
|
|
239
|
+
res.json({ success: true, running: true });
|
|
240
|
+
} catch (err) {
|
|
241
|
+
res.status(500).json({ error: 'Failed to start proxy', message: err.message });
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
app.post('/api/proxies/:id/stop', async (req, res) => {
|
|
246
|
+
await proxyManager.stopProxy(req.params.id);
|
|
247
|
+
res.json({ success: true, running: false });
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
// 获取运行状态
|
|
251
|
+
app.get('/api/status', (req, res) => {
|
|
252
|
+
res.json({
|
|
253
|
+
running: proxyManager.getRunningPorts(),
|
|
254
|
+
total: configStore.getProxies().length,
|
|
255
|
+
});
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
// 前端首页
|
|
259
|
+
app.get('/', (req, res) => {
|
|
260
|
+
res.sendFile(path.join(__dirname, 'public', 'index.html'));
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
// 启动
|
|
264
|
+
writePid();
|
|
171
265
|
|
|
172
|
-
async function init() {
|
|
173
266
|
// 启动所有已配置的代理
|
|
174
267
|
const proxies = configStore.getProxies();
|
|
175
268
|
for (const proxy of proxies) {
|
|
@@ -191,13 +284,50 @@ async function init() {
|
|
|
191
284
|
// 优雅关闭
|
|
192
285
|
process.on('SIGINT', async () => {
|
|
193
286
|
console.log('\nShutting down...');
|
|
194
|
-
|
|
287
|
+
removePid();
|
|
288
|
+
try {
|
|
289
|
+
const proxyManager = require('./lib/proxy-manager');
|
|
290
|
+
await proxyManager.stopAll();
|
|
291
|
+
} catch {}
|
|
195
292
|
process.exit(0);
|
|
196
293
|
});
|
|
197
294
|
|
|
198
295
|
process.on('SIGTERM', async () => {
|
|
199
|
-
|
|
296
|
+
removePid();
|
|
297
|
+
try {
|
|
298
|
+
const proxyManager = require('./lib/proxy-manager');
|
|
299
|
+
await proxyManager.stopAll();
|
|
300
|
+
} catch {}
|
|
200
301
|
process.exit(0);
|
|
201
302
|
});
|
|
202
303
|
|
|
203
|
-
|
|
304
|
+
// ==================== CLI Dispatch ====================
|
|
305
|
+
|
|
306
|
+
const cmd = process.argv[2];
|
|
307
|
+
|
|
308
|
+
switch (cmd) {
|
|
309
|
+
case 'help':
|
|
310
|
+
showHelp();
|
|
311
|
+
break;
|
|
312
|
+
case '-v':
|
|
313
|
+
case '--version':
|
|
314
|
+
showVersion();
|
|
315
|
+
break;
|
|
316
|
+
case 'update':
|
|
317
|
+
updateService();
|
|
318
|
+
break;
|
|
319
|
+
case 'stop':
|
|
320
|
+
stopService();
|
|
321
|
+
break;
|
|
322
|
+
case 'status':
|
|
323
|
+
showStatus();
|
|
324
|
+
break;
|
|
325
|
+
case 'start':
|
|
326
|
+
case undefined:
|
|
327
|
+
init();
|
|
328
|
+
break;
|
|
329
|
+
default:
|
|
330
|
+
console.error(`未知命令: ${cmd}`);
|
|
331
|
+
showHelp();
|
|
332
|
+
process.exit(1);
|
|
333
|
+
}
|