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.
- package/README.md +274 -26
- package/package.json +1 -1
- package/src/core/ClientBuilder.d.ts +37 -0
- package/src/core/ClientBuilder.js +57 -0
- package/src/core/IndoundBuilder.d.ts +1 -0
- package/src/core/X3UIClient.d.ts +31 -0
- package/src/core/X3UIClient.js +147 -0
- package/src/index.d.ts +12 -148
- package/src/index.d.ts~ +185 -0
- package/src/index.js +2 -455
- package/src/protocols/vless/RealityBuilder.d.ts +69 -0
- package/src/protocols/vless/RealityBuilder.js +223 -0
- package/src/protocols/vless/RealityClientBuilder.d.ts +11 -0
- package/src/protocols/vless/RealityClientBuilder.js +25 -0
- package/src/protocols/vless/index.d.ts +9 -0
- package/src/protocols/vless/index.js +4 -0
- package/src/protocols/vmess/VmessBuilder.d.ts +77 -0
- package/src/protocols/vmess/VmessBuilder.js +189 -0
- package/src/protocols/vmess/VmessClientBuilder.d.ts +13 -0
- package/src/protocols/vmess/VmessClientBuilder.js +92 -0
- package/src/protocols/vmess/index.d.ts +9 -0
- package/src/protocols/vmess/index.js +4 -0
- package/src/protocols/wireguard/WireguardBuilder.d.ts +118 -0
- package/src/protocols/wireguard/WireguardBuilder.js +204 -0
- package/src/protocols/wireguard/WireguardClientBuilder.d.ts +79 -0
- package/src/protocols/wireguard/WireguardClientBuilder.js +88 -0
- package/src/protocols/wireguard/index.d.ts +9 -0
- package/src/protocols/wireguard/index.js +4 -0
- package/src/protocols/wireguard/keyUtils.js +95 -0
|
@@ -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,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
|
+
}
|