Refactor VPN proxy client implementation
This commit is contained in:
@@ -0,0 +1,105 @@
|
||||
export interface ServerIdentityInput extends Record<string, unknown> {
|
||||
id?: unknown;
|
||||
label?: unknown;
|
||||
tag?: unknown;
|
||||
host?: unknown;
|
||||
server?: unknown;
|
||||
port?: unknown;
|
||||
server_port?: unknown;
|
||||
protocol?: unknown;
|
||||
type?: unknown;
|
||||
country?: unknown;
|
||||
city?: unknown;
|
||||
provider?: unknown;
|
||||
}
|
||||
|
||||
export interface NormalizedServer extends Record<string, unknown> {
|
||||
id: string;
|
||||
label: string;
|
||||
host: string;
|
||||
port: number;
|
||||
protocol: string;
|
||||
tag: string;
|
||||
server: string;
|
||||
server_port: number;
|
||||
type: string;
|
||||
}
|
||||
|
||||
const text = (value: unknown) => String(value || '').trim();
|
||||
|
||||
function record(value: unknown): ServerIdentityInput {
|
||||
return value && typeof value === 'object' && !Array.isArray(value)
|
||||
? value as ServerIdentityInput
|
||||
: {};
|
||||
}
|
||||
|
||||
function hash64(value: string) {
|
||||
let hash = 0xcbf29ce484222325n;
|
||||
for (let index = 0; index < value.length; index += 1) {
|
||||
hash ^= BigInt(value.charCodeAt(index));
|
||||
hash = BigInt.asUintN(64, hash * 0x100000001b3n);
|
||||
}
|
||||
return hash.toString(16).padStart(16, '0');
|
||||
}
|
||||
|
||||
export function serverIdentityKey(value: unknown) {
|
||||
const server = record(value);
|
||||
const protocol = text(server.protocol || server.type).toLowerCase();
|
||||
const host = text(server.host || server.server).toLowerCase();
|
||||
const port = Number(server.port || server.server_port) || 0;
|
||||
return `${protocol}\u0000${host}\u0000${port}`;
|
||||
}
|
||||
|
||||
export function createServerId(server: unknown) {
|
||||
return `srv_${hash64(`endpoint\u0000${serverIdentityKey(server)}`)}`;
|
||||
}
|
||||
|
||||
export function normalizeServer(server: unknown): NormalizedServer {
|
||||
const source = record(server);
|
||||
const protocol = text(source.protocol || source.type).toLowerCase();
|
||||
const host = text(source.host || source.server);
|
||||
const port = Number(source.port || source.server_port) || 0;
|
||||
const id = text(source.id) || createServerId(source);
|
||||
const label = text(source.label || source.tag) || host;
|
||||
const metadata = Object.fromEntries([
|
||||
['country', text(source.country)],
|
||||
['city', text(source.city)],
|
||||
['provider', text(source.provider)],
|
||||
].filter(([, value]) => value));
|
||||
|
||||
return {
|
||||
id,
|
||||
label,
|
||||
host,
|
||||
port,
|
||||
protocol,
|
||||
...metadata,
|
||||
// v0 aliases remain until old clients no longer consume this API.
|
||||
tag: label,
|
||||
server: host,
|
||||
server_port: port,
|
||||
type: protocol,
|
||||
};
|
||||
}
|
||||
|
||||
export function normalizeServers(servers: unknown): NormalizedServer[] {
|
||||
const seen = new Set<string>();
|
||||
return (Array.isArray(servers) ? servers : []).flatMap((server) => {
|
||||
const normalized = normalizeServer(server);
|
||||
if (!normalized.id || seen.has(normalized.id)) return [];
|
||||
seen.add(normalized.id);
|
||||
return [normalized];
|
||||
});
|
||||
}
|
||||
|
||||
export function resolveServerId(
|
||||
servers: readonly Pick<NormalizedServer, 'id' | 'label'>[],
|
||||
serverId: unknown,
|
||||
legacyTag: unknown = '',
|
||||
) {
|
||||
const id = text(serverId);
|
||||
if (id) return servers.some((server) => server.id === id) ? id : '';
|
||||
const tag = text(legacyTag);
|
||||
const matches = tag ? servers.filter((server) => server.label === tag) : [];
|
||||
return matches.length === 1 ? matches[0].id : '';
|
||||
}
|
||||
Reference in New Issue
Block a user