From 850c5effaa574ef6e44a775bfe260aba6814bfbe Mon Sep 17 00:00:00 2001 From: Griatch Date: Sun, 15 Feb 2015 08:58:50 +0100 Subject: [PATCH] Starting reworking the webclient to use the new oob system format. --- evennia/server/portal/telnet.py | 4 +- evennia/server/portal/websocket_client.py | 53 +++++-- .../js/evennia_websocket_webclient.js | 142 +++++++++++++----- 3 files changed, 145 insertions(+), 54 deletions(-) diff --git a/evennia/server/portal/telnet.py b/evennia/server/portal/telnet.py index ecdc783a5f..2ad8acfb6c 100644 --- a/evennia/server/portal/telnet.py +++ b/evennia/server/portal/telnet.py @@ -236,9 +236,9 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session): return if "oob" in kwargs and "OOB" in self.protocol_flags: # oob is a list of [(cmdname, arg, kwarg), ...] - for cmdname, args, kwargs in kwargs["oob"]: + for cmdname, args, okwargs in kwargs["oob"]: #print "telnet oob data_out:", cmdname, args, kwargs - self.oob.data_out(cmdname, *args, **kwargs) + self.oob.data_out(cmdname, *args, **okwargs) # parse **kwargs, falling back to ttype if nothing is given explicitly ttype = self.protocol_flags.get('TTYPE', {}) diff --git a/evennia/server/portal/websocket_client.py b/evennia/server/portal/websocket_client.py index 06c63d13ac..9b3e97deaf 100644 --- a/evennia/server/portal/websocket_client.py +++ b/evennia/server/portal/websocket_client.py @@ -78,27 +78,48 @@ class WebSocketClient(Protocol, Session): OOB - This is an Out-of-band instruction. If so, the remaining string should be a json-packed string on the form {oobfuncname: [args, ], ...} - any other prefix (or lack of prefix) is considered - plain text data, to be treated like a game + CMD - plain text data, to be treated like a game input command. """ - if string[:3] == "OOB": - string = string[3:] - try: - oobdata = json.loads(string) - for (key, args) in oobdata.items(): - #print "oob data in:", (key, args) - self.data_in(text=None, oob=(key, make_iter(args))) - except Exception: - log_trace("Websocket malformed OOB request: %s" % string) - else: + mode = string[:3] + data = string[3:] + + if mode == "OOB": + # an out-of-band command + self.decode_json(data) + elif mode == "CMD": # plain text input - self.data_in(text=string) + self.data_in(text=data) def sendLine(self, line): "send data to client" return self.transport.write(line) + def json_decode(self, data): + """ + Decodes incoming data from the client + + [cmdname, [args],{kwargs}] -> cmdname *args **kwargs + + """ + try: + cmdname, args, kwargs = json.loads(data) + except Exception: + log_trace("Websocket malformed OOB request: %s" % data) + raise + self.sessionhandler.data_in(oob=(cmdname, args, kwargs)) + + def json_encode(self, cmdname, *args, **kwargs): + """ + Encode OOB data for sending to client + + cmdname *args -> cmdname [json array] + cmdname **kwargs -> cmdname {json object} + + """ + cmdtuple = [cmdname, list(args), kwargs] + self.sendLine("OOB" + json.dumps(cmdtuple)) + def data_in(self, text=None, **kwargs): """ Data Websocket -> Server @@ -121,9 +142,9 @@ class WebSocketClient(Protocol, Session): except Exception, e: self.sendLine(str(e)) if "oob" in kwargs: - oobstruct = self.sessionhandler.oobstruct_parser(kwargs.pop("oob")) - #print "oob data_out:", "OOB" + json.dumps(oobstruct) - self.sendLine("OOB" + json.dumps(oobstruct)) + for cmdname, args, okwargs in kwargs["oob"]: + self.encode_json(cmdname, *args, **okwargs) + raw = kwargs.get("raw", False) nomarkup = kwargs.get("nomarkup", False) if "prompt" in kwargs: diff --git a/evennia/web/webclient/static/webclient/js/evennia_websocket_webclient.js b/evennia/web/webclient/static/webclient/js/evennia_websocket_webclient.js index dec896ed8e..9d0823f659 100644 --- a/evennia/web/webclient/static/webclient/js/evennia_websocket_webclient.js +++ b/evennia/web/webclient/static/webclient/js/evennia_websocket_webclient.js @@ -36,14 +36,14 @@ function list (args, kwargs) { function send (args, kwargs) { // show in main window. SEND returns kwargs {name:value}. - for (sendvalue in kwargs) { + for (var sendvalue in kwargs) { doShow("out", sendvalue + " = " + kwargs[sendvalue]);} } function report (args, kwargs) { // show in main window. REPORT returns kwargs // {attrfieldname:value} - for (name in kwargs) { + for (var name in kwargs) { doShow("out", name + " = " + kwargs[name]) } } @@ -55,6 +55,8 @@ function err (args, kwargs) { // display error doShow("err", args) } +// Map above functions with oob command names +var CMD_MAP = {"ECHO":echo, "LIST":list, "SEND":send, "REPORT":report, "error":err}; // // Webclient code @@ -86,34 +88,60 @@ function onClose(evt) { } function onMessage(evt) { - // called when the Evennia is sending data to client - var inmsg = evt.data - if (inmsg.length > 3 && inmsg.substr(0, 3) == "OOB") { + // called when the Evennia is sending data to client. + // Such data is always prepended by a 3-letter marker + // OOB, PRT or CMD, defining its operation + var inmsg = evt.data; + if (inmsg.length < 4) return; + var mode = inmsg.substr(0, 3); + var message = inmsg.slice(3); + if (mode == "OOB") { // dynamically call oob methods, if available + // The variables are come on the form [(cmname, [args], {kwargs}), ...] + var oobcmds = JSON.parse(message); + var errmsg = ""; + try { - var oobarray = JSON.parse(inmsg.slice(3));} // everything after OOB } - catch(err) { - // not JSON packed - a normal text - doShow('out', inmsg); - return; - } - if (typeof oobarray != "undefined") { - for (var ind in oobarray) { - try { - window[oobarray[ind][0]](oobarray[ind][1], oobarray[ind][2]) } - catch(err) { - doShow("err", "Could not execute js OOB function '" + oobarray[ind][0] + "(" + oobarray[ind][1] + oobarray[ind][2] + ")'") } + if (oobcmds instanceof Array == false) { + errmsg = "oob instruction's outermost level must be an Array."; + throw + } + for (var icmd = 0; i < oobcmds.length; icmd++) { + // call each command tuple in turn + var cmdname = oobcmds[icmd][0]; + var args = oobcmds[icmd][1]; + var kwargs = oobcmds[icmd][2]; + // match cmdname with a command existing in the + // CMD_MAP mapping + if (cmdname in CMD_MAP == false) { + errmsg = "oob command " + cmdname + " is not supported by client."; + throw + } + // we have a matching oob command in CMD_MAP. + // Prepare the error message beforehand + errmsg = "Client could not execute OOB function" + "cmdname" + "(" + args + kwargs + ")."; + // Execute + CMD_MAP[cmdname](args, kwargs); + } + } + catch(error) { + if (errmsg) { + doShow("err", errmsg); + } + else { + doShow("err", "Client could not execute OOB function in " + oobcmds); } } } - else if (inmsg.length >= 6 && inmsg.substr(0, 6) == "PROMPT") { + else if (mode == "PRT") { // handle prompt - var game_prompt = inmsg.slice(6); - doPrompt("prompt", game_prompt); + doPrompt("prompt", message); } - else { + else if (mode == "CMD") { + // normal command operation // normal message - doShow('out', inmsg); } + doShow('out', message); + } } function onError(evt) { @@ -123,15 +151,17 @@ function onError(evt) { function doSend(){ // relays data from client to Evennia. - // If OOB_debug is set, allows OOB test data on the - // form ##OOB{func:args} + // If OOB_debug is set, allows OOB test data using the syntax + // ##OOB[funcname, args, kwargs] outmsg = $("#inputfield").val(); history_add(outmsg); HISTORY_POS = 0; $('#inputform')[0].reset(); // clear input field if (OOB_debug && outmsg.length > 4 && outmsg.substr(0, 5) == "##OOB") { - if (outmsg == "##OOBUNITTEST") { + // OOB direct input + var outmsg = outmsg.slice(5); + if (outmsg == "UNITTEST") { // unittest mode doShow("out", "OOB testing mode ..."); doOOB(JSON.parse('{"ECHO":"Echo test"}')); @@ -144,26 +174,66 @@ function doSend(){ doShow("out", "... OOB testing mode done."); return } - // test OOB messaging + // send a manual OOB instruction try { - doShow("out", "OOB input: " + outmsg.slice(5)); + doShow("out", "OOB input: " + outmsg); if (outmsg.length == 5) { - doShow("err", "OOB testing syntax: ##OOB{\"cmdname:args, ...}"); } + doShow("err", "OOB syntax: ##OOB[\"cmdname\", [args], {kwargs}]"); } else { - doOOB(JSON.parse(outmsg.slice(5))); } } + doOOB(JSON.parse(outmsg.slice(5))); } + } catch(err) { - doShow("err", err) } + doShow("err", err) + } } else { // normal output - websocket.send(outmsg); } + websocket.send("CMD" + outmsg); } } -function doOOB(oobdict){ +function doOOB(cmdstring){ // Send OOB data from client to Evennia. - // Takes input on form {funcname:[args], funcname: [args], ... } - var oobmsg = JSON.stringify(oobdict); - websocket.send("OOB" + oobmsg); + // Takes input strings with syntax ["cmdname", args, kwargs] + var errmsg = ""; + try { + var cmdtuple = JSON.parse(cmdstring); + var oobmsg = ""; + if (cmdtuple instanceof Array == false) { + // a single command instruction without arguments + oobmsg = [cmdtuple, (), {}]; + } + else if { + switch (cmdtuple.length) { + case 0: + throw; + break; + case 1: + // [cmdname] + oobmsg = [cmdtuple[0], (), {}]; + break; + case 2: + // [cmdname, args] + oobmsg = [cmdtuple[0], cmdtuple[1], {}]; + break; + case 3: + // [cmdname, args, kwargs] + oobmsg = [cmdtuple[0], cmdtuple[1], cmdtuple[2]]; + break; + default: + errmsg = "Malformed OOB instruction:" + cmdstring + } + // convert to string and send it to the server + oobmsg = JSON.stringify(oobmsg); + websocket.send("OOB" + oobmsg); + } + catch { + if (errmsg) { + doSend("err", errmsg); + } + else { + doSend("err", "OOB output " + cmdtuple + " is not on the right form."); + } + } } function doShow(type, msg){ @@ -352,6 +422,6 @@ $(document).ready(function(){ }, 500); // set an idle timer to avoid proxy servers to time out on us (every 3 minutes) setInterval(function() { - websocket.send("idle"); + doSend("idle") }, 60000*3); });