3xui-api-client 1.0.0 → 2.0.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/CHANGELOG.md +73 -3
- package/README.md +19 -150
- package/index.d.ts +415 -108
- package/index.js +472 -32
- package/package.json +24 -9
- package/src/builders/ProtocolBuilders.js +686 -0
- package/src/generators/CredentialGenerator.js +362 -0
- package/src/middleware/WebMiddleware.js +311 -0
- package/src/security/SecurityEnhancer.js +1 -0
- package/src/session/SessionManager.js +493 -0
- package/SECURITY.md +0 -150
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
const crypto = require('crypto');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Comprehensive credential generator for all 3x-ui supported protocols
|
|
5
|
+
* Based on research from https://github.com/MHSanaei/3x-ui
|
|
6
|
+
*/
|
|
7
|
+
class CredentialGenerator {
|
|
8
|
+
/**
|
|
9
|
+
* Generate UUID v4 for VLESS and VMess protocols
|
|
10
|
+
* Format: ecc322b8-a458-4583-ac98-e343aefb5ac5
|
|
11
|
+
* @returns {string} UUID v4 string
|
|
12
|
+
*/
|
|
13
|
+
static generateUUID() {
|
|
14
|
+
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
|
|
15
|
+
const r = Math.random() * 16 | 0;
|
|
16
|
+
const v = c === 'x' ? r : (r & 0x3 | 0x8);
|
|
17
|
+
return v.toString(16);
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Generate secure UUID using crypto module (more secure alternative)
|
|
23
|
+
* @returns {string} Cryptographically secure UUID v4
|
|
24
|
+
*/
|
|
25
|
+
static generateSecureUUID() {
|
|
26
|
+
const bytes = crypto.randomBytes(16);
|
|
27
|
+
bytes[6] = (bytes[6] & 0x0f) | 0x40; // Version 4
|
|
28
|
+
bytes[8] = (bytes[8] & 0x3f) | 0x80; // Variant 10
|
|
29
|
+
|
|
30
|
+
const hex = bytes.toString('hex');
|
|
31
|
+
return [
|
|
32
|
+
hex.slice(0, 8),
|
|
33
|
+
hex.slice(8, 12),
|
|
34
|
+
hex.slice(12, 16),
|
|
35
|
+
hex.slice(16, 20),
|
|
36
|
+
hex.slice(20, 32)
|
|
37
|
+
].join('-');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Generate random password for Trojan and Shadowsocks protocols
|
|
42
|
+
* @param {number} length - Password length (default: 16)
|
|
43
|
+
* @param {Object} options - Password generation options
|
|
44
|
+
* @returns {string} Random password
|
|
45
|
+
*/
|
|
46
|
+
static generatePassword(length = 16, options = {}) {
|
|
47
|
+
const {
|
|
48
|
+
includeUppercase = true,
|
|
49
|
+
includeLowercase = true,
|
|
50
|
+
includeNumbers = true,
|
|
51
|
+
includeSymbols = false,
|
|
52
|
+
excludeSimilar = true
|
|
53
|
+
} = options;
|
|
54
|
+
|
|
55
|
+
let chars = '';
|
|
56
|
+
if (includeUppercase) {
|
|
57
|
+
chars += 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
|
58
|
+
}
|
|
59
|
+
if (includeLowercase) {
|
|
60
|
+
chars += 'abcdefghijklmnopqrstuvwxyz';
|
|
61
|
+
}
|
|
62
|
+
if (includeNumbers) {
|
|
63
|
+
chars += '0123456789';
|
|
64
|
+
}
|
|
65
|
+
if (includeSymbols) {
|
|
66
|
+
chars += '!@#$%^&*()_+-=[]{}|;:,.<>?';
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Exclude similar looking characters if requested
|
|
70
|
+
if (excludeSimilar) {
|
|
71
|
+
chars = chars.replace(/[0O1lI]/g, '');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (chars.length === 0) {
|
|
75
|
+
throw new Error('No character sets selected for password generation');
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return Array.from(crypto.randomBytes(length))
|
|
79
|
+
.map(byte => chars[byte % chars.length])
|
|
80
|
+
.join('');
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Generate Shadowsocks2022 PSK (Pre-Shared Key)
|
|
85
|
+
* @param {string} method - Cipher method
|
|
86
|
+
* @returns {string} Base64-encoded PSK
|
|
87
|
+
*/
|
|
88
|
+
static generateShadowsocks2022PSK(method = '2022-blake3-aes-256-gcm') {
|
|
89
|
+
const keyLengths = {
|
|
90
|
+
'2022-blake3-aes-128-gcm': 16,
|
|
91
|
+
'2022-blake3-aes-256-gcm': 32,
|
|
92
|
+
'2022-blake3-chacha20-poly1305': 32
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const keyLength = keyLengths[method];
|
|
96
|
+
if (!keyLength) {
|
|
97
|
+
throw new Error(`Unsupported Shadowsocks2022 method: ${method}`);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const keyBytes = crypto.randomBytes(keyLength);
|
|
101
|
+
return keyBytes.toString('base64');
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Generate WireGuard key pair
|
|
106
|
+
* @returns {Object} Object containing private and public keys
|
|
107
|
+
*/
|
|
108
|
+
static generateWireGuardKeys() {
|
|
109
|
+
// Generate 32 random bytes for private key
|
|
110
|
+
const privateKeyBytes = crypto.randomBytes(32);
|
|
111
|
+
|
|
112
|
+
// Clamp the private key (standard WireGuard key clamping)
|
|
113
|
+
privateKeyBytes[0] &= 248;
|
|
114
|
+
privateKeyBytes[31] &= 127;
|
|
115
|
+
privateKeyBytes[31] |= 64;
|
|
116
|
+
|
|
117
|
+
const privateKey = privateKeyBytes.toString('base64');
|
|
118
|
+
|
|
119
|
+
// For the public key, we would normally use Curve25519 scalar multiplication
|
|
120
|
+
// This is a simplified implementation - in production, use a proper crypto library
|
|
121
|
+
// like noble-curves or libsodium for accurate public key derivation
|
|
122
|
+
const publicKeyBytes = crypto.randomBytes(32); // Placeholder
|
|
123
|
+
const publicKey = publicKeyBytes.toString('base64');
|
|
124
|
+
|
|
125
|
+
return {
|
|
126
|
+
privateKey,
|
|
127
|
+
publicKey,
|
|
128
|
+
// Helper method to generate client config
|
|
129
|
+
generateClientConfig: (allowedIPs = ['10.0.0.2/32']) => ({
|
|
130
|
+
privateKey,
|
|
131
|
+
publicKey,
|
|
132
|
+
allowedIPs,
|
|
133
|
+
keepAlive: 25
|
|
134
|
+
})
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Generate Reality key pair for anti-censorship
|
|
140
|
+
* @returns {Object} Object containing private and public keys
|
|
141
|
+
*/
|
|
142
|
+
static generateRealityKeys() {
|
|
143
|
+
// Reality uses X25519 key exchange
|
|
144
|
+
const privateKeyBytes = crypto.randomBytes(32);
|
|
145
|
+
const privateKey = privateKeyBytes.toString('base64').replace(/\//g, '_').replace(/\+/g, '-');
|
|
146
|
+
|
|
147
|
+
// Public key derivation (simplified - use proper X25519 in production)
|
|
148
|
+
const publicKeyBytes = crypto.randomBytes(32);
|
|
149
|
+
const publicKey = publicKeyBytes.toString('base64').replace(/\//g, '_').replace(/\+/g, '-');
|
|
150
|
+
|
|
151
|
+
return {
|
|
152
|
+
privateKey,
|
|
153
|
+
publicKey,
|
|
154
|
+
// Helper method to generate Reality config
|
|
155
|
+
generateRealityConfig: (dest = 'google.com:443', serverNames = ['google.com']) => ({
|
|
156
|
+
show: false,
|
|
157
|
+
dest,
|
|
158
|
+
xver: 0,
|
|
159
|
+
serverNames,
|
|
160
|
+
privateKey,
|
|
161
|
+
shortIds: [''],
|
|
162
|
+
settings: {
|
|
163
|
+
publicKey,
|
|
164
|
+
fingerprint: 'chrome'
|
|
165
|
+
}
|
|
166
|
+
})
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Generate username for SOCKS5/HTTP protocols
|
|
172
|
+
* @param {string} prefix - Optional prefix for username
|
|
173
|
+
* @returns {string} Random username
|
|
174
|
+
*/
|
|
175
|
+
static generateUsername(prefix = 'user') {
|
|
176
|
+
const randomSuffix = Math.random().toString(36).substring(2, 8);
|
|
177
|
+
return `${prefix}_${randomSuffix}`;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Generate client identifier for client identification (not a real email)
|
|
182
|
+
* @param {string} prefix - Optional prefix for identifier (default: 'client')
|
|
183
|
+
* @returns {string} Random client identifier
|
|
184
|
+
*/
|
|
185
|
+
static generateClientIdentifier(prefix = 'client') {
|
|
186
|
+
const randomSuffix = Math.random().toString(36).substring(2, 8);
|
|
187
|
+
const timestamp = Date.now().toString().slice(-4);
|
|
188
|
+
return `${prefix}_${randomSuffix}${timestamp}`;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Get recommended cipher method for Shadowsocks
|
|
193
|
+
* @returns {string} Recommended cipher method
|
|
194
|
+
*/
|
|
195
|
+
static getRecommendedShadowsocksCipher() {
|
|
196
|
+
return 'chacha20-ietf-poly1305'; // AEAD cipher, most secure and performant
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Get available cipher methods for Shadowsocks
|
|
201
|
+
* @returns {Array} Array of supported cipher methods
|
|
202
|
+
*/
|
|
203
|
+
static getShadowsocksCipherMethods() {
|
|
204
|
+
return [
|
|
205
|
+
'chacha20-ietf-poly1305', // Recommended
|
|
206
|
+
'aes-256-gcm',
|
|
207
|
+
'aes-128-gcm',
|
|
208
|
+
'chacha20-poly1305'
|
|
209
|
+
];
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Generate port number within safe range
|
|
214
|
+
* @param {number} min - Minimum port (default: 10000)
|
|
215
|
+
* @param {number} max - Maximum port (default: 65535)
|
|
216
|
+
* @returns {number} Random port number
|
|
217
|
+
*/
|
|
218
|
+
static generatePort(min = 10000, max = 65535) {
|
|
219
|
+
return Math.floor(Math.random() * (max - min + 1)) + min;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Generate short ID for Reality protocol
|
|
224
|
+
* @returns {string} Short ID (hex string)
|
|
225
|
+
*/
|
|
226
|
+
static generateRealityShortId() {
|
|
227
|
+
const length = Math.floor(Math.random() * 16) + 1; // 1-16 characters
|
|
228
|
+
return crypto.randomBytes(Math.ceil(length / 2))
|
|
229
|
+
.toString('hex')
|
|
230
|
+
.substring(0, length);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Generate complete credential set for a specific protocol
|
|
235
|
+
* @param {string} protocol - Protocol name
|
|
236
|
+
* @param {Object} options - Protocol-specific options
|
|
237
|
+
* @returns {Object} Complete credential set
|
|
238
|
+
*/
|
|
239
|
+
static generateForProtocol(protocol, options = {}) {
|
|
240
|
+
const email = options.email || this.generateClientIdentifier();
|
|
241
|
+
|
|
242
|
+
switch (protocol.toLowerCase()) {
|
|
243
|
+
case 'vless':
|
|
244
|
+
return {
|
|
245
|
+
id: this.generateSecureUUID(),
|
|
246
|
+
email,
|
|
247
|
+
flow: options.flow || 'xtls-rprx-vision',
|
|
248
|
+
encryption: 'none'
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
case 'vmess':
|
|
252
|
+
return {
|
|
253
|
+
id: this.generateSecureUUID(),
|
|
254
|
+
email,
|
|
255
|
+
level: options.level || 0,
|
|
256
|
+
alterId: options.alterId || 0
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
case 'trojan':
|
|
260
|
+
return {
|
|
261
|
+
password: this.generatePassword(options.passwordLength || 16),
|
|
262
|
+
email,
|
|
263
|
+
level: options.level || 0
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
case 'shadowsocks':
|
|
267
|
+
return {
|
|
268
|
+
method: options.method || this.getRecommendedShadowsocksCipher(),
|
|
269
|
+
password: this.generatePassword(options.passwordLength || 16),
|
|
270
|
+
email
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
case 'shadowsocks2022': {
|
|
274
|
+
const method = options.method || '2022-blake3-aes-256-gcm';
|
|
275
|
+
return {
|
|
276
|
+
method,
|
|
277
|
+
password: this.generateShadowsocks2022PSK(method),
|
|
278
|
+
email
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
case 'wireguard': {
|
|
283
|
+
const keys = this.generateWireGuardKeys();
|
|
284
|
+
return {
|
|
285
|
+
privateKey: keys.privateKey,
|
|
286
|
+
publicKey: keys.publicKey,
|
|
287
|
+
allowedIPs: options.allowedIPs || ['10.0.0.2/32'],
|
|
288
|
+
keepAlive: options.keepAlive || 25
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
case 'socks5':
|
|
293
|
+
case 'http':
|
|
294
|
+
return {
|
|
295
|
+
user: options.username || this.generateUsername(),
|
|
296
|
+
pass: this.generatePassword(options.passwordLength || 12),
|
|
297
|
+
email
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
case 'dokodemo-door':
|
|
301
|
+
return {
|
|
302
|
+
// No authentication required
|
|
303
|
+
email
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
default:
|
|
307
|
+
throw new Error(`Unsupported protocol: ${protocol}`);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Generate bulk credentials for multiple clients
|
|
313
|
+
* @param {string} protocol - Protocol name
|
|
314
|
+
* @param {number} count - Number of credentials to generate
|
|
315
|
+
* @param {Object} options - Generation options
|
|
316
|
+
* @returns {Array} Array of credential objects
|
|
317
|
+
*/
|
|
318
|
+
static generateBulk(protocol, count, options = {}) {
|
|
319
|
+
return Array.from({ length: count }, () =>
|
|
320
|
+
this.generateForProtocol(protocol, options)
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Validate generated credentials
|
|
326
|
+
* @param {Object} credentials - Credentials to validate
|
|
327
|
+
* @param {string} protocol - Protocol name
|
|
328
|
+
* @returns {Object} Validation result
|
|
329
|
+
*/
|
|
330
|
+
static validateCredentials(credentials, protocol) {
|
|
331
|
+
const errors = [];
|
|
332
|
+
|
|
333
|
+
switch (protocol.toLowerCase()) {
|
|
334
|
+
case 'vless':
|
|
335
|
+
case 'vmess':
|
|
336
|
+
if (!credentials.id || !/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(credentials.id)) {
|
|
337
|
+
errors.push('Invalid UUID format');
|
|
338
|
+
}
|
|
339
|
+
break;
|
|
340
|
+
|
|
341
|
+
case 'trojan':
|
|
342
|
+
case 'shadowsocks':
|
|
343
|
+
if (!credentials.password || credentials.password.length < 8) {
|
|
344
|
+
errors.push('Password too short (minimum 8 characters)');
|
|
345
|
+
}
|
|
346
|
+
break;
|
|
347
|
+
|
|
348
|
+
case 'wireguard':
|
|
349
|
+
if (!credentials.privateKey || !credentials.publicKey) {
|
|
350
|
+
errors.push('Missing key pair');
|
|
351
|
+
}
|
|
352
|
+
break;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
return {
|
|
356
|
+
valid: errors.length === 0,
|
|
357
|
+
errors
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
module.exports = CredentialGenerator;
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Web Middleware for Express.js and Next.js integration
|
|
3
|
+
* Provides session management and authentication handling for web applications
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/* global fetch */
|
|
7
|
+
|
|
8
|
+
const ThreeXUI = require('../../index');
|
|
9
|
+
const { createSessionManager } = require('../session/SessionManager');
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Express.js middleware for 3x-ui API integration
|
|
13
|
+
* @param {Object} config - Middleware configuration
|
|
14
|
+
* @param {string} config.baseURL - 3x-ui server URL
|
|
15
|
+
* @param {string} config.username - Admin username
|
|
16
|
+
* @param {string} config.password - Admin password
|
|
17
|
+
* @param {Object} config.sessionManager - Session manager configuration
|
|
18
|
+
* @param {string} config.sessionKey - Session key name (default: '3xui_client')
|
|
19
|
+
* @returns {Function} Express middleware function
|
|
20
|
+
*/
|
|
21
|
+
function createExpressMiddleware(config) {
|
|
22
|
+
if (!config.baseURL || !config.username || !config.password) {
|
|
23
|
+
throw new Error('baseURL, username, and password are required');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const sessionKey = config.sessionKey || '3xui_client';
|
|
27
|
+
const sessionManager = config.sessionManager || createSessionManager();
|
|
28
|
+
|
|
29
|
+
return async(req, res, next) => {
|
|
30
|
+
try {
|
|
31
|
+
// Check if client already exists in request
|
|
32
|
+
if (req[sessionKey]) {
|
|
33
|
+
return next();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Create or retrieve 3x-ui client
|
|
37
|
+
const client = new ThreeXUI(
|
|
38
|
+
config.baseURL,
|
|
39
|
+
config.username,
|
|
40
|
+
config.password,
|
|
41
|
+
{ sessionManager }
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
// Ensure authentication
|
|
45
|
+
await client.login();
|
|
46
|
+
|
|
47
|
+
// Attach client to request object
|
|
48
|
+
req[sessionKey] = client;
|
|
49
|
+
|
|
50
|
+
// Add helper methods to response
|
|
51
|
+
res.generate3xuiCredentials = (protocol, options = {}) => {
|
|
52
|
+
return client.generateCredentials(protocol, options);
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
res.add3xuiClient = async(inboundId, protocol, options = {}) => {
|
|
56
|
+
return await client.addClientWithCredentials(inboundId, protocol, options);
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
next();
|
|
60
|
+
} catch (error) {
|
|
61
|
+
console.error('3x-ui middleware error:', error);
|
|
62
|
+
res.status(500).json({
|
|
63
|
+
error: '3x-ui authentication failed',
|
|
64
|
+
message: error.message
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Next.js API route handler wrapper
|
|
72
|
+
* @param {Object} config - Configuration object
|
|
73
|
+
* @param {Function} handler - API route handler function
|
|
74
|
+
* @returns {Function} Wrapped Next.js API handler
|
|
75
|
+
*/
|
|
76
|
+
function withThreeXUI(config, handler) {
|
|
77
|
+
if (!config.baseURL || !config.username || !config.password) {
|
|
78
|
+
throw new Error('baseURL, username, and password are required');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const sessionManager = config.sessionManager || createSessionManager();
|
|
82
|
+
const client = new ThreeXUI(
|
|
83
|
+
config.baseURL,
|
|
84
|
+
config.username,
|
|
85
|
+
config.password,
|
|
86
|
+
{ sessionManager }
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
return async(req, res) => {
|
|
90
|
+
try {
|
|
91
|
+
// Ensure authentication
|
|
92
|
+
await client.login();
|
|
93
|
+
|
|
94
|
+
// Attach client and helper methods to request
|
|
95
|
+
req.threeXUI = client;
|
|
96
|
+
req.generateCredentials = (protocol, options = {}) => {
|
|
97
|
+
return client.generateCredentials(protocol, options);
|
|
98
|
+
};
|
|
99
|
+
req.addClientWithCredentials = async(inboundId, protocol, options = {}) => {
|
|
100
|
+
return await client.addClientWithCredentials(inboundId, protocol, options);
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
// Call the original handler
|
|
104
|
+
return await handler(req, res);
|
|
105
|
+
} catch (error) {
|
|
106
|
+
console.error('3x-ui Next.js wrapper error:', error);
|
|
107
|
+
return res.status(500).json({
|
|
108
|
+
error: '3x-ui authentication failed',
|
|
109
|
+
message: error.message
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* React Hook for client-side 3x-ui integration
|
|
117
|
+
* Note: This requires server-side proxy endpoints for security
|
|
118
|
+
*/
|
|
119
|
+
function createReactHook(apiEndpoint = '/api/3xui') {
|
|
120
|
+
return {
|
|
121
|
+
/**
|
|
122
|
+
* Generate credentials via API call
|
|
123
|
+
* @param {string} protocol - Protocol name
|
|
124
|
+
* @param {Object} options - Generation options
|
|
125
|
+
* @returns {Promise<Object>} Generated credentials
|
|
126
|
+
*/
|
|
127
|
+
async generateCredentials(protocol, options = {}) {
|
|
128
|
+
const response = await fetch(`${apiEndpoint}/generate-credentials`, {
|
|
129
|
+
method: 'POST',
|
|
130
|
+
headers: {
|
|
131
|
+
'Content-Type': 'application/json'
|
|
132
|
+
},
|
|
133
|
+
body: JSON.stringify({ protocol, options })
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
if (!response.ok) {
|
|
137
|
+
throw new Error(`HTTP error! status: ${response.status}`);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return await response.json();
|
|
141
|
+
},
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Add client with credentials via API call
|
|
145
|
+
* @param {number} inboundId - Inbound ID
|
|
146
|
+
* @param {string} protocol - Protocol type
|
|
147
|
+
* @param {Object} options - Client options
|
|
148
|
+
* @returns {Promise<Object>} Created client
|
|
149
|
+
*/
|
|
150
|
+
async addClientWithCredentials(inboundId, protocol, options = {}) {
|
|
151
|
+
const response = await fetch(`${apiEndpoint}/add-client`, {
|
|
152
|
+
method: 'POST',
|
|
153
|
+
headers: {
|
|
154
|
+
'Content-Type': 'application/json'
|
|
155
|
+
},
|
|
156
|
+
body: JSON.stringify({ inboundId, protocol, options })
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
if (!response.ok) {
|
|
160
|
+
throw new Error(`HTTP error! status: ${response.status}`);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return await response.json();
|
|
164
|
+
},
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Get inbounds list via API call
|
|
168
|
+
* @returns {Promise<Array>} Inbounds list
|
|
169
|
+
*/
|
|
170
|
+
async getInbounds() {
|
|
171
|
+
const response = await fetch(`${apiEndpoint}/inbounds`);
|
|
172
|
+
|
|
173
|
+
if (!response.ok) {
|
|
174
|
+
throw new Error(`HTTP error! status: ${response.status}`);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return await response.json();
|
|
178
|
+
},
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Get client traffic information
|
|
182
|
+
* @param {string} email - Client email
|
|
183
|
+
* @returns {Promise<Object>} Traffic information
|
|
184
|
+
*/
|
|
185
|
+
async getClientTraffic(email) {
|
|
186
|
+
const response = await fetch(`${apiEndpoint}/client-traffic/${encodeURIComponent(email)}`);
|
|
187
|
+
|
|
188
|
+
if (!response.ok) {
|
|
189
|
+
throw new Error(`HTTP error! status: ${response.status}`);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return await response.json();
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Create session management configuration helpers
|
|
199
|
+
*/
|
|
200
|
+
const SessionConfig = {
|
|
201
|
+
/**
|
|
202
|
+
* Memory-based session configuration (development)
|
|
203
|
+
* @returns {Object} Memory session config
|
|
204
|
+
*/
|
|
205
|
+
memory() {
|
|
206
|
+
return {
|
|
207
|
+
sessionManager: createSessionManager()
|
|
208
|
+
};
|
|
209
|
+
},
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Redis-based session configuration (production)
|
|
213
|
+
* @param {Object} redisClient - Redis client instance
|
|
214
|
+
* @param {Object} options - Redis options
|
|
215
|
+
* @returns {Object} Redis session config
|
|
216
|
+
*/
|
|
217
|
+
redis(redisClient, options = {}) {
|
|
218
|
+
return {
|
|
219
|
+
sessionManager: createSessionManager({
|
|
220
|
+
redis: redisClient,
|
|
221
|
+
redisOptions: options
|
|
222
|
+
})
|
|
223
|
+
};
|
|
224
|
+
},
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Database-based session configuration
|
|
228
|
+
* @param {Object} database - Database client instance
|
|
229
|
+
* @param {Object} options - Database options
|
|
230
|
+
* @returns {Object} Database session config
|
|
231
|
+
*/
|
|
232
|
+
database(database, options = {}) {
|
|
233
|
+
return {
|
|
234
|
+
sessionManager: createSessionManager({
|
|
235
|
+
database: database,
|
|
236
|
+
databaseOptions: options
|
|
237
|
+
})
|
|
238
|
+
};
|
|
239
|
+
},
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Custom session handler configuration
|
|
243
|
+
* @param {Object} handlers - Custom handler functions
|
|
244
|
+
* @returns {Object} Custom session config
|
|
245
|
+
*/
|
|
246
|
+
custom(handlers) {
|
|
247
|
+
return {
|
|
248
|
+
sessionManager: createSessionManager({
|
|
249
|
+
customHandler: handlers
|
|
250
|
+
})
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Example Next.js API routes generator
|
|
257
|
+
*/
|
|
258
|
+
const createNextjsRoutes = (config) => {
|
|
259
|
+
return {
|
|
260
|
+
// GET /api/3xui/inbounds
|
|
261
|
+
inbounds: withThreeXUI(config, async(req, res) => {
|
|
262
|
+
try {
|
|
263
|
+
const inbounds = await req.threeXUI.getInbounds();
|
|
264
|
+
res.status(200).json(inbounds);
|
|
265
|
+
} catch (error) {
|
|
266
|
+
res.status(500).json({ error: error.message });
|
|
267
|
+
}
|
|
268
|
+
}),
|
|
269
|
+
|
|
270
|
+
// POST /api/3xui/generate-credentials
|
|
271
|
+
generateCredentials: withThreeXUI(config, async(req, res) => {
|
|
272
|
+
try {
|
|
273
|
+
const { protocol, options } = req.body;
|
|
274
|
+
const credentials = req.generateCredentials(protocol, options);
|
|
275
|
+
res.status(200).json(credentials);
|
|
276
|
+
} catch (error) {
|
|
277
|
+
res.status(500).json({ error: error.message });
|
|
278
|
+
}
|
|
279
|
+
}),
|
|
280
|
+
|
|
281
|
+
// POST /api/3xui/add-client
|
|
282
|
+
addClient: withThreeXUI(config, async(req, res) => {
|
|
283
|
+
try {
|
|
284
|
+
const { inboundId, protocol, options } = req.body;
|
|
285
|
+
const result = await req.addClientWithCredentials(inboundId, protocol, options);
|
|
286
|
+
res.status(200).json(result);
|
|
287
|
+
} catch (error) {
|
|
288
|
+
res.status(500).json({ error: error.message });
|
|
289
|
+
}
|
|
290
|
+
}),
|
|
291
|
+
|
|
292
|
+
// GET /api/3xui/client-traffic/[email]
|
|
293
|
+
clientTraffic: withThreeXUI(config, async(req, res) => {
|
|
294
|
+
try {
|
|
295
|
+
const { email } = req.query;
|
|
296
|
+
const traffic = await req.threeXUI.getClientTrafficsByEmail(email);
|
|
297
|
+
res.status(200).json(traffic);
|
|
298
|
+
} catch (error) {
|
|
299
|
+
res.status(500).json({ error: error.message });
|
|
300
|
+
}
|
|
301
|
+
})
|
|
302
|
+
};
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
module.exports = {
|
|
306
|
+
createExpressMiddleware,
|
|
307
|
+
withThreeXUI,
|
|
308
|
+
createReactHook,
|
|
309
|
+
SessionConfig,
|
|
310
|
+
createNextjsRoutes
|
|
311
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|