x3ui-api 1.0.3 → 1.0.5

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.
@@ -0,0 +1,223 @@
1
+ const crypto = require("crypto");
2
+ const RealityClientBuilder = require("./RealityClientBuilder");
3
+
4
+ module.exports = class RealityBuilder {
5
+ constructor(client, options = {}) {
6
+ this.client = client;
7
+ // Initialize from InboundConfig
8
+ this.id = options.id || undefined;
9
+ this.port = options.port || 0;
10
+ this.remark = options.remark || '';
11
+ this.listenIP = options.listen || '';
12
+ this.expiryTime = options.expiryTime || 0;
13
+ this.enable = true;
14
+
15
+ // Initialize from StreamSettings and RealitySettings
16
+ const streamSettings = typeof options.streamSettings === 'string'
17
+ ? JSON.parse(options.streamSettings)
18
+ : options.streamSettings || {};
19
+
20
+ const realitySettings = streamSettings?.realitySettings || {};
21
+
22
+ this.dest = realitySettings.dest || 'yahoo.com:443';
23
+ this.serverNames = realitySettings.serverNames || ['yahoo.com', 'www.yahoo.com'];
24
+ this.privateKey = realitySettings.privateKey || '';
25
+ this.publicKey = realitySettings.settings?.publicKey || '';
26
+ this.shortIds = realitySettings.shortIds;
27
+ this.fingerprint = realitySettings.settings?.fingerprint || 'chrome';
28
+
29
+ // Initialize clients
30
+ this.clients = [];
31
+ const settings = typeof options.settings === 'string'
32
+ ? JSON.parse(options.settings)
33
+ : options.settings;
34
+
35
+ if (settings?.clients) {
36
+ settings.clients.forEach(client => {
37
+ this.addClient(client);
38
+ });
39
+ }
40
+ }
41
+
42
+ setPort(port) {
43
+ this.port = port;
44
+ return this;
45
+ }
46
+
47
+ setRemark(remark) {
48
+ this.remark = remark;
49
+ return this;
50
+ }
51
+
52
+ setDest(dest) {
53
+ this.dest = dest;
54
+ return this;
55
+ }
56
+
57
+ setServerNames(names) {
58
+ this.serverNames = names;
59
+ return this;
60
+ }
61
+
62
+ setKeyPair(privateKey, publicKey) {
63
+ this.privateKey = privateKey;
64
+ this.publicKey = publicKey;
65
+ return this;
66
+ }
67
+
68
+ setShortIds(ids) {
69
+ this.shortIds = ids;
70
+ return this;
71
+ }
72
+
73
+ setFingerprint(fingerprint) {
74
+ this.fingerprint = fingerprint;
75
+ return this;
76
+ }
77
+
78
+ setListenIP(ip) {
79
+ this.listenIP = ip;
80
+ return this;
81
+ }
82
+
83
+ setExpiryTime(timestamp) {
84
+ this.expiryTime = timestamp;
85
+ return this;
86
+ }
87
+
88
+ addClient(options = {}) {
89
+ const builder = new RealityClientBuilder(this);
90
+ if (options.id) builder.setId(options.id);
91
+ if (options.email) builder.setEmail(options.email);
92
+ if (options.totalGB) builder.setTotalGB(options.totalGB);
93
+ if (options.expiryTime) builder.setExpiryTime(options.expiryTime);
94
+ if (options.tgId) builder.setTgId(options.tgId);
95
+ builder.parent.streamSettings = {
96
+ realitySettings: {
97
+ serverNames: this.serverNames,
98
+ settings: {
99
+ fingerprint: this.fingerprint,
100
+ publicKey: this.publicKey,
101
+ spiderX: '/'
102
+ },
103
+ shortIds: this.shortIds
104
+ }
105
+ };
106
+ this.clients.push(builder);
107
+ return builder;
108
+ }
109
+
110
+ getClientLink(clientIndex = 0, host) {
111
+ if (clientIndex < 0 || clientIndex >= this.clients.length) {
112
+ throw new Error('Invalid client index');
113
+ }
114
+ const client = this.clients[clientIndex];
115
+ return client.getLink(host || this.listenIP || 'localhost', this.port);
116
+ }
117
+
118
+ generateRandomPort() {
119
+ return Math.floor(Math.random() * (65535 - 1024) + 1024);
120
+ }
121
+
122
+ generateShortId() {
123
+ const length = Math.floor(Math.random() * 7) * 2 + 2; // Random length between 2 and 16
124
+ return crypto.randomBytes(Math.ceil(length / 2))
125
+ .toString('hex')
126
+ .slice(0, length);
127
+ }
128
+
129
+ async build() {
130
+ if (!this.remark) {
131
+ throw new Error('Remark is required');
132
+ }
133
+
134
+ // If no port specified, find unused one
135
+ if (!this.port) {
136
+ const inbounds = await this.client.getInbounds();
137
+ const usedPorts = new Set(inbounds.map(i => i.port));
138
+ let port;
139
+ do {
140
+ port = this.generateRandomPort();
141
+ } while (usedPorts.has(port));
142
+ this.port = port;
143
+ }
144
+
145
+ // If no keypair provided, generate one
146
+ if (!this.privateKey || !this.publicKey) {
147
+ const cert = await this.client.getNewX25519Cert();
148
+ this.privateKey = cert.privateKey;
149
+ this.publicKey = cert.publicKey;
150
+ }
151
+
152
+ // If no shortIds provided, generate random ones
153
+ if (!this.shortIds) {
154
+ this.shortIds = Array.from({ length: 8 }, () => this.generateShortId());
155
+ }
156
+
157
+ // If no clients added, create one default client
158
+ if (this.clients.length === 0) {
159
+ this.addClient();
160
+ }
161
+
162
+ // Build all clients
163
+ const clientConfigs = await Promise.all(this.clients.map(builder => builder.build()));
164
+
165
+ return {
166
+ id: this.id,
167
+ up: 0,
168
+ down: 0,
169
+ total: 0,
170
+ remark: this.remark,
171
+ enable: true,
172
+ expiryTime: this.expiryTime,
173
+ listen: this.listenIP,
174
+ port: this.port,
175
+ protocol: 'vless',
176
+ settings: {
177
+ clients: clientConfigs,
178
+ decryption: 'none',
179
+ fallbacks: []
180
+ },
181
+ streamSettings: {
182
+ network: 'tcp',
183
+ security: 'reality',
184
+ externalProxy: [],
185
+ realitySettings: {
186
+ show: false,
187
+ xver: 0,
188
+ dest: this.dest,
189
+ serverNames: this.serverNames,
190
+ privateKey: this.privateKey,
191
+ minClient: '',
192
+ maxClient: '',
193
+ maxTimediff: 0,
194
+ shortIds: this.shortIds,
195
+ settings: {
196
+ publicKey: this.publicKey,
197
+ fingerprint: this.fingerprint,
198
+ serverName: '',
199
+ spiderX: '/'
200
+ }
201
+ },
202
+ tcpSettings: {
203
+ acceptProxyProtocol: false,
204
+ header: {
205
+ type: 'none'
206
+ }
207
+ }
208
+ },
209
+ tag: `inbound-${this.port}`,
210
+ sniffing: {
211
+ enabled: false,
212
+ destOverride: ['http', 'tls', 'quic', 'fakedns'],
213
+ metadataOnly: false,
214
+ routeOnly: false
215
+ },
216
+ allocate: {
217
+ strategy: 'always',
218
+ refresh: 5,
219
+ concurrency: 3
220
+ }
221
+ };
222
+ }
223
+ }
@@ -0,0 +1,11 @@
1
+ import ClientBuilder from "../../core/ClientBuilder";
2
+
3
+ export default class RealityClientBuilder extends ClientBuilder {
4
+
5
+ /**
6
+ * Generate connection link for this client
7
+ * @param host Host address
8
+ * @param port Optional port number, defaults to parent's port
9
+ */
10
+ getLink(host: string, port?: number): string;
11
+ }
@@ -0,0 +1,25 @@
1
+ const ClientBuilder = require('../../core/ClientBuilder');
2
+
3
+ module.exports = class RealityClientBuilder extends ClientBuilder {
4
+ getLink(host, port) {
5
+ const id = this.id || crypto.randomUUID();
6
+ const settings = this.parent.streamSettings?.realitySettings;
7
+ if (!settings) throw new Error('Reality settings not found');
8
+
9
+ port = port || this.parent.port;
10
+ let protocol = this.parent.protocol || 'vless';
11
+
12
+ const params = new URLSearchParams({
13
+ security: this.parent.streamSettings?.security || 'reality',
14
+ sni: settings.serverNames[0],
15
+ fp: settings.settings.fingerprint,
16
+ pbk: settings.settings.publicKey,
17
+ sid: settings.shortIds[0],
18
+ spx: settings.settings.spiderX || '/',
19
+ type: this.parent.streamSettings?.network || 'tcp',
20
+ encryption: this.parent.settings?.decryption || 'none'
21
+ });
22
+
23
+ return `${protocol}://${id}@${host}:${port}?${params.toString()}#${encodeURIComponent(this.email || 'default')}`;
24
+ }
25
+ }
@@ -0,0 +1,9 @@
1
+ import RealityBuilder from "./RealityBuilder";
2
+ import RealityClientBuilder from "./RealityClientBuilder";
3
+
4
+ declare const _default: {
5
+ RealityBuilder: typeof RealityBuilder;
6
+ RealityClientBuilder: typeof RealityClientBuilder;
7
+ };
8
+
9
+ export default _default;
@@ -0,0 +1,4 @@
1
+ module.exports = {
2
+ RealityBuilder: require('./RealityBuilder'),
3
+ RealityClientBuilder: require('./RealityClientBuilder')
4
+ }
@@ -0,0 +1,77 @@
1
+ import X3UIClient, {ClientSettings, InboundConfig} from "../../index";
2
+ import VmessClientBuilder from "./VmessClientBuilder";
3
+
4
+ /**
5
+ * Builder for Vmess inbound configurations
6
+ */
7
+ export default class VmessBuilder {
8
+
9
+ constructor(client: X3UIClient, inbound?: Partial<InboundConfig>);
10
+
11
+ /**
12
+ * Set the port for the inbound. If not provided, will auto-generate unused port
13
+ */
14
+ setPort(port: number): this;
15
+
16
+ /**
17
+ * Set the remark/name for the inbound
18
+ */
19
+ setRemark(remark: string): this;
20
+
21
+ /**
22
+ * Set the network type (e.g. "httpupgrade", "tcp", "ws")
23
+ */
24
+ setNetwork(network: string): this;
25
+
26
+ /**
27
+ * Set security type (e.g. "none", "tls")
28
+ */
29
+ setSecurity(security: string): this;
30
+
31
+ /**
32
+ * Set HTTP upgrade path
33
+ */
34
+ setHttpUpgradePath(path: string): this;
35
+
36
+ /**
37
+ * Set HTTP upgrade host
38
+ */
39
+ setHttpUpgradeHost(host: string): this;
40
+
41
+ /**
42
+ * Configure sniffing settings
43
+ */
44
+ setSniffing(enabled: boolean, destOverride?: string[], metadataOnly?: boolean, routeOnly?: boolean): this;
45
+
46
+ /**
47
+ * Set listen IP address. Defaults to empty string
48
+ */
49
+ setListenIP(ip: string): this;
50
+
51
+ /**
52
+ * Set inbound expiry time. Defaults to 0 (no expiry)
53
+ */
54
+ setExpiryTime(timestamp: number): this;
55
+
56
+ /**
57
+ * Add a new client to the inbound
58
+ */
59
+ addClient(options?: Partial<ClientSettings>): VmessClientBuilder;
60
+
61
+ /**
62
+ * Get connection link for a client
63
+ * @param clientIndex Index of the client (defaults to 0)
64
+ * @param host Optional host address (defaults to listenIP or 'localhost')
65
+ */
66
+ getClientLink(clientIndex?: number, host?: string): string;
67
+
68
+ /**
69
+ * Generate a random port number
70
+ */
71
+ generateRandomPort(): number;
72
+
73
+ /**
74
+ * Build the final inbound config
75
+ */
76
+ build(): Promise<InboundConfig>;
77
+ }
@@ -0,0 +1,189 @@
1
+ const VmessClientBuilder = require('./VmessClientBuilder');
2
+
3
+ module.exports = class VmessBuilder {
4
+ constructor(client, options = {}) {
5
+ this.client = client;
6
+ // Initialize from InboundConfig
7
+ this.id = options.id || undefined;
8
+ this.port = options.port || 0;
9
+ this.remark = options.remark || '';
10
+ this.listenIP = options.listen || '';
11
+ this.expiryTime = options.expiryTime || 0;
12
+ this.enable = true;
13
+ this.up = options.up || 0;
14
+ this.down = options.down || 0;
15
+ this.total = options.total || 0;
16
+
17
+ // Initialize from StreamSettings
18
+ const streamSettings = typeof options.streamSettings === 'string'
19
+ ? JSON.parse(options.streamSettings)
20
+ : options.streamSettings || {};
21
+
22
+ this.network = streamSettings.network || 'httpupgrade';
23
+ this.security = streamSettings.security || 'none';
24
+
25
+ // Initialize httpupgrade settings
26
+ this.httpupgradeSettings = streamSettings.httpupgradeSettings || {
27
+ acceptProxyProtocol: false,
28
+ path: '/',
29
+ host: '',
30
+ headers: {}
31
+ };
32
+
33
+ // Initialize sniffing
34
+ this.sniffing = options.sniffing || {
35
+ enabled: false,
36
+ destOverride: ['http', 'tls', 'quic', 'fakedns'],
37
+ metadataOnly: false,
38
+ routeOnly: false
39
+ };
40
+
41
+ // Initialize allocate
42
+ this.allocate = options.allocate || {
43
+ strategy: 'always',
44
+ refresh: 5,
45
+ concurrency: 3
46
+ };
47
+
48
+ // Initialize clients
49
+ this.clients = [];
50
+ const settings = typeof options.settings === 'string'
51
+ ? JSON.parse(options.settings)
52
+ : options.settings;
53
+
54
+ if (settings?.clients) {
55
+ settings.clients.forEach(client => {
56
+ this.addClient(client);
57
+ });
58
+ }
59
+ }
60
+
61
+ setPort(port) {
62
+ this.port = port;
63
+ return this;
64
+ }
65
+
66
+ setRemark(remark) {
67
+ this.remark = remark;
68
+ return this;
69
+ }
70
+
71
+ setNetwork(network) {
72
+ this.network = network;
73
+ return this;
74
+ }
75
+
76
+ setSecurity(security) {
77
+ this.security = security;
78
+ return this;
79
+ }
80
+
81
+ setHttpUpgradePath(path) {
82
+ this.httpupgradeSettings.path = path;
83
+ return this;
84
+ }
85
+
86
+ setHttpUpgradeHost(host) {
87
+ this.httpupgradeSettings.host = host;
88
+ return this;
89
+ }
90
+
91
+ setSniffing(enabled, destOverride = ['http', 'tls', 'quic', 'fakedns'], metadataOnly = false, routeOnly = false) {
92
+ this.sniffing = {
93
+ enabled,
94
+ destOverride,
95
+ metadataOnly,
96
+ routeOnly
97
+ };
98
+ return this;
99
+ }
100
+
101
+ setListenIP(ip) {
102
+ this.listenIP = ip;
103
+ return this;
104
+ }
105
+
106
+ setExpiryTime(timestamp) {
107
+ this.expiryTime = timestamp;
108
+ return this;
109
+ }
110
+
111
+ addClient(options = {}) {
112
+ const builder = new VmessClientBuilder(this);
113
+ if (options.id) builder.setId(options.id);
114
+ if (options.email) builder.setEmail(options.email);
115
+ if (options.totalGB) builder.setTotalGB(options.totalGB);
116
+ if (options.expiryTime) builder.setExpiryTime(options.expiryTime);
117
+ if (options.tgId) builder.setTgId(options.tgId);
118
+ if (options.security) builder.setSecurity(options.security);
119
+ if (options.limitIp) builder.setLimitIp(options.limitIp);
120
+
121
+ this.clients.push(builder);
122
+ return builder;
123
+ }
124
+
125
+ getClientLink(clientIndex = 0, host, port) {
126
+ if (clientIndex < 0 || clientIndex >= this.clients.length) {
127
+ throw new Error('Invalid client index');
128
+ }
129
+ const client = this.clients[clientIndex];
130
+ return client.getLink(host || this.listenIP || 'localhost', port || this.port);
131
+ }
132
+
133
+ generateRandomPort() {
134
+ return Math.floor(Math.random() * (65535 - 1024) + 1024);
135
+ }
136
+
137
+ async build() {
138
+ if (!this.remark) {
139
+ throw new Error('Remark is required');
140
+ }
141
+
142
+ // If no port specified, find unused one
143
+ if (!this.port) {
144
+ const inbounds = await this.client.getInbounds();
145
+ const usedPorts = new Set(inbounds.map(i => i.port));
146
+ let port;
147
+ do {
148
+ port = this.generateRandomPort();
149
+ } while (usedPorts.has(port));
150
+ this.port = port;
151
+ }
152
+
153
+ // If no clients added, create one default client
154
+ if (this.clients.length === 0) {
155
+ this.addClient();
156
+ }
157
+
158
+ // Build all clients
159
+ const clientConfigs = await Promise.all(this.clients.map(builder => builder.build()));
160
+
161
+ // Create tag for inbound
162
+ const tag = `inbound-${this.port}`;
163
+
164
+ // Build the complete inbound configuration
165
+ return {
166
+ up: this.up,
167
+ down: this.down,
168
+ total: this.total,
169
+ remark: this.remark,
170
+ enable: this.enable,
171
+ expiryTime: this.expiryTime,
172
+ listen: this.listenIP,
173
+ port: this.port,
174
+ protocol: 'vmess',
175
+ settings: {
176
+ clients: clientConfigs
177
+ },
178
+ streamSettings: {
179
+ network: this.network,
180
+ security: this.security,
181
+ externalProxy: [],
182
+ httpupgradeSettings: this.httpupgradeSettings
183
+ },
184
+ tag,
185
+ sniffing: this.sniffing,
186
+ allocate: this.allocate
187
+ };
188
+ }
189
+ }
@@ -0,0 +1,13 @@
1
+ import ClientBuilder from "../../core/ClientBuilder";
2
+
3
+ /**
4
+ * Builder for Vmess client configurations
5
+ */
6
+ export default class VmessClientBuilder extends ClientBuilder {
7
+ /**
8
+ * Generate connection link for this client
9
+ * @param host Host address
10
+ * @param port Optional port number, defaults to parent's port
11
+ */
12
+ getLink(host: string, port?: number): string;
13
+ }
@@ -0,0 +1,92 @@
1
+ const crypto = require("crypto");
2
+ module.exports = class VmessClientBuilder {
3
+ constructor(parent) {
4
+ this.parent = parent;
5
+ this.id = '';
6
+ this.email = '';
7
+ this.limitIp = 0;
8
+ this.totalGB = 0;
9
+ this.expiryTime = 0;
10
+ this.enable = true;
11
+ this.tgId = '';
12
+ this.subId = '';
13
+ this.reset = 0;
14
+ this.security = 'auto';
15
+ }
16
+
17
+ setId(id) {
18
+ this.id = id;
19
+ return this;
20
+ }
21
+
22
+ setEmail(email) {
23
+ this.email = email;
24
+ return this;
25
+ }
26
+
27
+ setTotalGB(gb) {
28
+ this.totalGB = gb;
29
+ return this;
30
+ }
31
+
32
+ setExpiryTime(timestamp) {
33
+ this.expiryTime = timestamp;
34
+ return this;
35
+ }
36
+
37
+ setTgId(id) {
38
+ this.tgId = id;
39
+ return this;
40
+ }
41
+
42
+ setSecurity(security) {
43
+ this.security = security;
44
+ return this;
45
+ }
46
+
47
+ setLimitIp(limit) {
48
+ this.limitIp = limit;
49
+ return this;
50
+ }
51
+
52
+ build() {
53
+ return {
54
+ id: this.id || crypto.randomUUID(),
55
+ email: this.email || Math.random().toString(36).substring(2, 10),
56
+ enable: this.enable,
57
+ expiryTime: this.expiryTime,
58
+ limitIp: this.limitIp,
59
+ reset: this.reset,
60
+ security: this.security,
61
+ subId: this.subId || Math.random().toString(36).substring(2, 18),
62
+ tgId: this.tgId,
63
+ totalGB: this.totalGB
64
+ };
65
+ }
66
+
67
+ getLink(host, port) {
68
+ const id = this.id || crypto.randomUUID();
69
+ const email = this.email || Math.random().toString(36).substring(2, 10);
70
+
71
+ // Create vmess config object
72
+ const vmessConfig = {
73
+ v: '2',
74
+ ps: email,
75
+ add: host,
76
+ port: port,
77
+ id: id,
78
+ aid: 0,
79
+ net: this.parent.network,
80
+ type: 'none',
81
+ host: this.parent.httpupgradeSettings.host,
82
+ path: this.parent.httpupgradeSettings.path,
83
+ tls: this.parent.security === 'tls' ? 'tls' : 'none',
84
+ sni: this.parent.httpupgradeSettings.host,
85
+ alpn: ''
86
+ };
87
+
88
+ // Convert to base64
89
+ const vmessLink = 'vmess://' + Buffer.from(JSON.stringify(vmessConfig)).toString('base64');
90
+ return vmessLink;
91
+ }
92
+ }
@@ -0,0 +1,9 @@
1
+ import VmessBuilder from "./VmessBuilder";
2
+ import VmessClientBuilder from "./VmessClientBuilder";
3
+
4
+ declare const _default: {
5
+ VmessBuilder: typeof VmessBuilder;
6
+ VmessClientBuilder: typeof VmessClientBuilder;
7
+ };
8
+
9
+ export default _default;
@@ -0,0 +1,4 @@
1
+ module.exports = {
2
+ VmessBuilder: require('./VmessBuilder'),
3
+ VmessClientBuilder: require('./VmessClientBuilder')
4
+ }