2020-03-30 11:44:18 +00:00
import {
AbstractServerConnection ,
CommandOptionDefaults ,
2020-12-09 12:36:56 +00:00
CommandOptions , ConnectionPing ,
2021-04-27 11:30:33 +00:00
ConnectionStatistics , ServerCommand ,
2020-03-30 11:44:18 +00:00
} from "tc-shared/connection/ConnectionBase" ;
import { ConnectionHandler , ConnectionState , DisconnectReason } from "tc-shared/ConnectionHandler" ;
import { HandshakeHandler } from "tc-shared/connection/HandshakeHandler" ;
import { ConnectionCommandHandler , ServerConnectionCommandBoss } from "tc-shared/connection/CommandHandler" ;
import { CommandResult } from "tc-shared/connection/ServerConnectionDeclaration" ;
import { settings , Settings } from "tc-shared/settings" ;
import * as log from "tc-shared/log" ;
2021-01-10 16:36:57 +00:00
import { LogCategory , logDebug , logError , logInfo , logTrace , logWarn } from "tc-shared/log" ;
2020-03-30 11:44:18 +00:00
import { AbstractCommandHandlerBoss } from "tc-shared/connection/AbstractCommandHandler" ;
2021-03-18 17:25:20 +00:00
import { WrappedWebSocket } from "./WrappedWebSocket" ;
2020-08-10 12:41:34 +00:00
import { AbstractVoiceConnection } from "tc-shared/connection/VoiceConnection" ;
2021-03-18 17:25:20 +00:00
import { parseCommand } from "./CommandParser" ;
2020-09-16 19:50:21 +00:00
import { ServerAddress } from "tc-shared/tree/Server" ;
2021-03-18 17:25:20 +00:00
import { RtpVoiceConnection } from "../voice/Connection" ;
2020-11-07 12:16:07 +00:00
import { VideoConnection } from "tc-shared/connection/VideoConnection" ;
2020-11-17 13:27:46 +00:00
import { ServerFeature } from "tc-shared/connection/ServerFeatures" ;
2020-11-28 18:33:54 +00:00
import { RTCConnection } from "tc-shared/connection/rtc/Connection" ;
import { RtpVideoConnection } from "tc-shared/connection/rtc/video/Connection" ;
2020-11-29 13:42:02 +00:00
import { tr } from "tc-shared/i18n/localize" ;
2021-03-16 14:55:27 +00:00
import { createErrorModal } from "tc-shared/ui/elements/Modal" ;
2021-04-19 11:27:09 +00:00
import ipRegex from "ip-regex" ;
2020-03-30 11:44:18 +00:00
2019-02-23 13:15:22 +00:00
class ReturnListener < T > {
resolve : ( value? : T | PromiseLike < T > ) = > void ;
reject : ( reason? : any ) = > void ;
code : string ;
2020-07-19 15:12:41 +00:00
timeout : number ;
2019-02-23 13:15:22 +00:00
}
2020-07-23 22:29:36 +00:00
let globalReturnCodeIndex = 0 ;
2020-03-30 11:44:18 +00:00
export class ServerConnection extends AbstractServerConnection {
2020-07-23 22:29:36 +00:00
private remoteServerAddress : ServerAddress ;
private handshakeHandler : HandshakeHandler ;
2019-11-23 22:41:37 +00:00
2020-07-23 22:29:36 +00:00
private commandHandlerBoss : ServerConnectionCommandBoss ;
private defaultCommandHandler : ConnectionCommandHandler ;
2019-11-23 22:41:37 +00:00
2020-07-23 22:29:36 +00:00
private socket : WrappedWebSocket ;
private connectCancelCallback : ( ) = > void ;
2019-11-23 22:41:37 +00:00
2020-07-23 22:29:36 +00:00
private returnListeners : ReturnListener < CommandResult > [ ] = [ ] ;
2019-02-23 13:15:22 +00:00
2020-11-07 12:16:07 +00:00
private rtcConnection : RTCConnection ;
private voiceConnection : RtpVoiceConnection ;
private videoConnection : RtpVideoConnection ;
2019-04-04 19:47:52 +00:00
2020-07-23 22:29:36 +00:00
private pingStatistics = {
2020-03-30 11:44:18 +00:00
thread_id : 0 ,
2019-08-21 08:00:01 +00:00
2020-07-23 22:29:36 +00:00
lastRequestTimestamp : 0 ,
lastResponseTimestamp : 0 ,
currentRequestId : 0 ,
2019-08-21 08:00:01 +00:00
2020-03-30 11:44:18 +00:00
interval : 5000 ,
timeout : 7500 ,
2019-08-21 08:00:01 +00:00
2020-07-23 22:29:36 +00:00
currentJsValue : 0 ,
currentNativeValue : 0 /* ping value for native (WS) */
2020-03-30 11:44:18 +00:00
} ;
2019-08-21 08:00:01 +00:00
2020-03-30 11:44:18 +00:00
constructor ( client : ConnectionHandler ) {
super ( client ) ;
2019-02-23 13:15:22 +00:00
2020-07-23 22:29:36 +00:00
this . commandHandlerBoss = new ServerConnectionCommandBoss ( this ) ;
this . defaultCommandHandler = new ConnectionCommandHandler ( this ) ;
2019-02-23 13:15:22 +00:00
2021-04-27 11:30:33 +00:00
this . commandHandlerBoss . registerHandler ( this . defaultCommandHandler ) ;
2020-03-30 11:44:18 +00:00
this . command_helper . initialize ( ) ;
2019-04-04 19:47:52 +00:00
2020-11-28 18:33:54 +00:00
this . rtcConnection = new RTCConnection ( this , true ) ;
2020-11-07 12:16:07 +00:00
this . voiceConnection = new RtpVoiceConnection ( this , this . rtcConnection ) ;
this . videoConnection = new RtpVideoConnection ( this . rtcConnection ) ;
2020-03-30 11:44:18 +00:00
}
2019-02-23 13:15:22 +00:00
2020-03-30 11:44:18 +00:00
destroy() {
this . disconnect ( "handle destroyed" ) . catch ( error = > {
2021-01-10 16:36:57 +00:00
logWarn ( LogCategory . NETWORKING , tr ( "Failed to disconnect on server connection destroy: %o" ) , error ) ;
2020-03-30 11:44:18 +00:00
} ) . then ( ( ) = > {
2020-07-23 22:29:36 +00:00
clearInterval ( this . pingStatistics . thread_id ) ;
2020-09-28 08:46:11 +00:00
if ( this . connectCancelCallback ) {
2020-07-23 22:29:36 +00:00
this . connectCancelCallback ( ) ;
2020-09-28 08:46:11 +00:00
}
2019-08-21 08:00:01 +00:00
2020-07-23 22:29:36 +00:00
for ( const listener of this . returnListeners ) {
2020-03-30 11:44:18 +00:00
try {
listener . reject ( "handler destroyed" ) ;
} catch ( error ) {
2021-01-10 16:36:57 +00:00
logWarn ( LogCategory . NETWORKING , tr ( "Failed to reject command promise: %o" ) , error ) ;
2019-08-21 08:00:01 +00:00
}
2020-09-28 08:46:11 +00:00
clearTimeout ( listener . timeout ) ;
2020-03-30 11:44:18 +00:00
}
2020-07-23 22:29:36 +00:00
this . returnListeners = undefined ;
2019-08-21 08:00:01 +00:00
2020-11-07 12:16:07 +00:00
this . rtcConnection . destroy ( ) ;
2020-03-30 11:44:18 +00:00
this . command_helper . destroy ( ) ;
2019-08-21 08:00:01 +00:00
2021-04-27 11:30:33 +00:00
this . defaultCommandHandler && this . commandHandlerBoss . unregisterHandler ( this . defaultCommandHandler ) ;
2020-07-23 22:29:36 +00:00
this . defaultCommandHandler = undefined ;
2019-08-21 08:00:01 +00:00
2020-08-10 12:41:34 +00:00
this . voiceConnection && this . voiceConnection . destroy ( ) ;
this . voiceConnection = undefined ;
2019-02-23 13:15:22 +00:00
2020-07-23 22:29:36 +00:00
this . commandHandlerBoss && this . commandHandlerBoss . destroy ( ) ;
this . commandHandlerBoss = undefined ;
2020-08-10 12:41:34 +00:00
this . events . destroy ( ) ;
2020-03-30 11:44:18 +00:00
} ) ;
}
2019-02-23 13:15:22 +00:00
2020-03-30 11:44:18 +00:00
async connect ( address : ServerAddress , handshake : HandshakeHandler , timeout? : number ) : Promise < void > {
2020-07-23 22:29:36 +00:00
const connectBeginTimestamp = Date . now ( ) ;
2020-12-02 17:23:42 +00:00
timeout = typeof ( timeout ) === "number" ? timeout : 10_000 ;
2019-11-23 22:41:37 +00:00
2020-03-30 11:44:18 +00:00
try {
2020-07-23 22:29:36 +00:00
await this . disconnect ( ) ;
2020-03-30 11:44:18 +00:00
} catch ( error ) {
2021-01-10 16:36:57 +00:00
logError ( LogCategory . NETWORKING , tr ( "Failed to close old connection properly. Error: %o" ) , error ) ;
2020-03-30 11:44:18 +00:00
throw "failed to cleanup old connection" ;
}
2019-02-23 13:15:22 +00:00
2020-03-30 11:44:18 +00:00
this . updateConnectionState ( ConnectionState . CONNECTING ) ;
2020-07-23 22:29:36 +00:00
this . remoteServerAddress = address ;
2019-02-23 13:15:22 +00:00
2020-07-23 22:29:36 +00:00
this . handshakeHandler = handshake ;
this . handshakeHandler . setConnection ( this ) ;
2019-11-23 22:41:37 +00:00
2020-03-30 11:44:18 +00:00
/* The direct one connect directly to the target address. The other via the .con-gate.work */
2020-07-23 22:29:36 +00:00
let availableSockets : WrappedWebSocket [ ] = [ ] ;
proxySocket :
2021-01-10 15:13:15 +00:00
if ( ! settings . getValue ( Settings . KEY_CONNECT_NO_DNSPROXY ) ) {
2020-07-23 22:29:36 +00:00
let host ;
2021-04-19 11:27:09 +00:00
if ( ipRegex ( { exact : true } ) . test ( address . host ) ) {
host = address . host ;
host = host . replace ( /\./g , "-" ) ;
host = host . replace ( /:/g , "_" ) ;
host = host + ".con-gate.work" ;
2020-07-23 22:29:36 +00:00
} else {
break proxySocket ;
2020-03-30 11:44:18 +00:00
}
2019-11-22 20:51:26 +00:00
2020-07-25 11:56:30 +00:00
availableSockets . push ( new WrappedWebSocket ( {
host : host ,
port : address.port ,
secure : true
} ) )
2020-07-23 22:29:36 +00:00
}
2020-07-25 11:56:30 +00:00
availableSockets . push ( new WrappedWebSocket ( {
host : address.host ,
port : address.port ,
secure : true
} ) ) ;
2020-07-23 22:29:36 +00:00
let timeoutRaised = false ;
2021-04-29 12:51:30 +00:00
let timeoutPromise = new Promise < void > ( resolve = > setTimeout ( ( ) = > {
2020-07-23 22:29:36 +00:00
timeoutRaised = true ;
resolve ( ) ;
} , timeout ) ) ;
let cancelRaised = false ;
2021-04-29 12:51:30 +00:00
let cancelPromise = new Promise < void > ( resolve = > {
2020-07-23 22:29:36 +00:00
this . connectCancelCallback = ( ) = > {
this . connectCancelCallback = undefined ;
cancelRaised = true ;
resolve ( ) ;
} ;
} ) ;
2019-08-21 08:00:01 +00:00
2020-07-23 22:29:36 +00:00
availableSockets . forEach ( e = > e . doConnect ( ) ) ;
while ( availableSockets . length > 0 ) {
await Promise . race ( [ . . . availableSockets . map ( e = > e . awaitConnectResult ( ) ) , timeoutPromise , cancelPromise ] ) ;
2019-08-21 08:00:01 +00:00
2020-07-23 22:29:36 +00:00
if ( cancelRaised ) {
2021-01-10 16:36:57 +00:00
logDebug ( LogCategory . NETWORKING , tr ( "Aborting connect attempt due to a cancel request." ) ) ;
2020-07-23 22:29:36 +00:00
availableSockets . forEach ( e = > e . closeConnection ( ) ) ;
return
}
2019-11-23 22:41:37 +00:00
2020-07-23 22:29:36 +00:00
if ( timeoutRaised ) {
2021-01-10 16:36:57 +00:00
logInfo ( LogCategory . NETWORKING , tr ( "Connect timeout triggered. Aborting connect attempt!" ) ) ;
2020-07-23 22:29:36 +00:00
availableSockets . forEach ( e = > e . closeConnection ( ) ) ;
this . updateConnectionState ( ConnectionState . UNCONNECTED ) ; /* firstly update the state, that fire event */
this . client . handleDisconnect ( DisconnectReason . CONNECT_FAILURE ) ;
return
}
2019-02-23 13:15:22 +00:00
2020-07-23 22:29:36 +00:00
let finished = availableSockets . find ( e = > e . state !== "connecting" ) ;
if ( ! finished ) continue ; /* should not happen, but we want to ensure it */
availableSockets . remove ( finished ) ;
2020-03-30 11:44:18 +00:00
2020-07-23 22:29:36 +00:00
switch ( finished . state ) {
case "unconnected" :
2021-01-10 16:36:57 +00:00
logDebug ( LogCategory . NETWORKING , tr ( "Connection attempt to %s:%d via %s got aborted." ) , this . remoteServerAddress . host , this . remoteServerAddress . port , finished . socketUrl ( ) ) ;
2020-07-23 22:29:36 +00:00
continue ;
2020-03-30 11:44:18 +00:00
2020-07-23 22:29:36 +00:00
case "errored" :
const error = finished . popError ( ) ;
2021-01-10 16:36:57 +00:00
logInfo ( LogCategory . NETWORKING , tr ( "Connection attempt to %s:%d via %s failed:\n%o" ) , this . remoteServerAddress . host , this . remoteServerAddress . port , finished . socketUrl ( ) , error ) ;
2020-07-23 22:29:36 +00:00
continue ;
2020-03-30 11:44:18 +00:00
2020-07-23 22:29:36 +00:00
case "connected" :
break ;
2020-03-30 11:44:18 +00:00
}
2019-02-23 13:15:22 +00:00
2020-07-23 22:29:36 +00:00
this . socket = finished ;
2019-11-23 22:41:37 +00:00
2020-07-23 22:29:36 +00:00
/* abort any other ongoing connection attempts, we already succeeded */
availableSockets . forEach ( e = > e . closeConnection ( ) ) ;
break ;
}
2019-02-23 13:15:22 +00:00
2020-07-23 22:29:36 +00:00
if ( ! this . socket ) {
2021-01-10 16:36:57 +00:00
logInfo ( LogCategory . NETWORKING , tr ( "Failed to connect to %s:%d. No connection attempt succeeded." ) , this . remoteServerAddress . host , this . remoteServerAddress . port ) ;
2020-07-23 22:29:36 +00:00
this . updateConnectionState ( ConnectionState . UNCONNECTED ) ; /* firstly update the state, that fire event */
this . client . handleDisconnect ( DisconnectReason . CONNECT_FAILURE ) ;
return ;
}
2019-02-23 13:15:22 +00:00
2020-07-23 22:29:36 +00:00
this . socket . callbackMessage = message = > this . handleSocketMessage ( message ) ;
this . socket . callbackDisconnect = ( code , reason ) = > {
try {
this . disconnect ( ) ;
} catch ( error ) {
2021-01-10 16:36:57 +00:00
logWarn ( LogCategory . NETWORKING , tr ( "Failed to disconnect with an already closed socket: %o" ) , error ) ;
2020-07-23 22:29:36 +00:00
}
2019-02-23 13:15:22 +00:00
2020-07-23 22:29:36 +00:00
this . client . handleDisconnect ( DisconnectReason . CONNECTION_CLOSED , {
code : code ,
reason : reason
} ) ;
} ;
this . socket . callbackErrored = ( ) = > {
if ( this . socket . hasError ( ) ) {
2021-01-10 16:36:57 +00:00
logError ( LogCategory . NETWORKING , tr ( "Server connection %s:%d has been terminated due to an unexpected error (%o)." ) ,
2020-07-23 22:29:36 +00:00
this . remoteServerAddress . host ,
this . remoteServerAddress . port ,
this . socket . popError ( )
) ;
} else {
2021-01-10 16:36:57 +00:00
logError ( LogCategory . NETWORKING , tr ( "Server connection %s:%d has been terminated due to an unexpected error." ) , this . remoteServerAddress . host , this . remoteServerAddress . port ) ;
2020-07-23 22:29:36 +00:00
}
try {
this . disconnect ( ) ;
} catch ( error ) {
2021-01-10 16:36:57 +00:00
logWarn ( LogCategory . NETWORKING , tr ( "Failed to disconnect with an already closed socket: %o" ) , error ) ;
2020-07-23 22:29:36 +00:00
}
2019-02-23 13:15:22 +00:00
2020-07-23 22:29:36 +00:00
this . client . handleDisconnect ( DisconnectReason . CONNECTION_CLOSED ) ;
} ;
2019-02-23 13:15:22 +00:00
2020-07-23 22:29:36 +00:00
const connectEndTimestamp = Date . now ( ) ;
2021-01-10 16:36:57 +00:00
logInfo ( LogCategory . NETWORKING , tr ( "Successfully initialized a connection to %s:%d via %s within %d milliseconds." ) ,
2020-07-23 22:29:36 +00:00
this . remoteServerAddress . host ,
this . remoteServerAddress . port ,
2020-07-25 11:56:30 +00:00
this . socket . socketUrl ( ) ,
2020-07-23 22:29:36 +00:00
connectEndTimestamp - connectBeginTimestamp ) ;
2019-08-21 08:00:01 +00:00
2020-09-03 10:14:15 +00:00
/* enabling raw commands, if the server supports it */
this . sendData ( JSON . stringify ( {
type : "enable-raw-commands"
} ) )
2020-11-07 12:16:07 +00:00
this . startHandshake ( ) ;
2020-03-30 11:44:18 +00:00
}
2019-02-23 13:15:22 +00:00
2020-11-07 12:16:07 +00:00
private startHandshake() {
2020-03-30 11:44:18 +00:00
this . updateConnectionState ( ConnectionState . INITIALISING ) ;
2020-12-18 16:06:38 +00:00
this . client . log . log ( "connection.login" , { } ) ;
2020-07-23 22:29:36 +00:00
this . handshakeHandler . initialize ( ) ;
this . handshakeHandler . startHandshake ( ) ;
2020-03-30 11:44:18 +00:00
}
2019-11-23 22:41:37 +00:00
2020-03-30 11:44:18 +00:00
async disconnect ( reason? : string ) : Promise < void > {
2020-07-23 22:29:36 +00:00
if ( this . connectCancelCallback )
this . connectCancelCallback ( ) ;
2020-08-10 12:41:34 +00:00
if ( this . connectionState === ConnectionState . UNCONNECTED )
2020-07-23 22:41:04 +00:00
return ;
2020-04-09 13:10:14 +00:00
this . updateConnectionState ( ConnectionState . DISCONNECTING ) ;
try {
2020-07-23 22:29:36 +00:00
clearTimeout ( this . pingStatistics . thread_id ) ;
this . pingStatistics . thread_id = undefined ;
2019-02-23 13:15:22 +00:00
2020-04-09 13:10:14 +00:00
if ( typeof ( reason ) === "string" ) {
//TODO send disconnect reason
}
2019-08-21 08:00:01 +00:00
2020-07-23 22:29:36 +00:00
if ( this . socket ) {
this . socket . callbackMessage = undefined ;
this . socket . callbackDisconnect = undefined ;
this . socket . callbackErrored = undefined ;
2019-02-23 13:15:22 +00:00
2020-07-23 22:29:36 +00:00
this . socket . closeConnection ( ) ; /* 3000 + 0xFF, tr("request disconnect") */
this . socket = undefined ;
}
2019-02-23 13:15:22 +00:00
2020-07-23 22:29:36 +00:00
for ( let future of this . returnListeners )
2020-04-09 13:10:14 +00:00
future . reject ( tr ( "Connection closed" ) ) ;
2020-07-23 22:29:36 +00:00
this . returnListeners = [ ] ;
2020-04-09 13:10:14 +00:00
} finally {
this . updateConnectionState ( ConnectionState . UNCONNECTED ) ;
}
2020-03-30 11:44:18 +00:00
}
2019-02-23 13:15:22 +00:00
2020-07-23 22:29:36 +00:00
private handleSocketMessage ( data ) {
2020-03-30 11:44:18 +00:00
if ( typeof ( data ) === "string" ) {
let json ;
try {
json = JSON . parse ( data ) ;
} catch ( e ) {
2021-01-10 16:36:57 +00:00
logWarn ( LogCategory . NETWORKING , tr ( "Could not parse message json!" ) ) ;
2020-03-30 11:44:18 +00:00
return ;
}
if ( json [ "type" ] === undefined ) {
2021-01-10 16:36:57 +00:00
logWarn ( LogCategory . NETWORKING , tr ( "Missing data type in message!" ) ) ;
2020-03-30 11:44:18 +00:00
return ;
}
if ( json [ "type" ] === "command" ) {
2020-04-18 18:25:58 +00:00
/* devel-block(log-networking-commands) */
2020-03-30 11:44:18 +00:00
let group = log . group ( log . LogType . DEBUG , LogCategory . NETWORKING , tr ( "Handling command '%s'" ) , json [ "command" ] ) ;
group . log ( tr ( "Handling command '%s'" ) , json [ "command" ] ) ;
group . group ( log . LogType . TRACE , tr ( "Json:" ) ) . collapsed ( true ) . log ( "%o" , json ) . end ( ) ;
2020-04-18 18:25:58 +00:00
/* devel-block-end */
2020-03-30 11:44:18 +00:00
2021-04-27 11:30:33 +00:00
this . commandHandlerBoss . invokeCommand ( new ServerCommand ( json [ "command" ] , json [ "data" ] , [ ] ) ) ;
2020-03-30 11:44:18 +00:00
if ( json [ "command" ] === "initserver" ) {
2020-11-17 13:27:46 +00:00
this . handleServerInit ( ) ;
2020-03-30 11:44:18 +00:00
}
2020-04-18 18:25:58 +00:00
/* devel-block(log-networking-commands) */
2020-03-30 11:44:18 +00:00
group . end ( ) ;
2020-04-18 18:25:58 +00:00
/* devel-block-end */
2020-09-03 10:14:15 +00:00
} else if ( json [ "type" ] === "command-raw" ) {
const command = parseCommand ( json [ "payload" ] ) ;
logTrace ( LogCategory . NETWORKING , tr ( "Received command %s" ) , command . command ) ;
2021-04-27 11:30:33 +00:00
this . commandHandlerBoss . invokeCommand ( command ) ;
2020-09-03 10:14:15 +00:00
if ( command . command === "initserver" ) {
2020-11-17 13:27:46 +00:00
this . handleServerInit ( ) ;
2020-09-03 10:14:15 +00:00
}
2020-03-30 11:44:18 +00:00
} else if ( json [ "type" ] === "ping" ) {
2020-07-23 22:29:36 +00:00
this . sendData ( JSON . stringify ( {
type : 'pong' ,
payload : json [ "payload" ]
} ) ) ;
2020-03-30 11:44:18 +00:00
} else if ( json [ "type" ] === "pong" ) {
const id = parseInt ( json [ "payload" ] ) ;
2020-07-23 22:29:36 +00:00
if ( id != this . pingStatistics . currentRequestId ) {
2021-01-10 16:36:57 +00:00
logWarn ( LogCategory . NETWORKING , tr ( "Received pong which is older than the last request. Delay may over %oms? (Index: %o, Current index: %o)" ) , this . pingStatistics . timeout , id , this . pingStatistics . currentRequestId ) ;
2019-08-21 08:00:01 +00:00
} else {
2020-07-23 22:29:36 +00:00
this . pingStatistics . lastResponseTimestamp = 'now' in performance ? performance . now ( ) : Date . now ( ) ;
this . pingStatistics . currentJsValue = this . pingStatistics . lastResponseTimestamp - this . pingStatistics . lastRequestTimestamp ;
this . pingStatistics . currentNativeValue = parseInt ( json [ "ping_native" ] ) / 1000 ; /* we're getting it in microseconds and not milliseconds */
2020-12-09 12:36:56 +00:00
this . events . fire ( "notify_ping_updated" , { newPing : this.ping ( ) } ) ;
2021-01-10 16:36:57 +00:00
//logDebug(LogCategory.NETWORKING, tr("Received new pong. Updating ping to: JS: %o Native: %o"), this._ping.value.toFixed(3), this._ping.value_native.toFixed(3));
2019-02-23 13:15:22 +00:00
}
} else {
2021-01-10 16:36:57 +00:00
logWarn ( LogCategory . NETWORKING , tr ( "Unknown command type %o" ) , json [ "type" ] ) ;
2019-02-23 13:15:22 +00:00
}
2020-03-30 11:44:18 +00:00
} else {
2021-01-10 16:36:57 +00:00
logWarn ( LogCategory . NETWORKING , tr ( "Received unknown message of type %s. Dropping message" ) , typeof ( data ) ) ;
2019-02-23 13:15:22 +00:00
}
2020-03-30 11:44:18 +00:00
}
2019-02-23 13:15:22 +00:00
2020-03-30 11:44:18 +00:00
sendData ( data : any ) {
2020-07-23 22:29:36 +00:00
if ( ! this . socket || this . socket . state !== "connected" ) {
2021-01-10 16:36:57 +00:00
logWarn ( LogCategory . NETWORKING , tr ( "Tried to send data via a non connected server socket." ) ) ;
2020-03-30 11:44:18 +00:00
return ;
2019-02-23 13:15:22 +00:00
}
2020-07-23 22:29:36 +00:00
2020-11-16 20:02:18 +00:00
this . socket . sendMessage ( data ) ;
2020-03-30 11:44:18 +00:00
}
2019-02-23 13:15:22 +00:00
2020-11-17 13:27:46 +00:00
private handleServerInit() {
this . pingStatistics . thread_id = setInterval ( ( ) = > this . doNextPing ( ) , this . pingStatistics . interval ) as any ;
this . doNextPing ( ) ;
this . updateConnectionState ( ConnectionState . CONNECTED ) ;
this . client . serverFeatures . awaitFeatures ( ) . then ( succeeded = > {
if ( ! succeeded ) {
/* something like a disconnect happened or so */
return ;
}
if ( this . client . serverFeatures . supportsFeature ( ServerFeature . VIDEO , 1 ) ) {
2021-03-16 14:55:27 +00:00
this . rtcConnection . doInitialSetup ( ) ;
2020-11-17 13:27:46 +00:00
} else {
/* old voice connection */
2021-03-16 14:55:27 +00:00
logDebug ( LogCategory . NETWORKING , tr ( "Using legacy voice connection for TeaSpeak server bellow 1.5" ) ) ;
createErrorModal ( tr ( "Server outdated" ) , tr ( "Please update your server in order to use the WebClient" ) ) . open ( ) ;
this . rtcConnection . setNotSupported ( ) ;
2020-11-17 13:27:46 +00:00
}
} ) ;
}
2020-07-23 22:29:36 +00:00
private static commandDataToJson ( input : any ) : string {
2020-03-30 11:44:18 +00:00
return JSON . stringify ( input , ( key , value ) = > {
switch ( typeof value ) {
case "boolean" : return value == true ? "1" : "0" ;
case "function" : return value ( ) ;
default :
return value ;
2019-02-23 13:15:22 +00:00
}
2020-03-30 11:44:18 +00:00
} ) ;
2019-02-23 13:15:22 +00:00
2020-03-30 11:44:18 +00:00
}
2019-04-18 11:19:08 +00:00
2020-03-30 11:44:18 +00:00
send_command ( command : string , data? : any | any [ ] , _options? : CommandOptions ) : Promise < CommandResult > {
2020-07-23 22:29:36 +00:00
if ( ! this . socket || ! this . connected ( ) ) {
2021-01-10 16:36:57 +00:00
logWarn ( LogCategory . NETWORKING , tr ( "Tried to send a command without a valid connection." ) ) ;
2020-03-30 11:44:18 +00:00
return Promise . reject ( tr ( "not connected" ) ) ;
2019-02-23 13:15:22 +00:00
}
2020-03-30 11:44:18 +00:00
const options : CommandOptions = { } ;
Object . assign ( options , CommandOptionDefaults ) ;
Object . assign ( options , _options ) ;
2020-08-10 12:41:34 +00:00
data = Array . isArray ( data ) ? data : [ data || { } ] ;
2020-03-30 11:44:18 +00:00
if ( data . length == 0 ) /* we require min one arg to append return_code */
data . push ( { } ) ;
let result = new Promise < CommandResult > ( ( resolve , failed ) = > {
2020-08-10 12:41:34 +00:00
let payload = Array . isArray ( data ) ? data : [ data ] ;
2020-07-23 22:29:36 +00:00
let returnCode = typeof payload [ 0 ] [ "return_code" ] === "string" ? payload [ 0 ] . return_code : ++ globalReturnCodeIndex ;
payload [ 0 ] . return_code = returnCode ;
2020-03-30 11:44:18 +00:00
let listener = new ReturnListener < CommandResult > ( ) ;
listener . resolve = resolve ;
listener . reject = failed ;
2020-07-23 22:29:36 +00:00
listener . code = returnCode ;
2020-03-30 11:44:18 +00:00
listener . timeout = setTimeout ( ( ) = > {
2020-07-23 22:29:36 +00:00
this . returnListeners . remove ( listener ) ;
2020-03-30 11:44:18 +00:00
listener . reject ( "timeout" ) ;
2020-12-02 17:23:42 +00:00
} , options . timeout || 15 _000 ) ;
2020-07-23 22:29:36 +00:00
this . returnListeners . push ( listener ) ;
2020-03-30 11:44:18 +00:00
2020-07-23 22:29:36 +00:00
this . sendData ( ServerConnection . commandDataToJson ( {
2020-03-30 11:44:18 +00:00
"type" : "command" ,
"command" : command ,
2020-07-23 22:29:36 +00:00
"data" : payload ,
2020-03-30 11:44:18 +00:00
"flags" : options . flagset . filter ( entry = > entry . length != 0 )
2020-07-23 22:29:36 +00:00
} ) )
2020-03-30 11:44:18 +00:00
} ) ;
2020-07-23 22:29:36 +00:00
return this . defaultCommandHandler . proxy_command_promise ( result , options ) ;
2020-03-30 11:44:18 +00:00
}
2019-02-23 13:15:22 +00:00
2020-03-30 11:44:18 +00:00
connected ( ) : boolean {
2020-07-23 22:29:36 +00:00
return ! ! this . socket && this . socket . state === "connected" ;
2020-03-30 11:44:18 +00:00
}
2019-02-23 13:15:22 +00:00
2020-08-10 12:41:34 +00:00
getVoiceConnection ( ) : AbstractVoiceConnection {
2021-03-16 14:55:27 +00:00
return this . voiceConnection ;
2020-11-07 12:16:07 +00:00
}
getVideoConnection ( ) : VideoConnection {
return this . videoConnection ;
2020-03-30 11:44:18 +00:00
}
2019-04-04 19:47:52 +00:00
2021-04-27 11:30:33 +00:00
getCommandHandler ( ) : AbstractCommandHandlerBoss {
2020-07-23 22:29:36 +00:00
return this . commandHandlerBoss ;
2020-03-30 11:44:18 +00:00
}
2019-04-04 19:47:52 +00:00
2020-03-30 11:44:18 +00:00
handshake_handler ( ) : HandshakeHandler {
2020-07-23 22:29:36 +00:00
return this . handshakeHandler ;
2020-03-30 11:44:18 +00:00
}
2019-08-21 08:00:01 +00:00
2020-03-30 11:44:18 +00:00
remote_address ( ) : ServerAddress {
2020-07-23 22:29:36 +00:00
return this . remoteServerAddress ;
2020-03-30 11:44:18 +00:00
}
2019-08-21 08:00:01 +00:00
2020-07-25 11:56:30 +00:00
connectionProxyAddress ( ) : ServerAddress | undefined {
return this . socket ? . address ;
}
2020-07-23 22:29:36 +00:00
private doNextPing() {
if ( this . pingStatistics . lastRequestTimestamp + this . pingStatistics . timeout < Date . now ( ) ) {
this . pingStatistics . currentJsValue = this . pingStatistics . timeout ;
this . pingStatistics . lastResponseTimestamp = this . pingStatistics . lastRequestTimestamp + 1 ;
2020-03-30 11:44:18 +00:00
}
2020-07-23 22:29:36 +00:00
if ( this . pingStatistics . lastResponseTimestamp > this . pingStatistics . lastRequestTimestamp ) {
this . pingStatistics . lastRequestTimestamp = 'now' in performance ? performance . now ( ) : Date . now ( ) ;
2020-03-30 11:44:18 +00:00
this . sendData ( JSON . stringify ( {
type : 'ping' ,
2020-07-23 22:29:36 +00:00
payload : ( ++ this . pingStatistics . currentRequestId ) . toString ( )
2020-03-30 11:44:18 +00:00
} ) ) ;
2019-08-21 08:00:01 +00:00
}
2019-04-15 13:33:51 +00:00
}
2020-12-09 12:36:56 +00:00
ping ( ) : ConnectionPing {
2020-03-30 11:44:18 +00:00
return {
2020-07-23 22:29:36 +00:00
javascript : this.pingStatistics.currentJsValue ,
2020-12-09 12:36:56 +00:00
/* if the native value is zero that means we don't have any */
native : this.pingStatistics.currentNativeValue === 0 ? this . pingStatistics.currentJsValue : this.pingStatistics.currentNativeValue
2020-03-30 11:44:18 +00:00
} ;
2019-02-23 13:15:22 +00:00
}
2020-11-16 20:02:18 +00:00
getControlStatistics ( ) : ConnectionStatistics {
return this . socket ? . getControlStatistics ( ) || { bytesSend : 0 , bytesReceived : 0 } ;
}
2021-02-15 18:03:22 +00:00
getServerType ( ) : "teaspeak" | "teamspeak" | "unknown" {
/* It's simple. Only TeaSpeak support web clients */
return "teaspeak" ;
}
2019-02-23 13:15:22 +00:00
}