3xui-api-client 2.1.0 → 3.0.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/CHANGELOG.md +185 -146
- package/LICENSE +20 -20
- package/README.md +374 -317
- package/index.d.ts +506 -436
- package/index.js +1359 -714
- package/index.mjs +4 -4
- package/package.json +113 -113
- package/src/builders/ProtocolBuilders.js +689 -689
- package/src/generators/CredentialGenerator.js +361 -361
- package/src/middleware/WebMiddleware.js +310 -310
- package/src/security/SecurityEnhancer.js +325 -325
- package/src/session/SessionManager.js +492 -492
package/index.js
CHANGED
|
@@ -1,715 +1,1360 @@
|
|
|
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,
|
|
36
|
-
if (!baseURL) {
|
|
37
|
-
throw new Error('baseURL is required');
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
this.
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
this.
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
if (
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
//
|
|
233
|
-
this.loginRetryCount
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
this.
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
*
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
*
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
*
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
return
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
*
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
return
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
}
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
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, usernameOrOptions, password, options = {}) {
|
|
36
|
+
if (!baseURL) {
|
|
37
|
+
throw new Error('baseURL is required');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Apply security validations
|
|
41
|
+
this.baseURL = InputValidator.validateURL(baseURL);
|
|
42
|
+
|
|
43
|
+
// Warning for common configuration error
|
|
44
|
+
if (this.baseURL.endsWith('/panel') || this.baseURL.endsWith('/panel/')) {
|
|
45
|
+
console.warn('WARNING: baseURL should NOT end with "/panel". The library appends this automatically. Please remove it from your configuration.');
|
|
46
|
+
// Auto-fix for better user experience
|
|
47
|
+
this.baseURL = this.baseURL.replace(/\/panel\/?$/, '');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Object containing configs as 2nd parameter
|
|
51
|
+
if (typeof usernameOrOptions === 'object' && usernameOrOptions !== null) {
|
|
52
|
+
options = usernameOrOptions;
|
|
53
|
+
this.username = options.username;
|
|
54
|
+
this.password = options.password;
|
|
55
|
+
} else {
|
|
56
|
+
this.username = usernameOrOptions;
|
|
57
|
+
this.password = password;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
this.token = options.token || options.apiToken || null;
|
|
61
|
+
|
|
62
|
+
if (this.token) {
|
|
63
|
+
this.username = this.username || 'token-auth'; // prevent missing args error
|
|
64
|
+
this.password = this.password || 'token-auth';
|
|
65
|
+
} else {
|
|
66
|
+
if (!this.username) {
|
|
67
|
+
throw new Error('username is required');
|
|
68
|
+
}
|
|
69
|
+
if (!this.password) {
|
|
70
|
+
throw new Error('password is required');
|
|
71
|
+
}
|
|
72
|
+
this.username = InputValidator.validateUsername(this.username);
|
|
73
|
+
this.password = InputValidator.validatePassword(this.password);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
this.cookie = null;
|
|
77
|
+
this.options = options;
|
|
78
|
+
this.loginMutex = false; // Add mutex to prevent concurrent logins
|
|
79
|
+
this.loginRetryCount = 0; // Add retry counter
|
|
80
|
+
this.maxLoginRetries = 3; // Maximum login attempts
|
|
81
|
+
|
|
82
|
+
// Initialize security monitoring
|
|
83
|
+
this.securityMonitor = new SecurityMonitor({
|
|
84
|
+
maxRequestsPerMinute: options.maxRequestsPerMinute || 60,
|
|
85
|
+
maxLoginAttemptsPerHour: options.maxLoginAttemptsPerHour || 10
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
// Security configuration
|
|
89
|
+
this.isDevelopment = options.isDevelopment || process.env.NODE_ENV === 'development';
|
|
90
|
+
|
|
91
|
+
// Initialize session manager if provided
|
|
92
|
+
if (options.sessionManager) {
|
|
93
|
+
this.sessionManager = options.sessionManager instanceof SessionManager
|
|
94
|
+
? options.sessionManager
|
|
95
|
+
: createSessionManager(options.sessionManager);
|
|
96
|
+
} else {
|
|
97
|
+
// Default to memory-based session management
|
|
98
|
+
this.sessionManager = createSessionManager();
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Create axios instance with security best practices
|
|
102
|
+
const secureHeaders = SecureHeaders.getSecureHeaders({
|
|
103
|
+
userAgent: options.userAgent || '3xui-api-client/2.0.0 (Security-Enhanced)',
|
|
104
|
+
enableCSP: options.enableCSP || false
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
if (this.token) {
|
|
108
|
+
secureHeaders['Authorization'] = `Bearer ${this.token}`;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
this.api = axios.create({
|
|
112
|
+
baseURL: this.baseURL,
|
|
113
|
+
timeout: options.timeout || 30000, // 30 second timeout
|
|
114
|
+
maxRedirects: 5,
|
|
115
|
+
validateStatus: (status) => status >= 200 && status < 300,
|
|
116
|
+
headers: secureHeaders
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
// Add request interceptor for security headers
|
|
120
|
+
this.api.interceptors.request.use((config) => {
|
|
121
|
+
// Add security headers
|
|
122
|
+
config.headers['X-Requested-With'] = 'XMLHttpRequest';
|
|
123
|
+
return config;
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
// Add response interceptor for error handling
|
|
127
|
+
this.api.interceptors.response.use(
|
|
128
|
+
(response) => response,
|
|
129
|
+
(error) => {
|
|
130
|
+
if (error.code === 'ECONNABORTED') {
|
|
131
|
+
throw new Error('Request timeout - server took too long to respond');
|
|
132
|
+
}
|
|
133
|
+
if (error.code === 'ENOTFOUND') {
|
|
134
|
+
throw new Error(`Cannot connect to server: ${this.baseURL}`);
|
|
135
|
+
}
|
|
136
|
+
throw error;
|
|
137
|
+
}
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Login with session management support
|
|
143
|
+
* @param {boolean} forceRefresh - Force a new login even if session exists
|
|
144
|
+
* @returns {Object} Login response
|
|
145
|
+
*/
|
|
146
|
+
async login(forceRefresh = false) {
|
|
147
|
+
// If API token is configured, skip cookie auth
|
|
148
|
+
if (this.token) {
|
|
149
|
+
return {
|
|
150
|
+
success: true,
|
|
151
|
+
message: 'Authenticated successfully using API Token'
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Check rate limiting first
|
|
156
|
+
const identifier = CredentialSecurity.hashForLogging(this.username);
|
|
157
|
+
if (!this.securityMonitor.checkRateLimit(identifier, 'login')) {
|
|
158
|
+
const error = new Error('Rate limit exceeded for login attempts');
|
|
159
|
+
ErrorSecurity.logError(error, { username: this.username, baseURL: this.baseURL });
|
|
160
|
+
throw error;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Increment retry count
|
|
164
|
+
this.loginRetryCount++;
|
|
165
|
+
// Check for existing valid session first
|
|
166
|
+
if (!forceRefresh && this.sessionManager) {
|
|
167
|
+
const existingSession = await this.sessionManager.getSession(this.baseURL, this.username);
|
|
168
|
+
if (existingSession && existingSession.cookie) {
|
|
169
|
+
this.cookie = existingSession.cookie;
|
|
170
|
+
this.api.defaults.headers.Cookie = this.cookie;
|
|
171
|
+
// Reset retry count on successful session restore
|
|
172
|
+
this.loginRetryCount = 0;
|
|
173
|
+
return {
|
|
174
|
+
success: true,
|
|
175
|
+
fromCache: true,
|
|
176
|
+
data: { msg: 'Session restored from cache' }
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
try {
|
|
182
|
+
const params = new URLSearchParams();
|
|
183
|
+
params.append('username', this.username);
|
|
184
|
+
params.append('password', this.password);
|
|
185
|
+
|
|
186
|
+
const response = await this.api.post('/login', params, {
|
|
187
|
+
headers: {
|
|
188
|
+
'Content-Type': 'application/x-www-form-urlencoded'
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
if (response.data.success) {
|
|
193
|
+
const cookies = response.headers['set-cookie'];
|
|
194
|
+
if (cookies && cookies.length > 0) {
|
|
195
|
+
this.cookie = cookies[0].split(';')[0];
|
|
196
|
+
this.api.defaults.headers.Cookie = this.cookie;
|
|
197
|
+
|
|
198
|
+
// Store session if session manager is available
|
|
199
|
+
if (this.sessionManager) {
|
|
200
|
+
try {
|
|
201
|
+
await this.sessionManager.storeSession(this.baseURL, this.username, {
|
|
202
|
+
cookie: this.cookie,
|
|
203
|
+
loginTime: new Date().toISOString()
|
|
204
|
+
});
|
|
205
|
+
} catch (sessionError) {
|
|
206
|
+
console.warn('Failed to store session, continuing without cache:', sessionError.message);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
// Reset retry count on successful login
|
|
210
|
+
this.loginRetryCount = 0;
|
|
211
|
+
} else {
|
|
212
|
+
throw new Error('Login failed: No session cookie received.');
|
|
213
|
+
}
|
|
214
|
+
return {
|
|
215
|
+
success: true,
|
|
216
|
+
fromCache: false,
|
|
217
|
+
cookie: this.cookie, // Explicitly return the cookie
|
|
218
|
+
headers: response.headers,
|
|
219
|
+
data: response.data
|
|
220
|
+
};
|
|
221
|
+
} else {
|
|
222
|
+
throw new Error(`Login failed: ${response.data.msg}`);
|
|
223
|
+
}
|
|
224
|
+
} catch (error) {
|
|
225
|
+
// Log the error securely
|
|
226
|
+
ErrorSecurity.logError(error, {
|
|
227
|
+
username: this.username,
|
|
228
|
+
baseURL: this.baseURL,
|
|
229
|
+
action: 'login'
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
// Log suspicious activity for multiple failed logins
|
|
233
|
+
if (this.loginRetryCount >= this.maxLoginRetries) {
|
|
234
|
+
this.securityMonitor.logSuspiciousActivity('multiple_failed_logins', {
|
|
235
|
+
username: CredentialSecurity.hashForLogging(this.username),
|
|
236
|
+
attempts: this.loginRetryCount
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Return sanitized error
|
|
241
|
+
const sanitizedError = ErrorSecurity.sanitizeError(error, this.isDevelopment);
|
|
242
|
+
throw sanitizedError;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Logout and clear session
|
|
248
|
+
*/
|
|
249
|
+
async logout() {
|
|
250
|
+
try {
|
|
251
|
+
await this._request('get', '/logout');
|
|
252
|
+
} catch {
|
|
253
|
+
// Ignore server-side logout errors, proceed to clear local session
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
this.cookie = null;
|
|
257
|
+
delete this.api.defaults.headers.Cookie;
|
|
258
|
+
|
|
259
|
+
if (this.sessionManager) {
|
|
260
|
+
await this.sessionManager.deleteSession(this.baseURL, this.username);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Check if Two-Factor Authentication is enabled
|
|
266
|
+
* @returns {Promise<Object>} 2FA status
|
|
267
|
+
*/
|
|
268
|
+
getTwoFactorEnable() {
|
|
269
|
+
return this._request('post', '/getTwoFactorEnable');
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
async _request(method, path, data = {}) {
|
|
273
|
+
// Check session validity first with mutex protection if token is not provided
|
|
274
|
+
if (!this.token) {
|
|
275
|
+
if (!this.loginMutex && this.sessionManager && !await this.sessionManager.hasValidSession(this.baseURL, this.username)) {
|
|
276
|
+
await this._ensureAuthenticated();
|
|
277
|
+
} else if (!this.loginMutex && !this.cookie) {
|
|
278
|
+
await this._ensureAuthenticated();
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
try {
|
|
283
|
+
const response = await this.api.request({
|
|
284
|
+
method,
|
|
285
|
+
url: path,
|
|
286
|
+
data,
|
|
287
|
+
...(method.toLowerCase() === 'post' ? { headers: { 'Content-Type': 'application/json' } } : {})
|
|
288
|
+
});
|
|
289
|
+
// Reset retry counter on successful request
|
|
290
|
+
this.loginRetryCount = 0;
|
|
291
|
+
return response.data;
|
|
292
|
+
} catch (error) {
|
|
293
|
+
if (error.response && error.response.status === 401) {
|
|
294
|
+
if (this.token) {
|
|
295
|
+
throw new Error('API Token is invalid or expired. Please check your credentials.');
|
|
296
|
+
}
|
|
297
|
+
// Cookie might have expired, try to login again with retry limit
|
|
298
|
+
if (this.loginRetryCount < this.maxLoginRetries) {
|
|
299
|
+
await this._ensureAuthenticated(true); // Force refresh
|
|
300
|
+
const response = await this.api.request({
|
|
301
|
+
method,
|
|
302
|
+
url: path,
|
|
303
|
+
data,
|
|
304
|
+
...(method.toLowerCase() === 'post' ? { headers: { 'Content-Type': 'application/json' } } : {})
|
|
305
|
+
});
|
|
306
|
+
return response.data;
|
|
307
|
+
} else {
|
|
308
|
+
throw new Error('Maximum login retry attempts exceeded. Check your credentials.');
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
throw error;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Ensure authentication with mutex protection
|
|
317
|
+
* @param {boolean} forceRefresh - Force a new login
|
|
318
|
+
*/
|
|
319
|
+
async _ensureAuthenticated(forceRefresh = false) {
|
|
320
|
+
// Prevent concurrent login attempts
|
|
321
|
+
if (this.loginMutex) {
|
|
322
|
+
// Wait for ongoing login to complete
|
|
323
|
+
while (this.loginMutex) {
|
|
324
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
325
|
+
}
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
this.loginMutex = true;
|
|
330
|
+
try {
|
|
331
|
+
await this.login(forceRefresh);
|
|
332
|
+
} finally {
|
|
333
|
+
this.loginMutex = false;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// ===========================================
|
|
338
|
+
// CREDENTIAL GENERATION METHODS
|
|
339
|
+
// ===========================================
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Generate credentials for a specific protocol
|
|
343
|
+
* @param {string} protocol - Protocol name (vless, vmess, trojan, etc.)
|
|
344
|
+
* @param {Object} options - Generation options
|
|
345
|
+
* @returns {Object} Generated credentials
|
|
346
|
+
*/
|
|
347
|
+
generateCredentials(protocol, options = {}) {
|
|
348
|
+
return CredentialGenerator.generateForProtocol(protocol, options);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Generate UUID for VLESS/VMess protocols
|
|
353
|
+
* @param {boolean} secure - Use cryptographically secure generation
|
|
354
|
+
* @returns {string} UUID
|
|
355
|
+
*/
|
|
356
|
+
generateUUID(secure = true) {
|
|
357
|
+
return secure ? CredentialGenerator.generateSecureUUID() : CredentialGenerator.generateUUID();
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Generate password for Trojan/Shadowsocks protocols
|
|
362
|
+
* @param {number} length - Password length
|
|
363
|
+
* @param {Object} options - Password options
|
|
364
|
+
* @returns {string} Generated password
|
|
365
|
+
*/
|
|
366
|
+
generatePassword(length = 16, options = {}) {
|
|
367
|
+
return CredentialGenerator.generatePassword(length, options);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* Generate bulk credentials for multiple clients
|
|
372
|
+
* @param {string} protocol - Protocol name
|
|
373
|
+
* @param {number} count - Number of credentials to generate
|
|
374
|
+
* @param {Object} options - Generation options
|
|
375
|
+
* @returns {Array} Array of credentials
|
|
376
|
+
*/
|
|
377
|
+
generateBulkCredentials(protocol, count, options = {}) {
|
|
378
|
+
return CredentialGenerator.generateBulk(protocol, count, options);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* Get available cipher methods for Shadowsocks
|
|
383
|
+
* @returns {Array} Array of cipher methods
|
|
384
|
+
*/
|
|
385
|
+
getShadowsocksCiphers() {
|
|
386
|
+
return CredentialGenerator.getShadowsocksCipherMethods();
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* Get recommended cipher for Shadowsocks
|
|
391
|
+
* @returns {string} Recommended cipher
|
|
392
|
+
*/
|
|
393
|
+
getRecommendedShadowsocksCipher() {
|
|
394
|
+
return CredentialGenerator.getRecommendedShadowsocksCipher();
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* Generate WireGuard key pair
|
|
399
|
+
* @returns {Object} Key pair with helper methods
|
|
400
|
+
*/
|
|
401
|
+
generateWireGuardKeys() {
|
|
402
|
+
return CredentialGenerator.generateWireGuardKeys();
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* Generate Reality key pair for anti-censorship
|
|
407
|
+
* @returns {Object} Reality key pair with helper methods
|
|
408
|
+
*/
|
|
409
|
+
generateRealityKeys() {
|
|
410
|
+
return CredentialGenerator.generateRealityKeys();
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* Generate random port number
|
|
415
|
+
* @param {number} min - Minimum port
|
|
416
|
+
* @param {number} max - Maximum port
|
|
417
|
+
* @returns {number} Random port
|
|
418
|
+
*/
|
|
419
|
+
generatePort(min = 10000, max = 65535) {
|
|
420
|
+
return CredentialGenerator.generatePort(min, max);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* Validate generated credentials
|
|
425
|
+
* @param {Object} credentials - Credentials to validate
|
|
426
|
+
* @param {string} protocol - Protocol name
|
|
427
|
+
* @returns {Object} Validation result
|
|
428
|
+
*/
|
|
429
|
+
validateCredentials(credentials, protocol) {
|
|
430
|
+
return CredentialGenerator.validateCredentials(credentials, protocol);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// ===========================================
|
|
434
|
+
// ENHANCED CLIENT MANAGEMENT WITH AUTO-GENERATION
|
|
435
|
+
// ===========================================
|
|
436
|
+
|
|
437
|
+
/**
|
|
438
|
+
* Add client with automatic credential generation
|
|
439
|
+
* @param {number} inboundId - Inbound ID
|
|
440
|
+
* @param {string} protocol - Protocol type
|
|
441
|
+
* @param {Object} options - Client options
|
|
442
|
+
* @returns {Object} Created client with credentials
|
|
443
|
+
*/
|
|
444
|
+
async addClientWithCredentials(inboundId, protocol, options = {}) {
|
|
445
|
+
const credentials = this.generateCredentials(protocol, options);
|
|
446
|
+
|
|
447
|
+
const clientConfig = {
|
|
448
|
+
id: inboundId,
|
|
449
|
+
settings: JSON.stringify({
|
|
450
|
+
clients: [{
|
|
451
|
+
...credentials,
|
|
452
|
+
enable: true,
|
|
453
|
+
expiryTime: options.expiryTime || 0,
|
|
454
|
+
limitIp: options.limitIp || 0,
|
|
455
|
+
totalGB: options.totalGB || 0,
|
|
456
|
+
subId: options.subId || this.generateUUID()
|
|
457
|
+
}]
|
|
458
|
+
})
|
|
459
|
+
};
|
|
460
|
+
|
|
461
|
+
const result = await this.addClient(clientConfig);
|
|
462
|
+
return {
|
|
463
|
+
...result,
|
|
464
|
+
credentials,
|
|
465
|
+
protocol
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
/**
|
|
470
|
+
* Update client with automatic credential management
|
|
471
|
+
* @param {string} clientId - Client UUID/ID
|
|
472
|
+
* @param {number} inboundId - Inbound ID
|
|
473
|
+
* @param {Object} options - Update options
|
|
474
|
+
* @returns {Object} Update result
|
|
475
|
+
*/
|
|
476
|
+
async updateClientWithCredentials(clientId, inboundId, options = {}) {
|
|
477
|
+
try {
|
|
478
|
+
// First, get the current inbound to obtain all existing clients
|
|
479
|
+
const inboundData = await this.getInbound(inboundId);
|
|
480
|
+
|
|
481
|
+
if (!inboundData.success || !inboundData.obj) {
|
|
482
|
+
throw new Error(`Failed to get inbound ${inboundId} for client update`);
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// Parse existing settings to get all clients
|
|
486
|
+
const currentSettings = JSON.parse(inboundData.obj.settings);
|
|
487
|
+
const existingClients = currentSettings.clients || [];
|
|
488
|
+
|
|
489
|
+
// Find the client to update
|
|
490
|
+
const clientIndex = existingClients.findIndex(client => client.id === clientId);
|
|
491
|
+
if (clientIndex === -1) {
|
|
492
|
+
throw new Error(`Client with ID ${clientId} not found in inbound ${inboundId}`);
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// Convert user-friendly options to API format
|
|
496
|
+
const processedOptions = {
|
|
497
|
+
email: options.email || existingClients[clientIndex].email,
|
|
498
|
+
limitIp: options.limitIp !== undefined ? options.limitIp : existingClients[clientIndex].limitIp,
|
|
499
|
+
// totalGB is specified in gigabytes in 3x-ui config; do not convert to bytes
|
|
500
|
+
totalGB: options.totalGB !== undefined ? options.totalGB : existingClients[clientIndex].totalGB,
|
|
501
|
+
expiryTime: options.expiryDays ? Date.now() + (options.expiryDays * 24 * 60 * 60 * 1000) : existingClients[clientIndex].expiryTime,
|
|
502
|
+
enable: options.enable !== undefined ? options.enable : existingClients[clientIndex].enable,
|
|
503
|
+
flow: options.flow || existingClients[clientIndex].flow,
|
|
504
|
+
encryption: options.encryption || existingClients[clientIndex].encryption || 'none',
|
|
505
|
+
subId: options.subId || existingClients[clientIndex].subId
|
|
506
|
+
};
|
|
507
|
+
|
|
508
|
+
// Update the specific client while preserving others
|
|
509
|
+
existingClients[clientIndex] = {
|
|
510
|
+
...existingClients[clientIndex],
|
|
511
|
+
...processedOptions
|
|
512
|
+
};
|
|
513
|
+
|
|
514
|
+
// Prepare the complete settings with all clients
|
|
515
|
+
const updatedSettings = {
|
|
516
|
+
...currentSettings,
|
|
517
|
+
clients: existingClients
|
|
518
|
+
};
|
|
519
|
+
|
|
520
|
+
const clientConfig = {
|
|
521
|
+
id: inboundId,
|
|
522
|
+
settings: JSON.stringify(updatedSettings)
|
|
523
|
+
};
|
|
524
|
+
|
|
525
|
+
const result = await this.updateClient(clientId, clientConfig);
|
|
526
|
+
return {
|
|
527
|
+
...result,
|
|
528
|
+
updatedOptions: processedOptions,
|
|
529
|
+
conversions: {
|
|
530
|
+
totalGB: options.totalGB !== undefined ? `${options.totalGB}GB` : 'unchanged',
|
|
531
|
+
expiryDays: options.expiryDays ? `${options.expiryDays} days → ${new Date(processedOptions.expiryTime).toISOString()}` : 'unchanged'
|
|
532
|
+
}
|
|
533
|
+
};
|
|
534
|
+
} catch (error) {
|
|
535
|
+
return {
|
|
536
|
+
success: false,
|
|
537
|
+
message: error.message,
|
|
538
|
+
error: error.message,
|
|
539
|
+
details: 'updateClientWithCredentials failed - check client ID and inbound ID'
|
|
540
|
+
};
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
// ===========================================
|
|
545
|
+
// SESSION MANAGEMENT METHODS
|
|
546
|
+
// ===========================================
|
|
547
|
+
|
|
548
|
+
/**
|
|
549
|
+
* Get session statistics
|
|
550
|
+
* @returns {Object} Session statistics
|
|
551
|
+
*/
|
|
552
|
+
async getSessionStats() {
|
|
553
|
+
if (this.sessionManager) {
|
|
554
|
+
return await this.sessionManager.getStats();
|
|
555
|
+
}
|
|
556
|
+
return { message: 'Session manager not initialized' };
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
/**
|
|
560
|
+
* Clear all cached sessions
|
|
561
|
+
*/
|
|
562
|
+
async clearAllSessions() {
|
|
563
|
+
if (this.sessionManager) {
|
|
564
|
+
await this.sessionManager.clearAllSessions();
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
/**
|
|
569
|
+
* Check if current session is valid
|
|
570
|
+
* @returns {boolean} Session validity
|
|
571
|
+
*/
|
|
572
|
+
async isSessionValid() {
|
|
573
|
+
if (this.token) {
|
|
574
|
+
return true;
|
|
575
|
+
}
|
|
576
|
+
if (this.sessionManager) {
|
|
577
|
+
return await this.sessionManager.hasValidSession(this.baseURL, this.username);
|
|
578
|
+
}
|
|
579
|
+
return !!this.cookie;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
// ===========================================
|
|
583
|
+
// MODERN API METHODS (3X-UI >= 2.x)
|
|
584
|
+
// ===========================================
|
|
585
|
+
|
|
586
|
+
// --- Clients ---
|
|
587
|
+
|
|
588
|
+
/**
|
|
589
|
+
* Get list of all clients
|
|
590
|
+
* @returns {Promise<Object>} Formatted list of all clients
|
|
591
|
+
*/
|
|
592
|
+
getClients() {
|
|
593
|
+
return this._request('get', '/panel/api/clients/list');
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
/**
|
|
597
|
+
* Get paginated list of clients
|
|
598
|
+
* @param {Object} params - Pagination parameters
|
|
599
|
+
* @param {number} params.page - Page number (default: 1)
|
|
600
|
+
* @param {number} params.size - Items per page (default: 10)
|
|
601
|
+
* @param {string} params.sort - Sort field (e.g., 'email', 'expireTime')
|
|
602
|
+
* @param {string} params.order - Sort order ('asc' or 'desc')
|
|
603
|
+
* @param {string} params.email - Filter by email
|
|
604
|
+
* @returns {Promise<Object>} Paginated clients
|
|
605
|
+
*/
|
|
606
|
+
getPagedClients(params = {}) {
|
|
607
|
+
const queryParams = new URLSearchParams();
|
|
608
|
+
if (params.page !== undefined) {
|
|
609
|
+
queryParams.append('page', params.page);
|
|
610
|
+
}
|
|
611
|
+
if (params.size !== undefined) {
|
|
612
|
+
queryParams.append('size', params.size);
|
|
613
|
+
}
|
|
614
|
+
if (params.sort !== undefined) {
|
|
615
|
+
queryParams.append('sort', params.sort);
|
|
616
|
+
}
|
|
617
|
+
if (params.order !== undefined) {
|
|
618
|
+
queryParams.append('order', params.order);
|
|
619
|
+
}
|
|
620
|
+
if (params.email !== undefined) {
|
|
621
|
+
queryParams.append('email', params.email);
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
const queryString = queryParams.toString();
|
|
625
|
+
const url = queryString ? `/panel/api/clients/list/paged?${queryString}` : '/panel/api/clients/list/paged';
|
|
626
|
+
|
|
627
|
+
return this._request('get', url);
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
/**
|
|
631
|
+
* Get client by email
|
|
632
|
+
* @param {string} email - Exact client email address
|
|
633
|
+
* @returns {Promise<Object>} Client metadata
|
|
634
|
+
*/
|
|
635
|
+
getClient(email) {
|
|
636
|
+
return this._request('get', `/panel/api/clients/get/${encodeURIComponent(email)}`);
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
/**
|
|
640
|
+
* Get client traffic by email
|
|
641
|
+
* @param {string} email - Exact client email
|
|
642
|
+
* @returns {Promise<Object>} Client traffic details
|
|
643
|
+
*/
|
|
644
|
+
getClientTraffic(email) {
|
|
645
|
+
return this._request('get', `/panel/api/clients/traffic/${encodeURIComponent(email)}`);
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
/**
|
|
649
|
+
* Get subscription links for a client by subscription ID
|
|
650
|
+
* @param {string} subId - Subscription ID (UUID)
|
|
651
|
+
* @returns {Promise<Object>} Subscription details and links
|
|
652
|
+
*/
|
|
653
|
+
getSubLinks(subId) {
|
|
654
|
+
return this._request('get', `/panel/api/clients/subLinks/${encodeURIComponent(subId)}`);
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
/**
|
|
658
|
+
* Get generic client links by email
|
|
659
|
+
* @param {string} email - Exact client email
|
|
660
|
+
* @returns {Promise<Object>} Link strings
|
|
661
|
+
*/
|
|
662
|
+
getClientLinks(email) {
|
|
663
|
+
return this._request('get', `/panel/api/clients/links/${encodeURIComponent(email)}`);
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
/**
|
|
667
|
+
* Add a new client via Modern API
|
|
668
|
+
* @param {Object} data - Client payload
|
|
669
|
+
* @returns {Promise<Object>} Addition response
|
|
670
|
+
*/
|
|
671
|
+
addModernClient(data) {
|
|
672
|
+
return this._request('post', '/panel/api/clients/add', data);
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
/**
|
|
676
|
+
* Update client by email via Modern API
|
|
677
|
+
* @param {string} email - Exact client email
|
|
678
|
+
* @param {Object} data - Update payload
|
|
679
|
+
* @returns {Promise<Object>} Update response
|
|
680
|
+
*/
|
|
681
|
+
updateModernClient(email, data) {
|
|
682
|
+
return this._request('post', `/panel/api/clients/update/${encodeURIComponent(email)}`, data);
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
/**
|
|
686
|
+
* Delete client by email via Modern API
|
|
687
|
+
* @param {string} email - Exact client email
|
|
688
|
+
* @returns {Promise<Object>} Delete response
|
|
689
|
+
*/
|
|
690
|
+
deleteModernClient(email) {
|
|
691
|
+
return this._request('post', `/panel/api/clients/del/${encodeURIComponent(email)}`);
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
attachClientToInbounds(email, data) {
|
|
695
|
+
return this._request('post', `/panel/api/clients/${encodeURIComponent(email)}/attach`, data);
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
detachClientFromInbounds(email, data) {
|
|
699
|
+
return this._request('post', `/panel/api/clients/${encodeURIComponent(email)}/detach`, data);
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
resetAllModernClientTraffics() {
|
|
703
|
+
return this._request('post', '/panel/api/clients/resetAllTraffics');
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
deleteDepletedModernClients() {
|
|
707
|
+
return this._request('post', '/panel/api/clients/delDepleted');
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
bulkAdjustModernClients(data) {
|
|
711
|
+
return this._request('post', '/panel/api/clients/bulkAdjust', data);
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
bulkDeleteModernClients(data) {
|
|
715
|
+
return this._request('post', '/panel/api/clients/bulkDel', data);
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
bulkCreateModernClients(data) {
|
|
719
|
+
return this._request('post', '/panel/api/clients/bulkCreate', data);
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
bulkAttachModernClients(data) {
|
|
723
|
+
return this._request('post', '/panel/api/clients/bulkAttach', data);
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
bulkDetachModernClients(data) {
|
|
727
|
+
return this._request('post', '/panel/api/clients/bulkDetach', data);
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
bulkResetTrafficModernClients(data) {
|
|
731
|
+
return this._request('post', '/panel/api/clients/bulkResetTraffic', data);
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
resetModernClientTrafficByEmail(email) {
|
|
735
|
+
return this._request('post', `/panel/api/clients/resetTraffic/${encodeURIComponent(email)}`);
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
updateModernClientTrafficByEmail(email, data) {
|
|
739
|
+
return this._request('post', `/panel/api/clients/updateTraffic/${encodeURIComponent(email)}`, data);
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
getModernClientIps(email) {
|
|
743
|
+
return this._request('post', `/panel/api/clients/ips/${encodeURIComponent(email)}`);
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
clearModernClientIps(email) {
|
|
747
|
+
return this._request('post', `/panel/api/clients/clearIps/${encodeURIComponent(email)}`);
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
getOnlines() {
|
|
751
|
+
return this._request('post', '/panel/api/clients/onlines');
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
getModernLastOnline() {
|
|
755
|
+
return this._request('post', '/panel/api/clients/lastOnline');
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
// --- Client Groups ---
|
|
759
|
+
|
|
760
|
+
/**
|
|
761
|
+
* Get list of all client groups
|
|
762
|
+
* @returns {Promise<Object>} List of client groups
|
|
763
|
+
*/
|
|
764
|
+
getGroups() {
|
|
765
|
+
return this._request('get', '/panel/api/clients/groups');
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
/**
|
|
769
|
+
* Get list of emails belonging to a specific group
|
|
770
|
+
* @param {string} groupName - The name of the group
|
|
771
|
+
* @returns {Promise<Object>} List of emails in the group
|
|
772
|
+
*/
|
|
773
|
+
getGroupEmails(groupName) {
|
|
774
|
+
return this._request('get', `/panel/api/clients/groups/${encodeURIComponent(groupName)}/emails`);
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
createGroup(data) {
|
|
778
|
+
return this._request('post', '/panel/api/clients/groups/create', data);
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
renameGroup(data) {
|
|
782
|
+
return this._request('post', '/panel/api/clients/groups/rename', data);
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
deleteGroup(data) {
|
|
786
|
+
return this._request('post', '/panel/api/clients/groups/delete', data);
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
bulkAddGroups(data) {
|
|
790
|
+
return this._request('post', '/panel/api/clients/groups/bulkAdd', data);
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
bulkRemoveGroups(data) {
|
|
794
|
+
return this._request('post', '/panel/api/clients/groups/bulkRemove', data);
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
// --- Nodes ---
|
|
798
|
+
|
|
799
|
+
/**
|
|
800
|
+
* Get list of all nodes
|
|
801
|
+
* @returns {Promise<Object>} List of nodes
|
|
802
|
+
*/
|
|
803
|
+
getNodes() {
|
|
804
|
+
return this._request('get', '/panel/api/nodes/list');
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
/**
|
|
808
|
+
* Get specific node by ID
|
|
809
|
+
* @param {number|string} id - Node ID
|
|
810
|
+
* @returns {Promise<Object>} Node details
|
|
811
|
+
*/
|
|
812
|
+
getNode(id) {
|
|
813
|
+
return this._request('get', `/panel/api/nodes/get/${encodeURIComponent(id)}`);
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
/**
|
|
817
|
+
* Get history metrics for a node
|
|
818
|
+
* @param {number|string} id - Node ID
|
|
819
|
+
* @param {string} metric - Metric name (e.g., 'cpu', 'memory')
|
|
820
|
+
* @param {string} bucket - Time bucket size
|
|
821
|
+
* @returns {Promise<Object>} Node history data
|
|
822
|
+
*/
|
|
823
|
+
getNodeHistory(id, metric, bucket) {
|
|
824
|
+
return this._request('get', `/panel/api/nodes/history/${encodeURIComponent(id)}/${encodeURIComponent(metric)}/${encodeURIComponent(bucket)}`);
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
addNode(data) {
|
|
828
|
+
return this._request('post', '/panel/api/nodes/add', data);
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
updateNode(id, data) {
|
|
832
|
+
return this._request('post', `/panel/api/nodes/update/${encodeURIComponent(id)}`, data);
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
deleteNode(id) {
|
|
836
|
+
return this._request('post', `/panel/api/nodes/del/${encodeURIComponent(id)}`);
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
setNodeEnable(id) {
|
|
840
|
+
return this._request('post', `/panel/api/nodes/setEnable/${encodeURIComponent(id)}`);
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
testNode(data) {
|
|
844
|
+
return this._request('post', '/panel/api/nodes/test', data);
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
probeNode(id) {
|
|
848
|
+
return this._request('post', `/panel/api/nodes/probe/${encodeURIComponent(id)}`);
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
// --- Custom Geo ---
|
|
852
|
+
|
|
853
|
+
/**
|
|
854
|
+
* Get list of custom geo sites/ips
|
|
855
|
+
* @returns {Promise<Object>} List of custom geos
|
|
856
|
+
*/
|
|
857
|
+
getCustomGeos() {
|
|
858
|
+
return this._request('get', '/panel/api/custom-geo/list');
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
/**
|
|
862
|
+
* Get aliases for custom geos
|
|
863
|
+
* @returns {Promise<Object>} Custom geo aliases
|
|
864
|
+
*/
|
|
865
|
+
getGeoAliases() {
|
|
866
|
+
return this._request('get', '/panel/api/custom-geo/aliases');
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
addCustomGeo(data) {
|
|
870
|
+
return this._request('post', '/panel/api/custom-geo/add', data);
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
updateCustomGeo(id, data) {
|
|
874
|
+
return this._request('post', `/panel/api/custom-geo/update/${encodeURIComponent(id)}`, data);
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
deleteCustomGeo(id) {
|
|
878
|
+
return this._request('post', `/panel/api/custom-geo/delete/${encodeURIComponent(id)}`);
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
downloadCustomGeo(id) {
|
|
882
|
+
return this._request('post', `/panel/api/custom-geo/download/${encodeURIComponent(id)}`);
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
updateAllCustomGeo() {
|
|
886
|
+
return this._request('post', '/panel/api/custom-geo/update-all');
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
// ===========================================
|
|
890
|
+
// ORIGINAL API METHODS (UNCHANGED)
|
|
891
|
+
// ===========================================
|
|
892
|
+
|
|
893
|
+
// Inbounds
|
|
894
|
+
getInbounds() {
|
|
895
|
+
return this._request('get', '/panel/api/inbounds/list');
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
getInbound(id) {
|
|
899
|
+
return this._request('get', `/panel/api/inbounds/get/${id}`);
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
addInbound(inboundConfig) {
|
|
903
|
+
// Validate inbound configuration for security
|
|
904
|
+
const validatedConfig = InputValidator.validateInboundConfig(inboundConfig);
|
|
905
|
+
return this._request('post', '/panel/api/inbounds/add', validatedConfig);
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
deleteInbound(id) {
|
|
909
|
+
return this._request('post', `/panel/api/inbounds/del/${id}`);
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
updateInbound(id, inboundConfig) {
|
|
913
|
+
// Validate inbound configuration for security
|
|
914
|
+
const validatedConfig = InputValidator.validateInboundConfig(inboundConfig);
|
|
915
|
+
return this._request('post', `/panel/api/inbounds/update/${id}`, validatedConfig);
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
/**
|
|
919
|
+
* Import inbounds
|
|
920
|
+
* @param {Array} inbounds - Array of inbound configurations
|
|
921
|
+
*/
|
|
922
|
+
importInbounds(inbounds) {
|
|
923
|
+
return this._request('post', '/panel/api/inbounds/import', { inbounds });
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
/**
|
|
927
|
+
* Get last online time for clients
|
|
928
|
+
*/
|
|
929
|
+
getLastOnline() {
|
|
930
|
+
return this._request('post', '/panel/api/inbounds/lastOnline');
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
// Clients
|
|
934
|
+
addClient(clientConfig) {
|
|
935
|
+
// Validate client configuration for security
|
|
936
|
+
const validatedConfig = InputValidator.validateClientConfig(clientConfig);
|
|
937
|
+
return this._request('post', '/panel/api/inbounds/addClient', validatedConfig);
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
deleteClient(inboundId, clientId) {
|
|
941
|
+
return this._request('post', `/panel/api/inbounds/${inboundId}/delClient/${clientId}`);
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
updateClient(clientId, clientConfig) {
|
|
945
|
+
// Validate client configuration for security
|
|
946
|
+
const validatedConfig = InputValidator.validateClientConfig(clientConfig);
|
|
947
|
+
return this._request('post', `/panel/api/inbounds/updateClient/${clientId}`, validatedConfig);
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
/**
|
|
951
|
+
* Update client traffic limit and expiry by email
|
|
952
|
+
* @param {string} email - Client email
|
|
953
|
+
* @param {Object} trafficConfig - Traffic configuration (totalGB, expiryTime)
|
|
954
|
+
*/
|
|
955
|
+
updateClientTraffic(email, trafficConfig) {
|
|
956
|
+
return this._request('post', `/panel/api/inbounds/updateClientTraffic/${email}`, trafficConfig);
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
/**
|
|
960
|
+
* Delete client by email
|
|
961
|
+
* @param {number} inboundId - Inbound ID
|
|
962
|
+
* @param {string} email - Client email
|
|
963
|
+
*/
|
|
964
|
+
deleteClientByEmail(inboundId, email) {
|
|
965
|
+
return this._request('post', `/panel/api/inbounds/${inboundId}/delClientByEmail/${email}`);
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
getClientTrafficsByEmail(email) {
|
|
969
|
+
return this._request('get', `/panel/api/inbounds/getClientTraffics/${email}`);
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
getClientTrafficsById(id) {
|
|
973
|
+
return this._request('get', `/panel/api/inbounds/getClientTrafficsById/${id}`);
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
getClientIps(email) {
|
|
977
|
+
return this._request('post', `/panel/api/inbounds/clientIps/${email}`);
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
clearClientIps(email) {
|
|
981
|
+
return this._request('post', `/panel/api/inbounds/clearClientIps/${email}`);
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
// Traffic
|
|
985
|
+
resetClientTraffic(inboundId, email) {
|
|
986
|
+
return this._request('post', `/panel/api/inbounds/${inboundId}/resetClientTraffic/${email}`);
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
resetAllTraffics() {
|
|
990
|
+
return this._request('post', '/panel/api/inbounds/resetAllTraffics');
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
resetAllClientTraffics(inboundId) {
|
|
994
|
+
return this._request('post', `/panel/api/inbounds/resetAllClientTraffics/${inboundId}`);
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
deleteDepletedClients(inboundId) {
|
|
998
|
+
return this._request('post', `/panel/api/inbounds/delDepletedClients/${inboundId}`);
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
// System
|
|
1002
|
+
getOnlineClients() {
|
|
1003
|
+
return this._request('post', '/panel/api/inbounds/onlines');
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
createBackup() {
|
|
1007
|
+
return this._request('get', '/panel/api/inbounds/createbackup');
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
/**
|
|
1011
|
+
* Trigger sending a backup to Telegram bot admins
|
|
1012
|
+
*/
|
|
1013
|
+
backupToTgBot() {
|
|
1014
|
+
return this._request('post', '/panel/api/backuptotgbot');
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
// ===========================================
|
|
1018
|
+
// SERVER MANAGEMENT
|
|
1019
|
+
// ===========================================
|
|
1020
|
+
|
|
1021
|
+
/**
|
|
1022
|
+
* Get server status (CPU, RAM, etc.)
|
|
1023
|
+
*/
|
|
1024
|
+
getServerStatus() {
|
|
1025
|
+
return this._request('get', '/panel/api/server/status');
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
/**
|
|
1029
|
+
* Get CPU usage history
|
|
1030
|
+
* @param {string} bucket - Time bucket (e.g., 'min', 'hour')
|
|
1031
|
+
*/
|
|
1032
|
+
getCPUHistory(bucket = 'min') {
|
|
1033
|
+
return this._request('get', `/panel/api/server/cpuHistory/${bucket}`);
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
/**
|
|
1037
|
+
* Get current Xray version
|
|
1038
|
+
*/
|
|
1039
|
+
getXrayVersion() {
|
|
1040
|
+
return this._request('get', '/panel/api/server/getXrayVersion');
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
/**
|
|
1044
|
+
* Get Xray config as JSON
|
|
1045
|
+
*/
|
|
1046
|
+
getConfigJson() {
|
|
1047
|
+
return this._request('get', '/panel/api/server/getConfigJson');
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
/**
|
|
1051
|
+
* Download database
|
|
1052
|
+
*/
|
|
1053
|
+
getDb() {
|
|
1054
|
+
return this._request('get', '/panel/api/server/getDb');
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
/**
|
|
1058
|
+
* Stop Xray core service
|
|
1059
|
+
*/
|
|
1060
|
+
stopXrayService() {
|
|
1061
|
+
return this._request('post', '/panel/api/server/stopXrayService');
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
/**
|
|
1065
|
+
* Restart Xray core service
|
|
1066
|
+
*/
|
|
1067
|
+
restartXrayService() {
|
|
1068
|
+
return this._request('post', '/panel/api/server/restartXrayService');
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
/**
|
|
1072
|
+
* Install specific Xray version
|
|
1073
|
+
* @param {string} version - Version to install
|
|
1074
|
+
*/
|
|
1075
|
+
installXray(version) {
|
|
1076
|
+
return this._request('post', `/panel/api/server/installXray/${version}`);
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
/**
|
|
1080
|
+
* Get panel logs
|
|
1081
|
+
* @param {number} count - Number of logs to retrieve
|
|
1082
|
+
*/
|
|
1083
|
+
getPanelLogs(count = 100) {
|
|
1084
|
+
return this._request('post', `/panel/api/server/logs/${count}`);
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
/**
|
|
1088
|
+
* Get Xray logs
|
|
1089
|
+
* @param {number} count - Number of logs to retrieve
|
|
1090
|
+
*/
|
|
1091
|
+
getXrayLogs(count = 100) {
|
|
1092
|
+
return this._request('post', `/panel/api/server/xraylogs/${count}`);
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
/**
|
|
1096
|
+
* Update GeoIP/GeoSite files
|
|
1097
|
+
* @param {string} [fileName] - Specific file to update (optional)
|
|
1098
|
+
*/
|
|
1099
|
+
updateGeofile(fileName) {
|
|
1100
|
+
const url = fileName
|
|
1101
|
+
? `/panel/api/server/updateGeofile/${fileName}`
|
|
1102
|
+
: '/panel/api/server/updateGeofile';
|
|
1103
|
+
return this._request('post', url);
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
/**
|
|
1107
|
+
* Import database
|
|
1108
|
+
* @param {FormData} formData - FormData containing the database file
|
|
1109
|
+
*/
|
|
1110
|
+
async importDB(formData) {
|
|
1111
|
+
// Ensure authenticated before direct API call
|
|
1112
|
+
if (!this.cookie) {
|
|
1113
|
+
await this._ensureAuthenticated();
|
|
1114
|
+
}
|
|
1115
|
+
// Use direct api call to handle multipart/form-data correctly
|
|
1116
|
+
return this.api.post('/panel/api/server/importDB', formData);
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
// ===========================================
|
|
1120
|
+
// SERVER-SIDE GENERATORS
|
|
1121
|
+
// ===========================================
|
|
1122
|
+
|
|
1123
|
+
getNewUUID() {
|
|
1124
|
+
return this._request('get', '/panel/api/server/getNewUUID');
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
getNewX25519Cert() {
|
|
1128
|
+
return this._request('get', '/panel/api/server/getNewX25519Cert');
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
getNewmldsa65() {
|
|
1132
|
+
return this._request('get', '/panel/api/server/getNewmldsa65');
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
getNewmlkem768() {
|
|
1136
|
+
return this._request('get', '/panel/api/server/getNewmlkem768');
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
getNewVlessEnc() {
|
|
1140
|
+
return this._request('get', '/panel/api/server/getNewVlessEnc');
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
getNewEchCert() {
|
|
1144
|
+
return this._request('post', '/panel/api/server/getNewEchCert');
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
// ===========================================
|
|
1148
|
+
// PANEL SETTINGS
|
|
1149
|
+
// ===========================================
|
|
1150
|
+
|
|
1151
|
+
/**
|
|
1152
|
+
* Get all panel settings
|
|
1153
|
+
*/
|
|
1154
|
+
getAllSettings() {
|
|
1155
|
+
return this._request('post', '/panel/setting/all');
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
/**
|
|
1159
|
+
* Update panel settings
|
|
1160
|
+
* @param {Object} settings - Settings to update
|
|
1161
|
+
*/
|
|
1162
|
+
updateSetting(settings) {
|
|
1163
|
+
return this._request('post', '/panel/setting/update', settings);
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
/**
|
|
1167
|
+
* Update admin username and password
|
|
1168
|
+
* @param {string} oldUsername - Current username
|
|
1169
|
+
* @param {string} oldPassword - Current password
|
|
1170
|
+
* @param {string} newUsername - New username
|
|
1171
|
+
* @param {string} newPassword - New password
|
|
1172
|
+
*/
|
|
1173
|
+
updateUser(oldUsername, oldPassword, newUsername, newPassword) {
|
|
1174
|
+
return this._request('post', '/panel/setting/updateUser', {
|
|
1175
|
+
oldUsername,
|
|
1176
|
+
oldPassword,
|
|
1177
|
+
newUsername,
|
|
1178
|
+
newPassword
|
|
1179
|
+
});
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1182
|
+
/**
|
|
1183
|
+
* Restart the panel
|
|
1184
|
+
*/
|
|
1185
|
+
restartPanel() {
|
|
1186
|
+
return this._request('post', '/panel/setting/restartPanel');
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
/**
|
|
1190
|
+
* Get default settings
|
|
1191
|
+
*/
|
|
1192
|
+
getDefaultSettings() {
|
|
1193
|
+
return this._request('post', '/panel/setting/defaultSettings');
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
/**
|
|
1197
|
+
* Get default Xray JSON config
|
|
1198
|
+
*/
|
|
1199
|
+
getDefaultJsonConfig() {
|
|
1200
|
+
return this._request('get', '/panel/setting/getDefaultJsonConfig');
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
// ===========================================
|
|
1204
|
+
// XRAY CONFIGURATION
|
|
1205
|
+
// ===========================================
|
|
1206
|
+
|
|
1207
|
+
/**
|
|
1208
|
+
* Get Xray configuration
|
|
1209
|
+
*/
|
|
1210
|
+
getXrayConfig() {
|
|
1211
|
+
return this._request('post', '/panel/xray/');
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
/**
|
|
1215
|
+
* Update Xray configuration
|
|
1216
|
+
* @param {string} config - Xray configuration content
|
|
1217
|
+
*/
|
|
1218
|
+
updateXrayConfig(config) {
|
|
1219
|
+
return this._request('post', '/panel/xray/update', { content: config });
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
/**
|
|
1223
|
+
* Manage WARP
|
|
1224
|
+
* @param {string} action - Action to perform (data, del, config, reg, license)
|
|
1225
|
+
* @param {Object} [data] - Additional data for the action
|
|
1226
|
+
*/
|
|
1227
|
+
manageWarp(action, data = {}) {
|
|
1228
|
+
return this._request('post', `/panel/xray/warp/${action}`, data);
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
/**
|
|
1232
|
+
* Get outbound traffic statistics
|
|
1233
|
+
*/
|
|
1234
|
+
getOutboundsTraffic() {
|
|
1235
|
+
return this._request('get', '/panel/xray/getOutboundsTraffic');
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
/**
|
|
1239
|
+
* Reset outbound traffic statistics
|
|
1240
|
+
*/
|
|
1241
|
+
resetOutboundsTraffic() {
|
|
1242
|
+
return this._request('post', '/panel/xray/resetOutboundsTraffic');
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
/**
|
|
1246
|
+
* Get Xray execution result
|
|
1247
|
+
*/
|
|
1248
|
+
getXrayResult() {
|
|
1249
|
+
return this._request('get', '/panel/xray/getXrayResult');
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
// Security Methods
|
|
1253
|
+
|
|
1254
|
+
/**
|
|
1255
|
+
* Get security statistics and monitoring data
|
|
1256
|
+
* @returns {Object} Security statistics
|
|
1257
|
+
*/
|
|
1258
|
+
getSecurityStats() {
|
|
1259
|
+
return this.securityMonitor.getStats();
|
|
1260
|
+
}
|
|
1261
|
+
|
|
1262
|
+
/**
|
|
1263
|
+
* Clear blocked IPs (admin function)
|
|
1264
|
+
*/
|
|
1265
|
+
clearBlockedIPs() {
|
|
1266
|
+
this.securityMonitor.clearBlockedIPs();
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
/**
|
|
1270
|
+
* Validate credential strength
|
|
1271
|
+
* @param {string} credential - Credential to validate
|
|
1272
|
+
* @param {string} type - Type of credential
|
|
1273
|
+
* @returns {Object} Validation result
|
|
1274
|
+
*/
|
|
1275
|
+
validateCredentialStrength(credential, type) {
|
|
1276
|
+
return CredentialSecurity.validateCredentialStrength(credential, type);
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
/**
|
|
1280
|
+
* Generate secure session token
|
|
1281
|
+
* @returns {string} Secure session token
|
|
1282
|
+
*/
|
|
1283
|
+
generateSecureToken() {
|
|
1284
|
+
return CredentialSecurity.generateSessionToken();
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1287
|
+
/**
|
|
1288
|
+
* Enable development mode for detailed error messages
|
|
1289
|
+
* @param {boolean} enabled - Whether to enable development mode
|
|
1290
|
+
*/
|
|
1291
|
+
setDevelopmentMode(enabled) {
|
|
1292
|
+
this.isDevelopment = enabled;
|
|
1293
|
+
}
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
// Export static methods for standalone use
|
|
1297
|
+
ThreeXUI.CredentialGenerator = CredentialGenerator;
|
|
1298
|
+
ThreeXUI.SessionManager = SessionManager;
|
|
1299
|
+
ThreeXUI.createSessionManager = createSessionManager;
|
|
1300
|
+
|
|
1301
|
+
module.exports = ThreeXUI;
|
|
1302
|
+
|
|
1303
|
+
// Define lazy getters to avoid circular dependencies
|
|
1304
|
+
Object.defineProperties(module.exports, {
|
|
1305
|
+
// Web middleware helpers
|
|
1306
|
+
createExpressMiddleware: {
|
|
1307
|
+
enumerable: true,
|
|
1308
|
+
get: () => require('./src/middleware/WebMiddleware').createExpressMiddleware
|
|
1309
|
+
},
|
|
1310
|
+
withThreeXUI: {
|
|
1311
|
+
enumerable: true,
|
|
1312
|
+
get: () => require('./src/middleware/WebMiddleware').withThreeXUI
|
|
1313
|
+
},
|
|
1314
|
+
createReactHook: {
|
|
1315
|
+
enumerable: true,
|
|
1316
|
+
get: () => require('./src/middleware/WebMiddleware').createReactHook
|
|
1317
|
+
},
|
|
1318
|
+
createNextjsRoutes: {
|
|
1319
|
+
enumerable: true,
|
|
1320
|
+
get: () => require('./src/middleware/WebMiddleware').createNextjsRoutes
|
|
1321
|
+
},
|
|
1322
|
+
SessionConfig: {
|
|
1323
|
+
enumerable: true,
|
|
1324
|
+
get: () => require('./src/middleware/WebMiddleware').SessionConfig
|
|
1325
|
+
},
|
|
1326
|
+
// Protocol builders
|
|
1327
|
+
ProtocolBuilder: {
|
|
1328
|
+
enumerable: true,
|
|
1329
|
+
get: () => require('./src/builders/ProtocolBuilders').ProtocolBuilder
|
|
1330
|
+
},
|
|
1331
|
+
VLESSBuilder: {
|
|
1332
|
+
enumerable: true,
|
|
1333
|
+
get: () => require('./src/builders/ProtocolBuilders').VLESSBuilder
|
|
1334
|
+
},
|
|
1335
|
+
VMESSBuilder: {
|
|
1336
|
+
enumerable: true,
|
|
1337
|
+
get: () => require('./src/builders/ProtocolBuilders').VMESSBuilder
|
|
1338
|
+
},
|
|
1339
|
+
TrojanBuilder: {
|
|
1340
|
+
enumerable: true,
|
|
1341
|
+
get: () => require('./src/builders/ProtocolBuilders').TrojanBuilder
|
|
1342
|
+
},
|
|
1343
|
+
ShadowsocksBuilder: {
|
|
1344
|
+
enumerable: true,
|
|
1345
|
+
get: () => require('./src/builders/ProtocolBuilders').ShadowsocksBuilder
|
|
1346
|
+
},
|
|
1347
|
+
WireGuardBuilder: {
|
|
1348
|
+
enumerable: true,
|
|
1349
|
+
get: () => require('./src/builders/ProtocolBuilders').WireGuardBuilder
|
|
1350
|
+
},
|
|
1351
|
+
BaseBuilder: {
|
|
1352
|
+
enumerable: true,
|
|
1353
|
+
get: () => require('./src/builders/ProtocolBuilders').BaseBuilder
|
|
1354
|
+
},
|
|
1355
|
+
// Security helpers
|
|
1356
|
+
SecurityEnhancer: {
|
|
1357
|
+
enumerable: true,
|
|
1358
|
+
get: () => require('./src/security/SecurityEnhancer')
|
|
1359
|
+
}
|
|
715
1360
|
});
|