diff --git a/.travis.yml b/.travis.yml
index a4653a9c..c15d192d 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -6,9 +6,7 @@ services:
before_install:
- echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin
- - docker images
- docker pull $DOCKER_USERNAME/teaweb:build_new
- - docker images
jobs:
include:
@@ -16,15 +14,6 @@ jobs:
name: TeaWeb build master branch
script:
- "mkdir -p /tmp/build"
- - "docker run --rm -v /tmp/build/logs/:/build/logs/ -v /tmp/build/packages/:/build/packages/ -v `pwd`:/build/TeaWeb $DOCKER_USERNAME/teaweb:build_new --enable-release --enable-debug"
- - "ls -lah /tmp/build/"
- - "ls -lah /tmp/build/logs/"
- - "ls -lah /tmp/build/packages/"
- - "wget https://github.com/buildkite/github-release/releases/download/v1.0/github-release-linux-amd64 -O /tmp/git-release -q; chmod +x /tmp/git-release;"
- - >
- export GITHUB_RELEASE_TAG="This is a auto build release from travis";
- export GITHUB_RELEASE_REPOSITORY="$TRAVIS_REPO_SLUG";
- export GITHUB_RELEASE_ACCESS_TOKEN="$GIT_AUTHTOKEN";
- echo "Release commit: $TRAVIS_COMMIT"; export GITHUB_RELEASE_COMMIT="$TRAVIS_COMMIT";
- /tmp/git-release "Travis autobuild $TRAVIS_COMMIT" /tmp/build/packages/* /tmp/build/logs/*;
+ - "docker run --rm -v /tmp/build/logs/:/build/logs/ -v /tmp/build/packages/:/build/packages/ -v `pwd`:/build/TeaWeb $DOCKER_USERNAME/teaweb:build_new --enable-release --enable-debug || travis_terminate 1;"
+ - "./scripts/travis_deploy.sh || travis_terminate 1;"
if: branch = master
\ No newline at end of file
diff --git a/scripts/build_declarations.sh b/scripts/build_declarations.sh
index afbc292e..ae1f35a1 100755
--- a/scripts/build_declarations.sh
+++ b/scripts/build_declarations.sh
@@ -21,7 +21,7 @@ function replace_tribble() {
#Building the generator
./tools/build_dtsgen.sh
-if [ $? -ne 0 ]; then
+if [[ $? -ne 0 ]]; then
echo "Failed to build typescript declaration generator"
exit 1
fi
diff --git a/scripts/cleanup.sh b/scripts/cleanup.sh
new file mode 100755
index 00000000..2ae764e5
--- /dev/null
+++ b/scripts/cleanup.sh
@@ -0,0 +1,25 @@
+#!/usr/bin/env bash
+
+BASEDIR=$(dirname "$0")
+cd "$BASEDIR/../"
+
+# This script cleanups all generated files
+function remove_if_exists() {
+ if [[ -f "$1" ]] || [[ -d "$1" ]]; then
+ echo "Deleting $1"
+ rm -r "$1"
+ fi
+}
+
+function cleanup_declarations() {
+ remove_if_exists shared/declarations/
+ remove_if_exists web/declarations/
+}
+
+function cleanup_generated_files() {
+ remove_if_exists shared/generated
+ remove_if_exists web/generated
+}
+
+cleanup_declarations
+cleanup_generated_files
\ No newline at end of file
diff --git a/scripts/travis_deploy.sh b/scripts/travis_deploy.sh
new file mode 100755
index 00000000..b35f0483
--- /dev/null
+++ b/scripts/travis_deploy.sh
@@ -0,0 +1,93 @@
+#!/usr/bin/env bash
+
+if [[ -z "${GIT_AUTHTOKEN}" ]]; then
+ echo "Missing environment variable GIT_AUTHTOKEN. Please set it before usign this script!"
+ exit 1
+fi
+
+GIT_COMMIT_SHORT=$(git rev-parse --short HEAD)
+GIT_COMMIT_LONG=$(git rev-parse HEAD)
+echo "Deploying $GIT_COMMIT_SHORT ($GIT_COMMIT_LONG) to github."
+
+GIT_RELEASE_EXECUTABLE="/tmp/git-release"
+if [[ ! -x ${GIT_RELEASE_EXECUTABLE} ]]; then
+ if [[ ! -f ${GIT_RELEASE_EXECUTABLE} ]]; then
+ echo "Downloading github-release-linux (1.2.4)"
+
+ if [[ -f /tmp/git-release.gz ]]; then
+ rm /tmp/git-release.gz
+ fi
+ wget https://github.com/tfausak/github-release/releases/download/1.2.4/github-release-linux.gz -O /tmp/git-release.gz -q;
+ [[ $? -eq 0 ]] || {
+ echo "Failed to download github-release-linux"
+ exit 1
+ }
+
+ gunzip /tmp/git-release.gz && chmod +x /tmp/git-release;
+ [[ $? -eq 0 ]] || {
+ echo "Failed to unzip github-release-linux"
+ exit 1
+ }
+
+ echo "Download of github-release-linux (1.2.4) finished"
+ else
+ chmod +x ${GIT_RELEASE_EXECUTABLE}
+ fi
+
+ if [[ ! -x ${GIT_RELEASE_EXECUTABLE} ]]; then
+ echo "git-release isn't executable"
+ exit 1
+ fi
+fi
+
+echo "Generating release"
+${GIT_RELEASE_EXECUTABLE} release \
+ --repo "TeaWeb" \
+ --owner "TeaSpeak" \
+ --token "${GIT_AUTHTOKEN}" \
+ --title "Travis autobuild ${GIT_COMMIT_SHORT}" \
+ --tag "${GIT_COMMIT_SHORT}" \
+ --description "This is a autobuild release from travis"
+[[ $? -eq 0 ]] || {
+ echo "Failed to generate git release"
+ exit 1
+}
+
+echo "Uploading release files"
+folders=("/tmp/build/logs/" "/tmp/build/packages/")
+uploaded_files=()
+failed_files=()
+
+for folder in "${folders[@]}"; do
+ echo "Scanning folder $folder"
+ if [[ ! -d ${folder} ]]; then
+ continue;
+ fi
+
+ for file in ${folder}*; do
+ if [[ -d ${file} ]]; then
+ echo " Skipping directory `basename $file` ($file)"
+ continue
+ fi
+ echo " Found entry `basename $file` ($file). Uploading file.";
+
+ ${GIT_RELEASE_EXECUTABLE} upload \
+ --repo "TeaWeb" \
+ --owner "TeaSpeak" \
+ --token "${GIT_AUTHTOKEN}" \
+ --tag "${GIT_COMMIT_SHORT}" \
+ --file "$file" \
+ --name "`basename $file`"
+
+ [[ $? -eq 0 ]] && {
+ echo " Uploaded.";
+ uploaded_files+="$file"
+ } || {
+ echo "Failed to generate git release"
+ failed_files+="$file"
+ }
+ done
+done
+
+echo "Successfully uploaded ${#uploaded_files[@]} files. ${#failed_files[@]} uploads failed."
+exit 0
\ No newline at end of file
diff --git a/shared/css/static/ts/icons.scss b/shared/css/static/ts/icons.scss
index 9eb76e8b..58c69698 100644
--- a/shared/css/static/ts/icons.scss
+++ b/shared/css/static/ts/icons.scss
@@ -8,7 +8,7 @@
width: 16px;
height: 16px;
- background: url('../../../img/client_icon_sprite.svg') no-repeat;
+ background: url('../../../img/client_icon_sprite.svg'), url('../../img/client_icon_sprite.svg') no-repeat;
}
/* Icons x16 */
@@ -612,7 +612,7 @@
.icon_x32 {
display: inline-block;
- background: url('../../../img/client_icon_sprite.svg') no-repeat;
+ background: url('../../../img/client_icon_sprite.svg'), url('../../img/client_icon_sprite.svg') no-repeat;
background-size: calc(496px * 2) calc(400px * 2);
height: 32px;
width: 32px;
diff --git a/shared/generate_packed.sh b/shared/generate_packed.sh
index 324c918c..86da58ae 100755
--- a/shared/generate_packed.sh
+++ b/shared/generate_packed.sh
@@ -12,6 +12,7 @@ if [[ -e ${LOADER_FILE} ]]; then
echo "Failed to remove loader file!\nThis could be critical later!"
fi
fi
+
npm run dtsgen -- --config $(pwd)/tsconfig/dtsconfig_loader.json -v
if [[ ! -e ${LOADER_FILE} ]]; then
echo "Failed to generate definitions"
@@ -19,6 +20,18 @@ if [[ ! -e ${LOADER_FILE} ]]; then
exit 1
fi
+npm run dtsgen -- --config $(pwd)/tsconfig/dtsconfig_packed.json -v
+if [[ $? -ne 0 ]]; then
+ echo "Failed to generate definitions for the loader"
+ exit 1
+fi
+
+execute_ttsc -p tsconfig/tsconfig_packed_loader.json
+if [[ $? -ne 0 ]]; then
+ echo "Failed to generate packed loader file!"
+ exit 1
+fi
+
execute_ttsc -p tsconfig/tsconfig_packed.json
if [[ $? -ne 0 ]]; then
echo "Failed to generate packed file!"
diff --git a/shared/js/FileManager.ts b/shared/js/FileManager.ts
index ef5b5875..1c0ad5e1 100644
--- a/shared/js/FileManager.ts
+++ b/shared/js/FileManager.ts
@@ -1,4 +1,5 @@
///
+///
class FileEntry {
name: string;
@@ -109,7 +110,7 @@ class DownloadFileTransfer {
}
}
-class FileManager {
+class FileManager extends connection.AbstractCommandHandler {
handle: TSClient;
icons: IconManager;
avatars: AvatarManager;
@@ -119,13 +120,28 @@ class FileManager {
private downloadCounter : number = 0;
constructor(client: TSClient) {
+ super(client.serverConnection);
+
this.handle = client;
this.icons = new IconManager(this);
this.avatars = new AvatarManager(this);
- this.handle.serverConnection.commandHandler["notifyfilelist"] = this.notifyFileList.bind(this);
- this.handle.serverConnection.commandHandler["notifyfilelistfinished"] = this.notifyFileListFinished.bind(this);
- this.handle.serverConnection.commandHandler["notifystartdownload"] = this.notifyStartDownload.bind(this);
+ this.connection.command_handler_boss().register_handler(this);
+ }
+
+ handle_command(command: connection.ServerCommand): boolean {
+ switch (command.command) {
+ case "notifyfilelist":
+ this.notifyFileList(command.arguments);
+ return true;
+ case "notifyfilelistfinished":
+ this.notifyFileListFinished(command.arguments);
+ return true;
+ case "notifystartdownload":
+ this.notifyStartDownload(command.arguments);
+ return true;
+ }
+ return false;
}
@@ -140,7 +156,7 @@ class FileManager {
req.callback = accept;
_this.listRequests.push(req);
- _this.handle.serverConnection.sendCommand("ftgetfilelist", {"path": path, "cid": (channel ? channel.channelId : "0"), "cpw": (password ? password : "")}).then(() => {}).catch(reason => {
+ _this.handle.serverConnection.send_command("ftgetfilelist", {"path": path, "cid": (channel ? channel.channelId : "0"), "cpw": (password ? password : "")}).then(() => {}).catch(reason => {
_this.listRequests.remove(req);
if(reason instanceof CommandResult) {
if(reason.id == 0x0501) {
@@ -197,7 +213,7 @@ class FileManager {
this.pendingDownloadTransfers.push(transfer);
return new Promise((resolve, reject) => {
transfer["_promiseCallback"] = resolve;
- _this.handle.serverConnection.sendCommand("ftinitdownload", {
+ _this.handle.serverConnection.send_command("ftinitdownload", {
"path": path,
"name": file,
"cid": (channel ? channel.channelId : "0"),
diff --git a/shared/js/chat.ts b/shared/js/chat.ts
index 83df61f1..bf76bf3e 100644
--- a/shared/js/chat.ts
+++ b/shared/js/chat.ts
@@ -354,7 +354,13 @@ class ChatBox {
chat.serverChat().appendError(tr("Could not send chant message (Not connected)"));
return;
}
- globalClient.serverConnection.sendMessage(text, ChatType.SERVER);
+ globalClient.serverConnection.command_helper.sendMessage(text, ChatType.SERVER).catch(error => {
+ if(error instanceof CommandResult)
+ return;
+
+ chat.serverChat().appendMessage(tr("Failed to send text message."));
+ console.error(tr("Failed to send server text message: %o"), error);
+ });
};
this.serverChat().name = tr("Server chat");
@@ -364,7 +370,10 @@ class ChatBox {
return;
}
- globalClient.serverConnection.sendMessage(text, ChatType.CHANNEL, globalClient.getClient().currentChannel());
+ globalClient.serverConnection.command_helper.sendMessage(text, ChatType.CHANNEL, globalClient.getClient().currentChannel()).catch(error => {
+ chat.channelChat().appendMessage(tr("Failed to send text message."));
+ console.error(tr("Failed to send channel text message: %o"), error);
+ });
};
this.channelChat().name = tr("Channel chat");
diff --git a/shared/js/client.ts b/shared/js/client.ts
index a529ae14..ef02b4a1 100644
--- a/shared/js/client.ts
+++ b/shared/js/client.ts
@@ -2,13 +2,13 @@
///
///
///
-///
///
///
///
///
///
///
+///
enum DisconnectReason {
REQUESTED,
@@ -49,7 +49,7 @@ enum ViewReasonId {
class TSClient {
channelTree: ChannelTree;
- serverConnection: ServerConnection;
+ serverConnection: connection.ServerConnection;
voiceConnection: VoiceConnection;
fileManager: FileManager;
selectInfo: InfoBar;
@@ -65,7 +65,7 @@ class TSClient {
constructor() {
this.selectInfo = new InfoBar(this, $("#select_info"));
this.channelTree = new ChannelTree(this, $("#channelTree"));
- this.serverConnection = new ServerConnection(this);
+ this.serverConnection = new connection.ServerConnection(this);
this.fileManager = new FileManager(this);
this.permissions = new PermissionManager(this);
this.groups = new GroupManager(this);
@@ -101,12 +101,15 @@ class TSClient {
if(password && !password.hashed) {
helpers.hashPassword(password.password).then(password => {
- this.serverConnection.startConnection({host, port}, new HandshakeHandler(profile, name, 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!
") + error).open();
})
- } else
- this.serverConnection.startConnection({host, port}, new HandshakeHandler(profile, name, password ? password.password : undefined));
+ } 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));
+ }
}
@@ -122,7 +125,7 @@ class TSClient {
return this._clientId;
}
- getServerConnection() : ServerConnection { return this.serverConnection; }
+ getServerConnection() : connection.ServerConnection { return this.serverConnection; }
/**
@@ -133,7 +136,7 @@ class TSClient {
this.channelTree.registerClient(this._ownEntry);
settings.setServer(this.channelTree.server);
this.permissions.requestPermissionList();
- this.serverConnection.sendCommand("channelsubscribeall");
+ this.serverConnection.send_command("channelsubscribeall");
if(this.groups.serverGroups.length == 0)
this.groups.requestGroups();
this.controlBar.updateProperties();
@@ -142,7 +145,7 @@ class TSClient {
}
get connected() : boolean {
- return !!this.serverConnection && this.serverConnection.connected;
+ return this.serverConnection && this.serverConnection.connected();
}
private certAcceptUrl() {
diff --git a/shared/js/connection.ts b/shared/js/connection.ts
deleted file mode 100644
index d9f5dbdd..00000000
--- a/shared/js/connection.ts
+++ /dev/null
@@ -1,1336 +0,0 @@
-///
-///
-///
-///
-
-import noExitRuntime = Module.noExitRuntime;
-
-enum ErrorID {
- PERMISSION_ERROR = 2568,
- EMPTY_RESULT = 0x0501,
- PLAYLIST_IS_IN_USE = 0x2103
-}
-
-class CommandResult {
- success: boolean;
- id: number;
- message: string;
- extra_message: string;
-
- json: any;
-
- constructor(json) {
- this.json = json;
- this.id = json["id"];
- this.message = json["msg"];
-
- this.extra_message = "";
- if(json["extra_msg"]) this.extra_message = json["extra_msg"];
-
- this.success = this.id == 0;
- }
-}
-
-class ReturnListener {
- resolve: (value?: T | PromiseLike) => void;
- reject: (reason?: any) => void;
- code: string;
-
- timeout: NodeJS.Timer;
-}
-
-class ServerConnection {
- _client: TSClient;
- _remote_address: ServerAddress;
- _socket: WebSocket;
- _connectionState: ConnectionState = ConnectionState.UNCONNECTED;
- _handshakeHandler: HandshakeHandler;
- commandHandler: ConnectionCommandHandler;
-
- readonly helper: CommandHelper;
- private _connectTimeoutHandler: NodeJS.Timer = undefined;
- private _connected: boolean = false;
- private _retCodeIdx: number;
- private _retListener: ReturnListener[];
-
- constructor(client : TSClient) {
- this._client = client;
-
- this._socket = null;
- this.commandHandler = new ConnectionCommandHandler(this);
- this.helper = new CommandHelper(this);
- this._retCodeIdx = 0;
- this._retListener = [];
- }
-
- on_connect: () => void = () => {
- console.log(tr("Socket connected"));
- chat.serverChat().appendMessage(tr("Logging in..."));
- this._handshakeHandler.startHandshake();
- };
-
- private generateReturnCode() : string {
- return (this._retCodeIdx++).toString();
- }
-
- startConnection(address : ServerAddress, handshake: HandshakeHandler, timeout: number = 1000) {
- if(this._connectTimeoutHandler) {
- clearTimeout(this._connectTimeoutHandler);
- this._connectTimeoutHandler = null;
- this.disconnect();
- }
- this.updateConnectionState(ConnectionState.CONNECTING);
- this._remote_address = address;
- this._handshakeHandler = handshake;
- this._handshakeHandler.setConnection(this);
- this._connected = false;
- chat.serverChat().appendMessage(tr("Connecting to {0}:{1}"), true, address.host, address.port);
-
- const self = this;
- try {
- this._connectTimeoutHandler = setTimeout(() => {
- console.log(tr("Connect timeout triggered!"));
- this.disconnect();
- this._client.handleDisconnect(DisconnectReason.CONNECT_FAILURE);
- }, timeout);
- let sockCpy;
- this._socket = (sockCpy = new WebSocket('wss://' + address.host + ":" + address.port));
- clearTimeout(this._connectTimeoutHandler);
- this._connectTimeoutHandler = null;
- if(this._socket != sockCpy) return; //Connect timeouted
-
- this._socket.onopen = () => {
- if(this._socket != sockCpy) return;
- this._connected = true;
- this.on_connect();
- };
-
- this._socket.onclose = event => {
- if(this._socket != sockCpy) return;
- this._client.handleDisconnect(this._connected ? DisconnectReason.CONNECTION_CLOSED : DisconnectReason.CONNECT_FAILURE, {
- code: event.code,
- reason: event.reason,
- event: event
- });
- };
-
- this._socket.onerror = e => {
- if(this._socket != sockCpy) return;
- console.log(tr("Got error: (%s)"), self._socket.readyState);
- console.log(e);
- };
-
- this._socket.onmessage = msg => {
- if(this._socket != sockCpy) return;
- self.handleWebSocketMessage(msg.data);
- };
- this.updateConnectionState(ConnectionState.INITIALISING);
- } catch (e) {
- this.disconnect();
- this._client.handleDisconnect(DisconnectReason.CONNECT_FAILURE, e);
- }
- }
-
- updateConnectionState(state: ConnectionState) {
- this._connectionState = state;
- this._client.controlBar.update_connection_state();
- }
-
- disconnect() : boolean {
- if(this._connectionState == ConnectionState.UNCONNECTED) return false;
- this.updateConnectionState(ConnectionState.UNCONNECTED);
-
- if(this._socket) this._socket.close(3000 + 0xFF, tr("request disconnect"));
- this._socket = null;
- for(let future of this._retListener)
- future.reject(tr("Connection closed"));
- this._retListener = [];
- this._retCodeIdx = 0;
- this._connected = false;
- return true;
- }
-
- private handleWebSocketMessage(data) {
- if(typeof(data) === "string") {
- let json;
- try {
- json = JSON.parse(data);
- } catch(e) {
- console.error(tr("Could not parse message json!"));
- alert(e); // error in the above string (in this case, yes)!
- return;
- }
- if(json["type"] === undefined) {
- console.log(tr("Missing data type!"));
- return;
- }
- if(json["type"] === "command") this.handleCommand(json);
- else if(json["type"] === "WebRTC") this._client.voiceConnection.handleControlPacket(json);
- else {
- console.log(tr("Unknown command type %o"), json["type"]);
- }
- }
- }
-
- handleCommand(json) {
- 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();
-
- try {
- let fn = this.commandHandler[json["command"]];
- if(fn === undefined) {
- group.log(tr("Missing command '%s'"), json["command"]);
- return;
- }
- fn.call(this.commandHandler, json["data"]);
- } finally {
- group.end();
- }
- }
-
- sendData(data: any) { //TODO check stuff?
- this._socket.send(data);
- }
-
- private commandiefy(input: any) : string {
- return JSON.stringify(input, (key, value) => {
- switch (typeof value) {
- case "boolean": return value == true ? "1" : "0";
- case "function": return value();
- default:
- return value;
- }
- });
- }
-
- sendCommand(command: string, data: any = {}, flags: string[] = [], logResult: boolean = true) : Promise {
- const _this = this;
- let result = new Promise((resolve, failed) => {
- let _data = $.isArray(data) ? data : [data];
- let retCode = _data[0]["return_code"] !== undefined ? _data[0].return_code : _this.generateReturnCode();
- _data[0].return_code = retCode;
-
- let listener = new ReturnListener();
- listener.resolve = resolve;
- listener.reject = failed;
- listener.code = retCode;
- listener.timeout = setTimeout(() => {
- _this._retListener.remove(listener);
- listener.reject("timeout");
- }, 1500);
- this._retListener.push(listener);
-
- this._socket.send(this.commandiefy({
- "type": "command",
- "command": command,
- "data": _data,
- "flags": flags.filter(entry => entry.length != 0)
- }));
- });
- return new Promise((resolve, failed) => {
- result.then(resolve).catch(ex => {
- if(logResult) {
- if(ex instanceof CommandResult) {
- let res = ex;
- if(!res.success) {
- if(res.id == 2568) { //Permission error
- res.message = tr("Insufficient client permissions. Failed on permission ") + this._client.permissions.resolveInfo(res.json["failed_permid"] as number).name;
- chat.serverChat().appendError(tr("Insufficient client permissions. Failed on permission {}"), this._client.permissions.resolveInfo(res.json["failed_permid"] as number).name);
- sound.play(Sound.ERROR_INSUFFICIENT_PERMISSIONS);
- } else {
- chat.serverChat().appendError(res.extra_message.length == 0 ? res.message : res.extra_message);
- }
- }
- } else if(typeof(ex) === "string") {
- chat.serverChat().appendError(tr("Command execution results in ") + ex);
- } else {
- console.error(tr("Invalid promise result type: %o. Result:"), typeof (ex));
- console.error(ex);
- }
- }
- failed(ex);
- })
- });
- }
-
- get connected() : boolean {
- return this._socket && this._socket.readyState == WebSocket.OPEN;
- }
-
- /**
- * HELPER METHODS
- */
- joinChannel(channel: ChannelEntry, password: string = "") : Promise {
- return this.sendCommand("clientmove", [{
- "clid": this._client.getClientId(),
- "cid": channel.getChannelId(),
- "cpw": password
- }])
- }
-
- sendMessage(message: string, type: ChatType, target?: ChannelEntry | ClientEntry) : Promise {
- if(type == ChatType.SERVER)
- return this.sendCommand("sendtextmessage", {"targetmode": 3, "target": 0, "msg": message});
- else if(type == ChatType.CHANNEL)
- return this.sendCommand("sendtextmessage", {"targetmode": 2, "target": (target as ChannelEntry).getChannelId(), "msg": message});
- else if(type == ChatType.CLIENT)
- return this.sendCommand("sendtextmessage", {"targetmode": 1, "target": (target as ClientEntry).clientId(), "msg": message});
- }
-
- updateClient(key: string, value: string) : Promise {
- let data = {};
- data[key] = value;
- return this.sendCommand("clientupdate", data);
- }
-}
-
-interface HandshakeIdentityHandler {
- connection: ServerConnection;
-
- start_handshake();
- register_callback(callback: (success: boolean, message?: string) => any);
-}
-
-class HandshakeHandler {
- private connection: ServerConnection;
- private handshake_handler: HandshakeIdentityHandler;
- private failed = false;
-
- readonly profile: profiles.ConnectionProfile;
- readonly name: string;
- readonly server_password: string;
-
- constructor(profile: profiles.ConnectionProfile, name: string, password: string) {
- this.profile = profile;
- this.server_password = password;
- this.name = name;
- }
-
- setConnection(con: ServerConnection) {
- this.connection = con;
- }
-
- startHandshake() {
- this.handshake_handler = this.profile.spawn_identity_handshake_handler(this.connection);
- if(!this.handshake_handler) {
- this.handshake_failed("failed to create identity handler");
- return;
- }
-
- this.handshake_handler.register_callback((flag, message) => {
- if(flag)
- this.handshake_finished();
- else
- this.handshake_failed(message);
- });
-
- this.handshake_handler.start_handshake();
- }
-
- private handshake_failed(message: string) {
- if(this.failed) return;
-
- this.failed = true;
- this.connection._client.handleDisconnect(DisconnectReason.HANDSHAKE_FAILED, message);
- }
-
- private handshake_finished(version?: string) {
- if(native_client && window["native"] && native.client_version && !version) {
- native.client_version()
- .then( this.handshake_finished.bind(this))
- .catch(error => {
- console.error(tr("Failed to get version:"));
- console.error(error);
- this.handshake_finished("?.?.?");
- });
- return;
- }
-
- const git_version = settings.static_global("version", "unknown");
- const browser_name = (navigator.browserSpecs || {})["name"] || " ";
- let data = {
- //TODO variables!
- client_nickname: this.name,
- client_platform: (browser_name ? browser_name + " " : "") + navigator.platform,
- client_version: "TeaWeb " + git_version + " (" + navigator.userAgent + ")",
-
- client_server_password: this.server_password,
- client_browser_engine: navigator.product
- };
-
- if(version) {
- data.client_version = "TeaClient ";
- data.client_version += " " + version;
-
- const os = require("os");
- const arch_mapping = {
- "x32": "32bit",
- "x64": "64bit"
- };
-
- data.client_version += " " + (arch_mapping[os.arch()] || os.arch());
-
- const os_mapping = {
- "win32": "Windows",
- "linux": "Linux"
- };
- data.client_platform = (os_mapping[os.platform()] || os.platform());
- }
-
- this.connection.sendCommand("clientinit", data).catch(error => {
- this.connection.disconnect();
- if(error instanceof CommandResult) {
- if(error.id == 1028) {
- this.connection._client.handleDisconnect(DisconnectReason.SERVER_REQUIRES_PASSWORD);
- } else {
-
- this.connection._client.handleDisconnect(DisconnectReason.CLIENT_KICKED, error);
- }
- }
- });
- }
-}
-
-interface ClientNameInfo {
- //cluid=tYzKUryn\/\/Y8VBMf8PHUT6B1eiE= name=Exp clname=Exp cldbid=9
- client_unique_id: string;
- client_nickname: string;
- client_database_id: number;
-}
-
-interface ClientNameFromUid {
- promise: LaterPromise,
- keys: string[],
- response: ClientNameInfo[]
-}
-
-interface QueryListEntry {
- username: string;
- unique_id: string;
- bounded_server: number;
-}
-
-interface QueryList {
- flag_own: boolean;
- flag_all: boolean;
-
- queries: QueryListEntry[];
-}
-
-interface Playlist {
- playlist_id: number;
- playlist_bot_id: number;
- playlist_title: string;
- playlist_type: number;
- playlist_owner_dbid: number;
- playlist_owner_name: string;
-
- needed_power_modify: number;
- needed_power_permission_modify: number;
- needed_power_delete: number;
- needed_power_song_add: number;
- needed_power_song_move: number;
- needed_power_song_remove: number;
-}
-
-interface PlaylistInfo {
- playlist_id: number,
- playlist_title: string,
- playlist_description: string,
- playlist_type: number,
-
- playlist_owner_dbid: number,
- playlist_owner_name: string,
-
- playlist_flag_delete_played: boolean,
- playlist_flag_finished: boolean,
- playlist_replay_mode: number,
- playlist_current_song_id: number,
-}
-
-interface PlaylistSong {
- song_id: number;
- song_previous_song_id: number;
- song_invoker: string;
- song_url: string;
- song_url_loader: string;
- song_loaded: boolean;
- song_metadata: string;
-}
-
-class CommandHelper {
- readonly connection: ServerConnection;
-
- private _callbacks_namefromuid: ClientNameFromUid[] = [];
- private _who_am_i: any;
-
- constructor(connection) {
- this.connection = connection;
- this.connection.commandHandler["notifyclientnamefromuid"] = this.handle_notifyclientnamefromuid.bind(this);
- }
-
- info_from_uid(...uid: string[]) : Promise {
- let uids = [...uid];
- for(let p of this._callbacks_namefromuid)
- if(p.keys == uids) return p.promise;
-
- let req: ClientNameFromUid = {} as any;
- req.keys = uids;
- req.response = new Array(uids.length);
- req.promise = new LaterPromise();
-
- for(let uid of uids) {
- this.connection.sendCommand("clientgetnamefromuid", {
- cluid: uid
- }).catch(req.promise.function_rejected());
- }
-
- this._callbacks_namefromuid.push(req);
- return req.promise;
- }
-
- request_query_list(server_id: number = undefined) : Promise {
- return new Promise((resolve, reject) => {
- this.connection.commandHandler["notifyquerylist"] = json => {
- const result = {} as QueryList;
-
- result.flag_all = json[0]["flag_all"];
- result.flag_own = json[0]["flag_own"];
- result.queries = [];
-
- for(const entry of json) {
- const rentry = {} as QueryListEntry;
- rentry.bounded_server = entry["client_bounded_server"];
- rentry.username = entry["client_login_name"];
- rentry.unique_id = entry["client_unique_identifier"];
-
- result.queries.push(rentry);
- }
-
- resolve(result);
- this.connection.commandHandler["notifyquerylist"] = undefined;
- };
-
- let data = {};
- if(server_id !== undefined)
- data["server_id"] = server_id;
-
- this.connection.sendCommand("querylist", data).catch(error => {
- if(error instanceof CommandResult) {
- if(error.id == ErrorID.EMPTY_RESULT) {
- resolve(undefined);
- this.connection.commandHandler["notifyquerylist"] = undefined;
- return;
- }
- }
- reject(error);
- })
- });
- }
-
- request_playlist_list() : Promise {
- return new Promise((resolve, reject) => {
- const notify_handler = json => {
- const result: Playlist[] = [];
-
- for(const entry of json) {
- try {
- result.push({
- playlist_id: parseInt(entry["playlist_id"]),
- playlist_bot_id: parseInt(entry["playlist_bot_id"]),
- playlist_title: entry["playlist_title"],
- playlist_type: parseInt(entry["playlist_type"]),
- playlist_owner_dbid: parseInt(entry["playlist_owner_dbid"]),
- playlist_owner_name: entry["playlist_owner_name"],
-
- needed_power_modify: parseInt(entry["needed_power_modify"]),
- needed_power_permission_modify: parseInt(entry["needed_power_permission_modify"]),
- needed_power_delete: parseInt(entry["needed_power_delete"]),
- needed_power_song_add: parseInt(entry["needed_power_song_add"]),
- needed_power_song_move: parseInt(entry["needed_power_song_move"]),
- needed_power_song_remove: parseInt(entry["needed_power_song_remove"])
- });
- } catch(error) {
- log.error(LogCategory.NETWORKING, tr("Failed to parse playlist entry: %o"), error);
- }
- }
-
- this.connection.commandHandler.unset_handler("notifyplaylistlist", notify_handler);
- resolve(result);
- };
-
- this.connection.commandHandler.set_handler("notifyplaylistlist", notify_handler);
- this.connection.sendCommand("playlistlist").catch(error => {
- if(error instanceof CommandResult) {
- if(error.id == ErrorID.EMPTY_RESULT) {
- this.connection.commandHandler.unset_handler("notifyplaylistlist", notify_handler);
- resolve([]);
- return;
- }
- }
- reject(error);
- })
- });
- }
-
- request_playlist_songs(playlist_id: number) : Promise {
- return new Promise((resolve, reject) => {
- const notify_handler = json => {
- if(json[0]["playlist_id"] != playlist_id) {
- log.error(LogCategory.NETWORKING, tr("Received invalid notification for playlist songs"));
- return;
- }
-
- const result: PlaylistSong[] = [];
-
- for(const entry of json) {
- try {
- result.push({
- song_id: parseInt(entry["song_id"]),
- song_invoker: entry["song_invoker"],
- song_previous_song_id: parseInt(entry["song_previous_song_id"]),
- song_url: entry["song_url"],
- song_url_loader: entry["song_url_loader"],
-
- song_loaded: entry["song_loaded"] == true || entry["song_loaded"] == "1",
- song_metadata: entry["song_metadata"]
- });
- } catch(error) {
- log.error(LogCategory.NETWORKING, tr("Failed to parse playlist song entry: %o"), error);
- }
- }
-
- this.connection.commandHandler.unset_handler("notifyplaylistsonglist", notify_handler);
- resolve(result);
- };
-
- this.connection.commandHandler.set_handler("notifyplaylistsonglist", notify_handler);
- this.connection.sendCommand("playlistsonglist", {playlist_id: playlist_id}).catch(error => {
- if(error instanceof CommandResult) {
- if(error.id == ErrorID.EMPTY_RESULT) {
- this.connection.commandHandler.unset_handler("notifyplaylistsonglist", notify_handler);
- resolve([]);
- return;
- }
- }
- reject(error);
- })
- });
- }
-
- request_playlist_info(playlist_id: number) : Promise {
- return new Promise((resolve, reject) => {
- const notify_handler = json => {
- if(json[0]["playlist_id"] != playlist_id) {
- log.error(LogCategory.NETWORKING, tr("Received invalid notification for playlist info"));
- return;
- }
-
- json = json[0];
-
- try {
- //resolve
- resolve({
- playlist_id: parseInt(json["playlist_id"]),
- playlist_title: json["playlist_title"],
- playlist_description: json["playlist_description"],
- playlist_type: parseInt(json["playlist_type"]),
-
- playlist_owner_dbid: parseInt(json["playlist_owner_dbid"]),
- playlist_owner_name: json["playlist_owner_name"],
-
- playlist_flag_delete_played: json["playlist_flag_delete_played"] == true || json["playlist_flag_delete_played"] == "1",
- playlist_flag_finished: json["playlist_flag_finished"] == true || json["playlist_flag_finished"] == "1",
- playlist_replay_mode: parseInt(json["playlist_replay_mode"]),
- playlist_current_song_id: parseInt(json["playlist_current_song_id"]),
- });
- } catch(error) {
- log.error(LogCategory.NETWORKING, tr("Failed to parse playlist info: %o"), error);
- reject("failed to parse info");
- }
-
- this.connection.commandHandler.unset_handler("notifyplaylistinfo", notify_handler);
- };
-
- this.connection.commandHandler.set_handler("notifyplaylistinfo", notify_handler);
- this.connection.sendCommand("playlistinfo", {playlist_id: playlist_id}).catch(error => {
- reject(error);
- })
- });
- }
-
- /**
- * @deprecated
- * Its just a workaround for the query management.
- * There is no garante that the whoami trick will work forever
- */
- current_virtual_server_id() : Promise {
- if(this._who_am_i)
- return Promise.resolve(parseInt(this._who_am_i["virtualserver_id"]));
-
- return new Promise((resolve, reject) => {
- this.connection.commandHandler[""] = json => {
- this._who_am_i = json[0];
- resolve(parseInt(this._who_am_i["virtualserver_id"]));
- this.connection.commandHandler[""] = undefined;
- };
- this.connection.sendCommand("whoami");
- });
- }
-
- private handle_notifyclientnamefromuid(json: any[]) {
- for(let entry of json) {
- let info: ClientNameInfo = {} as any;
- info.client_unique_id = entry["cluid"];
- info.client_nickname = entry["clname"];
- info.client_database_id = parseInt(entry["cldbid"]);
-
- for(let elm of this._callbacks_namefromuid.slice(0)) {
- let unset = 0;
- for(let index = 0; index < elm.keys.length; index++) {
- if(elm.keys[index] == info.client_unique_id) {
- elm.response[index] = info;
- }
- if(elm.response[index] == undefined) unset++;
- }
- if(unset == 0) {
- this._callbacks_namefromuid.remove(elm);
- elm.promise.resolved(elm.response);
- }
- }
- }
- }
-}
-
-class ConnectionCommandHandler {
- readonly connection: ServerConnection;
-
- constructor(connection) {
- this.connection = connection;
- this["error"] = this.handleCommandResult;
- this["channellist"] = this.handleCommandChannelList;
- this["channellistfinished"] = this.handleCommandChannelListFinished;
- this["notifychannelcreated"] = this.handleCommandChannelCreate;
- this["notifychanneldeleted"] = this.handleCommandChannelDelete;
- this["notifychannelhide"] = this.handleCommandChannelHide;
- this["notifychannelshow"] = this.handleCommandChannelShow;
-
- this["notifycliententerview"] = this.handleCommandClientEnterView;
- this["notifyclientleftview"] = this.handleCommandClientLeftView;
- this["notifyclientmoved"] = this.handleNotifyClientMoved;
- this["initserver"] = this.handleCommandServerInit;
- this["notifychannelmoved"] = this.handleNotifyChannelMoved;
- this["notifychanneledited"] = this.handleNotifyChannelEdited;
- this["notifytextmessage"] = this.handleNotifyTextMessage;
- this["notifyclientupdated"] = this.handleNotifyClientUpdated;
- this["notifyserveredited"] = this.handleNotifyServerEdited;
- this["notifyserverupdated"] = this.handleNotifyServerUpdated;
-
- this["notifyclientpoke"] = this.handleNotifyClientPoke;
-
- this["notifymusicplayerinfo"] = this.handleNotifyMusicPlayerInfo;
-
- this["notifyservergroupclientadded"] = this.handleNotifyServerGroupClientAdd;
- this["notifyservergroupclientdeleted"] = this.handleNotifyServerGroupClientRemove;
- this["notifyclientchannelgroupchanged"] = this.handleNotifyClientChannelGroupChanged;
- }
-
- set_handler(command: string, handler: any) {
- this[command] = handler;
- }
-
- unset_handler(command: string, handler?: any) {
- if(handler && this[command] != handler) return;
- this[command] = undefined;
- }
-
- handleCommandResult(json) {
- json = json[0]; //Only one bulk
-
- let code : string = json["return_code"];
- if(code.length == 0) {
- console.log(tr("Invalid return code! (%o)"), json);
- return;
- }
- let retListeners = this.connection["_retListener"];
-
- for(let e of retListeners) {
- if(e.code != code) continue;
- retListeners.remove(e);
- let result = new CommandResult(json);
- if(result.success)
- e.resolve(result);
- else
- e.reject(result);
- break;
- }
- }
-
- handleCommandServerInit(json){
- //We could setup the voice channel
- console.log(tr("Setting up voice"));
- this.connection._client.voiceConnection.createSession();
-
-
- json = json[0]; //Only one bulk
-
- this.connection._client.clientId = parseInt(json["aclid"]);
- this.connection._client.getClient().updateVariables({key: "client_nickname", value: json["acn"]});
-
- let updates: {
- key: string,
- value: string
- }[] = [];
- for(let key in json) {
- if(key === "aclid") continue;
- if(key === "acn") continue;
-
- updates.push({key: key, value: json[key]});
- }
- this.connection._client.channelTree.server.updateVariables(false, ...updates);
-
-
- chat.serverChat().name = this.connection._client.channelTree.server.properties["virtualserver_name"];
- chat.serverChat().appendMessage(tr("Connected as {0}"), true, this.connection._client.getClient().createChatTag(true));
- sound.play(Sound.CONNECTION_CONNECTED);
- globalClient.onConnected();
- }
-
- private createChannelFromJson(json, ignoreOrder: boolean = false) {
- let tree = this.connection._client.channelTree;
-
- let channel = new ChannelEntry(parseInt(json["cid"]), json["channel_name"], tree.findChannel(json["cpid"]));
- tree.insertChannel(channel);
- if(json["channel_order"] !== "0") {
- let prev = tree.findChannel(json["channel_order"]);
- if(!prev && json["channel_order"] != 0) {
- if(!ignoreOrder) {
- console.error(tr("Invalid channel order id!"));
- return;
- }
- }
-
- let parent = tree.findChannel(json["cpid"]);
- if(!parent && json["cpid"] != 0) {
- console.error(tr("Invalid channel parent"));
- return;
- }
- tree.moveChannel(channel, prev, parent); //TODO test if channel exists!
- }
- if(ignoreOrder) {
- for(let ch of tree.channels) {
- if(ch.properties.channel_order == channel.channelId) {
- tree.moveChannel(ch, channel, channel.parent); //Corrent the order :)
- }
- }
- }
-
- let updates: {
- key: string,
- value: string
- }[] = [];
- for(let key in json) {
- if(key === "cid") continue;
- if(key === "cpid") continue;
- if(key === "invokerid") continue;
- if(key === "invokername") continue;
- if(key === "invokeruid") continue;
- if(key === "reasonid") continue;
-
- updates.push({key: key, value: json[key]});
- }
- channel.updateVariables(...updates);
- }
-
- handleCommandChannelList(json) {
- this.connection._client.channelTree.hide_channel_tree(); /* dont perform channel inserts on the dom to prevent style recalculations */
- console.log(tr("Got %d new channels"), json.length);
- for(let index = 0; index < json.length; index++)
- this.createChannelFromJson(json[index], true);
- }
-
-
- handleCommandChannelListFinished(json) {
- this.connection._client.channelTree.show_channel_tree();
- }
-
- handleCommandChannelCreate(json) {
- this.createChannelFromJson(json[0]);
- }
-
- handleCommandChannelShow(json) {
- this.createChannelFromJson(json[0]); //TODO may chat?
- }
-
- handleCommandChannelDelete(json) {
- let tree = this.connection._client.channelTree;
-
- console.log(tr("Got %d channel deletions"), json.length);
- for(let index = 0; index < json.length; index++) {
- let channel = tree.findChannel(json[index]["cid"]);
- if(!channel) {
- console.error(tr("Invalid channel onDelete (Unknown channel)"));
- continue;
- }
- tree.deleteChannel(channel);
- }
- }
-
- handleCommandChannelHide(json) {
- let tree = this.connection._client.channelTree;
-
- console.log(tr("Got %d channel hides"), json.length);
- for(let index = 0; index < json.length; index++) {
- let channel = tree.findChannel(json[index]["cid"]);
- if(!channel) {
- console.error(tr("Invalid channel on hide (Unknown channel)"));
- continue;
- }
- tree.deleteChannel(channel);
- }
- }
-
- handleCommandClientEnterView(json) {
- json = json[0]; //Only one bulk
- let tree = this.connection._client.channelTree;
-
- let client: ClientEntry;
- let channel = tree.findChannel(json["ctid"]);
- let old_channel = tree.findChannel(json["cfid"]);
-
- client = tree.findClient(json["clid"]);
-
- if(!client) {
- if(parseInt(json["client_type_exact"]) == ClientType.CLIENT_MUSIC) {
- client = new MusicClientEntry(parseInt(json["clid"]), json["client_nickname"]);
- } else {
- client = new ClientEntry(parseInt(json["clid"]), json["client_nickname"]);
- }
-
- client.properties.client_type = parseInt(json["client_type"]);
- client = tree.insertClient(client, channel);
- } else {
- if(client == this.connection._client.getClient())
- chat.channelChat().name = channel.channelName();
- tree.moveClient(client, channel);
- }
-
- if(this.connection._client.controlBar.query_visible || client.properties.client_type != ClientType.CLIENT_QUERY) {
- const own_channel = this.connection._client.getClient().currentChannel();
- if(json["reasonid"] == ViewReasonId.VREASON_USER_ACTION) {
- if(own_channel == channel)
- if(old_channel)
- sound.play(Sound.USER_ENTERED);
- else
- sound.play(Sound.USER_ENTERED_CONNECT);
- if(old_channel) {
- chat.serverChat().appendMessage(tr("{0} appeared from {1} to {2}"), true, client.createChatTag(true), old_channel.generate_tag(true), channel.generate_tag(true));
- } else {
- chat.serverChat().appendMessage(tr("{0} connected to channel {1}"), true, client.createChatTag(true), channel.generate_tag(true));
- }
- } else if(json["reasonid"] == ViewReasonId.VREASON_MOVED) {
- if(own_channel == channel)
- sound.play(Sound.USER_ENTERED_MOVED);
-
- chat.serverChat().appendMessage(tr("{0} appeared from {1} to {2}, moved by {3}"), true,
- client.createChatTag(true),
- old_channel ? old_channel.generate_tag(true) : undefined,
- channel.generate_tag(true),
- ClientEntry.chatTag(json["invokerid"], json["invokername"], json["invokeruid"]),
- );
- } else if(json["reasonid"] == ViewReasonId.VREASON_CHANNEL_KICK) {
- if(own_channel == channel)
- sound.play(Sound.USER_ENTERED_KICKED);
-
- chat.serverChat().appendMessage(tr("{0} appeared from {1} to {2}, kicked by {3}{4}"), true,
- client.createChatTag(true),
- old_channel ? old_channel.generate_tag(true) : undefined,
- channel.generate_tag(true),
- ClientEntry.chatTag(json["invokerid"], json["invokername"], json["invokeruid"]),
- json["reasonmsg"] > 0 ? " (" + json["msg"] + ")" : ""
- );
- } else {
- console.warn(tr("Unknown reasonid for %o"), json["reasonid"]);
- }
- }
-
- let updates: {
- key: string,
- value: string
- }[] = [];
-
- for(let key in json) {
- if(key == "cfid") continue;
- if(key == "ctid") continue;
- if(key === "invokerid") continue;
- if(key === "invokername") continue;
- if(key === "invokeruid") continue;
- if(key === "reasonid") continue;
-
- updates.push({key: key, value: json[key]});
- }
-
- client.updateVariables(...updates);
-
- if(client instanceof LocalClientEntry)
- this.connection._client.controlBar.updateVoice();
- }
-
- handleCommandClientLeftView(json) {
- json = json[0]; //Only one bulk
- let tree = this.connection._client.channelTree;
- let client = tree.findClient(json["clid"]);
- if(!client) {
- console.error(tr("Unknown client left!"));
- return 0;
- }
- if(client == this.connection._client.getClient()) {
- if(json["reasonid"] == ViewReasonId.VREASON_BAN) {
- this.connection._client.handleDisconnect(DisconnectReason.CLIENT_BANNED, json);
- } else if(json["reasonid"] == ViewReasonId.VREASON_SERVER_KICK) {
- this.connection._client.handleDisconnect(DisconnectReason.CLIENT_KICKED, json);
- } else if(json["reasonid"] == ViewReasonId.VREASON_SERVER_SHUTDOWN) {
- this.connection._client.handleDisconnect(DisconnectReason.SERVER_CLOSED, json);
- } else if(json["reasonid"] == ViewReasonId.VREASON_SERVER_STOPPED) {
- this.connection._client.handleDisconnect(DisconnectReason.SERVER_CLOSED, json);
- } else
- this.connection._client.handleDisconnect(DisconnectReason.UNKNOWN, json);
- return;
- }
-
- if(this.connection._client.controlBar.query_visible || client.properties.client_type != ClientType.CLIENT_QUERY) {
- const own_channel = this.connection._client.getClient().currentChannel();
- let channel_from = tree.findChannel(json["cfid"]);
- let channel_to = tree.findChannel(json["ctid"]);
-
- if(json["reasonid"] == ViewReasonId.VREASON_USER_ACTION) {
- chat.serverChat().appendMessage(tr("{0} disappeared from {1} to {2}"), true, client.createChatTag(true), channel_from.generate_tag(true), channel_to.generate_tag(true));
-
- if(channel_from == own_channel)
- sound.play(Sound.USER_LEFT);
- } else if(json["reasonid"] == ViewReasonId.VREASON_SERVER_LEFT) {
- chat.serverChat().appendMessage(tr("{0} left the server{1}"), true,
- client.createChatTag(true),
- json["reasonmsg"] ? " (" + json["reasonmsg"] + ")" : ""
- );
-
- if(channel_from == own_channel)
- sound.play(Sound.USER_LEFT_DISCONNECT);
- } else if(json["reasonid"] == ViewReasonId.VREASON_SERVER_KICK) {
- chat.serverChat().appendError(tr("{0} was kicked from the server by {1}.{2}"),
- client.createChatTag(true),
- ClientEntry.chatTag(json["invokerid"], json["invokername"], json["invokeruid"]),
- json["reasonmsg"] ? " (" + json["reasonmsg"] + ")" : ""
- );
- if(channel_from == own_channel)
- sound.play(Sound.USER_LEFT_KICKED_SERVER);
- } else if(json["reasonid"] == ViewReasonId.VREASON_CHANNEL_KICK) {
- chat.serverChat().appendError(tr("{0} was kicked from your channel by {1}.{2}"),
- client.createChatTag(true),
- ClientEntry.chatTag(json["invokerid"], json["invokername"], json["invokeruid"]),
- json["reasonmsg"] ? " (" + json["reasonmsg"] + ")" : ""
- );
-
- if(channel_from == own_channel)
- sound.play(Sound.USER_LEFT_KICKED_CHANNEL);
- } else if(json["reasonid"] == ViewReasonId.VREASON_BAN) {
- //"Mulus" was banned for 1 second from the server by "WolverinDEV" (Sry brauchte kurz ein opfer :P <3 (Nohomo))
- let duration = "permanently";
- if(json["bantime"])
- duration = "for " + formatDate(Number.parseInt(json["bantime"]));
-
- chat.serverChat().appendError(tr("{0} was banned {1} by {2}.{3}"),
- client.createChatTag(true),
- duration,
- ClientEntry.chatTag(json["invokerid"], json["invokername"], json["invokeruid"]),
- json["reasonmsg"] ? " (" + json["reasonmsg"] + ")" : ""
- );
-
- if(channel_from == own_channel)
- sound.play(Sound.USER_LEFT_BANNED);
- } else {
- console.error(tr("Unknown client left reason!"));
- }
- }
-
- tree.deleteClient(client);
- }
-
- handleNotifyClientMoved(json) {
- json = json[0]; //Only one bulk
- let tree = this.connection._client.channelTree;
- let client = tree.findClient(json["clid"]);
- let channel_to = tree.findChannel(json["ctid"]);
- let channel_from = tree.findChannel(json["cfid"]);
-
- if(!client) {
- console.error(tr("Unknown client move (Client)!"));
- return 0;
- }
-
- if(!channel_to) {
- console.error(tr("Unknown client move (Channel to)!"));
- return 0;
- }
- if(!channel_from) //Not critical
- console.error(tr("Unknown client move (Channel from)!"));
-
- let self = client instanceof LocalClientEntry;
- let current_clients;
- if(self) {
- chat.channelChat().name = channel_to.channelName();
- current_clients = client.channelTree.clientsByChannel(client.currentChannel())
- this.connection._client.controlBar.updateVoice(channel_to);
- }
-
- tree.moveClient(client, channel_to);
- for(const entry of current_clients || [])
- if(entry !== client) entry.getAudioController().stopAudio(true);
-
- const own_channel = this.connection._client.getClient().currentChannel();
- if(json["reasonid"] == ViewReasonId.VREASON_MOVED) {
- chat.serverChat().appendMessage(self ? tr("You was moved by {3} from channel {1} to {2}") : tr("{0} was moved from channel {1} to {2} by {3}"), true,
- client.createChatTag(true),
- channel_from ? channel_from.generate_tag(true) : undefined,
- channel_to.generate_tag(true),
- ClientEntry.chatTag(json["invokerid"], json["invokername"], json["invokeruid"])
- );
- if(self)
- sound.play(Sound.USER_MOVED_SELF);
- else if(own_channel == channel_to)
- sound.play(Sound.USER_ENTERED_MOVED);
- else if(own_channel == channel_from)
- sound.play(Sound.USER_LEFT_MOVED);
- } else if(json["reasonid"] == ViewReasonId.VREASON_USER_ACTION) {
- chat.serverChat().appendMessage(self ? tr("You switched from channel {1} to {2}") : tr("{0} switched from channel {1} to {2}"), true,
- client.createChatTag(true),
- channel_from ? channel_from.generate_tag(true) : undefined,
- channel_to.generate_tag(true)
- );
- if(self) {} //If we do an action we wait for the error response
- else if(own_channel == channel_to)
- sound.play(Sound.USER_ENTERED);
- else if(own_channel == channel_from)
- sound.play(Sound.USER_LEFT);
- } else if(json["reasonid"] == ViewReasonId.VREASON_CHANNEL_KICK) {
- chat.serverChat().appendMessage(self ? tr("You got kicked out of the channel {1} to channel {2} by {3}{4}") : tr("{0} got kicked from channel {1} to {2} by {3}{4}"), true,
- client.createChatTag(true),
- channel_from ? channel_from.generate_tag(true) : undefined,
- channel_to.generate_tag(true),
- ClientEntry.chatTag(json["invokerid"], json["invokername"], json["invokeruid"]),
- json["reasonmsg"] ? " (" + json["reasonmsg"] + ")" : ""
- );
- if(self) {
- sound.play(Sound.CHANNEL_KICKED);
- } else if(own_channel == channel_to)
- sound.play(Sound.USER_ENTERED_KICKED);
- else if(own_channel == channel_from)
- sound.play(Sound.USER_LEFT_KICKED_CHANNEL);
- } else {
- console.warn(tr("Unknown reason id %o"), json["reasonid"]);
- }
- }
-
- handleNotifyChannelMoved(json) {
- json = json[0]; //Only one bulk
- for(let key in json)
- console.log("Key: " + key + " Value: " + json[key]);
-
- let tree = this.connection._client.channelTree;
- let channel = tree.findChannel(json["cid"]);
- if(!channel) {
- console.error(tr("Unknown channel move (Channel)!"));
- return 0;
- }
-
- let prev = tree.findChannel(json["order"]);
- if(!prev && json["order"] != 0) {
- console.error(tr("Unknown channel move (prev)!"));
- return 0;
- }
-
- let parent = tree.findChannel(json["cpid"]);
- if(!parent && json["cpid"] != 0) {
- console.error(tr("Unknown channel move (parent)!"));
- return 0;
- }
-
- tree.moveChannel(channel, prev, parent);
- }
-
- handleNotifyChannelEdited(json) {
- json = json[0]; //Only one bulk
-
- let tree = this.connection._client.channelTree;
- let channel = tree.findChannel(json["cid"]);
- if(!channel) {
- console.error(tr("Unknown channel edit (Channel)!"));
- return 0;
- }
-
- let updates: {
- key: string,
- value: string
- }[] = [];
- for(let key in json) {
- if(key === "cid") continue;
- if(key === "invokerid") continue;
- if(key === "invokername") continue;
- if(key === "invokeruid") continue;
- if(key === "reasonid") continue;
- updates.push({key: key, value: json[key]});
- }
- channel.updateVariables(...updates);
- }
-
- handleNotifyTextMessage(json) {
- json = json[0]; //Only one bulk
-
- //TODO chat format?
- let mode = json["targetmode"];
- if(mode == 1){
- let invoker = this.connection._client.channelTree.findClient(json["invokerid"]);
- let target = this.connection._client.channelTree.findClient(json["target"]);
- if(!invoker) { //TODO spawn chat (Client is may invisible)
- console.error(tr("Got private message from invalid client!"));
- return;
- }
- if(!target) { //TODO spawn chat (Client is may invisible)
- console.error(tr("Got private message from invalid client!"));
- return;
- }
- if(invoker == this.connection._client.getClient()) {
- sound.play(Sound.MESSAGE_SEND, {default_volume: .5});
- target.chat(true).appendMessage("{0}: {1}", true, this.connection._client.getClient().createChatTag(true), MessageHelper.bbcode_chat(json["msg"]));
- } else {
- sound.play(Sound.MESSAGE_RECEIVED, {default_volume: .5});
- invoker.chat(true).appendMessage("{0}: {1}", true, ClientEntry.chatTag(json["invokerid"], json["invokername"], json["invokeruid"], true), MessageHelper.bbcode_chat(json["msg"]));
- }
- } else if(mode == 2) {
- if(json["invokerid"] == this.connection._client.clientId)
- sound.play(Sound.MESSAGE_SEND, {default_volume: .5});
- else
- sound.play(Sound.MESSAGE_RECEIVED, {default_volume: .5});
- chat.channelChat().appendMessage("{0}: {1}", true, ClientEntry.chatTag(json["invokerid"], json["invokername"], json["invokeruid"], true), MessageHelper.bbcode_chat(json["msg"]))
- } else if(mode == 3) {
- chat.serverChat().appendMessage("{0}: {1}", true, ClientEntry.chatTag(json["invokerid"], json["invokername"], json["invokeruid"], true), MessageHelper.bbcode_chat(json["msg"]));
- }
- }
-
- handleNotifyClientUpdated(json) {
- json = json[0]; //Only one bulk
-
- let client = this.connection._client.channelTree.findClient(json["clid"]);
- if(!client) {
- console.error(tr("Tried to update an non existing client"));
- return;
- }
-
- let updates: {
- key: string,
- value: string
- }[] = [];
- for(let key in json) {
- if(key == "clid") continue;
- updates.push({key: key, value: json[key]});
- }
- client.updateVariables(...updates);
- if(this.connection._client.selectInfo.currentSelected == client)
- this.connection._client.selectInfo.update();
- }
-
- handleNotifyServerEdited(json) {
- json = json[0];
-
- let updates: {
- key: string,
- value: string
- }[] = [];
- for(let key in json) {
- if(key === "invokerid") continue;
- if(key === "invokername") continue;
- if(key === "invokeruid") continue;
- if(key === "reasonid") continue;
-
- updates.push({key: key, value: json[key]});
- }
- this.connection._client.channelTree.server.updateVariables(false, ...updates);
- if(this.connection._client.selectInfo.currentSelected == this.connection._client.channelTree.server)
- this.connection._client.selectInfo.update();
- }
-
- handleNotifyServerUpdated(json) {
- json = json[0];
-
- let updates: {
- key: string,
- value: string
- }[] = [];
- for(let key in json) {
- if(key === "invokerid") continue;
- if(key === "invokername") continue;
- if(key === "invokeruid") continue;
- if(key === "reasonid") continue;
-
- updates.push({key: key, value: json[key]});
- }
- this.connection._client.channelTree.server.updateVariables(true, ...updates);
- let info = this.connection._client.selectInfo;
- if(info.currentSelected instanceof ServerEntry)
- info.update();
- }
-
- handleNotifyMusicPlayerInfo(json) {
- json = json[0];
-
- let bot = this.connection._client.channelTree.find_client_by_dbid(json["bot_id"]);
- if(!bot || !(bot instanceof MusicClientEntry)) {
- log.warn(LogCategory.CLIENT, tr("Got music player info for unknown or invalid bot! (ID: %i, Entry: %o)"), json["bot_id"], bot);
- return;
- }
-
- bot.handlePlayerInfo(json);
- }
-
- handleNotifyClientPoke(json) {
- json = json[0];
- Modals.spawnPoke({
- id: parseInt(json["invokerid"]),
- name: json["invokername"],
- unique_id: json["invokeruid"]
- }, json["msg"]);
-
- sound.play(Sound.USER_POKED_SELF);
- }
-
- //TODO server chat message
- handleNotifyServerGroupClientAdd(json) {
- json = json[0];
-
- const self = this.connection._client.getClient();
- if(json["clid"] == self.clientId())
- sound.play(Sound.GROUP_SERVER_ASSIGNED_SELF);
- }
-
- //TODO server chat message
- handleNotifyServerGroupClientRemove(json) {
- json = json[0];
-
- const self = this.connection._client.getClient();
- if(json["clid"] == self.clientId()) {
- sound.play(Sound.GROUP_SERVER_REVOKED_SELF);
- } else {
- }
- }
-
- //TODO server chat message
- handleNotifyClientChannelGroupChanged(json) {
- json = json[0];
-
- const self = this.connection._client.getClient();
- if(json["clid"] == self.clientId()) {
- sound.play(Sound.GROUP_CHANNEL_CHANGED_SELF);
- }
- }
-}
\ No newline at end of file
diff --git a/shared/js/connection/CommandHandler.ts b/shared/js/connection/CommandHandler.ts
new file mode 100644
index 00000000..5fc46d82
--- /dev/null
+++ b/shared/js/connection/CommandHandler.ts
@@ -0,0 +1,649 @@
+namespace connection {
+ export class ServerConnectionCommandBoss extends AbstractCommandHandlerBoss {
+ constructor(connection: AbstractServerConnection) {
+ super(connection);
+ }
+ }
+
+ export class ConnectionCommandHandler extends AbstractCommandHandler {
+ readonly connection: ServerConnection;
+
+ constructor(connection: ServerConnection) {
+ super(connection);
+
+ this["error"] = this.handleCommandResult;
+ this["channellist"] = this.handleCommandChannelList;
+ this["channellistfinished"] = this.handleCommandChannelListFinished;
+ this["notifychannelcreated"] = this.handleCommandChannelCreate;
+ this["notifychanneldeleted"] = this.handleCommandChannelDelete;
+ this["notifychannelhide"] = this.handleCommandChannelHide;
+ this["notifychannelshow"] = this.handleCommandChannelShow;
+
+ this["notifycliententerview"] = this.handleCommandClientEnterView;
+ this["notifyclientleftview"] = this.handleCommandClientLeftView;
+ this["notifyclientmoved"] = this.handleNotifyClientMoved;
+ this["initserver"] = this.handleCommandServerInit;
+ this["notifychannelmoved"] = this.handleNotifyChannelMoved;
+ this["notifychanneledited"] = this.handleNotifyChannelEdited;
+ this["notifytextmessage"] = this.handleNotifyTextMessage;
+ this["notifyclientupdated"] = this.handleNotifyClientUpdated;
+ this["notifyserveredited"] = this.handleNotifyServerEdited;
+ this["notifyserverupdated"] = this.handleNotifyServerUpdated;
+
+ this["notifyclientpoke"] = this.handleNotifyClientPoke;
+
+ this["notifymusicplayerinfo"] = this.handleNotifyMusicPlayerInfo;
+
+ this["notifyservergroupclientadded"] = this.handleNotifyServerGroupClientAdd;
+ this["notifyservergroupclientdeleted"] = this.handleNotifyServerGroupClientRemove;
+ this["notifyclientchannelgroupchanged"] = this.handleNotifyClientChannelGroupChanged;
+ }
+
+ handle_command(command: ServerCommand) : boolean {
+ if(this[command.command]) {
+ this[command.command](command.arguments);
+ return true;
+ }
+
+ return false;
+ }
+
+ set_handler(command: string, handler: any) {
+ this[command] = handler;
+ }
+
+ unset_handler(command: string, handler?: any) {
+ if(handler && this[command] != handler) return;
+ this[command] = undefined;
+ }
+
+ handleCommandResult(json) {
+ json = json[0]; //Only one bulk
+
+ let code : string = json["return_code"];
+ if(code.length == 0) {
+ console.log(tr("Invalid return code! (%o)"), json);
+ return;
+ }
+ let retListeners = this.connection["_retListener"];
+
+ for(let e of retListeners) {
+ if(e.code != code) continue;
+ retListeners.remove(e);
+ let result = new CommandResult(json);
+ if(result.success)
+ e.resolve(result);
+ else
+ e.reject(result);
+ break;
+ }
+ }
+
+ handleCommandServerInit(json){
+ //We could setup the voice channel
+ console.log(tr("Setting up voice"));
+ this.connection.client.voiceConnection.createSession();
+
+
+ json = json[0]; //Only one bulk
+
+ this.connection.client.clientId = parseInt(json["aclid"]);
+ this.connection.client.getClient().updateVariables({key: "client_nickname", value: json["acn"]});
+
+ let updates: {
+ key: string,
+ value: string
+ }[] = [];
+ for(let key in json) {
+ if(key === "aclid") continue;
+ if(key === "acn") continue;
+
+ updates.push({key: key, value: json[key]});
+ }
+ this.connection.client.channelTree.server.updateVariables(false, ...updates);
+
+
+ chat.serverChat().name = this.connection.client.channelTree.server.properties["virtualserver_name"];
+ chat.serverChat().appendMessage(tr("Connected as {0}"), true, this.connection.client.getClient().createChatTag(true));
+ sound.play(Sound.CONNECTION_CONNECTED);
+ globalClient.onConnected();
+ }
+
+ private createChannelFromJson(json, ignoreOrder: boolean = false) {
+ let tree = this.connection.client.channelTree;
+
+ let channel = new ChannelEntry(parseInt(json["cid"]), json["channel_name"], tree.findChannel(json["cpid"]));
+ tree.insertChannel(channel);
+ if(json["channel_order"] !== "0") {
+ let prev = tree.findChannel(json["channel_order"]);
+ if(!prev && json["channel_order"] != 0) {
+ if(!ignoreOrder) {
+ console.error(tr("Invalid channel order id!"));
+ return;
+ }
+ }
+
+ let parent = tree.findChannel(json["cpid"]);
+ if(!parent && json["cpid"] != 0) {
+ console.error(tr("Invalid channel parent"));
+ return;
+ }
+ tree.moveChannel(channel, prev, parent); //TODO test if channel exists!
+ }
+ if(ignoreOrder) {
+ for(let ch of tree.channels) {
+ if(ch.properties.channel_order == channel.channelId) {
+ tree.moveChannel(ch, channel, channel.parent); //Corrent the order :)
+ }
+ }
+ }
+
+ let updates: {
+ key: string,
+ value: string
+ }[] = [];
+ for(let key in json) {
+ if(key === "cid") continue;
+ if(key === "cpid") continue;
+ if(key === "invokerid") continue;
+ if(key === "invokername") continue;
+ if(key === "invokeruid") continue;
+ if(key === "reasonid") continue;
+
+ updates.push({key: key, value: json[key]});
+ }
+ channel.updateVariables(...updates);
+ }
+
+ handleCommandChannelList(json) {
+ this.connection.client.channelTree.hide_channel_tree(); /* dont perform channel inserts on the dom to prevent style recalculations */
+ console.log(tr("Got %d new channels"), json.length);
+ for(let index = 0; index < json.length; index++)
+ this.createChannelFromJson(json[index], true);
+ }
+
+
+ handleCommandChannelListFinished(json) {
+ this.connection.client.channelTree.show_channel_tree();
+ }
+
+ handleCommandChannelCreate(json) {
+ this.createChannelFromJson(json[0]);
+ }
+
+ handleCommandChannelShow(json) {
+ this.createChannelFromJson(json[0]); //TODO may chat?
+ }
+
+ handleCommandChannelDelete(json) {
+ let tree = this.connection.client.channelTree;
+
+ console.log(tr("Got %d channel deletions"), json.length);
+ for(let index = 0; index < json.length; index++) {
+ let channel = tree.findChannel(json[index]["cid"]);
+ if(!channel) {
+ console.error(tr("Invalid channel onDelete (Unknown channel)"));
+ continue;
+ }
+ tree.deleteChannel(channel);
+ }
+ }
+
+ handleCommandChannelHide(json) {
+ let tree = this.connection.client.channelTree;
+
+ console.log(tr("Got %d channel hides"), json.length);
+ for(let index = 0; index < json.length; index++) {
+ let channel = tree.findChannel(json[index]["cid"]);
+ if(!channel) {
+ console.error(tr("Invalid channel on hide (Unknown channel)"));
+ continue;
+ }
+ tree.deleteChannel(channel);
+ }
+ }
+
+ handleCommandClientEnterView(json) {
+ json = json[0]; //Only one bulk
+ let tree = this.connection.client.channelTree;
+
+ let client: ClientEntry;
+ let channel = tree.findChannel(json["ctid"]);
+ let old_channel = tree.findChannel(json["cfid"]);
+
+ client = tree.findClient(json["clid"]);
+
+ if(!client) {
+ if(parseInt(json["client_type_exact"]) == ClientType.CLIENT_MUSIC) {
+ client = new MusicClientEntry(parseInt(json["clid"]), json["client_nickname"]);
+ } else {
+ client = new ClientEntry(parseInt(json["clid"]), json["client_nickname"]);
+ }
+
+ client.properties.client_type = parseInt(json["client_type"]);
+ client = tree.insertClient(client, channel);
+ } else {
+ if(client == this.connection.client.getClient())
+ chat.channelChat().name = channel.channelName();
+ tree.moveClient(client, channel);
+ }
+
+ if(this.connection.client.controlBar.query_visible || client.properties.client_type != ClientType.CLIENT_QUERY) {
+ const own_channel = this.connection.client.getClient().currentChannel();
+ if(json["reasonid"] == ViewReasonId.VREASON_USER_ACTION) {
+ if(own_channel == channel)
+ if(old_channel)
+ sound.play(Sound.USER_ENTERED);
+ else
+ sound.play(Sound.USER_ENTERED_CONNECT);
+ if(old_channel) {
+ chat.serverChat().appendMessage(tr("{0} appeared from {1} to {2}"), true, client.createChatTag(true), old_channel.generate_tag(true), channel.generate_tag(true));
+ } else {
+ chat.serverChat().appendMessage(tr("{0} connected to channel {1}"), true, client.createChatTag(true), channel.generate_tag(true));
+ }
+ } else if(json["reasonid"] == ViewReasonId.VREASON_MOVED) {
+ if(own_channel == channel)
+ sound.play(Sound.USER_ENTERED_MOVED);
+
+ chat.serverChat().appendMessage(tr("{0} appeared from {1} to {2}, moved by {3}"), true,
+ client.createChatTag(true),
+ old_channel ? old_channel.generate_tag(true) : undefined,
+ channel.generate_tag(true),
+ ClientEntry.chatTag(json["invokerid"], json["invokername"], json["invokeruid"]),
+ );
+ } else if(json["reasonid"] == ViewReasonId.VREASON_CHANNEL_KICK) {
+ if(own_channel == channel)
+ sound.play(Sound.USER_ENTERED_KICKED);
+
+ chat.serverChat().appendMessage(tr("{0} appeared from {1} to {2}, kicked by {3}{4}"), true,
+ client.createChatTag(true),
+ old_channel ? old_channel.generate_tag(true) : undefined,
+ channel.generate_tag(true),
+ ClientEntry.chatTag(json["invokerid"], json["invokername"], json["invokeruid"]),
+ json["reasonmsg"] > 0 ? " (" + json["msg"] + ")" : ""
+ );
+ } else {
+ console.warn(tr("Unknown reasonid for %o"), json["reasonid"]);
+ }
+ }
+
+ let updates: {
+ key: string,
+ value: string
+ }[] = [];
+
+ for(let key in json) {
+ if(key == "cfid") continue;
+ if(key == "ctid") continue;
+ if(key === "invokerid") continue;
+ if(key === "invokername") continue;
+ if(key === "invokeruid") continue;
+ if(key === "reasonid") continue;
+
+ updates.push({key: key, value: json[key]});
+ }
+
+ client.updateVariables(...updates);
+
+ if(client instanceof LocalClientEntry)
+ this.connection.client.controlBar.updateVoice();
+ }
+
+ handleCommandClientLeftView(json) {
+ json = json[0]; //Only one bulk
+ let tree = this.connection.client.channelTree;
+ let client = tree.findClient(json["clid"]);
+ if(!client) {
+ console.error(tr("Unknown client left!"));
+ return 0;
+ }
+ if(client == this.connection.client.getClient()) {
+ if(json["reasonid"] == ViewReasonId.VREASON_BAN) {
+ this.connection.client.handleDisconnect(DisconnectReason.CLIENT_BANNED, json);
+ } else if(json["reasonid"] == ViewReasonId.VREASON_SERVER_KICK) {
+ this.connection.client.handleDisconnect(DisconnectReason.CLIENT_KICKED, json);
+ } else if(json["reasonid"] == ViewReasonId.VREASON_SERVER_SHUTDOWN) {
+ this.connection.client.handleDisconnect(DisconnectReason.SERVER_CLOSED, json);
+ } else if(json["reasonid"] == ViewReasonId.VREASON_SERVER_STOPPED) {
+ this.connection.client.handleDisconnect(DisconnectReason.SERVER_CLOSED, json);
+ } else
+ this.connection.client.handleDisconnect(DisconnectReason.UNKNOWN, json);
+ return;
+ }
+
+ if(this.connection.client.controlBar.query_visible || client.properties.client_type != ClientType.CLIENT_QUERY) {
+ const own_channel = this.connection.client.getClient().currentChannel();
+ let channel_from = tree.findChannel(json["cfid"]);
+ let channel_to = tree.findChannel(json["ctid"]);
+
+ if(json["reasonid"] == ViewReasonId.VREASON_USER_ACTION) {
+ chat.serverChat().appendMessage(tr("{0} disappeared from {1} to {2}"), true, client.createChatTag(true), channel_from.generate_tag(true), channel_to.generate_tag(true));
+
+ if(channel_from == own_channel)
+ sound.play(Sound.USER_LEFT);
+ } else if(json["reasonid"] == ViewReasonId.VREASON_SERVER_LEFT) {
+ chat.serverChat().appendMessage(tr("{0} left the server{1}"), true,
+ client.createChatTag(true),
+ json["reasonmsg"] ? " (" + json["reasonmsg"] + ")" : ""
+ );
+
+ if(channel_from == own_channel)
+ sound.play(Sound.USER_LEFT_DISCONNECT);
+ } else if(json["reasonid"] == ViewReasonId.VREASON_SERVER_KICK) {
+ chat.serverChat().appendError(tr("{0} was kicked from the server by {1}.{2}"),
+ client.createChatTag(true),
+ ClientEntry.chatTag(json["invokerid"], json["invokername"], json["invokeruid"]),
+ json["reasonmsg"] ? " (" + json["reasonmsg"] + ")" : ""
+ );
+ if(channel_from == own_channel)
+ sound.play(Sound.USER_LEFT_KICKED_SERVER);
+ } else if(json["reasonid"] == ViewReasonId.VREASON_CHANNEL_KICK) {
+ chat.serverChat().appendError(tr("{0} was kicked from your channel by {1}.{2}"),
+ client.createChatTag(true),
+ ClientEntry.chatTag(json["invokerid"], json["invokername"], json["invokeruid"]),
+ json["reasonmsg"] ? " (" + json["reasonmsg"] + ")" : ""
+ );
+
+ if(channel_from == own_channel)
+ sound.play(Sound.USER_LEFT_KICKED_CHANNEL);
+ } else if(json["reasonid"] == ViewReasonId.VREASON_BAN) {
+ //"Mulus" was banned for 1 second from the server by "WolverinDEV" (Sry brauchte kurz ein opfer :P <3 (Nohomo))
+ let duration = "permanently";
+ if(json["bantime"])
+ duration = "for " + formatDate(Number.parseInt(json["bantime"]));
+
+ chat.serverChat().appendError(tr("{0} was banned {1} by {2}.{3}"),
+ client.createChatTag(true),
+ duration,
+ ClientEntry.chatTag(json["invokerid"], json["invokername"], json["invokeruid"]),
+ json["reasonmsg"] ? " (" + json["reasonmsg"] + ")" : ""
+ );
+
+ if(channel_from == own_channel)
+ sound.play(Sound.USER_LEFT_BANNED);
+ } else {
+ console.error(tr("Unknown client left reason!"));
+ }
+ }
+
+ tree.deleteClient(client);
+ }
+
+ handleNotifyClientMoved(json) {
+ json = json[0]; //Only one bulk
+ let tree = this.connection.client.channelTree;
+ let client = tree.findClient(json["clid"]);
+ let channel_to = tree.findChannel(json["ctid"]);
+ let channel_from = tree.findChannel(json["cfid"]);
+
+ if(!client) {
+ console.error(tr("Unknown client move (Client)!"));
+ return 0;
+ }
+
+ if(!channel_to) {
+ console.error(tr("Unknown client move (Channel to)!"));
+ return 0;
+ }
+ if(!channel_from) //Not critical
+ console.error(tr("Unknown client move (Channel from)!"));
+
+ let self = client instanceof LocalClientEntry;
+ let current_clients;
+ if(self) {
+ chat.channelChat().name = channel_to.channelName();
+ current_clients = client.channelTree.clientsByChannel(client.currentChannel())
+ this.connection.client.controlBar.updateVoice(channel_to);
+ }
+
+ tree.moveClient(client, channel_to);
+ for(const entry of current_clients || [])
+ if(entry !== client) entry.getAudioController().stopAudio(true);
+
+ const own_channel = this.connection.client.getClient().currentChannel();
+ if(json["reasonid"] == ViewReasonId.VREASON_MOVED) {
+ chat.serverChat().appendMessage(self ? tr("You was moved by {3} from channel {1} to {2}") : tr("{0} was moved from channel {1} to {2} by {3}"), true,
+ client.createChatTag(true),
+ channel_from ? channel_from.generate_tag(true) : undefined,
+ channel_to.generate_tag(true),
+ ClientEntry.chatTag(json["invokerid"], json["invokername"], json["invokeruid"])
+ );
+ if(self)
+ sound.play(Sound.USER_MOVED_SELF);
+ else if(own_channel == channel_to)
+ sound.play(Sound.USER_ENTERED_MOVED);
+ else if(own_channel == channel_from)
+ sound.play(Sound.USER_LEFT_MOVED);
+ } else if(json["reasonid"] == ViewReasonId.VREASON_USER_ACTION) {
+ chat.serverChat().appendMessage(self ? tr("You switched from channel {1} to {2}") : tr("{0} switched from channel {1} to {2}"), true,
+ client.createChatTag(true),
+ channel_from ? channel_from.generate_tag(true) : undefined,
+ channel_to.generate_tag(true)
+ );
+ if(self) {} //If we do an action we wait for the error response
+ else if(own_channel == channel_to)
+ sound.play(Sound.USER_ENTERED);
+ else if(own_channel == channel_from)
+ sound.play(Sound.USER_LEFT);
+ } else if(json["reasonid"] == ViewReasonId.VREASON_CHANNEL_KICK) {
+ chat.serverChat().appendMessage(self ? tr("You got kicked out of the channel {1} to channel {2} by {3}{4}") : tr("{0} got kicked from channel {1} to {2} by {3}{4}"), true,
+ client.createChatTag(true),
+ channel_from ? channel_from.generate_tag(true) : undefined,
+ channel_to.generate_tag(true),
+ ClientEntry.chatTag(json["invokerid"], json["invokername"], json["invokeruid"]),
+ json["reasonmsg"] ? " (" + json["reasonmsg"] + ")" : ""
+ );
+ if(self) {
+ sound.play(Sound.CHANNEL_KICKED);
+ } else if(own_channel == channel_to)
+ sound.play(Sound.USER_ENTERED_KICKED);
+ else if(own_channel == channel_from)
+ sound.play(Sound.USER_LEFT_KICKED_CHANNEL);
+ } else {
+ console.warn(tr("Unknown reason id %o"), json["reasonid"]);
+ }
+ }
+
+ handleNotifyChannelMoved(json) {
+ json = json[0]; //Only one bulk
+ for(let key in json)
+ console.log("Key: " + key + " Value: " + json[key]);
+
+ let tree = this.connection.client.channelTree;
+ let channel = tree.findChannel(json["cid"]);
+ if(!channel) {
+ console.error(tr("Unknown channel move (Channel)!"));
+ return 0;
+ }
+
+ let prev = tree.findChannel(json["order"]);
+ if(!prev && json["order"] != 0) {
+ console.error(tr("Unknown channel move (prev)!"));
+ return 0;
+ }
+
+ let parent = tree.findChannel(json["cpid"]);
+ if(!parent && json["cpid"] != 0) {
+ console.error(tr("Unknown channel move (parent)!"));
+ return 0;
+ }
+
+ tree.moveChannel(channel, prev, parent);
+ }
+
+ handleNotifyChannelEdited(json) {
+ json = json[0]; //Only one bulk
+
+ let tree = this.connection.client.channelTree;
+ let channel = tree.findChannel(json["cid"]);
+ if(!channel) {
+ console.error(tr("Unknown channel edit (Channel)!"));
+ return 0;
+ }
+
+ let updates: {
+ key: string,
+ value: string
+ }[] = [];
+ for(let key in json) {
+ if(key === "cid") continue;
+ if(key === "invokerid") continue;
+ if(key === "invokername") continue;
+ if(key === "invokeruid") continue;
+ if(key === "reasonid") continue;
+ updates.push({key: key, value: json[key]});
+ }
+ channel.updateVariables(...updates);
+ }
+
+ handleNotifyTextMessage(json) {
+ json = json[0]; //Only one bulk
+
+ //TODO chat format?
+ let mode = json["targetmode"];
+ if(mode == 1){
+ let invoker = this.connection.client.channelTree.findClient(json["invokerid"]);
+ let target = this.connection.client.channelTree.findClient(json["target"]);
+ if(!invoker) { //TODO spawn chat (Client is may invisible)
+ console.error(tr("Got private message from invalid client!"));
+ return;
+ }
+ if(!target) { //TODO spawn chat (Client is may invisible)
+ console.error(tr("Got private message from invalid client!"));
+ return;
+ }
+ if(invoker == this.connection.client.getClient()) {
+ sound.play(Sound.MESSAGE_SEND, {default_volume: .5});
+ target.chat(true).appendMessage("{0}: {1}", true, this.connection.client.getClient().createChatTag(true), MessageHelper.bbcode_chat(json["msg"]));
+ } else {
+ sound.play(Sound.MESSAGE_RECEIVED, {default_volume: .5});
+ invoker.chat(true).appendMessage("{0}: {1}", true, ClientEntry.chatTag(json["invokerid"], json["invokername"], json["invokeruid"], true), MessageHelper.bbcode_chat(json["msg"]));
+ }
+ } else if(mode == 2) {
+ if(json["invokerid"] == this.connection.client.clientId)
+ sound.play(Sound.MESSAGE_SEND, {default_volume: .5});
+ else
+ sound.play(Sound.MESSAGE_RECEIVED, {default_volume: .5});
+ chat.channelChat().appendMessage("{0}: {1}", true, ClientEntry.chatTag(json["invokerid"], json["invokername"], json["invokeruid"], true), MessageHelper.bbcode_chat(json["msg"]))
+ } else if(mode == 3) {
+ chat.serverChat().appendMessage("{0}: {1}", true, ClientEntry.chatTag(json["invokerid"], json["invokername"], json["invokeruid"], true), MessageHelper.bbcode_chat(json["msg"]));
+ }
+ }
+
+ handleNotifyClientUpdated(json) {
+ json = json[0]; //Only one bulk
+
+ let client = this.connection.client.channelTree.findClient(json["clid"]);
+ if(!client) {
+ console.error(tr("Tried to update an non existing client"));
+ return;
+ }
+
+ let updates: {
+ key: string,
+ value: string
+ }[] = [];
+ for(let key in json) {
+ if(key == "clid") continue;
+ updates.push({key: key, value: json[key]});
+ }
+ client.updateVariables(...updates);
+ if(this.connection.client.selectInfo.currentSelected == client)
+ this.connection.client.selectInfo.update();
+ }
+
+ handleNotifyServerEdited(json) {
+ json = json[0];
+
+ let updates: {
+ key: string,
+ value: string
+ }[] = [];
+ for(let key in json) {
+ if(key === "invokerid") continue;
+ if(key === "invokername") continue;
+ if(key === "invokeruid") continue;
+ if(key === "reasonid") continue;
+
+ updates.push({key: key, value: json[key]});
+ }
+ this.connection.client.channelTree.server.updateVariables(false, ...updates);
+ if(this.connection.client.selectInfo.currentSelected == this.connection.client.channelTree.server)
+ this.connection.client.selectInfo.update();
+ }
+
+ handleNotifyServerUpdated(json) {
+ json = json[0];
+
+ let updates: {
+ key: string,
+ value: string
+ }[] = [];
+ for(let key in json) {
+ if(key === "invokerid") continue;
+ if(key === "invokername") continue;
+ if(key === "invokeruid") continue;
+ if(key === "reasonid") continue;
+
+ updates.push({key: key, value: json[key]});
+ }
+ this.connection.client.channelTree.server.updateVariables(true, ...updates);
+ let info = this.connection.client.selectInfo;
+ if(info.currentSelected instanceof ServerEntry)
+ info.update();
+ }
+
+ handleNotifyMusicPlayerInfo(json) {
+ json = json[0];
+
+ let bot = this.connection.client.channelTree.find_client_by_dbid(json["bot_id"]);
+ if(!bot || !(bot instanceof MusicClientEntry)) {
+ log.warn(LogCategory.CLIENT, tr("Got music player info for unknown or invalid bot! (ID: %i, Entry: %o)"), json["bot_id"], bot);
+ return;
+ }
+
+ bot.handlePlayerInfo(json);
+ }
+
+ handleNotifyClientPoke(json) {
+ json = json[0];
+ Modals.spawnPoke({
+ id: parseInt(json["invokerid"]),
+ name: json["invokername"],
+ unique_id: json["invokeruid"]
+ }, json["msg"]);
+
+ sound.play(Sound.USER_POKED_SELF);
+ }
+
+ //TODO server chat message
+ handleNotifyServerGroupClientAdd(json) {
+ json = json[0];
+
+ const self = this.connection.client.getClient();
+ if(json["clid"] == self.clientId())
+ sound.play(Sound.GROUP_SERVER_ASSIGNED_SELF);
+ }
+
+ //TODO server chat message
+ handleNotifyServerGroupClientRemove(json) {
+ json = json[0];
+
+ const self = this.connection.client.getClient();
+ if(json["clid"] == self.clientId()) {
+ sound.play(Sound.GROUP_SERVER_REVOKED_SELF);
+ } else {
+ }
+ }
+
+ //TODO server chat message
+ handleNotifyClientChannelGroupChanged(json) {
+ json = json[0];
+
+ const self = this.connection.client.getClient();
+ if(json["clid"] == self.clientId()) {
+ sound.play(Sound.GROUP_CHANNEL_CHANGED_SELF);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/shared/js/connection/CommandHelper.ts b/shared/js/connection/CommandHelper.ts
new file mode 100644
index 00000000..d5d53e25
--- /dev/null
+++ b/shared/js/connection/CommandHelper.ts
@@ -0,0 +1,311 @@
+namespace connection {
+ export class CommandHelper extends AbstractCommandHandler {
+ private _callbacks_namefromuid: ClientNameFromUid[] = [];
+ private _who_am_i: any;
+
+ constructor(connection) {
+ super(connection);
+
+ this.volatile_handler_boss = false;
+ this.ignore_consumed = true;
+ }
+
+ initialize() {
+ this.connection.command_handler_boss().register_handler(this);
+ /* notifyquerylist */
+ }
+
+ handle_command(command: connection.ServerCommand): boolean {
+ if(command.command == "notifyclientnamefromuid")
+ this.handle_notifyclientnamefromuid(command.arguments);
+ else
+ return false;
+ return true;
+ }
+
+ joinChannel(channel: ChannelEntry, password?: string) : Promise {
+ return this.connection.send_command("clientmove", {
+ "clid": this.connection.client.getClientId(),
+ "cid": channel.getChannelId(),
+ "cpw": password || ""
+ });
+ }
+
+ sendMessage(message: string, type: ChatType, target?: ChannelEntry | ClientEntry) : Promise {
+ if(type == ChatType.SERVER)
+ return this.connection.send_command("sendtextmessage", {"targetmode": 3, "target": 0, "msg": message});
+ else if(type == ChatType.CHANNEL)
+ return this.connection.send_command("sendtextmessage", {"targetmode": 2, "target": (target as ChannelEntry).getChannelId(), "msg": message});
+ else if(type == ChatType.CLIENT)
+ return this.connection.send_command("sendtextmessage", {"targetmode": 1, "target": (target as ClientEntry).clientId(), "msg": message});
+ }
+
+ updateClient(key: string, value: string) : Promise {
+ let data = {};
+ data[key] = value;
+ return this.connection.send_command("clientupdate", data);
+ }
+
+ info_from_uid(...uid: string[]) : Promise {
+ let uids = [...uid];
+ for(let p of this._callbacks_namefromuid)
+ if(p.keys == uids) return p.promise;
+
+ let req: ClientNameFromUid = {} as any;
+ req.keys = uids;
+ req.response = new Array(uids.length);
+ req.promise = new LaterPromise();
+
+ for(let uid of uids) {
+ this.connection.send_command("clientgetnamefromuid", {
+ cluid: uid
+ }).catch(req.promise.function_rejected());
+ }
+
+ this._callbacks_namefromuid.push(req);
+ return req.promise;
+ }
+
+ request_query_list(server_id: number = undefined) : Promise {
+ return new Promise((resolve, reject) => {
+ const single_handler = {
+ command: "notifyquerylist",
+ function: command => {
+ const json = command.arguments;
+
+ const result = {} as QueryList;
+
+ result.flag_all = json[0]["flag_all"];
+ result.flag_own = json[0]["flag_own"];
+ result.queries = [];
+
+ for(const entry of json) {
+ const rentry = {} as QueryListEntry;
+ rentry.bounded_server = entry["client_bounded_server"];
+ rentry.username = entry["client_login_name"];
+ rentry.unique_id = entry["client_unique_identifier"];
+
+ result.queries.push(rentry);
+ }
+
+ resolve(result);
+ return true;
+ }
+ };
+ this.handler_boss.register_single_handler(single_handler);
+
+ let data = {};
+ if(server_id !== undefined)
+ data["server_id"] = server_id;
+
+ this.connection.send_command("querylist", data).catch(error => {
+ this.handler_boss.remove_single_handler(single_handler);
+
+ if(error instanceof CommandResult) {
+ if(error.id == ErrorID.EMPTY_RESULT) {
+ resolve(undefined);
+ return;
+ }
+ }
+ reject(error);
+ });
+ });
+ }
+
+ request_playlist_list() : Promise {
+ return new Promise((resolve, reject) => {
+ const single_handler: SingleCommandHandler = {
+ command: "notifyplaylistlist",
+ function: command => {
+ const json = command.arguments;
+ const result: Playlist[] = [];
+
+ for(const entry of json) {
+ try {
+ result.push({
+ playlist_id: parseInt(entry["playlist_id"]),
+ playlist_bot_id: parseInt(entry["playlist_bot_id"]),
+ playlist_title: entry["playlist_title"],
+ playlist_type: parseInt(entry["playlist_type"]),
+ playlist_owner_dbid: parseInt(entry["playlist_owner_dbid"]),
+ playlist_owner_name: entry["playlist_owner_name"],
+
+ needed_power_modify: parseInt(entry["needed_power_modify"]),
+ needed_power_permission_modify: parseInt(entry["needed_power_permission_modify"]),
+ needed_power_delete: parseInt(entry["needed_power_delete"]),
+ needed_power_song_add: parseInt(entry["needed_power_song_add"]),
+ needed_power_song_move: parseInt(entry["needed_power_song_move"]),
+ needed_power_song_remove: parseInt(entry["needed_power_song_remove"])
+ });
+ } catch(error) {
+ log.error(LogCategory.NETWORKING, tr("Failed to parse playlist entry: %o"), error);
+ }
+ }
+
+ resolve(result);
+ return true;
+ }
+ };
+ this.handler_boss.register_single_handler(single_handler);
+
+ this.connection.send_command("playlistlist").catch(error => {
+ this.handler_boss.remove_single_handler(single_handler);
+
+ if(error instanceof CommandResult) {
+ if(error.id == ErrorID.EMPTY_RESULT) {
+ resolve([]);
+ return;
+ }
+ }
+ reject(error);
+ })
+ });
+ }
+
+ request_playlist_songs(playlist_id: number) : Promise {
+ return new Promise((resolve, reject) => {
+ const single_handler: SingleCommandHandler = {
+ command: "notifyplaylistsonglist",
+ function: command => {
+ const json = command.arguments;
+
+ if(json[0]["playlist_id"] != playlist_id) {
+ log.error(LogCategory.NETWORKING, tr("Received invalid notification for playlist songs"));
+ return false;
+ }
+
+ const result: PlaylistSong[] = [];
+
+ for(const entry of json) {
+ try {
+ result.push({
+ song_id: parseInt(entry["song_id"]),
+ song_invoker: entry["song_invoker"],
+ song_previous_song_id: parseInt(entry["song_previous_song_id"]),
+ song_url: entry["song_url"],
+ song_url_loader: entry["song_url_loader"],
+
+ song_loaded: entry["song_loaded"] == true || entry["song_loaded"] == "1",
+ song_metadata: entry["song_metadata"]
+ });
+ } catch(error) {
+ log.error(LogCategory.NETWORKING, tr("Failed to parse playlist song entry: %o"), error);
+ }
+ }
+
+ resolve(result);
+ return true;
+ }
+ };
+ this.handler_boss.register_single_handler(single_handler);
+
+ this.connection.send_command("playlistsonglist", {playlist_id: playlist_id}).catch(error => {
+ this.handler_boss.remove_single_handler(single_handler);
+ if(error instanceof CommandResult) {
+ if(error.id == ErrorID.EMPTY_RESULT) {
+ resolve([]);
+ return;
+ }
+ }
+ reject(error);
+ })
+ });
+ }
+
+ request_playlist_info(playlist_id: number) : Promise {
+ return new Promise((resolve, reject) => {
+ const single_handler: SingleCommandHandler = {
+ command: "notifyplaylistinfo",
+ function: command => {
+ const json = command.arguments[0];
+ if (json["playlist_id"] != playlist_id) {
+ log.error(LogCategory.NETWORKING, tr("Received invalid notification for playlist info"));
+ return;
+ }
+
+ try {
+ //resolve
+ resolve({
+ playlist_id: parseInt(json["playlist_id"]),
+ playlist_title: json["playlist_title"],
+ playlist_description: json["playlist_description"],
+ playlist_type: parseInt(json["playlist_type"]),
+
+ playlist_owner_dbid: parseInt(json["playlist_owner_dbid"]),
+ playlist_owner_name: json["playlist_owner_name"],
+
+ playlist_flag_delete_played: json["playlist_flag_delete_played"] == true || json["playlist_flag_delete_played"] == "1",
+ playlist_flag_finished: json["playlist_flag_finished"] == true || json["playlist_flag_finished"] == "1",
+ playlist_replay_mode: parseInt(json["playlist_replay_mode"]),
+ playlist_current_song_id: parseInt(json["playlist_current_song_id"]),
+ });
+ } catch (error) {
+ log.error(LogCategory.NETWORKING, tr("Failed to parse playlist info: %o"), error);
+ reject("failed to parse info");
+ }
+
+ return true;
+ }
+ };
+ this.handler_boss.register_single_handler(single_handler);
+
+ this.connection.send_command("playlistinfo", {playlist_id: playlist_id}).catch(error => {
+ this.handler_boss.remove_single_handler(single_handler);
+ reject(error);
+ })
+ });
+ }
+
+ /**
+ * @deprecated
+ * Its just a workaround for the query management.
+ * There is no garante that the whoami trick will work forever
+ */
+ current_virtual_server_id() : Promise {
+ if(this._who_am_i)
+ return Promise.resolve(parseInt(this._who_am_i["virtualserver_id"]));
+
+ return new Promise((resolve, reject) => {
+ const single_handler: SingleCommandHandler = {
+ function: command => {
+ if(command.command != "")
+ return false;
+
+ this._who_am_i = command.arguments[0];
+ resolve(parseInt(this._who_am_i["virtualserver_id"]));
+ return true;
+ }
+ };
+ this.handler_boss.register_single_handler(single_handler);
+
+ this.connection.send_command("whoami").catch(error => {
+ this.handler_boss.remove_single_handler(single_handler);
+ reject(error);
+ });
+ });
+ }
+
+ private handle_notifyclientnamefromuid(json: any[]) {
+ for(let entry of json) {
+ let info: ClientNameInfo = {} as any;
+ info.client_unique_id = entry["cluid"];
+ info.client_nickname = entry["clname"];
+ info.client_database_id = parseInt(entry["cldbid"]);
+
+ for(let elm of this._callbacks_namefromuid.slice(0)) {
+ let unset = 0;
+ for(let index = 0; index < elm.keys.length; index++) {
+ if(elm.keys[index] == info.client_unique_id) {
+ elm.response[index] = info;
+ }
+ if(elm.response[index] == undefined) unset++;
+ }
+ if(unset == 0) {
+ this._callbacks_namefromuid.remove(elm);
+ elm.promise.resolved(elm.response);
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/shared/js/connection/ConnectionBase.ts b/shared/js/connection/ConnectionBase.ts
new file mode 100644
index 00000000..eb82a4b0
--- /dev/null
+++ b/shared/js/connection/ConnectionBase.ts
@@ -0,0 +1,148 @@
+namespace connection {
+ export interface CommandOptions {
+ flagset?: string[]; /* default: [] */
+ process_result?: boolean; /* default: true */
+
+ timeout?: number /* default: 1000 */;
+ }
+ export const CommandOptionDefaults: CommandOptions = {
+ flagset: [],
+ process_result: true,
+ timeout: 1000
+ };
+
+ export abstract class AbstractServerConnection {
+ readonly client: TSClient;
+ readonly command_helper: CommandHelper;
+
+ protected constructor(client: TSClient) {
+ this.client = client;
+
+ this.command_helper = new CommandHelper(this);
+ }
+
+ /* resolved as soon a connection has been established. This does not means that the authentication had yet been done! */
+ abstract connect(address: ServerAddress, handshake: HandshakeHandler, timeout?: number) : Promise;
+
+ abstract connected() : boolean;
+ abstract disconnect(reason?: string) : Promise;
+
+ abstract support_voice() : boolean;
+ abstract voice_connection() : AbstractVoiceConnection | undefined;
+
+ abstract command_handler_boss() : AbstractCommandHandlerBoss;
+ abstract send_command(command: string, data?: any | any[], options?: CommandOptions) : Promise;
+ }
+
+ export abstract class AbstractVoiceConnection {
+ readonly connection: AbstractServerConnection;
+
+ protected constructor(connection: AbstractServerConnection) {
+ this.connection = connection;
+ }
+
+ abstract connected() : boolean;
+ }
+
+ export class ServerCommand {
+ command: string;
+ arguments: any[];
+ }
+
+ export abstract class AbstractCommandHandler {
+ readonly connection: AbstractServerConnection;
+
+ handler_boss: AbstractCommandHandlerBoss | undefined;
+ volatile_handler_boss: boolean = false; /* if true than the command handler could be registered twice to two or more handlers */
+
+ ignore_consumed: boolean = false;
+
+ protected constructor(connection: AbstractServerConnection) {
+ this.connection = connection;
+ }
+
+ /**
+ * @return If the command should be consumed
+ */
+ abstract handle_command(command: ServerCommand) : boolean;
+ }
+
+ export interface SingleCommandHandler {
+ name?: string;
+ command?: string;
+ timeout?: number;
+
+ /* if the return is true then the command handler will be removed */
+ function: (command: ServerCommand) => boolean;
+ }
+
+ export abstract class AbstractCommandHandlerBoss {
+ readonly connection: AbstractServerConnection;
+ protected command_handlers: AbstractCommandHandler[] = [];
+ /* TODO: Timeout */
+ protected single_command_handler: SingleCommandHandler[] = [];
+
+ protected constructor(connection: AbstractServerConnection) {
+ this.connection = connection;
+ }
+
+ register_handler(handler: AbstractCommandHandler) {
+ if(!handler.volatile_handler_boss && handler.handler_boss)
+ throw "handler already registered";
+
+ this.command_handlers.remove(handler); /* just to be sure */
+ this.command_handlers.push(handler);
+ handler.handler_boss = this;
+ }
+
+ unregister_handler(handler: AbstractCommandHandler) {
+ if(!handler.volatile_handler_boss && handler.handler_boss !== this) {
+ console.warn(tr("Tried to unregister command handler which does not belong to the handler boss"));
+ return;
+ }
+
+ this.command_handlers.remove(handler);
+ handler.handler_boss = undefined;
+ }
+
+
+ register_single_handler(handler: SingleCommandHandler) {
+ this.single_command_handler.push(handler);
+ }
+
+ remove_single_handler(handler: SingleCommandHandler) {
+ this.single_command_handler.remove(handler);
+ }
+
+ handlers() : AbstractCommandHandler[] {
+ return this.command_handlers;
+ }
+
+ invoke_handle(command: ServerCommand) : boolean {
+ let flag_consumed = false;
+
+ for(const handler of this.command_handlers) {
+ try {
+ if(!flag_consumed || handler.ignore_consumed)
+ flag_consumed = flag_consumed || handler.handle_command(command);
+ } catch(error) {
+ console.error(tr("Failed to invoke command handler. Invocation results in an exception: %o"), error);
+ }
+ }
+
+ for(const handler of [...this.single_command_handler]) {
+ if(handler.command && handler.command != command.command)
+ continue;
+
+ try {
+ if(handler.function(command))
+ this.single_command_handler.remove(handler);
+ } catch(error) {
+ console.error(tr("Failed to invoke single command handler. Invocation results in an exception: %o"), error);
+ }
+ }
+
+ return flag_consumed;
+ }
+ }
+}
\ No newline at end of file
diff --git a/shared/js/connection/HandshakeHandler.ts b/shared/js/connection/HandshakeHandler.ts
new file mode 100644
index 00000000..d64cbb04
--- /dev/null
+++ b/shared/js/connection/HandshakeHandler.ts
@@ -0,0 +1,108 @@
+namespace connection {
+ export interface HandshakeIdentityHandler {
+ connection: AbstractServerConnection;
+
+ start_handshake();
+ register_callback(callback: (success: boolean, message?: string) => any);
+ }
+
+ export class HandshakeHandler {
+ private connection: ServerConnection;
+ private handshake_handler: HandshakeIdentityHandler;
+ private failed = false;
+
+ readonly profile: profiles.ConnectionProfile;
+ readonly name: string;
+ readonly server_password: string;
+
+ constructor(profile: profiles.ConnectionProfile, name: string, password: string) {
+ this.profile = profile;
+ this.server_password = password;
+ this.name = name;
+ }
+
+ setConnection(con: ServerConnection) {
+ this.connection = con;
+ }
+
+ startHandshake() {
+ this.handshake_handler = this.profile.spawn_identity_handshake_handler(this.connection);
+ if(!this.handshake_handler) {
+ this.handshake_failed("failed to create identity handler");
+ return;
+ }
+
+ this.handshake_handler.register_callback((flag, message) => {
+ if(flag)
+ this.handshake_finished();
+ else
+ this.handshake_failed(message);
+ });
+
+ this.handshake_handler.start_handshake();
+ }
+
+ private handshake_failed(message: string) {
+ if(this.failed) return;
+
+ this.failed = true;
+ this.connection.client.handleDisconnect(DisconnectReason.HANDSHAKE_FAILED, message);
+ }
+
+ private handshake_finished(version?: string) {
+ if(native_client && window["native"] && native.client_version && !version) {
+ native.client_version()
+ .then( this.handshake_finished.bind(this))
+ .catch(error => {
+ console.error(tr("Failed to get version:"));
+ console.error(error);
+ this.handshake_finished("?.?.?");
+ });
+ return;
+ }
+
+ const git_version = settings.static_global("version", "unknown");
+ const browser_name = (navigator.browserSpecs || {})["name"] || " ";
+ let data = {
+ //TODO variables!
+ client_nickname: this.name,
+ client_platform: (browser_name ? browser_name + " " : "") + navigator.platform,
+ client_version: "TeaWeb " + git_version + " (" + navigator.userAgent + ")",
+
+ client_server_password: this.server_password,
+ client_browser_engine: navigator.product
+ };
+
+ if(version) {
+ data.client_version = "TeaClient ";
+ data.client_version += " " + version;
+
+ const os = require("os");
+ const arch_mapping = {
+ "x32": "32bit",
+ "x64": "64bit"
+ };
+
+ data.client_version += " " + (arch_mapping[os.arch()] || os.arch());
+
+ const os_mapping = {
+ "win32": "Windows",
+ "linux": "Linux"
+ };
+ data.client_platform = (os_mapping[os.platform()] || os.platform());
+ }
+
+ this.connection.send_command("clientinit", data).catch(error => {
+ this.connection.disconnect();
+ if(error instanceof CommandResult) {
+ if(error.id == 1028) {
+ this.connection.client.handleDisconnect(DisconnectReason.SERVER_REQUIRES_PASSWORD);
+ } else {
+
+ this.connection.client.handleDisconnect(DisconnectReason.CLIENT_KICKED, error);
+ }
+ }
+ });
+ }
+ }
+}
\ No newline at end of file
diff --git a/shared/js/connection/ServerConnection.ts b/shared/js/connection/ServerConnection.ts
new file mode 100644
index 00000000..a28610e9
--- /dev/null
+++ b/shared/js/connection/ServerConnection.ts
@@ -0,0 +1,377 @@
+enum ErrorID {
+ PERMISSION_ERROR = 2568,
+ EMPTY_RESULT = 0x0501,
+ PLAYLIST_IS_IN_USE = 0x2103
+}
+
+class CommandResult {
+ success: boolean;
+ id: number;
+ message: string;
+ extra_message: string;
+
+ json: any;
+
+ constructor(json) {
+ this.json = json;
+ this.id = json["id"];
+ this.message = json["msg"];
+
+ this.extra_message = "";
+ if(json["extra_msg"]) this.extra_message = json["extra_msg"];
+
+ this.success = this.id == 0;
+ }
+}
+
+class ReturnListener {
+ resolve: (value?: T | PromiseLike) => void;
+ reject: (reason?: any) => void;
+ code: string;
+
+ timeout: NodeJS.Timer;
+}
+
+namespace connection {
+ export class ServerConnection extends AbstractServerConnection {
+ _remote_address: ServerAddress;
+ _socket: WebSocket;
+ _connectionState: ConnectionState = ConnectionState.UNCONNECTED;
+ _handshakeHandler: HandshakeHandler;
+
+ private _command_boss: ServerConnectionCommandBoss;
+ private _command_handler_default: ConnectionCommandHandler;
+ private _command_handler_handshake: AbstractCommandHandler; //The new handshake handler :)
+
+ private _connect_timeout_timer: NodeJS.Timer = undefined;
+ private _connected: boolean = false;
+ private _retCodeIdx: number;
+ private _retListener: ReturnListener[];
+
+ constructor(client : TSClient) {
+ super(client);
+
+ this._socket = null;
+ this._retCodeIdx = 0;
+ this._retListener = [];
+
+ this._command_boss = new ServerConnectionCommandBoss(this);
+ this._command_handler_default = new ConnectionCommandHandler(this);
+
+ this._command_boss.register_handler(this._command_handler_default);
+ this.command_helper.initialize();
+ }
+
+ on_connect: () => void = () => {
+ console.log(tr("Socket connected"));
+ chat.serverChat().appendMessage(tr("Logging in..."));
+ this._handshakeHandler.startHandshake();
+ };
+
+ private generateReturnCode() : string {
+ return (this._retCodeIdx++).toString();
+ }
+
+ async connect(address : ServerAddress, handshake: HandshakeHandler, timeout?: number) : Promise {
+ timeout = typeof(timeout) === "number" ? timeout : 0;
+
+ if(this._connect_timeout_timer) {
+ clearTimeout(this._connect_timeout_timer);
+ this._connect_timeout_timer = null;
+ try {
+ await this.disconnect()
+ } catch(error) {
+ console.error(tr("Failed to close old connection properly. Error: %o"), error);
+ throw "failed to cleanup old connection";
+ }
+ }
+ this.updateConnectionState(ConnectionState.CONNECTING);
+ this._remote_address = address;
+ this._handshakeHandler = handshake;
+ this._handshakeHandler.setConnection(this);
+ this._connected = false;
+ chat.serverChat().appendMessage(tr("Connecting to {0}:{1}"), true, address.host, address.port);
+
+ const self = this;
+ try {
+ let local_socket: WebSocket;
+ let local_timeout_timer: NodeJS.Timer;
+
+ local_timeout_timer = setTimeout(async () => {
+ console.log(tr("Connect timeout triggered!"));
+ try {
+ await this.disconnect();
+ } catch(error) {
+ log.warn(LogCategory.NETWORKING, tr("Failed to close connection after timeout had been triggered! (%o)"), error);
+ }
+ this.client.handleDisconnect(DisconnectReason.CONNECT_FAILURE);
+ }, timeout);
+ this._connect_timeout_timer = local_timeout_timer;
+
+ this._socket = (local_socket = new WebSocket('wss://' + address.host + ":" + address.port)); /* this may hangs */
+ clearTimeout(local_timeout_timer);
+ if(this._connect_timeout_timer == local_timeout_timer)
+ this._connect_timeout_timer = undefined;
+
+ if(this._socket != local_socket) return; /* something had changed and we dont use this connection anymore! */
+ local_socket.onopen = () => {
+ if(this._socket != local_socket) return; /* this socket isn't from interest anymore */
+
+ this._connected = true;
+ this.on_connect();
+ };
+
+ local_socket.onclose = event => {
+ if(this._socket != local_socket) return; /* this socket isn't from interest anymore */
+
+ this.client.handleDisconnect(this._connected ? DisconnectReason.CONNECTION_CLOSED : DisconnectReason.CONNECT_FAILURE, {
+ code: event.code,
+ reason: event.reason,
+ event: event
+ });
+ };
+
+ local_socket.onerror = e => {
+ if(this._socket != local_socket) return; /* this socket isn't from interest anymore */
+
+ console.log(tr("Received web socket error: (%o)"), e);
+ };
+
+ local_socket.onmessage = msg => {
+ if(this._socket != local_socket) return; /* this socket isn't from interest anymore */
+
+ self.handle_socket_message(msg.data);
+ };
+ this.updateConnectionState(ConnectionState.INITIALISING);
+ } catch (e) {
+ try {
+ await this.disconnect();
+ } catch(error) {
+ log.warn(LogCategory.NETWORKING, tr("Failed to close connection after connect attempt failed (%o)"), error);
+ }
+ this.client.handleDisconnect(DisconnectReason.CONNECT_FAILURE, e);
+ }
+ }
+
+ updateConnectionState(state: ConnectionState) {
+ this._connectionState = state;
+ this.client.controlBar.update_connection_state();
+ }
+
+ async disconnect(reason?: string) : Promise {
+ if(typeof(reason) === "string") {
+ //TODO send disconnect reason
+ }
+
+ if(this._connectionState == ConnectionState.UNCONNECTED)
+ return;
+
+ this.updateConnectionState(ConnectionState.UNCONNECTED);
+
+ if(this._socket)
+ this._socket.close(3000 + 0xFF, tr("request disconnect"));
+ this._socket = null;
+ for(let future of this._retListener)
+ future.reject(tr("Connection closed"));
+ this._retListener = [];
+ this._retCodeIdx = 0;
+ this._connected = false;
+ }
+
+ private handle_socket_message(data) {
+ if(typeof(data) === "string") {
+ let json;
+ try {
+ json = JSON.parse(data);
+ } catch(e) {
+ console.error(tr("Could not parse message json!"));
+ alert(e); // error in the above string (in this case, yes)!
+ return;
+ }
+ if(json["type"] === undefined) {
+ console.log(tr("Missing data type!"));
+ return;
+ }
+ if(json["type"] === "command") {
+ 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();
+
+ this._command_boss.invoke_handle({
+ command: json["command"],
+ arguments: json["data"]
+ });
+ group.end();
+ } else if(json["type"] === "WebRTC") this.client.voiceConnection.handleControlPacket(json);
+ else {
+ console.log(tr("Unknown command type %o"), json["type"]);
+ }
+ } else {
+ log.warn(LogCategory.NETWORKING, tr("Received unknown message of type %s. Dropping message"), typeof(data));
+ }
+ }
+
+ sendData(data: any) {
+ if(!this._socket || this._socket.readyState != 1) {
+ log.warn(LogCategory.NETWORKING, tr("Tried to send on a invalid socket (%s)"), this._socket ? "invalid state (" + this._socket.readyState + ")" : "invalid socket");
+ return;
+ }
+ this._socket.send(data);
+ }
+
+ private commandiefy(input: any) : string {
+ return JSON.stringify(input, (key, value) => {
+ switch (typeof value) {
+ case "boolean": return value == true ? "1" : "0";
+ case "function": return value();
+ default:
+ return value;
+ }
+ });
+ }
+
+ send_command(command: string, data?: any | any[], _options?: CommandOptions) : Promise {
+ if(!this._socket || !this.connected()) {
+ console.warn(tr("Tried to send a command without a valid connection."));
+ return;
+ }
+
+ const options: CommandOptions = {};
+ Object.assign(options, CommandOptionDefaults);
+ Object.assign(options, _options);
+
+ data = $.isArray(data) ? data : [data || {}];
+
+ const _this = this;
+ let result = new Promise((resolve, failed) => {
+ let _data = $.isArray(data) ? data : [data];
+ let retCode = _data[0]["return_code"] !== undefined ? _data[0].return_code : _this.generateReturnCode();
+ _data[0].return_code = retCode;
+
+ let listener = new ReturnListener();
+ listener.resolve = resolve;
+ listener.reject = failed;
+ listener.code = retCode;
+ listener.timeout = setTimeout(() => {
+ _this._retListener.remove(listener);
+ listener.reject("timeout");
+ }, 1500);
+ this._retListener.push(listener);
+
+ this._socket.send(this.commandiefy({
+ "type": "command",
+ "command": command,
+ "data": _data,
+ "flags": options.flagset.filter(entry => entry.length != 0)
+ }));
+ });
+ return new Promise((resolve, failed) => {
+ result.then(resolve).catch(ex => {
+ if(options.process_result) {
+ if(ex instanceof CommandResult) {
+ let res = ex;
+ if(!res.success) {
+ if(res.id == 2568) { //Permission error
+ res.message = tr("Insufficient client permissions. Failed on permission ") + this.client.permissions.resolveInfo(res.json["failed_permid"] as number).name;
+ chat.serverChat().appendError(tr("Insufficient client permissions. Failed on permission {}"), this.client.permissions.resolveInfo(res.json["failed_permid"] as number).name);
+ sound.play(Sound.ERROR_INSUFFICIENT_PERMISSIONS);
+ } else {
+ chat.serverChat().appendError(res.extra_message.length == 0 ? res.message : res.extra_message);
+ }
+ }
+ } else if(typeof(ex) === "string") {
+ chat.serverChat().appendError(tr("Command execution results in ") + ex);
+ } else {
+ console.error(tr("Invalid promise result type: %o. Result:"), typeof (ex));
+ console.error(ex);
+ }
+ }
+ failed(ex);
+ })
+ });
+ }
+
+ connected() : boolean {
+ return this._socket && this._socket.readyState == WebSocket.OPEN;
+ }
+
+ support_voice(): boolean {
+ return false;
+ }
+
+ voice_connection(): connection.AbstractVoiceConnection | undefined {
+ return undefined;
+ }
+
+ command_handler_boss(): connection.AbstractCommandHandlerBoss {
+ return this._command_boss;
+ }
+ }
+}
+
+interface ClientNameInfo {
+ //cluid=tYzKUryn\/\/Y8VBMf8PHUT6B1eiE= name=Exp clname=Exp cldbid=9
+ client_unique_id: string;
+ client_nickname: string;
+ client_database_id: number;
+}
+
+interface ClientNameFromUid {
+ promise: LaterPromise,
+ keys: string[],
+ response: ClientNameInfo[]
+}
+
+interface QueryListEntry {
+ username: string;
+ unique_id: string;
+ bounded_server: number;
+}
+
+interface QueryList {
+ flag_own: boolean;
+ flag_all: boolean;
+
+ queries: QueryListEntry[];
+}
+
+interface Playlist {
+ playlist_id: number;
+ playlist_bot_id: number;
+ playlist_title: string;
+ playlist_type: number;
+ playlist_owner_dbid: number;
+ playlist_owner_name: string;
+
+ needed_power_modify: number;
+ needed_power_permission_modify: number;
+ needed_power_delete: number;
+ needed_power_song_add: number;
+ needed_power_song_move: number;
+ needed_power_song_remove: number;
+}
+
+interface PlaylistInfo {
+ playlist_id: number,
+ playlist_title: string,
+ playlist_description: string,
+ playlist_type: number,
+
+ playlist_owner_dbid: number,
+ playlist_owner_name: string,
+
+ playlist_flag_delete_played: boolean,
+ playlist_flag_finished: boolean,
+ playlist_replay_mode: number,
+ playlist_current_song_id: number,
+}
+
+interface PlaylistSong {
+ song_id: number;
+ song_previous_song_id: number;
+ song_invoker: string;
+ song_url: string;
+ song_url_loader: string;
+ song_loaded: boolean;
+ song_metadata: string;
+}
\ No newline at end of file
diff --git a/shared/js/load.ts b/shared/js/load.ts
index b0cff134..193a706c 100644
--- a/shared/js/load.ts
+++ b/shared/js/load.ts
@@ -470,6 +470,11 @@ const loader_javascript = {
})
}
+ /* load some extends classes */
+ await loader.load_scripts([
+ ["js/connection/ConnectionBase.js"]
+ ]);
+
/* load the main app */
await loader.load_scripts([
//Load general API's
@@ -539,11 +544,18 @@ const loader_javascript = {
"js/settings.js",
"js/bookmarks.js",
"js/contextMenu.js",
- "js/connection.js",
"js/FileManager.js",
"js/client.js",
"js/chat.js",
+ //Connection
+ "js/connection/CommandHandler.js",
+ "js/connection/CommandHelper.js",
+ "js/connection/HandshakeHandler.js",
+ "js/connection/ServerConnection.js",
+
+ "js/stats.js",
+
"js/PPTListener.js",
@@ -685,7 +697,7 @@ async function check_updates() {
const version = version_node.hasAttribute("value") ? version_node.getAttribute("value") : undefined;
if(!version) return undefined;
- if(version == "unknown" || version.replace("0", "").length == 0)
+ if(version == "unknown" || version.replace(/0+/, "").length == 0)
return undefined;
return version;
diff --git a/shared/js/log.ts b/shared/js/log.ts
index 49235ce4..3e887810 100644
--- a/shared/js/log.ts
+++ b/shared/js/log.ts
@@ -40,7 +40,7 @@ namespace log {
[LogCategory.GENERAL, true],
[LogCategory.NETWORKING, true],
[LogCategory.VOICE, true],
- [LogCategory.I18N, true]
+ [LogCategory.I18N, false]
]);
loader.register_task(loader.Stage.LOADED, {
diff --git a/shared/js/main.ts b/shared/js/main.ts
index 8a85df4f..d3d825ad 100644
--- a/shared/js/main.ts
+++ b/shared/js/main.ts
@@ -272,6 +272,15 @@ function main() {
globalClient.channelTree.handle_resized();
}, 1000);
});
+
+ stats.initialize({
+ verbose: true,
+ anonymize_ip_addresses: true,
+ volatile_collection_only: false
+ });
+ stats.register_user_count_listener(status => {
+ console.log("Received user count update: %o", status);
+ });
}
loader.register_task(loader.Stage.LOADED, {
diff --git a/shared/js/permission/GroupManager.ts b/shared/js/permission/GroupManager.ts
index da21b49d..b989e560 100644
--- a/shared/js/permission/GroupManager.ts
+++ b/shared/js/permission/GroupManager.ts
@@ -1,3 +1,4 @@
+///
enum GroupType {
QUERY,
@@ -58,7 +59,7 @@ class Group {
}
}
-class GroupManager {
+class GroupManager extends connection.AbstractCommandHandler {
readonly handle: TSClient;
serverGroups: Group[] = [];
@@ -66,18 +67,29 @@ class GroupManager {
private requests_group_permissions: GroupPermissionRequest[] = [];
constructor(client: TSClient) {
+ super(client.serverConnection);
+
+ client.serverConnection.command_handler_boss().register_handler(this);
this.handle = client;
+ }
- this.handle.serverConnection.commandHandler["notifyservergrouplist"] = this.onServerGroupList.bind(this);
- this.handle.serverConnection.commandHandler["notifychannelgrouplist"] = this.onServerGroupList.bind(this);
-
- this.handle.serverConnection.commandHandler["notifyservergrouppermlist"] = this.onPermissionList.bind(this);
- this.handle.serverConnection.commandHandler["notifychannelgrouppermlist"] = this.onPermissionList.bind(this);
+ handle_command(command: connection.ServerCommand): boolean {
+ switch (command.command) {
+ case "notifyservergrouplist":
+ case "notifychannelgrouplist":
+ this.handle_grouplist(command.arguments);
+ return true;
+ case "notifyservergrouppermlist":
+ case "notifychannelgrouppermlist":
+ this.handle_group_permission_list(command.arguments);
+ return true;
+ }
+ return false;
}
requestGroups(){
- this.handle.serverConnection.sendCommand("servergrouplist");
- this.handle.serverConnection.sendCommand("channelgrouplist");
+ this.handle.serverConnection.send_command("servergrouplist");
+ this.handle.serverConnection.send_command("channelgrouplist");
}
static sorter() : (a: Group, b: Group) => number {
@@ -107,7 +119,7 @@ class GroupManager {
return undefined;
}
- private onServerGroupList(json) {
+ private handle_grouplist(json) {
let target : GroupTarget;
if(json[0]["sgid"]) target = GroupTarget.SERVER;
else if(json[0]["cgid"]) target = GroupTarget.CHANNEL;
@@ -167,7 +179,7 @@ class GroupManager {
req.promise = new LaterPromise();
this.requests_group_permissions.push(req);
- this.handle.serverConnection.sendCommand(group.target == GroupTarget.SERVER ? "servergrouppermlist" : "channelgrouppermlist", {
+ this.handle.serverConnection.send_command(group.target == GroupTarget.SERVER ? "servergrouppermlist" : "channelgrouppermlist", {
cgid: group.id,
sgid: group.id
}).catch(error => {
@@ -179,7 +191,7 @@ class GroupManager {
return req.promise;
}
- private onPermissionList(json: any[]) {
+ private handle_group_permission_list(json: any[]) {
let group = json[0]["sgid"] ? this.serverGroup(parseInt(json[0]["sgid"])) : this.channelGroup(parseInt(json[0]["cgid"]));
if(!group) {
log.error(LogCategory.PERMISSIONS, tr("Got group permissions for group %o/%o, but its not a registered group!"), json[0]["sgid"], json[0]["cgid"]);
diff --git a/shared/js/permission/PermissionManager.ts b/shared/js/permission/PermissionManager.ts
index 537c8a5b..61b4a1fd 100644
--- a/shared/js/permission/PermissionManager.ts
+++ b/shared/js/permission/PermissionManager.ts
@@ -1,4 +1,5 @@
///
+///
enum PermissionType {
B_SERVERINSTANCE_HELP_VIEW = "b_serverinstance_help_view", /* Permission ID: 1 */
@@ -416,7 +417,7 @@ class TeaPermissionRequest {
promise: LaterPromise;
}
-class PermissionManager {
+class PermissionManager extends connection.AbstractCommandHandler {
readonly handle: TSClient;
permissionList: PermissionInfo[] = [];
@@ -505,14 +506,34 @@ class PermissionManager {
}
constructor(client: TSClient) {
+ super(client.serverConnection);
+
+ //FIXME? Dont register the handler like this?
+ this.volatile_handler_boss = true;
+ client.serverConnection.command_handler_boss().register_handler(this);
+
this.handle = client;
+ }
- this.handle.serverConnection.commandHandler["notifyclientneededpermissions"] = this.onNeededPermissions.bind(this);
- this.handle.serverConnection.commandHandler["notifypermissionlist"] = this.onPermissionList.bind(this);
-
- this.handle.serverConnection.commandHandler["notifychannelpermlist"] = this.onChannelPermList.bind(this);
- this.handle.serverConnection.commandHandler["notifyclientpermlist"] = this.onClientPermList.bind(this);
- this.handle.serverConnection.commandHandler["notifyplaylistpermlist"] = this.onPlaylistPermList.bind(this);
+ handle_command(command: connection.ServerCommand): boolean {
+ switch (command.command) {
+ case "notifyclientneededpermissions":
+ this.onNeededPermissions(command.arguments);
+ return true;
+ case "notifypermissionlist":
+ this.onPermissionList(command.arguments);
+ return true;
+ case "notifychannelpermlist":
+ this.onChannelPermList(command.arguments);
+ return true;
+ case "notifyclientpermlist":
+ this.onClientPermList(command.arguments);
+ return true;
+ case "notifyplaylistpermlist":
+ this.onPlaylistPermList(command.arguments);
+ return true;
+ }
+ return false;
}
initialized() : boolean {
@@ -520,7 +541,7 @@ class PermissionManager {
}
public requestPermissionList() {
- this.handle.serverConnection.sendCommand("permissionlist");
+ this.handle.serverConnection.send_command("permissionlist");
}
private onPermissionList(json) {
@@ -648,7 +669,7 @@ class PermissionManager {
request = new ChannelPermissionRequest();
request.requested = Date.now();
request.channel_id = channelId;
- this.handle.serverConnection.sendCommand("channelpermlist", {"cid": channelId});
+ this.handle.serverConnection.send_command("channelpermlist", {"cid": channelId});
this.requests_channel_permissions.push(request);
}
request.callback_error.push(reject);
@@ -676,7 +697,7 @@ class PermissionManager {
request.client_id = client_id;
request.promise = new LaterPromise();
- this.handle.serverConnection.sendCommand("clientpermlist", {cldbid: client_id}).catch(error => {
+ this.handle.serverConnection.send_command("clientpermlist", {cldbid: client_id}).catch(error => {
if(error instanceof CommandResult && error.id == ErrorID.EMPTY_RESULT)
request.promise.resolved([]);
else
@@ -697,7 +718,7 @@ class PermissionManager {
request.channel_id = channel_id;
request.promise = new LaterPromise();
- this.handle.serverConnection.sendCommand("channelclientpermlist", {cldbid: client_id, cid: channel_id}).catch(error => {
+ this.handle.serverConnection.send_command("channelclientpermlist", {cldbid: client_id, cid: channel_id}).catch(error => {
if(error instanceof CommandResult && error.id == ErrorID.EMPTY_RESULT)
request.promise.resolved([]);
else
@@ -730,7 +751,7 @@ class PermissionManager {
request.playlist_id = playlist_id;
request.promise = new LaterPromise();
- this.handle.serverConnection.sendCommand("playlistpermlist", {playlist_id: playlist_id}).catch(error => {
+ this.handle.serverConnection.send_command("playlistpermlist", {playlist_id: playlist_id}).catch(error => {
if(error instanceof CommandResult && error.id == ErrorID.EMPTY_RESULT)
request.promise.resolved([]);
else
diff --git a/shared/js/profiles/ConnectionProfile.ts b/shared/js/profiles/ConnectionProfile.ts
index 1a420bfa..914fdd94 100644
--- a/shared/js/profiles/ConnectionProfile.ts
+++ b/shared/js/profiles/ConnectionProfile.ts
@@ -38,7 +38,7 @@ namespace profiles {
this.identities[identities.IdentitifyType[type].toLowerCase()] = identity;
}
- spawn_identity_handshake_handler?(connection: ServerConnection) : HandshakeIdentityHandler {
+ spawn_identity_handshake_handler?(connection: connection.AbstractServerConnection) : connection.HandshakeIdentityHandler {
const identity = this.selected_identity();
if(!identity)
return undefined;
diff --git a/shared/js/profiles/Identity.ts b/shared/js/profiles/Identity.ts
index 91dafb8e..f81c0b42 100644
--- a/shared/js/profiles/Identity.ts
+++ b/shared/js/profiles/Identity.ts
@@ -15,7 +15,7 @@ namespace profiles.identities {
encode?() : string;
decode(data: string) : Promise;
- spawn_identity_handshake_handler(connection: ServerConnection) : HandshakeIdentityHandler;
+ spawn_identity_handshake_handler(connection: connection.AbstractServerConnection) : connection.HandshakeIdentityHandler;
}
export async function decode_identity(type: IdentitifyType, data: string) : Promise {
@@ -61,12 +61,33 @@ namespace profiles.identities {
return identity;
}
- export abstract class AbstractHandshakeIdentityHandler implements HandshakeIdentityHandler {
- connection: ServerConnection;
+ export class HandshakeCommandHandler extends connection.AbstractCommandHandler {
+ readonly handle: T;
+
+ constructor(connection: connection.AbstractServerConnection, handle: T) {
+ super(connection);
+ this.handle = handle;
+ }
+
+
+ handle_command(command: connection.ServerCommand): boolean {
+ if($.isFunction(this[command.command]))
+ this[command.command](command.arguments);
+ else if(command.command == "error") {
+ return false;
+ } else {
+ console.warn(tr("Received unknown command while handshaking (%o)"), command);
+ }
+ return true;
+ }
+ }
+
+ export abstract class AbstractHandshakeIdentityHandler implements connection.HandshakeIdentityHandler {
+ connection: connection.AbstractServerConnection;
protected callbacks: ((success: boolean, message?: string) => any)[] = [];
- protected constructor(connection: ServerConnection) {
+ protected constructor(connection: connection.AbstractServerConnection) {
this.connection = connection;
}
diff --git a/shared/js/profiles/identities/NameIdentity.ts b/shared/js/profiles/identities/NameIdentity.ts
index 790ca7b7..236e1273 100644
--- a/shared/js/profiles/identities/NameIdentity.ts
+++ b/shared/js/profiles/identities/NameIdentity.ts
@@ -3,16 +3,19 @@
namespace profiles.identities {
class NameHandshakeHandler extends AbstractHandshakeIdentityHandler {
readonly identity: NameIdentity;
+ handler: HandshakeCommandHandler;
- constructor(connection: ServerConnection, identity: profiles.identities.NameIdentity) {
+ constructor(connection: connection.AbstractServerConnection, identity: profiles.identities.NameIdentity) {
super(connection);
this.identity = identity;
+
+ this.handler = new HandshakeCommandHandler(connection, this);
+ this.handler["handshakeidentityproof"] = () => this.trigger_fail("server requested unexpected proof");
}
start_handshake() {
- this.connection.commandHandler["handshakeidentityproof"] = () => this.trigger_fail("server requested unexpected proof");
-
- this.connection.sendCommand("handshakebegin", {
+ this.connection.command_handler_boss().register_handler(this.handler);
+ this.connection.send_command("handshakebegin", {
intention: 0,
authentication_method: this.identity.type(),
client_nickname: this.identity.name()
@@ -24,6 +27,16 @@ namespace profiles.identities {
this.trigger_fail("failed to execute begin (" + error + ")");
}).then(() => this.trigger_success());
}
+
+ protected trigger_fail(message: string) {
+ this.connection.command_handler_boss().unregister_handler(this.handler);
+ super.trigger_fail(message);
+ }
+
+ protected trigger_success() {
+ this.connection.command_handler_boss().unregister_handler(this.handler);
+ super.trigger_success();
+ }
}
export class NameIdentity implements Identity {
@@ -67,7 +80,7 @@ namespace profiles.identities {
});
}
- spawn_identity_handshake_handler(connection: ServerConnection) : HandshakeIdentityHandler {
+ spawn_identity_handshake_handler(connection: connection.AbstractServerConnection) : connection.HandshakeIdentityHandler {
return new NameHandshakeHandler(connection, this);
}
}
diff --git a/shared/js/profiles/identities/TeaForumIdentity.ts b/shared/js/profiles/identities/TeaForumIdentity.ts
index 97e9a9e1..af0914ca 100644
--- a/shared/js/profiles/identities/TeaForumIdentity.ts
+++ b/shared/js/profiles/identities/TeaForumIdentity.ts
@@ -3,16 +3,18 @@
namespace profiles.identities {
class TeaForumHandshakeHandler extends AbstractHandshakeIdentityHandler {
readonly identity: TeaForumIdentity;
+ handler: HandshakeCommandHandler;
- constructor(connection: ServerConnection, identity: profiles.identities.TeaForumIdentity) {
+ constructor(connection: connection.AbstractServerConnection, identity: profiles.identities.TeaForumIdentity) {
super(connection);
this.identity = identity;
+ this.handler = new HandshakeCommandHandler(connection, this);
+ this.handler["handshakeidentityproof"] = this.handle_proof.bind(this);
}
start_handshake() {
- this.connection.commandHandler["handshakeidentityproof"] = this.handle_proof.bind(this);
-
- this.connection.sendCommand("handshakebegin", {
+ this.connection.command_handler_boss().register_handler(this.handler);
+ this.connection.send_command("handshakebegin", {
intention: 0,
authentication_method: this.identity.type(),
data: this.identity.data_json()
@@ -27,7 +29,7 @@ namespace profiles.identities {
private handle_proof(json) {
- this.connection.sendCommand("handshakeindentityproof", {
+ this.connection.send_command("handshakeindentityproof", {
proof: this.identity.data_sign()
}).catch(error => {
console.error(tr("Failed to proof the identity. Error: %o"), error);
@@ -37,6 +39,16 @@ namespace profiles.identities {
this.trigger_fail("failed to execute proof (" + error + ")");
}).then(() => this.trigger_success());
}
+
+ protected trigger_fail(message: string) {
+ this.connection.command_handler_boss().unregister_handler(this.handler);
+ super.trigger_fail(message);
+ }
+
+ protected trigger_success() {
+ this.connection.command_handler_boss().unregister_handler(this.handler);
+ super.trigger_success();
+ }
}
export class TeaForumIdentity implements Identity {
@@ -104,7 +116,7 @@ namespace profiles.identities {
});
}
- spawn_identity_handshake_handler(connection: ServerConnection) : HandshakeIdentityHandler {
+ spawn_identity_handshake_handler(connection: connection.AbstractServerConnection) : connection.HandshakeIdentityHandler {
return new TeaForumHandshakeHandler(connection, this);
}
}
diff --git a/shared/js/profiles/identities/TeamSpeakIdentity.ts b/shared/js/profiles/identities/TeamSpeakIdentity.ts
index 7f3442a6..b83f2b1a 100644
--- a/shared/js/profiles/identities/TeamSpeakIdentity.ts
+++ b/shared/js/profiles/identities/TeamSpeakIdentity.ts
@@ -198,16 +198,18 @@ namespace profiles.identities {
class TeaSpeakHandshakeHandler extends AbstractHandshakeIdentityHandler {
identity: TeaSpeakIdentity;
+ handler: HandshakeCommandHandler;
- constructor(connection: ServerConnection, identity: TeaSpeakIdentity) {
+ constructor(connection: connection.AbstractServerConnection, identity: TeaSpeakIdentity) {
super(connection);
this.identity = identity;
+ this.handler = new HandshakeCommandHandler(connection, this);
+ this.handler["handshakeidentityproof"] = this.handle_proof.bind(this);
}
start_handshake() {
- this.connection.commandHandler["handshakeidentityproof"] = this.handle_proof.bind(this);
-
- this.connection.sendCommand("handshakebegin", {
+ this.connection.command_handler_boss().register_handler(this.handler);
+ this.connection.send_command("handshakebegin", {
intention: 0,
authentication_method: this.identity.type(),
publicKey: this.identity.public_key
@@ -227,7 +229,7 @@ namespace profiles.identities {
}
this.identity.sign_message(json[0]["message"], json[0]["digest"]).then(proof => {
- this.connection.sendCommand("handshakeindentityproof", {proof: proof}).catch(error => {
+ this.connection.send_command("handshakeindentityproof", {proof: proof}).catch(error => {
console.error(tr("Failed to proof the identity. Error: %o"), error);
if(error instanceof CommandResult)
@@ -238,6 +240,16 @@ namespace profiles.identities {
this.trigger_fail("failed to sign message");
});
}
+
+ protected trigger_fail(message: string) {
+ this.connection.command_handler_boss().unregister_handler(this.handler);
+ super.trigger_fail(message);
+ }
+
+ protected trigger_success() {
+ this.connection.command_handler_boss().unregister_handler(this.handler);
+ super.trigger_success();
+ }
}
class IdentityPOWWorker {
@@ -831,7 +843,7 @@ namespace profiles.identities {
return base64ArrayBuffer(buffer.subarray(0, index));
}
- spawn_identity_handshake_handler(connection: ServerConnection): HandshakeIdentityHandler {
+ spawn_identity_handshake_handler(connection: connection.AbstractServerConnection): connection.HandshakeIdentityHandler {
return new TeaSpeakHandshakeHandler(connection, this);
}
}
diff --git a/shared/js/sound/Sounds.ts b/shared/js/sound/Sounds.ts
index 98c68d9e..ac40f8ca 100644
--- a/shared/js/sound/Sounds.ts
+++ b/shared/js/sound/Sounds.ts
@@ -240,6 +240,10 @@ namespace sound {
const path = "audio/" + file.filename;
const context = audio.player.context();
+ if(!context) {
+ console.warn(tr("Tried to replay a sound without an audio context (Sound: %o). Dropping playback"), sound);
+ return;
+ }
const volume = get_sound_volume(sound, options.default_volume);
console.log(tr("Replaying sound %s (Sound volume: %o | Master volume %o)"), sound, volume, master_volume);
diff --git a/shared/js/stats.ts b/shared/js/stats.ts
index 49c52e80..417b54db 100644
--- a/shared/js/stats.ts
+++ b/shared/js/stats.ts
@@ -96,16 +96,14 @@ namespace stats {
export function start_connection() {
cancel_reconnect();
-
- if(connection) {
- const connection_copy = connection;
- connection = undefined;
-
- connection_copy.close(3001);
- }
+ close_connection();
connection_state = ConnectionState.CONNECTING;
- connection = new WebSocket('wss://web-stats.teaspeak.de:1774');
+
+ connection = new WebSocket('wss://web-stats.teaspeak.de:27790');
+ if(!connection)
+ connection = new WebSocket('wss://localhost:27788');
+
{
const connection_copy = connection;
connection.onclose = (event: CloseEvent) => {
@@ -135,7 +133,7 @@ namespace stats {
console.log(LOG_PREFIX + tr("Received an error. Closing connection. Object: %o"), event);
connection.close(CloseCodes.INTERNAL_ERROR);
- this.invoke_reconnect();
+ invoke_reconnect();
};
connection.onmessage = (event: MessageEvent) => {
@@ -152,7 +150,20 @@ namespace stats {
}
}
+ export function close_connection() {
+ if(connection) {
+ const connection_copy = connection;
+ connection = undefined;
+
+ try {
+ connection_copy.close(3001);
+ } catch(_) {}
+ }
+ }
+
function invoke_reconnect() {
+ close_connection();
+
if(reconnect_timer) {
clearTimeout(reconnect_timer);
reconnect_timer = undefined;
diff --git a/shared/js/ui/channel.ts b/shared/js/ui/channel.ts
index fc048ff5..edf372fd 100644
--- a/shared/js/ui/channel.ts
+++ b/shared/js/ui/channel.ts
@@ -94,7 +94,7 @@ class ChannelEntry {
if(this._cached_channel_description) return new Promise(resolve => resolve(this._cached_channel_description));
if(this._cached_channel_description_promise) return this._cached_channel_description_promise;
- this.channelTree.client.serverConnection.sendCommand("channelgetdescription", {cid: this.channelId}).catch(error => {
+ this.channelTree.client.serverConnection.send_command("channelgetdescription", {cid: this.channelId}).catch(error => {
this._cached_channel_description_promise_reject(error);
});
@@ -440,7 +440,7 @@ class ChannelEntry {
Modals.createChannelModal(this, undefined, this.channelTree.client.permissions, (changes?, permissions?) => {
if(changes) {
changes["cid"] = this.channelId;
- this.channelTree.client.serverConnection.sendCommand("channeledit", changes);
+ this.channelTree.client.serverConnection.send_command("channeledit", changes);
log.info(LogCategory.CHANNEL, tr("Changed channel properties of channel %s: %o"), this.channelName(), changes);
}
@@ -456,7 +456,9 @@ class ChannelEntry {
}
perms[0]["cid"] = this.channelId;
- this.channelTree.client.serverConnection.sendCommand("channeladdperm", perms, ["continueonerror"]).then(() => {
+ this.channelTree.client.serverConnection.send_command("channeladdperm", perms, {
+ flagset: ["continueonerror"]
+ }).then(() => {
sound.play(Sound.CHANNEL_EDITED_SELF);
});
}
@@ -469,7 +471,7 @@ class ChannelEntry {
name: tr("Delete channel"),
invalidPermission: !flagDelete,
callback: () => {
- this.channelTree.client.serverConnection.sendCommand("channeldelete", {cid: this.channelId}).then(() => {
+ this.channelTree.client.serverConnection.send_command("channeldelete", {cid: this.channelId}).then(() => {
sound.play(Sound.CHANNEL_DELETED);
})
}
@@ -480,7 +482,7 @@ class ChannelEntry {
icon: "client-addon-collection",
name: tr("Create music bot"),
callback: () => {
- this.channelTree.client.serverConnection.sendCommand("musicbotcreate", {cid: this.channelId}).then(() => {
+ this.channelTree.client.serverConnection.send_command("musicbotcreate", {cid: this.channelId}).then(() => {
createInfoModal(tr("Bot successfully created"), tr("Bot has been successfully created.")).open();
}).catch(error => {
if(error instanceof CommandResult) {
@@ -692,7 +694,7 @@ class ChannelEntry {
});
}).open();
} else if(this.channelTree.client.getClient().currentChannel() != this)
- this.channelTree.client.getServerConnection().joinChannel(this, this._cachedPassword).then(() => {
+ this.channelTree.client.getServerConnection().command_helper.joinChannel(this, this._cachedPassword).then(() => {
sound.play(Sound.CHANNEL_JOINED);
}).catch(error => {
if(error instanceof CommandResult) {
diff --git a/shared/js/ui/client.ts b/shared/js/ui/client.ts
index a7579013..43460497 100644
--- a/shared/js/ui/client.ts
+++ b/shared/js/ui/client.ts
@@ -146,7 +146,7 @@ class ClientEntry {
const source = client._channel;
const self = this.channelTree.client.getClient();
- this.channelTree.client.serverConnection.sendCommand("clientmove", {
+ this.channelTree.client.serverConnection.send_command("clientmove", {
clid: client.clientId(),
cid: target.getChannelId()
}).then(event => {
@@ -179,7 +179,7 @@ class ClientEntry {
entry.name = group.name + " [" + (group.properties.savedb ? "perm" : "tmp") + "]";
if(this.groupAssigned(group)) {
entry.callback = () => {
- this.channelTree.client.serverConnection.sendCommand("servergroupdelclient", {
+ this.channelTree.client.serverConnection.send_command("servergroupdelclient", {
sgid: group.id,
cldbid: this.properties.client_database_id
});
@@ -187,7 +187,7 @@ class ClientEntry {
entry.disabled = !this.channelTree.client.permissions.neededPermission(PermissionType.I_GROUP_MEMBER_ADD_POWER).granted(group.requiredMemberRemovePower);
} else {
entry.callback = () => {
- this.channelTree.client.serverConnection.sendCommand("servergroupaddclient", {
+ this.channelTree.client.serverConnection.send_command("servergroupaddclient", {
sgid: group.id,
cldbid: this.properties.client_database_id
});
@@ -211,7 +211,7 @@ class ClientEntry {
}
entry.name = group.name + " [" + (group.properties.savedb ? "perm" : "tmp") + "]";
entry.callback = () => {
- this.channelTree.client.serverConnection.sendCommand("setclientchannelgroup", {
+ this.channelTree.client.serverConnection.send_command("setclientchannelgroup", {
cldbid: this.properties.client_database_id,
cgid: group.id,
cid: this.currentChannel().channelId
@@ -234,12 +234,12 @@ class ClientEntry {
callback: () => {
Modals.createServerGroupAssignmentModal(this, (group, flag) => {
if(flag) {
- return this.channelTree.client.serverConnection.sendCommand("servergroupaddclient", {
+ return this.channelTree.client.serverConnection.send_command("servergroupaddclient", {
sgid: group.id,
cldbid: this.properties.client_database_id
}).then(result => true);
} else
- return this.channelTree.client.serverConnection.sendCommand("servergroupdelclient", {
+ return this.channelTree.client.serverConnection.send_command("servergroupdelclient", {
sgid: group.id,
cldbid: this.properties.client_database_id
}).then(result => true);
@@ -286,7 +286,7 @@ class ClientEntry {
if(typeof(result) === "string") {
//TODO tr
console.log("Poking client " + _this.clientNickName() + " with message " + result);
- _this.channelTree.client.serverConnection.sendCommand("clientpoke", {
+ _this.channelTree.client.serverConnection.send_command("clientpoke", {
clid: _this.clientId(),
msg: result
});
@@ -303,7 +303,7 @@ class ClientEntry {
if(typeof(result) === "string") {
//TODO tr
console.log("Changing " + _this.clientNickName() + "'s description to " + result);
- _this.channelTree.client.serverConnection.sendCommand("clientedit", {
+ _this.channelTree.client.serverConnection.send_command("clientedit", {
clid: _this.clientId(),
client_description: result
});
@@ -319,7 +319,7 @@ class ClientEntry {
icon: "client-move_client_to_own_channel",
name: tr("Move client to your channel"),
callback: () => {
- this.channelTree.client.serverConnection.sendCommand("clientmove", {
+ this.channelTree.client.serverConnection.send_command("clientmove", {
clid: this.clientId(),
cid: this.channelTree.client.getClient().currentChannel().getChannelId()
});
@@ -333,7 +333,7 @@ class ClientEntry {
if(result) {
//TODO tr
console.log("Kicking client " + _this.clientNickName() + " from channel with reason " + result);
- _this.channelTree.client.serverConnection.sendCommand("clientkick", {
+ _this.channelTree.client.serverConnection.send_command("clientkick", {
clid: _this.clientId(),
reasonid: ViewReasonId.VREASON_CHANNEL_KICK,
reasonmsg: result
@@ -351,7 +351,7 @@ class ClientEntry {
if(result) {
//TODO tr
console.log("Kicking client " + _this.clientNickName() + " from server with reason " + result);
- _this.channelTree.client.serverConnection.sendCommand("clientkick", {
+ _this.channelTree.client.serverConnection.send_command("clientkick", {
clid: _this.clientId(),
reasonid: ViewReasonId.VREASON_SERVER_KICK,
reasonmsg: result
@@ -367,11 +367,13 @@ class ClientEntry {
invalidPermission: !this.channelTree.client.permissions.neededPermission(PermissionType.I_CLIENT_BAN_MAX_BANTIME).granted(1),
callback: () => {
Modals.spawnBanClient(this.properties.client_nickname, (data) => {
- this.channelTree.client.serverConnection.sendCommand("banclient", {
+ this.channelTree.client.serverConnection.send_command("banclient", {
uid: this.properties.client_unique_identifier,
banreason: data.reason,
time: data.length
- }, [data.no_ip ? "no-ip" : "", data.no_hwid ? "no-hardware-id" : "", data.no_name ? "no-nickname" : ""]).then(() => {
+ }, {
+ flagset: [data.no_ip ? "no-ip" : "", data.no_hwid ? "no-hardware-id" : "", data.no_name ? "no-nickname" : ""]
+ }).then(() => {
sound.play(Sound.USER_BANNED);
});
});
@@ -386,7 +388,7 @@ class ClientEntry {
invalidPermission: true, //!this.channelTree.client.permissions.neededPermission(PermissionType.I_CLIENT_BAN_MAX_BANTIME).granted(1),
callback: () => {
Modals.spawnBanClient(this.properties.client_nickname, (duration, reason) => {
- this.channelTree.client.serverConnection.sendCommand("banclient", {
+ this.channelTree.client.serverConnection.send_command("banclient", {
uid: this.properties.client_unique_identifier,
banreason: reason,
time: duration
@@ -511,7 +513,6 @@ class ClientEntry {
let icon: string = "";
let clicon: string = "";
- console.error(this.properties.client_nickname + " - " + this.properties.client_type_exact + " - " + this.properties.client_type);
if(this.properties.client_type_exact == ClientType.CLIENT_QUERY) {
icon = "client-server_query";
console.log("Server query!");
@@ -653,7 +654,7 @@ class ClientEntry {
updateClientVariables(){
if(this.lastVariableUpdate == 0 || new Date().getTime() - 10 * 60 * 1000 > this.lastVariableUpdate){ //Cache these only 10 min
this.lastVariableUpdate = new Date().getTime();
- this.channelTree.client.serverConnection.sendCommand("clientgetvariables", {clid: this.clientId()});
+ this.channelTree.client.serverConnection.send_command("clientgetvariables", {clid: this.clientId()});
}
}
@@ -667,12 +668,12 @@ class ClientEntry {
const _this = this;
c.onMessageSend = function (text: string) {
- _this.channelTree.client.serverConnection.sendMessage(text, ChatType.CLIENT, _this);
+ _this.channelTree.client.serverConnection.command_helper.sendMessage(text, ChatType.CLIENT, _this);
};
c.onClose = function () : boolean {
//TODO check online?
- _this.channelTree.client.serverConnection.sendCommand("clientchatclosed", {"clid": _this.clientId()});
+ _this.channelTree.client.serverConnection.send_command("clientchatclosed", {"clid": _this.clientId()});
return true;
}
}
@@ -797,7 +798,7 @@ class LocalClientEntry extends ClientEntry {
createInputModal(tr("Change own description"), tr("New description:
"), text => true, result => {
if(result) {
console.log(tr("Changing own description to %s"), result);
- _self.channelTree.client.serverConnection.sendCommand("clientedit", {
+ _self.channelTree.client.serverConnection.send_command("clientedit", {
clid: _self.clientId(),
client_description: result
});
@@ -853,7 +854,7 @@ class LocalClientEntry extends ClientEntry {
if(_self.clientNickName() == text) return;
elm.text(_self.clientNickName());
- _self.handle.serverConnection.updateClient("client_nickname", text).then((e) => {
+ _self.handle.serverConnection.command_helper.updateClient("client_nickname", text).then((e) => {
chat.serverChat().appendMessage(tr("Nickname successfully changed"));
}).catch((e: CommandResult) => {
chat.serverChat().appendError(tr("Could not change nickname ({})"), e.extra_message);
@@ -915,7 +916,7 @@ class MusicClientEntry extends ClientEntry {
callback: () => {
createInputModal(tr("Change music bots nickname"), tr("New nickname:
"), text => text.length >= 3 && text.length <= 31, result => {
if(result) {
- this.channelTree.client.serverConnection.sendCommand("clientedit", {
+ this.channelTree.client.serverConnection.send_command("clientedit", {
clid: this.clientId(),
client_nickname: result
});
@@ -931,7 +932,7 @@ class MusicClientEntry extends ClientEntry {
callback: () => {
createInputModal(tr("Change music bots description"), tr("New description:
"), text => true, result => {
if(typeof(result) === 'string') {
- this.channelTree.client.serverConnection.sendCommand("clientedit", {
+ this.channelTree.client.serverConnection.send_command("clientedit", {
clid: this.clientId(),
client_description: result
});
@@ -955,7 +956,7 @@ class MusicClientEntry extends ClientEntry {
icon: "client-edit",
disabled: false,
callback: () => {
- this.channelTree.client.serverConnection.helper.request_playlist_list().then(lists => {
+ this.channelTree.client.serverConnection.command_helper.request_playlist_list().then(lists => {
for(const entry of lists) {
if(entry.playlist_id == this.properties.client_playlist_id) {
Modals.spawnPlaylistEdit(this.channelTree.client, entry);
@@ -976,7 +977,7 @@ class MusicClientEntry extends ClientEntry {
callback: () => {
createInputModal(tr("Please enter the URL"), tr("URL:"), text => true, result => {
if(result) {
- this.channelTree.client.serverConnection.sendCommand("musicbotqueueadd", {
+ this.channelTree.client.serverConnection.send_command("musicbotqueueadd", {
bot_id: this.properties.client_database_id,
type: "yt", //Its a hint not a force!
url: result
@@ -999,7 +1000,7 @@ class MusicClientEntry extends ClientEntry {
icon: "client-move_client_to_own_channel",
name: tr("Move client to your channel"),
callback: () => {
- this.channelTree.client.serverConnection.sendCommand("clientmove", {
+ this.channelTree.client.serverConnection.send_command("clientmove", {
clid: this.clientId(),
cid: this.channelTree.client.getClient().currentChannel().getChannelId()
});
@@ -1012,7 +1013,7 @@ class MusicClientEntry extends ClientEntry {
createInputModal(tr("Kick client from channel"), tr("Kick reason:
"), text => true, result => {
if(result) {
console.log(tr("Kicking client %o from channel with reason %o"), this.clientNickName(), result);
- this.channelTree.client.serverConnection.sendCommand("clientkick", {
+ this.channelTree.client.serverConnection.send_command("clientkick", {
clid: this.clientId(),
reasonid: ViewReasonId.VREASON_CHANNEL_KICK,
reasonmsg: result
@@ -1045,7 +1046,7 @@ class MusicClientEntry extends ClientEntry {
max_volume = 100;
Modals.spawnChangeRemoteVolume(this.properties.player_volume, max_volume / 100, value => {
- this.channelTree.client.serverConnection.sendCommand("clientedit", {
+ this.channelTree.client.serverConnection.send_command("clientedit", {
clid: this.clientId(),
player_volume: value,
}).then(() => {
@@ -1064,7 +1065,7 @@ class MusicClientEntry extends ClientEntry {
const tag = $.spawn("div").append(MessageHelper.formatMessage(tr("Do you really want to delete {0}"), this.createChatTag(false)));
Modals.spawnYesNo(tr("Are you sure?"), $.spawn("div").append(tag), result => {
if(result) {
- this.channelTree.client.serverConnection.sendCommand("musicbotdelete", {
+ this.channelTree.client.serverConnection.send_command("musicbotdelete", {
bot_id: this.properties.client_database_id
});
}
@@ -1105,7 +1106,7 @@ class MusicClientEntry extends ClientEntry {
this._info_promise_resolve = resolve;
});
- this.channelTree.client.serverConnection.sendCommand("musicbotplayerinfo", {bot_id: this.properties.client_database_id });
+ this.channelTree.client.serverConnection.send_command("musicbotplayerinfo", {bot_id: this.properties.client_database_id });
return this._info_promise;
}
}
\ No newline at end of file
diff --git a/shared/js/ui/frames/ControlBar.ts b/shared/js/ui/frames/ControlBar.ts
index 6df969fe..59e5a8a9 100644
--- a/shared/js/ui/frames/ControlBar.ts
+++ b/shared/js/ui/frames/ControlBar.ts
@@ -137,7 +137,7 @@ class ControlBar {
if(this.handle.serverConnection.connected)
- this.handle.serverConnection.sendCommand("clientupdate", {
+ this.handle.serverConnection.send_command("clientupdate", {
client_input_muted: this._muteInput
});
settings.changeGlobal("mute_input", this._muteInput);
@@ -162,7 +162,7 @@ class ControlBar {
}
if(this.handle.serverConnection.connected)
- this.handle.serverConnection.sendCommand("clientupdate", {
+ this.handle.serverConnection.send_command("clientupdate", {
client_output_muted: this._muteOutput
});
settings.changeGlobal("mute_output", this._muteOutput);
@@ -187,7 +187,7 @@ class ControlBar {
}
if(this.handle.serverConnection.connected)
- this.handle.serverConnection.sendCommand("clientupdate", {
+ this.handle.serverConnection.send_command("clientupdate", {
client_away: this._away,
client_away_message: this._awayMessage
});
@@ -201,7 +201,7 @@ class ControlBar {
updateProperties() {
if(this.handle.serverConnection.connected)
- this.handle.serverConnection.sendCommand("clientupdate", {
+ this.handle.serverConnection.send_command("clientupdate", {
client_input_muted: this._muteInput,
client_output_muted: this._muteOutput,
client_away: this._away,
@@ -221,7 +221,7 @@ class ControlBar {
this.htmlTag.find(".btn_mute_input").prop("disabled", !this.codec_supported|| !this.support_playback || !this.support_record);
this.htmlTag.find(".btn_mute_output").prop("disabled", !this.codec_supported || !this.support_playback);
- this.handle.serverConnection.sendCommand("clientupdate", {
+ this.handle.serverConnection.send_command("clientupdate", {
client_input_hardware: this.codec_supported && this.support_record,
client_output_hardware: this.codec_supported && this.support_playback
});
@@ -273,7 +273,7 @@ class ControlBar {
createInputModal(tr("Use token"), tr("Please enter your token/priviledge key"), message => message.length > 0, result => {
if(!result) return;
if(this.handle.serverConnection.connected)
- this.handle.serverConnection.sendCommand("tokenuse", {
+ this.handle.serverConnection.send_command("tokenuse", {
token: result
}).then(() => {
createInfoModal(tr("Use token"), tr("Toke successfully used!")).open();
diff --git a/shared/js/ui/frames/SelectedItemInfo.ts b/shared/js/ui/frames/SelectedItemInfo.ts
index c4ef1b3c..1cd0f980 100644
--- a/shared/js/ui/frames/SelectedItemInfo.ts
+++ b/shared/js/ui/frames/SelectedItemInfo.ts
@@ -412,7 +412,7 @@ enum MusicPlayerState {
}
class MusicInfoManager extends ClientInfoManager {
- notify_status: (json) => any;
+ single_handler: connection.SingleCommandHandler;
createFrame<_>(handle: InfoBar<_>, channel: MusicClientEntry, html_tag: JQuery) {
super.createFrame(handle, channel, html_tag);
@@ -420,9 +420,9 @@ class MusicInfoManager extends ClientInfoManager {
}
updateFrame(bot: MusicClientEntry, html_tag: JQuery) {
- if(this.notify_status) {
- this.handle.handle.serverConnection.commandHandler.unset_handler("notifymusicstatusupdate", this.notify_status);
- this.notify_status = undefined;
+ if(this.single_handler) {
+ this.handle.handle.serverConnection.command_handler_boss().remove_single_handler(this.single_handler);
+ this.single_handler = undefined;
}
this.resetIntervals();
@@ -459,7 +459,7 @@ class MusicInfoManager extends ClientInfoManager {
let button_stop = frame.find('.button_stop');
button_play.click(handler => {
if(!button_play.hasClass("active")) {
- this.handle.handle.serverConnection.sendCommand("musicbotplayeraction", {
+ this.handle.handle.serverConnection.send_command("musicbotplayeraction", {
bot_id: bot.properties.client_database_id,
action: 1
}).then(updated => this.triggerUpdate()).catch(error => {
@@ -472,7 +472,7 @@ class MusicInfoManager extends ClientInfoManager {
});
button_pause.click(handler => {
if(!button_pause.hasClass("active")) {
- this.handle.handle.serverConnection.sendCommand("musicbotplayeraction", {
+ this.handle.handle.serverConnection.send_command("musicbotplayeraction", {
bot_id: bot.properties.client_database_id,
action: 2
}).then(updated => this.triggerUpdate()).catch(error => {
@@ -484,7 +484,7 @@ class MusicInfoManager extends ClientInfoManager {
button_pause.hide();
});
button_stop.click(handler => {
- this.handle.handle.serverConnection.sendCommand("musicbotplayeraction", {
+ this.handle.handle.serverConnection.send_command("musicbotplayeraction", {
bot_id: bot.properties.client_database_id,
action: 0
}).then(updated => this.triggerUpdate()).catch(error => {
@@ -507,7 +507,7 @@ class MusicInfoManager extends ClientInfoManager {
{ /* Button functions */
_frame.find(".btn-forward").click(() => {
- this.handle.handle.serverConnection.sendCommand("musicbotplayeraction", {
+ this.handle.handle.serverConnection.send_command("musicbotplayeraction", {
bot_id: bot.properties.client_database_id,
action: 3
}).then(updated => this.triggerUpdate()).catch(error => {
@@ -516,7 +516,7 @@ class MusicInfoManager extends ClientInfoManager {
});
});
_frame.find(".btn-rewind").click(() => {
- this.handle.handle.serverConnection.sendCommand("musicbotplayeraction", {
+ this.handle.handle.serverConnection.send_command("musicbotplayeraction", {
bot_id: bot.properties.client_database_id,
action: 4
}).then(updated => this.triggerUpdate()).catch(error => {
@@ -525,7 +525,7 @@ class MusicInfoManager extends ClientInfoManager {
});
});
_frame.find(".btn-settings").click(() => {
- this.handle.handle.serverConnection.helper.request_playlist_list().then(lists => {
+ this.handle.handle.serverConnection.command_helper.request_playlist_list().then(lists => {
for(const entry of lists) {
if(entry.playlist_id == bot.properties.client_playlist_id) {
Modals.spawnPlaylistEdit(bot.channelTree.client, entry);
@@ -581,7 +581,7 @@ class MusicInfoManager extends ClientInfoManager {
slider.prop("edited", true);
let current_timestamp = info.player_replay_index + Date.now() - timestamp;
- this.handle.handle.serverConnection.sendCommand("musicbotplayeraction", {
+ this.handle.handle.serverConnection.send_command("musicbotplayeraction", {
bot_id: bot.properties.client_database_id,
action: current_timestamp > target_timestamp ? 6 : 5,
units: current_timestamp < target_timestamp ? target_timestamp - current_timestamp : current_timestamp - target_timestamp
@@ -627,17 +627,23 @@ class MusicInfoManager extends ClientInfoManager {
update_handler();
/* register subscription */
- this.handle.handle.serverConnection.sendCommand("musicbotsetsubscription", {bot_id: bot.properties.client_database_id}).catch(error => {
+ this.handle.handle.serverConnection.send_command("musicbotsetsubscription", {bot_id: bot.properties.client_database_id}).catch(error => {
console.error("Failed to subscribe to displayed music bot! Using pseudo timeline");
}).then(() => {
clearInterval(interval);
});
- this.notify_status = json => {
- json = json[0];
- update_handler(parseInt(json["player_replay_index"]), parseInt(json["player_buffered_index"]));
+ this.single_handler = {
+ command: "notifymusicstatusupdate",
+ function: command => {
+ const json = command.arguments[0];
+ update_handler(parseInt(json["player_replay_index"]), parseInt(json["player_buffered_index"]));
+
+ return false; /* do not unregister me! */
+ }
};
- this.handle.handle.serverConnection.commandHandler.set_handler("notifymusicstatusupdate", this.notify_status);
+
+ this.handle.handle.serverConnection.command_handler_boss().register_single_handler(this.single_handler);
}
});
}
@@ -718,9 +724,9 @@ class MusicInfoManager extends ClientInfoManager {
}
finalizeFrame(object: ClientEntry, frame: JQuery) {
- if(this.notify_status) {
- this.handle.handle.serverConnection.commandHandler.unset_handler("notifymusicstatusupdate", this.notify_status);
- this.notify_status = undefined;
+ if(this.single_handler) {
+ this.handle.handle.serverConnection.command_handler_boss().remove_single_handler(this.single_handler);
+ this.single_handler = undefined;
}
super.finalizeFrame(object, frame);
diff --git a/shared/js/ui/modal/ModalBanList.ts b/shared/js/ui/modal/ModalBanList.ts
index 4d5f0e29..f716b102 100644
--- a/shared/js/ui/modal/ModalBanList.ts
+++ b/shared/js/ui/modal/ModalBanList.ts
@@ -25,6 +25,7 @@ namespace Modals {
export interface BanListManager {
addbans : (ban: BanEntry[]) => void;
clear : (ban?: any) => void;
+ modal: Modal;
}
export function openBanList(client: TSClient) {
@@ -34,7 +35,7 @@ namespace Modals {
if(result.server_id < 0) result.server_id = undefined;
console.log(tr("Adding ban %o"), result);
- client.serverConnection.sendCommand("banadd", {
+ client.serverConnection.send_command("banadd", {
ip: result.ip,
name: result.name,
uid: result.unique_id,
@@ -55,7 +56,7 @@ namespace Modals {
console.log(tr("Apply edit changes %o"), result);
if(result.server_id < 0) result.server_id = undefined;
- client.serverConnection.sendCommand("banedit", {
+ client.serverConnection.send_command("banedit", {
banid: result.banid,
ip: result.ip,
name: result.name,
@@ -73,7 +74,7 @@ namespace Modals {
});
}, ban => {
console.log(tr("Deleting ban %o"), ban);
- client.serverConnection.sendCommand("bandel", {
+ client.serverConnection.send_command("bandel", {
banid: ban.banid,
sid: ban.server_id
}).then(() => {
@@ -84,8 +85,10 @@ namespace Modals {
});
});
- update = () => {
- client.serverConnection.commandHandler["notifybanlist"] = json => {
+ const single_handler: connection.SingleCommandHandler = {
+ command: "notifybanlist",
+ function: command => {
+ const json = command.arguments;
console.log(tr("Got banlist: %o"), json);
let bans: BanEntry[] = [];
@@ -136,12 +139,20 @@ namespace Modals {
}
modal.addbans(bans);
- };
+ return false;
+ }
+ };
+ client.serverConnection.command_handler_boss().register_single_handler(single_handler);
+ modal.modal.close_listener.push(() => {
+ client.serverConnection.command_handler_boss().remove_single_handler(single_handler);
+ });
+
+ update = () => {
//TODO test permission
modal.clear();
- client.serverConnection.sendCommand("banlist", { sid: 0 }); //Global ban list
- client.serverConnection.sendCommand("banlist").catch(error => {
+ client.serverConnection.send_command("banlist", { sid: 0 }); //Global ban list
+ client.serverConnection.send_command("banlist").catch(error => {
if(error instanceof CommandResult) {
} else {
console.error(error);
@@ -208,6 +219,7 @@ namespace Modals {
modal.htmlTag.find(".entry-container .entries").children().detach();
update_function();
};
+ result.modal = modal;
return result;
}
diff --git a/shared/js/ui/modal/ModalPermissionEdit.ts b/shared/js/ui/modal/ModalPermissionEdit.ts
index e1f83c09..a40880fc 100644
--- a/shared/js/ui/modal/ModalPermissionEdit.ts
+++ b/shared/js/ui/modal/ModalPermissionEdit.ts
@@ -665,7 +665,7 @@ namespace Modals {
permission.id,
);
- return globalClient.serverConnection.sendCommand("channelclientdelperm", {
+ return globalClient.serverConnection.send_command("channelclientdelperm", {
cldbid: current_cldbid,
cid: current_channel.channelId,
permid: permission.id,
@@ -677,7 +677,7 @@ namespace Modals {
value.granted,
);
- return globalClient.serverConnection.sendCommand("channelclientdelperm", {
+ return globalClient.serverConnection.send_command("channelclientdelperm", {
cldbid: current_cldbid,
cid: current_channel.channelId,
permid: permission.id_grant(),
@@ -694,7 +694,7 @@ namespace Modals {
value.flag_negate
);
- return globalClient.serverConnection.sendCommand("channelclientaddperm", {
+ return globalClient.serverConnection.send_command("channelclientaddperm", {
cldbid: current_cldbid,
cid: current_channel.channelId,
permid: permission.id,
@@ -709,7 +709,7 @@ namespace Modals {
value.granted,
);
- return globalClient.serverConnection.sendCommand("channelclientaddperm", {
+ return globalClient.serverConnection.send_command("channelclientaddperm", {
cldbid: current_cldbid,
cid: current_channel.channelId,
permid: permission.id_grant(),
@@ -745,7 +745,7 @@ namespace Modals {
const resolve_client = () => {
let client_uid = tag_select_uid.val() as string;
- globalClient.serverConnection.helper.info_from_uid(client_uid).then(result => {
+ globalClient.serverConnection.command_helper.info_from_uid(client_uid).then(result => {
if(!result || result.length == 0) return Promise.reject("invalid data");
tag_select_uid.attr('pattern', null).removeClass('is-invalid');
@@ -821,7 +821,7 @@ namespace Modals {
permission.id,
);
- return globalClient.serverConnection.sendCommand("clientaddperm", {
+ return globalClient.serverConnection.send_command("clientaddperm", {
cldbid: current_cldbid,
permid: permission.id,
});
@@ -832,7 +832,7 @@ namespace Modals {
value.granted,
);
- return globalClient.serverConnection.sendCommand("clientaddperm", {
+ return globalClient.serverConnection.send_command("clientaddperm", {
cldbid: current_cldbid,
permid: permission.id_grant(),
});
@@ -848,7 +848,7 @@ namespace Modals {
value.flag_negate
);
- return globalClient.serverConnection.sendCommand("clientaddperm", {
+ return globalClient.serverConnection.send_command("clientaddperm", {
cldbid: current_cldbid,
permid: permission.id,
permvalue: value.value,
@@ -862,7 +862,7 @@ namespace Modals {
value.granted,
);
- return globalClient.serverConnection.sendCommand("clientaddperm", {
+ return globalClient.serverConnection.send_command("clientaddperm", {
cldbid: current_cldbid,
permid: permission.id_grant(),
permvalue: value.granted,
@@ -888,7 +888,7 @@ namespace Modals {
const resolve_client = () => {
let client_uid = tag_select_uid.val() as string;
- globalClient.serverConnection.helper.info_from_uid(client_uid).then(result => {
+ globalClient.serverConnection.command_helper.info_from_uid(client_uid).then(result => {
if(!result || result.length == 0) return Promise.reject("invalid data");
tag_select_uid.attr('pattern', null).removeClass('is-invalid');
@@ -956,7 +956,7 @@ namespace Modals {
permission.id,
);
- return globalClient.serverConnection.sendCommand("channeldelperm", {
+ return globalClient.serverConnection.send_command("channeldelperm", {
cid: current_channel.channelId,
permid: permission.id,
});
@@ -968,7 +968,7 @@ namespace Modals {
value.granted,
);
- return globalClient.serverConnection.sendCommand("channeldelperm", {
+ return globalClient.serverConnection.send_command("channeldelperm", {
cid: current_channel.channelId,
permid: permission.id_grant(),
});
@@ -984,7 +984,7 @@ namespace Modals {
value.flag_negate
);
- return globalClient.serverConnection.sendCommand("channeladdperm", {
+ return globalClient.serverConnection.send_command("channeladdperm", {
cid: current_channel.channelId,
permid: permission.id,
permvalue: value.value,
@@ -999,7 +999,7 @@ namespace Modals {
value.granted,
);
- return globalClient.serverConnection.sendCommand("channeladdperm", {
+ return globalClient.serverConnection.send_command("channeladdperm", {
cid: current_channel.channelId,
permid: permission.id_grant(),
permvalue: value.granted,
@@ -1058,7 +1058,7 @@ namespace Modals {
permission.id,
);
- return globalClient.serverConnection.sendCommand("channelgroupdelperm", {
+ return globalClient.serverConnection.send_command("channelgroupdelperm", {
cgid: current_group.id,
permid: permission.id,
});
@@ -1069,7 +1069,7 @@ namespace Modals {
value.granted,
);
- return globalClient.serverConnection.sendCommand("channelgroupdelperm", {
+ return globalClient.serverConnection.send_command("channelgroupdelperm", {
cgid: current_group.id,
permid: permission.id_grant(),
});
@@ -1085,7 +1085,7 @@ namespace Modals {
value.flag_negate
);
- return globalClient.serverConnection.sendCommand("channelgroupaddperm", {
+ return globalClient.serverConnection.send_command("channelgroupaddperm", {
cgid: current_group.id,
permid: permission.id,
permvalue: value.value,
@@ -1099,7 +1099,7 @@ namespace Modals {
value.granted,
);
- return globalClient.serverConnection.sendCommand("channelgroupaddperm", {
+ return globalClient.serverConnection.send_command("channelgroupaddperm", {
cgid: current_group.id,
permid: permission.id_grant(),
permvalue: value.granted,
@@ -1218,7 +1218,7 @@ namespace Modals {
permission.id,
);
- return globalClient.serverConnection.sendCommand("servergroupdelperm", {
+ return globalClient.serverConnection.send_command("servergroupdelperm", {
sgid: current_group.id,
permid: permission.id,
});
@@ -1229,7 +1229,7 @@ namespace Modals {
value.granted,
);
- return globalClient.serverConnection.sendCommand("servergroupdelperm", {
+ return globalClient.serverConnection.send_command("servergroupdelperm", {
sgid: current_group.id,
permid: permission.id_grant(),
});
@@ -1245,7 +1245,7 @@ namespace Modals {
value.flag_negate
);
- return globalClient.serverConnection.sendCommand("servergroupaddperm", {
+ return globalClient.serverConnection.send_command("servergroupaddperm", {
sgid: current_group.id,
permid: permission.id,
permvalue: value.value,
@@ -1259,7 +1259,7 @@ namespace Modals {
value.granted,
);
- return globalClient.serverConnection.sendCommand("servergroupaddperm", {
+ return globalClient.serverConnection.send_command("servergroupaddperm", {
sgid: current_group.id,
permid: permission.id_grant(),
permvalue: value.granted,
diff --git a/shared/js/ui/modal/ModalPlaylistEdit.ts b/shared/js/ui/modal/ModalPlaylistEdit.ts
index 7c6723a0..2d38192e 100644
--- a/shared/js/ui/modal/ModalPlaylistEdit.ts
+++ b/shared/js/ui/modal/ModalPlaylistEdit.ts
@@ -99,7 +99,7 @@ namespace Modals {
template.find(".buttons .button-save").on('click', event => {
if(Object.keys(changed_properties).length != 0) {
changed_properties["playlist_id"] = playlist.playlist_id;
- client.serverConnection.sendCommand("playlistedit", changed_properties).then(() => {
+ client.serverConnection.send_command("playlistedit", changed_properties).then(() => {
changed_properties = {};
update_save();
}).catch(error => {
@@ -121,7 +121,7 @@ namespace Modals {
}
array[0]["playlist_id"] = playlist.playlist_id;
- client.serverConnection.sendCommand("playlistaddperm", array).then(() => {
+ client.serverConnection.send_command("playlistaddperm", array).then(() => {
changed_permissions = {};
update_save();
}).catch(error => {
@@ -176,7 +176,7 @@ namespace Modals {
const update_songs = () => {
set_song_info(tr("loading song list"));
- client.serverConnection.helper.request_playlist_songs(playlist.playlist_id).then(result => {
+ client.serverConnection.command_helper.request_playlist_songs(playlist.playlist_id).then(result => {
const entries_tag = song_tag.find(".song-list-entries");
const entry_template = $("#tmpl_playlist_edit-song_entry");
entries_tag.empty();
@@ -193,7 +193,7 @@ namespace Modals {
button_delete.detach();
else
button_delete.on('click', event => {
- client.serverConnection.sendCommand("playlistsongremove", {
+ client.serverConnection.send_command("playlistsongremove", {
playlist_id: playlist.playlist_id,
song_id: song.song_id
}).then(() => {
@@ -236,7 +236,7 @@ namespace Modals {
song_tag.find(".button-song-add").on('click', event => {
spawnSongAdd(playlist, (url, loader) => {
//playlist_id invoker previous url
- client.serverConnection.sendCommand("playlistsongadd", {
+ client.serverConnection.send_command("playlistsongadd", {
playlist_id: playlist.playlist_id,
invoker: loader,
url: url
@@ -297,7 +297,7 @@ namespace Modals {
function apply_properties(tag: JQuery, client: TSClient, playlist: Playlist, change_property: (key: string, value: string) => any, callback_current_song: (id: number) => any) {
const owns_playlist = playlist.playlist_owner_dbid == client.getClient().properties.client_database_id;
- client.serverConnection.helper.request_playlist_info(playlist.playlist_id).then(info => {
+ client.serverConnection.command_helper.request_playlist_info(playlist.playlist_id).then(info => {
tag.find(".property-owner input")
.val(info.playlist_owner_name + " (" + info.playlist_owner_dbid + ")");
diff --git a/shared/js/ui/modal/ModalPlaylistList.ts b/shared/js/ui/modal/ModalPlaylistList.ts
index 7f15cc72..d30a25ab 100644
--- a/shared/js/ui/modal/ModalPlaylistList.ts
+++ b/shared/js/ui/modal/ModalPlaylistList.ts
@@ -43,7 +43,7 @@ namespace Modals {
update_selected();
try {
- available_playlists = await client.serverConnection.helper.request_playlist_list();
+ available_playlists = await client.serverConnection.command_helper.request_playlist_list();
} catch(error) {
info_tag.text("failed to query playlist list.");
//FIXME error handling?
@@ -92,24 +92,29 @@ namespace Modals {
template.find(".footer .buttons .button-refresh").on('click', update_list);
template.find(".button-playlist-create").on('click', event => {
- const notify_handler = json => {
- client.serverConnection.commandHandler.unset_handler("notifyplaylistcreated", notify_handler);
- update_list().then(() => {
- spawnYesNo(tr("Playlist created successful"), tr("The playlist has been successfully created.
Should we open the editor?"), result => {
- if(result) {
- for(const playlist of available_playlists) {
- if(playlist.playlist_id == json[0]["playlist_id"]) {
- spawnPlaylistEdit(client, playlist).close_listener.push(update_list);
- return;
+ const single_handler: connection.SingleCommandHandler = {
+ function: command => {
+ const json = command.arguments;
+ update_list().then(() => {
+ spawnYesNo(tr("Playlist created successful"), tr("The playlist has been successfully created.
Should we open the editor?"), result => {
+ if(result) {
+ for(const playlist of available_playlists) {
+ if(playlist.playlist_id == json[0]["playlist_id"]) {
+ spawnPlaylistEdit(client, playlist).close_listener.push(update_list);
+ return;
+ }
}
}
- }
+ });
});
- });
+
+ return true;
+ },
+ command: "notifyplaylistcreated"
};
- client.serverConnection.commandHandler.set_handler("notifyplaylistcreated", notify_handler);
- client.serverConnection.sendCommand("playlistcreate").catch(error => {
- client.serverConnection.commandHandler.unset_handler("notifyplaylistcreated", notify_handler);
+ client.serverConnection.command_handler_boss().register_single_handler(single_handler);
+ client.serverConnection.send_command("playlistcreate").catch(error => {
+ client.serverConnection.command_handler_boss().remove_single_handler(single_handler);
if(error instanceof CommandResult)
error = error.extra_message || error.message;
createErrorModal(tr("Unable to create playlist"), tr("Failed to create playlist
Message: ") + error).open();
@@ -126,7 +131,7 @@ namespace Modals {
Modals.spawnYesNo(tr("Are you sure?"), tr("Do you really want to delete this playlist?"), result => {
if(result) {
- client.serverConnection.sendCommand("playlistdelete", {playlist_id: selected_playlist.playlist_id}).then(() => {
+ client.serverConnection.send_command("playlistdelete", {playlist_id: selected_playlist.playlist_id}).then(() => {
createInfoModal(tr("Playlist deleted successful"), tr("This playlist has been deleted successfully.")).open();
update_list();
}).catch(error => {
diff --git a/shared/js/ui/modal/ModalQuery.ts b/shared/js/ui/modal/ModalQuery.ts
index 8308d5ab..ff68bd1e 100644
--- a/shared/js/ui/modal/ModalQuery.ts
+++ b/shared/js/ui/modal/ModalQuery.ts
@@ -20,21 +20,26 @@ namespace Modals {
}
//client_login_password
- globalClient.serverConnection.commandHandler["notifyquerycreated"] = json => {
- json = json[0];
+ const single_handler: connection.SingleCommandHandler = {
+ function: command => {
+ const json = command.arguments[0];
- spawnQueryCreated({
- username: name,
- password: json.client_login_password
- }, true);
+ spawnQueryCreated({
+ username: name,
+ password: json.client_login_password
+ }, true);
- if(callback_created)
- callback_created(name, json.client_login_password);
+ if(callback_created)
+ callback_created(name, json.client_login_password);
+ return true;
+ }
};
-
- globalClient.serverConnection.sendCommand("querycreate", {
+ globalClient.serverConnection.command_handler_boss().register_single_handler(single_handler);
+ globalClient.serverConnection.send_command("querycreate", {
client_login_name: name
}).catch(error => {
+ globalClient.serverConnection.command_handler_boss().remove_single_handler(single_handler);
+
if(error instanceof CommandResult)
error = error.extra_message || error.message;
createErrorModal(tr("Unable to create account"), tr("Failed to create account
Message: ") + error).open();
diff --git a/shared/js/ui/modal/ModalQueryManage.ts b/shared/js/ui/modal/ModalQueryManage.ts
index 7b990d7c..f5f3a1f1 100644
--- a/shared/js/ui/modal/ModalQueryManage.ts
+++ b/shared/js/ui/modal/ModalQueryManage.ts
@@ -19,8 +19,8 @@ namespace Modals {
const update_list = () => {
const info_tag = modal.htmlTag.find(".footer .info a");
info_tag.text("loading...");
- client.serverConnection.helper.current_virtual_server_id().then(server_id => {
- client.serverConnection.helper.request_query_list(server_id).then(result => {
+ client.serverConnection.command_helper.current_virtual_server_id().then(server_id => {
+ client.serverConnection.command_helper.request_query_list(server_id).then(result => {
selected_query = undefined;
const entries_tag = modal.htmlTag.find(".query-list-entries");
@@ -73,7 +73,7 @@ namespace Modals {
createInputModal(tr("Change account name"), tr("Enter the new name for the login:
"), text => text.length >= 3, result => {
if(result) {
- client.serverConnection.sendCommand("queryrename", {
+ client.serverConnection.send_command("queryrename", {
client_login_name: selected_query.username,
client_new_login_name: result
}).catch(error => {
@@ -92,23 +92,28 @@ namespace Modals {
createInputModal(tr("Change account's password"), tr("Enter a new password (leave blank for auto generation):
"), text => true, result => {
if(result !== false) {
- client.serverConnection.sendCommand("querychangepassword", {
+ const single_handler: connection.SingleCommandHandler = {
+ command: "notifyquerypasswordchanges",
+ function: command => {
+ Modals.spawnQueryCreated({
+ username: command.arguments[0]["client_login_name"],
+ password: command.arguments[0]["client_login_password"]
+ }, false);
+
+ return true;
+ }
+ };
+ client.serverConnection.command_handler_boss().register_single_handler(single_handler);
+
+ client.serverConnection.send_command("querychangepassword", {
client_login_name: selected_query.username,
client_login_password: result
}).catch(error => {
+ client.serverConnection.command_handler_boss().remove_single_handler(single_handler);
if(error instanceof CommandResult)
error = error.extra_message || error.message;
createErrorModal(tr("Unable to change password"), tr("Failed to change password
Message: ") + error).open();
});
-
- client.serverConnection.commandHandler["notifyquerypasswordchanges"] = json => {
- Modals.spawnQueryCreated({
- username: json[0]["client_login_name"],
- password: json[0]["client_login_password"]
- }, false);
-
- client.serverConnection.commandHandler["notifyquerypasswordchanges"] = undefined;
- };
}
}).open();
});
@@ -117,7 +122,7 @@ namespace Modals {
Modals.spawnYesNo(tr("Are you sure?"), tr("Do you really want to delete this account?"), result => {
if(result) {
- client.serverConnection.sendCommand("querydelete", {
+ client.serverConnection.send_command("querydelete", {
client_login_name: selected_query.username
}).catch(error => {
if(error instanceof CommandResult)
diff --git a/shared/js/ui/server.ts b/shared/js/ui/server.ts
index 07ddf314..cf9d9dac 100644
--- a/shared/js/ui/server.ts
+++ b/shared/js/ui/server.ts
@@ -144,7 +144,7 @@ class ServerEntry {
log.info(LogCategory.SERVER, tr("Changing server properties %o"), properties);
console.log(tr("Changed properties: %o"), properties);
if (properties)
- this.channelTree.client.serverConnection.sendCommand("serveredit", properties).then(() => {
+ this.channelTree.client.serverConnection.send_command("serveredit", properties).then(() => {
sound.play(Sound.SERVER_EDITED_SELF);
});
});
@@ -200,7 +200,7 @@ class ServerEntry {
if(this.info_request_promise && Date.now() - this.lastInfoRequest < 1000) return this.info_request_promise;
this.lastInfoRequest = Date.now();
this.nextInfoRequest = this.lastInfoRequest + 10 * 1000;
- this.channelTree.client.serverConnection.sendCommand("servergetvariables").catch(error => {
+ this.channelTree.client.serverConnection.send_command("servergetvariables").catch(error => {
this.info_request_promise_reject(error);
this.info_request_promise = undefined;
this.info_request_promise_reject = undefined;
diff --git a/shared/js/ui/view.ts b/shared/js/ui/view.ts
index c7d1a537..bb74e0b9 100644
--- a/shared/js/ui/view.ts
+++ b/shared/js/ui/view.ts
@@ -450,7 +450,7 @@ class ChannelTree {
createInputModal(tr("Poke clients"), tr("Poke message:
"), text => true, result => {
if (typeof(result) === "string") {
for (const client of this.currently_selected as ClientEntry[])
- this.client.serverConnection.sendCommand("clientpoke", {
+ this.client.serverConnection.send_command("clientpoke", {
clid: client.clientId(),
msg: result
});
@@ -467,7 +467,7 @@ class ChannelTree {
callback: () => {
const target = this.client.getClient().currentChannel().getChannelId();
for(const client of clients)
- this.client.serverConnection.sendCommand("clientmove", {
+ this.client.serverConnection.send_command("clientmove", {
clid: client.clientId(),
cid: target
});
@@ -483,7 +483,7 @@ class ChannelTree {
createInputModal(tr("Kick clients from channel"), tr("Kick reason:
"), text => true, result => {
if (result) {
for (const client of clients)
- this.client.serverConnection.sendCommand("clientkick", {
+ this.client.serverConnection.send_command("clientkick", {
clid: client.clientId(),
reasonid: ViewReasonId.VREASON_CHANNEL_KICK,
reasonmsg: result
@@ -503,7 +503,7 @@ class ChannelTree {
createInputModal(tr("Kick clients from server"), tr("Kick reason:
"), text => true, result => {
if (result) {
for (const client of clients)
- this.client.serverConnection.sendCommand("clientkick", {
+ this.client.serverConnection.send_command("clientkick", {
clid: client.clientId(),
reasonid: ViewReasonId.VREASON_SERVER_KICK,
reasonmsg: result
@@ -520,11 +520,13 @@ class ChannelTree {
callback: () => {
Modals.spawnBanClient((clients).map(entry => entry.clientNickName()), (data) => {
for (const client of clients)
- this.client.serverConnection.sendCommand("banclient", {
+ this.client.serverConnection.send_command("banclient", {
uid: client.properties.client_unique_identifier,
banreason: data.reason,
time: data.length
- }, [data.no_ip ? "no-ip" : "", data.no_hwid ? "no-hardware-id" : "", data.no_name ? "no-nickname" : ""]).then(() => {
+ }, {
+ flagset: [data.no_ip ? "no-ip" : "", data.no_hwid ? "no-hardware-id" : "", data.no_name ? "no-nickname" : ""]
+ }).then(() => {
sound.play(Sound.USER_BANNED);
});
});
@@ -545,7 +547,7 @@ class ChannelTree {
Modals.spawnYesNo(tr("Are you sure?"), tag_container, result => {
if(result) {
for(const client of clients)
- this.client.serverConnection.sendCommand("musicbotdelete", {
+ this.client.serverConnection.send_command("musicbotdelete", {
botid: client.properties.client_database_id
});
}
@@ -595,7 +597,7 @@ class ChannelTree {
if(!properties) return;
properties["cpid"] = parent ? parent.channelId : 0;
log.debug(LogCategory.CHANNEL, tr("Creating a new channel.\nProperties: %o\nPermissions: %o"), properties);
- this.client.serverConnection.sendCommand("channelcreate", properties).then(() => {
+ this.client.serverConnection.send_command("channelcreate", properties).then(() => {
let channel = this.find_channel_by_name(properties.channel_name, parent, true);
if(!channel) {
log.error(LogCategory.CHANNEL, tr("Failed to resolve channel after creation. Could not apply permissions!"));
@@ -613,7 +615,9 @@ class ChannelTree {
}
perms[0]["cid"] = channel.channelId;
- return this.client.serverConnection.sendCommand("channeladdperm", perms, ["continueonerror"]).then(() => new Promise(resolve => { resolve(channel); }));
+ return this.client.serverConnection.send_command("channeladdperm", perms, {
+ flagset: ["continueonerror"]
+ }).then(() => new Promise(resolve => { resolve(channel); }));
}
return new Promise(resolve => { resolve(channel); })
diff --git a/shared/js/voice/AudioController.ts b/shared/js/voice/AudioController.ts
index a2ba9201..85673e00 100644
--- a/shared/js/voice/AudioController.ts
+++ b/shared/js/voice/AudioController.ts
@@ -151,7 +151,7 @@ class AudioController {
private playQueue() {
let buffer: AudioBuffer;
- while(buffer = this.audioCache.pop_front()) {
+ while((buffer = this.audioCache.pop_front())) {
if(this.playingAudioCache.length >= this._latencyBufferLength * 1.5 + 3) {
console.log(tr("Dropping buffer because playing queue grows to much"));
continue; /* drop the data (we're behind) */
diff --git a/shared/js/voice/VoiceHandler.ts b/shared/js/voice/VoiceHandler.ts
index 52023bae..774cb86b 100644
--- a/shared/js/voice/VoiceHandler.ts
+++ b/shared/js/voice/VoiceHandler.ts
@@ -277,6 +277,11 @@ class VoiceConnection {
createSession() {
+ if(!audio.player.initialized()) {
+ console.log(tr("Audio player isn't initialized yet. Waiting for gesture."));
+ audio.player.on_ready(() => this.createSession());
+ return;
+ }
if(!this.current_encoding_supported()) return false;
if(this.rtcPeerConnection) {
diff --git a/shared/tsconfig/dtsconfig_packed.json b/shared/tsconfig/dtsconfig_packed.json
new file mode 100644
index 00000000..633c619a
--- /dev/null
+++ b/shared/tsconfig/dtsconfig_packed.json
@@ -0,0 +1,10 @@
+{
+ "source_files": [
+ "../js/**/*.ts"
+ ],
+ "exclude": [
+ "../js/workers/**/*.ts",
+ "../js/load.ts"
+ ],
+ "target_file": "../declarations/exports_packed.d.ts"
+}
diff --git a/shared/tsconfig/tsconfig_packed_loader.json b/shared/tsconfig/tsconfig_packed_loader.json
new file mode 100644
index 00000000..edfb5f30
--- /dev/null
+++ b/shared/tsconfig/tsconfig_packed_loader.json
@@ -0,0 +1,12 @@
+{
+ "compilerOptions": {
+ "target": "es6",
+ "module": "commonjs",
+ "sourceMap": true
+ },
+ "include": [
+ "../declarations/imports_*.d.ts",
+ "../declarations/exports_packed.d.ts",
+ "../js/load.ts"
+ ]
+}
\ No newline at end of file
diff --git a/tools/dtsgen/declarator.ts b/tools/dtsgen/declarator.ts
index f29e7f53..eb96f2a4 100644
--- a/tools/dtsgen/declarator.ts
+++ b/tools/dtsgen/declarator.ts
@@ -1,6 +1,8 @@
import * as ts from "typescript";
import {SyntaxKind} from "typescript";
+type TSExpression = ts.Type | ts.Node;
+
interface Array {
last?(): T;
}
@@ -100,9 +102,9 @@ class StackParameters implements StackParameter {
}
-const generators: {[key: number]:((settings: _Settings, stack: StackParameters, node: ts.Node) => ts.Node | undefined) | undefined} = {};
+const generators: {[key: number]:((settings: _Settings, stack: StackParameters, node: ts.Node | ts.Type) => ts.Node | ts.Type | undefined) | undefined} = {};
-function _generate(settings: _Settings, stack: StackParameters, layer: ts.Node[], node: ts.Node) {
+function _generate(settings: _Settings, stack: StackParameters, layer: TSExpression[], node: ts.Node) {
//console.log(SyntaxKind[node.kind]);
if(generators[node.kind]) {
const result = generators[node.kind](settings, stack, node);
@@ -322,7 +324,6 @@ generators[SyntaxKind.ClassDeclaration] = (settings, stack, node: ts.ClassDeclar
};
generators[SyntaxKind.PropertySignature] = (settings, stack, node: ts.PropertySignature) => {
- console.log(SyntaxKind[node.type.kind]);
let type: ts.TypeNode = node.type;
switch (node.type.kind) {
case SyntaxKind.LiteralType:
@@ -383,4 +384,11 @@ generators[SyntaxKind.EnumDeclaration] = (settings, stack, node: ts.EnumDeclarat
for(const member of node.members)
members.push(generators[SyntaxKind.EnumMember](settings, stack, member));
return ts.createEnumDeclaration(undefined, append_export(append_declare(node.modifiers, !stack.flag_declare), stack.flag_namespace), node.name, members);
-};
\ No newline at end of file
+};
+
+
+generators[SyntaxKind.TypeParameter] = (settings, stack, node: ts.TypeParameter) => undefined;
+generators[SyntaxKind.HeritageClause] = (settings, stack, node: ts.HeritageClause) => undefined;
+generators[SyntaxKind.IfStatement] = (settings, stack, node: ts.IfStatement) => undefined;
+generators[SyntaxKind.ExpressionStatement] = (settings, stack, node: ts.ExpressionStatement) => undefined;
+generators[SyntaxKind.SemicolonClassElement] = (settings, stack, node: ts.ExpressionStatement) => undefined;
\ No newline at end of file
diff --git a/tools/dtsgen/out.d.ts b/tools/dtsgen/out.d.ts
index 152bd336..d2410873 100644
--- a/tools/dtsgen/out.d.ts
+++ b/tools/dtsgen/out.d.ts
@@ -1,13 +1,6 @@
-/* File: /home/wolverindev/TeaSpeak/TeaSpeak/Web-Client/tools/dtsgen/test/test_03.ts */
-declare enum YY {
- H,
- B
-}
-declare interface X {
- type: any;
- c: YY.B;
-}
-declare class X {
- static x();
+/* File: /home/wolverindev/TeaSpeak/TeaSpeak/Web-Client/tools/dtsgen/test/test_04.ts */
+declare class TestClass extends Promise {
}
+declare const lambda_function;
+declare const lambda_function2;
diff --git a/tools/dtsgen/test/test_04.ts b/tools/dtsgen/test/test_04.ts
new file mode 100644
index 00000000..1263a5e6
--- /dev/null
+++ b/tools/dtsgen/test/test_04.ts
@@ -0,0 +1,4 @@
+class TestClass extends Promise { }
+
+const lambda_function = (str) => {};
+const lambda_function2 = (str: string) => {};
\ No newline at end of file