2019-04-04 19:47:52 +00:00
/// <reference path="log.ts" />
/// <reference path="voice/VoiceClient.ts" />
/// <reference path="proto.ts" />
/// <reference path="ui/view.ts" />
/// <reference path="settings.ts" />
/// <reference path="ui/frames/SelectedItemInfo.ts" />
/// <reference path="FileManager.ts" />
/// <reference path="permission/PermissionManager.ts" />
/// <reference path="permission/GroupManager.ts" />
/// <reference path="ui/frames/ControlBar.ts" />
/// <reference path="connection/ConnectionBase.ts" />
enum DisconnectReason {
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-04-15 13:33:51 +00:00
IDENTITY_TOO_LOW ,
2019-04-04 19:47:52 +00:00
UNKNOWN
}
enum ConnectionState {
UNCONNECTED ,
CONNECTING ,
INITIALISING ,
CONNECTED ,
DISCONNECTING
}
enum ViewReasonId {
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
}
interface VoiceStatus {
input_hardware : boolean ;
input_muted : boolean ;
output_muted : boolean ;
channel_codec_encoding_supported : boolean ;
channel_codec_decoding_supported : boolean ;
sound_playback_supported : boolean ;
sound_record_supported ;
away : boolean | string ;
channel_subscribe_all : boolean ;
queries_visible : boolean ;
}
class ConnectionHandler {
channelTree : ChannelTree ;
2019-04-15 13:33:51 +00:00
serverConnection : connection.AbstractServerConnection ;
2019-04-04 19:47:52 +00:00
fileManager : FileManager ;
permissions : PermissionManager ;
groups : GroupManager ;
select_info : InfoBar ;
chat : ChatBox ;
settings : ServerSettings ;
sound : sound.SoundManager ;
readonly tag_connection_handler : JQuery ;
private _clientId : number = 0 ;
private _local_client : LocalClientEntry ;
private _reconnect_timer : NodeJS.Timer ;
private _reconnect_attempt : boolean = false ;
2019-04-15 13:33:51 +00:00
private _connect_initialize_id : number = 1 ;
2019-04-04 19:47:52 +00:00
client_status : VoiceStatus = {
input_hardware : false ,
input_muted : false ,
output_muted : false ,
away : false ,
channel_subscribe_all : true ,
queries_visible : false ,
sound_playback_supported : undefined ,
sound_record_supported : undefined ,
channel_codec_encoding_supported : undefined ,
channel_codec_decoding_supported : undefined
} ;
invoke_resized_on_activate : boolean = false ;
constructor ( ) {
this . settings = new ServerSettings ( ) ;
this . select_info = new InfoBar ( this ) ;
this . channelTree = new ChannelTree ( this ) ;
this . chat = new ChatBox ( this ) ;
this . sound = new sound . SoundManager ( this ) ;
2019-04-15 13:33:51 +00:00
this . serverConnection = connection . spawn_server_connection ( this ) ;
2019-04-04 19:47:52 +00:00
this . serverConnection . onconnectionstatechanged = this . on_connection_state_changed . bind ( this ) ;
this . fileManager = new FileManager ( this ) ;
this . permissions = new PermissionManager ( this ) ;
this . groups = new GroupManager ( this ) ;
this . _local_client = new LocalClientEntry ( this ) ;
this . channelTree . registerClient ( this . _local_client ) ;
//settings.static_global(Settings.KEY_DISABLE_VOICE, false)
this . chat . initialize ( ) ;
this . tag_connection_handler = $ . spawn ( "div" ) . addClass ( "connection-container" ) ;
$ . spawn ( "div" ) . addClass ( "server-icon icon client-server_green" ) . appendTo ( this . tag_connection_handler ) ;
$ . spawn ( "div" ) . addClass ( "server-name" ) . text ( tr ( "Not connected" ) ) . appendTo ( this . tag_connection_handler ) ;
$ . spawn ( "div" ) . addClass ( "button-close icon client-tab_close_button" ) . appendTo ( this . tag_connection_handler ) ;
this . tag_connection_handler . on ( 'click' , event = > {
if ( event . isDefaultPrevented ( ) )
return ;
server_connections . set_active_connection_handler ( this ) ;
} ) ;
this . tag_connection_handler . find ( ".button-close" ) . on ( 'click' , event = > {
server_connections . destroy_server_connection_handler ( this ) ;
event . preventDefault ( ) ;
} ) ;
}
setup() { }
startConnection ( addr : string , profile : profiles.ConnectionProfile , name? : string , password ? : { password : string , hashed : boolean } ) {
this . tag_connection_handler . find ( ".server-name" ) . text ( tr ( "Connecting" ) ) ;
this . cancel_reconnect ( ) ;
this . _reconnect_attempt = false ;
if ( this . serverConnection )
this . handleDisconnect ( DisconnectReason . REQUESTED ) ;
let idx = addr . lastIndexOf ( ':' ) ;
let port : number ;
let host : string ;
if ( idx != - 1 ) {
port = parseInt ( addr . substr ( idx + 1 ) ) ;
host = addr . substr ( 0 , idx ) ;
} else {
host = addr ;
port = 9987 ;
}
console . log ( tr ( "Start connection to %s:%d" ) , host , port ) ;
this . channelTree . initialiseHead ( addr , { host , port } ) ;
2019-04-29 17:48:05 +00:00
this . chat . serverChat ( ) . appendMessage ( tr ( "Initializing connection to {0}{1}" ) , true , host , port == 9987 ? "" : ":" + port ) ;
2019-04-15 13:33:51 +00:00
const do_connect = ( address : string , port : number ) = > {
const remote_address = {
host : address ,
port : port
} ;
2019-04-29 17:48:05 +00:00
this . chat . serverChat ( ) . appendMessage ( tr ( "Connecting to {0}{1}" ) , true , address , port == 9987 ? "" : ":" + port ) ;
2019-04-15 13:33:51 +00:00
if ( password && ! password . hashed ) {
helpers . hashPassword ( password . password ) . then ( password = > {
/* errors will be already handled via the handle disconnect thing */
this . serverConnection . connect ( remote_address , new connection . HandshakeHandler ( profile , name , password ) ) ;
} ) . catch ( error = > {
createErrorModal ( tr ( "Error while hashing password" ) , tr ( "Failed to hash server password!<br>" ) + error ) . open ( ) ;
} )
} else {
2019-04-04 19:47:52 +00:00
/* errors will be already handled via the handle disconnect thing */
2019-04-15 13:33:51 +00:00
this . serverConnection . connect ( remote_address , new connection . HandshakeHandler ( profile , name , password ? password.password : undefined ) ) ;
}
} ;
if ( dns . supported ( ) && ! host . match ( Modals . Regex . IP_V4 ) && ! host . match ( Modals . Regex . IP_V6 ) ) {
const id = ++ this . _connect_initialize_id ;
this . chat . serverChat ( ) . appendMessage ( tr ( "Resolving hostname..." ) ) ;
dns . resolve_address ( host , { timeout : 5000 } ) . then ( result = > {
if ( id != this . _connect_initialize_id )
return ; /* we're old */
const _result = result || { target_ip : undefined , target_port : undefined } ;
//if(!result)
// throw "empty result";
this . chat . serverChat ( ) . appendMessage ( tr ( "Hostname successfully resolved to {0}" ) , true , _result . target_ip || host ) ;
do_connect ( _result . target_ip || host , _result . target_port || port ) ;
2019-04-04 19:47:52 +00:00
} ) . catch ( error = > {
2019-04-15 13:33:51 +00:00
if ( id != this . _connect_initialize_id )
return ; /* we're old */
this . handleDisconnect ( DisconnectReason . DNS_FAILED , error ) ;
} ) ;
2019-04-04 19:47:52 +00:00
} else {
2019-04-15 13:33:51 +00:00
do_connect ( host , port ) ;
2019-04-04 19:47:52 +00:00
}
}
getClient ( ) : LocalClientEntry { return this . _local_client ; }
getClientId() { return this . _clientId ; }
set clientId ( id : number ) {
this . _clientId = id ;
this . _local_client [ "_clientId" ] = id ;
}
get clientId() {
return this . _clientId ;
}
2019-04-15 13:33:51 +00:00
getServerConnection ( ) : connection . AbstractServerConnection { return this . serverConnection ; }
2019-04-04 19:47:52 +00:00
/ * *
* LISTENER
* /
onConnected() {
console . log ( "Client connected!" ) ;
this . channelTree . registerClient ( this . _local_client ) ;
this . permissions . requestPermissionList ( ) ;
if ( this . groups . serverGroups . length == 0 )
this . groups . requestGroups ( ) ;
this . initialize_server_settings ( ) ;
/* apply the server settings */
if ( this . client_status . channel_subscribe_all )
this . channelTree . subscribe_all_channels ( ) ;
else
this . channelTree . unsubscribe_all_channels ( ) ;
this . channelTree . toggle_server_queries ( this . client_status . queries_visible ) ;
this . sync_status_with_server ( ) ;
/ *
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 ( ) ;
* /
}
private initialize_server_settings() {
let update_control = false ;
this . settings . setServer ( this . channelTree . server ) ;
{
const flag_subscribe = this . settings . server ( Settings . KEY_CONTROL_CHANNEL_SUBSCRIBE_ALL , true ) ;
if ( this . client_status . channel_subscribe_all != flag_subscribe ) {
this . client_status . channel_subscribe_all = flag_subscribe ;
update_control = true ;
}
}
{
const flag_query = this . settings . server ( Settings . KEY_CONTROL_SHOW_QUERIES , false ) ;
if ( this . client_status . queries_visible != flag_query ) {
this . client_status . queries_visible = flag_query ;
update_control = true ;
}
}
if ( update_control && server_connections . active_connection_handler ( ) === this ) {
control_bar . apply_server_state ( ) ;
}
}
get connected ( ) : boolean {
return this . serverConnection && this . serverConnection . connected ( ) ;
}
private generate_ssl_certificate_accept ( ) : JQuery {
const properties = {
connect_default : true ,
2019-04-15 13:33:51 +00:00
connect_profile : this.serverConnection.handshake_handler ( ) . profile . id ,
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
} ;
const build_url = props = > {
const parameters : string [ ] = [ ] ;
for ( const key of Object . keys ( props ) )
parameters . push ( key + "=" + encodeURIComponent ( props [ key ] ) ) ;
let callback = document . location . origin + document . location . pathname + document . location . search ; /* don't use document.URL because it may contains a #! */
if ( document . location . search . length == 0 )
callback += "?" + parameters . join ( "&" ) ;
else
callback += "&" + parameters . join ( "&" ) ;
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 */
const tag = $ . spawn ( "a" ) . text ( tr ( "here" ) ) ;
if ( bipc . supported ( ) ) {
tag . attr ( 'href' , "#" ) ;
let popup : Window ;
tag . on ( 'click' , event = > {
const features = {
status : "no" ,
location : "no" ,
toolbar : "no" ,
menubar : "no" ,
width : 600 ,
height : 400
} ;
if ( popup )
popup . close ( ) ;
properties [ "certificate_callback" ] = bipc . get_handler ( ) . register_certificate_accept_callback ( ( ) = > {
log . info ( LogCategory . GENERAL , tr ( "Received notification that the certificate has been accepted! Attempting reconnect!" ) ) ;
if ( this . _certificate_modal )
this . _certificate_modal . close ( ) ;
popup . close ( ) ; /* no need, but nicer */
this . startConnection ( properties . connect_address , profiles . find_profile ( properties . connect_profile ) || profiles . default_profile ( ) ) ;
} ) ;
const url = build_url ( properties ) ;
const features_string = [ . . . Object . keys ( features ) ] . map ( e = > e + "=" + features [ e ] ) . reduce ( ( a , b ) = > a + "," + b ) ;
popup = window . open ( url , "TeaWeb certificate accept" , features_string ) ;
try {
popup . focus ( ) ;
} catch ( e ) {
log . warn ( LogCategory . GENERAL , tr ( "Certificate accept popup has been blocked. Trying a blank page and replacing href" ) ) ;
window . open ( url , "TeaWeb certificate accept" ) ; /* trying without features */
tag . attr ( "target" , "_blank" ) ;
tag . attr ( "href" , url ) ;
tag . unbind ( 'click' ) ;
}
} ) ;
} else {
tag . attr ( 'href' , build_url ( properties ) ) ;
}
return tag ;
}
private _certificate_modal : Modal ;
handleDisconnect ( type : DisconnectReason , data : any = { } ) {
2019-04-15 13:33:51 +00:00
this . _connect_initialize_id ++ ;
2019-04-04 19:47:52 +00:00
this . tag_connection_handler . find ( ".server-name" ) . text ( tr ( "Not connected" ) ) ;
let auto_reconnect = false ;
switch ( type ) {
case DisconnectReason . REQUESTED :
break ;
case DisconnectReason . HANDLER_DESTROYED :
if ( data )
this . sound . play ( Sound . CONNECTION_DISCONNECTED ) ;
break ;
2019-04-15 13:33:51 +00:00
case DisconnectReason . DNS_FAILED :
console . error ( tr ( "Failed to resolve hostname: %o" ) , data ) ;
this . chat . serverChat ( ) . appendError ( tr ( "Failed to resolve hostname: {0}" ) , data ) ;
this . sound . play ( Sound . CONNECTION_REFUSED ) ;
break ;
2019-04-04 19:47:52 +00:00
case DisconnectReason . CONNECT_FAILURE :
if ( this . _reconnect_attempt ) {
auto_reconnect = true ;
this . chat . serverChat ( ) . appendError ( tr ( "Connect failed" ) ) ;
break ;
}
2019-04-15 13:33:51 +00:00
console . error ( tr ( "Could not connect to remote host! Error: %o" ) , data ) ;
2019-04-04 19:47:52 +00:00
if ( native_client ) {
createErrorModal (
tr ( "Could not connect" ) ,
tr ( "Could not connect to remote host (Connection refused)" )
) . open ( ) ;
} else {
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.\n" +
"Click {0} to accept the remote certificate" ;
this . _certificate_modal = createErrorModal (
tr ( "Could not connect" ) ,
MessageHelper . formatMessage ( tr ( error_message_format ) , this . generate_ssl_certificate_accept ( ) )
) ;
this . _certificate_modal . close_listener . push ( ( ) = > this . _certificate_modal = undefined ) ;
this . _certificate_modal . open ( ) ;
}
this . sound . play ( Sound . CONNECTION_REFUSED ) ;
break ;
case DisconnectReason . HANDSHAKE_FAILED :
//TODO sound
console . error ( tr ( "Failed to process handshake: %o" ) , data ) ;
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" ) ,
2019-04-25 18:22:13 +00:00
MessageHelper . formatMessage ( tr ( "The target server is a TeamSpeak 3 server!{:br:}Only TeamSpeak 3 based identities are able to connect.{:br:}Please select another profile or change the identify type." ) )
2019-04-15 14:58:42 +00:00
) . open ( ) ;
this . sound . play ( Sound . CONNECTION_DISCONNECTED ) ;
auto_reconnect = false ;
break ;
2019-04-15 13:33:51 +00:00
case DisconnectReason . IDENTITY_TOO_LOW :
createErrorModal (
tr ( "Identity level is too low" ) ,
MessageHelper . formatMessage ( tr ( "You've been disconnected, because your Identity level is too low.{:br:}You need at least a level of {0}" ) , data [ "extra_message" ] )
) . open ( ) ;
this . sound . play ( Sound . CONNECTION_DISCONNECTED ) ;
auto_reconnect = false ;
break ;
2019-04-04 19:47:52 +00:00
case DisconnectReason . CONNECTION_CLOSED :
console . error ( tr ( "Lost connection to remote server!" ) ) ;
createErrorModal (
tr ( "Connection closed" ) ,
tr ( "The connection was closed by remote host" )
) . open ( ) ;
this . sound . play ( Sound . CONNECTION_DISCONNECTED ) ;
auto_reconnect = true ;
break ;
case DisconnectReason . CONNECTION_PING_TIMEOUT :
console . error ( tr ( "Connection ping timeout" ) ) ;
this . sound . play ( Sound . CONNECTION_DISCONNECTED_TIMEOUT ) ;
createErrorModal (
tr ( "Connection lost" ) ,
tr ( "Lost connection to remote host (Ping timeout)<br>Even possible?" )
) . open ( ) ;
break ;
case DisconnectReason . SERVER_CLOSED :
this . chat . serverChat ( ) . appendError ( tr ( "Server closed ({0})" ) , data . reasonmsg ) ;
createErrorModal (
tr ( "Server closed" ) ,
"The server is closed.<br>" + //TODO tr
"Reason: " + data . reasonmsg
) . open ( ) ;
this . sound . play ( Sound . CONNECTION_DISCONNECTED ) ;
auto_reconnect = true ;
break ;
case DisconnectReason . SERVER_REQUIRES_PASSWORD :
this . chat . serverChat ( ) . appendError ( tr ( "Server requires password" ) ) ;
createInputModal ( tr ( "Server password" ) , tr ( "Enter server password:" ) , password = > password . length != 0 , password = > {
if ( ! ( typeof password === "string" ) ) return ;
2019-04-15 13:33:51 +00:00
this . startConnection ( this . serverConnection . remote_address ( ) . host + ":" + this . serverConnection . remote_address ( ) . port ,
this . serverConnection . handshake_handler ( ) . profile ,
this . serverConnection . handshake_handler ( ) . name ,
2019-04-04 19:47:52 +00:00
{ password : password as string , hashed : false } ) ;
} ) . open ( ) ;
break ;
case DisconnectReason . CLIENT_KICKED :
2019-04-15 14:58:42 +00:00
createErrorModal (
tr ( "You've been banned" ) ,
MessageHelper . formatMessage ( tr ( "You've been banned from this server.{:br:}{0}" ) , data [ "extra_message" ] )
) . open ( ) ;
this . sound . play ( Sound . SERVER_KICKED ) ;
auto_reconnect = false ;
break ;
case DisconnectReason . HANDSHAKE_BANNED :
this . chat . serverChat ( ) . appendError ( tr ( "You got banned from the server by {0}{1}" ) ,
2019-04-04 19:47:52 +00:00
ClientEntry . chatTag ( data [ "invokerid" ] , data [ "invokername" ] , data [ "invokeruid" ] ) ,
data [ "reasonmsg" ] ? " (" + data [ "reasonmsg" ] + ")" : "" ) ;
2019-04-15 14:58:42 +00:00
this . sound . play ( Sound . CONNECTION_BANNED ) ; //TODO findout if it was a disconnect or a connect refuse
2019-04-04 19:47:52 +00:00
break ;
case DisconnectReason . CLIENT_BANNED :
this . chat . serverChat ( ) . appendError ( tr ( "You got banned from the server by {0}{1}" ) ,
ClientEntry . chatTag ( data [ "invokerid" ] , data [ "invokername" ] , data [ "invokeruid" ] ) ,
data [ "reasonmsg" ] ? " (" + data [ "reasonmsg" ] + ")" : "" ) ;
this . sound . play ( Sound . CONNECTION_BANNED ) ; //TODO findout if it was a disconnect or a connect refuse
break ;
default :
console . error ( tr ( "Got uncaught disconnect!" ) ) ;
console . error ( tr ( "Type: %o Data:" ) , type ) ;
console . error ( data ) ;
break ;
}
this . channelTree . reset ( ) ;
if ( this . serverConnection )
this . serverConnection . disconnect ( ) ;
if ( control_bar . current_connection_handler ( ) == this )
control_bar . update_connection_state ( ) ;
this . select_info . setCurrentSelected ( null ) ;
this . select_info . update_banner ( ) ;
if ( auto_reconnect ) {
if ( ! this . serverConnection ) {
console . log ( tr ( "Allowed to auto reconnect but cant reconnect because we dont have any information left..." ) ) ;
return ;
}
this . chat . serverChat ( ) . appendMessage ( tr ( "Reconnecting in 5 seconds" ) ) ;
console . log ( tr ( "Allowed to auto reconnect. Reconnecting in 5000ms" ) ) ;
2019-04-15 13:33:51 +00:00
const server_address = this . serverConnection . remote_address ( ) ;
const profile = this . serverConnection . handshake_handler ( ) . profile ;
const name = this . serverConnection . handshake_handler ( ) . name ;
const password = this . serverConnection . handshake_handler ( ) . server_password ;
2019-04-04 19:47:52 +00:00
this . _reconnect_timer = setTimeout ( ( ) = > {
this . _reconnect_timer = undefined ;
this . chat . serverChat ( ) . appendMessage ( tr ( "Reconnecting..." ) ) ;
log . info ( LogCategory . NETWORKING , tr ( "Reconnecting..." ) )
this . startConnection ( server_address . host + ":" + server_address . port , profile , name , password ? { password : password , hashed : true } : undefined ) ;
this . _reconnect_attempt = true ;
} , 5000 ) ;
}
}
cancel_reconnect() {
if ( this . _reconnect_timer ) {
this . chat . serverChat ( ) . appendMessage ( tr ( "Reconnect canceled" ) ) ;
clearTimeout ( this . _reconnect_timer ) ;
this . _reconnect_timer = undefined ;
}
}
private on_connection_state_changed() {
if ( control_bar . current_connection_handler ( ) == this )
control_bar . update_connection_state ( ) ;
}
update_voice_status ( targetChannel? : ChannelEntry ) {
targetChannel = targetChannel || this . getClient ( ) . currentChannel ( ) ;
const vconnection = this . serverConnection . voice_connection ( ) ;
const basic_voice_support = this . serverConnection . support_voice ( ) && vconnection . connected ( ) ;
const support_record = basic_voice_support && ( ! targetChannel || vconnection . encoding_supported ( targetChannel . properties . channel_codec ) ) ;
const support_playback = basic_voice_support && ( ! targetChannel || vconnection . decoding_supported ( targetChannel . properties . channel_codec ) ) ;
const property_update = {
client_input_muted : this.client_status.input_muted ,
client_output_muted : this.client_status.output_muted
} ;
if ( ! this . serverConnection . support_voice ( ) || ! vconnection . connected ( ) ) {
property_update [ "client_input_hardware" ] = false ;
property_update [ "client_output_hardware" ] = false ;
this . client_status . input_hardware = true ; /* IDK if we have input hardware or not, but it dosn't matter at all so */
} else {
const audio_source = vconnection . voice_recorder ( ) ;
const recording_supported = typeof ( audio_source ) !== "undefined" && audio_source . is_recording_supported ( ) && ( ! targetChannel || vconnection . encoding_supported ( targetChannel . properties . channel_codec ) ) ;
const playback_supported = ! targetChannel || vconnection . decoding_supported ( targetChannel . properties . channel_codec ) ;
property_update [ "client_input_hardware" ] = recording_supported ;
property_update [ "client_output_hardware" ] = playback_supported ;
this . client_status . input_hardware = recording_supported ;
}
if ( this . serverConnection && this . serverConnection . connected ( ) ) {
const client_properties = this . getClient ( ) . properties ;
for ( const key of Object . keys ( property_update ) ) {
if ( client_properties [ key ] === property_update [ key ] )
delete property_update [ key ] ;
}
if ( Object . keys ( property_update ) . length > 0 ) {
this . serverConnection . send_command ( "clientupdate" , property_update ) . catch ( error = > {
log . warn ( LogCategory . GENERAL , tr ( "Failed to update client audio hardware properties. Error: %o" ) , error ) ;
this . chat . serverChat ( ) . appendError ( tr ( "Failed to update audio hardware properties." ) ) ;
/* Update these properties anyways (for case the server fails to handle the command) */
const updates = [ ] ;
for ( const key of Object . keys ( property_update ) )
updates . push ( { key : key , value : ( property_update [ key ] ) + "" } ) ;
this . getClient ( ) . updateVariables ( . . . updates ) ;
} ) ;
}
} else { /* no icons are shown so no update at all */ }
if ( targetChannel && ( ! vconnection || vconnection . connected ( ) ) ) {
2019-04-15 13:33:51 +00:00
const encoding_supported = vconnection && vconnection . encoding_supported ( targetChannel . properties . channel_codec ) ;
const decoding_supported = vconnection && vconnection . decoding_supported ( targetChannel . properties . channel_codec ) ;
2019-04-04 19:47:52 +00:00
if ( this . client_status . channel_codec_decoding_supported !== decoding_supported || this . client_status . channel_codec_encoding_supported !== encoding_supported ) {
this . client_status . channel_codec_decoding_supported = decoding_supported ;
this . client_status . channel_codec_encoding_supported = encoding_supported ;
let message ;
if ( ! encoding_supported && ! decoding_supported )
message = tr ( "This channel has an unsupported codec.<br>You cant speak or listen to anybody within this channel!" ) ;
else if ( ! encoding_supported )
message = tr ( "This channel has an unsupported codec.<br>You cant speak within this channel!" ) ;
else if ( ! decoding_supported )
message = tr ( "This channel has an unsupported codec.<br>You listen to anybody within this channel!" ) ; /* implies speaking does not work as well */
if ( message )
createErrorModal ( tr ( "Channel codec unsupported" ) , message ) . open ( ) ;
}
}
this . client_status = this . client_status || { } as any ;
this . client_status . sound_record_supported = support_record ;
this . client_status . sound_playback_supported = support_playback ;
if ( vconnection && vconnection . voice_recorder ( ) && vconnection . voice_recorder ( ) . is_recording_supported ( ) )
vconnection . voice_recorder ( ) . set_recording ( ! this . client_status . input_muted && ! this . client_status . output_muted ) ;
if ( control_bar . current_connection_handler ( ) === this )
control_bar . apply_server_voice_state ( ) ;
}
sync_status_with_server() {
if ( this . serverConnection . connected ( ) )
this . serverConnection . send_command ( "clientupdate" , {
client_input_muted : this.client_status.input_muted ,
client_output_muted : this.client_status.output_muted ,
client_away : typeof ( this . client_status . away ) === "string" || this . client_status . away ,
client_away_message : typeof ( this . client_status . away ) === "string" ? this . client_status . away : "" ,
client_input_hardware : this.client_status.sound_record_supported && this . client_status . input_hardware ,
client_output_hardware : this.client_status.sound_playback_supported
} ) . catch ( error = > {
log . warn ( LogCategory . GENERAL , tr ( "Failed to sync handler state with server. Error: %o" ) , error ) ;
this . chat . serverChat ( ) . appendError ( tr ( "Failed to sync handler state with server." ) ) ;
} ) ;
}
set_away_status ( state : boolean | string ) {
if ( this . client_status . away === state )
return ;
this . client_status . away = state ;
this . serverConnection . send_command ( "clientupdate" , {
client_away : typeof ( this . client_status . away ) === "string" || this . client_status . away ,
client_away_message : typeof ( this . client_status . away ) === "string" ? this . client_status . away : "" ,
} ) . catch ( error = > {
log . warn ( LogCategory . GENERAL , tr ( "Failed to update away status. Error: %o" ) , error ) ;
this . chat . serverChat ( ) . appendError ( tr ( "Failed to update away status." ) ) ;
} ) ;
control_bar . update_button_away ( ) ;
}
resize_elements() {
this . channelTree . handle_resized ( ) ;
this . select_info . handle_resize ( ) ;
this . invoke_resized_on_activate = false ;
}
acquire_recorder ( voice_recoder : VoiceRecorder , update_control_bar : boolean ) {
const vconnection = this . serverConnection . voice_connection ( ) ;
if ( vconnection )
vconnection . acquire_voice_recorder ( voice_recoder ) ;
if ( voice_recoder )
voice_recoder . clean_recording_supported ( ) ;
this . update_voice_status ( undefined ) ;
}
}