x3ui-api 1.0.3 → 1.0.4

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/README.md CHANGED
@@ -13,7 +13,9 @@ npm install x3ui-api
13
13
  - Authentication with x3ui panel
14
14
  - Get system statistics (CPU, memory, network, etc.)
15
15
  - Manage inbounds (list, add, update, delete)
16
- - Convenient Reality inbound builder
16
+ - Protocol-specific builders with fluent API:
17
+ - VLESS with Reality support
18
+ - VMess with HTTP Upgrade support
17
19
  - Client management with fluent API
18
20
  - TypeScript support
19
21
 
@@ -59,15 +61,72 @@ async function main() {
59
61
  main();
60
62
  ```
61
63
 
62
- ### Creating a Reality Inbound
64
+ ### Creating a VMess Inbound
65
+
66
+ The library provides a convenient builder pattern for creating VMess inbounds with HTTP Upgrade:
67
+
68
+ ```javascript
69
+ const X3UIClient = require('x3ui-api');
70
+ const vmess = require('x3ui-api/src/protocols/vmess');
71
+
72
+ const client = new X3UIClient({
73
+ baseURL: 'http://your-x3ui-panel.com:54321'
74
+ });
75
+
76
+ async function createVmessInbound() {
77
+ try {
78
+ await client.login('admin', 'password');
79
+
80
+ // Create a VMess inbound builder
81
+ let builder = new vmess.VmessBuilder(client)
82
+ .setRemark('My VMess Inbound')
83
+ .setPort(8443) // Optional, will auto-generate if not provided
84
+ .setHttpUpgradeHost('your-domain.com');
85
+
86
+ // Add a client
87
+ builder.addClient()
88
+ .setEmail('user@example.com')
89
+ .setTotalGB(100) // 100 GB traffic limit
90
+ .setExpiryTime(Date.now() + 30 * 24 * 60 * 60 * 1000); // 30 days
91
+
92
+ // Build and add the inbound
93
+ const inbound = await builder.build();
94
+ const createdInbound = await client.addInbound(inbound);
95
+
96
+ // Update the builder with the server-returned inbound to get all metrics and IDs
97
+ builder = new vmess.VmessBuilder(client, createdInbound);
98
+
99
+ console.log('Inbound created:', createdInbound);
100
+
101
+ // Get connection link for the client (now with accurate server-assigned values)
102
+ const link = builder.getClientLink(0, 'your-server-ip.com');
103
+ console.log('Client connection link:', link);
104
+ } catch (error) {
105
+ console.error('Error creating inbound:', error.message);
106
+ }
107
+ }
108
+
109
+ createVmessInbound();
110
+ ```
111
+
112
+ ### Creating a VLESS Reality Inbound
63
113
 
64
114
  The library provides a convenient builder pattern for creating Reality inbounds:
65
115
 
66
116
  ```javascript
117
+ const X3UIClient = require('x3ui-api');
118
+ const vless = require('x3ui-api/src/protocols/vless');
119
+
120
+ const client = new X3UIClient({
121
+ baseURL: 'http://your-x3ui-panel.com:54321'
122
+ });
123
+
67
124
  async function createRealityInbound() {
68
125
  try {
126
+ await client.login('admin', 'password');
127
+
69
128
  // Create a Reality inbound builder
70
- const builder = client.createRealityBuilder({
129
+ let builder = new vless.RealityBuilder(client, {
71
130
  remark: 'My Reality Inbound',
72
131
  port: 8443 // Optional, will auto-generate if not provided
73
132
  });
@@ -79,18 +138,21 @@ async function createRealityInbound() {
79
138
  .setFingerprint('chrome');
80
139
 
81
140
  // Add a client
82
- const clientBuilder = builder.addClient()
141
+ builder.addClient()
83
142
  .setEmail('user@example.com')
84
143
  .setTotalGB(100) // 100 GB traffic limit
85
144
  .setExpiryTime(Date.now() + 30 * 24 * 60 * 60 * 1000); // 30 days
86
145
 
87
146
  // Build and add the inbound
88
147
  const inbound = await builder.build();
89
- const result = await client.addInbound(inbound);
148
+ const createdInbound = await client.addInbound(inbound);
90
149
 
91
- console.log('Inbound created:', result);
150
+ // Update the builder with the server-returned inbound to get all metrics and IDs
151
+ builder = new vless.RealityBuilder(client, createdInbound);
92
152
 
93
- // Get connection link for the client
153
+ console.log('Inbound created:', createdInbound);
154
+
155
+ // Get connection link for the client (now with accurate server-assigned values)
94
156
  const link = builder.getClientLink(0, 'your-server-ip.com');
95
157
  console.log('Client connection link:', link);
96
158
  } catch (error) {
@@ -99,24 +161,84 @@ async function createRealityInbound() {
99
161
  }
100
162
  ```
101
163
 
164
+ ### Real-World Example
165
+
166
+ Here's a complete example showing how to create a VMess inbound and get the client link:
167
+
168
+ ```javascript
169
+ const X3UIClient = require('x3ui-api');
170
+ const vmess = require('x3ui-api/src/protocols/vmess');
171
+
172
+ const client = new X3UIClient({
173
+ baseURL: 'https://your-panel-url.com/path/',
174
+ });
175
+
176
+ (async () => {
177
+ await client.login('username', 'password');
178
+
179
+ // Create VMess builder with HTTP Upgrade
180
+ let vmessBuilder = new vmess.VmessBuilder(client)
181
+ .setRemark("My VMess Inbound")
182
+ .setHttpUpgradeHost("your-domain.com");
183
+
184
+ // Add the inbound to the server
185
+ const vmessInbound = await client.addInbound(await vmessBuilder.build());
186
+
187
+ // Create a new builder with the server-returned inbound to get accurate client links
188
+ vmessBuilder = new vmess.VmessBuilder(client, vmessInbound);
189
+
190
+ // Generate and output the client connection link
191
+ console.log(vmessBuilder.getClientLink(0, "your-domain.com"));
192
+ })();
193
+ ```
194
+
102
195
  ### Managing Existing Inbounds
103
196
 
104
197
  ```javascript
105
198
  async function manageInbounds() {
106
- // Get all inbounds
107
- const inbounds = await client.getInbounds();
108
-
109
- if (inbounds.length > 0) {
110
- const inboundId = inbounds[0].id;
111
-
112
- // Update an inbound
113
- await client.updateInbound(inboundId, {
114
- remark: 'Updated Inbound Name',
115
- enable: true
116
- });
199
+ try {
200
+ // Get all inbounds
201
+ const inbounds = await client.getInbounds();
117
202
 
118
- // Delete an inbound
119
- await client.deleteInbound(inboundId);
203
+ if (inbounds.length > 0) {
204
+ // Get the first inbound
205
+ const inbound = inbounds[0];
206
+ const inboundId = inbound.id;
207
+
208
+ // IMPORTANT: When updating an inbound, always start with the complete inbound object
209
+ // to avoid losing data, then modify only what you need to change
210
+
211
+ // Example 1: Update the remark and enable status
212
+ inbound.remark = 'Updated Inbound Name';
213
+ inbound.enable = true;
214
+
215
+ // Send the complete updated inbound back to the server
216
+ await client.updateInbound(inboundId, inbound);
217
+
218
+ // Example 2: Add a new client to an existing inbound
219
+ if (inbound.protocol === 'vmess') {
220
+ // Create a builder with the existing inbound
221
+ const builder = new vmess.VmessBuilder(client, inbound);
222
+
223
+ // Add a new client
224
+ builder.addClient()
225
+ .setEmail('newuser@example.com')
226
+ .setTotalGB(50);
227
+
228
+ // Build the updated inbound and send it to the server
229
+ const updatedInbound = await builder.build();
230
+ await client.updateInbound(inboundId, updatedInbound);
231
+
232
+ // Get the link for the new client
233
+ const link = builder.getClientLink(builder.clients.length - 1, 'your-server-ip.com');
234
+ console.log('New client link:', link);
235
+ }
236
+
237
+ // Example 3: Delete an inbound
238
+ await client.deleteInbound(inboundId);
239
+ }
240
+ } catch (error) {
241
+ console.error('Error managing inbounds:', error.message);
120
242
  }
121
243
  }
122
244
  ```
@@ -140,18 +262,44 @@ new X3UIClient({
140
262
  - `login(username: string, password: string)` - Authenticate with the panel
141
263
  - `getSystemStats()` - Get system statistics
142
264
  - `getInbounds()` - Get all configured inbounds
143
- - `addInbound(config)` - Add a new inbound
144
- - `updateInbound(id, config)` - Update an existing inbound
265
+ - `addInbound(config)` - Add a new inbound and returns the created inbound with server-assigned values
266
+ - `updateInbound(id, config)` - Update an existing inbound with the complete inbound configuration
145
267
  - `deleteInbound(id)` - Delete an inbound
146
268
  - `getNewX25519Cert()` - Generate a new X25519 certificate
147
- - `createRealityBuilder(options)` - Create a Reality inbound builder
148
269
 
149
- ### RealityBuilder
270
+ ### Protocol Builders
150
271
 
151
- Builder class for creating Reality inbounds with a fluent API.
272
+ The library provides protocol-specific builders for creating different types of inbounds:
152
273
 
153
- #### Methods
274
+ #### VMess Builder
275
+
276
+ ```javascript
277
+ const vmess = require('x3ui-api/src/protocols/vmess');
278
+ const builder = new vmess.VmessBuilder(client, options);
279
+ ```
280
+
281
+ Methods:
282
+ - `setPort(port)` - Set the port for the inbound
283
+ - `setRemark(remark)` - Set the name/remark for the inbound
284
+ - `setNetwork(network)` - Set the network type (default: 'httpupgrade')
285
+ - `setSecurity(security)` - Set the security type (default: 'none')
286
+ - `setHttpUpgradePath(path)` - Set the HTTP upgrade path
287
+ - `setHttpUpgradeHost(host)` - Set the HTTP upgrade host
288
+ - `setSniffing(enabled, destOverride, metadataOnly, routeOnly)` - Configure sniffing
289
+ - `setListenIP(ip)` - Set listen IP address
290
+ - `setExpiryTime(timestamp)` - Set inbound expiry time
291
+ - `addClient(options)` - Add a new client to the inbound
292
+ - `getClientLink(clientIndex, host, port)` - Get connection link for a client
293
+ - `build()` - Build the final inbound config
294
+
295
+ #### VLESS Reality Builder
296
+
297
+ ```javascript
298
+ const vless = require('x3ui-api/src/protocols/vless');
299
+ const builder = new vless.RealityBuilder(client, options);
300
+ ```
154
301
 
302
+ Methods:
155
303
  - `setPort(port)` - Set the port for the inbound
156
304
  - `setRemark(remark)` - Set the name/remark for the inbound
157
305
  - `setDest(dest)` - Set the destination address (e.g., "yahoo.com:443")
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "x3ui-api",
3
- "version": "1.0.3",
3
+ "version": "1.0.4",
4
4
  "description": "API client for x3ui panel",
5
5
  "main": "src/index.js",
6
6
  "types": "src/index.d.ts",
@@ -0,0 +1,37 @@
1
+ import {ClientSettings, InboundConfig} from "../index";
2
+ import InboundBuilder from "./IndoundBuilder";
3
+
4
+ export default class ClientBuilder {
5
+
6
+ constructor(parent: InboundBuilder | InboundConfig);
7
+
8
+ /**
9
+ * Set client UUID. If not provided, will be auto-generated
10
+ */
11
+ setId(id: string): this;
12
+
13
+ /**
14
+ * Set client email
15
+ */
16
+ setEmail(email: string): this;
17
+
18
+ /**
19
+ * Set total traffic limit in GB
20
+ */
21
+ setTotalGB(gb: number): this;
22
+
23
+ /**
24
+ * Set expiry time in unix timestamp
25
+ */
26
+ setExpiryTime(timestamp: number): this;
27
+
28
+ /**
29
+ * Set Telegram ID
30
+ */
31
+ setTgId(id: string): this;
32
+
33
+ /**
34
+ * Build the client configuration
35
+ */
36
+ build(): ClientSettings;
37
+ }
@@ -0,0 +1,57 @@
1
+ const crypto = require("crypto");
2
+
3
+ module.exports = class ClientBuilder {
4
+ constructor(parent) {
5
+ this.parent = parent;
6
+ this.id = '';
7
+ this.flow = '';
8
+ this.email = '';
9
+ this.limitIp = 0;
10
+ this.totalGB = 0;
11
+ this.expiryTime = 0;
12
+ this.enable = true;
13
+ this.tgId = '';
14
+ this.subId = '';
15
+ this.reset = 0;
16
+ }
17
+
18
+ setId(id) {
19
+ this.id = id;
20
+ return this;
21
+ }
22
+
23
+ setEmail(email) {
24
+ this.email = email;
25
+ return this;
26
+ }
27
+
28
+ setTotalGB(gb) {
29
+ this.totalGB = gb;
30
+ return this;
31
+ }
32
+
33
+ setExpiryTime(timestamp) {
34
+ this.expiryTime = timestamp;
35
+ return this;
36
+ }
37
+
38
+ setTgId(id) {
39
+ this.tgId = id;
40
+ return this;
41
+ }
42
+
43
+ build() {
44
+ return {
45
+ id: this.id || crypto.randomUUID(),
46
+ flow: this.flow,
47
+ email: this.email || Math.random().toString(36).substring(2, 10),
48
+ limitIp: this.limitIp,
49
+ totalGB: this.totalGB,
50
+ expiryTime: this.expiryTime,
51
+ enable: this.enable,
52
+ tgId: this.tgId,
53
+ subId: this.subId || Math.random().toString(36).substring(2, 18),
54
+ reset: this.reset
55
+ };
56
+ }
57
+ }
@@ -0,0 +1 @@
1
+ export default class InboundBuilder {}
@@ -0,0 +1,31 @@
1
+ import {AxiosInstance} from "axios";
2
+ import {
3
+ InboundConfig,
4
+ LoginResponse,
5
+ SystemStats,
6
+ X3UIConfig
7
+ } from "../index";
8
+
9
+ export default class X3UIClient {
10
+ private client: AxiosInstance;
11
+
12
+ constructor(config: X3UIConfig);
13
+
14
+ login(username: string, password: string): Promise<LoginResponse>;
15
+
16
+ getSystemStats(): Promise<SystemStats>;
17
+
18
+ getInbounds(): Promise<InboundConfig[]>;
19
+
20
+ addInbound(config: Omit<InboundConfig, 'id'>): Promise<InboundConfig>;
21
+
22
+ updateInbound(id: number, config: Partial<InboundConfig>): Promise<void>;
23
+
24
+ deleteInbound(id: number): Promise<void>;
25
+
26
+ getInboundTraffic(id: number): Promise<{ up: number; down: number }>;
27
+
28
+ resetInboundTraffic(id: number): Promise<void>;
29
+
30
+ getNewX25519Cert(): Promise<{ privateKey: string; publicKey: string }>;
31
+ }
@@ -0,0 +1,147 @@
1
+ const axios = require("axios");
2
+
3
+ module.exports = class X3UIClient {
4
+ constructor(config) {
5
+ this.parseJSONSettings = config.parseJSONSettings || true;
6
+ this.client = axios.create({
7
+ baseURL: config.baseURL,
8
+ headers: config.token ? {
9
+ 'Cookie': `lang=en-US; 3x-ui=${config.token}`,
10
+ } : {
11
+ 'Cookie': 'lang=en-US'
12
+ }
13
+ });
14
+ }
15
+
16
+ /**
17
+ * Login to the x3ui panel
18
+ * @param {string} username Panel username
19
+ * @param {string} password Panel password
20
+ * @returns {Promise<{success: boolean, msg: string, obj: any}>} Login response with success status and token
21
+ */
22
+ async login(username, password) {
23
+ const formData = new URLSearchParams();
24
+ formData.append('username', username);
25
+ formData.append('password', password);
26
+
27
+ const response = await this.client.post('/login', formData, {
28
+ headers: {
29
+ 'Content-Type': 'application/x-www-form-urlencoded'
30
+ }
31
+ });
32
+
33
+ if (response.data.success) {
34
+ this.client.defaults.headers.Cookie = `lang=en-US; ${response.headers.get("set-cookie")[0].split(";")[0]}`;
35
+ }
36
+
37
+ return response.data;
38
+ }
39
+
40
+ async getSystemStats() {
41
+ const response = await this.client.post('/server/status');
42
+ return response.data.obj;
43
+ }
44
+
45
+ /**
46
+ * Get all inbounds
47
+ * @returns {Promise<Array<{
48
+ * id?: number,
49
+ * up: number,
50
+ * down: number,
51
+ * total: number,
52
+ * remark: string,
53
+ * enable: boolean,
54
+ * expiryTime: number | null,
55
+ * listen: string,
56
+ * port: number,
57
+ * protocol: string,
58
+ * settings: any,
59
+ * streamSettings: any,
60
+ * sniffing: any
61
+ * }>>}
62
+ */
63
+ async getInbounds() {
64
+ const response = await this.client.get('/panel/api/inbounds/list');
65
+ let inbounds = response.data.obj;
66
+ if (this.parseJSONSettings) {
67
+ inbounds = inbounds.map(inbound => this.parseInbound(inbound));
68
+ }
69
+ return inbounds;
70
+ }
71
+
72
+ parseInbound(inbound) {
73
+ if (this.parseJSONSettings) {
74
+ inbound.settings = inbound.settings && inbound.settings.length > 0 ? JSON.parse(inbound.settings) : {};
75
+ inbound.streamSettings = inbound.streamSettings && inbound.streamSettings.length > 0 ? JSON.parse(inbound.streamSettings) : {};
76
+ inbound.sniffing = inbound.sniffing && inbound.sniffing.length > 0 ? JSON.parse(inbound.sniffing) : {};
77
+ inbound.allocate = inbound.allocate && inbound.allocate.length > 0 ? JSON.parse(inbound.allocate) : {};
78
+ }
79
+ return inbound;
80
+ }
81
+
82
+ stringifyInbound(inbound) {
83
+ inbound.settings = JSON.stringify(inbound.settings);
84
+ inbound.streamSettings = JSON.stringify(inbound.streamSettings);
85
+ inbound.sniffing = JSON.stringify(inbound.sniffing);
86
+ inbound.allocate = JSON.stringify(inbound.allocate);
87
+ return inbound;
88
+ }
89
+
90
+ /**
91
+ * Add new inbound
92
+ * @param {Object} config Inbound configuration
93
+ * @param {number} config.up Upload traffic in bytes
94
+ * @param {number} config.down Download traffic in bytes
95
+ * @param {number} config.total Total traffic limit in bytes
96
+ * @param {string} config.remark Inbound remark/name
97
+ * @param {boolean} config.enable Enable status
98
+ * @param {number|null} config.expiryTime Expiry timestamp
99
+ * @param {string} config.listen Listen address
100
+ * @param {number} config.port Port number
101
+ * @param {string} config.protocol Protocol type
102
+ * @param {Object} config.settings Protocol settings
103
+ * @param {Object} config.streamSettings Stream settings
104
+ * @param {Object} config.sniffing Sniffing settings
105
+ * @param {Object} config.allocate Allocation settings
106
+ * @returns {Promise<void>}
107
+ */
108
+ async addInbound(config) {
109
+ config = this.stringifyInbound(config);
110
+ const response = await this.client.post('/panel/api/inbounds/add', config);
111
+ if(!response.data.success)
112
+ throw new Error(response.data.msg);
113
+ return this.parseInbound(response.data.obj);
114
+ }
115
+
116
+ /**
117
+ * Update existing inbound
118
+ * @param {number} id Inbound ID
119
+ * @param {Object} config Partial inbound configuration
120
+ * @returns {Promise<void>}
121
+ */
122
+ async updateInbound(id, config) {
123
+ config = this.stringifyInbound(config);
124
+ const response = await this.client.post(`/panel/api/inbounds/update/${id}`, config);
125
+ if(!response.data.success)
126
+ throw new Error(response.data.msg);
127
+ return this.parseInbound(response.data.obj);
128
+ }
129
+
130
+ /**
131
+ * Delete inbound
132
+ * @param {number} id Inbound ID
133
+ * @returns {Promise<void>}
134
+ */
135
+ async deleteInbound(id) {
136
+ await this.client.post(`/panel/api/inbounds/del/${id}`);
137
+ }
138
+
139
+ /**
140
+ * Get new X25519 certificate
141
+ * @returns {Promise<{privateKey: string, publicKey: string}>} New X25519 key pair
142
+ */
143
+ async getNewX25519Cert() {
144
+ const response = await this.client.post('/server/getNewX25519Cert');
145
+ return response.data.obj;
146
+ }
147
+ }