From 13feceb289f36d0d4fb0d6030309f4c37edfece0 Mon Sep 17 00:00:00 2001 From: WolverinDEV Date: Sun, 16 May 2021 12:22:11 +0200 Subject: [PATCH] Fixed the legacy modal icon and bring it back to the front --- shared/js/ConnectionHandler.ts | 58 ++++++++++++--- shared/js/connection/HandshakeHandler.ts | 18 ++++- shared/js/profiles/ConnectionProfile.ts | 3 +- shared/js/settings.ts | 7 ++ shared/js/tree/ChannelDefinitions.ts | 9 +++ shared/js/ui/elements/Modal.ts | 8 +-- shared/js/ui/utils/IpcVariable.ts | 89 ++++++++++++++++++++---- 7 files changed, 159 insertions(+), 33 deletions(-) diff --git a/shared/js/ConnectionHandler.ts b/shared/js/ConnectionHandler.ts index cb1df76b..6f2d0862 100644 --- a/shared/js/ConnectionHandler.ts +++ b/shared/js/ConnectionHandler.ts @@ -39,6 +39,7 @@ import ipRegex from "ip-regex"; import * as htmltags from "./ui/htmltags"; import {ServerSettings} from "tc-shared/ServerSettings"; import {ignorePromise} from "tc-shared/proto"; +import {InvokerInfo} from "tc-shared/tree/ChannelDefinitions"; assertMainApplication(); @@ -68,6 +69,43 @@ export enum DisconnectReason { UNKNOWN } +export type ClientDisconnectInfo = { + reason: "handler-destroy" | "requested" +} | { + reason: "connection-closed" | "connection-ping-timeout" +} | { + reason: "connect-failure" | "connect-dns-fail" | "connect-identity-too-low" +} | { + reason: "connect-identity-unsupported", + unsupportedReason: "ts3-server" +} | { + /* Connect fail since client has been banned */ + reason: "connect-banned", + message: string +} | { + /* Connection got closed because the client got kicked */ + reason: "client-kicked", + message: string, + invoker: InvokerInfo +} | { + /* Connection got closed because the client got banned */ + reason: "client-banned", + message: string, + length: number | 0, + invoker?: InvokerInfo, +} | { + reason: "server-shutdown", + message: string, + /* TODO: Do we have an invoker here? */ +} | { + reason: "connect-host-message-disconnect", + message: string +} | { + reason: "generic-connection-error", + message: string, + allowReconnect: boolean +} + export enum ConnectionState { UNCONNECTED, /* no connection is currenting running */ CONNECTING, /* we try to establish a connection to the target server */ @@ -120,17 +158,6 @@ export interface LocalClientStatus { queries_visible: boolean; } -export interface ConnectParametersOld { - nickname?: string; - channel?: { - target: string | number; - password?: string; - }; - token?: string; - password?: {password: string, hashed: boolean}; - auto_reconnect_attempt?: boolean; -} - export class ConnectionHandler { readonly handlerId: string; @@ -530,6 +557,15 @@ export class ConnectionHandler { return tag; } + /** + * This method dispatches a connection disconnect. + * The method can be called out of every context and will properly terminate + * all resources related to the current connection. + */ + handleDisconnectNew(reason: ClientDisconnectInfo) { + /* TODO: */ + } + private _certificate_modal: Modal; handleDisconnect(type: DisconnectReason, data: any = {}) { this.connectAttemptId++; diff --git a/shared/js/connection/HandshakeHandler.ts b/shared/js/connection/HandshakeHandler.ts index 6b90ec6d..552818ce 100644 --- a/shared/js/connection/HandshakeHandler.ts +++ b/shared/js/connection/HandshakeHandler.ts @@ -4,6 +4,7 @@ import {AbstractServerConnection} from "../connection/ConnectionBase"; import {DisconnectReason} from "../ConnectionHandler"; import {ConnectParameters} from "tc-shared/ui/modal/connect/Controller"; import {getBackend} from "tc-shared/backend"; +import {ErrorCode} from "tc-shared/connection/ErrorCode"; export interface HandshakeIdentityHandler { connection: AbstractServerConnection; @@ -14,6 +15,18 @@ export interface HandshakeIdentityHandler { fillClientInitData(data: any); } +export type ServerHandshakeError = { + reason: "identity-unsupported", +} + +export type ServerHandshakeResult = { + status: "success", + /* TODO: May some other variables as well? */ +} | { + status: "failed", + error: ServerHandshakeError +}; + export class HandshakeHandler { private connection: AbstractServerConnection; private handshakeImpl: HandshakeIdentityHandler; @@ -114,7 +127,7 @@ export class HandshakeHandler { this.handshakeImpl.fillClientInitData(data); this.connection.send_command("clientinit", data).catch(error => { if(error instanceof CommandResult) { - if(error.id == 1028) { + if(error.id == ErrorCode.SERVER_INVALID_PASSWORD) { this.connection.client.handleDisconnect(DisconnectReason.SERVER_REQUIRES_PASSWORD); } else if(error.id == 783 || error.id == 519) { error.extra_message = isNaN(parseInt(error.extra_message)) ? "8" : error.extra_message; @@ -124,8 +137,9 @@ export class HandshakeHandler { } else { this.connection.client.handleDisconnect(DisconnectReason.CLIENT_KICKED, error); } - } else + } else { this.connection.disconnect(); + } }); } } \ No newline at end of file diff --git a/shared/js/profiles/ConnectionProfile.ts b/shared/js/profiles/ConnectionProfile.ts index c8b7f14d..4caa5cdf 100644 --- a/shared/js/profiles/ConnectionProfile.ts +++ b/shared/js/profiles/ConnectionProfile.ts @@ -71,8 +71,9 @@ export class ConnectionProfile { spawnIdentityHandshakeHandler(connection: AbstractServerConnection): HandshakeIdentityHandler | undefined { const identity = this.selectedIdentity(); - if (!identity) + if (!identity) { return undefined; + } return identity.spawn_identity_handshake_handler(connection); } diff --git a/shared/js/settings.ts b/shared/js/settings.ts index a33b176f..70cf46f0 100644 --- a/shared/js/settings.ts +++ b/shared/js/settings.ts @@ -808,6 +808,13 @@ export class Settings { description: "Last used TeaSpeak Client version (TeaClient only)", } + /* When using a higher number clients crash due to a bug in NodeJS */ + static readonly KEY_IPC_EVENT_BUNDLE_MAX_SIZE: ValuedRegistryKey = { + key: "ipc_event_bundle_max_size", + valueType: "number", + defaultValue: 0 + } + static readonly FN_LOG_ENABLED: (category: string) => RegistryKey = category => { return { key: "log." + category.toLowerCase() + ".enabled", diff --git a/shared/js/tree/ChannelDefinitions.ts b/shared/js/tree/ChannelDefinitions.ts index 7aa32fc0..6f92aa7a 100644 --- a/shared/js/tree/ChannelDefinitions.ts +++ b/shared/js/tree/ChannelDefinitions.ts @@ -12,4 +12,13 @@ export type ChannelDescriptionResult = { } | { status: "error", message: string +}; + +/** + * The invoker info the server sends with all notifies if required + */ +export type InvokerInfo = { + invokerName: string, + invokerUniqueId: string, + invokerId: number, }; \ No newline at end of file diff --git a/shared/js/ui/elements/Modal.ts b/shared/js/ui/elements/Modal.ts index 96401b69..bb4033a5 100644 --- a/shared/js/ui/elements/Modal.ts +++ b/shared/js/ui/elements/Modal.ts @@ -2,6 +2,7 @@ import * as loader from "tc-loader"; import {Stage} from "tc-loader"; import $ from "jquery"; import {LogCategory, logError} from "tc-shared/log"; +import ModalIcon from "../react-elements/modal/TeaCup.png"; export enum ElementType { HEADER, @@ -88,8 +89,6 @@ export class ModalProperties { } template_properties?: any = {}; - trigger_tab: boolean = true; - full_size?: boolean = false; } namespace modal { @@ -218,13 +217,14 @@ export class Modal { modal_footer: footer, closeable: this.properties.closeable, - full_size: this.properties.full_size + full_size: false }; if(this.properties.template_properties) Object.assign(properties, this.properties.template_properties); const tag = template.renderTag(properties); + tag.find(".modal-header .container-icon img").attr("src", ModalIcon); if(typeof(this.properties.width) !== "undefined" && typeof(this.properties.min_width) !== "undefined") tag.find(".modal-content") .css("min-width", this.properties.min_width) @@ -258,7 +258,7 @@ export class Modal { _global_modal_last = this.htmlTag[0]; this.shown = true; - this.htmlTag.prependTo($("body")); + this.htmlTag.appendTo($("body")); _global_modal_count++; this.htmlTag.show(); diff --git a/shared/js/ui/utils/IpcVariable.ts b/shared/js/ui/utils/IpcVariable.ts index 5dba20ee..e2c8e602 100644 --- a/shared/js/ui/utils/IpcVariable.ts +++ b/shared/js/ui/utils/IpcVariable.ts @@ -2,16 +2,43 @@ import {UiVariableConsumer, UiVariableMap, UiVariableProvider} from "tc-shared/u import {guid} from "tc-shared/crypto/uid"; import {LogCategory, logWarn} from "tc-shared/log"; import ReactDOM from "react-dom"; +import {Settings, settings} from "tc-shared/settings"; + +/* + * We need to globally bundle all IPC invoke events since + * calling setImmediate too often will cause a electron crash with + * "async hook stack has become corrupted (actual: 88, expected: 0)". + * + * WolverinDEV has never experience it by himself but @REDOSS had. + */ +let ipcInvokeCallbacks: (() => void)[]; + +function registerInvokeCallback(callback: () => void) { + if(Array.isArray(ipcInvokeCallbacks)) { + ipcInvokeCallbacks.push(callback); + } else { + ipcInvokeCallbacks = [ callback ]; + setImmediate(() => { + const callbacks = ipcInvokeCallbacks; + ipcInvokeCallbacks = undefined; + for(const callback of callbacks) { + callback(); + } + }); + } +} export class IpcUiVariableProvider extends UiVariableProvider { readonly ipcChannelId: string; - private broadcastChannel: BroadcastChannel; + private readonly bundleMaxSize: number; + private broadcastChannel: BroadcastChannel; private enqueuedMessages: any[]; constructor() { super(); + this.bundleMaxSize = settings.getValue(Settings.KEY_IPC_EVENT_BUNDLE_MAX_SIZE); this.ipcChannelId = "teaspeak-ipc-vars-" + guid(); this.broadcastChannel = new BroadcastChannel(this.ipcChannelId); this.broadcastChannel.onmessage = event => this.handleIpcMessage(event.data, event.source, event.origin); @@ -96,19 +123,33 @@ export class IpcUiVariableProvider extends UiVa * @private */ private broadcastIpcMessage(message: any) { + if(this.bundleMaxSize <= 0) { + this.broadcastChannel.postMessage(message); + return; + } + if(Array.isArray(this.enqueuedMessages)) { this.enqueuedMessages.push(message); + if(this.enqueuedMessages.length >= this.bundleMaxSize) { + this.sendEnqueuedMessages(); + } return; } this.enqueuedMessages = [ message ]; - setImmediate(() => { - this.broadcastChannel.postMessage({ - type: "bundled", - messages: this.enqueuedMessages - }); - this.enqueuedMessages = undefined; - }) + registerInvokeCallback(() => this.sendEnqueuedMessages()); + } + + private sendEnqueuedMessages() { + if(!this.enqueuedMessages) { + return; + } + + this.broadcastChannel.postMessage({ + type: "bundled", + messages: this.enqueuedMessages + }); + this.enqueuedMessages = undefined; } } @@ -117,8 +158,11 @@ export type IpcVariableDescriptor = { } let editTokenIndex = 0; + class IpcUiVariableConsumer extends UiVariableConsumer { readonly description: IpcVariableDescriptor; + private readonly bundleMaxSize: number; + private broadcastChannel: BroadcastChannel; private editListener: {[key: string]: { resolve: () => void, reject: (error) => void }}; @@ -129,6 +173,7 @@ class IpcUiVariableConsumer extends UiVariableC this.description = description; this.editListener = {}; + this.bundleMaxSize = settings.getValue(Settings.KEY_IPC_EVENT_BUNDLE_MAX_SIZE); this.broadcastChannel = new BroadcastChannel(this.description.ipcChannelId); this.broadcastChannel.onmessage = event => this.handleIpcMessage(event.data, event.source); } @@ -207,19 +252,33 @@ class IpcUiVariableConsumer extends UiVariableC * @private */ private sendIpcMessage(message: any) { + if(this.bundleMaxSize <= 0) { + this.broadcastChannel.postMessage(message); + return; + } + if(Array.isArray(this.enqueuedMessages)) { this.enqueuedMessages.push(message); + if(this.enqueuedMessages.length >= this.bundleMaxSize) { + this.sendEnqueuedMessages(); + } return; } this.enqueuedMessages = [ message ]; - setImmediate(() => { - this.broadcastChannel.postMessage({ - type: "bundled", - messages: this.enqueuedMessages - }); - this.enqueuedMessages = undefined; - }) + registerInvokeCallback(() => this.sendEnqueuedMessages()); + } + + private sendEnqueuedMessages() { + if(!this.enqueuedMessages) { + return; + } + + this.broadcastChannel.postMessage({ + type: "bundled", + messages: this.enqueuedMessages + }); + this.enqueuedMessages = undefined; } }