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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/server.js +289 -138
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "protocol-proxy",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
4
  "description": "OpenAI / Anthropic 协议转换透明代理",
5
5
  "main": "server.js",
6
6
  "bin": {
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 cors = require('cors');
5
- const { exec } = require('child_process');
3
+ const { exec, spawn } = require('child_process');
6
4
  const os = require('os');
7
- const configStore = require('./lib/config-store');
8
- const proxyManager = require('./lib/proxy-manager');
9
-
10
- const app = express();
11
- const PORT = process.env.ADMIN_PORT || 3000;
12
-
13
- function openBrowser(url) {
14
- const platform = os.platform();
15
- let command;
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
- app.use(cors());
29
- app.use(express.json());
30
- app.use(express.static(path.join(__dirname, 'public')));
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
- app.get('/api/proxies/:id', (req, res) => {
49
- const proxy = configStore.getProxyById(req.params.id);
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
- app.post('/api/proxies', async (req, res) => {
56
- const { name, port, requireAuth, authToken, target } = req.body;
24
+ function isProcessAlive(pid) {
25
+ try { process.kill(pid, 0); return true; } catch { return false; }
26
+ }
57
27
 
58
- if (!name || !port || !target) {
59
- return res.status(400).json({ error: 'name, port and target are required' });
60
- }
28
+ function showHelp() {
29
+ console.log(`
30
+ protocol-proxy - OpenAI / Anthropic 协议转换透明代理
61
31
 
62
- const parsedPort = parseInt(port);
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 existing = configStore.getProxies().find(p => p.port === parsedPort);
66
- if (existing) {
67
- return res.status(409).json({
68
- error: `端口 ${parsedPort} 已被代理「${existing.name}」占用,请更换端口`,
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 proxy = configStore.addProxy({
73
- name,
74
- port: parsedPort,
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
- await proxyManager.startProxy(proxy);
82
- res.status(201).json({ ...proxy, running: true });
89
+ process.kill(pid, 'SIGTERM');
90
+ removePid();
91
+ console.log(`服务已停止 (PID: ${pid})`);
83
92
  } catch (err) {
84
- // 启动失败,回滚已保存的配置
85
- configStore.removeProxy(proxy.id);
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.put('/api/proxies/:id', async (req, res) => {
92
- const { name, port, requireAuth, authToken, target } = req.body;
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) {
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: `端口 ${updates.port} 已被代理「${conflict.name}」占用,请更换端口`,
177
+ error: `端口 ${parsedPort} 已被代理「${existing.name}」占用,请更换端口`,
110
178
  });
111
179
  }
112
- }
113
180
 
114
- const updated = configStore.updateProxy(req.params.id, updates);
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.restartProxy(updated);
190
+ await proxyManager.startProxy(proxy);
191
+ res.status(201).json({ ...proxy, running: true });
119
192
  } catch (err) {
120
- return res.status(500).json({ error: `代理重启失败: ${err.message}` });
193
+ // 启动失败,回滚已保存的配置
194
+ configStore.removeProxy(proxy.id);
195
+ res.status(500).json({ error: `代理启动失败: ${err.message}` });
121
196
  }
122
- } else {
123
- proxyManager.updateProxyConfig(updated);
124
- }
197
+ });
125
198
 
126
- res.json({ ...updated, running: proxyManager.isRunning(updated.id) });
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
- app.delete('/api/proxies/:id', async (req, res) => {
131
- const existing = configStore.getProxyById(req.params.id);
132
- if (!existing) return res.status(404).json({ error: 'Proxy not found' });
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
- await proxyManager.stopProxy(req.params.id);
135
- configStore.removeProxy(req.params.id);
136
- res.json({ success: true });
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
- try {
145
- await proxyManager.startProxy(proxy);
146
- res.json({ success: true, running: true });
147
- } catch (err) {
148
- res.status(500).json({ error: 'Failed to start proxy', message: err.message });
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
- app.post('/api/proxies/:id/stop', async (req, res) => {
153
- await proxyManager.stopProxy(req.params.id);
154
- res.json({ success: true, running: false });
155
- });
235
+ res.json({ ...updated, running: proxyManager.isRunning(updated.id) });
236
+ });
156
237
 
157
- // 获取运行状态
158
- app.get('/api/status', (req, res) => {
159
- res.json({
160
- running: proxyManager.getRunningPorts(),
161
- total: configStore.getProxies().length,
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.get('/', (req, res) => {
167
- res.sendFile(path.join(__dirname, 'public', 'index.html'));
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
- await proxyManager.stopAll();
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
- await proxyManager.stopAll();
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
- init();
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
+ }