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