153 lines
3.8 KiB
TypeScript
153 lines
3.8 KiB
TypeScript
import { Base64 } from 'js-base64';
|
||
|
||
class VLESSQuery {
|
||
constructor(
|
||
public security: string,
|
||
public alpn: string[],
|
||
public sni: string,
|
||
public fp: string,
|
||
public sid: string,
|
||
public pbk: string,
|
||
public flow: string,
|
||
public encryption: string,
|
||
public type: string,
|
||
public headerType: string,
|
||
public path: string,
|
||
public host: string
|
||
) { }
|
||
}
|
||
|
||
class VLESS {
|
||
constructor(
|
||
public name: string,
|
||
public uuid: string,
|
||
public server: string,
|
||
public port: number,
|
||
public query: VLESSQuery
|
||
) { }
|
||
|
||
// Base64 解码函数
|
||
// private static base64Decode(str: string): string {
|
||
// return Buffer.from(str, 'base64').toString('utf-8');
|
||
// }
|
||
|
||
// 编码 VLESS URL
|
||
encodeURL(): string {
|
||
const url = new URL(`vless://${this.uuid}@${this.server}:${this.port}`);
|
||
|
||
// 设置 Query 参数
|
||
const query = url.searchParams;
|
||
query.set('security', this.query.security);
|
||
query.set('sni', this.query.sni);
|
||
query.set('fp', this.query.fp);
|
||
query.set('sid', this.query.sid);
|
||
query.set('pbk', this.query.pbk);
|
||
query.set('flow', this.query.flow);
|
||
query.set('encryption', this.query.encryption);
|
||
query.set('type', this.query.type);
|
||
query.set('headerType', this.query.headerType);
|
||
query.set('path', this.query.path);
|
||
query.set('host', this.query.host);
|
||
|
||
// 移除空值参数
|
||
for (const [key, value] of query) {
|
||
if (value === '') {
|
||
query.delete(key);
|
||
}
|
||
}
|
||
|
||
// 如果有 name,设置为 Fragment
|
||
if (this.name) {
|
||
url.hash = this.name;
|
||
} else {
|
||
url.hash = `${this.server}:${this.port}`;
|
||
}
|
||
|
||
return url.toString();
|
||
}
|
||
|
||
// 解码 VLESS URL
|
||
static decodeURL(urlStr: string): VLESS {
|
||
if (!urlStr.startsWith('vless://')) {
|
||
throw new Error(`Invalid vless URL: ${urlStr}`);
|
||
}
|
||
|
||
const decodedUrl = urlStr.split('@');
|
||
const url = new URL(`http://${decodedUrl[1]}`);
|
||
const regex = /[0-9a-f]{8}(-[0-9a-f]{4}){3}-[0-9a-f]{12}/;
|
||
const match = urlStr.match(regex);
|
||
const uuid = match[0];
|
||
const hostname = url.hostname;
|
||
const port = parseInt(url.port, 10);
|
||
|
||
const query = url.searchParams;
|
||
const encryption = query.get('encryption') || '';
|
||
const security = query.get('security') || '';
|
||
const type = query.get('type') || '';
|
||
const flow = query.get('flow') || '';
|
||
const headerType = query.get('headerType') || '';
|
||
const pbk = query.get('pbk') || '';
|
||
const sid = query.get('sid') || '';
|
||
const fp = query.get('fp') || '';
|
||
const alpns = query.get('alpn') || '';
|
||
const alpn = alpns ? alpns.split(',') : [];
|
||
const sni = query.get('sni') || '';
|
||
const path = query.get('path') || '';
|
||
const host = query.get('host') || '';
|
||
|
||
// 获取 name,若为空则使用 `hostname:port`
|
||
const name = url.hash ? decodeURIComponent(url.hash.slice(1)) : `${hostname}:${port}`;
|
||
|
||
const queryObj = new VLESSQuery(
|
||
security,
|
||
alpn,
|
||
sni,
|
||
fp,
|
||
sid,
|
||
pbk,
|
||
flow,
|
||
encryption,
|
||
type,
|
||
headerType,
|
||
path,
|
||
host
|
||
);
|
||
|
||
return new VLESS(name, uuid, hostname, port, queryObj);
|
||
}
|
||
}
|
||
|
||
// // 示例用法
|
||
// function callVLESS() {
|
||
// const vless = new VLESS(
|
||
// 'Sharon-香港',
|
||
// '6adb4f43-9813-45f4-abf8-772be7db08sd',
|
||
// 'ss.com',
|
||
// 443,
|
||
// new VLESSQuery(
|
||
// 'reality',
|
||
// ['http/1.1'],
|
||
// 'ss.com',
|
||
// 'chrome',
|
||
// '',
|
||
// 'g-oxbqigzCaXqARxuyD2_vbTYeMD9zn8wnTo02S69QM',
|
||
// 'xtls-rprx-vision',
|
||
// 'none',
|
||
// 'tcp',
|
||
// 'none',
|
||
// '',
|
||
// ''
|
||
// )
|
||
// );
|
||
|
||
// const encoded = vless.encodeURL();
|
||
// console.log('Encoded VLESS URL:', encoded);
|
||
|
||
// const decoded = VLESS.decodeURL(encoded);
|
||
// console.log('Decoded VLESS:', decoded);
|
||
// }
|
||
|
||
// callVLESS();
|
||
|
||
export { VLESS }
|