protocol-proxy 1.0.0 → 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 +268 -137
package/package.json
CHANGED
package/server.js
CHANGED
|
@@ -1,174 +1,268 @@
|
|
|
1
|
-
|
|
1
|
+
#!/usr/bin/env node
|
|
2
2
|
const path = require('path');
|
|
3
|
-
const cors = require('cors');
|
|
4
3
|
const { exec } = require('child_process');
|
|
5
4
|
const os = require('os');
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
+
}
|
|
19
59
|
} else {
|
|
20
|
-
|
|
60
|
+
removePid();
|
|
61
|
+
console.log('服务未运行');
|
|
21
62
|
}
|
|
22
|
-
exec(command, (err) => {
|
|
23
|
-
if (err) console.error('[Browser] 打开浏览器失败:', err.message);
|
|
24
|
-
});
|
|
25
63
|
}
|
|
26
64
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
res.json(proxies);
|
|
44
|
-
});
|
|
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
|
+
}
|
|
45
81
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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
|
+
}
|
|
52
94
|
|
|
53
|
-
//
|
|
54
|
-
app.post('/api/proxies', async (req, res) => {
|
|
55
|
-
const { name, port, requireAuth, authToken, target } = req.body;
|
|
95
|
+
// ==================== 启动 ====================
|
|
56
96
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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');
|
|
60
102
|
|
|
61
|
-
const
|
|
103
|
+
const app = express();
|
|
104
|
+
const PORT = process.env.ADMIN_PORT || 3000;
|
|
62
105
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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);
|
|
68
118
|
});
|
|
69
119
|
}
|
|
70
120
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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);
|
|
77
138
|
});
|
|
78
139
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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);
|
|
88
156
|
|
|
89
|
-
//
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
const existing = configStore.getProxyById(req.params.id);
|
|
93
|
-
if (!existing) return res.status(404).json({ error: 'Proxy not found' });
|
|
94
|
-
|
|
95
|
-
const updates = {};
|
|
96
|
-
if (name !== undefined) updates.name = name;
|
|
97
|
-
if (port !== undefined) updates.port = parseInt(port);
|
|
98
|
-
if (requireAuth !== undefined) updates.requireAuth = !!requireAuth;
|
|
99
|
-
if (authToken !== undefined) updates.authToken = authToken || null;
|
|
100
|
-
if (target !== undefined) updates.target = target;
|
|
101
|
-
|
|
102
|
-
// 端口变更时校验冲突
|
|
103
|
-
const needRestart = updates.port !== undefined && updates.port !== existing.port;
|
|
104
|
-
if (needRestart) {
|
|
105
|
-
const conflict = configStore.getProxies().find(p => p.id !== req.params.id && p.port === updates.port);
|
|
106
|
-
if (conflict) {
|
|
157
|
+
// 端口冲突校验
|
|
158
|
+
const existing = configStore.getProxies().find(p => p.port === parsedPort);
|
|
159
|
+
if (existing) {
|
|
107
160
|
return res.status(409).json({
|
|
108
|
-
error: `端口 ${
|
|
161
|
+
error: `端口 ${parsedPort} 已被代理「${existing.name}」占用,请更换端口`,
|
|
109
162
|
});
|
|
110
163
|
}
|
|
111
|
-
}
|
|
112
164
|
|
|
113
|
-
|
|
165
|
+
const proxy = configStore.addProxy({
|
|
166
|
+
name,
|
|
167
|
+
port: parsedPort,
|
|
168
|
+
requireAuth: !!requireAuth,
|
|
169
|
+
authToken: authToken || null,
|
|
170
|
+
target,
|
|
171
|
+
});
|
|
114
172
|
|
|
115
|
-
if (needRestart) {
|
|
116
173
|
try {
|
|
117
|
-
await proxyManager.
|
|
174
|
+
await proxyManager.startProxy(proxy);
|
|
175
|
+
res.status(201).json({ ...proxy, running: true });
|
|
118
176
|
} catch (err) {
|
|
119
|
-
|
|
177
|
+
// 启动失败,回滚已保存的配置
|
|
178
|
+
configStore.removeProxy(proxy.id);
|
|
179
|
+
res.status(500).json({ error: `代理启动失败: ${err.message}` });
|
|
120
180
|
}
|
|
121
|
-
}
|
|
122
|
-
proxyManager.updateProxyConfig(updated);
|
|
123
|
-
}
|
|
181
|
+
});
|
|
124
182
|
|
|
125
|
-
|
|
126
|
-
|
|
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' });
|
|
127
188
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
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;
|
|
132
195
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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
|
+
}
|
|
137
206
|
|
|
138
|
-
|
|
139
|
-
app.post('/api/proxies/:id/start', async (req, res) => {
|
|
140
|
-
const proxy = configStore.getProxyById(req.params.id);
|
|
141
|
-
if (!proxy) return res.status(404).json({ error: 'Proxy not found' });
|
|
207
|
+
const updated = configStore.updateProxy(req.params.id, updates);
|
|
142
208
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
}
|
|
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
|
+
}
|
|
150
218
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
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' });
|
|
155
226
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
running: proxyManager.getRunningPorts(),
|
|
160
|
-
total: configStore.getProxies().length,
|
|
227
|
+
await proxyManager.stopProxy(req.params.id);
|
|
228
|
+
configStore.removeProxy(req.params.id);
|
|
229
|
+
res.json({ success: true });
|
|
161
230
|
});
|
|
162
|
-
});
|
|
163
231
|
|
|
164
|
-
//
|
|
165
|
-
app.
|
|
166
|
-
|
|
167
|
-
});
|
|
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' });
|
|
168
236
|
|
|
169
|
-
|
|
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();
|
|
170
265
|
|
|
171
|
-
async function init() {
|
|
172
266
|
// 启动所有已配置的代理
|
|
173
267
|
const proxies = configStore.getProxies();
|
|
174
268
|
for (const proxy of proxies) {
|
|
@@ -190,13 +284,50 @@ async function init() {
|
|
|
190
284
|
// 优雅关闭
|
|
191
285
|
process.on('SIGINT', async () => {
|
|
192
286
|
console.log('\nShutting down...');
|
|
193
|
-
|
|
287
|
+
removePid();
|
|
288
|
+
try {
|
|
289
|
+
const proxyManager = require('./lib/proxy-manager');
|
|
290
|
+
await proxyManager.stopAll();
|
|
291
|
+
} catch {}
|
|
194
292
|
process.exit(0);
|
|
195
293
|
});
|
|
196
294
|
|
|
197
295
|
process.on('SIGTERM', async () => {
|
|
198
|
-
|
|
296
|
+
removePid();
|
|
297
|
+
try {
|
|
298
|
+
const proxyManager = require('./lib/proxy-manager');
|
|
299
|
+
await proxyManager.stopAll();
|
|
300
|
+
} catch {}
|
|
199
301
|
process.exit(0);
|
|
200
302
|
});
|
|
201
303
|
|
|
202
|
-
|
|
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
|
+
}
|