llmapi-v2 2.1.0
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/.env.example +40 -0
- package/Dockerfile +17 -0
- package/dist/config.d.ts +48 -0
- package/dist/config.js +98 -0
- package/dist/config.js.map +1 -0
- package/dist/converter/request.d.ts +6 -0
- package/dist/converter/request.js +184 -0
- package/dist/converter/request.js.map +1 -0
- package/dist/converter/response.d.ts +6 -0
- package/dist/converter/response.js +76 -0
- package/dist/converter/response.js.map +1 -0
- package/dist/converter/stream.d.ts +54 -0
- package/dist/converter/stream.js +318 -0
- package/dist/converter/stream.js.map +1 -0
- package/dist/converter/types.d.ts +239 -0
- package/dist/converter/types.js +6 -0
- package/dist/converter/types.js.map +1 -0
- package/dist/data/posts.d.ts +19 -0
- package/dist/data/posts.js +462 -0
- package/dist/data/posts.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +233 -0
- package/dist/index.js.map +1 -0
- package/dist/middleware/api-key-auth.d.ts +6 -0
- package/dist/middleware/api-key-auth.js +76 -0
- package/dist/middleware/api-key-auth.js.map +1 -0
- package/dist/middleware/quota-guard.d.ts +10 -0
- package/dist/middleware/quota-guard.js +27 -0
- package/dist/middleware/quota-guard.js.map +1 -0
- package/dist/middleware/rate-limiter.d.ts +5 -0
- package/dist/middleware/rate-limiter.js +50 -0
- package/dist/middleware/rate-limiter.js.map +1 -0
- package/dist/middleware/request-logger.d.ts +6 -0
- package/dist/middleware/request-logger.js +37 -0
- package/dist/middleware/request-logger.js.map +1 -0
- package/dist/middleware/session-auth.d.ts +19 -0
- package/dist/middleware/session-auth.js +99 -0
- package/dist/middleware/session-auth.js.map +1 -0
- package/dist/providers/aliyun.d.ts +13 -0
- package/dist/providers/aliyun.js +20 -0
- package/dist/providers/aliyun.js.map +1 -0
- package/dist/providers/base-provider.d.ts +36 -0
- package/dist/providers/base-provider.js +133 -0
- package/dist/providers/base-provider.js.map +1 -0
- package/dist/providers/deepseek.d.ts +11 -0
- package/dist/providers/deepseek.js +18 -0
- package/dist/providers/deepseek.js.map +1 -0
- package/dist/providers/registry.d.ts +18 -0
- package/dist/providers/registry.js +98 -0
- package/dist/providers/registry.js.map +1 -0
- package/dist/providers/types.d.ts +17 -0
- package/dist/providers/types.js +3 -0
- package/dist/providers/types.js.map +1 -0
- package/dist/routes/admin.d.ts +1 -0
- package/dist/routes/admin.js +153 -0
- package/dist/routes/admin.js.map +1 -0
- package/dist/routes/auth.d.ts +2 -0
- package/dist/routes/auth.js +318 -0
- package/dist/routes/auth.js.map +1 -0
- package/dist/routes/blog.d.ts +1 -0
- package/dist/routes/blog.js +29 -0
- package/dist/routes/blog.js.map +1 -0
- package/dist/routes/dashboard.d.ts +1 -0
- package/dist/routes/dashboard.js +184 -0
- package/dist/routes/dashboard.js.map +1 -0
- package/dist/routes/messages.d.ts +1 -0
- package/dist/routes/messages.js +309 -0
- package/dist/routes/messages.js.map +1 -0
- package/dist/routes/models.d.ts +1 -0
- package/dist/routes/models.js +39 -0
- package/dist/routes/models.js.map +1 -0
- package/dist/routes/payment.d.ts +1 -0
- package/dist/routes/payment.js +150 -0
- package/dist/routes/payment.js.map +1 -0
- package/dist/routes/sitemap.d.ts +1 -0
- package/dist/routes/sitemap.js +38 -0
- package/dist/routes/sitemap.js.map +1 -0
- package/dist/services/alipay.d.ts +27 -0
- package/dist/services/alipay.js +106 -0
- package/dist/services/alipay.js.map +1 -0
- package/dist/services/database.d.ts +4 -0
- package/dist/services/database.js +170 -0
- package/dist/services/database.js.map +1 -0
- package/dist/services/health-checker.d.ts +13 -0
- package/dist/services/health-checker.js +95 -0
- package/dist/services/health-checker.js.map +1 -0
- package/dist/services/mailer.d.ts +3 -0
- package/dist/services/mailer.js +91 -0
- package/dist/services/mailer.js.map +1 -0
- package/dist/services/metrics.d.ts +56 -0
- package/dist/services/metrics.js +94 -0
- package/dist/services/metrics.js.map +1 -0
- package/dist/services/remote-control.d.ts +20 -0
- package/dist/services/remote-control.js +209 -0
- package/dist/services/remote-control.js.map +1 -0
- package/dist/services/remote-ws.d.ts +5 -0
- package/dist/services/remote-ws.js +143 -0
- package/dist/services/remote-ws.js.map +1 -0
- package/dist/services/usage.d.ts +13 -0
- package/dist/services/usage.js +39 -0
- package/dist/services/usage.js.map +1 -0
- package/dist/utils/errors.d.ts +27 -0
- package/dist/utils/errors.js +48 -0
- package/dist/utils/errors.js.map +1 -0
- package/dist/utils/logger.d.ts +2 -0
- package/dist/utils/logger.js +14 -0
- package/dist/utils/logger.js.map +1 -0
- package/docker-compose.yml +19 -0
- package/package.json +39 -0
- package/public/robots.txt +8 -0
- package/src/config.ts +140 -0
- package/src/converter/request.ts +207 -0
- package/src/converter/response.ts +85 -0
- package/src/converter/stream.ts +373 -0
- package/src/converter/types.ts +257 -0
- package/src/data/posts.ts +474 -0
- package/src/index.ts +219 -0
- package/src/middleware/api-key-auth.ts +82 -0
- package/src/middleware/quota-guard.ts +28 -0
- package/src/middleware/rate-limiter.ts +61 -0
- package/src/middleware/request-logger.ts +36 -0
- package/src/middleware/session-auth.ts +91 -0
- package/src/providers/aliyun.ts +16 -0
- package/src/providers/base-provider.ts +148 -0
- package/src/providers/deepseek.ts +14 -0
- package/src/providers/registry.ts +111 -0
- package/src/providers/types.ts +26 -0
- package/src/routes/admin.ts +169 -0
- package/src/routes/auth.ts +369 -0
- package/src/routes/blog.ts +28 -0
- package/src/routes/dashboard.ts +208 -0
- package/src/routes/messages.ts +346 -0
- package/src/routes/models.ts +37 -0
- package/src/routes/payment.ts +189 -0
- package/src/routes/sitemap.ts +40 -0
- package/src/services/alipay.ts +116 -0
- package/src/services/database.ts +187 -0
- package/src/services/health-checker.ts +115 -0
- package/src/services/mailer.ts +90 -0
- package/src/services/metrics.ts +104 -0
- package/src/services/remote-control.ts +226 -0
- package/src/services/remote-ws.ts +145 -0
- package/src/services/usage.ts +57 -0
- package/src/types/express.d.ts +46 -0
- package/src/utils/errors.ts +44 -0
- package/src/utils/logger.ts +8 -0
- package/tsconfig.json +17 -0
- package/views/pages/404.ejs +14 -0
- package/views/pages/admin.ejs +307 -0
- package/views/pages/blog-post.ejs +378 -0
- package/views/pages/blog.ejs +148 -0
- package/views/pages/dashboard.ejs +441 -0
- package/views/pages/docs.ejs +807 -0
- package/views/pages/index.ejs +416 -0
- package/views/pages/login.ejs +170 -0
- package/views/pages/orders.ejs +111 -0
- package/views/pages/pricing.ejs +379 -0
- package/views/pages/register.ejs +397 -0
- package/views/pages/remote.ejs +334 -0
- package/views/pages/settings.ejs +373 -0
- package/views/partials/header.ejs +70 -0
- package/views/partials/nav.ejs +140 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"metrics.js","sourceRoot":"","sources":["../../src/services/metrics.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Request, Response } from 'express';
|
|
2
|
+
/**
|
|
3
|
+
* Check if the last user message contains a remote-control trigger phrase.
|
|
4
|
+
*/
|
|
5
|
+
export declare function detectRemoteTrigger(body: any): boolean;
|
|
6
|
+
/**
|
|
7
|
+
* Handle remote-control trigger:
|
|
8
|
+
* 1. Check if user already has an active bridge -> return URL
|
|
9
|
+
* 2. If no active bridge -> create session in DB -> return Bash tool_use for installation
|
|
10
|
+
* 3. If no Bash tool available -> return text-only manual instructions
|
|
11
|
+
*/
|
|
12
|
+
export declare function handleRemoteTrigger(req: Request, res: Response): Promise<void>;
|
|
13
|
+
/**
|
|
14
|
+
* API endpoint: browser fetches the API key for a remote session.
|
|
15
|
+
* Called by the remote page to get credentials for direct API access.
|
|
16
|
+
*/
|
|
17
|
+
export declare function getRemoteSessionKey(sessionId: string, userId?: number): Promise<{
|
|
18
|
+
apiKey: string;
|
|
19
|
+
baseUrl: string;
|
|
20
|
+
} | null>;
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.detectRemoteTrigger = detectRemoteTrigger;
|
|
7
|
+
exports.handleRemoteTrigger = handleRemoteTrigger;
|
|
8
|
+
exports.getRemoteSessionKey = getRemoteSessionKey;
|
|
9
|
+
const uuid_1 = require("uuid");
|
|
10
|
+
const crypto_1 = __importDefault(require("crypto"));
|
|
11
|
+
const database_1 = require("./database");
|
|
12
|
+
const logger_1 = require("../utils/logger");
|
|
13
|
+
const remote_ws_1 = require("./remote-ws");
|
|
14
|
+
const TRIGGER_PHRASES = ['开启远程控制', '远程控制', 'enable remote', 'start remote', 'remote control', 'llmapi remote'];
|
|
15
|
+
// i18n
|
|
16
|
+
const ZH = {
|
|
17
|
+
alreadyActive: '🔗 远程控制已在运行中!',
|
|
18
|
+
openLink: '在手机或其他设备上打开以下链接:',
|
|
19
|
+
settingUp: '🔗 正在设置远程控制...',
|
|
20
|
+
sessionCreated: '🔗 远程控制会话已创建!',
|
|
21
|
+
manualInstruct: '请在终端中运行以下命令来启动守护进程:',
|
|
22
|
+
expires: '会话将在 2 小时后过期。',
|
|
23
|
+
};
|
|
24
|
+
const EN = {
|
|
25
|
+
alreadyActive: '🔗 Remote control is already active!',
|
|
26
|
+
openLink: 'Open this link on your phone or another device:',
|
|
27
|
+
settingUp: '🔗 Setting up remote control...',
|
|
28
|
+
sessionCreated: '🔗 Remote control session created!',
|
|
29
|
+
manualInstruct: 'Run this command in your terminal to start the daemon:',
|
|
30
|
+
expires: 'Session expires in 2 hours.',
|
|
31
|
+
};
|
|
32
|
+
function getLastUserText(body) {
|
|
33
|
+
if (!body?.messages?.length)
|
|
34
|
+
return '';
|
|
35
|
+
for (let i = body.messages.length - 1; i >= 0; i--) {
|
|
36
|
+
const msg = body.messages[i];
|
|
37
|
+
if (msg.role !== 'user')
|
|
38
|
+
continue;
|
|
39
|
+
if (typeof msg.content === 'string')
|
|
40
|
+
return msg.content;
|
|
41
|
+
if (Array.isArray(msg.content))
|
|
42
|
+
return msg.content.filter((b) => b.type === 'text').map((b) => b.text).join('');
|
|
43
|
+
}
|
|
44
|
+
return '';
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Check if the last user message contains a remote-control trigger phrase.
|
|
48
|
+
*/
|
|
49
|
+
function detectRemoteTrigger(body) {
|
|
50
|
+
if (!body || !Array.isArray(body.messages) || body.messages.length === 0)
|
|
51
|
+
return false;
|
|
52
|
+
let lastUserMsg = null;
|
|
53
|
+
for (let i = body.messages.length - 1; i >= 0; i--) {
|
|
54
|
+
if (body.messages[i].role === 'user') {
|
|
55
|
+
lastUserMsg = body.messages[i];
|
|
56
|
+
break;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
if (!lastUserMsg)
|
|
60
|
+
return false;
|
|
61
|
+
let text = '';
|
|
62
|
+
if (typeof lastUserMsg.content === 'string') {
|
|
63
|
+
text = lastUserMsg.content;
|
|
64
|
+
}
|
|
65
|
+
else if (Array.isArray(lastUserMsg.content)) {
|
|
66
|
+
for (const block of lastUserMsg.content) {
|
|
67
|
+
if (block.type === 'text' && typeof block.text === 'string')
|
|
68
|
+
text += block.text + ' ';
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
text = text.trim().toLowerCase();
|
|
72
|
+
if (!text)
|
|
73
|
+
return false;
|
|
74
|
+
return TRIGGER_PHRASES.some(phrase => text.includes(phrase.toLowerCase()));
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Handle remote-control trigger:
|
|
78
|
+
* 1. Check if user already has an active bridge -> return URL
|
|
79
|
+
* 2. If no active bridge -> create session in DB -> return Bash tool_use for installation
|
|
80
|
+
* 3. If no Bash tool available -> return text-only manual instructions
|
|
81
|
+
*/
|
|
82
|
+
async function handleRemoteTrigger(req, res) {
|
|
83
|
+
const body = req.body;
|
|
84
|
+
const displayModel = body.model || 'claude-sonnet-4-6';
|
|
85
|
+
const userId = req.userId;
|
|
86
|
+
// Detect user language from the trigger message
|
|
87
|
+
const lastMsg = getLastUserText(body);
|
|
88
|
+
const isChinese = /[\u4e00-\u9fff]/.test(lastMsg);
|
|
89
|
+
const t = isChinese ? ZH : EN;
|
|
90
|
+
// Check if user already has an active bridge
|
|
91
|
+
const existingBridge = (0, remote_ws_1.hasActiveBridge)(userId);
|
|
92
|
+
if (existingBridge) {
|
|
93
|
+
const remoteUrl = `https://llmapi.pro/remote?s=${existingBridge.sessionId}`;
|
|
94
|
+
sendSSE(res, displayModel, [
|
|
95
|
+
{ type: 'text', text: `${t.alreadyActive}\n\n${t.openLink}\n${remoteUrl}\n` },
|
|
96
|
+
]);
|
|
97
|
+
logger_1.logger.info({ sessionId: existingBridge.sessionId, userId }, 'Remote control: reusing existing bridge');
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
// Create new session
|
|
101
|
+
const sessionId = (0, uuid_1.v4)();
|
|
102
|
+
try {
|
|
103
|
+
const pool = (0, database_1.getPool)();
|
|
104
|
+
await pool.execute("INSERT INTO remote_sessions (session_id, user_id, status) VALUES (?, ?, 'pending')", [sessionId, userId]);
|
|
105
|
+
}
|
|
106
|
+
catch (err) {
|
|
107
|
+
logger_1.logger.error({ err }, 'Failed to create remote session');
|
|
108
|
+
res.status(500).json({ type: 'error', error: { type: 'api_error', message: 'Failed to create remote session' } });
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
const remoteUrl = `https://llmapi.pro/remote?s=${sessionId}`;
|
|
112
|
+
// Check if Bash tool is available in the request
|
|
113
|
+
const hasBashTool = Array.isArray(body.tools) && body.tools.some((t) => t.name === 'bash' || t.name === 'Bash');
|
|
114
|
+
const bashCommand = `echo "╔══════════════════════════════════════════════════════════╗" && \\
|
|
115
|
+
echo "║ LLM API Remote Control ║" && \\
|
|
116
|
+
echo "║ ║" && \\
|
|
117
|
+
echo "║ This will: ║" && \\
|
|
118
|
+
echo "║ 1. Install llmapi-remote globally (one-time) ║" && \\
|
|
119
|
+
echo "║ 2. Start a background daemon on this machine ║" && \\
|
|
120
|
+
echo "║ 3. The daemon connects to llmapi.pro via WebSocket ║" && \\
|
|
121
|
+
echo "║ 4. You can then control Claude Code from your phone ║" && \\
|
|
122
|
+
echo "║ ║" && \\
|
|
123
|
+
echo "║ Data stored in: ~/.llmapi-remote/ ║" && \\
|
|
124
|
+
echo "║ To stop: llmapi-remote stop ║" && \\
|
|
125
|
+
echo "║ To remove: npm uninstall -g llmapi-remote ║" && \\
|
|
126
|
+
echo "║ rm -rf ~/.llmapi-remote ║" && \\
|
|
127
|
+
echo "╚══════════════════════════════════════════════════════════╝" && \\
|
|
128
|
+
npm install -g llmapi-remote@latest && \\
|
|
129
|
+
llmapi-remote start --session ${sessionId} --ws wss://llmapi.pro/remote-ws --key $ANTHROPIC_API_KEY`;
|
|
130
|
+
if (hasBashTool) {
|
|
131
|
+
sendSSE(res, displayModel, [
|
|
132
|
+
{ type: 'text', text: `${t.settingUp}\n\n${t.openLink}\n${remoteUrl}\n` },
|
|
133
|
+
{ type: 'tool_use', name: 'Bash', command: bashCommand },
|
|
134
|
+
]);
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
sendSSE(res, displayModel, [
|
|
138
|
+
{ type: 'text', text: `${t.sessionCreated}\n\n${t.manualInstruct}\n\n\`\`\`bash\nnpm install -g llmapi-remote@latest && \\\nllmapi-remote start --session ${sessionId} --ws wss://llmapi.pro/remote-ws --key $ANTHROPIC_API_KEY\n\`\`\`\n\n${t.openLink}\n${remoteUrl}\n\n${t.expires}\n` },
|
|
139
|
+
]);
|
|
140
|
+
}
|
|
141
|
+
logger_1.logger.info({ sessionId, userId, hasBashTool }, 'Remote control session created');
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* API endpoint: browser fetches the API key for a remote session.
|
|
145
|
+
* Called by the remote page to get credentials for direct API access.
|
|
146
|
+
*/
|
|
147
|
+
async function getRemoteSessionKey(sessionId, userId) {
|
|
148
|
+
const pool = (0, database_1.getPool)();
|
|
149
|
+
const [rows] = await pool.execute("SELECT user_id, encrypted_key FROM remote_sessions WHERE session_id = ? AND status = 'active'", [sessionId]);
|
|
150
|
+
const session = rows[0];
|
|
151
|
+
if (!session || !session.encrypted_key)
|
|
152
|
+
return null;
|
|
153
|
+
const apiKey = decryptApiKey(session.encrypted_key, sessionId);
|
|
154
|
+
return { apiKey, baseUrl: process.env.SITE_URL || 'https://llmapi.pro' };
|
|
155
|
+
}
|
|
156
|
+
// Simple encryption: AES-256-CBC with session ID as key material
|
|
157
|
+
function encryptApiKey(apiKey, sessionId) {
|
|
158
|
+
const key = crypto_1.default.createHash('sha256').update(sessionId).digest();
|
|
159
|
+
const iv = crypto_1.default.randomBytes(16);
|
|
160
|
+
const cipher = crypto_1.default.createCipheriv('aes-256-cbc', key, iv);
|
|
161
|
+
let encrypted = cipher.update(apiKey, 'utf8', 'hex');
|
|
162
|
+
encrypted += cipher.final('hex');
|
|
163
|
+
return iv.toString('hex') + ':' + encrypted;
|
|
164
|
+
}
|
|
165
|
+
function decryptApiKey(encrypted, sessionId) {
|
|
166
|
+
const [ivHex, data] = encrypted.split(':');
|
|
167
|
+
const key = crypto_1.default.createHash('sha256').update(sessionId).digest();
|
|
168
|
+
const iv = Buffer.from(ivHex, 'hex');
|
|
169
|
+
const decipher = crypto_1.default.createDecipheriv('aes-256-cbc', key, iv);
|
|
170
|
+
let decrypted = decipher.update(data, 'hex', 'utf8');
|
|
171
|
+
decrypted += decipher.final('utf8');
|
|
172
|
+
return decrypted;
|
|
173
|
+
}
|
|
174
|
+
function sendSSE(res, displayModel, blocks) {
|
|
175
|
+
res.setHeader('Content-Type', 'text/event-stream');
|
|
176
|
+
res.setHeader('Cache-Control', 'no-cache');
|
|
177
|
+
res.setHeader('Connection', 'keep-alive');
|
|
178
|
+
res.flushHeaders();
|
|
179
|
+
const msgId = `msg_${(0, uuid_1.v4)().replace(/-/g, '').slice(0, 24)}`;
|
|
180
|
+
const send = (event, data) => {
|
|
181
|
+
res.write(`event: ${event}\ndata: ${JSON.stringify(data)}\n\n`);
|
|
182
|
+
};
|
|
183
|
+
send('message_start', {
|
|
184
|
+
type: 'message_start',
|
|
185
|
+
message: { id: msgId, type: 'message', role: 'assistant', model: displayModel, content: [], stop_reason: null, stop_sequence: null, usage: { input_tokens: 0, output_tokens: 0 } },
|
|
186
|
+
});
|
|
187
|
+
send('ping', { type: 'ping' });
|
|
188
|
+
let index = 0;
|
|
189
|
+
for (const block of blocks) {
|
|
190
|
+
if (block.type === 'text') {
|
|
191
|
+
send('content_block_start', { type: 'content_block_start', index, content_block: { type: 'text', text: '' } });
|
|
192
|
+
send('content_block_delta', { type: 'content_block_delta', index, delta: { type: 'text_delta', text: block.text } });
|
|
193
|
+
send('content_block_stop', { type: 'content_block_stop', index });
|
|
194
|
+
}
|
|
195
|
+
else if (block.type === 'tool_use') {
|
|
196
|
+
const toolId = `toolu_${(0, uuid_1.v4)().replace(/-/g, '').slice(0, 24)}`;
|
|
197
|
+
const input = { command: block.command, restart: false };
|
|
198
|
+
send('content_block_start', { type: 'content_block_start', index, content_block: { type: 'tool_use', id: toolId, name: block.name, input: {} } });
|
|
199
|
+
send('content_block_delta', { type: 'content_block_delta', index, delta: { type: 'input_json_delta', partial_json: JSON.stringify(input) } });
|
|
200
|
+
send('content_block_stop', { type: 'content_block_stop', index });
|
|
201
|
+
}
|
|
202
|
+
index++;
|
|
203
|
+
}
|
|
204
|
+
const stopReason = blocks.some(b => b.type === 'tool_use') ? 'tool_use' : 'end_turn';
|
|
205
|
+
send('message_delta', { type: 'message_delta', delta: { stop_reason: stopReason, stop_sequence: null }, usage: { output_tokens: 20 } });
|
|
206
|
+
send('message_stop', { type: 'message_stop' });
|
|
207
|
+
res.end();
|
|
208
|
+
}
|
|
209
|
+
//# sourceMappingURL=remote-control.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"remote-control.js","sourceRoot":"","sources":["../../src/services/remote-control.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.initRemoteWs = initRemoteWs;
|
|
7
|
+
exports.hasActiveBridge = hasActiveBridge;
|
|
8
|
+
const ws_1 = require("ws");
|
|
9
|
+
const crypto_1 = __importDefault(require("crypto"));
|
|
10
|
+
const database_1 = require("./database");
|
|
11
|
+
const logger_1 = require("../utils/logger");
|
|
12
|
+
const sessions = new Map();
|
|
13
|
+
function initRemoteWs(server) {
|
|
14
|
+
const wss = new ws_1.WebSocketServer({ server, path: '/remote-ws' });
|
|
15
|
+
wss.on('connection', async (ws, req) => {
|
|
16
|
+
const url = new URL(req.url || '', 'http://localhost');
|
|
17
|
+
const sessionId = url.searchParams.get('session');
|
|
18
|
+
const role = url.searchParams.get('role'); // 'bridge' or 'browser'
|
|
19
|
+
const token = url.searchParams.get('token');
|
|
20
|
+
if (!sessionId || !role) {
|
|
21
|
+
ws.close(4001, 'Missing params');
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
// Authenticate
|
|
25
|
+
let userId;
|
|
26
|
+
try {
|
|
27
|
+
if (role === 'bridge') {
|
|
28
|
+
// Bridge sends API key as token
|
|
29
|
+
if (!token)
|
|
30
|
+
throw new Error('No token');
|
|
31
|
+
const keyPrefix = token.substring(0, 12);
|
|
32
|
+
const keyHash = crypto_1.default.createHash('sha256').update(token).digest('hex');
|
|
33
|
+
const pool = (0, database_1.getPool)();
|
|
34
|
+
const [keys] = await pool.execute('SELECT user_id FROM api_keys WHERE key_prefix = ? AND key_hash = ? AND status = ?', [keyPrefix, keyHash, 'active']);
|
|
35
|
+
const matched = keys[0];
|
|
36
|
+
if (!matched)
|
|
37
|
+
throw new Error('Invalid key');
|
|
38
|
+
userId = matched.user_id;
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
// Browser: session ID itself is the auth (knowing the UUID = access)
|
|
42
|
+
const pool = (0, database_1.getPool)();
|
|
43
|
+
const [rows] = await pool.execute("SELECT user_id FROM remote_sessions WHERE session_id = ? AND status = 'active'", [sessionId]);
|
|
44
|
+
const row = rows[0];
|
|
45
|
+
if (!row)
|
|
46
|
+
throw new Error('Session not found');
|
|
47
|
+
userId = row.user_id;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
catch (err) {
|
|
51
|
+
logger_1.logger.warn({ err: err.message, role, sessionId }, 'Remote WS auth failed');
|
|
52
|
+
ws.close(4003, 'Auth failed');
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
// Get or create in-memory session
|
|
56
|
+
let session = sessions.get(sessionId);
|
|
57
|
+
if (!session) {
|
|
58
|
+
session = { bridge: null, browsers: new Set(), userId };
|
|
59
|
+
sessions.set(sessionId, session);
|
|
60
|
+
}
|
|
61
|
+
if (role === 'bridge') {
|
|
62
|
+
session.bridge = ws;
|
|
63
|
+
const pool = (0, database_1.getPool)();
|
|
64
|
+
await pool.execute("UPDATE remote_sessions SET status = 'active' WHERE session_id = ?", [sessionId]);
|
|
65
|
+
// Notify browsers
|
|
66
|
+
session.browsers.forEach(b => {
|
|
67
|
+
if (b.readyState === ws_1.WebSocket.OPEN)
|
|
68
|
+
b.send(JSON.stringify({ type: 'bridge_connected' }));
|
|
69
|
+
});
|
|
70
|
+
logger_1.logger.info({ sessionId, userId }, 'Remote bridge connected');
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
session.browsers.add(ws);
|
|
74
|
+
// Notify bridge
|
|
75
|
+
if (session.bridge?.readyState === ws_1.WebSocket.OPEN) {
|
|
76
|
+
session.bridge.send(JSON.stringify({ type: 'browser_connected' }));
|
|
77
|
+
}
|
|
78
|
+
// Tell browser if bridge is already connected
|
|
79
|
+
if (session.bridge?.readyState === ws_1.WebSocket.OPEN) {
|
|
80
|
+
ws.send(JSON.stringify({ type: 'bridge_connected' }));
|
|
81
|
+
}
|
|
82
|
+
logger_1.logger.info({ sessionId }, 'Remote browser connected');
|
|
83
|
+
}
|
|
84
|
+
// Relay messages
|
|
85
|
+
ws.on('message', (raw) => {
|
|
86
|
+
const msg = raw.toString();
|
|
87
|
+
if (role === 'bridge') {
|
|
88
|
+
// Forward to all browsers
|
|
89
|
+
session.browsers.forEach(b => { if (b.readyState === ws_1.WebSocket.OPEN)
|
|
90
|
+
b.send(msg); });
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
// Forward to bridge
|
|
94
|
+
if (session.bridge?.readyState === ws_1.WebSocket.OPEN)
|
|
95
|
+
session.bridge.send(msg);
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
ws.on('close', () => {
|
|
99
|
+
if (role === 'bridge') {
|
|
100
|
+
session.bridge = null;
|
|
101
|
+
session.browsers.forEach(b => {
|
|
102
|
+
if (b.readyState === ws_1.WebSocket.OPEN)
|
|
103
|
+
b.send(JSON.stringify({ type: 'bridge_disconnected' }));
|
|
104
|
+
});
|
|
105
|
+
logger_1.logger.info({ sessionId }, 'Remote bridge disconnected');
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
session.browsers.delete(ws);
|
|
109
|
+
if (session.bridge?.readyState === ws_1.WebSocket.OPEN) {
|
|
110
|
+
session.bridge.send(JSON.stringify({ type: 'browser_disconnected' }));
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
// Cleanup empty sessions
|
|
114
|
+
if (!session.bridge && session.browsers.size === 0) {
|
|
115
|
+
sessions.delete(sessionId);
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
// Heartbeat
|
|
120
|
+
setInterval(() => {
|
|
121
|
+
wss.clients.forEach(ws => { if (ws.readyState === ws_1.WebSocket.OPEN)
|
|
122
|
+
ws.ping(); });
|
|
123
|
+
}, 30000);
|
|
124
|
+
// Cleanup old DB sessions every 5 min
|
|
125
|
+
setInterval(async () => {
|
|
126
|
+
try {
|
|
127
|
+
const pool = (0, database_1.getPool)();
|
|
128
|
+
await pool.execute("UPDATE remote_sessions SET status = 'closed', closed_at = NOW() WHERE status = 'active' AND created_at < NOW() - INTERVAL '2 hours'");
|
|
129
|
+
}
|
|
130
|
+
catch { }
|
|
131
|
+
}, 5 * 60000);
|
|
132
|
+
logger_1.logger.info('Remote WebSocket server initialized on /remote-ws');
|
|
133
|
+
}
|
|
134
|
+
// Check if a user has an active bridge connected
|
|
135
|
+
function hasActiveBridge(userId) {
|
|
136
|
+
for (const [sessionId, session] of sessions) {
|
|
137
|
+
if (session.userId === userId && session.bridge?.readyState === ws_1.WebSocket.OPEN) {
|
|
138
|
+
return { sessionId };
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
//# sourceMappingURL=remote-ws.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"remote-ws.js","sourceRoot":"","sources":["../../src/services/remote-ws.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface UsageDetails {
|
|
2
|
+
inputTokens: number;
|
|
3
|
+
outputTokens: number;
|
|
4
|
+
thinkingTokens: number;
|
|
5
|
+
ttftMs: number;
|
|
6
|
+
tokensPerSec: number;
|
|
7
|
+
durationMs: number;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Record API usage and update subscription quota.
|
|
11
|
+
* Called after every successful request (async, non-blocking).
|
|
12
|
+
*/
|
|
13
|
+
export declare function recordUsage(userId: number, apiKeyId: number | null, displayModel: string, providerName: string, backendModel: string, details: UsageDetails): Promise<void>;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.recordUsage = recordUsage;
|
|
4
|
+
const database_1 = require("./database");
|
|
5
|
+
const logger_1 = require("../utils/logger");
|
|
6
|
+
/**
|
|
7
|
+
* Record API usage and update subscription quota.
|
|
8
|
+
* Called after every successful request (async, non-blocking).
|
|
9
|
+
*/
|
|
10
|
+
async function recordUsage(userId, apiKeyId, displayModel, providerName, backendModel, details) {
|
|
11
|
+
const pool = (0, database_1.getPool)();
|
|
12
|
+
try {
|
|
13
|
+
// Insert usage log
|
|
14
|
+
await pool.execute(`
|
|
15
|
+
INSERT INTO usage_logs (
|
|
16
|
+
user_id, api_key_id, model, provider_name,
|
|
17
|
+
input_tokens, output_tokens, thinking_tokens,
|
|
18
|
+
ttft_ms, tokens_per_sec, duration_ms, status
|
|
19
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'success')
|
|
20
|
+
`, [
|
|
21
|
+
userId, apiKeyId, displayModel, providerName,
|
|
22
|
+
details.inputTokens, details.outputTokens, details.thinkingTokens,
|
|
23
|
+
details.ttftMs, details.tokensPerSec, details.durationMs,
|
|
24
|
+
]);
|
|
25
|
+
// Update subscription quota (input + output tokens count toward limit)
|
|
26
|
+
const totalTokens = details.inputTokens + details.outputTokens;
|
|
27
|
+
if (totalTokens > 0) {
|
|
28
|
+
await pool.execute('UPDATE subscriptions SET tokens_used = tokens_used + ? WHERE id = (SELECT id FROM subscriptions WHERE user_id = ? ORDER BY period_start DESC LIMIT 1)', [totalTokens, userId]);
|
|
29
|
+
}
|
|
30
|
+
// Update API key last_used_at
|
|
31
|
+
if (apiKeyId) {
|
|
32
|
+
await pool.execute('UPDATE api_keys SET last_used_at = NOW() WHERE id = ?', [apiKeyId]);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
catch (err) {
|
|
36
|
+
logger_1.logger.error({ err, userId }, 'Failed to record usage');
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=usage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"usage.js","sourceRoot":"","sources":["../../src/services/usage.ts"],"names":[],"mappings":";;AAgBA,kCAwCC;AAxDD,yCAAqC;AACrC,4CAAyC;AAWzC;;;GAGG;AACI,KAAK,UAAU,WAAW,CAC/B,MAAc,EACd,QAAuB,EACvB,YAAoB,EACpB,YAAoB,EACpB,YAAoB,EACpB,OAAqB;IAErB,MAAM,IAAI,GAAG,IAAA,kBAAO,GAAE,CAAC;IAEvB,IAAI,CAAC;QACH,mBAAmB;QACnB,MAAM,IAAI,CAAC,OAAO,CAAC;;;;;;KAMlB,EAAE;YACD,MAAM,EAAE,QAAQ,EAAE,YAAY,EAAE,YAAY;YAC5C,OAAO,CAAC,WAAW,EAAE,OAAO,CAAC,YAAY,EAAE,OAAO,CAAC,cAAc;YACjE,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,YAAY,EAAE,OAAO,CAAC,UAAU;SACzD,CAAC,CAAC;QAEH,uEAAuE;QACvE,MAAM,WAAW,GAAG,OAAO,CAAC,WAAW,GAAG,OAAO,CAAC,YAAY,CAAC;QAC/D,IAAI,WAAW,GAAG,CAAC,EAAE,CAAC;YACpB,MAAM,IAAI,CAAC,OAAO,CAChB,uJAAuJ,EACvJ,CAAC,WAAW,EAAE,MAAM,CAAC,CACtB,CAAC;QACJ,CAAC;QAED,8BAA8B;QAC9B,IAAI,QAAQ,EAAE,CAAC;YACb,MAAM,IAAI,CAAC,OAAO,CAAC,uDAAuD,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC;QAC1F,CAAC;IACH,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,eAAM,CAAC,KAAK,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,EAAE,wBAAwB,CAAC,CAAC;IAC1D,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error class that produces Anthropic API error format responses.
|
|
3
|
+
*/
|
|
4
|
+
export declare class AnthropicError extends Error {
|
|
5
|
+
statusCode: number;
|
|
6
|
+
errorType: string;
|
|
7
|
+
constructor(statusCode: number, errorType: string, message: string);
|
|
8
|
+
toJSON(): {
|
|
9
|
+
type: string;
|
|
10
|
+
error: {
|
|
11
|
+
type: string;
|
|
12
|
+
message: string;
|
|
13
|
+
};
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
export declare class AuthenticationError extends AnthropicError {
|
|
17
|
+
constructor(message?: string);
|
|
18
|
+
}
|
|
19
|
+
export declare class PermissionError extends AnthropicError {
|
|
20
|
+
constructor(message?: string);
|
|
21
|
+
}
|
|
22
|
+
export declare class RateLimitError extends AnthropicError {
|
|
23
|
+
constructor(message?: string);
|
|
24
|
+
}
|
|
25
|
+
export declare class OverloadedError extends AnthropicError {
|
|
26
|
+
constructor(message?: string);
|
|
27
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.OverloadedError = exports.RateLimitError = exports.PermissionError = exports.AuthenticationError = exports.AnthropicError = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Error class that produces Anthropic API error format responses.
|
|
6
|
+
*/
|
|
7
|
+
class AnthropicError extends Error {
|
|
8
|
+
statusCode;
|
|
9
|
+
errorType;
|
|
10
|
+
constructor(statusCode, errorType, message) {
|
|
11
|
+
super(message);
|
|
12
|
+
this.statusCode = statusCode;
|
|
13
|
+
this.errorType = errorType;
|
|
14
|
+
this.name = 'AnthropicError';
|
|
15
|
+
}
|
|
16
|
+
toJSON() {
|
|
17
|
+
return {
|
|
18
|
+
type: 'error',
|
|
19
|
+
error: { type: this.errorType, message: this.message },
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
exports.AnthropicError = AnthropicError;
|
|
24
|
+
class AuthenticationError extends AnthropicError {
|
|
25
|
+
constructor(message = 'Invalid API key.') {
|
|
26
|
+
super(401, 'authentication_error', message);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
exports.AuthenticationError = AuthenticationError;
|
|
30
|
+
class PermissionError extends AnthropicError {
|
|
31
|
+
constructor(message = 'Permission denied.') {
|
|
32
|
+
super(403, 'permission_error', message);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
exports.PermissionError = PermissionError;
|
|
36
|
+
class RateLimitError extends AnthropicError {
|
|
37
|
+
constructor(message = 'Rate limit exceeded.') {
|
|
38
|
+
super(429, 'rate_limit_error', message);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
exports.RateLimitError = RateLimitError;
|
|
42
|
+
class OverloadedError extends AnthropicError {
|
|
43
|
+
constructor(message = 'All backend providers are currently unavailable.') {
|
|
44
|
+
super(503, 'overloaded_error', message);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
exports.OverloadedError = OverloadedError;
|
|
48
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/utils/errors.ts"],"names":[],"mappings":";;;AAAA;;GAEG;AACH,MAAa,cAAe,SAAQ,KAAK;IAE9B;IACA;IAFT,YACS,UAAkB,EAClB,SAAiB,EACxB,OAAe;QAEf,KAAK,CAAC,OAAO,CAAC,CAAC;QAJR,eAAU,GAAV,UAAU,CAAQ;QAClB,cAAS,GAAT,SAAS,CAAQ;QAIxB,IAAI,CAAC,IAAI,GAAG,gBAAgB,CAAC;IAC/B,CAAC;IAED,MAAM;QACJ,OAAO;YACL,IAAI,EAAE,OAAO;YACb,KAAK,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE;SACvD,CAAC;IACJ,CAAC;CACF;AAhBD,wCAgBC;AAED,MAAa,mBAAoB,SAAQ,cAAc;IACrD,YAAY,OAAO,GAAG,kBAAkB;QACtC,KAAK,CAAC,GAAG,EAAE,sBAAsB,EAAE,OAAO,CAAC,CAAC;IAC9C,CAAC;CACF;AAJD,kDAIC;AAED,MAAa,eAAgB,SAAQ,cAAc;IACjD,YAAY,OAAO,GAAG,oBAAoB;QACxC,KAAK,CAAC,GAAG,EAAE,kBAAkB,EAAE,OAAO,CAAC,CAAC;IAC1C,CAAC;CACF;AAJD,0CAIC;AAED,MAAa,cAAe,SAAQ,cAAc;IAChD,YAAY,OAAO,GAAG,sBAAsB;QAC1C,KAAK,CAAC,GAAG,EAAE,kBAAkB,EAAE,OAAO,CAAC,CAAC;IAC1C,CAAC;CACF;AAJD,wCAIC;AAED,MAAa,eAAgB,SAAQ,cAAc;IACjD,YAAY,OAAO,GAAG,kDAAkD;QACtE,KAAK,CAAC,GAAG,EAAE,kBAAkB,EAAE,OAAO,CAAC,CAAC;IAC1C,CAAC;CACF;AAJD,0CAIC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.logger = void 0;
|
|
7
|
+
const pino_1 = __importDefault(require("pino"));
|
|
8
|
+
exports.logger = (0, pino_1.default)({
|
|
9
|
+
level: process.env.LOG_LEVEL || 'info',
|
|
10
|
+
transport: process.env.NODE_ENV === 'development'
|
|
11
|
+
? { target: 'pino-pretty', options: { colorize: true } }
|
|
12
|
+
: undefined,
|
|
13
|
+
});
|
|
14
|
+
//# sourceMappingURL=logger.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.js","sourceRoot":"","sources":["../../src/utils/logger.ts"],"names":[],"mappings":";;;;;;AAAA,gDAAwB;AAEX,QAAA,MAAM,GAAG,IAAA,cAAI,EAAC;IACzB,KAAK,EAAE,OAAO,CAAC,GAAG,CAAC,SAAS,IAAI,MAAM;IACtC,SAAS,EAAE,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,aAAa;QAC/C,CAAC,CAAC,EAAE,MAAM,EAAE,aAAa,EAAE,OAAO,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE;QACxD,CAAC,CAAC,SAAS;CACd,CAAC,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
services:
|
|
2
|
+
app:
|
|
3
|
+
build: .
|
|
4
|
+
container_name: llmapi
|
|
5
|
+
ports:
|
|
6
|
+
- "3103:3000"
|
|
7
|
+
env_file: .env
|
|
8
|
+
restart: unless-stopped
|
|
9
|
+
external_links:
|
|
10
|
+
- postgres:postgres
|
|
11
|
+
networks:
|
|
12
|
+
- default
|
|
13
|
+
- shared
|
|
14
|
+
|
|
15
|
+
networks:
|
|
16
|
+
default:
|
|
17
|
+
shared:
|
|
18
|
+
external: true
|
|
19
|
+
name: bridge
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "llmapi-v2",
|
|
3
|
+
"version": "2.1.0",
|
|
4
|
+
"description": "Claude Code API Relay - Anthropic API compatible proxy for Chinese LLM providers",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "tsx watch src/index.ts",
|
|
8
|
+
"build": "tsc",
|
|
9
|
+
"start": "node dist/index.js",
|
|
10
|
+
"typecheck": "tsc --noEmit"
|
|
11
|
+
},
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"alipay-sdk": "^4.14.0",
|
|
14
|
+
"bcryptjs": "^2.4.3",
|
|
15
|
+
"cors": "^2.8.5",
|
|
16
|
+
"ejs": "^3.1.10",
|
|
17
|
+
"express": "^4.21.0",
|
|
18
|
+
"helmet": "^7.1.0",
|
|
19
|
+
"jsonwebtoken": "^9.0.2",
|
|
20
|
+
"pg": "^8.20.0",
|
|
21
|
+
"pino": "^9.6.0",
|
|
22
|
+
"pino-pretty": "^13.0.0",
|
|
23
|
+
"qrcode": "^1.5.4",
|
|
24
|
+
"uuid": "^10.0.0",
|
|
25
|
+
"ws": "^8.20.0"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@types/bcryptjs": "^2.4.6",
|
|
29
|
+
"@types/cors": "^2.8.17",
|
|
30
|
+
"@types/express": "^4.17.21",
|
|
31
|
+
"@types/jsonwebtoken": "^9.0.7",
|
|
32
|
+
"@types/node": "^20.14.0",
|
|
33
|
+
"@types/pg": "^8.20.0",
|
|
34
|
+
"@types/uuid": "^10.0.0",
|
|
35
|
+
"@types/ws": "^8.18.1",
|
|
36
|
+
"tsx": "^4.19.0",
|
|
37
|
+
"typescript": "^5.6.0"
|
|
38
|
+
}
|
|
39
|
+
}
|