2020-09-12 13:49:20 +00:00
import { AbstractServerConnection } from "./connection/ConnectionBase" ;
import { PermissionManager } from "./permission/PermissionManager" ;
import { GroupManager } from "./permission/GroupManager" ;
2021-04-19 18:26:37 +00:00
import { Settings , settings } from "./settings" ;
2021-03-18 17:25:20 +00:00
import { Sound , SoundManager } from "./audio/Sounds" ;
2021-01-10 16:36:57 +00:00
import { LogCategory , logError , logInfo , logTrace , logWarn } from "./log" ;
2021-03-30 09:20:27 +00:00
import { createErrorModal , createInputModal , Modal } from "./ui/elements/Modal" ;
2020-09-12 13:49:20 +00:00
import { hashPassword } from "./utils/helpers" ;
import { HandshakeHandler } from "./connection/HandshakeHandler" ;
2021-01-10 12:48:53 +00:00
import { FilterMode , InputStartError , InputState } from "./voice/RecorderBase" ;
2020-09-12 13:49:20 +00:00
import { defaultRecorder , RecorderProfile } from "./voice/RecorderProfile" ;
import { formatMessage } from "./ui/frames/chat" ;
import { EventHandler , Registry } from "./events" ;
import { FileManager } from "./file/FileManager" ;
2021-04-24 11:59:49 +00:00
import { tr , tra } from "./i18n/localize" ;
2020-09-12 13:49:20 +00:00
import { guid } from "./crypto/uid" ;
import { PluginCmdRegistry } from "./connection/PluginCmdHandler" ;
import { VoiceConnectionStatus , WhisperSessionInitializeData } from "./connection/VoiceConnection" ;
import { getServerConnectionFactory } from "./connection/ConnectionFactory" ;
import { WhisperSession } from "./voice/VoiceWhisper" ;
import { ServerFeature , ServerFeatures } from "./connection/ServerFeatures" ;
import { ChannelTree } from "./tree/ChannelTree" ;
import { LocalClientEntry } from "./tree/Client" ;
2021-01-10 12:48:53 +00:00
import { parseServerAddress } from "./tree/Server" ;
2020-11-07 12:16:07 +00:00
import { ChannelVideoFrame } from "tc-shared/ui/frames/video/Controller" ;
2020-11-28 14:41:44 +00:00
import { global_client_actions } from "tc-shared/events/GlobalEvents" ;
2020-12-09 12:36:56 +00:00
import { ChannelConversationManager } from "./conversations/ChannelConversationManager" ;
import { PrivateConversationManager } from "tc-shared/conversations/PrivateConversationManager" ;
2020-12-09 19:44:33 +00:00
import { SelectedClientInfo } from "./SelectedClientInfo" ;
import { SideBarManager } from "tc-shared/SideBarManager" ;
2020-12-18 16:06:38 +00:00
import { ServerEventLog } from "tc-shared/connectionlog/ServerEventLog" ;
2020-12-29 15:53:04 +00:00
import { PlaylistManager } from "tc-shared/music/PlaylistManager" ;
2021-01-08 22:51:26 +00:00
import { connectionHistory } from "tc-shared/connectionlog/History" ;
2021-01-10 12:48:53 +00:00
import { ConnectParameters } from "tc-shared/ui/modal/connect/Controller" ;
2021-01-22 15:50:55 +00:00
import { assertMainApplication } from "tc-shared/ui/utils" ;
2021-03-18 11:55:35 +00:00
import { getDNSProvider } from "tc-shared/dns" ;
2021-03-20 18:12:21 +00:00
import { W2GPluginCmdHandler } from "tc-shared/ui/modal/video-viewer/W2GPlugin" ;
2021-04-19 11:27:09 +00:00
import ipRegex from "ip-regex" ;
2021-04-05 18:06:44 +00:00
import * as htmltags from "./ui/htmltags" ;
2021-04-19 18:26:37 +00:00
import { ServerSettings } from "tc-shared/ServerSettings" ;
import { ignorePromise } from "tc-shared/proto" ;
2021-05-16 10:22:11 +00:00
import { InvokerInfo } from "tc-shared/tree/ChannelDefinitions" ;
2021-01-22 15:50:55 +00:00
assertMainApplication ( ) ;
2020-08-19 17:33:57 +00:00
export enum InputHardwareState {
MISSING ,
START_FAILED ,
VALID
}
2020-03-30 11:44:18 +00:00
export enum DisconnectReason {
2019-04-04 19:47:52 +00:00
HANDLER_DESTROYED ,
REQUESTED ,
2019-04-15 13:33:51 +00:00
DNS_FAILED ,
2019-04-04 19:47:52 +00:00
CONNECT_FAILURE ,
CONNECTION_CLOSED ,
CONNECTION_FATAL_ERROR ,
CONNECTION_PING_TIMEOUT ,
CLIENT_KICKED ,
CLIENT_BANNED ,
HANDSHAKE_FAILED ,
2019-04-15 14:58:42 +00:00
HANDSHAKE_TEAMSPEAK_REQUIRED ,
HANDSHAKE_BANNED ,
2019-04-04 19:47:52 +00:00
SERVER_CLOSED ,
SERVER_REQUIRES_PASSWORD ,
2019-08-21 08:00:01 +00:00
SERVER_HOSTMESSAGE ,
2019-04-15 13:33:51 +00:00
IDENTITY_TOO_LOW ,
2019-04-04 19:47:52 +00:00
UNKNOWN
}
2021-05-16 10:22:11 +00:00
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
}
2020-03-30 11:44:18 +00:00
export enum ConnectionState {
2020-04-09 13:10:14 +00:00
UNCONNECTED , /* no connection is currenting running */
CONNECTING , /* we try to establish a connection to the target server */
INITIALISING , /* we're setting up the connection encryption */
2020-07-12 14:31:57 +00:00
AUTHENTICATING , /* we're authenticating our self so we get a unique ID */
2020-04-09 13:10:14 +00:00
CONNECTED , /* we're connected to the server. Server init has been done, may not everything is initialized */
DISCONNECTING /* we're curently disconnecting from the server and awaiting disconnect acknowledge */
}
export namespace ConnectionState {
2020-07-12 14:31:57 +00:00
export function socketConnected ( state : ConnectionState ) {
2020-04-09 13:10:14 +00:00
switch ( state ) {
case ConnectionState . CONNECTED :
case ConnectionState . AUTHENTICATING :
//case ConnectionState.INITIALISING: /* its not yet possible to send any data */
return true ;
default :
return false ;
}
}
2020-07-12 14:31:57 +00:00
export function fullyConnected ( state : ConnectionState ) {
return state === ConnectionState . CONNECTED ;
}
2019-04-04 19:47:52 +00:00
}
2020-03-30 11:44:18 +00:00
export enum ViewReasonId {
2019-04-04 19:47:52 +00:00
VREASON_USER_ACTION = 0 ,
VREASON_MOVED = 1 ,
VREASON_SYSTEM = 2 ,
VREASON_TIMEOUT = 3 ,
VREASON_CHANNEL_KICK = 4 ,
VREASON_SERVER_KICK = 5 ,
VREASON_BAN = 6 ,
VREASON_SERVER_STOPPED = 7 ,
VREASON_SERVER_LEFT = 8 ,
VREASON_CHANNEL_UPDATED = 9 ,
VREASON_EDITED = 10 ,
VREASON_SERVER_SHUTDOWN = 11
}
2020-04-09 13:10:14 +00:00
export interface LocalClientStatus {
2019-04-04 19:47:52 +00:00
input_muted : boolean ;
output_muted : boolean ;
2020-09-07 10:42:00 +00:00
lastChannelCodecWarned : number ,
2019-04-04 19:47:52 +00:00
away : boolean | string ;
channel_subscribe_all : boolean ;
queries_visible : boolean ;
}
2020-03-30 11:44:18 +00:00
export class ConnectionHandler {
2020-07-20 17:08:13 +00:00
readonly handlerId : string ;
2021-01-05 17:13:57 +00:00
private readonly events_ : Registry < ConnectionEvents > ;
2019-04-04 19:47:52 +00:00
channelTree : ChannelTree ;
2020-04-25 18:27:45 +00:00
connection_state : ConnectionState = ConnectionState . UNCONNECTED ;
2020-03-30 11:44:18 +00:00
serverConnection : AbstractServerConnection ;
2021-01-08 22:51:26 +00:00
currentConnectId : number ; /* Id used for the connection history */
2019-04-04 19:47:52 +00:00
fileManager : FileManager ;
permissions : PermissionManager ;
groups : GroupManager ;
2020-11-07 12:16:07 +00:00
video_frame : ChannelVideoFrame ;
2019-04-04 19:47:52 +00:00
settings : ServerSettings ;
2020-03-30 11:44:18 +00:00
sound : SoundManager ;
2019-04-04 19:47:52 +00:00
2020-09-07 10:42:00 +00:00
serverFeatures : ServerFeatures ;
2021-04-05 21:05:44 +00:00
log : ServerEventLog ;
2020-09-07 10:42:00 +00:00
2020-12-09 19:44:33 +00:00
private sideBar : SideBarManager ;
2020-12-29 15:53:04 +00:00
private playlistManager : PlaylistManager ;
2020-12-09 19:44:33 +00:00
2020-12-09 12:36:56 +00:00
private channelConversations : ChannelConversationManager ;
private privateConversations : PrivateConversationManager ;
2020-12-09 19:44:33 +00:00
private clientInfoManager : SelectedClientInfo ;
2021-01-05 17:13:57 +00:00
private localClientId : number = 0 ;
2020-11-07 12:16:07 +00:00
private localClient : LocalClientEntry ;
2019-10-19 15:13:40 +00:00
2021-01-05 17:13:57 +00:00
private autoReconnectTimer : number ;
2021-04-19 18:26:37 +00:00
private isReconnectAttempt : boolean ;
2019-10-19 15:13:40 +00:00
2021-01-05 17:13:57 +00:00
private connectAttemptId : number = 1 ;
2020-09-07 10:42:00 +00:00
private echoTestRunning = false ;
2019-04-04 19:47:52 +00:00
2020-08-07 11:40:11 +00:00
private pluginCmdRegistry : PluginCmdRegistry ;
2021-04-05 21:05:44 +00:00
private handlerState : LocalClientStatus = {
2019-04-04 19:47:52 +00:00
input_muted : false ,
2020-08-19 17:33:57 +00:00
2019-04-04 19:47:52 +00:00
output_muted : false ,
away : false ,
channel_subscribe_all : true ,
queries_visible : false ,
2020-09-07 10:42:00 +00:00
lastChannelCodecWarned : - 1
2019-04-04 19:47:52 +00:00
} ;
2020-11-17 13:35:26 +00:00
private clientStatusSync = false ;
2019-04-04 19:47:52 +00:00
2020-08-19 17:33:57 +00:00
private inputHardwareState : InputHardwareState = InputHardwareState . MISSING ;
2021-01-08 20:30:48 +00:00
private listenerRecorderInputDeviceChanged : ( ( ) = > void ) ;
2020-08-19 17:33:57 +00:00
2019-04-04 19:47:52 +00:00
constructor ( ) {
2020-07-20 17:08:13 +00:00
this . handlerId = guid ( ) ;
2021-01-05 17:13:57 +00:00
this . events_ = new Registry < ConnectionEvents > ( ) ;
this . events_ . enableDebug ( "connection-handler" ) ;
2020-04-09 13:10:14 +00:00
2019-04-04 19:47:52 +00:00
this . settings = new ServerSettings ( ) ;
2020-08-10 12:41:34 +00:00
this . serverConnection = getServerConnectionFactory ( ) . create ( this ) ;
2021-04-05 21:05:44 +00:00
this . serverConnection . events . on ( "notify_connection_state_changed" , event = > {
logTrace ( LogCategory . CLIENT , tr ( "From %s to %s" ) , ConnectionState [ event . oldState ] , ConnectionState [ event . newState ] ) ;
this . events_ . fire ( "notify_connection_state_changed" , {
oldState : event.oldState ,
newState : event.newState
} ) ;
} ) ;
2020-08-10 12:41:34 +00:00
2021-01-08 20:30:48 +00:00
this . serverConnection . getVoiceConnection ( ) . events . on ( "notify_recorder_changed" , event = > {
2020-08-19 17:33:57 +00:00
this . setInputHardwareState ( this . getVoiceRecorder ( ) ? InputHardwareState.VALID : InputHardwareState.MISSING ) ;
2021-01-08 20:30:48 +00:00
this . updateVoiceStatus ( ) ;
if ( this . listenerRecorderInputDeviceChanged ) {
this . listenerRecorderInputDeviceChanged ( ) ;
this . listenerRecorderInputDeviceChanged = undefined ;
}
if ( event . newRecorder ) {
this . listenerRecorderInputDeviceChanged = event . newRecorder . input ? . events . on ( "notify_device_changed" , ( ) = > {
this . setInputHardwareState ( InputHardwareState . VALID ) ;
this . updateVoiceStatus ( ) ;
} ) ;
}
2020-08-19 17:33:57 +00:00
} ) ;
2020-11-17 12:10:24 +00:00
this . serverConnection . getVoiceConnection ( ) . events . on ( "notify_connection_status_changed" , ( ) = > this . update_voice_status ( ) ) ;
2020-08-26 10:33:53 +00:00
this . serverConnection . getVoiceConnection ( ) . setWhisperSessionInitializer ( this . initializeWhisperSession . bind ( this ) ) ;
2020-09-07 10:42:00 +00:00
this . serverFeatures = new ServerFeatures ( this ) ;
2020-09-26 19:34:46 +00:00
this . groups = new GroupManager ( this ) ;
2020-09-07 10:42:00 +00:00
2020-07-12 14:31:57 +00:00
this . channelTree = new ChannelTree ( this ) ;
2019-04-04 19:47:52 +00:00
this . fileManager = new FileManager ( this ) ;
this . permissions = new PermissionManager ( this ) ;
2020-12-29 15:53:04 +00:00
this . playlistManager = new PlaylistManager ( this ) ;
2020-07-12 14:31:57 +00:00
2020-12-09 19:44:33 +00:00
this . sideBar = new SideBarManager ( this ) ;
2020-12-09 12:36:56 +00:00
this . privateConversations = new PrivateConversationManager ( this ) ;
this . channelConversations = new ChannelConversationManager ( this ) ;
2020-12-09 19:44:33 +00:00
this . clientInfoManager = new SelectedClientInfo ( this ) ;
2020-12-09 12:36:56 +00:00
2020-08-07 11:40:11 +00:00
this . pluginCmdRegistry = new PluginCmdRegistry ( this ) ;
2020-11-07 12:16:07 +00:00
this . video_frame = new ChannelVideoFrame ( this ) ;
2020-08-07 11:40:11 +00:00
2020-07-21 22:55:28 +00:00
this . log = new ServerEventLog ( this ) ;
2020-07-12 14:31:57 +00:00
this . sound = new SoundManager ( this ) ;
2019-08-21 08:00:01 +00:00
2020-11-07 12:16:07 +00:00
this . localClient = new LocalClientEntry ( this ) ;
this . localClient . channelTree = this . channelTree ;
2019-04-04 19:47:52 +00:00
2021-01-22 12:34:43 +00:00
this . events_ . registerHandler ( this ) ;
2020-08-07 11:40:11 +00:00
this . pluginCmdRegistry . registerHandler ( new W2GPluginCmdHandler ( ) ) ;
2020-12-09 12:36:56 +00:00
2021-01-05 17:13:57 +00:00
this . events_ . fire ( "notify_handler_initialized" ) ;
2020-04-09 13:10:14 +00:00
}
2021-04-05 21:05:44 +00:00
initializeHandlerState ( source? : ConnectionHandler ) {
if ( source ) {
this . handlerState . input_muted = source . handlerState . input_muted ;
this . handlerState . output_muted = source . handlerState . output_muted ;
this . update_voice_status ( ) ;
this . setAway ( source . handlerState . away ) ;
this . setQueriesShown ( source . handlerState . queries_visible ) ;
this . setSubscribeToAllChannels ( source . handlerState . channel_subscribe_all ) ;
/* Ignore lastChannelCodecWarned */
} else {
this . handlerState . input_muted = settings . getValue ( Settings . KEY_CLIENT_STATE_MICROPHONE_MUTED ) ;
this . handlerState . output_muted = settings . getValue ( Settings . KEY_CLIENT_STATE_SPEAKER_MUTED ) ;
this . update_voice_status ( ) ;
2020-04-09 13:10:14 +00:00
2021-04-05 21:05:44 +00:00
this . setSubscribeToAllChannels ( settings . getValue ( Settings . KEY_CLIENT_STATE_SUBSCRIBE_ALL_CHANNELS ) ) ;
this . doSetAway ( settings . getValue ( Settings . KEY_CLIENT_STATE_AWAY ) ? settings . getValue ( Settings . KEY_CLIENT_AWAY_MESSAGE ) : false , false ) ;
this . setQueriesShown ( settings . getValue ( Settings . KEY_CLIENT_STATE_QUERY_SHOWN ) ) ;
}
2020-04-09 13:10:14 +00:00
}
events ( ) : Registry < ConnectionEvents > {
2021-01-05 17:13:57 +00:00
return this . events_ ;
2019-08-21 08:00:01 +00:00
}
2021-04-19 18:26:37 +00:00
async startConnectionNew ( parameters : ConnectParameters , isReconnectAttempt : boolean ) {
2021-01-10 12:48:53 +00:00
this . cancelAutoReconnect ( true ) ;
2021-04-19 18:26:37 +00:00
this . isReconnectAttempt = isReconnectAttempt ;
2020-08-19 17:33:57 +00:00
this . handleDisconnect ( DisconnectReason . REQUESTED ) ;
2019-04-04 19:47:52 +00:00
2021-01-10 12:48:53 +00:00
const localConnectionAttemptId = ++ this . connectAttemptId ;
const parsedAddress = parseServerAddress ( parameters . targetAddress ) ;
const resolvedAddress = Object . assign ( { } , parsedAddress ) ;
2020-12-18 16:06:38 +00:00
this . log . log ( "connection.begin" , {
2019-07-09 22:52:08 +00:00
address : {
2021-01-10 12:48:53 +00:00
server_hostname : parsedAddress.host ,
server_port : parsedAddress.port
2019-07-09 22:52:08 +00:00
} ,
client_nickname : parameters.nickname
} ) ;
2019-06-19 13:56:51 +00:00
2021-02-19 22:05:48 +00:00
this . channelTree . initialiseHead ( parameters . targetAddress , parsedAddress ) ;
2021-01-10 12:48:53 +00:00
/* hash the password if not already hashed */
2021-02-19 22:05:48 +00:00
if ( parameters . serverPassword && ! parameters . serverPasswordHashed ) {
2019-06-19 13:56:51 +00:00
try {
2021-02-19 22:05:48 +00:00
parameters . serverPassword = await hashPassword ( parameters . serverPassword ) ;
parameters . serverPasswordHashed = true ;
2019-06-19 13:56:51 +00:00
} catch ( error ) {
2021-01-10 12:48:53 +00:00
logError ( LogCategory . CLIENT , tr ( "Failed to hash connect password: %o" ) , error ) ;
2019-06-19 13:56:51 +00:00
createErrorModal ( tr ( "Error while hashing password" ) , tr ( "Failed to hash server password!<br>" ) + error ) . open ( ) ;
2021-02-19 22:05:48 +00:00
/* FIXME: Abort connection attempt */
}
2021-01-10 12:48:53 +00:00
2021-02-19 22:05:48 +00:00
if ( this . connectAttemptId !== localConnectionAttemptId ) {
/* Our attempt has been aborted */
return ;
}
}
if ( parameters . defaultChannelPassword && ! parameters . defaultChannelPasswordHashed ) {
try {
parameters . defaultChannelPassword = await hashPassword ( parameters . defaultChannelPassword ) ;
parameters . defaultChannelPasswordHashed = true ;
} catch ( error ) {
logError ( LogCategory . CLIENT , tr ( "Failed to hash channel password: %o" ) , error ) ;
createErrorModal ( tr ( "Error while hashing password" ) , tr ( "Failed to hash channel password!<br>" ) + error ) . open ( ) ;
2021-01-10 12:48:53 +00:00
/* FIXME: Abort connection attempt */
}
if ( this . connectAttemptId !== localConnectionAttemptId ) {
/* Our attempt has been aborted */
return ;
2019-06-19 13:56:51 +00:00
}
2019-08-21 08:00:01 +00:00
}
2019-04-15 13:33:51 +00:00
2021-04-19 11:27:09 +00:00
if ( ipRegex ( { exact : true } ) . test ( resolvedAddress . host ) ) {
2021-01-10 12:48:53 +00:00
/* We don't have to resolve the target host */
2021-03-18 11:55:35 +00:00
} else {
2020-12-18 16:06:38 +00:00
this . log . log ( "connection.hostname.resolve" , { } ) ;
2019-06-19 13:56:51 +00:00
try {
2021-03-18 11:55:35 +00:00
const resolved = await getDNSProvider ( ) . resolveAddress ( { hostname : parsedAddress.host , port : parsedAddress.port } , { timeout : 5000 } ) ;
2021-01-10 12:48:53 +00:00
if ( this . connectAttemptId !== localConnectionAttemptId ) {
/* Our attempt has been aborted */
return ;
}
2021-03-18 11:55:35 +00:00
if ( resolved . status === "empty-result" ) {
throw tr ( "address resolve result empty" ) ;
} else if ( resolved . status === "error" ) {
throw resolved . message ;
}
resolvedAddress . host = resolved . resolvedAddress . hostname ;
resolvedAddress . port = resolved . resolvedAddress . port ;
if ( typeof resolvedAddress . port !== "number" ) {
resolvedAddress . port = parsedAddress . port ;
2021-01-10 12:48:53 +00:00
}
2019-04-15 13:33:51 +00:00
2020-12-18 16:06:38 +00:00
this . log . log ( "connection.hostname.resolved" , {
2019-07-09 22:52:08 +00:00
address : {
2021-01-08 22:51:26 +00:00
server_port : resolvedAddress.port ,
server_hostname : resolvedAddress.host
2019-07-09 22:52:08 +00:00
}
} ) ;
2019-06-19 13:56:51 +00:00
} catch ( error ) {
2021-01-10 12:48:53 +00:00
if ( this . connectAttemptId !== localConnectionAttemptId ) {
/* Our attempt has been aborted */
return ;
}
2019-04-15 13:33:51 +00:00
this . handleDisconnect ( DisconnectReason . DNS_FAILED , error ) ;
2021-02-19 22:05:48 +00:00
return ;
2019-06-19 13:56:51 +00:00
}
2019-04-04 19:47:52 +00:00
}
2019-06-19 13:56:51 +00:00
2021-04-19 18:26:37 +00:00
if ( this . isReconnectAttempt ) {
2021-01-10 12:48:53 +00:00
/* this.currentConnectId = 0; */
/* Reconnect attempts are connecting to the last server. No need to update the general attempt id */
2021-01-08 22:51:26 +00:00
} else {
2021-01-10 12:48:53 +00:00
this . currentConnectId = await connectionHistory . logConnectionAttempt ( {
nickname : parameters.nicknameSpecified ? parameters.nickname : undefined ,
2021-02-19 22:05:48 +00:00
hashedPassword : parameters.serverPassword , /* Password will be hashed by now! */
2021-01-10 12:48:53 +00:00
targetAddress : parameters.targetAddress ,
} ) ;
2021-01-08 22:51:26 +00:00
}
2021-01-10 12:48:53 +00:00
await this . serverConnection . connect ( resolvedAddress , new HandshakeHandler ( parameters ) ) ;
}
2021-01-08 22:51:26 +00:00
2020-04-09 13:10:14 +00:00
async disconnectFromServer ( reason? : string ) {
2021-01-10 12:48:53 +00:00
this . cancelAutoReconnect ( true ) ;
2021-04-05 21:05:44 +00:00
if ( ! this . connected ) {
return ;
}
2020-04-10 18:57:50 +00:00
2020-08-19 17:33:57 +00:00
this . handleDisconnect ( DisconnectReason . REQUESTED ) ;
2020-04-09 13:10:14 +00:00
try {
await this . serverConnection . disconnect ( ) ;
} catch ( error ) {
2021-01-10 16:36:57 +00:00
logWarn ( LogCategory . CLIENT , tr ( "Failed to successfully disconnect from server: {}" ) , error ) ;
2020-04-09 13:10:14 +00:00
}
this . sound . play ( Sound . CONNECTION_DISCONNECTED ) ;
2020-12-18 16:06:38 +00:00
this . log . log ( "disconnected" , { } ) ;
2020-04-09 13:10:14 +00:00
}
2019-04-04 19:47:52 +00:00
2020-11-07 12:16:07 +00:00
getClient ( ) : LocalClientEntry { return this . localClient ; }
2021-01-05 17:13:57 +00:00
getClientId() { return this . localClientId ; }
2019-04-04 19:47:52 +00:00
2020-12-09 12:36:56 +00:00
getPrivateConversations ( ) : PrivateConversationManager {
return this . privateConversations ;
}
getChannelConversations ( ) : ChannelConversationManager {
return this . channelConversations ;
}
2020-12-09 19:44:33 +00:00
getSelectedClientInfo ( ) : SelectedClientInfo {
return this . clientInfoManager ;
}
getSideBar ( ) : SideBarManager {
return this . sideBar ;
}
2020-12-29 15:53:04 +00:00
getPlaylistManager ( ) : PlaylistManager {
return this . playlistManager ;
}
2020-06-12 17:33:05 +00:00
initializeLocalClient ( clientId : number , acceptedName : string ) {
2021-01-05 17:13:57 +00:00
this . localClientId = clientId ;
2020-11-07 12:16:07 +00:00
this . localClient [ "_clientId" ] = clientId ;
2019-04-04 19:47:52 +00:00
2020-11-07 12:16:07 +00:00
this . channelTree . registerClient ( this . localClient ) ;
this . localClient . updateVariables ( { key : "client_nickname" , value : acceptedName } ) ;
2019-04-04 19:47:52 +00:00
}
2020-03-30 11:44:18 +00:00
getServerConnection ( ) : AbstractServerConnection { return this . serverConnection ; }
2019-04-04 19:47:52 +00:00
2020-04-09 13:10:14 +00:00
@EventHandler < ConnectionEvents > ( "notify_connection_state_changed" )
2020-08-19 17:33:57 +00:00
private handleConnectionStateChanged ( event : ConnectionEvents [ "notify_connection_state_changed" ] ) {
2020-12-09 12:36:56 +00:00
this . connection_state = event . newState ;
if ( event . newState === ConnectionState . CONNECTED ) {
2021-01-08 20:30:48 +00:00
logInfo ( LogCategory . CLIENT , tr ( "Client connected" ) ) ;
2020-12-18 16:06:38 +00:00
this . log . log ( "connection.connected" , {
2020-08-19 17:33:57 +00:00
serverAddress : {
server_port : this.channelTree.server.remote_address.port ,
server_hostname : this.channelTree.server.remote_address.host
} ,
serverName : this.channelTree.server.properties.virtualserver_name ,
own_client : this.getClient ( ) . log_data ( )
} ) ;
this . sound . play ( Sound . CONNECTION_CONNECTED ) ;
2020-04-11 09:43:41 +00:00
2020-08-19 17:33:57 +00:00
this . permissions . requestPermissionList ( ) ;
2020-11-17 13:35:26 +00:00
/ *
There is no need to request the server groups since they must be send by the server
2020-11-28 18:33:54 +00:00
if ( this . groups . serverGroups . length == 0 ) {
2020-08-19 17:33:57 +00:00
this . groups . requestGroups ( ) ;
2020-11-28 18:33:54 +00:00
}
2020-11-17 13:35:26 +00:00
* /
2019-04-04 19:47:52 +00:00
2021-04-19 18:26:37 +00:00
this . settings . setServerUniqueId ( this . channelTree . server . properties . virtualserver_unique_identifier ) ;
2019-04-04 19:47:52 +00:00
2020-08-19 17:33:57 +00:00
/* apply the server settings */
2021-04-05 21:05:44 +00:00
if ( this . handlerState . channel_subscribe_all ) {
2020-08-19 17:33:57 +00:00
this . channelTree . subscribe_all_channels ( ) ;
2020-11-28 18:33:54 +00:00
} else {
2020-08-19 17:33:57 +00:00
this . channelTree . unsubscribe_all_channels ( ) ;
2020-11-28 18:33:54 +00:00
}
2021-04-05 21:05:44 +00:00
this . channelTree . toggle_server_queries ( this . handlerState . queries_visible ) ;
2020-08-19 17:33:57 +00:00
this . sync_status_with_server ( ) ;
this . channelTree . server . updateProperties ( ) ;
/ *
No need to update the voice stuff because as soon we see ourself we ' re doing it
this . update_voice_status ( ) ;
if ( control_bar . current_connection_handler ( ) === this )
control_bar . apply_server_voice_state ( ) ;
* /
2020-09-07 10:42:00 +00:00
2021-01-10 15:13:15 +00:00
if ( __build . target === "web" && settings . getValue ( Settings . KEY_VOICE_ECHO_TEST_ENABLED ) ) {
2020-09-25 08:46:42 +00:00
this . serverFeatures . awaitFeatures ( ) . then ( result = > {
if ( ! result ) {
return ;
}
2020-11-28 18:33:54 +00:00
2020-09-25 08:46:42 +00:00
if ( this . serverFeatures . supportsFeature ( ServerFeature . WHISPER_ECHO ) ) {
2020-11-28 14:41:44 +00:00
global_client_actions . fire ( "action_open_window" , { window : "server-echo-test" , connection : this } ) ;
2020-09-25 08:46:42 +00:00
}
} ) ;
}
2020-08-19 17:33:57 +00:00
} else {
this . setInputHardwareState ( this . getVoiceRecorder ( ) ? InputHardwareState.VALID : InputHardwareState.MISSING ) ;
}
2019-04-04 19:47:52 +00:00
}
get connected ( ) : boolean {
return this . serverConnection && this . serverConnection . connected ( ) ;
}
2021-03-18 11:55:35 +00:00
private generate_ssl_certificate_accept ( ) : HTMLAnchorElement {
2019-04-04 19:47:52 +00:00
const properties = {
connect_default : true ,
2021-01-10 12:48:53 +00:00
connect_profile : this.serverConnection.handshake_handler ( ) . parameters . profile . id ,
2019-04-15 13:33:51 +00:00
connect_address : this.serverConnection.remote_address ( ) . host + ( this . serverConnection . remote_address ( ) . port !== 9987 ? ":" + this . serverConnection . remote_address ( ) . port : "" )
2019-04-04 19:47:52 +00:00
} ;
2019-09-01 15:24:06 +00:00
const build_url = ( base : string , search : string , props : any ) = > {
2019-04-04 19:47:52 +00:00
const parameters : string [ ] = [ ] ;
2021-03-18 11:55:35 +00:00
for ( const key of Object . keys ( props ) ) {
2019-04-04 19:47:52 +00:00
parameters . push ( key + "=" + encodeURIComponent ( props [ key ] ) ) ;
2021-03-18 11:55:35 +00:00
}
2019-04-04 19:47:52 +00:00
2019-09-01 15:24:06 +00:00
let callback = base + search ; /* don't use document.URL because it may contains a #! */
2021-03-18 11:55:35 +00:00
if ( ! search ) {
2019-04-04 19:47:52 +00:00
callback += "?" + parameters . join ( "&" ) ;
2021-03-18 11:55:35 +00:00
} else {
2019-04-04 19:47:52 +00:00
callback += "&" + parameters . join ( "&" ) ;
2021-03-18 11:55:35 +00:00
}
2019-04-04 19:47:52 +00:00
2019-04-15 13:33:51 +00:00
return "https://" + this . serverConnection . remote_address ( ) . host + ":" + this . serverConnection . remote_address ( ) . port + "/?forward_url=" + encodeURIComponent ( callback ) ;
2019-04-04 19:47:52 +00:00
} ;
/* generate the tag */
2021-03-18 11:55:35 +00:00
const tag = document . createElement ( "a" ) ;
tag . text = tr ( "here" ) ;
2019-04-04 19:47:52 +00:00
2019-11-06 11:38:33 +00:00
let pathname = document . location . pathname ;
if ( pathname . endsWith ( ".php" ) )
pathname = pathname . substring ( 0 , pathname . lastIndexOf ( "/" ) ) ;
2021-03-18 11:55:35 +00:00
tag . href = build_url ( document . location . origin + pathname , document . location . search , properties ) ;
2019-04-04 19:47:52 +00:00
return tag ;
}
2021-05-16 10:22:11 +00:00
/ * *
* 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: */
}
2019-04-04 19:47:52 +00:00
private _certificate_modal : Modal ;
handleDisconnect ( type : DisconnectReason , data : any = { } ) {
2021-01-05 17:13:57 +00:00
this . connectAttemptId ++ ;
2019-04-15 13:33:51 +00:00
2021-01-10 12:48:53 +00:00
let autoReconnect = false ;
2019-04-04 19:47:52 +00:00
switch ( type ) {
case DisconnectReason . REQUESTED :
2019-08-21 08:00:01 +00:00
case DisconnectReason . SERVER_HOSTMESSAGE : /* already handled */
2019-04-04 19:47:52 +00:00
break ;
case DisconnectReason . HANDLER_DESTROYED :
2019-09-12 21:59:35 +00:00
if ( data ) {
2019-04-04 19:47:52 +00:00
this . sound . play ( Sound . CONNECTION_DISCONNECTED ) ;
2020-12-18 16:06:38 +00:00
this . log . log ( "disconnected" , { } ) ;
2019-09-12 21:59:35 +00:00
}
2019-04-04 19:47:52 +00:00
break ;
2019-04-15 13:33:51 +00:00
case DisconnectReason . DNS_FAILED :
2021-01-10 16:36:57 +00:00
logError ( LogCategory . CLIENT , tr ( "Failed to resolve hostname: %o" ) , data ) ;
2020-12-18 16:06:38 +00:00
this . log . log ( "connection.hostname.resolve.error" , {
2019-07-09 22:52:08 +00:00
message : data as any
} ) ;
2019-04-15 13:33:51 +00:00
this . sound . play ( Sound . CONNECTION_REFUSED ) ;
break ;
2019-04-04 19:47:52 +00:00
case DisconnectReason . CONNECT_FAILURE :
2021-04-19 18:26:37 +00:00
if ( this . isReconnectAttempt ) {
2021-01-10 12:48:53 +00:00
autoReconnect = true ;
2019-04-04 19:47:52 +00:00
break ;
}
2020-07-23 22:29:36 +00:00
2020-12-17 10:55:53 +00:00
if ( data ) {
2021-01-10 16:36:57 +00:00
logError ( LogCategory . CLIENT , tr ( "Could not connect to remote host! Extra data: %o" ) , data ) ;
2020-12-17 10:55:53 +00:00
} else {
2021-01-10 16:36:57 +00:00
logError ( LogCategory . CLIENT , tr ( "Could not connect to remote host!" ) , data ) ;
2020-12-17 10:55:53 +00:00
}
2019-04-04 19:47:52 +00:00
2021-03-18 11:55:35 +00:00
if ( __build . target === "client" ) {
2019-04-04 19:47:52 +00:00
createErrorModal (
tr ( "Could not connect" ) ,
tr ( "Could not connect to remote host (Connection refused)" )
) . open ( ) ;
} else {
2020-07-23 22:29:36 +00:00
const generateAddressPart = ( ) = > Math . floor ( Math . random ( ) * 256 ) ;
const addressParts = [ generateAddressPart ( ) , generateAddressPart ( ) , generateAddressPart ( ) , generateAddressPart ( ) ] ;
2021-03-18 11:55:35 +00:00
getDNSProvider ( ) . resolveAddressIPv4 ( {
hostname : addressParts.join ( "-" ) + ".con-gate.work" ,
port : 9987
} , { timeout : 5000 } ) . then ( async result = > {
if ( result . status === "empty-result" ) {
throw tr ( "empty result" ) ;
} else if ( result . status === "error" ) {
throw result . message ;
}
if ( result . resolvedAddress . hostname !== addressParts . join ( "." ) ) {
2020-07-23 22:29:36 +00:00
throw "miss matching address" ;
2021-03-18 11:55:35 +00:00
}
2020-07-23 22:29:36 +00:00
createErrorModal (
tr ( "Could not connect" ) ,
tr ( "Could not connect to remote host (Connection refused)" )
) . open ( ) ;
} ) . catch ( ( ) = > {
const error_message_format =
"Could not connect to remote host (Connection refused)\n" +
"If you're sure that the remote host is up, than you may not allow unsigned certificates or that con-gate.work works properly.\n" +
"Click {0} to accept the remote certificate" ;
this . _certificate_modal = createErrorModal (
tr ( "Could not connect" ) ,
formatMessage ( /* @tr-ignore */ tr ( error_message_format ) , this . generate_ssl_certificate_accept ( ) )
) ;
this . _certificate_modal . close_listener . push ( ( ) = > this . _certificate_modal = undefined ) ;
this . _certificate_modal . open ( ) ;
} ) ;
2019-04-04 19:47:52 +00:00
}
2020-12-18 16:06:38 +00:00
this . log . log ( "connection.failed" , {
2020-12-17 10:55:53 +00:00
serverAddress : {
server_hostname : this.serverConnection.remote_address ( ) . host ,
server_port : this.serverConnection.remote_address ( ) . port
}
} ) ;
2019-04-04 19:47:52 +00:00
this . sound . play ( Sound . CONNECTION_REFUSED ) ;
break ;
case DisconnectReason . HANDSHAKE_FAILED :
//TODO sound
2021-01-10 16:36:57 +00:00
logError ( LogCategory . CLIENT , tr ( "Failed to process handshake: %o" ) , data ) ;
2019-04-04 19:47:52 +00:00
createErrorModal (
tr ( "Could not connect" ) ,
tr ( "Failed to process handshake: " ) + data as string
) . open ( ) ;
break ;
2019-04-15 14:58:42 +00:00
case DisconnectReason . HANDSHAKE_TEAMSPEAK_REQUIRED :
createErrorModal (
tr ( "Target server is a TeamSpeak server" ) ,
2021-04-19 11:27:09 +00:00
tr ( "The target server is a TeamSpeak 3 server!\nOnly TeamSpeak 3 based identities are able to connect.\nPlease select another profile or change the identify type." )
2019-04-15 14:58:42 +00:00
) . open ( ) ;
this . sound . play ( Sound . CONNECTION_DISCONNECTED ) ;
2021-01-10 12:48:53 +00:00
autoReconnect = false ;
2019-04-15 14:58:42 +00:00
break ;
2019-04-15 13:33:51 +00:00
case DisconnectReason . IDENTITY_TOO_LOW :
createErrorModal (
tr ( "Identity level is too low" ) ,
2021-04-24 11:59:49 +00:00
formatMessage ( tr ( "You've been disconnected, because your Identity level is too low.\nYou need at least a level of {0}" ) , data [ "extra_message" ] )
2019-04-15 13:33:51 +00:00
) . open ( ) ;
this . sound . play ( Sound . CONNECTION_DISCONNECTED ) ;
2021-01-10 12:48:53 +00:00
autoReconnect = false ;
2019-04-15 13:33:51 +00:00
break ;
2019-04-04 19:47:52 +00:00
case DisconnectReason . CONNECTION_CLOSED :
2021-01-10 16:36:57 +00:00
logError ( LogCategory . CLIENT , tr ( "Lost connection to remote server!" ) ) ;
2021-04-19 18:26:37 +00:00
if ( ! this . isReconnectAttempt ) {
2019-10-19 15:13:40 +00:00
createErrorModal (
tr ( "Connection closed" ) ,
tr ( "The connection was closed by remote host" )
) . open ( ) ;
2021-04-19 18:26:37 +00:00
this . sound . play ( Sound . CONNECTION_DISCONNECTED ) ;
2019-10-19 15:13:40 +00:00
}
2019-04-04 19:47:52 +00:00
2021-01-10 12:48:53 +00:00
autoReconnect = true ;
2019-04-04 19:47:52 +00:00
break ;
case DisconnectReason . CONNECTION_PING_TIMEOUT :
2021-01-10 16:36:57 +00:00
logError ( LogCategory . CLIENT , tr ( "Connection ping timeout" ) ) ;
2019-04-04 19:47:52 +00:00
this . sound . play ( Sound . CONNECTION_DISCONNECTED_TIMEOUT ) ;
createErrorModal (
tr ( "Connection lost" ) ,
tr ( "Lost connection to remote host (Ping timeout)<br>Even possible?" )
) . open ( ) ;
2021-04-19 18:26:37 +00:00
autoReconnect = true ;
2019-04-04 19:47:52 +00:00
break ;
case DisconnectReason . SERVER_CLOSED :
2020-12-18 16:06:38 +00:00
this . log . log ( "server.closed" , { message : data.reasonmsg } ) ;
2019-10-19 15:13:40 +00:00
2019-04-04 19:47:52 +00:00
createErrorModal (
tr ( "Server closed" ) ,
"The server is closed.<br>" + //TODO tr
"Reason: " + data . reasonmsg
) . open ( ) ;
this . sound . play ( Sound . CONNECTION_DISCONNECTED ) ;
2021-01-10 12:48:53 +00:00
autoReconnect = true ;
2019-04-04 19:47:52 +00:00
break ;
case DisconnectReason . SERVER_REQUIRES_PASSWORD :
2020-12-18 16:06:38 +00:00
this . log . log ( "server.requires.password" , { } ) ;
2019-08-21 08:00:01 +00:00
2021-04-19 18:26:37 +00:00
const reconnectParameters = this . generateReconnectParameters ( ) ;
createInputModal ( tr ( "Server password" ) , tr ( "Enter server password:" ) , password = > password . length != 0 , password = > {
2021-01-10 12:48:53 +00:00
if ( typeof password !== "string" ) {
return ;
}
2019-06-19 13:56:51 +00:00
2021-04-19 18:26:37 +00:00
reconnectParameters . serverPassword = password ;
reconnectParameters . serverPasswordHashed = false ;
ignorePromise ( this . startConnectionNew ( reconnectParameters , false ) ) ;
2019-04-04 19:47:52 +00:00
} ) . open ( ) ;
break ;
case DisconnectReason . CLIENT_KICKED :
2019-10-24 16:37:12 +00:00
const have_invoker = typeof ( data [ "invokerid" ] ) !== "undefined" && parseInt ( data [ "invokerid" ] ) !== 0 ;
const modal = createErrorModal (
2019-10-19 15:13:40 +00:00
tr ( "You've been kicked" ) ,
2020-03-30 11:44:18 +00:00
formatMessage (
2021-04-24 11:59:49 +00:00
have_invoker ? tr ( "You've been kicked from the server by {0}:\n{1}" ) : tr ( "You've been kicked from the server:\n{1}" ) ,
2019-10-24 16:37:12 +00:00
have_invoker ?
htmltags . generate_client_object ( { client_id : parseInt ( data [ "invokerid" ] ) , client_unique_id : data [ "invokeruid" ] , client_name : data [ "invokername" ] } ) :
2019-10-24 16:22:26 +00:00
"" ,
2019-10-24 16:37:12 +00:00
data [ "extra_message" ] || data [ "reasonmsg" ] || ""
2019-10-24 16:22:26 +00:00
)
2019-10-24 16:37:12 +00:00
) ;
modal . htmlTag . find ( ".modal-body" ) . addClass ( "modal-disconnect-kick" ) ;
modal . open ( ) ;
2019-04-15 14:58:42 +00:00
this . sound . play ( Sound . SERVER_KICKED ) ;
2021-01-10 12:48:53 +00:00
autoReconnect = false ;
2019-04-15 14:58:42 +00:00
break ;
case DisconnectReason . HANDSHAKE_BANNED :
2019-12-21 15:23:35 +00:00
//Reason message already printed because of the command error handling
this . sound . play ( Sound . CONNECTION_BANNED ) ;
2019-04-04 19:47:52 +00:00
break ;
case DisconnectReason . CLIENT_BANNED :
2020-12-18 16:06:38 +00:00
this . log . log ( "server.banned" , {
2019-08-21 08:00:01 +00:00
invoker : {
client_name : data [ "invokername" ] ,
client_id : parseInt ( data [ "invokerid" ] ) ,
client_unique_id : data [ "invokeruid" ]
} ,
message : data [ "reasonmsg" ] ,
time : parseInt ( data [ "time" ] )
} ) ;
2019-12-21 15:23:35 +00:00
this . sound . play ( Sound . CONNECTION_BANNED ) ;
2019-04-04 19:47:52 +00:00
break ;
default :
2021-01-10 16:36:57 +00:00
logError ( LogCategory . CLIENT , tr ( "Got uncaught disconnect!" ) ) ;
logError ( LogCategory . CLIENT , tr ( "Type: %o Data: %o" ) , type , data ) ;
2019-04-04 19:47:52 +00:00
break ;
}
2020-11-07 12:16:07 +00:00
this . channelTree . unregisterClient ( this . localClient ) ; /* if we dont unregister our client here the client will be destroyed */
2019-04-04 19:47:52 +00:00
this . channelTree . reset ( ) ;
2021-04-19 18:26:37 +00:00
ignorePromise ( this . serverConnection ? . disconnect ( ) ) ;
2019-04-04 19:47:52 +00:00
2021-04-05 21:05:44 +00:00
this . handlerState . lastChannelCodecWarned = 0 ;
2019-04-04 19:47:52 +00:00
2021-01-10 12:48:53 +00:00
if ( autoReconnect ) {
2019-04-04 19:47:52 +00:00
if ( ! this . serverConnection ) {
2021-01-08 20:30:48 +00:00
logInfo ( LogCategory . NETWORKING , tr ( "Allowed to auto reconnect but cant reconnect because we dont have any information left..." ) ) ;
2019-04-04 19:47:52 +00:00
return ;
}
2020-12-18 16:06:38 +00:00
this . log . log ( "reconnect.scheduled" , { timeout : 5000 } ) ;
2019-04-04 19:47:52 +00:00
2021-01-08 20:30:48 +00:00
logInfo ( LogCategory . NETWORKING , tr ( "Allowed to auto reconnect. Reconnecting in 5000ms" ) ) ;
2021-04-19 18:26:37 +00:00
const reconnectParameters = this . generateReconnectParameters ( ) ;
2021-01-05 17:13:57 +00:00
this . autoReconnectTimer = setTimeout ( ( ) = > {
this . autoReconnectTimer = undefined ;
2020-12-18 16:06:38 +00:00
this . log . log ( "reconnect.execute" , { } ) ;
2021-01-08 20:30:48 +00:00
logInfo ( LogCategory . NETWORKING , tr ( "Reconnecting..." ) ) ;
2019-06-19 13:56:51 +00:00
2021-04-19 18:26:37 +00:00
ignorePromise ( this . startConnectionNew ( reconnectParameters , true ) ) ;
2019-04-04 19:47:52 +00:00
} , 5000 ) ;
}
2020-04-21 15:11:06 +00:00
this . serverConnection . updateConnectionState ( ConnectionState . UNCONNECTED ) ; /* Fix for the native client... */
2019-04-04 19:47:52 +00:00
}
2021-01-10 12:48:53 +00:00
cancelAutoReconnect ( log_event : boolean ) {
2021-01-05 17:13:57 +00:00
if ( this . autoReconnectTimer ) {
2021-01-10 12:48:53 +00:00
if ( log_event ) {
this . log . log ( "reconnect.canceled" , { } ) ;
}
2021-01-05 17:13:57 +00:00
clearTimeout ( this . autoReconnectTimer ) ;
this . autoReconnectTimer = undefined ;
2019-04-04 19:47:52 +00:00
}
}
2020-09-07 10:42:00 +00:00
private updateVoiceStatus() {
2020-11-07 12:16:07 +00:00
if ( ! this . localClient ) {
2020-08-19 17:33:57 +00:00
/* we've been destroyed */
return ;
}
2019-08-21 08:00:01 +00:00
2020-09-07 10:42:00 +00:00
let shouldRecord = false ;
2019-04-04 19:47:52 +00:00
2020-09-07 10:42:00 +00:00
const voiceConnection = this . serverConnection . getVoiceConnection ( ) ;
if ( this . serverConnection . connected ( ) ) {
let localClientUpdates : {
client_output_hardware? : boolean ,
2020-09-16 18:37:39 +00:00
client_input_hardware? : boolean ,
client_input_muted? : boolean ,
client_output_muted? : boolean
2020-09-07 10:42:00 +00:00
} = { } ;
2019-04-04 19:47:52 +00:00
2020-09-07 10:42:00 +00:00
const currentChannel = this . getClient ( ) . currentChannel ( ) ;
2019-04-04 19:47:52 +00:00
2020-09-07 10:42:00 +00:00
if ( ! currentChannel ) {
/* Don't update the voice state, firstly await for us to be fully connected */
} else if ( voiceConnection . getConnectionState ( ) !== VoiceConnectionStatus . Connected ) {
/* We're currently not having a valid voice connection. We need to await that. */
2020-09-25 08:46:42 +00:00
localClientUpdates . client_input_hardware = false ;
localClientUpdates . client_output_hardware = false ;
2020-09-07 10:42:00 +00:00
} else {
let codecSupportEncode = voiceConnection . encodingSupported ( currentChannel . properties . channel_codec ) ;
let codecSupportDecode = voiceConnection . decodingSupported ( currentChannel . properties . channel_codec ) ;
2020-08-19 17:33:57 +00:00
2020-09-07 10:42:00 +00:00
localClientUpdates . client_input_hardware = codecSupportEncode ;
localClientUpdates . client_output_hardware = codecSupportDecode ;
2019-04-04 19:47:52 +00:00
2021-04-05 21:05:44 +00:00
if ( this . handlerState . lastChannelCodecWarned !== currentChannel . getChannelId ( ) ) {
this . handlerState . lastChannelCodecWarned = currentChannel . getChannelId ( ) ;
2019-04-04 19:47:52 +00:00
2020-09-07 10:42:00 +00:00
if ( ! codecSupportEncode || ! codecSupportDecode ) {
let message ;
if ( ! codecSupportEncode && ! codecSupportDecode ) {
message = tr ( "This channel has an unsupported codec.<br>You cant speak or listen to anybody within this channel!" ) ;
} else if ( ! codecSupportEncode ) {
message = tr ( "This channel has an unsupported codec.<br>You cant speak within this channel!" ) ;
} else if ( ! codecSupportDecode ) {
message = tr ( "This channel has an unsupported codec.<br>You cant listen to anybody within this channel!" ) ;
}
2019-04-04 19:47:52 +00:00
2020-09-07 10:42:00 +00:00
createErrorModal ( tr ( "Channel codec unsupported" ) , message ) . open ( ) ;
}
}
2019-04-04 19:47:52 +00:00
2020-09-07 10:42:00 +00:00
shouldRecord = codecSupportEncode && ! ! voiceConnection . voiceRecorder ( ) ? . input ;
2019-04-04 19:47:52 +00:00
}
2020-09-25 08:46:42 +00:00
localClientUpdates . client_input_hardware = localClientUpdates . client_input_hardware && this . inputHardwareState === InputHardwareState . VALID ;
2021-04-05 21:05:44 +00:00
localClientUpdates . client_output_muted = this . handlerState . output_muted ;
localClientUpdates . client_input_muted = this . handlerState . input_muted ;
2020-09-16 18:37:39 +00:00
if ( localClientUpdates . client_input_muted || localClientUpdates . client_output_muted ) {
shouldRecord = false ;
}
2020-09-07 10:42:00 +00:00
/* update our owns client properties */
{
const currentClientProperties = this . getClient ( ) . properties ;
2020-11-17 13:35:26 +00:00
if ( this . clientStatusSync ) {
for ( const key of Object . keys ( localClientUpdates ) ) {
if ( currentClientProperties [ key ] === localClientUpdates [ key ] )
delete localClientUpdates [ key ] ;
}
2020-08-19 17:33:57 +00:00
}
2020-09-07 10:42:00 +00:00
if ( Object . keys ( localClientUpdates ) . length > 0 ) {
2020-11-17 13:35:26 +00:00
/* directly update all update locally so we don't send updates twice */
const updates = [ ] ;
2021-01-08 20:30:48 +00:00
for ( const key of Object . keys ( localClientUpdates ) ) {
2020-11-17 13:35:26 +00:00
updates . push ( { key : key , value : localClientUpdates [ key ] ? "1" : "0" } ) ;
2021-01-08 20:30:48 +00:00
}
2020-11-17 13:35:26 +00:00
this . getClient ( ) . updateVariables ( . . . updates ) ;
this . clientStatusSync = true ;
2021-04-05 21:05:44 +00:00
if ( this . connected ) {
this . serverConnection . send_command ( "clientupdate" , localClientUpdates ) . catch ( error = > {
logWarn ( LogCategory . GENERAL , tr ( "Failed to update client audio hardware properties. Error: %o" ) , error ) ;
this . log . log ( "error.custom" , { message : tr ( "Failed to update audio hardware properties." ) } ) ;
this . clientStatusSync = false ;
} ) ;
}
2020-08-19 17:33:57 +00:00
}
2019-04-04 19:47:52 +00:00
}
2020-09-07 10:42:00 +00:00
} else {
/* we're not connect, so we should not record either */
2019-04-04 19:47:52 +00:00
}
2020-09-07 10:42:00 +00:00
/* update the recorder state */
const currentInput = voiceConnection . voiceRecorder ( ) ? . input ;
if ( currentInput ) {
2020-09-16 19:12:28 +00:00
if ( shouldRecord || this . echoTestRunning ) {
2020-09-07 10:42:00 +00:00
if ( this . getInputHardwareState ( ) !== InputHardwareState . START_FAILED ) {
2021-01-08 20:30:48 +00:00
this . startVoiceRecorder ( Date . now ( ) - this . lastRecordErrorPopup > 10 * 1000 ) . then ( ( ) = > {
2021-01-05 17:13:57 +00:00
this . events_ . fire ( "notify_state_updated" , { state : "microphone" } ) ;
2020-09-16 17:30:28 +00:00
} ) ;
2019-08-21 08:00:01 +00:00
}
2020-09-07 10:42:00 +00:00
} else {
currentInput . stop ( ) . catch ( error = > {
logWarn ( LogCategory . AUDIO , tr ( "Failed to stop the microphone input recorder: %o" ) , error ) ;
2020-09-16 17:30:28 +00:00
} ) . then ( ( ) = > {
2021-01-05 17:13:57 +00:00
this . events_ . fire ( "notify_state_updated" , { state : "microphone" } ) ;
2020-09-07 10:42:00 +00:00
} ) ;
2019-08-21 08:00:01 +00:00
}
2019-05-20 16:57:14 +00:00
}
2020-09-07 10:42:00 +00:00
}
2019-04-04 19:47:52 +00:00
2021-01-08 20:30:48 +00:00
private lastRecordErrorPopup : number = 0 ;
2020-09-12 12:51:03 +00:00
update_voice_status() {
2020-09-07 10:42:00 +00:00
this . updateVoiceStatus ( ) ;
return ;
2019-04-04 19:47:52 +00:00
}
sync_status_with_server() {
if ( this . serverConnection . connected ( ) )
this . serverConnection . send_command ( "clientupdate" , {
2021-04-05 21:05:44 +00:00
client_input_muted : this.handlerState.input_muted ,
client_output_muted : this.handlerState.output_muted ,
client_away : typeof ( this . handlerState . away ) === "string" || this . handlerState . away ,
client_away_message : typeof ( this . handlerState . away ) === "string" ? this . handlerState . away : "" ,
2020-09-07 10:42:00 +00:00
/* TODO: Somehow store this? */
//client_input_hardware: this.client_status.sound_record_supported && this.getInputHardwareState() === InputHardwareState.VALID,
//client_output_hardware: this.client_status.sound_playback_supported
2019-04-04 19:47:52 +00:00
} ) . catch ( error = > {
2021-01-10 16:36:57 +00:00
logWarn ( LogCategory . GENERAL , tr ( "Failed to sync handler state with server. Error: %o" ) , error ) ;
2020-12-18 16:06:38 +00:00
this . log . log ( "error.custom" , { message : tr ( "Failed to sync handler state with server." ) } ) ;
2019-04-04 19:47:52 +00:00
} ) ;
}
2020-08-19 17:33:57 +00:00
/* can be called as much as you want, does nothing if nothing changed */
async acquireInputHardware() {
try {
2021-04-05 21:05:44 +00:00
await this . serverConnection . getVoiceConnection ( ) . acquireVoiceRecorder ( defaultRecorder ) ;
2020-08-19 17:33:57 +00:00
} catch ( error ) {
logError ( LogCategory . AUDIO , tr ( "Failed to acquire recorder: %o" ) , error ) ;
createErrorModal ( tr ( "Failed to acquire recorder" ) , tr ( "Failed to acquire recorder.\nLookup the console for more details." ) ) . open ( ) ;
return ;
}
2020-11-15 22:28:00 +00:00
/* our voice status will be updated automatically due to the notify_recorder_changed event which should be fired when the acquired recorder changed */
2019-04-04 19:47:52 +00:00
}
2020-09-07 10:42:00 +00:00
async startVoiceRecorder ( notifyError : boolean ) : Promise < { state : "success" | "no-input" } | { state : "error" , message : string } > {
2020-08-19 17:33:57 +00:00
const input = this . getVoiceRecorder ( ) ? . input ;
2020-09-07 10:42:00 +00:00
if ( ! input ) {
return { state : "no-input" } ;
}
2020-08-19 17:33:57 +00:00
if ( input . currentState ( ) === InputState . PAUSED && this . connection_state === ConnectionState . CONNECTED ) {
try {
const result = await input . start ( ) ;
2020-11-07 12:16:07 +00:00
if ( result !== true ) {
2020-08-19 17:33:57 +00:00
throw result ;
}
this . setInputHardwareState ( InputHardwareState . VALID ) ;
2021-01-08 20:30:48 +00:00
this . updateVoiceStatus ( ) ;
2020-09-07 10:42:00 +00:00
return { state : "success" } ;
2020-08-19 17:33:57 +00:00
} catch ( error ) {
this . setInputHardwareState ( InputHardwareState . START_FAILED ) ;
2021-01-08 20:30:48 +00:00
this . updateVoiceStatus ( ) ;
2020-08-19 17:33:57 +00:00
let errorMessage ;
2021-01-08 20:30:48 +00:00
if ( error === InputStartError . ENOTSUPPORTED ) {
2020-08-19 17:33:57 +00:00
errorMessage = tr ( "Your browser does not support voice recording" ) ;
2021-01-08 20:30:48 +00:00
} else if ( error === InputStartError . EBUSY ) {
2020-08-19 17:33:57 +00:00
errorMessage = tr ( "The input device is busy" ) ;
2021-01-08 20:30:48 +00:00
} else if ( error === InputStartError . EDEVICEUNKNOWN ) {
2020-08-19 17:33:57 +00:00
errorMessage = tr ( "Invalid input device" ) ;
2021-01-08 20:30:48 +00:00
} else if ( error === InputStartError . ENOTALLOWED ) {
2020-08-19 17:33:57 +00:00
errorMessage = tr ( "No permissions" ) ;
2021-01-08 20:30:48 +00:00
} else if ( error === InputStartError . ESYSTEMUNINITIALIZED ) {
errorMessage = tr ( "Window audio not initialized." ) ;
2020-08-19 17:33:57 +00:00
} else if ( error instanceof Error ) {
errorMessage = error . message ;
} else if ( typeof error === "string" ) {
errorMessage = error ;
} else {
errorMessage = tr ( "lookup the console" ) ;
}
2020-09-07 10:42:00 +00:00
2021-01-09 13:25:11 +00:00
logWarn ( LogCategory . VOICE , tr ( "Failed to start microphone input (%o)." ) , error ) ;
2020-08-19 17:33:57 +00:00
if ( notifyError ) {
2021-01-08 20:30:48 +00:00
this . lastRecordErrorPopup = Date . now ( ) ;
2020-08-19 17:33:57 +00:00
createErrorModal ( tr ( "Failed to start recording" ) , tra ( "Microphone start failed.\nError: {}" , errorMessage ) ) . open ( ) ;
}
2020-09-07 10:42:00 +00:00
return { state : "error" , message : errorMessage } ;
2020-08-19 17:33:57 +00:00
}
} else {
this . setInputHardwareState ( InputHardwareState . VALID ) ;
2020-09-07 10:42:00 +00:00
return { state : "success" } ;
2020-08-19 17:33:57 +00:00
}
2019-04-04 19:47:52 +00:00
}
2019-06-19 13:56:51 +00:00
2021-04-05 21:05:44 +00:00
getVoiceRecorder ( ) : RecorderProfile | undefined { return this . serverConnection ? . getVoiceConnection ( ) . voiceRecorder ( ) ; }
2020-04-09 13:10:14 +00:00
2021-01-10 12:48:53 +00:00
2021-04-19 18:26:37 +00:00
generateReconnectParameters ( ) : ConnectParameters | undefined {
const baseProfile = this . serverConnection . handshake_handler ( ) ? . parameters ;
if ( ! baseProfile ) {
/* We never tried to connect to anywhere */
return undefined ;
}
2021-01-10 12:48:53 +00:00
2021-04-19 18:26:37 +00:00
baseProfile . nickname = this . getClient ( ) ? . clientNickName ( ) || baseProfile . nickname ;
baseProfile . nicknameSpecified = false ;
2021-01-10 12:48:53 +00:00
2021-04-19 18:26:37 +00:00
const targetChannel = this . getClient ( ) ? . currentChannel ( ) ;
if ( targetChannel ) {
baseProfile . defaultChannel = targetChannel . channelId ;
baseProfile . defaultChannelPassword = targetChannel . getCachedPasswordHash ( ) ;
baseProfile . defaultChannelPasswordHashed = true ;
2019-06-19 13:56:51 +00:00
}
2021-04-19 18:26:37 +00:00
return baseProfile ;
2019-06-19 13:56:51 +00:00
}
2019-08-21 08:00:01 +00:00
2020-08-26 10:33:53 +00:00
private async initializeWhisperSession ( session : WhisperSession ) : Promise < WhisperSessionInitializeData > {
/* TODO: Try to load the clients unique via a clientgetuidfromclid */
if ( ! session . getClientUniqueId ( ) )
throw "missing clients unique id" ;
logInfo ( LogCategory . CLIENT , tr ( "Initializing a whisper session for client %d (%s | %s)" ) , session . getClientId ( ) , session . getClientUniqueId ( ) , session . getClientName ( ) ) ;
return {
clientName : session.getClientName ( ) ,
clientUniqueId : session.getClientUniqueId ( ) ,
2020-09-07 10:42:00 +00:00
blocked : session.getClientId ( ) !== this . getClient ( ) . clientId ( ) ,
2020-08-26 10:33:53 +00:00
volume : 1 ,
2020-09-07 10:42:00 +00:00
sessionTimeout : 5 * 1000
2020-08-26 10:33:53 +00:00
}
}
2019-08-21 08:00:01 +00:00
destroy() {
2021-01-22 12:34:43 +00:00
this . events_ . unregisterHandler ( this ) ;
2021-01-10 12:48:53 +00:00
this . cancelAutoReconnect ( true ) ;
2019-08-21 08:00:01 +00:00
2020-09-07 10:42:00 +00:00
this . pluginCmdRegistry ? . destroy ( ) ;
2020-08-07 11:40:11 +00:00
this . pluginCmdRegistry = undefined ;
2020-11-07 12:16:07 +00:00
if ( this . localClient ) {
const voiceHandle = this . localClient . getVoiceClient ( ) ;
2020-09-28 08:13:20 +00:00
if ( voiceHandle ) {
2020-11-07 12:16:07 +00:00
logWarn ( LogCategory . GENERAL , tr ( "Local voice client has received a voice handle. This should never happen!" ) ) ;
this . localClient . setVoiceClient ( undefined ) ;
2020-09-28 08:13:20 +00:00
this . serverConnection . getVoiceConnection ( ) . unregisterVoiceClient ( voiceHandle ) ;
}
2020-11-07 12:16:07 +00:00
const videoHandle = this . localClient . getVideoClient ( ) ;
if ( videoHandle ) {
logWarn ( LogCategory . GENERAL , tr ( "Local voice client has received a video handle. This should never happen!" ) ) ;
this . localClient . setVoiceClient ( undefined ) ;
this . serverConnection . getVideoConnection ( ) . unregisterVideoClient ( videoHandle ) ;
}
this . localClient . destroy ( ) ;
2020-09-28 08:13:20 +00:00
}
2020-11-07 12:16:07 +00:00
this . localClient = undefined ;
2019-08-21 08:00:01 +00:00
2020-12-09 12:36:56 +00:00
this . privateConversations ? . destroy ( ) ;
this . privateConversations = undefined ;
this . channelConversations ? . destroy ( ) ;
this . channelConversations = undefined ;
2020-09-07 10:42:00 +00:00
this . channelTree ? . destroy ( ) ;
2019-08-21 08:00:01 +00:00
this . channelTree = undefined ;
2020-12-09 19:44:33 +00:00
this . sideBar ? . destroy ( ) ;
this . sideBar = undefined ;
2020-12-29 15:53:04 +00:00
this . playlistManager ? . destroy ( ) ;
this . playlistManager = undefined ;
2020-12-09 19:44:33 +00:00
this . clientInfoManager ? . destroy ( ) ;
this . clientInfoManager = undefined ;
2019-08-21 08:00:01 +00:00
2020-09-07 10:42:00 +00:00
this . log ? . destroy ( ) ;
2019-08-21 08:00:01 +00:00
this . log = undefined ;
2020-09-07 10:42:00 +00:00
this . permissions ? . destroy ( ) ;
2019-08-21 08:00:01 +00:00
this . permissions = undefined ;
2020-09-07 10:42:00 +00:00
this . groups ? . destroy ( ) ;
2019-08-21 08:00:01 +00:00
this . groups = undefined ;
2020-09-07 10:42:00 +00:00
this . fileManager ? . destroy ( ) ;
2019-08-21 08:00:01 +00:00
this . fileManager = undefined ;
2020-09-07 10:42:00 +00:00
this . serverFeatures ? . destroy ( ) ;
this . serverFeatures = undefined ;
2021-01-08 20:30:48 +00:00
this . settings ? . destroy ( ) ;
2019-08-21 08:00:01 +00:00
this . settings = undefined ;
2021-01-08 20:30:48 +00:00
if ( this . listenerRecorderInputDeviceChanged ) {
this . listenerRecorderInputDeviceChanged ( ) ;
this . listenerRecorderInputDeviceChanged = undefined ;
}
2019-08-21 08:00:01 +00:00
if ( this . serverConnection ) {
2020-08-10 12:41:34 +00:00
getServerConnectionFactory ( ) . destroy ( this . serverConnection ) ;
2019-08-21 08:00:01 +00:00
}
this . serverConnection = undefined ;
this . sound = undefined ;
2020-11-07 12:16:07 +00:00
this . localClient = undefined ;
2019-08-21 08:00:01 +00:00
}
2020-04-09 13:10:14 +00:00
/* state changing methods */
2020-11-28 12:15:27 +00:00
setMicrophoneMuted ( muted : boolean , dontPlaySound? : boolean ) {
2021-04-05 21:05:44 +00:00
if ( this . handlerState . input_muted === muted ) {
2021-01-08 20:30:48 +00:00
return ;
}
2021-04-05 21:05:44 +00:00
this . handlerState . input_muted = muted ;
2020-11-28 12:15:27 +00:00
if ( ! dontPlaySound ) {
this . sound . play ( muted ? Sound.MICROPHONE_MUTED : Sound.MICROPHONE_ACTIVATED ) ;
}
2020-04-09 13:10:14 +00:00
this . update_voice_status ( ) ;
2021-01-05 17:13:57 +00:00
this . events_ . fire ( "notify_state_updated" , { state : "microphone" } ) ;
2020-04-09 13:10:14 +00:00
}
2020-04-10 18:57:50 +00:00
toggleMicrophone() { this . setMicrophoneMuted ( ! this . isMicrophoneMuted ( ) ) ; }
2020-04-09 13:10:14 +00:00
2021-04-05 21:05:44 +00:00
isMicrophoneMuted() { return this . handlerState . input_muted ; }
2020-08-19 17:33:57 +00:00
isMicrophoneDisabled() { return this . inputHardwareState !== InputHardwareState . VALID ; }
2020-04-09 13:10:14 +00:00
2020-11-28 12:15:27 +00:00
setSpeakerMuted ( muted : boolean , dontPlaySound? : boolean ) {
2021-04-05 21:05:44 +00:00
if ( this . handlerState . output_muted === muted ) {
return ;
}
if ( muted && ! dontPlaySound ) {
/* play the sound *before* we're setting the muted state */
this . sound . play ( Sound . SOUND_MUTED ) ;
}
this . handlerState . output_muted = muted ;
2021-01-05 17:13:57 +00:00
this . events_ . fire ( "notify_state_updated" , { state : "speaker" } ) ;
2021-04-05 21:05:44 +00:00
if ( ! muted && ! dontPlaySound ) {
/* play the sound *after* we're setting we've unmuted the sound */
this . sound . play ( Sound . SOUND_ACTIVATED ) ;
}
2020-04-09 13:10:14 +00:00
this . update_voice_status ( ) ;
2020-09-24 13:51:22 +00:00
this . serverConnection . getVoiceConnection ( ) . stopAllVoiceReplays ( ) ;
2020-04-09 13:10:14 +00:00
}
2020-04-10 18:57:50 +00:00
toggleSpeakerMuted() { this . setSpeakerMuted ( ! this . isSpeakerMuted ( ) ) ; }
2021-04-05 21:05:44 +00:00
isSpeakerMuted() { return this . handlerState . output_muted ; }
2020-04-09 13:10:14 +00:00
/ *
* Returns whatever the client is able to playback sound ( voice ) . Reasons for returning true could be :
* - Channel codec isn ' t supported
* - Voice bridge hasn ' t been set upped yet
* /
//TODO: This currently returns false
2020-08-07 11:40:11 +00:00
isSpeakerDisabled ( ) : boolean { return false ; }
2020-04-09 13:10:14 +00:00
setSubscribeToAllChannels ( flag : boolean ) {
2021-04-05 21:05:44 +00:00
if ( this . handlerState . channel_subscribe_all === flag ) {
return ;
}
this . handlerState . channel_subscribe_all = flag ;
2020-12-04 12:36:34 +00:00
if ( flag ) {
2020-04-09 13:10:14 +00:00
this . channelTree . subscribe_all_channels ( ) ;
2020-12-04 12:36:34 +00:00
} else {
2020-04-09 13:10:14 +00:00
this . channelTree . unsubscribe_all_channels ( ) ;
2020-12-04 12:36:34 +00:00
}
2021-01-05 17:13:57 +00:00
this . events_ . fire ( "notify_state_updated" , { state : "subscribe" } ) ;
2020-04-09 13:10:14 +00:00
}
2021-04-05 21:05:44 +00:00
isSubscribeToAllChannels ( ) : boolean { return this . handlerState . channel_subscribe_all ; }
2020-04-09 13:10:14 +00:00
setAway ( state : boolean | string ) {
2020-08-19 17:36:17 +00:00
this . doSetAway ( state , true ) ;
2020-04-09 13:10:14 +00:00
}
2021-04-05 21:05:44 +00:00
private doSetAway ( state : boolean | string , playSound : boolean ) {
if ( this . handlerState . away === state ) {
2020-04-09 13:10:14 +00:00
return ;
2021-04-05 21:05:44 +00:00
}
2020-04-09 13:10:14 +00:00
2021-04-05 21:05:44 +00:00
const wasAway = this . isAway ( ) ;
const willAway = typeof state === "boolean" ? state : true ;
if ( wasAway != willAway && playSound ) {
this . sound . play ( willAway ? Sound.AWAY_ACTIVATED : Sound.AWAY_DEACTIVATED ) ;
}
2020-04-09 13:10:14 +00:00
2021-04-05 21:05:44 +00:00
this . handlerState . away = state ;
2020-04-09 13:10:14 +00:00
this . serverConnection . send_command ( "clientupdate" , {
2021-04-05 21:05:44 +00:00
client_away : typeof ( this . handlerState . away ) === "string" || this . handlerState . away ,
client_away_message : typeof ( this . handlerState . away ) === "string" ? this . handlerState . away : "" ,
2020-04-09 13:10:14 +00:00
} ) . catch ( error = > {
2021-01-10 16:36:57 +00:00
logWarn ( LogCategory . GENERAL , tr ( "Failed to update away status. Error: %o" ) , error ) ;
2020-12-18 16:06:38 +00:00
this . log . log ( "error.custom" , { message : tr ( "Failed to update away status." ) } ) ;
2020-04-09 13:10:14 +00:00
} ) ;
2021-01-05 17:13:57 +00:00
this . events_ . fire ( "notify_state_updated" , {
2020-04-09 13:10:14 +00:00
state : "away"
} ) ;
}
2020-04-10 18:57:50 +00:00
toggleAway() { this . setAway ( ! this . isAway ( ) ) ; }
2021-04-05 21:05:44 +00:00
isAway ( ) : boolean { return typeof this . handlerState . away !== "boolean" || this . handlerState . away ; }
2020-04-09 13:10:14 +00:00
setQueriesShown ( flag : boolean ) {
2021-04-05 21:05:44 +00:00
if ( this . handlerState . queries_visible === flag ) {
return ;
}
this . handlerState . queries_visible = flag ;
2020-04-09 13:10:14 +00:00
this . channelTree . toggle_server_queries ( flag ) ;
2021-01-05 17:13:57 +00:00
this . events_ . fire ( "notify_state_updated" , {
2020-04-09 13:10:14 +00:00
state : "query"
} ) ;
}
2020-08-07 11:40:11 +00:00
areQueriesShown ( ) : boolean {
2021-04-05 21:05:44 +00:00
return this . handlerState . queries_visible ;
2020-04-09 13:10:14 +00:00
}
2020-07-19 15:12:41 +00:00
2020-08-19 17:33:57 +00:00
getInputHardwareState ( ) : InputHardwareState { return this . inputHardwareState ; }
private setInputHardwareState ( state : InputHardwareState ) {
2021-01-08 20:30:48 +00:00
if ( this . inputHardwareState === state ) {
2020-08-19 17:33:57 +00:00
return ;
2021-01-08 20:30:48 +00:00
}
2020-08-19 17:33:57 +00:00
this . inputHardwareState = state ;
2021-01-05 17:13:57 +00:00
this . events_ . fire ( "notify_state_updated" , { state : "microphone" } ) ;
2020-08-19 17:33:57 +00:00
}
hasOutputHardware ( ) : boolean { return true ; }
2020-08-07 11:40:11 +00:00
getPluginCmdRegistry ( ) : PluginCmdRegistry { return this . pluginCmdRegistry ; }
2020-09-07 10:42:00 +00:00
async startEchoTest ( ) : Promise < void > {
await this . serverConnection . getVoiceConnection ( ) . startWhisper ( { target : "echo" } ) ;
this . update_voice_status ( ) ;
try {
this . echoTestRunning = true ;
const startResult = await this . startVoiceRecorder ( false ) ;
/* FIXME: Don't do it like that! */
this . getVoiceRecorder ( ) ? . input ? . setFilterMode ( FilterMode . Bypass ) ;
if ( startResult . state === "error" ) {
throw startResult . message ;
}
} catch ( error ) {
this . echoTestRunning = false ;
/* TODO: Restore voice recorder state! */
throw error ;
}
}
stopEchoTest() {
this . echoTestRunning = false ;
this . serverConnection . getVoiceConnection ( ) . stopWhisper ( ) ;
this . getVoiceRecorder ( ) ? . input ? . setFilterMode ( FilterMode . Filter ) ;
this . update_voice_status ( ) ;
}
2020-09-25 23:22:21 +00:00
getCurrentServerUniqueId() {
return this . channelTree . server . properties . virtualserver_unique_identifier ;
}
2020-04-09 13:10:14 +00:00
}
export type ConnectionStateUpdateType = "microphone" | "speaker" | "away" | "subscribe" | "query" ;
export interface ConnectionEvents {
notify_state_updated : {
state : ConnectionStateUpdateType ;
}
notify_connection_state_changed : {
2020-12-09 12:36:56 +00:00
oldState : ConnectionState ,
newState : ConnectionState
2020-06-11 09:17:56 +00:00
} ,
2020-07-17 21:56:20 +00:00
/* fill only trigger once, after everything has been constructed */
notify_handler_initialized : { }
2019-04-04 19:47:52 +00:00
}