From 3531d46cc3644a64b990f904e40e4846b8242a07 Mon Sep 17 00:00:00 2001 From: lassulus Date: Tue, 12 Nov 2013 02:01:29 +0100 Subject: webchat: refactor to rpc --- webchat/index.js | 129 +++++++++++++------------- webchat/proto_spec | 62 +++++++++++++ webchat/public/client.js | 75 ++++++++------- webchat/public/commands.js | 11 ++- webchat/public/functions.js | 20 +--- webchat/public/handler.js | 35 ------- webchat/public/rpc.js | 99 ++++++++++++++++++++ webchat/public/sockjs_client_transport.js | 27 ++++++ webchat/serverCommands.js | 26 ------ webchat/sockjs_server_connection_transport.js | 26 ++++++ 10 files changed, 327 insertions(+), 183 deletions(-) create mode 100644 webchat/proto_spec delete mode 100644 webchat/public/handler.js create mode 100644 webchat/public/rpc.js create mode 100644 webchat/public/sockjs_client_transport.js delete mode 100644 webchat/serverCommands.js create mode 100644 webchat/sockjs_server_connection_transport.js (limited to 'webchat') diff --git a/webchat/index.js b/webchat/index.js index 52f41d1c..70b32d14 100644 --- a/webchat/index.js +++ b/webchat/index.js @@ -4,34 +4,15 @@ var http = require('https'); var sockjs = require('sockjs'); var connect = require('connect'); var irc = require('irc'); -var serverCommands = require('./serverCommands.js'); - -var serverstate = { - connected: false, - nicks: [], - lastping: 0, -}; +var make_sockjs_server_connection_transport = require('./sockjs_server_connection_transport.js') +var RPC = require('./public/rpc.js'); var clients = []; -clients.notifyAll = function (method, params) { - var object = { - method: method, - params: params, - } - clients.forEach(function (client) { - client.conn.write(JSON.stringify(object)); - }); -} - -serverstate.clients = clients; - var irc_reconnect = function() { //reconnt to irc - serverstate.connected = false console.log("reconnecting due to pingtimeout") irc_client.disconnect() irc_client.connect() - serverstate.connected = true } var pingTimeoutDelay = 3*60*1000 @@ -52,7 +33,6 @@ var irc_client = new irc.Client('irc.freenode.net', 'kweb', { //create irc_clien autoConnect: true, stripColors: true }); -serverstate.irc_client = irc_client; irc_client.on('ping', function(server) { //restart timer on server ping console.log("got ping from server, renewing timeout for automatic reconnect"); @@ -62,25 +42,32 @@ irc_client.on('ping', function(server) { //restart timer on server ping irc_client.on('message#krebs', function(from, message) { console.log({ from: from, message: message }); - clients.notifyAll('message', { nick: from, msg: message }); + clients.map(pluck('rpc')).forEach(function (rpc) { + rpc.send('msg', {nick: from, msg: message}) + }) clearTimeout(lastping); }); irc_client.on('names#krebs', function(nicks) { - clients.notifyAll('nicklist', { nicklist: nicks }) - serverstate.connected = true - serverstate.irc_nicklist = nicks + clients.map(pluck('rpc')).forEach(function (rpc) { + Object.keys(nicks).forEach(function (nick) { + rpc.send('join', {type: 'irc', nick: nick}) + }) + }) }); irc_client.on('join#krebs', function(nick, msg) { if (nick !== 'kweb'){ - clients.notifyAll('join', { from: nick }) + clients.map(pluck('rpc')).forEach(function (rpc) { + rpc.send('join', {type: 'irc', nick: nick}) + }) } -}); +}) irc_client.on('part#krebs', function(nick, rs, msg) { - clients.notifyAll('quit', { from: nick }) -// clients.broadcast({method: 'quit', params: { from: nick }}); + clients.map(pluck('rpc')).forEach(function (rpc) { + rpc.send('part', {type: 'irc', nick: nick}) + }) }); irc_client.on('error', function (error) { @@ -93,40 +80,57 @@ var web_serv_options = { //certificates for https }; var echo = sockjs.createServer(); -echo.on('connection', function(conn) { - var origin = conn.remoteAddress; - var settings = { - conn: conn, - addr: conn.remoteAddress, - nick: conn.remoteAddress + +function pluck (key) { + return function (object) { + return object[key] } - clients.push(settings); - clients.notifyAll('join', { from: settings.nick }) -// irc_client.say("#krebs", origin + ' has joined'); - if (typeof irc_client.chans['#krebs'] === 'object') { - conn.write(JSON.stringify({method: 'nicklist', params: { nicklist: irc_client.chans['#krebs'].users }})); //send current nicklist - }; - conn.write(JSON.stringify({method: 'message', params: { nick: 'system', msg: 'hello' }})) //welcome message - console.log(irc_client.chans['#krebs']) - conn.on('data', function(data) { - console.log('data:',data); - try { - var command = JSON.parse(data); - } catch (error) { - console.log(error); - } - if (!command || typeof command !== 'object') { - command = {} +} +var total_clients_ever_connected = 0 + +echo.on('connection', function (connection) { + var client = {} + client.rpc = new RPC(make_sockjs_server_connection_transport(connection)) + client.nick = 'anon'+(++total_clients_ever_connected) + client.rpc.send('your_nick', {nick: client.nick}) + client.rpc.register('msg', {msg: 'string'}, function (params, callback) { + callback(null) + clients.map(pluck('rpc')).forEach(function (rpc) { + rpc.send('msg', {type: 'web', nick: client.nick, msg: params.msg}) + }) + }) + client.rpc.register('nick', {nick: 'string'}, function (params, callback) { + if (!!~clients.map(pluck('nick')).indexOf(params.nick)) { + callback('name already taken') + } else if (/^anon[0-9]+$/.test(params.nick)) { + callback('bad nick') + } else { + var oldnick = client.nick + client.nick = params.nick + callback(null) + clients.map(pluck('rpc')).forEach(function (rpc) { + rpc.send('nick', {type: 'web', newnick: client.nick, oldnick: oldnick}) + }) } - return (serverCommands[command.method] || serverCommands.badcommand)(serverstate, settings, command.params, command.id) - }); - conn.on('close', function() { //propagate if client quits the page - clients.splice(clients.indexOf(conn)); - clients.notifyAll('quit', { from: settings.nick }) -// irc_client.say("#krebs", origin + ' has quit'); -}); -}); + }) + connection.on('close', function() { //propagate if client quits the page + clients.splice(clients.indexOf(client)); + clients.map(pluck('rpc')).forEach(function (rpc) { + rpc.send('part', {type: 'web', nick: client.nick}) + }) + }) + //send nicklist to newly joined client + clients.map(pluck('nick')).forEach(function (nick) { + client.rpc.send('join', {type: 'web', nick: nick}) + }) + //add new client to list + clients.push(client) + //send all including the new client the join + clients.map(pluck('rpc')).forEach(function (rpc) { + rpc.send('join', {type: 'web', nick: client.nick}) + }) +}) var app = connect() .use(connect.logger('dev')) @@ -139,7 +143,8 @@ var app = connect() page_template+='\n'; page_template+='\n'; page_template+='\n'; - page_template+='\n'; + page_template+='\n'; + page_template+='\n'; page_template+='\n'; page_template+='
'; page_template+='
'; diff --git a/webchat/proto_spec b/webchat/proto_spec new file mode 100644 index 00000000..fffce165 --- /dev/null +++ b/webchat/proto_spec @@ -0,0 +1,62 @@ +server -> client: +#old +type: 'message' | 'join' | 'quit' | 'nicklist' | 'nickchange' | 'usererror' +nick: the clients nickname ('message','nickchange') +newnick: new nick after nickchange ('nickchange') +from: the clients ip ('message','quit','join') +message: the data send ('message', 'nicklist','usererror' + + +#new +type: 'irc_msg' | 'irc_join' | 'irc_quit' | 'irc_nickchange' | 'irc_client_connect' | 'irc_client_disconnect' | 'web_welcome' |'web_msg' | 'web_join' | 'web_quit' | 'web_nickchange' | 'usererror' +params:{ nick:, oldnick:, nicklist:, msg:, errormsg: } + +'irc_msg': nick, msg +'irc_join': nick +'irc_quit': nick +'irc_nickchange': oldnick, nick +'kweb_irc_connect': nicklist +'kweb_irc_disconnect': -- +'web_welcome': msg, nicklist, nick +'web_msg': nick, msg +'web_join': nick +'web_quit': nick +'web_nickchange': oldnick, nick +'usererror': msg + + +client -> server +#old +method: 'say', 'nick' +params:{ msg:, nick: } + +'say': msg +'nick': nick + + +##############JSON RPC################ +server->client: +{method: 'say', params: {msg: msg}, id: id} +-> {result: {ok: ok}, error {error muted?}, id: id} + +{method: 'nick', params: {nick: nick}, id: id} +-> {result: {nick: nick}, error: {error name taken?/reserved/not allowed}, id: id} + + +client->server: +broadcast: + {method: 'irc_msg', params: {nick: nick, msg: msg}, id: 0} #notification + {method: 'irc_join', params: {nick: nick}, id: 0} #notification + {method: 'irc_quit', params: {nick: nick}, id: 0} #notification + {method: 'irc_nickchange', params: {nick: nick, oldnick: oldnick}, id: 0} #notification + {method: 'kweb_irc_connect', params: {nicklist: nicklist}, id: 0} #notification + {method: 'kweb_irc_disconnect', params: {}, id: 0} #notification + {method: 'web_msg', params: {nick: nick, msg: msg}, id: 0} #notification + {method: 'web_join', params: {nick: nick}, id: 0} #notification + {method: 'web_quit', params: {nick: nick}, id: 0} #notification + {method: 'web_nickchange', params: {nick: nick, oldnick: oldnick}, id: 0} #notification + +unicast: + {method: 'coi', params: {}, id: id} + -> {result: {result: {nick: nick, addr: addr}, error: {connection error?}, id: id} + {method: 'usererror', params: {msg: 'error type'}, id: 0} #notification diff --git a/webchat/public/client.js b/webchat/public/client.js index 8f7becb7..8865fcc0 100644 --- a/webchat/public/client.js +++ b/webchat/public/client.js @@ -23,44 +23,43 @@ function request (settings, method, params, callback) { $(function connect() { settings.sock = new SockJS('/echo'); - - settings.sock.onopen = function() { - console.log('open'); - request(settings, 'coi', {}, function (error, result) { - if (error) { - console.log('coi error', error) - } else { - settings.nick = result.nick //TODO: write to display - settings.addr = result.addr //TODO: write to display - } - }) - }; - settings.sock.onmessage = function(e) { - console.log('message', e.data); - try { - var object = JSON.parse(e.data); - console.log(object); - - } catch (error) { - console.log(error); - throw error; - } - if (typeof object.method === 'string') { - return methodDispatcher(settings, object); - } else if (typeof object.result === 'string') { - return resultDispatcher(settings, object); - } else { - console.log('bad message:', object) - } - } - settings.sock.onclose = function(event) { - console.log('close'); - switch (event.code) { - case 1006: //abnormal closure - return setTimeout(connect, 1000); - }; - }; - + var transport = make_sockjs_client_transport(settings.sock) + var rpc = new RPC(transport) + rpc.register('msg', {type: 'string', nick: 'string', msg: 'string'}, function(params, callback) { + var safe_message = $('
').text(params.msg).html(); + safe_message = replaceURLWithHTMLLinks(safe_message); + var safe_from = $('
').text(params.nick).html(); + chatboxAppend(safe_from, safe_message, 'web_msg') + return callback(null) + }) + rpc.register('nick', {type: 'string', newnick: 'string', oldnick: 'string'}, function(params, callback) { + var safe_oldnick = $('
').text(params.oldnick).html(); + var safe_newnick = $('
').text(params.newnick).html(); + var safe_type = $('
').text(params.type).html(); + $(getNicklistElement(safe_oldnick,safe_type)).remove(); + $('#nicklist').append('
' + safe_newnick + '
') ; + chatboxAppend(safe_oldnick, 'is now known as ' + safe_newnick, 'nick'); + return callback(null) + }) + rpc.register('your_nick', {nick: 'string'}, function(params, callback) { + var safe_nick = $('
').text(params.nick).html(); + settings.nick = safe_nick + return callback(null) + }) + rpc.register('join', {type: 'string', nick: 'string'}, function(params, callback) { + var safe_nick = $('
').text(params.nick).html(); + var safe_type = $('
').text(params.type).html(); + $('#nicklist').append('
' + safe_nick + '
') ; + chatboxAppend(safe_nick, 'has joined'); + return callback(null) + }) + rpc.register('part', {type: 'string', nick: 'string'}, function(params, callback) { + var safe_nick = $('
').text(params.nick).html(); + var safe_type = $('
').text(params.type).html(); + $(getNicklistElement(safe_nick,safe_type)).remove(); + chatboxAppend(safe_nick, 'has parted'); + return callback(null) + }) }); $(function() { $('#input').keydown(function(e) { diff --git a/webchat/public/commands.js b/webchat/public/commands.js index 5a570556..d4408c4c 100644 --- a/webchat/public/commands.js +++ b/webchat/public/commands.js @@ -1,14 +1,15 @@ var commands = {} -commands.say = function (settings, params) { +commands.msg = function (settings, params) { var sendObj = { - method: 'say', - params: { msg: params }, - }; + method: 'msg', + params: { msg: params } + } settings.sock.send(JSON.stringify(sendObj)) } commands.nick = function (settings, params) { + settings.nick = params var sendObj = { method: 'nick', params: { nick: params }, @@ -17,7 +18,7 @@ commands.nick = function (settings, params) { } commands.badcommand = function (settings, params) { - console.log("error"); + console.log("error", params); chatboxAppend( 'error', 'command not found' ) diff --git a/webchat/public/functions.js b/webchat/public/functions.js index 45c8ad3f..244af67b 100644 --- a/webchat/public/functions.js +++ b/webchat/public/functions.js @@ -3,25 +3,11 @@ function inputParser (str) { if (match) { return { method: match[1], params: match[2] } } else { - return { method: 'say', params: str } + return { method: 'msg', params: str } } } -function methodDispatcher (settings, object) { - console.log('parser: ',object) - return (handler[object.method] || console.log)(settings, object.params) -}; - -function resultDispatcher (settings, object) { - console.log('parser: ',object) - var callback = settings.waiting_callbacks[object.id] - delete settings.waiting_callbacks[object.id] - if (typeof callback === 'function') { - callback(object.error, object.result) - } -}; - function replaceURLWithHTMLLinks (text) { var exp = /(\b(https?|ftp|file):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/ig; @@ -37,9 +23,9 @@ function setMaybeNick (input) { function sortNicklist () { }; -function getNicklistElement(name) { +function getNicklistElement(name, type) { var el; - $('.name').each(function (i,e) { + $('.'+type+'_name').each(function (i,e) { if (e.innerHTML === name) { if (typeof el !== 'undefined') { throw new Error('duplicate name: ' + name); diff --git a/webchat/public/handler.js b/webchat/public/handler.js deleted file mode 100644 index 0930646d..00000000 --- a/webchat/public/handler.js +++ /dev/null @@ -1,35 +0,0 @@ -var handler = {} - -handler.message = function(settings, object) { - var safe_message = $('
').text(object.msg).html(); - safe_message = replaceURLWithHTMLLinks(safe_message); - var safe_from = $('
').text(object.nick).html(); - return chatboxAppend(safe_from, safe_message, 'msg') -}; - -handler.join = function(settings, object) { - var safe_from = $('
').text(object.from).html(); - $('#nicklist').append('
' + safe_from + '
') ; - return chatboxAppend(safe_from, 'joined', 'join') -}; - -handler.quit = function(settings, object) { - var safe_from = $('
').text(object.from).html(); - $(getNicklistElement(safe_from)).remove(); - return chatboxAppend(safe_from, 'quit', 'quit') -}; - -handler.nicklist = function(settings, object) { - Object.keys(object.nicklist).forEach(function (nick) { - var hash_from = btoa(nick).replace(/=/g,'_'); - $('#nicklist').append('
' + nick + '
') ; - }); -}; - -handler.nickchange = function(settings, object) { - var safe_from = $('
').text(object.nick).html(); - var safe_newnick = $('
').text(object.newnick).html(); - $(getNicklistElement(safe_from)).remove(); - $('#nicklist').append('
' + safe_newnick + '
') ; - return chatboxAppend(safe_from, 'is now known as ' + safe_newnick, 'nick'); -}; diff --git a/webchat/public/rpc.js b/webchat/public/rpc.js new file mode 100644 index 00000000..8e911e1d --- /dev/null +++ b/webchat/public/rpc.js @@ -0,0 +1,99 @@ +try { + module.exports = RPC +} +catch(e){ +} + +function RPC (transport) { + this._id = 0 + this._waiting_callbacks = {} + this._methods = {} + this._transport = transport + + transport.onmessage = this.onmessage.bind(this) +} + +RPC.prototype.register = function (method, params, callback) { + this._methods[method] = callback +} + +RPC.prototype.send = function (method, params, callback) { + var message = { + method: method, + params: params, + } + if (callback) { + var id = ++this._id + this._waiting_callbacks[id] = callback + message.id = id + } + return this._transport.send(message) +} + +function _is_request (message) { + return typeof message.method === 'string' +} + +function _is_response (message) { + return message.hasOwnProperty('result') + || message.hasOwnProperty('error') +} + +RPC.prototype.onmessage = function (message) { + console.log('RPC message:', message) + if (_is_request(message)) { + return this._on_request(message) + } + if (_is_response(message)) { + return this._on_response(message) + } + return this._on_bad_message(message) +} + +RPC.prototype._on_request = function (request) { + var method = this._methods[request.method] || function(){ + console.log('method not found', request.method) + } + var params = request.params + var id = request.id + + var transport = this._transport + + if (typeof id === 'string' || typeof id === 'number' || id === null) { + return method(params, function (error, result) { + var response = { + id: id, + } + if (error) { + response.error = error + } else { + response.result = result + } + console.log('request:', request, '->', response) + return transport.send(response) + }) + } else { + return method(params, function (error, result) { + var response = { + id: id, + } + if (error) { + response.error = error + } else { + response.result = result + } + console.log('notification:', request, '->', response) + }) + } +} + +RPC.prototype._on_response = function (response) { + var result = response.result + var error = response.error + var id = response.id + + var callback = this._waiting_callbacks[id] + delete this._waiting_callbacks[id] + + return callback(result, error) +} diff --git a/webchat/public/sockjs_client_transport.js b/webchat/public/sockjs_client_transport.js new file mode 100644 index 00000000..4e525d0d --- /dev/null +++ b/webchat/public/sockjs_client_transport.js @@ -0,0 +1,27 @@ + +function make_sockjs_client_transport (sock) { + var transport = {} + + sock.onmessage = function (data) { + console.log('sockjs parse', data) + try { + var message = JSON.parse(data.data) + } catch (error) { + return console.log('error', error) + } + transport.onmessage(message) + } + + transport.send = function (message) { + try { + var data = JSON.stringify(message) + } catch (error) { + return console.log('sockjs transport send error:', error) + } + sock.send(data) + } + + return transport +} + + diff --git a/webchat/serverCommands.js b/webchat/serverCommands.js deleted file mode 100644 index 303c4805..00000000 --- a/webchat/serverCommands.js +++ /dev/null @@ -1,26 +0,0 @@ -var serverCommands = {}; - -serverCommands.coi = function (serverstate, settings, params, id) { - return settings.conn.write({ result: { nick: settings.nick, addr: settings.addr}, id: id}) -} - -serverCommands.say = function (serverstate, settings, params, id) { - var nick = settings.nick - var message = params.msg - params.nick = nick - serverstate.irc_client.say("#krebs", nick + ' → ' + message); - return serverstate.clients.notifyAll('message', params) -} - -serverCommands.nick = function (serverstate, settings, params, id) { - var oldnick = settings.nick - var newnick = params.nick - settings.nick = newnick - return serverstate.clients.notifyAll('nickchange', { nick: oldnick, newnick: newnick }); -} - -serverCommands.badcommand = function (serversate, settings, params, id) { - settings.conn.write(JSON.stringify({ method: 'usererror', params: { message: 'bad command' }})) -} - -module.exports = serverCommands diff --git a/webchat/sockjs_server_connection_transport.js b/webchat/sockjs_server_connection_transport.js new file mode 100644 index 00000000..6f68b955 --- /dev/null +++ b/webchat/sockjs_server_connection_transport.js @@ -0,0 +1,26 @@ + +module.exports = function make_sockjs_server_connection_transport (connection) { + var transport = {} + + connection.on('data', function (data) { + try { + var message = JSON.parse(data) + } catch (error) { + return console.log('error', error) + } + transport.onmessage(message) + }) + connection.on('close', function () { + }) + + transport.send = function (message) { + try { + var data = JSON.stringify(message) + } catch (error) { + return console.log('sockjs transport send error:', error) + } + connection.write(data) + } + + return transport +} -- cgit v1.2.3