3xui-api-client 2.1.1 → 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 CHANGED
@@ -5,6 +5,36 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [3.0.1] - 2026-06-07
9
+
10
+ ### Fixed
11
+ - Re-release as 3.0.1 — version 3.0.0 was previously published and unpublished, making it permanently blocked on npm.
12
+
13
+ ## [3.0.0] - 2026-06-07
14
+
15
+ ### Added
16
+ - 🔑 **API Token Authentication** — Pass `token` or `apiToken` in options to authenticate via Bearer token instead of cookie-based login. Resolves compatibility with 3x-ui v3.0.2+ where the legacy login mechanism changed. ([#1](https://github.com/iamhelitha/3xui-api-client/issues/1))
17
+ - 🆕 **Object-style constructor** — `new ThreeXUI(url, { username, password, token })` as an alternative to the positional `(url, user, pass)` signature.
18
+ - 📡 **48 new Modern API routes** (3x-ui v2.x/v3.x endpoints):
19
+ - **Clients** (25 routes): `getClients`, `getPagedClients`, `getClient`, `getClientTraffic`, `getSubLinks`, `getClientLinks`, `addModernClient`, `updateModernClient`, `deleteModernClient`, `attachClientToInbounds`, `detachClientFromInbounds`, `resetAllModernClientTraffics`, `deleteDepletedModernClients`, bulk operations (`bulkAdjust`, `bulkDel`, `bulkCreate`, `bulkAttach`, `bulkDetach`, `bulkResetTraffic`), `resetModernClientTrafficByEmail`, `updateModernClientTrafficByEmail`, `getModernClientIps`, `clearModernClientIps`, `getOnlines`, `getModernLastOnline`
20
+ - **Client Groups** (7 routes): `getGroups`, `getGroupEmails`, `createGroup`, `renameGroup`, `deleteGroup`, `bulkAddGroups`, `bulkRemoveGroups`
21
+ - **Nodes** (9 routes): `getNodes`, `getNode`, `getNodeHistory`, `addNode`, `updateNode`, `deleteNode`, `setNodeEnable`, `testNode`, `probeNode`
22
+ - **Custom Geo** (7 routes): `getCustomGeos`, `getGeoAliases`, `addCustomGeo`, `updateCustomGeo`, `deleteCustomGeo`, `downloadCustomGeo`, `updateAllCustomGeo`
23
+
24
+ ### Changed
25
+ - Constructor signature extended to accept an options object as the second argument (fully backward compatible — existing `(url, user, pass)` usage is unchanged).
26
+ - `_request()` and `login()` skip cookie-based auth flow when a token is configured.
27
+ - `isSessionValid()` returns `true` immediately when token auth is active.
28
+
29
+ ### Fixed
30
+ - ESLint errors (trailing whitespace, missing curly braces) introduced in new route additions.
31
+ - Indentation inconsistency in TypeScript declarations (`updateClientWithCredentials`).
32
+
33
+ ### Backward Compatibility
34
+ - All existing `(url, username, password)` constructor calls continue to work without any changes.
35
+ - All 55 original API routes are unchanged.
36
+ - Verified against older 3x-ui servers — cookie-based login, session management, and all core routes function identically.
37
+
8
38
  ## [2.1.1] - 2025-11-30
9
39
 
10
40
  ### Fixed
package/index.d.ts CHANGED
@@ -16,6 +16,8 @@ declare module '3xui-api-client' {
16
16
  isDevelopment?: boolean;
17
17
  enableCSP?: boolean;
18
18
  userAgent?: string;
19
+ token?: string;
20
+ apiToken?: string;
19
21
  }
20
22
 
21
23
  export interface LoginResponse {
@@ -226,6 +228,7 @@ declare module '3xui-api-client' {
226
228
 
227
229
  export default class ThreeXUI {
228
230
  constructor(baseURL: string, username: string, password: string, options?: ThreeXUIOptions);
231
+ constructor(baseURL: string, options: ThreeXUIOptions);
229
232
 
230
233
  // Authentication
231
234
  login(forceRefresh?: boolean): Promise<LoginResponse>;
@@ -252,14 +255,82 @@ declare module '3xui-api-client' {
252
255
 
253
256
  // Enhanced Client Management
254
257
  addClientWithCredentials(inboundId: number, protocol: string, options?: CredentialOptions): Promise<any>;
255
- updateClientWithCredentials(clientId: string, inboundId: number, options?: CredentialOptions): Promise<any>;
258
+ updateClientWithCredentials(clientId: string, inboundId: number, options?: CredentialOptions): Promise<any>;
256
259
 
257
260
  // Session Management
258
261
  getSessionStats(): Promise<any>;
259
262
  clearAllSessions(): Promise<void>;
260
263
  isSessionValid(): Promise<boolean>;
261
264
 
265
+ // ===========================================
266
+ // Modern API Methods (3X-UI >= 2.x)
267
+ // ===========================================
268
+
269
+ // --- Clients ---
270
+ getClients(): Promise<any>;
271
+ getPagedClients(params?: {
272
+ page?: number;
273
+ size?: number;
274
+ sort?: string;
275
+ order?: 'asc' | 'desc';
276
+ email?: string;
277
+ }): Promise<any>;
278
+ getClient(email: string): Promise<any>;
279
+ getClientTraffic(email: string): Promise<any>;
280
+ getSubLinks(subId: string): Promise<any>;
281
+ getClientLinks(email: string): Promise<any>;
282
+ addModernClient(data: any): Promise<any>;
283
+ updateModernClient(email: string, data: any): Promise<any>;
284
+ deleteModernClient(email: string): Promise<any>;
285
+ attachClientToInbounds(email: string, data: any): Promise<any>;
286
+ detachClientFromInbounds(email: string, data: any): Promise<any>;
287
+ resetAllModernClientTraffics(): Promise<any>;
288
+ deleteDepletedModernClients(): Promise<any>;
289
+ bulkAdjustModernClients(data: any): Promise<any>;
290
+ bulkDeleteModernClients(data: any): Promise<any>;
291
+ bulkCreateModernClients(data: any): Promise<any>;
292
+ bulkAttachModernClients(data: any): Promise<any>;
293
+ bulkDetachModernClients(data: any): Promise<any>;
294
+ bulkResetTrafficModernClients(data: any): Promise<any>;
295
+ resetModernClientTrafficByEmail(email: string): Promise<any>;
296
+ updateModernClientTrafficByEmail(email: string, data: any): Promise<any>;
297
+ getModernClientIps(email: string): Promise<any>;
298
+ clearModernClientIps(email: string): Promise<any>;
299
+ getOnlines(): Promise<any>;
300
+ getModernLastOnline(): Promise<any>;
301
+
302
+ // --- Client Groups ---
303
+ getGroups(): Promise<any>;
304
+ getGroupEmails(groupName: string): Promise<any>;
305
+ createGroup(data: any): Promise<any>;
306
+ renameGroup(data: any): Promise<any>;
307
+ deleteGroup(data: any): Promise<any>;
308
+ bulkAddGroups(data: any): Promise<any>;
309
+ bulkRemoveGroups(data: any): Promise<any>;
310
+
311
+ // --- Nodes ---
312
+ getNodes(): Promise<any>;
313
+ getNode(id: number | string): Promise<any>;
314
+ getNodeHistory(id: number | string, metric: string, bucket: string): Promise<any>;
315
+ addNode(data: any): Promise<any>;
316
+ updateNode(id: number | string, data: any): Promise<any>;
317
+ deleteNode(id: number | string): Promise<any>;
318
+ setNodeEnable(id: number | string): Promise<any>;
319
+ testNode(data: any): Promise<any>;
320
+ probeNode(id: number | string): Promise<any>;
321
+
322
+ // --- Custom Geo ---
323
+ getCustomGeos(): Promise<any>;
324
+ getGeoAliases(): Promise<any>;
325
+ addCustomGeo(data: any): Promise<any>;
326
+ updateCustomGeo(id: string | number, data: any): Promise<any>;
327
+ deleteCustomGeo(id: string | number): Promise<any>;
328
+ downloadCustomGeo(id: string | number): Promise<any>;
329
+ updateAllCustomGeo(): Promise<any>;
330
+
331
+ // ===========================================
262
332
  // Original API Methods
333
+ // ===========================================
263
334
  getInbounds(): Promise<any>;
264
335
  getInbound(id: number): Promise<any>;
265
336
  addInbound(inboundConfig: InboundConfig): Promise<any>;
package/index.js CHANGED
@@ -32,16 +32,10 @@ class ThreeXUI {
32
32
  * @param {number} options.timeout - Request timeout in milliseconds (default: 30000)
33
33
  * @throws {Error} If baseURL, username, or password is missing
34
34
  */
35
- constructor(baseURL, username, password, options = {}) {
35
+ constructor(baseURL, usernameOrOptions, password, options = {}) {
36
36
  if (!baseURL) {
37
37
  throw new Error('baseURL is required');
38
38
  }
39
- if (!username) {
40
- throw new Error('username is required');
41
- }
42
- if (!password) {
43
- throw new Error('password is required');
44
- }
45
39
 
46
40
  // Apply security validations
47
41
  this.baseURL = InputValidator.validateURL(baseURL);
@@ -53,8 +47,32 @@ class ThreeXUI {
53
47
  this.baseURL = this.baseURL.replace(/\/panel\/?$/, '');
54
48
  }
55
49
 
56
- this.username = InputValidator.validateUsername(username);
57
- this.password = InputValidator.validatePassword(password);
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
+
58
76
  this.cookie = null;
59
77
  this.options = options;
60
78
  this.loginMutex = false; // Add mutex to prevent concurrent logins
@@ -81,15 +99,21 @@ class ThreeXUI {
81
99
  }
82
100
 
83
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
+
84
111
  this.api = axios.create({
85
112
  baseURL: this.baseURL,
86
113
  timeout: options.timeout || 30000, // 30 second timeout
87
114
  maxRedirects: 5,
88
115
  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
- })
116
+ headers: secureHeaders
93
117
  });
94
118
 
95
119
  // Add request interceptor for security headers
@@ -120,6 +144,14 @@ class ThreeXUI {
120
144
  * @returns {Object} Login response
121
145
  */
122
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
+
123
155
  // Check rate limiting first
124
156
  const identifier = CredentialSecurity.hashForLogging(this.username);
125
157
  if (!this.securityMonitor.checkRateLimit(identifier, 'login')) {
@@ -238,11 +270,13 @@ class ThreeXUI {
238
270
  }
239
271
 
240
272
  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();
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
+ }
246
280
  }
247
281
 
248
282
  try {
@@ -257,6 +291,9 @@ class ThreeXUI {
257
291
  return response.data;
258
292
  } catch (error) {
259
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
+ }
260
297
  // Cookie might have expired, try to login again with retry limit
261
298
  if (this.loginRetryCount < this.maxLoginRetries) {
262
299
  await this._ensureAuthenticated(true); // Force refresh
@@ -533,12 +570,322 @@ class ThreeXUI {
533
570
  * @returns {boolean} Session validity
534
571
  */
535
572
  async isSessionValid() {
573
+ if (this.token) {
574
+ return true;
575
+ }
536
576
  if (this.sessionManager) {
537
577
  return await this.sessionManager.hasValidSession(this.baseURL, this.username);
538
578
  }
539
579
  return !!this.cookie;
540
580
  }
541
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
+
542
889
  // ===========================================
543
890
  // ORIGINAL API METHODS (UNCHANGED)
544
891
  // ===========================================
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "3xui-api-client",
3
- "version": "2.1.1",
3
+ "version": "3.0.1",
4
4
  "description": "A Node.js client library for 3x-ui panel API with built-in credential generation, session management, and web integration support",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",