3xui-api-client 2.1.0 → 2.1.1

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/index.js CHANGED
@@ -1,715 +1,1013 @@
1
- const axios = require('axios');
2
- const CredentialGenerator = require('./src/generators/CredentialGenerator');
3
- const { SessionManager, createSessionManager } = require('./src/session/SessionManager');
4
- const {
5
- InputValidator,
6
- SecurityMonitor,
7
- SecureHeaders,
8
- CredentialSecurity,
9
- ErrorSecurity
10
- } = require('./src/security/SecurityEnhancer');
11
-
12
- /**
13
- * 3X-UI API Client Library
14
- *
15
- * A Node.js client for managing 3x-ui panel APIs with automatic session management.
16
- * Now includes credential generation and advanced session management for web applications.
17
- *
18
- * @class ThreeXUI
19
- * @version 2.0.0
20
- * @author Helitha Guruge
21
- */
22
- class ThreeXUI {
23
- /**
24
- * Creates a new ThreeXUI client instance
25
- *
26
- * @param {string} baseURL - The base URL of your 3x-ui server (e.g., 'https://your-server.com')
27
- * @param {string} username - Admin username for authentication
28
- * @param {string} password - Admin password for authentication
29
- * @param {Object} options - Configuration options
30
- * @param {Object} options.sessionManager - Session management configuration
31
- * @param {boolean} options.autoGenerateCredentials - Enable automatic credential generation
32
- * @param {number} options.timeout - Request timeout in milliseconds (default: 30000)
33
- * @throws {Error} If baseURL, username, or password is missing
34
- */
35
- constructor(baseURL, username, password, options = {}) {
36
- if (!baseURL) {
37
- throw new Error('baseURL is required');
38
- }
39
- if (!username) {
40
- throw new Error('username is required');
41
- }
42
- if (!password) {
43
- throw new Error('password is required');
44
- }
45
-
46
- // Apply security validations
47
- this.baseURL = InputValidator.validateURL(baseURL);
48
- this.username = InputValidator.validateUsername(username);
49
- this.password = InputValidator.validatePassword(password);
50
- this.cookie = null;
51
- this.options = options;
52
- this.loginMutex = false; // Add mutex to prevent concurrent logins
53
- this.loginRetryCount = 0; // Add retry counter
54
- this.maxLoginRetries = 3; // Maximum login attempts
55
-
56
- // Initialize security monitoring
57
- this.securityMonitor = new SecurityMonitor({
58
- maxRequestsPerMinute: options.maxRequestsPerMinute || 60,
59
- maxLoginAttemptsPerHour: options.maxLoginAttemptsPerHour || 10
60
- });
61
-
62
- // Security configuration
63
- this.isDevelopment = options.isDevelopment || process.env.NODE_ENV === 'development';
64
-
65
- // Initialize session manager if provided
66
- if (options.sessionManager) {
67
- this.sessionManager = options.sessionManager instanceof SessionManager
68
- ? options.sessionManager
69
- : createSessionManager(options.sessionManager);
70
- } else {
71
- // Default to memory-based session management
72
- this.sessionManager = createSessionManager();
73
- }
74
-
75
- // Create axios instance with security best practices
76
- this.api = axios.create({
77
- baseURL: this.baseURL,
78
- timeout: options.timeout || 30000, // 30 second timeout
79
- maxRedirects: 5,
80
- validateStatus: (status) => status >= 200 && status < 300,
81
- headers: SecureHeaders.getSecureHeaders({
82
- userAgent: options.userAgent || '3xui-api-client/2.0.0 (Security-Enhanced)',
83
- enableCSP: options.enableCSP || false
84
- })
85
- });
86
-
87
- // Add request interceptor for security headers
88
- this.api.interceptors.request.use((config) => {
89
- // Add security headers
90
- config.headers['X-Requested-With'] = 'XMLHttpRequest';
91
- return config;
92
- });
93
-
94
- // Add response interceptor for error handling
95
- this.api.interceptors.response.use(
96
- (response) => response,
97
- (error) => {
98
- if (error.code === 'ECONNABORTED') {
99
- throw new Error('Request timeout - server took too long to respond');
100
- }
101
- if (error.code === 'ENOTFOUND') {
102
- throw new Error(`Cannot connect to server: ${this.baseURL}`);
103
- }
104
- throw error;
105
- }
106
- );
107
- }
108
-
109
- /**
110
- * Login with session management support
111
- * @param {boolean} forceRefresh - Force a new login even if session exists
112
- * @returns {Object} Login response
113
- */
114
- async login(forceRefresh = false) {
115
- // Check rate limiting first
116
- const identifier = CredentialSecurity.hashForLogging(this.username);
117
- if (!this.securityMonitor.checkRateLimit(identifier, 'login')) {
118
- const error = new Error('Rate limit exceeded for login attempts');
119
- ErrorSecurity.logError(error, { username: this.username, baseURL: this.baseURL });
120
- throw error;
121
- }
122
-
123
- // Increment retry count
124
- this.loginRetryCount++;
125
- // Check for existing valid session first
126
- if (!forceRefresh && this.sessionManager) {
127
- const existingSession = await this.sessionManager.getSession(this.baseURL, this.username);
128
- if (existingSession && existingSession.cookie) {
129
- this.cookie = existingSession.cookie;
130
- this.api.defaults.headers.Cookie = this.cookie;
131
- // Reset retry count on successful session restore
132
- this.loginRetryCount = 0;
133
- return {
134
- success: true,
135
- fromCache: true,
136
- data: { msg: 'Session restored from cache' }
137
- };
138
- }
139
- }
140
-
141
- try {
142
- const params = new URLSearchParams();
143
- params.append('username', this.username);
144
- params.append('password', this.password);
145
-
146
- const response = await this.api.post('/login', params, {
147
- headers: {
148
- 'Content-Type': 'application/x-www-form-urlencoded'
149
- }
150
- });
151
-
152
- if (response.data.success) {
153
- const cookies = response.headers['set-cookie'];
154
- if (cookies && cookies.length > 0) {
155
- this.cookie = cookies[0].split(';')[0];
156
- this.api.defaults.headers.Cookie = this.cookie;
157
-
158
- // Store session if session manager is available
159
- if (this.sessionManager) {
160
- try {
161
- await this.sessionManager.storeSession(this.baseURL, this.username, {
162
- cookie: this.cookie,
163
- loginTime: new Date().toISOString()
164
- });
165
- } catch (sessionError) {
166
- console.warn('Failed to store session, continuing without cache:', sessionError.message);
167
- }
168
- }
169
- // Reset retry count on successful login
170
- this.loginRetryCount = 0;
171
- } else {
172
- throw new Error('Login failed: No session cookie received.');
173
- }
174
- return {
175
- success: true,
176
- fromCache: false,
177
- headers: response.headers,
178
- data: response.data
179
- };
180
- } else {
181
- throw new Error(`Login failed: ${response.data.msg}`);
182
- }
183
- } catch (error) {
184
- // Log the error securely
185
- ErrorSecurity.logError(error, {
186
- username: this.username,
187
- baseURL: this.baseURL,
188
- action: 'login'
189
- });
190
-
191
- // Log suspicious activity for multiple failed logins
192
- if (this.loginRetryCount >= this.maxLoginRetries) {
193
- this.securityMonitor.logSuspiciousActivity('multiple_failed_logins', {
194
- username: CredentialSecurity.hashForLogging(this.username),
195
- attempts: this.loginRetryCount
196
- });
197
- }
198
-
199
- // Return sanitized error
200
- const sanitizedError = ErrorSecurity.sanitizeError(error, this.isDevelopment);
201
- throw sanitizedError;
202
- }
203
- }
204
-
205
- /**
206
- * Logout and clear session
207
- */
208
- async logout() {
209
- this.cookie = null;
210
- delete this.api.defaults.headers.Cookie;
211
-
212
- if (this.sessionManager) {
213
- await this.sessionManager.deleteSession(this.baseURL, this.username);
214
- }
215
- }
216
-
217
- async _request(method, path, data = {}) {
218
- // Check session validity first with mutex protection
219
- if (!this.loginMutex && this.sessionManager && !await this.sessionManager.hasValidSession(this.baseURL, this.username)) {
220
- await this._ensureAuthenticated();
221
- } else if (!this.loginMutex && !this.cookie) {
222
- await this._ensureAuthenticated();
223
- }
224
-
225
- try {
226
- const response = await this.api.request({
227
- method,
228
- url: path,
229
- data,
230
- ...(method.toLowerCase() === 'post' ? { headers: { 'Content-Type': 'application/json' } } : {})
231
- });
232
- // Reset retry counter on successful request
233
- this.loginRetryCount = 0;
234
- return response.data;
235
- } catch (error) {
236
- if (error.response && error.response.status === 401) {
237
- // Cookie might have expired, try to login again with retry limit
238
- if (this.loginRetryCount < this.maxLoginRetries) {
239
- await this._ensureAuthenticated(true); // Force refresh
240
- const response = await this.api.request({
241
- method,
242
- url: path,
243
- data,
244
- ...(method.toLowerCase() === 'post' ? { headers: { 'Content-Type': 'application/json' } } : {})
245
- });
246
- return response.data;
247
- } else {
248
- throw new Error('Maximum login retry attempts exceeded. Check your credentials.');
249
- }
250
- }
251
- throw error;
252
- }
253
- }
254
-
255
- /**
256
- * Ensure authentication with mutex protection
257
- * @param {boolean} forceRefresh - Force a new login
258
- */
259
- async _ensureAuthenticated(forceRefresh = false) {
260
- // Prevent concurrent login attempts
261
- if (this.loginMutex) {
262
- // Wait for ongoing login to complete
263
- while (this.loginMutex) {
264
- await new Promise(resolve => setTimeout(resolve, 100));
265
- }
266
- return;
267
- }
268
-
269
- this.loginMutex = true;
270
- try {
271
- await this.login(forceRefresh);
272
- } finally {
273
- this.loginMutex = false;
274
- }
275
- }
276
-
277
- // ===========================================
278
- // CREDENTIAL GENERATION METHODS
279
- // ===========================================
280
-
281
- /**
282
- * Generate credentials for a specific protocol
283
- * @param {string} protocol - Protocol name (vless, vmess, trojan, etc.)
284
- * @param {Object} options - Generation options
285
- * @returns {Object} Generated credentials
286
- */
287
- generateCredentials(protocol, options = {}) {
288
- return CredentialGenerator.generateForProtocol(protocol, options);
289
- }
290
-
291
- /**
292
- * Generate UUID for VLESS/VMess protocols
293
- * @param {boolean} secure - Use cryptographically secure generation
294
- * @returns {string} UUID
295
- */
296
- generateUUID(secure = true) {
297
- return secure ? CredentialGenerator.generateSecureUUID() : CredentialGenerator.generateUUID();
298
- }
299
-
300
- /**
301
- * Generate password for Trojan/Shadowsocks protocols
302
- * @param {number} length - Password length
303
- * @param {Object} options - Password options
304
- * @returns {string} Generated password
305
- */
306
- generatePassword(length = 16, options = {}) {
307
- return CredentialGenerator.generatePassword(length, options);
308
- }
309
-
310
- /**
311
- * Generate bulk credentials for multiple clients
312
- * @param {string} protocol - Protocol name
313
- * @param {number} count - Number of credentials to generate
314
- * @param {Object} options - Generation options
315
- * @returns {Array} Array of credentials
316
- */
317
- generateBulkCredentials(protocol, count, options = {}) {
318
- return CredentialGenerator.generateBulk(protocol, count, options);
319
- }
320
-
321
- /**
322
- * Get available cipher methods for Shadowsocks
323
- * @returns {Array} Array of cipher methods
324
- */
325
- getShadowsocksCiphers() {
326
- return CredentialGenerator.getShadowsocksCipherMethods();
327
- }
328
-
329
- /**
330
- * Get recommended cipher for Shadowsocks
331
- * @returns {string} Recommended cipher
332
- */
333
- getRecommendedShadowsocksCipher() {
334
- return CredentialGenerator.getRecommendedShadowsocksCipher();
335
- }
336
-
337
- /**
338
- * Generate WireGuard key pair
339
- * @returns {Object} Key pair with helper methods
340
- */
341
- generateWireGuardKeys() {
342
- return CredentialGenerator.generateWireGuardKeys();
343
- }
344
-
345
- /**
346
- * Generate Reality key pair for anti-censorship
347
- * @returns {Object} Reality key pair with helper methods
348
- */
349
- generateRealityKeys() {
350
- return CredentialGenerator.generateRealityKeys();
351
- }
352
-
353
- /**
354
- * Generate random port number
355
- * @param {number} min - Minimum port
356
- * @param {number} max - Maximum port
357
- * @returns {number} Random port
358
- */
359
- generatePort(min = 10000, max = 65535) {
360
- return CredentialGenerator.generatePort(min, max);
361
- }
362
-
363
- /**
364
- * Validate generated credentials
365
- * @param {Object} credentials - Credentials to validate
366
- * @param {string} protocol - Protocol name
367
- * @returns {Object} Validation result
368
- */
369
- validateCredentials(credentials, protocol) {
370
- return CredentialGenerator.validateCredentials(credentials, protocol);
371
- }
372
-
373
- // ===========================================
374
- // ENHANCED CLIENT MANAGEMENT WITH AUTO-GENERATION
375
- // ===========================================
376
-
377
- /**
378
- * Add client with automatic credential generation
379
- * @param {number} inboundId - Inbound ID
380
- * @param {string} protocol - Protocol type
381
- * @param {Object} options - Client options
382
- * @returns {Object} Created client with credentials
383
- */
384
- async addClientWithCredentials(inboundId, protocol, options = {}) {
385
- const credentials = this.generateCredentials(protocol, options);
386
-
387
- const clientConfig = {
388
- id: inboundId,
389
- settings: JSON.stringify({
390
- clients: [{
391
- ...credentials,
392
- enable: true,
393
- expiryTime: options.expiryTime || 0,
394
- limitIp: options.limitIp || 0,
395
- totalGB: options.totalGB || 0,
396
- subId: options.subId || this.generateUUID()
397
- }]
398
- })
399
- };
400
-
401
- const result = await this.addClient(clientConfig);
402
- return {
403
- ...result,
404
- credentials,
405
- protocol
406
- };
407
- }
408
-
409
- /**
410
- * Update client with automatic credential management
411
- * @param {string} clientId - Client UUID/ID
412
- * @param {number} inboundId - Inbound ID
413
- * @param {Object} options - Update options
414
- * @returns {Object} Update result
415
- */
416
- async updateClientWithCredentials(clientId, inboundId, options = {}) {
417
- try {
418
- // First, get the current inbound to obtain all existing clients
419
- const inboundData = await this.getInbound(inboundId);
420
-
421
- if (!inboundData.success || !inboundData.obj) {
422
- throw new Error(`Failed to get inbound ${inboundId} for client update`);
423
- }
424
-
425
- // Parse existing settings to get all clients
426
- const currentSettings = JSON.parse(inboundData.obj.settings);
427
- const existingClients = currentSettings.clients || [];
428
-
429
- // Find the client to update
430
- const clientIndex = existingClients.findIndex(client => client.id === clientId);
431
- if (clientIndex === -1) {
432
- throw new Error(`Client with ID ${clientId} not found in inbound ${inboundId}`);
433
- }
434
-
435
- // Convert user-friendly options to API format
436
- const processedOptions = {
437
- email: options.email || existingClients[clientIndex].email,
438
- limitIp: options.limitIp !== undefined ? options.limitIp : existingClients[clientIndex].limitIp,
439
- // totalGB is specified in gigabytes in 3x-ui config; do not convert to bytes
440
- totalGB: options.totalGB !== undefined ? options.totalGB : existingClients[clientIndex].totalGB,
441
- expiryTime: options.expiryDays ? Date.now() + (options.expiryDays * 24 * 60 * 60 * 1000) : existingClients[clientIndex].expiryTime,
442
- enable: options.enable !== undefined ? options.enable : existingClients[clientIndex].enable,
443
- flow: options.flow || existingClients[clientIndex].flow,
444
- encryption: options.encryption || existingClients[clientIndex].encryption || 'none',
445
- subId: options.subId || existingClients[clientIndex].subId
446
- };
447
-
448
- // Update the specific client while preserving others
449
- existingClients[clientIndex] = {
450
- ...existingClients[clientIndex],
451
- ...processedOptions
452
- };
453
-
454
- // Prepare the complete settings with all clients
455
- const updatedSettings = {
456
- ...currentSettings,
457
- clients: existingClients
458
- };
459
-
460
- const clientConfig = {
461
- id: inboundId,
462
- settings: JSON.stringify(updatedSettings)
463
- };
464
-
465
- const result = await this.updateClient(clientId, clientConfig);
466
- return {
467
- ...result,
468
- updatedOptions: processedOptions,
469
- conversions: {
470
- totalGB: options.totalGB !== undefined ? `${options.totalGB}GB` : 'unchanged',
471
- expiryDays: options.expiryDays ? `${options.expiryDays} days ${new Date(processedOptions.expiryTime).toISOString()}` : 'unchanged'
472
- }
473
- };
474
- } catch (error) {
475
- return {
476
- success: false,
477
- message: error.message,
478
- error: error.message,
479
- details: 'updateClientWithCredentials failed - check client ID and inbound ID'
480
- };
481
- }
482
- }
483
-
484
- // ===========================================
485
- // SESSION MANAGEMENT METHODS
486
- // ===========================================
487
-
488
- /**
489
- * Get session statistics
490
- * @returns {Object} Session statistics
491
- */
492
- async getSessionStats() {
493
- if (this.sessionManager) {
494
- return await this.sessionManager.getStats();
495
- }
496
- return { message: 'Session manager not initialized' };
497
- }
498
-
499
- /**
500
- * Clear all cached sessions
501
- */
502
- async clearAllSessions() {
503
- if (this.sessionManager) {
504
- await this.sessionManager.clearAllSessions();
505
- }
506
- }
507
-
508
- /**
509
- * Check if current session is valid
510
- * @returns {boolean} Session validity
511
- */
512
- async isSessionValid() {
513
- if (this.sessionManager) {
514
- return await this.sessionManager.hasValidSession(this.baseURL, this.username);
515
- }
516
- return !!this.cookie;
517
- }
518
-
519
- // ===========================================
520
- // ORIGINAL API METHODS (UNCHANGED)
521
- // ===========================================
522
-
523
- // Inbounds
524
- getInbounds() {
525
- return this._request('get', '/panel/api/inbounds/list');
526
- }
527
-
528
- getInbound(id) {
529
- return this._request('get', `/panel/api/inbounds/get/${id}`);
530
- }
531
-
532
- addInbound(inboundConfig) {
533
- // Validate inbound configuration for security
534
- const validatedConfig = InputValidator.validateInboundConfig(inboundConfig);
535
- return this._request('post', '/panel/api/inbounds/add', validatedConfig);
536
- }
537
-
538
- deleteInbound(id) {
539
- return this._request('post', `/panel/api/inbounds/del/${id}`);
540
- }
541
-
542
- updateInbound(id, inboundConfig) {
543
- // Validate inbound configuration for security
544
- const validatedConfig = InputValidator.validateInboundConfig(inboundConfig);
545
- return this._request('post', `/panel/api/inbounds/update/${id}`, validatedConfig);
546
- }
547
-
548
- // Clients
549
- addClient(clientConfig) {
550
- // Validate client configuration for security
551
- const validatedConfig = InputValidator.validateClientConfig(clientConfig);
552
- return this._request('post', '/panel/api/inbounds/addClient', validatedConfig);
553
- }
554
-
555
- deleteClient(inboundId, clientId) {
556
- return this._request('post', `/panel/api/inbounds/${inboundId}/delClient/${clientId}`);
557
- }
558
-
559
- updateClient(clientId, clientConfig) {
560
- // Validate client configuration for security
561
- const validatedConfig = InputValidator.validateClientConfig(clientConfig);
562
- return this._request('post', `/panel/api/inbounds/updateClient/${clientId}`, validatedConfig);
563
- }
564
-
565
- getClientTrafficsByEmail(email) {
566
- return this._request('get', `/panel/api/inbounds/getClientTraffics/${email}`);
567
- }
568
-
569
- getClientTrafficsById(id) {
570
- return this._request('get', `/panel/api/inbounds/getClientTrafficsById/${id}`);
571
- }
572
-
573
- getClientIps(email) {
574
- return this._request('post', `/panel/api/inbounds/clientIps/${email}`);
575
- }
576
-
577
- clearClientIps(email) {
578
- return this._request('post', `/panel/api/inbounds/clearClientIps/${email}`);
579
- }
580
-
581
- // Traffic
582
- resetClientTraffic(inboundId, email) {
583
- return this._request('post', `/panel/api/inbounds/${inboundId}/resetClientTraffic/${email}`);
584
- }
585
-
586
- resetAllTraffics() {
587
- return this._request('post', '/panel/api/inbounds/resetAllTraffics');
588
- }
589
-
590
- resetAllClientTraffics(inboundId) {
591
- return this._request('post', `/panel/api/inbounds/resetAllClientTraffics/${inboundId}`);
592
- }
593
-
594
- deleteDepletedClients(inboundId) {
595
- return this._request('post', `/panel/api/inbounds/delDepletedClients/${inboundId}`);
596
- }
597
-
598
- // System
599
- getOnlineClients() {
600
- return this._request('post', '/panel/api/inbounds/onlines');
601
- }
602
-
603
- createBackup() {
604
- return this._request('get', '/panel/api/inbounds/createbackup');
605
- }
606
-
607
- // Security Methods
608
-
609
- /**
610
- * Get security statistics and monitoring data
611
- * @returns {Object} Security statistics
612
- */
613
- getSecurityStats() {
614
- return this.securityMonitor.getStats();
615
- }
616
-
617
- /**
618
- * Clear blocked IPs (admin function)
619
- */
620
- clearBlockedIPs() {
621
- this.securityMonitor.clearBlockedIPs();
622
- }
623
-
624
- /**
625
- * Validate credential strength
626
- * @param {string} credential - Credential to validate
627
- * @param {string} type - Type of credential
628
- * @returns {Object} Validation result
629
- */
630
- validateCredentialStrength(credential, type) {
631
- return CredentialSecurity.validateCredentialStrength(credential, type);
632
- }
633
-
634
- /**
635
- * Generate secure session token
636
- * @returns {string} Secure session token
637
- */
638
- generateSecureToken() {
639
- return CredentialSecurity.generateSessionToken();
640
- }
641
-
642
- /**
643
- * Enable development mode for detailed error messages
644
- * @param {boolean} enabled - Whether to enable development mode
645
- */
646
- setDevelopmentMode(enabled) {
647
- this.isDevelopment = enabled;
648
- }
649
- }
650
-
651
- // Export static methods for standalone use
652
- ThreeXUI.CredentialGenerator = CredentialGenerator;
653
- ThreeXUI.SessionManager = SessionManager;
654
- ThreeXUI.createSessionManager = createSessionManager;
655
-
656
- module.exports = ThreeXUI;
657
-
658
- // Define lazy getters to avoid circular dependencies
659
- Object.defineProperties(module.exports, {
660
- // Web middleware helpers
661
- createExpressMiddleware: {
662
- enumerable: true,
663
- get: () => require('./src/middleware/WebMiddleware').createExpressMiddleware
664
- },
665
- withThreeXUI: {
666
- enumerable: true,
667
- get: () => require('./src/middleware/WebMiddleware').withThreeXUI
668
- },
669
- createReactHook: {
670
- enumerable: true,
671
- get: () => require('./src/middleware/WebMiddleware').createReactHook
672
- },
673
- createNextjsRoutes: {
674
- enumerable: true,
675
- get: () => require('./src/middleware/WebMiddleware').createNextjsRoutes
676
- },
677
- SessionConfig: {
678
- enumerable: true,
679
- get: () => require('./src/middleware/WebMiddleware').SessionConfig
680
- },
681
- // Protocol builders
682
- ProtocolBuilder: {
683
- enumerable: true,
684
- get: () => require('./src/builders/ProtocolBuilders').ProtocolBuilder
685
- },
686
- VLESSBuilder: {
687
- enumerable: true,
688
- get: () => require('./src/builders/ProtocolBuilders').VLESSBuilder
689
- },
690
- VMESSBuilder: {
691
- enumerable: true,
692
- get: () => require('./src/builders/ProtocolBuilders').VMESSBuilder
693
- },
694
- TrojanBuilder: {
695
- enumerable: true,
696
- get: () => require('./src/builders/ProtocolBuilders').TrojanBuilder
697
- },
698
- ShadowsocksBuilder: {
699
- enumerable: true,
700
- get: () => require('./src/builders/ProtocolBuilders').ShadowsocksBuilder
701
- },
702
- WireGuardBuilder: {
703
- enumerable: true,
704
- get: () => require('./src/builders/ProtocolBuilders').WireGuardBuilder
705
- },
706
- BaseBuilder: {
707
- enumerable: true,
708
- get: () => require('./src/builders/ProtocolBuilders').BaseBuilder
709
- },
710
- // Security helpers
711
- SecurityEnhancer: {
712
- enumerable: true,
713
- get: () => require('./src/security/SecurityEnhancer')
714
- }
1
+ const axios = require('axios');
2
+ const CredentialGenerator = require('./src/generators/CredentialGenerator');
3
+ const { SessionManager, createSessionManager } = require('./src/session/SessionManager');
4
+ const {
5
+ InputValidator,
6
+ SecurityMonitor,
7
+ SecureHeaders,
8
+ CredentialSecurity,
9
+ ErrorSecurity
10
+ } = require('./src/security/SecurityEnhancer');
11
+
12
+ /**
13
+ * 3X-UI API Client Library
14
+ *
15
+ * A Node.js client for managing 3x-ui panel APIs with automatic session management.
16
+ * Now includes credential generation and advanced session management for web applications.
17
+ *
18
+ * @class ThreeXUI
19
+ * @version 2.0.0
20
+ * @author Helitha Guruge
21
+ */
22
+ class ThreeXUI {
23
+ /**
24
+ * Creates a new ThreeXUI client instance
25
+ *
26
+ * @param {string} baseURL - The base URL of your 3x-ui server (e.g., 'https://your-server.com')
27
+ * @param {string} username - Admin username for authentication
28
+ * @param {string} password - Admin password for authentication
29
+ * @param {Object} options - Configuration options
30
+ * @param {Object} options.sessionManager - Session management configuration
31
+ * @param {boolean} options.autoGenerateCredentials - Enable automatic credential generation
32
+ * @param {number} options.timeout - Request timeout in milliseconds (default: 30000)
33
+ * @throws {Error} If baseURL, username, or password is missing
34
+ */
35
+ constructor(baseURL, username, password, options = {}) {
36
+ if (!baseURL) {
37
+ throw new Error('baseURL is required');
38
+ }
39
+ if (!username) {
40
+ throw new Error('username is required');
41
+ }
42
+ if (!password) {
43
+ throw new Error('password is required');
44
+ }
45
+
46
+ // Apply security validations
47
+ this.baseURL = InputValidator.validateURL(baseURL);
48
+
49
+ // Warning for common configuration error
50
+ if (this.baseURL.endsWith('/panel') || this.baseURL.endsWith('/panel/')) {
51
+ console.warn('WARNING: baseURL should NOT end with "/panel". The library appends this automatically. Please remove it from your configuration.');
52
+ // Auto-fix for better user experience
53
+ this.baseURL = this.baseURL.replace(/\/panel\/?$/, '');
54
+ }
55
+
56
+ this.username = InputValidator.validateUsername(username);
57
+ this.password = InputValidator.validatePassword(password);
58
+ this.cookie = null;
59
+ this.options = options;
60
+ this.loginMutex = false; // Add mutex to prevent concurrent logins
61
+ this.loginRetryCount = 0; // Add retry counter
62
+ this.maxLoginRetries = 3; // Maximum login attempts
63
+
64
+ // Initialize security monitoring
65
+ this.securityMonitor = new SecurityMonitor({
66
+ maxRequestsPerMinute: options.maxRequestsPerMinute || 60,
67
+ maxLoginAttemptsPerHour: options.maxLoginAttemptsPerHour || 10
68
+ });
69
+
70
+ // Security configuration
71
+ this.isDevelopment = options.isDevelopment || process.env.NODE_ENV === 'development';
72
+
73
+ // Initialize session manager if provided
74
+ if (options.sessionManager) {
75
+ this.sessionManager = options.sessionManager instanceof SessionManager
76
+ ? options.sessionManager
77
+ : createSessionManager(options.sessionManager);
78
+ } else {
79
+ // Default to memory-based session management
80
+ this.sessionManager = createSessionManager();
81
+ }
82
+
83
+ // Create axios instance with security best practices
84
+ this.api = axios.create({
85
+ baseURL: this.baseURL,
86
+ timeout: options.timeout || 30000, // 30 second timeout
87
+ maxRedirects: 5,
88
+ validateStatus: (status) => status >= 200 && status < 300,
89
+ headers: SecureHeaders.getSecureHeaders({
90
+ userAgent: options.userAgent || '3xui-api-client/2.0.0 (Security-Enhanced)',
91
+ enableCSP: options.enableCSP || false
92
+ })
93
+ });
94
+
95
+ // Add request interceptor for security headers
96
+ this.api.interceptors.request.use((config) => {
97
+ // Add security headers
98
+ config.headers['X-Requested-With'] = 'XMLHttpRequest';
99
+ return config;
100
+ });
101
+
102
+ // Add response interceptor for error handling
103
+ this.api.interceptors.response.use(
104
+ (response) => response,
105
+ (error) => {
106
+ if (error.code === 'ECONNABORTED') {
107
+ throw new Error('Request timeout - server took too long to respond');
108
+ }
109
+ if (error.code === 'ENOTFOUND') {
110
+ throw new Error(`Cannot connect to server: ${this.baseURL}`);
111
+ }
112
+ throw error;
113
+ }
114
+ );
115
+ }
116
+
117
+ /**
118
+ * Login with session management support
119
+ * @param {boolean} forceRefresh - Force a new login even if session exists
120
+ * @returns {Object} Login response
121
+ */
122
+ async login(forceRefresh = false) {
123
+ // Check rate limiting first
124
+ const identifier = CredentialSecurity.hashForLogging(this.username);
125
+ if (!this.securityMonitor.checkRateLimit(identifier, 'login')) {
126
+ const error = new Error('Rate limit exceeded for login attempts');
127
+ ErrorSecurity.logError(error, { username: this.username, baseURL: this.baseURL });
128
+ throw error;
129
+ }
130
+
131
+ // Increment retry count
132
+ this.loginRetryCount++;
133
+ // Check for existing valid session first
134
+ if (!forceRefresh && this.sessionManager) {
135
+ const existingSession = await this.sessionManager.getSession(this.baseURL, this.username);
136
+ if (existingSession && existingSession.cookie) {
137
+ this.cookie = existingSession.cookie;
138
+ this.api.defaults.headers.Cookie = this.cookie;
139
+ // Reset retry count on successful session restore
140
+ this.loginRetryCount = 0;
141
+ return {
142
+ success: true,
143
+ fromCache: true,
144
+ data: { msg: 'Session restored from cache' }
145
+ };
146
+ }
147
+ }
148
+
149
+ try {
150
+ const params = new URLSearchParams();
151
+ params.append('username', this.username);
152
+ params.append('password', this.password);
153
+
154
+ const response = await this.api.post('/login', params, {
155
+ headers: {
156
+ 'Content-Type': 'application/x-www-form-urlencoded'
157
+ }
158
+ });
159
+
160
+ if (response.data.success) {
161
+ const cookies = response.headers['set-cookie'];
162
+ if (cookies && cookies.length > 0) {
163
+ this.cookie = cookies[0].split(';')[0];
164
+ this.api.defaults.headers.Cookie = this.cookie;
165
+
166
+ // Store session if session manager is available
167
+ if (this.sessionManager) {
168
+ try {
169
+ await this.sessionManager.storeSession(this.baseURL, this.username, {
170
+ cookie: this.cookie,
171
+ loginTime: new Date().toISOString()
172
+ });
173
+ } catch (sessionError) {
174
+ console.warn('Failed to store session, continuing without cache:', sessionError.message);
175
+ }
176
+ }
177
+ // Reset retry count on successful login
178
+ this.loginRetryCount = 0;
179
+ } else {
180
+ throw new Error('Login failed: No session cookie received.');
181
+ }
182
+ return {
183
+ success: true,
184
+ fromCache: false,
185
+ cookie: this.cookie, // Explicitly return the cookie
186
+ headers: response.headers,
187
+ data: response.data
188
+ };
189
+ } else {
190
+ throw new Error(`Login failed: ${response.data.msg}`);
191
+ }
192
+ } catch (error) {
193
+ // Log the error securely
194
+ ErrorSecurity.logError(error, {
195
+ username: this.username,
196
+ baseURL: this.baseURL,
197
+ action: 'login'
198
+ });
199
+
200
+ // Log suspicious activity for multiple failed logins
201
+ if (this.loginRetryCount >= this.maxLoginRetries) {
202
+ this.securityMonitor.logSuspiciousActivity('multiple_failed_logins', {
203
+ username: CredentialSecurity.hashForLogging(this.username),
204
+ attempts: this.loginRetryCount
205
+ });
206
+ }
207
+
208
+ // Return sanitized error
209
+ const sanitizedError = ErrorSecurity.sanitizeError(error, this.isDevelopment);
210
+ throw sanitizedError;
211
+ }
212
+ }
213
+
214
+ /**
215
+ * Logout and clear session
216
+ */
217
+ async logout() {
218
+ try {
219
+ await this._request('get', '/logout');
220
+ } catch {
221
+ // Ignore server-side logout errors, proceed to clear local session
222
+ }
223
+
224
+ this.cookie = null;
225
+ delete this.api.defaults.headers.Cookie;
226
+
227
+ if (this.sessionManager) {
228
+ await this.sessionManager.deleteSession(this.baseURL, this.username);
229
+ }
230
+ }
231
+
232
+ /**
233
+ * Check if Two-Factor Authentication is enabled
234
+ * @returns {Promise<Object>} 2FA status
235
+ */
236
+ getTwoFactorEnable() {
237
+ return this._request('post', '/getTwoFactorEnable');
238
+ }
239
+
240
+ async _request(method, path, data = {}) {
241
+ // Check session validity first with mutex protection
242
+ if (!this.loginMutex && this.sessionManager && !await this.sessionManager.hasValidSession(this.baseURL, this.username)) {
243
+ await this._ensureAuthenticated();
244
+ } else if (!this.loginMutex && !this.cookie) {
245
+ await this._ensureAuthenticated();
246
+ }
247
+
248
+ try {
249
+ const response = await this.api.request({
250
+ method,
251
+ url: path,
252
+ data,
253
+ ...(method.toLowerCase() === 'post' ? { headers: { 'Content-Type': 'application/json' } } : {})
254
+ });
255
+ // Reset retry counter on successful request
256
+ this.loginRetryCount = 0;
257
+ return response.data;
258
+ } catch (error) {
259
+ if (error.response && error.response.status === 401) {
260
+ // Cookie might have expired, try to login again with retry limit
261
+ if (this.loginRetryCount < this.maxLoginRetries) {
262
+ await this._ensureAuthenticated(true); // Force refresh
263
+ const response = await this.api.request({
264
+ method,
265
+ url: path,
266
+ data,
267
+ ...(method.toLowerCase() === 'post' ? { headers: { 'Content-Type': 'application/json' } } : {})
268
+ });
269
+ return response.data;
270
+ } else {
271
+ throw new Error('Maximum login retry attempts exceeded. Check your credentials.');
272
+ }
273
+ }
274
+ throw error;
275
+ }
276
+ }
277
+
278
+ /**
279
+ * Ensure authentication with mutex protection
280
+ * @param {boolean} forceRefresh - Force a new login
281
+ */
282
+ async _ensureAuthenticated(forceRefresh = false) {
283
+ // Prevent concurrent login attempts
284
+ if (this.loginMutex) {
285
+ // Wait for ongoing login to complete
286
+ while (this.loginMutex) {
287
+ await new Promise(resolve => setTimeout(resolve, 100));
288
+ }
289
+ return;
290
+ }
291
+
292
+ this.loginMutex = true;
293
+ try {
294
+ await this.login(forceRefresh);
295
+ } finally {
296
+ this.loginMutex = false;
297
+ }
298
+ }
299
+
300
+ // ===========================================
301
+ // CREDENTIAL GENERATION METHODS
302
+ // ===========================================
303
+
304
+ /**
305
+ * Generate credentials for a specific protocol
306
+ * @param {string} protocol - Protocol name (vless, vmess, trojan, etc.)
307
+ * @param {Object} options - Generation options
308
+ * @returns {Object} Generated credentials
309
+ */
310
+ generateCredentials(protocol, options = {}) {
311
+ return CredentialGenerator.generateForProtocol(protocol, options);
312
+ }
313
+
314
+ /**
315
+ * Generate UUID for VLESS/VMess protocols
316
+ * @param {boolean} secure - Use cryptographically secure generation
317
+ * @returns {string} UUID
318
+ */
319
+ generateUUID(secure = true) {
320
+ return secure ? CredentialGenerator.generateSecureUUID() : CredentialGenerator.generateUUID();
321
+ }
322
+
323
+ /**
324
+ * Generate password for Trojan/Shadowsocks protocols
325
+ * @param {number} length - Password length
326
+ * @param {Object} options - Password options
327
+ * @returns {string} Generated password
328
+ */
329
+ generatePassword(length = 16, options = {}) {
330
+ return CredentialGenerator.generatePassword(length, options);
331
+ }
332
+
333
+ /**
334
+ * Generate bulk credentials for multiple clients
335
+ * @param {string} protocol - Protocol name
336
+ * @param {number} count - Number of credentials to generate
337
+ * @param {Object} options - Generation options
338
+ * @returns {Array} Array of credentials
339
+ */
340
+ generateBulkCredentials(protocol, count, options = {}) {
341
+ return CredentialGenerator.generateBulk(protocol, count, options);
342
+ }
343
+
344
+ /**
345
+ * Get available cipher methods for Shadowsocks
346
+ * @returns {Array} Array of cipher methods
347
+ */
348
+ getShadowsocksCiphers() {
349
+ return CredentialGenerator.getShadowsocksCipherMethods();
350
+ }
351
+
352
+ /**
353
+ * Get recommended cipher for Shadowsocks
354
+ * @returns {string} Recommended cipher
355
+ */
356
+ getRecommendedShadowsocksCipher() {
357
+ return CredentialGenerator.getRecommendedShadowsocksCipher();
358
+ }
359
+
360
+ /**
361
+ * Generate WireGuard key pair
362
+ * @returns {Object} Key pair with helper methods
363
+ */
364
+ generateWireGuardKeys() {
365
+ return CredentialGenerator.generateWireGuardKeys();
366
+ }
367
+
368
+ /**
369
+ * Generate Reality key pair for anti-censorship
370
+ * @returns {Object} Reality key pair with helper methods
371
+ */
372
+ generateRealityKeys() {
373
+ return CredentialGenerator.generateRealityKeys();
374
+ }
375
+
376
+ /**
377
+ * Generate random port number
378
+ * @param {number} min - Minimum port
379
+ * @param {number} max - Maximum port
380
+ * @returns {number} Random port
381
+ */
382
+ generatePort(min = 10000, max = 65535) {
383
+ return CredentialGenerator.generatePort(min, max);
384
+ }
385
+
386
+ /**
387
+ * Validate generated credentials
388
+ * @param {Object} credentials - Credentials to validate
389
+ * @param {string} protocol - Protocol name
390
+ * @returns {Object} Validation result
391
+ */
392
+ validateCredentials(credentials, protocol) {
393
+ return CredentialGenerator.validateCredentials(credentials, protocol);
394
+ }
395
+
396
+ // ===========================================
397
+ // ENHANCED CLIENT MANAGEMENT WITH AUTO-GENERATION
398
+ // ===========================================
399
+
400
+ /**
401
+ * Add client with automatic credential generation
402
+ * @param {number} inboundId - Inbound ID
403
+ * @param {string} protocol - Protocol type
404
+ * @param {Object} options - Client options
405
+ * @returns {Object} Created client with credentials
406
+ */
407
+ async addClientWithCredentials(inboundId, protocol, options = {}) {
408
+ const credentials = this.generateCredentials(protocol, options);
409
+
410
+ const clientConfig = {
411
+ id: inboundId,
412
+ settings: JSON.stringify({
413
+ clients: [{
414
+ ...credentials,
415
+ enable: true,
416
+ expiryTime: options.expiryTime || 0,
417
+ limitIp: options.limitIp || 0,
418
+ totalGB: options.totalGB || 0,
419
+ subId: options.subId || this.generateUUID()
420
+ }]
421
+ })
422
+ };
423
+
424
+ const result = await this.addClient(clientConfig);
425
+ return {
426
+ ...result,
427
+ credentials,
428
+ protocol
429
+ };
430
+ }
431
+
432
+ /**
433
+ * Update client with automatic credential management
434
+ * @param {string} clientId - Client UUID/ID
435
+ * @param {number} inboundId - Inbound ID
436
+ * @param {Object} options - Update options
437
+ * @returns {Object} Update result
438
+ */
439
+ async updateClientWithCredentials(clientId, inboundId, options = {}) {
440
+ try {
441
+ // First, get the current inbound to obtain all existing clients
442
+ const inboundData = await this.getInbound(inboundId);
443
+
444
+ if (!inboundData.success || !inboundData.obj) {
445
+ throw new Error(`Failed to get inbound ${inboundId} for client update`);
446
+ }
447
+
448
+ // Parse existing settings to get all clients
449
+ const currentSettings = JSON.parse(inboundData.obj.settings);
450
+ const existingClients = currentSettings.clients || [];
451
+
452
+ // Find the client to update
453
+ const clientIndex = existingClients.findIndex(client => client.id === clientId);
454
+ if (clientIndex === -1) {
455
+ throw new Error(`Client with ID ${clientId} not found in inbound ${inboundId}`);
456
+ }
457
+
458
+ // Convert user-friendly options to API format
459
+ const processedOptions = {
460
+ email: options.email || existingClients[clientIndex].email,
461
+ limitIp: options.limitIp !== undefined ? options.limitIp : existingClients[clientIndex].limitIp,
462
+ // totalGB is specified in gigabytes in 3x-ui config; do not convert to bytes
463
+ totalGB: options.totalGB !== undefined ? options.totalGB : existingClients[clientIndex].totalGB,
464
+ expiryTime: options.expiryDays ? Date.now() + (options.expiryDays * 24 * 60 * 60 * 1000) : existingClients[clientIndex].expiryTime,
465
+ enable: options.enable !== undefined ? options.enable : existingClients[clientIndex].enable,
466
+ flow: options.flow || existingClients[clientIndex].flow,
467
+ encryption: options.encryption || existingClients[clientIndex].encryption || 'none',
468
+ subId: options.subId || existingClients[clientIndex].subId
469
+ };
470
+
471
+ // Update the specific client while preserving others
472
+ existingClients[clientIndex] = {
473
+ ...existingClients[clientIndex],
474
+ ...processedOptions
475
+ };
476
+
477
+ // Prepare the complete settings with all clients
478
+ const updatedSettings = {
479
+ ...currentSettings,
480
+ clients: existingClients
481
+ };
482
+
483
+ const clientConfig = {
484
+ id: inboundId,
485
+ settings: JSON.stringify(updatedSettings)
486
+ };
487
+
488
+ const result = await this.updateClient(clientId, clientConfig);
489
+ return {
490
+ ...result,
491
+ updatedOptions: processedOptions,
492
+ conversions: {
493
+ totalGB: options.totalGB !== undefined ? `${options.totalGB}GB` : 'unchanged',
494
+ expiryDays: options.expiryDays ? `${options.expiryDays} days → ${new Date(processedOptions.expiryTime).toISOString()}` : 'unchanged'
495
+ }
496
+ };
497
+ } catch (error) {
498
+ return {
499
+ success: false,
500
+ message: error.message,
501
+ error: error.message,
502
+ details: 'updateClientWithCredentials failed - check client ID and inbound ID'
503
+ };
504
+ }
505
+ }
506
+
507
+ // ===========================================
508
+ // SESSION MANAGEMENT METHODS
509
+ // ===========================================
510
+
511
+ /**
512
+ * Get session statistics
513
+ * @returns {Object} Session statistics
514
+ */
515
+ async getSessionStats() {
516
+ if (this.sessionManager) {
517
+ return await this.sessionManager.getStats();
518
+ }
519
+ return { message: 'Session manager not initialized' };
520
+ }
521
+
522
+ /**
523
+ * Clear all cached sessions
524
+ */
525
+ async clearAllSessions() {
526
+ if (this.sessionManager) {
527
+ await this.sessionManager.clearAllSessions();
528
+ }
529
+ }
530
+
531
+ /**
532
+ * Check if current session is valid
533
+ * @returns {boolean} Session validity
534
+ */
535
+ async isSessionValid() {
536
+ if (this.sessionManager) {
537
+ return await this.sessionManager.hasValidSession(this.baseURL, this.username);
538
+ }
539
+ return !!this.cookie;
540
+ }
541
+
542
+ // ===========================================
543
+ // ORIGINAL API METHODS (UNCHANGED)
544
+ // ===========================================
545
+
546
+ // Inbounds
547
+ getInbounds() {
548
+ return this._request('get', '/panel/api/inbounds/list');
549
+ }
550
+
551
+ getInbound(id) {
552
+ return this._request('get', `/panel/api/inbounds/get/${id}`);
553
+ }
554
+
555
+ addInbound(inboundConfig) {
556
+ // Validate inbound configuration for security
557
+ const validatedConfig = InputValidator.validateInboundConfig(inboundConfig);
558
+ return this._request('post', '/panel/api/inbounds/add', validatedConfig);
559
+ }
560
+
561
+ deleteInbound(id) {
562
+ return this._request('post', `/panel/api/inbounds/del/${id}`);
563
+ }
564
+
565
+ updateInbound(id, inboundConfig) {
566
+ // Validate inbound configuration for security
567
+ const validatedConfig = InputValidator.validateInboundConfig(inboundConfig);
568
+ return this._request('post', `/panel/api/inbounds/update/${id}`, validatedConfig);
569
+ }
570
+
571
+ /**
572
+ * Import inbounds
573
+ * @param {Array} inbounds - Array of inbound configurations
574
+ */
575
+ importInbounds(inbounds) {
576
+ return this._request('post', '/panel/api/inbounds/import', { inbounds });
577
+ }
578
+
579
+ /**
580
+ * Get last online time for clients
581
+ */
582
+ getLastOnline() {
583
+ return this._request('post', '/panel/api/inbounds/lastOnline');
584
+ }
585
+
586
+ // Clients
587
+ addClient(clientConfig) {
588
+ // Validate client configuration for security
589
+ const validatedConfig = InputValidator.validateClientConfig(clientConfig);
590
+ return this._request('post', '/panel/api/inbounds/addClient', validatedConfig);
591
+ }
592
+
593
+ deleteClient(inboundId, clientId) {
594
+ return this._request('post', `/panel/api/inbounds/${inboundId}/delClient/${clientId}`);
595
+ }
596
+
597
+ updateClient(clientId, clientConfig) {
598
+ // Validate client configuration for security
599
+ const validatedConfig = InputValidator.validateClientConfig(clientConfig);
600
+ return this._request('post', `/panel/api/inbounds/updateClient/${clientId}`, validatedConfig);
601
+ }
602
+
603
+ /**
604
+ * Update client traffic limit and expiry by email
605
+ * @param {string} email - Client email
606
+ * @param {Object} trafficConfig - Traffic configuration (totalGB, expiryTime)
607
+ */
608
+ updateClientTraffic(email, trafficConfig) {
609
+ return this._request('post', `/panel/api/inbounds/updateClientTraffic/${email}`, trafficConfig);
610
+ }
611
+
612
+ /**
613
+ * Delete client by email
614
+ * @param {number} inboundId - Inbound ID
615
+ * @param {string} email - Client email
616
+ */
617
+ deleteClientByEmail(inboundId, email) {
618
+ return this._request('post', `/panel/api/inbounds/${inboundId}/delClientByEmail/${email}`);
619
+ }
620
+
621
+ getClientTrafficsByEmail(email) {
622
+ return this._request('get', `/panel/api/inbounds/getClientTraffics/${email}`);
623
+ }
624
+
625
+ getClientTrafficsById(id) {
626
+ return this._request('get', `/panel/api/inbounds/getClientTrafficsById/${id}`);
627
+ }
628
+
629
+ getClientIps(email) {
630
+ return this._request('post', `/panel/api/inbounds/clientIps/${email}`);
631
+ }
632
+
633
+ clearClientIps(email) {
634
+ return this._request('post', `/panel/api/inbounds/clearClientIps/${email}`);
635
+ }
636
+
637
+ // Traffic
638
+ resetClientTraffic(inboundId, email) {
639
+ return this._request('post', `/panel/api/inbounds/${inboundId}/resetClientTraffic/${email}`);
640
+ }
641
+
642
+ resetAllTraffics() {
643
+ return this._request('post', '/panel/api/inbounds/resetAllTraffics');
644
+ }
645
+
646
+ resetAllClientTraffics(inboundId) {
647
+ return this._request('post', `/panel/api/inbounds/resetAllClientTraffics/${inboundId}`);
648
+ }
649
+
650
+ deleteDepletedClients(inboundId) {
651
+ return this._request('post', `/panel/api/inbounds/delDepletedClients/${inboundId}`);
652
+ }
653
+
654
+ // System
655
+ getOnlineClients() {
656
+ return this._request('post', '/panel/api/inbounds/onlines');
657
+ }
658
+
659
+ createBackup() {
660
+ return this._request('get', '/panel/api/inbounds/createbackup');
661
+ }
662
+
663
+ /**
664
+ * Trigger sending a backup to Telegram bot admins
665
+ */
666
+ backupToTgBot() {
667
+ return this._request('post', '/panel/api/backuptotgbot');
668
+ }
669
+
670
+ // ===========================================
671
+ // SERVER MANAGEMENT
672
+ // ===========================================
673
+
674
+ /**
675
+ * Get server status (CPU, RAM, etc.)
676
+ */
677
+ getServerStatus() {
678
+ return this._request('get', '/panel/api/server/status');
679
+ }
680
+
681
+ /**
682
+ * Get CPU usage history
683
+ * @param {string} bucket - Time bucket (e.g., 'min', 'hour')
684
+ */
685
+ getCPUHistory(bucket = 'min') {
686
+ return this._request('get', `/panel/api/server/cpuHistory/${bucket}`);
687
+ }
688
+
689
+ /**
690
+ * Get current Xray version
691
+ */
692
+ getXrayVersion() {
693
+ return this._request('get', '/panel/api/server/getXrayVersion');
694
+ }
695
+
696
+ /**
697
+ * Get Xray config as JSON
698
+ */
699
+ getConfigJson() {
700
+ return this._request('get', '/panel/api/server/getConfigJson');
701
+ }
702
+
703
+ /**
704
+ * Download database
705
+ */
706
+ getDb() {
707
+ return this._request('get', '/panel/api/server/getDb');
708
+ }
709
+
710
+ /**
711
+ * Stop Xray core service
712
+ */
713
+ stopXrayService() {
714
+ return this._request('post', '/panel/api/server/stopXrayService');
715
+ }
716
+
717
+ /**
718
+ * Restart Xray core service
719
+ */
720
+ restartXrayService() {
721
+ return this._request('post', '/panel/api/server/restartXrayService');
722
+ }
723
+
724
+ /**
725
+ * Install specific Xray version
726
+ * @param {string} version - Version to install
727
+ */
728
+ installXray(version) {
729
+ return this._request('post', `/panel/api/server/installXray/${version}`);
730
+ }
731
+
732
+ /**
733
+ * Get panel logs
734
+ * @param {number} count - Number of logs to retrieve
735
+ */
736
+ getPanelLogs(count = 100) {
737
+ return this._request('post', `/panel/api/server/logs/${count}`);
738
+ }
739
+
740
+ /**
741
+ * Get Xray logs
742
+ * @param {number} count - Number of logs to retrieve
743
+ */
744
+ getXrayLogs(count = 100) {
745
+ return this._request('post', `/panel/api/server/xraylogs/${count}`);
746
+ }
747
+
748
+ /**
749
+ * Update GeoIP/GeoSite files
750
+ * @param {string} [fileName] - Specific file to update (optional)
751
+ */
752
+ updateGeofile(fileName) {
753
+ const url = fileName
754
+ ? `/panel/api/server/updateGeofile/${fileName}`
755
+ : '/panel/api/server/updateGeofile';
756
+ return this._request('post', url);
757
+ }
758
+
759
+ /**
760
+ * Import database
761
+ * @param {FormData} formData - FormData containing the database file
762
+ */
763
+ async importDB(formData) {
764
+ // Ensure authenticated before direct API call
765
+ if (!this.cookie) {
766
+ await this._ensureAuthenticated();
767
+ }
768
+ // Use direct api call to handle multipart/form-data correctly
769
+ return this.api.post('/panel/api/server/importDB', formData);
770
+ }
771
+
772
+ // ===========================================
773
+ // SERVER-SIDE GENERATORS
774
+ // ===========================================
775
+
776
+ getNewUUID() {
777
+ return this._request('get', '/panel/api/server/getNewUUID');
778
+ }
779
+
780
+ getNewX25519Cert() {
781
+ return this._request('get', '/panel/api/server/getNewX25519Cert');
782
+ }
783
+
784
+ getNewmldsa65() {
785
+ return this._request('get', '/panel/api/server/getNewmldsa65');
786
+ }
787
+
788
+ getNewmlkem768() {
789
+ return this._request('get', '/panel/api/server/getNewmlkem768');
790
+ }
791
+
792
+ getNewVlessEnc() {
793
+ return this._request('get', '/panel/api/server/getNewVlessEnc');
794
+ }
795
+
796
+ getNewEchCert() {
797
+ return this._request('post', '/panel/api/server/getNewEchCert');
798
+ }
799
+
800
+ // ===========================================
801
+ // PANEL SETTINGS
802
+ // ===========================================
803
+
804
+ /**
805
+ * Get all panel settings
806
+ */
807
+ getAllSettings() {
808
+ return this._request('post', '/panel/setting/all');
809
+ }
810
+
811
+ /**
812
+ * Update panel settings
813
+ * @param {Object} settings - Settings to update
814
+ */
815
+ updateSetting(settings) {
816
+ return this._request('post', '/panel/setting/update', settings);
817
+ }
818
+
819
+ /**
820
+ * Update admin username and password
821
+ * @param {string} oldUsername - Current username
822
+ * @param {string} oldPassword - Current password
823
+ * @param {string} newUsername - New username
824
+ * @param {string} newPassword - New password
825
+ */
826
+ updateUser(oldUsername, oldPassword, newUsername, newPassword) {
827
+ return this._request('post', '/panel/setting/updateUser', {
828
+ oldUsername,
829
+ oldPassword,
830
+ newUsername,
831
+ newPassword
832
+ });
833
+ }
834
+
835
+ /**
836
+ * Restart the panel
837
+ */
838
+ restartPanel() {
839
+ return this._request('post', '/panel/setting/restartPanel');
840
+ }
841
+
842
+ /**
843
+ * Get default settings
844
+ */
845
+ getDefaultSettings() {
846
+ return this._request('post', '/panel/setting/defaultSettings');
847
+ }
848
+
849
+ /**
850
+ * Get default Xray JSON config
851
+ */
852
+ getDefaultJsonConfig() {
853
+ return this._request('get', '/panel/setting/getDefaultJsonConfig');
854
+ }
855
+
856
+ // ===========================================
857
+ // XRAY CONFIGURATION
858
+ // ===========================================
859
+
860
+ /**
861
+ * Get Xray configuration
862
+ */
863
+ getXrayConfig() {
864
+ return this._request('post', '/panel/xray/');
865
+ }
866
+
867
+ /**
868
+ * Update Xray configuration
869
+ * @param {string} config - Xray configuration content
870
+ */
871
+ updateXrayConfig(config) {
872
+ return this._request('post', '/panel/xray/update', { content: config });
873
+ }
874
+
875
+ /**
876
+ * Manage WARP
877
+ * @param {string} action - Action to perform (data, del, config, reg, license)
878
+ * @param {Object} [data] - Additional data for the action
879
+ */
880
+ manageWarp(action, data = {}) {
881
+ return this._request('post', `/panel/xray/warp/${action}`, data);
882
+ }
883
+
884
+ /**
885
+ * Get outbound traffic statistics
886
+ */
887
+ getOutboundsTraffic() {
888
+ return this._request('get', '/panel/xray/getOutboundsTraffic');
889
+ }
890
+
891
+ /**
892
+ * Reset outbound traffic statistics
893
+ */
894
+ resetOutboundsTraffic() {
895
+ return this._request('post', '/panel/xray/resetOutboundsTraffic');
896
+ }
897
+
898
+ /**
899
+ * Get Xray execution result
900
+ */
901
+ getXrayResult() {
902
+ return this._request('get', '/panel/xray/getXrayResult');
903
+ }
904
+
905
+ // Security Methods
906
+
907
+ /**
908
+ * Get security statistics and monitoring data
909
+ * @returns {Object} Security statistics
910
+ */
911
+ getSecurityStats() {
912
+ return this.securityMonitor.getStats();
913
+ }
914
+
915
+ /**
916
+ * Clear blocked IPs (admin function)
917
+ */
918
+ clearBlockedIPs() {
919
+ this.securityMonitor.clearBlockedIPs();
920
+ }
921
+
922
+ /**
923
+ * Validate credential strength
924
+ * @param {string} credential - Credential to validate
925
+ * @param {string} type - Type of credential
926
+ * @returns {Object} Validation result
927
+ */
928
+ validateCredentialStrength(credential, type) {
929
+ return CredentialSecurity.validateCredentialStrength(credential, type);
930
+ }
931
+
932
+ /**
933
+ * Generate secure session token
934
+ * @returns {string} Secure session token
935
+ */
936
+ generateSecureToken() {
937
+ return CredentialSecurity.generateSessionToken();
938
+ }
939
+
940
+ /**
941
+ * Enable development mode for detailed error messages
942
+ * @param {boolean} enabled - Whether to enable development mode
943
+ */
944
+ setDevelopmentMode(enabled) {
945
+ this.isDevelopment = enabled;
946
+ }
947
+ }
948
+
949
+ // Export static methods for standalone use
950
+ ThreeXUI.CredentialGenerator = CredentialGenerator;
951
+ ThreeXUI.SessionManager = SessionManager;
952
+ ThreeXUI.createSessionManager = createSessionManager;
953
+
954
+ module.exports = ThreeXUI;
955
+
956
+ // Define lazy getters to avoid circular dependencies
957
+ Object.defineProperties(module.exports, {
958
+ // Web middleware helpers
959
+ createExpressMiddleware: {
960
+ enumerable: true,
961
+ get: () => require('./src/middleware/WebMiddleware').createExpressMiddleware
962
+ },
963
+ withThreeXUI: {
964
+ enumerable: true,
965
+ get: () => require('./src/middleware/WebMiddleware').withThreeXUI
966
+ },
967
+ createReactHook: {
968
+ enumerable: true,
969
+ get: () => require('./src/middleware/WebMiddleware').createReactHook
970
+ },
971
+ createNextjsRoutes: {
972
+ enumerable: true,
973
+ get: () => require('./src/middleware/WebMiddleware').createNextjsRoutes
974
+ },
975
+ SessionConfig: {
976
+ enumerable: true,
977
+ get: () => require('./src/middleware/WebMiddleware').SessionConfig
978
+ },
979
+ // Protocol builders
980
+ ProtocolBuilder: {
981
+ enumerable: true,
982
+ get: () => require('./src/builders/ProtocolBuilders').ProtocolBuilder
983
+ },
984
+ VLESSBuilder: {
985
+ enumerable: true,
986
+ get: () => require('./src/builders/ProtocolBuilders').VLESSBuilder
987
+ },
988
+ VMESSBuilder: {
989
+ enumerable: true,
990
+ get: () => require('./src/builders/ProtocolBuilders').VMESSBuilder
991
+ },
992
+ TrojanBuilder: {
993
+ enumerable: true,
994
+ get: () => require('./src/builders/ProtocolBuilders').TrojanBuilder
995
+ },
996
+ ShadowsocksBuilder: {
997
+ enumerable: true,
998
+ get: () => require('./src/builders/ProtocolBuilders').ShadowsocksBuilder
999
+ },
1000
+ WireGuardBuilder: {
1001
+ enumerable: true,
1002
+ get: () => require('./src/builders/ProtocolBuilders').WireGuardBuilder
1003
+ },
1004
+ BaseBuilder: {
1005
+ enumerable: true,
1006
+ get: () => require('./src/builders/ProtocolBuilders').BaseBuilder
1007
+ },
1008
+ // Security helpers
1009
+ SecurityEnhancer: {
1010
+ enumerable: true,
1011
+ get: () => require('./src/security/SecurityEnhancer')
1012
+ }
715
1013
  });