TeaWeb/shared/js/ConnectionHandler.ts

664 lines
29 KiB
TypeScript
Raw Normal View History

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,
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});
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
};
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;
case DisconnectReason.HANDSHAKE_TEAMSPEAK_REQUIRED:
createErrorModal(
tr("Target server is a TeamSpeak server"),
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."))
).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:
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"] + ")" : "");
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);
}
}