596 lines
25 KiB
TypeScript
596 lines
25 KiB
TypeScript
|
/// <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,
|
||
|
CONNECT_FAILURE,
|
||
|
CONNECTION_CLOSED,
|
||
|
CONNECTION_FATAL_ERROR,
|
||
|
CONNECTION_PING_TIMEOUT,
|
||
|
CLIENT_KICKED,
|
||
|
CLIENT_BANNED,
|
||
|
HANDSHAKE_FAILED,
|
||
|
SERVER_CLOSED,
|
||
|
SERVER_REQUIRES_PASSWORD,
|
||
|
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;
|
||
|
|
||
|
serverConnection: connection.ServerConnection;
|
||
|
|
||
|
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;
|
||
|
|
||
|
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);
|
||
|
|
||
|
this.serverConnection = new connection.ServerConnection(this);
|
||
|
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});
|
||
|
|
||
|
if(password && !password.hashed) {
|
||
|
helpers.hashPassword(password.password).then(password => {
|
||
|
/* errors will be already handled via the handle disconnect thing */
|
||
|
this.serverConnection.connect({host, port}, new connection.HandshakeHandler(profile, name, password));
|
||
|
}).catch(error => {
|
||
|
createErrorModal(tr("Error while hashing password"), tr("Failed to hash server password!<br>") + error).open();
|
||
|
})
|
||
|
} else {
|
||
|
/* errors will be already handled via the handle disconnect thing */
|
||
|
this.serverConnection.connect({host, port}, new connection.HandshakeHandler(profile, name, password ? password.password : undefined));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
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;
|
||
|
}
|
||
|
|
||
|
getServerConnection() : connection.ServerConnection { return this.serverConnection; }
|
||
|
|
||
|
|
||
|
/**
|
||
|
* 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,
|
||
|
connect_profile: this.serverConnection._handshakeHandler.profile.id,
|
||
|
connect_address: this.serverConnection._remote_address.host + (this.serverConnection._remote_address.port !== 9987 ? ":" + this.serverConnection._remote_address.port : "")
|
||
|
};
|
||
|
|
||
|
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("&");
|
||
|
|
||
|
return "https://" + this.serverConnection._remote_address.host + ":" + this.serverConnection._remote_address.port + "/?forward_url=" + encodeURIComponent(callback);
|
||
|
};
|
||
|
|
||
|
/* 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 = {}) {
|
||
|
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;
|
||
|
case DisconnectReason.CONNECT_FAILURE:
|
||
|
if(this._reconnect_attempt) {
|
||
|
auto_reconnect = true;
|
||
|
this.chat.serverChat().appendError(tr("Connect failed"));
|
||
|
break;
|
||
|
}
|
||
|
console.error(tr("Could not connect to remote host! Exception: %o"), data);
|
||
|
|
||
|
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.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;
|
||
|
this.startConnection(this.serverConnection._remote_address.host + ":" + this.serverConnection._remote_address.port,
|
||
|
this.serverConnection._handshakeHandler.profile,
|
||
|
this.serverConnection._handshakeHandler.name,
|
||
|
{password: password as string, hashed: false});
|
||
|
}).open();
|
||
|
break;
|
||
|
case DisconnectReason.CLIENT_KICKED:
|
||
|
this.chat.serverChat().appendError(tr("You got kicked from the server by {0}{1}"),
|
||
|
ClientEntry.chatTag(data["invokerid"], data["invokername"], data["invokeruid"]),
|
||
|
data["reasonmsg"] ? " (" + data["reasonmsg"] + ")" : "");
|
||
|
this.sound.play(Sound.SERVER_KICKED);
|
||
|
auto_reconnect = true;
|
||
|
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"));
|
||
|
const server_address = this.serverConnection._remote_address;
|
||
|
const profile = this.serverConnection._handshakeHandler.profile;
|
||
|
const name = this.serverConnection._handshakeHandler.name;
|
||
|
const password = this.serverConnection._handshakeHandler.server_password;
|
||
|
|
||
|
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())) {
|
||
|
const encoding_supported = vconnection.encoding_supported(targetChannel.properties.channel_codec);
|
||
|
const decoding_supported = vconnection.decoding_supported(targetChannel.properties.channel_codec);
|
||
|
|
||
|
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);
|
||
|
}
|
||
|
}
|