From 1c28f62f36e9ef8660cb625a9f72bc47c92c2918 Mon Sep 17 00:00:00 2001 From: Gapodo Date: Tue, 21 Nov 2023 01:54:51 +0100 Subject: [PATCH] settings and resolver --- shared/js/settings.ts | 116 +++++++++++++++--------------- web/app/dns/resolver.ts | 153 ++++++++++++++++++++++------------------ 2 files changed, 143 insertions(+), 126 deletions(-) diff --git a/shared/js/settings.ts b/shared/js/settings.ts index 70cf46f0..98cb2a0e 100644 --- a/shared/js/settings.ts +++ b/shared/js/settings.ts @@ -1,10 +1,10 @@ -import {LogCategory, logError, logInfo, logTrace} from "./log"; +import { LogCategory, logError, logInfo, logTrace } from "./log"; import * as loader from "tc-loader"; -import {Stage} from "tc-loader"; -import {Registry} from "./events"; -import {tr} from "./i18n/localize"; -import {CallOnce, ignorePromise} from "tc-shared/proto"; -import {getStorageAdapter} from "tc-shared/StorageAdapter"; +import { Stage } from "tc-loader"; +import { Registry } from "./events"; +import { tr } from "./i18n/localize"; +import { CallOnce, ignorePromise } from "tc-shared/proto"; +import { getStorageAdapter } from "tc-shared/StorageAdapter"; /* * TODO: Sync settings across renderer instances @@ -14,17 +14,17 @@ export type RegistryValueType = boolean | number | string | object; export type RegistryValueTypeNames = "boolean" | "number" | "string" | "object"; export type RegistryValueTypeMapping = T extends boolean ? "boolean" : - T extends number ? "number" : - T extends string ? "string" : - T extends object ? "object" : - never; + T extends number ? "number" : + T extends string ? "string" : + T extends object ? "object" : + never; export interface RegistryKey { key: string; valueType: RegistryValueTypeMapping; fallbackKeys?: string | string[]; - fallbackImports?: {[key: string]:(value: string) => ValueType}; + fallbackImports?: { [key: string]: (value: string) => ValueType }; description?: string; @@ -37,7 +37,7 @@ export interface ValuedRegistryKey extends const UPDATE_DIRECT: boolean = true; -function decodeValueFromString(input: string, type: RegistryValueTypeMapping) : T { +function decodeValueFromString(input: string, type: RegistryValueTypeMapping): T { switch (type) { case "string": return input as any; @@ -60,7 +60,7 @@ function decodeValueFromString(input: string, type: } } -export function encodeSettingValueToString(input: T) : string { +export function encodeSettingValueToString(input: T): string { switch (typeof input) { case "string": return input; @@ -83,24 +83,24 @@ export function resolveSettingKey, resolver: (key: string) => string | undefined | null, defaultValue: DefaultType -) : ValueType | DefaultType { +): ValueType | DefaultType { let value = resolver(key.key); const keys = [key.key]; - if(Array.isArray(key.fallbackKeys)) { + if (Array.isArray(key.fallbackKeys)) { keys.push(...key.fallbackKeys); } - for(const resolveKey of keys) { + for (const resolveKey of keys) { value = resolver(resolveKey); - if(typeof value !== "string") { + if (typeof value !== "string") { continue; } switch (key.valueType) { case "number": case "boolean": - if(value.length === 0) { + if (value.length === 0) { continue; } break; @@ -109,9 +109,9 @@ export function resolveSettingKey(key: RegistryKey, defaultValue: DV) : V | DV; - getValue(key: ValuedRegistryKey, defaultValue?: V) : V; - getValue(key: RegistryKey | ValuedRegistryKey, defaultValue: DV) : V | DV { - if(arguments.length > 1) { + getValue(key: RegistryKey, defaultValue: DV): V | DV; + getValue(key: ValuedRegistryKey, defaultValue?: V): V; + getValue(key: RegistryKey | ValuedRegistryKey, defaultValue: DV): V | DV { + if (arguments.length > 1) { return resolveSettingKey(key, key => this.getParameter(key), defaultValue); - } else if("defaultValue" in key) { + } else if ("defaultValue" in key) { return resolveSettingKey(key, key => this.getParameter(key), key.defaultValue); } else { throw tr("missing value"); @@ -155,14 +155,14 @@ export class UrlParameterBuilder { private parameters = {}; setValue(key: RegistryKey, value: V) { - if(value === undefined) { + if (value === undefined) { delete this.parameters[key.key]; } else { this.parameters[key.key] = encodeURIComponent(encodeSettingValueToString(value)); } } - build() : string { + build(): string { return Object.keys(this.parameters).map(key => `${key}=${this.parameters[key]}`).join("&"); } } @@ -174,12 +174,12 @@ export class UrlParameterBuilder { export namespace AppParameters { export const Instance = new UrlParameterParser(new URL(window.location.href)); - export function getValue(key: RegistryKey, defaultValue: DV) : V | DV; - export function getValue(key: ValuedRegistryKey, defaultValue?: V) : V; - export function getValue(key: RegistryKey | ValuedRegistryKey, defaultValue: DV) : V | DV { - if(arguments.length > 1) { + export function getValue(key: RegistryKey, defaultValue: DV): V | DV; + export function getValue(key: ValuedRegistryKey, defaultValue?: V): V; + export function getValue(key: RegistryKey | ValuedRegistryKey, defaultValue: DV): V | DV { + if (arguments.length > 1) { return Instance.getValue(key, defaultValue); - } else if("defaultValue" in key) { + } else if ("defaultValue" in key) { return Instance.getValue(key); } else { throw tr("missing value"); @@ -408,12 +408,12 @@ export class Settings { static readonly KEY_FLAG_CONNECT_DEFAULT: ValuedRegistryKey = { key: "connect_default", valueType: "boolean", - defaultValue: false + defaultValue: true }; static readonly KEY_CONNECT_ADDRESS: ValuedRegistryKey = { key: "connect_address", valueType: "string", - defaultValue: undefined + defaultValue: "tea.lp.kle.li" }; static readonly KEY_CONNECT_PROFILE: ValuedRegistryKey = { key: "connect_profile", @@ -448,7 +448,7 @@ export class Settings { static readonly KEY_CONNECT_NO_DNSPROXY: ValuedRegistryKey = { key: "connect_no_dnsproxy", - defaultValue: false, + defaultValue: true, valueType: "boolean", }; @@ -616,7 +616,7 @@ export class Settings { /* defaultValue: */ }; - static readonly KEY_IPC_REMOTE_ADDRESS: RegistryKey = { + static readonly KEY_IPC_REMOTE_ADDRESS: RegistryKey = { key: "ipc-address", valueType: "string" }; @@ -913,8 +913,8 @@ export class Settings { static readonly KEYS = (() => { const result = []; - for(const key of Object.keys(Settings)) { - if(!key.toUpperCase().startsWith("KEY_")) { + for (const key of Object.keys(Settings)) { + if (!key.toUpperCase().startsWith("KEY_")) { continue; } @@ -943,13 +943,13 @@ export class Settings { const json = await getStorageAdapter().get("settings.global"); try { - if(json === null) { + if (json === null) { logInfo(LogCategory.GENERAL, tr("Found no settings. Creating new client settings.")); this.settingsCache = {}; } else { this.settingsCache = JSON.parse(json); } - } catch(error) { + } catch (error) { this.settingsCache = {}; logError(LogCategory.GENERAL, tr("Failed to load global settings!\nJson: %s\nError: %o"), json, error); @@ -957,7 +957,7 @@ export class Settings { //FIXME: Readd this //createErrorModal(tr("Failed to load global settings"), tr("Failed to load global client settings!\nLookup console for more information.")).open(); }; - if(!loader.finished()) { + if (!loader.finished()) { loader.register_task(loader.Stage.LOADED, { priority: 0, name: "Settings error", @@ -969,18 +969,18 @@ export class Settings { } this.saveWorker = setInterval(() => { - if(this.updated) { + if (this.updated) { this.save(); } }, 5 * 1000); } - getValue(key: RegistryKey, defaultValue: DV) : V | DV; - getValue(key: ValuedRegistryKey, defaultValue?: V) : V; - getValue(key: RegistryKey | ValuedRegistryKey, defaultValue: DV) : V | DV { - if(arguments.length > 1) { + getValue(key: RegistryKey, defaultValue: DV): V | DV; + getValue(key: ValuedRegistryKey, defaultValue?: V): V; + getValue(key: RegistryKey | ValuedRegistryKey, defaultValue: DV): V | DV { + if (arguments.length > 1) { return resolveSettingKey(key, key => this.settingsCache[key], defaultValue); - } else if("defaultValue" in key) { + } else if ("defaultValue" in key) { return resolveSettingKey(key, key => this.settingsCache[key], key.defaultValue); } else { debugger; @@ -988,17 +988,17 @@ export class Settings { } } - setValue(key: RegistryKey, value?: T){ - if(value === null) { + setValue(key: RegistryKey, value?: T) { + if (value === null) { value = undefined; } - if(this.settingsCache[key.key] === value) { + if (this.settingsCache[key.key] === value) { return; } const oldValue = this.settingsCache[key.key]; - if(value === undefined) { + if (value === undefined) { delete this.settingsCache[key.key]; } else { this.settingsCache[key.key] = encodeSettingValueToString(value); @@ -1014,21 +1014,21 @@ export class Settings { }); logTrace(LogCategory.GENERAL, tr("Changing global setting %s to %o"), key.key, value); - if(UPDATE_DIRECT) { + if (UPDATE_DIRECT) { this.save(); } } - globalChangeListener(key: RegistryKey, listener: (newValue: T) => void) : () => void { + globalChangeListener(key: RegistryKey, listener: (newValue: T) => void): () => void { return this.events.on("notify_setting_changed", event => { - if(event.setting === key.key && event.mode === "global") { + if (event.setting === key.key && event.mode === "global") { listener(event.newCastedValue); } }) } private async doSave() { - if(this.saveState === "none") { + if (this.saveState === "none") { return; } @@ -1040,7 +1040,7 @@ export class Settings { } catch (error) { logError(LogCategory.GENERAL, tr("Failed to save global settings: %o"), error); } - } while(this.saveState !== "saving"); + } while (this.saveState !== "saving"); this.saveState = "none"; } diff --git a/web/app/dns/resolver.ts b/web/app/dns/resolver.ts index d0f2e851..d5dda873 100644 --- a/web/app/dns/resolver.ts +++ b/web/app/dns/resolver.ts @@ -1,11 +1,11 @@ -import {LogCategory, logError, logTrace, logWarn} from "tc-shared/log"; -import {tr} from "tc-shared/i18n/localize"; -import {default_options, DNSAddress, DNSResolveResult, ResolveOptions} from "tc-shared/dns"; -import {executeDnsRequest, RRType} from "./api"; +import { LogCategory, logError, logTrace, logWarn } from "tc-shared/log"; +import { tr } from "tc-shared/i18n/localize"; +import { default_options, DNSAddress, DNSResolveResult, ResolveOptions } from "tc-shared/dns"; +import { executeDnsRequest, RRType } from "./api"; interface DNSResolveMethod { - name() : string; - resolve(address: DNSAddress) : Promise; + name(): string; + resolve(address: DNSAddress): Promise; } class LocalhostResolver implements DNSResolveMethod { @@ -14,7 +14,7 @@ class LocalhostResolver implements DNSResolveMethod { } async resolve(address: DNSAddress): Promise { - if(address.hostname === "localhost") { + if (address.hostname === "localhost") { return { hostname: "127.0.0.1", port: address.port @@ -26,6 +26,20 @@ class LocalhostResolver implements DNSResolveMethod { } +class FakeResolver implements DNSResolveMethod { + name(): string { + return "fake resolver"; + } + + async resolve(address: DNSAddress): Promise { + return { + hostname: "tea.lp.kle.li", + port: address.port + } + } + +} + class IPResolveMethod implements DNSResolveMethod { readonly v6: boolean; @@ -40,7 +54,7 @@ class IPResolveMethod implements DNSResolveMethod { async resolve(address: DNSAddress): Promise { const answer = await executeDnsRequest(address.hostname, this.v6 ? RRType.AAAA : RRType.A); - if(!answer.length) { + if (!answer.length) { return undefined; } @@ -72,10 +86,10 @@ class SRVResolveMethod implements DNSResolveMethod { async resolve(address: DNSAddress): Promise { const answer = await executeDnsRequest((this.application ? this.application + "." : "") + address.hostname, RRType.SRV); - const records: {[key: number]: ParsedSVRRecord[]} = {}; - for(const record of answer) { + const records: { [key: number]: ParsedSVRRecord[] } = {}; + for (const record of answer) { const parts = record.data.split(" "); - if(parts.length !== 4) { + if (parts.length !== 4) { logWarn(LogCategory.DNS, tr("Failed to parse SRV record %s. Invalid split length."), record); continue; } @@ -84,7 +98,7 @@ class SRVResolveMethod implements DNSResolveMethod { const weight = parseInt(parts[1]); const port = parseInt(parts[2]); - if((priority < 0 || priority > 65535) || (weight < 0 || weight > 65535) || (port < 0 || port > 65535)) { + if ((priority < 0 || priority > 65535) || (weight < 0 || weight > 65535) || (port < 0 || port > 65535)) { logWarn(LogCategory.DNS, tr("Failed to parse SRV record %s. Malformed data."), record); continue; } @@ -99,35 +113,35 @@ class SRVResolveMethod implements DNSResolveMethod { /* get the record with the highest priority */ const priority_strings = Object.keys(records); - if(!priority_strings.length) { + if (!priority_strings.length) { return undefined; } let highestPriority: ParsedSVRRecord[]; - for(const priority_str of priority_strings) { - if(!highestPriority || !highestPriority.length) { + for (const priority_str of priority_strings) { + if (!highestPriority || !highestPriority.length) { highestPriority = records[priority_str]; } - if(highestPriority[0].priority < parseInt(priority_str)) { + if (highestPriority[0].priority < parseInt(priority_str)) { highestPriority = records[priority_str]; } } - if(!highestPriority.length) { + if (!highestPriority.length) { return undefined; } /* select randomly one record */ let record: ParsedSVRRecord; const max_weight = highestPriority.map(e => e.weight).reduce((a, b) => a + b, 0); - if(max_weight == 0) { + if (max_weight == 0) { record = highestPriority[Math.floor(Math.random() * highestPriority.length)]; } else { let rnd = Math.random() * max_weight; - for(let i = 0; i < highestPriority.length; i++) { + for (let i = 0; i < highestPriority.length; i++) { rnd -= highestPriority[i].weight; - if(rnd > 0) { + if (rnd > 0) { continue; } @@ -136,7 +150,7 @@ class SRVResolveMethod implements DNSResolveMethod { } } - if(!record) { + if (!record) { /* shall never happen */ record = highestPriority[0]; } @@ -165,7 +179,7 @@ class SRV_IPResolveMethod implements DNSResolveMethod { async resolve(address: DNSAddress): Promise { const srvAddress = await this.srvResolver.resolve(address); - if(!srvAddress) { + if (!srvAddress) { return undefined; } @@ -190,7 +204,7 @@ class DomainRootResolveMethod implements DNSResolveMethod { async resolve(address: DNSAddress): Promise { const parts = address.hostname.split("."); - if(parts.length < 3) { + if (parts.length < 3) { return undefined; } @@ -203,7 +217,7 @@ class DomainRootResolveMethod implements DNSResolveMethod { class TeaSpeakDNSResolve { readonly address: DNSAddress; - private resolvers: {[key: string]:{ resolver: DNSResolveMethod, after: string[] }} = {}; + private resolvers: { [key: string]: { resolver: DNSResolveMethod, after: string[] } } = {}; private resolving = false; private timeout; @@ -218,15 +232,15 @@ class TeaSpeakDNSResolve { } registerResolver(resolver: DNSResolveMethod, ...after: (string | DNSResolveMethod)[]) { - if(this.resolving) { + if (this.resolving) { throw tr("resolver is already resolving"); } this.resolvers[resolver.name()] = { resolver: resolver, after: after.map(e => typeof e === "string" ? e : e.name()) }; } - resolve(timeout: number) : Promise { - if(this.resolving) { + resolve(timeout: number): Promise { + if (this.resolving) { throw tr("already resolving"); } this.resolving = true; @@ -263,52 +277,53 @@ class TeaSpeakDNSResolve { let invoke_count = 0; _main_loop: - for(const resolver_name of Object.keys(this.resolvers)) { - if(this.resolving_resolvers.findIndex(e => e === resolver_name) !== -1) continue; - if(this.finished_resolvers.findIndex(e => e === resolver_name) !== -1) continue; + for (const resolver_name of Object.keys(this.resolvers)) { + if (this.resolving_resolvers.findIndex(e => e === resolver_name) !== -1) continue; + if (this.finished_resolvers.findIndex(e => e === resolver_name) !== -1) continue; - const resolver = this.resolvers[resolver_name]; - for(const after of resolver.after) - if(this.finished_resolvers.findIndex(e => e === after) === -1) continue _main_loop; + const resolver = this.resolvers[resolver_name]; + for (const after of resolver.after) + if (this.finished_resolvers.findIndex(e => e === after) === -1) continue _main_loop; - invoke_count++; - logTrace(LogCategory.DNS, tr(" Executing resolver %s"), resolver_name); + invoke_count++; + logTrace(LogCategory.DNS, tr(" Executing resolver %s"), resolver_name); - this.resolving_resolvers.push(resolver_name); - resolver.resolver.resolve(this.address).then(result => { - if(!this.resolving || !this.callback_success) return; /* resolve has been finished already */ - this.finished_resolvers.push(resolver_name); + this.resolving_resolvers.push(resolver_name); + resolver.resolver.resolve(this.address).then(result => { + if (!this.resolving || !this.callback_success) return; /* resolve has been finished already */ + this.finished_resolvers.push(resolver_name); - if(!result) { - logTrace(LogCategory.DNS, tr(" Resolver %s returned an empty response."), resolver_name); - this.invoke_resolvers(); - return; - } - - logTrace(LogCategory.DNS, tr(" Successfully resolved address %s:%d to %s:%d via resolver %s"), - this.address.hostname, this.address.port, - result.hostname, result.port, - resolver_name); - this.callback_success(result); - }).catch(error => { - if(!this.resolving || !this.callback_success) return; /* resolve has been finished already */ - this.finished_resolvers.push(resolver_name); - - logTrace(LogCategory.DNS, tr(" Resolver %s ran into an error: %o"), resolver_name, error); + if (!result) { + logTrace(LogCategory.DNS, tr(" Resolver %s returned an empty response."), resolver_name); this.invoke_resolvers(); - }).then(() => { - this.resolving_resolvers.remove(resolver_name); - if(!this.resolving_resolvers.length && this.resolving) - this.invoke_resolvers(); - }); - } + return; + } - if(invoke_count === 0 && !this.resolving_resolvers.length && this.resolving) { + logTrace(LogCategory.DNS, tr(" Successfully resolved address %s:%d to %s:%d via resolver %s"), + this.address.hostname, this.address.port, + result.hostname, result.port, + resolver_name); + this.callback_success(result); + }).catch(error => { + if (!this.resolving || !this.callback_success) return; /* resolve has been finished already */ + this.finished_resolvers.push(resolver_name); + + logTrace(LogCategory.DNS, tr(" Resolver %s ran into an error: %o"), resolver_name, error); + this.invoke_resolvers(); + }).then(() => { + this.resolving_resolvers.remove(resolver_name); + if (!this.resolving_resolvers.length && this.resolving) + this.invoke_resolvers(); + }); + } + + if (invoke_count === 0 && !this.resolving_resolvers.length && this.resolving) { this.callback_fail("no response"); } } } +const kResolverFake = new FakeResolver(); const kResolverLocalhost = new LocalhostResolver(); const kResolverIpV4 = new IPResolveMethod(false); @@ -320,14 +335,16 @@ const resolverSrvTS3 = new SRV_IPResolveMethod(new SRVResolveMethod("_ts3._udp") const resolverDrSrvTS = new DomainRootResolveMethod(resolverSrvTS); const resolverDrSrvTS3 = new DomainRootResolveMethod(resolverSrvTS3); -export async function resolveTeaSpeakServerAddress(address: DNSAddress, _options?: ResolveOptions) : Promise { +export async function resolveTeaSpeakServerAddress(address: DNSAddress, _options?: ResolveOptions): Promise { try { const options = Object.assign({}, default_options); Object.assign(options, _options); const resolver = new TeaSpeakDNSResolve(address); - resolver.registerResolver(kResolverLocalhost); + resolver.registerResolver(kResolverFake); + + resolver.registerResolver(kResolverLocalhost, kResolverFake); resolver.registerResolver(resolverSrvTS, kResolverLocalhost); resolver.registerResolver(resolverSrvTS3, kResolverLocalhost); @@ -340,7 +357,7 @@ export async function resolveTeaSpeakServerAddress(address: DNSAddress, _options resolver.registerResolver(kResolverIpV6, kResolverIpV4); const response = await resolver.resolve(options.timeout || 5000); - if(!response) { + if (!response) { return { status: "empty-result" }; @@ -352,7 +369,7 @@ export async function resolveTeaSpeakServerAddress(address: DNSAddress, _options resolvedAddress: response }; } catch (error) { - if(typeof error !== "string") { + if (typeof error !== "string") { logError(LogCategory.DNS, tr("Failed to resolve %o: %o"), address, error); error = tr("lookup the console"); } @@ -364,9 +381,9 @@ export async function resolveTeaSpeakServerAddress(address: DNSAddress, _options } } -export async function resolveAddressIpV4(address: string) : Promise { +export async function resolveAddressIpV4(address: string): Promise { const result = await executeDnsRequest(address, RRType.A); - if(!result.length) return undefined; + if (!result.length) return undefined; return result[0].data; } \ No newline at end of file