TeaWeb/js/connection.ts

622 lines
22 KiB
TypeScript
Raw Normal View History

2018-02-27 16:20:49 +00:00
/// <reference path="ui/channel.ts" />
/// <reference path="client.ts" />
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<T> {
resolve: (value?: T | PromiseLike<T>) => void;
reject: (reason?: any) => void;
code: string;
timeout: NodeJS.Timer;
}
class ServerConnection {
_client: TSClient;
_remoteHost: string;
_remotePort: number;
_socket: WebSocket;
2018-03-07 18:06:52 +00:00
_connectionState: ConnectionState = ConnectionState.UNCONNECTED;
2018-02-27 16:20:49 +00:00
commandHandler: ConnectionCommandHandler;
2018-03-07 18:06:52 +00:00
private _connectTimeoutHandler: NodeJS.Timer = undefined;
2018-02-27 16:20:49 +00:00
private _retCodeIdx: number;
private _retListener: ReturnListener<CommandResult>[];
constructor(client : TSClient) {
this._client = client;
this._socket = null;
this.commandHandler = new ConnectionCommandHandler(this);
this._retCodeIdx = 0;
this._retListener = [];
}
on_connect: () => void = () => {
console.log("Client connected!");
chat.serverChat().appendMessage("Client connected");
};
on_connected: () => void = () => {};
private generateReturnCode() : string {
return (this._retCodeIdx++).toString();
}
2018-03-07 18:06:52 +00:00
startConnection(host : string, port : number, timeout: number = 1000) {
if(this._connectTimeoutHandler) {
clearTimeout(this._connectTimeoutHandler);
this._connectTimeoutHandler = null;
this.disconnect();
}
this.updateConnectionState(ConnectionState.CONNECTING);
2018-02-27 16:20:49 +00:00
this._remoteHost = host;
this._remotePort = port;
const self = this;
try {
2018-03-07 18:06:52 +00:00
this._connectTimeoutHandler = setTimeout(() => {
this.disconnect();
this._client.handleDisconnect(DisconnectReason.CONNECT_FAILURE);
}, timeout);
2018-02-27 16:20:49 +00:00
this._socket = new WebSocket('ws:' + this._remoteHost + ":" + this._remotePort);
2018-03-07 18:06:52 +00:00
clearTimeout(this._connectTimeoutHandler);
this._connectTimeoutHandler = null;
2018-03-07 19:14:36 +00:00
const _socketCpy = this._socket;
this._socket.onopen = () => {
if(this._socket != _socketCpy) return;
this.on_connect();
2018-02-27 16:20:49 +00:00
};
2018-03-07 18:06:52 +00:00
this._socket.onclose = event => {
2018-03-07 19:14:36 +00:00
if(this._socket != _socketCpy) return;
2018-03-07 18:06:52 +00:00
this._client.handleDisconnect(DisconnectReason.CONNECTION_CLOSED, {
code: event.code,
reason: event.reason,
event: event
});
2018-02-27 16:20:49 +00:00
};
2018-03-07 19:14:36 +00:00
this._socket.onerror = e => {
if(this._socket != _socketCpy) return;
2018-02-27 16:20:49 +00:00
console.log("Got error: (" + self._socket.readyState + ")");
console.log(e);
};
2018-03-07 19:14:36 +00:00
this._socket.onmessage = msg => {
if(this._socket != _socketCpy) return;
2018-03-07 18:06:52 +00:00
self.handleWebSocketMessage(msg.data);
2018-02-27 16:20:49 +00:00
};
2018-03-07 18:06:52 +00:00
this.updateConnectionState(ConnectionState.INITIALISING);
2018-02-27 16:20:49 +00:00
} catch (e) {
2018-03-07 18:06:52 +00:00
this.disconnect();
this._client.handleDisconnect(DisconnectReason.CONNECT_FAILURE, e);
2018-02-27 16:20:49 +00:00
}
}
2018-03-07 18:06:52 +00:00
updateConnectionState(state: ConnectionState) {
this._connectionState = state;
}
2018-03-07 19:14:36 +00:00
disconnect() : boolean {
if(this._connectionState == ConnectionState.UNCONNECTED) return false;
this.updateConnectionState(ConnectionState.UNCONNECTED);
2018-03-07 18:06:52 +00:00
2018-03-07 19:14:36 +00:00
if(this._socket) this._socket.close(3000 + 0xFF, "request disconnect");
2018-02-27 16:20:49 +00:00
this._socket = null;
2018-03-07 18:06:52 +00:00
for(let future of this._retListener)
future.reject("Connection closed");
this._retListener = [];
this._retCodeIdx = 0;
2018-03-07 19:14:36 +00:00
return true;
2018-02-27 16:20:49 +00:00
}
2018-03-07 18:06:52 +00:00
private handleWebSocketMessage(data) {
2018-02-27 16:20:49 +00:00
if(typeof(data) === "string") {
2018-03-07 18:06:52 +00:00
let json;
2018-02-27 16:20:49 +00:00
try {
json = JSON.parse(data);
} catch(e) {
console.error("Could not parse message json!");
alert(e); // error in the above string (in this case, yes)!
return;
}
if(json["type"] === undefined) {
console.log("Missing data type!");
return;
}
if(json["type"] === "command") this.handleCommand(json);
else if(json["type"] === "WebRTC") this._client.voiceConnection.handleControlPacket(json);
else {
console.log("Unknown command type " + json["type"]);
}
}
}
handleCommand(json) {
console.log("Handling command '" + json["command"] + "'");
let fn = this.commandHandler[json["command"]];
if(fn === undefined) {
console.log("Missing command '" + json["command"] + "'");
return;
}
fn.call(this.commandHandler, json["data"]);
}
2018-03-07 18:06:52 +00:00
sendData(data: any) { //TODO check stuff?
this._socket.send(data);
2018-02-27 16:20:49 +00:00
}
sendCommand(command: string, data: any = {}, logResult: boolean = true) : Promise<CommandResult> {
const _this = this;
let result = new Promise<CommandResult>((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<CommandResult>();
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(JSON.stringify({
"type": "command",
"command": command,
"data": _data
}));
});
return new Promise<CommandResult>((resolve, failed) => {
result.then(resolve).catch(ex => {
if(logResult) {
if(ex instanceof CommandResult) {
let res = ex;
if(!res.success) {
chat.serverChat().appendError(res.extra_message.length == 0 ? res.message : res.extra_message);
}
} else if(typeof(ex) == "string") {
chat.serverChat().appendError("Command execution resuluts in " + ex);
} else {
console.error("Invalid promise result type: " + typeof (ex) + ". Result:");
console.error(ex);
}
}
failed(ex);
})
});
}
get connected() : boolean {
return this._socket && this._socket.readyState == WebSocket.OPEN;
}
/**
* HELPER METHODS
*/
joinChannel(channel: ChannelEntry, password: string = "") : Promise<CommandResult> {
return this.sendCommand("clientmove", [{
"clid": this._client.getClientId(),
"cid": channel.getChannelId(),
"cpw": password
}])
}
sendMessage(message: string, type: ChatType, target?: ChannelEntry | ClientEntry) : Promise<CommandResult> {
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<CommandResult> {
2018-03-07 18:06:52 +00:00
let data = {};
2018-02-27 16:20:49 +00:00
data[key] = value;
return this.sendCommand("clientupdate", data);
}
}
class ConnectionCommandHandler {
2018-03-07 18:06:52 +00:00
readonly connection: ServerConnection;
2018-02-27 16:20:49 +00:00
constructor(connection) {
this.connection = connection;
this["error"] = this.handleCommandResult;
this["channellist"] = this.handleCommandChannelList;
this["notifychannelcreated"] = this.handleCommandChannelCreate;
this["notifychanneldeleted"] = this.handleCommandChannelDelete;
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;
}
handleCommandResult(json) {
json = json[0]; //Only one bulk
let code : string = json["return_code"];
if(code.length == 0) {
console.log("Invalid return code! (" + 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
2018-03-07 18:06:52 +00:00
console.log("Setting up voice ");
2018-02-27 16:20:49 +00:00
this.connection._client.voiceConnection.createSession();
json = json[0]; //Only one bulk
this.connection._client.clientId = json["aclid"];
this.connection._client.getClient().updateVariable("client_nickname", json["acn"]);
for(let key in json) {
if(key === "aclid") continue;
if(key === "acn") continue;
this.connection._client.channelTree.server.updateProperty(key, json[key]);
}
chat.serverChat().name = this.connection._client.channelTree.server.properties["virtualserver_name"];
chat.serverChat().appendMessage("Connected as {0}", true, this.connection._client.getClient().createChatTag(true));
this.connection.on_connected();
}
private createChannelFromJson(json, ignoreOrder: boolean = false) {
let tree = this.connection._client.channelTree;
let channel = new ChannelEntry(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("Invalid channel order id!");
return;
}
}
let parent = tree.findChannel(json["cpid"]);
if(!parent && json["cpid"] != 0) {
console.error("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 :)
}
}
}
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;
channel.updateProperty(key, json[key]);
}
}
handleCommandChannelList(json) {
console.log("Got " + json.length + " new channels");
for(let index = 0; index < json.length; index++)
this.createChannelFromJson(json[index], true);
}
handleCommandChannelCreate(json) {
this.createChannelFromJson(json[0]);
}
handleCommandChannelDelete(json) {
let tree = this.connection._client.channelTree;
console.log("Got " + json.length + " channel deletions");
for(let index = 0; index < json.length; index++) {
let channel = tree.findChannel(json[index]["cid"]);
if(!channel) {
console.error("Invalid channel onDelete (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) {
client = new ClientEntry(json["clid"], json["client_nickname"]);
client = tree.insertClient(client, channel);
} else {
2018-03-07 19:14:36 +00:00
if(client == this.connection._client.getClient())
2018-02-27 16:20:49 +00:00
chat.channelChat().name = channel.channelName();
tree.moveClient(client, channel);
}
if(json["reasonid"] == ViewReasonId.VREASON_USER_ACTION) {
if(old_channel) {
chat.serverChat().appendMessage("{0} appeared from {1} to {2}", true, client.createChatTag(true), old_channel.createChatTag(true), channel.createChatTag(true));
} else {
chat.serverChat().appendMessage("{0} connected to channel {1}", true, client.createChatTag(true), channel.createChatTag(true));
}
}
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;
client.updateVariable(key, json[key]);
}
}
handleCommandClientLeftView(json) {
json = json[0]; //Only one bulk
let tree = this.connection._client.channelTree;
let client = tree.findClient(json["clid"]);
if(!client) {
console.error("Unknown client left!");
return 0;
}
if(client == this.connection._client.getClient()) {
2018-03-07 18:06:52 +00:00
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
this.connection._client.handleDisconnect(DisconnectReason.UNKNOWN, json);
2018-02-27 16:20:49 +00:00
return;
}
let channel_from = tree.findChannel(json["cfid"]);
let channel_to = tree.findChannel(json["ctid"]);
if(json["reasonid"] == ViewReasonId.VREASON_USER_ACTION) {
chat.serverChat().appendMessage("{0} disappeared from {1} to {2}", true, client.createChatTag(true), channel_from.createChatTag(true), channel_to.createChatTag(true));
} else if(json["reasonid"] == ViewReasonId.VREASON_SERVER_LEFT) {
chat.serverChat().appendMessage("{0} left the server ({1})", true, client.createChatTag(true), json["reasonmsg"]);
} else if(json["reasonid"] == ViewReasonId.VREASON_SERVER_KICK) {
chat.serverChat().appendError("{0} was kicked from the server by {1}. ({2})",
client.createChatTag(true),
ClientEntry.chatTag(json["invokerid"], json["invokername"], json["invokeruid"]),
json["reasonmsg"]
);
} 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("{0} was banned {1} by {2}. ({3})",
client.createChatTag(true),
duration,
ClientEntry.chatTag(json["invokerid"], json["invokername"], json["invokeruid"]),
json["reasonmsg"]
);
} else {
console.error("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("Unknown client move (Client)!");
return 0;
}
if(!channel_to) {
console.error("Unknown client move (Channel to)!");
return 0;
}
if(!channel_from) //Not critical
console.error("Unknown client move (Channel from)!");
tree.moveClient(client, channel_to);
2018-03-07 19:14:36 +00:00
if(client instanceof LocalClientEntry)
chat.channelChat().name = channel_to.channelName();
2018-02-27 16:20:49 +00:00
if(json["reasonid"] == ViewReasonId.VREASON_MOVED) {
chat.serverChat().appendMessage("{0} was moved from channel {1} to {2} by {3}", true,
client.createChatTag(true),
channel_from ? channel_from.createChatTag(true) : undefined,
channel_to.createChatTag(true),
ClientEntry.chatTag(json["invokerid"], json["invokername"], json["invokeruid"])
);
} else if(json["reasonid"] == ViewReasonId.VREASON_USER_ACTION) {
chat.serverChat().appendMessage("{0} switched from channel {1} to {2}", true,
client.createChatTag(true),
channel_from ? channel_from.createChatTag(true) : undefined,
channel_to.createChatTag(true)
);
}
}
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("Unknown channel move (Channel)!");
return 0;
}
let prev = tree.findChannel(json["order"]);
if(!prev && json["order"] != 0) {
console.error("Unknown channel move (prev)!");
return 0;
}
let parent = tree.findChannel(json["cpid"]);
if(!parent && json["cpid"] != 0) {
console.error("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("Unknown channel edit (Channel)!");
return 0;
}
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;
channel.updateProperty(key, json[key]);
}
}
handleNotifyTextMessage(json) {
json = json[0]; //Only one bulk
//TODO chat format?
let mode = json["targetmode"];
let invoker = this.connection._client.channelTree.findClient(json["invokerid"]);
if(!invoker) { //TODO ignore?
console.error("Invalid chat message!");
return;
}
if(mode == 1){
if(invoker == this.connection._client.getClient()) {
let target = this.connection._client.channelTree.findClient(json["target"]);
target.chat(true).appendMessage("<< " + json["msg"]);
} else {
invoker.chat(true).appendMessage(">> " + json["msg"]);
}
} else if(mode == 2) {
chat.channelChat().appendMessage("{0} >> " + json["msg"], true, invoker.createChatTag(true))
} else if(mode == 3) {
chat.serverChat().appendMessage("{0} >> " + json["msg"], true, invoker.createChatTag(true));
}
}
handleNotifyClientUpdated(json) {
json = json[0]; //Only one bulk
let client = this.connection._client.channelTree.findClient(json["clid"]);
if(!client) {
console.error("Tried to update an non existing client");
return;
}
for(let key in json) {
if(key == "clid") continue;
client.updateVariable(key, json[key]);
}
if(this.connection._client.selectInfo.currentSelected == client)
this.connection._client.selectInfo.update();
}
handleNotifyServerEdited(json) {
json = json[0];
for(let key in json) {
if(key === "invokerid") continue;
if(key === "invokername") continue;
if(key === "invokeruid") continue;
if(key === "reasonid") continue;
this.connection._client.channelTree.server.updateProperty(key, json[key]);
}
}
handleNotifyServerUpdated(json) {
json = json[0];
for(let key in json) {
if(key === "invokerid") continue;
if(key === "invokername") continue;
if(key === "invokeruid") continue;
if(key === "reasonid") continue;
this.connection._client.channelTree.server.updateProperty(key, json[key]);
}
let info = this.connection._client.selectInfo;
if(info.currentSelected instanceof ServerEntry)
info.update();
}
}