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