summaryrefslogtreecommitdiffstats
path: root/webchat
diff options
context:
space:
mode:
authormakefu <github@syntax-fehler.de>2013-11-12 14:43:50 +0100
committermakefu <github@syntax-fehler.de>2013-11-12 14:43:50 +0100
commit8b60dff25e85921fe533f621a1b5d0f8149bf38f (patch)
treeb39eb0e15e630f5b5af624ec91b8f222a25170ae /webchat
parent8ebba4db20b8127e57490eeea95d50d158dc69dd (diff)
parentb749c216ae84f02330da7396135be11259ec12d9 (diff)
Merge branch 'master' of github.com:krebscode/painload
Diffstat (limited to 'webchat')
-rw-r--r--webchat/index.js152
-rw-r--r--webchat/proto_spec62
-rw-r--r--webchat/public/client.js123
-rw-r--r--webchat/public/functions.js54
-rw-r--r--webchat/public/krebs.pngbin956 -> 2583 bytes
-rw-r--r--webchat/public/reset.css23
-rw-r--r--webchat/public/rpc.js99
-rw-r--r--webchat/public/sockjs_client_transport.js25
-rw-r--r--webchat/sockjs_server_connection_transport.js26
9 files changed, 432 insertions, 132 deletions
diff --git a/webchat/index.js b/webchat/index.js
index 8ef737fc..9569fc80 100644
--- a/webchat/index.js
+++ b/webchat/index.js
@@ -1,20 +1,30 @@
+'use strict';
var fs = require('fs');
var http = require('https');
var sockjs = require('sockjs');
var connect = require('connect');
var irc = require('irc');
-var Clients = [];
+var make_sockjs_server_connection_transport = require('./sockjs_server_connection_transport.js')
+var RPC = require('./public/rpc.js');
+var irc_nicks = []
-Clients.broadcast = function(object) { //broadcast to all clients
- Clients.forEach(function(client) {
- client.write(JSON.stringify(object));
- });
+function pluck (key) {
+ return function (object) {
+ return object[key]
+ }
+}
+
+var clients = [];
+clients.broadcast = function (method, params) {
+ clients.map(pluck('rpc')).forEach(function (rpc) {
+ rpc.send(method, params)
+ })
}
var irc_reconnect = function() { //reconnt to irc
- console.log("reconnecting due to pingtimeout");
- irc_client.disconnect();
- irc_client.connect();
+ console.log("would reconnect now")
+// irc_client.disconnect()
+// irc_client.connect()
}
var pingTimeoutDelay = 3*60*1000
@@ -27,7 +37,7 @@ var irc_client = new irc.Client('irc.freenode.net', 'kweb', { //create irc_clien
userName: 'kweb', //todo: read from local_config
realName: 'kweb', //todo: read from local_config
password: fs.readFileSync(__dirname+'/local_config/irc.key').toString(),
- debug: false,
+ debug: true,
showErrors: true,
floodProtection: true,
port: 6697,
@@ -36,7 +46,6 @@ var irc_client = new irc.Client('irc.freenode.net', 'kweb', { //create irc_clien
stripColors: true
});
-
irc_client.on('ping', function(server) { //restart timer on server ping
console.log("got ping from server, renewing timeout for automatic reconnect");
clearTimeout(lastping);
@@ -45,60 +54,97 @@ 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.broadcast({ from: from, message: message }); //broadcast irc messages to all connected clients
+ clients.broadcast('msg', {nick: from, msg: message})
clearTimeout(lastping);
});
-var web_serv_options = { //certificates for https
- key: fs.readFileSync(__dirname+'/local_config/server_npw.key'),
- cert: fs.readFileSync(__dirname+'/local_config/server.crt'),
-};
+irc_client.on('names#krebs', function(nicks) {
+ Object.keys(nicks).forEach(function (nick) {
+ irc_nicks.push(nick)
+ clients.broadcast('join', {type: 'irc', nick: nick})
+ })
+})
-var echo = sockjs.createServer();
-echo.on('connection', function(conn) {
- var origin = conn.remoteAddress;
- Clients.push(conn);
- Clients.broadcast({from: 'system', message: origin + ' has joined'})
-// irc_client.say("#krebs", origin + ' has joined');
- conn.write(JSON.stringify({from: 'system', message: 'hello'})) //welcome message
- conn.on('data', function(data) {
- console.log('data:',data);
- try {
- var object = JSON.parse(data);
- if (object.message.length > 0) { //if message is not empty
- if (/^\/nick\s+(.+)$/.test(object.message)) { //if nick is send use nick instead of ip
- object.from = origin;
- } else if (typeof object.nick === 'string') {
- object.from = object.nick;
- } else {
- object.from = origin;
- };
- console.log(object.message);
- irc_client.say("#krebs", object.from + ' → ' + object.message);
- Clients.broadcast(object);
- }
-
- } catch (error) {
- console.log(error);
- }
- });
- conn.on('close', function() { //propagate if client quits the page
- Clients.splice(Clients.indexOf(conn));
- Clients.broadcast({from: 'system', message: origin + ' has quit'})
-// irc_client.say("#krebs", origin + ' has quit');
-});
+irc_client.on('join#krebs', function(nick, msg) {
+ if (nick !== 'kweb'){
+ irc_nicks.push(nick)
+ clients.broadcast('join', {type: 'irc', nick: nick})
+ }
+})
+
+irc_client.on('part#krebs', function(nick, rs, msg) {
+ clients.broadcast('part', {type: 'irc', nick: nick})
});
+irc_client.on('error', function (error) {
+ irc_nicks.forEach( function(nick) {
+ client.rpc.send('part', {type: 'irc', nick: nick})
+ irc_nicks.splice(irc_nicks.indexOf(nick))
+ })
+ console.log('irc-client error', error)
+})
+
+var echo = sockjs.createServer();
+
+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.broadcast('msg', {type: 'web', nick: client.nick, msg: params.msg})
+ irc_client.say('#krebs', client.nick + ' → ' + 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.broadcast('nick', {type: 'web', newnick: client.nick, oldnick: oldnick})
+ irc_client.say('#krebs', oldnick + ' is now known as ' + client.nick)
+ }
+ })
+ connection.on('close', function() { //propagate if client quits the page
+ clients.splice(clients.indexOf(client));
+ clients.broadcast('part', {type: 'web', nick: client.nick})
+ irc_client.say('#krebs', client.nick + ' has parted')
+ })
+ //send the irc nicklist to the new joined client
+ irc_nicks.forEach( function(nick) {
+ client.rpc.send('join', {type: 'irc', nick: 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.broadcast('join', {type: 'web', nick: client.nick})
+ //send join to irc
+ irc_client.say('#krebs', client.nick + ' has joined')
+})
var app = connect()
.use(connect.logger('dev'))
.use(connect.static(__dirname+'/public'))
.use( function (req, res) {
res.writeHead(200, {'Content-Type': 'text/html'});
- page_template='<!doctype html>\n';
+ var page_template='<!doctype html>\n';
page_template+='<link rel="stylesheet" type="text/css" href="reset.css">\n';
page_template+='<script src="sockjs-0.3.min.js"></script>\n';
page_template+='<script src="jquery-2.0.3.min.js"></script>\n';
+ page_template+='<script src="commands.js"></script>\n';
+ page_template+='<script src="functions.js"></script>\n';
+ page_template+='<script src="sockjs_client_transport.js"></script>\n';
+ page_template+='<script src="rpc.js"></script>\n';
page_template+='<script src="client.js"></script>\n';
page_template+='<div id="bg">';
page_template+='<div id="chatter">';
@@ -106,7 +152,8 @@ var app = connect()
page_template+='hello, this is the official krebs support:<br>\n';
page_template+='<table id="chatbox"><tr id="foot"><td id="time"></td><td id="nick" class="chat_from"></td><td><input type="text" id="input"></td></tr></table>\n';
page_template+='</div>';
- page_template+='<div id="sideboard"><div id="links">';
+ page_template+='<div id="sideboard"><div id=nicklist></div>';
+ page_template+='<div id="links">';
page_template+='<a href="http://gold.krebsco.de/">krebsgold browser plugin</a><br>';
page_template+='<a href="http://ire:1027/dashboard/">ire: Retiolum Dashboard</a><br>';
page_template+='<a href="http://pigstarter/">pigstarter: network graphs</a><br>';
@@ -114,6 +161,11 @@ var app = connect()
res.end(page_template);
})
+
+var web_serv_options = { //certificates for https
+ key: fs.readFileSync(__dirname+'/local_config/server_npw.key'),
+ cert: fs.readFileSync(__dirname+'/local_config/server.crt'),
+};
var server = http.createServer(web_serv_options, app);
echo.installHandlers(server, {prefix:'/echo'});
server.listen(1337, '0.0.0.0');
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 ca71b537..54ccfe34 100644
--- a/webchat/public/client.js
+++ b/webchat/public/client.js
@@ -1,34 +1,50 @@
-function replaceURLWithHTMLLinks (text) {
- var exp = /(\b(https?|ftp|file):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/ig;
- return text.replace(exp,"<a href='$1'>$1</a>");
-}
-function setMaybeNick (input) {
- var match = /^\/nick\s+(.+)$/.exec(input);
- if (match) {
- nick = match[1];
- $('#nick').html(nick);
- }
-}
+var settings = {}
+settings.sock = new SockJS('/echo');
+settings.waiting_callbacks = {}
-function getCurTime () {
- date = new Date;
- h = date.getHours();
- if(h<10)
- {
- h = "0"+h;
- }
- m = date.getMinutes();
- if(m<10)
- {
- m = "0"+m;
- }
- s = date.getSeconds();
- if(s<10)
- {
- s = "0"+s;
+var transport = make_sockjs_client_transport(settings.sock)
+settings.rpc = new RPC(transport)
+
+settings.rpc.register('msg', {type: 'string', nick: 'string', msg: 'string'}, function(params, callback) {
+ var safe_message = $('<div/>').text(params.msg).html();
+ safe_message = replaceURLWithHTMLLinks(safe_message);
+ var safe_from = $('<div/>').text(params.nick).html();
+ chatboxAppend(safe_from, safe_message, 'web_msg')
+ return callback(null)
+})
+settings.rpc.register('nick', {type: 'string', newnick: 'string', oldnick: 'string'}, function(params, callback) {
+ var safe_oldnick = $('<div/>').text(params.oldnick).html();
+ var safe_newnick = $('<div/>').text(params.newnick).html();
+ var safe_type = $('<div/>').text(params.type).html();
+ if (safe_oldnick === settings.nick){
+ settings.nick = safe_newnick
+ $('#nick').html(settings.nick)
}
- return ''+h+':'+m+':'+s;
-};
+ $(getNicklistElement(safe_oldnick,safe_type)).remove();
+ $('#nicklist').append('<div class="'+safe_type+'_name">' + safe_newnick + '</div>') ;
+ chatboxAppend(safe_oldnick, 'is now known as ' + safe_newnick, 'nick');
+ return callback(null)
+})
+settings.rpc.register('your_nick', {nick: 'string'}, function(params, callback) {
+ var safe_nick = $('<div/>').text(params.nick).html();
+ settings.nick = safe_nick
+ $('#nick').html(settings.nick)
+ return callback(null)
+})
+settings.rpc.register('join', {type: 'string', nick: 'string'}, function(params, callback) {
+ var safe_nick = $('<div/>').text(params.nick).html();
+ var safe_type = $('<div/>').text(params.type).html();
+ $('#nicklist').append('<div class="'+safe_type+'_name">' + safe_nick + '</div>') ;
+ chatboxAppend(safe_nick, 'has joined');
+ return callback(null)
+})
+settings.rpc.register('part', {type: 'string', nick: 'string'}, function(params, callback) {
+ var safe_nick = $('<div/>').text(params.nick).html();
+ var safe_type = $('<div/>').text(params.type).html();
+ $(getNicklistElement(safe_nick,safe_type)).remove();
+ chatboxAppend(safe_nick, 'has parted');
+ return callback(null)
+})
$(function updateTime () {
$('#time').html(getCurTime());
@@ -36,59 +52,18 @@ $(function updateTime () {
return true;
});
-var nick;
-
-$(function connect() {
- sock = new SockJS('/echo');
-
- sock.onopen = function() {
- console.log('open');
- sock.send('open');
- };
- sock.onmessage = function(e) {
- console.log('message', e.data);
- try {
- var object = JSON.parse(e.data);
- console.log(object.message);
- var safe_message = $('<div/>').text(object.message).html();
- safe_message = replaceURLWithHTMLLinks(safe_message);
- var safe_from = $('<div/>').text(object.from).html();
- $('<tr><td class="chat_date">'+getCurTime()+'</td><td class="chat_from">'+safe_from+'</td><td class="chat_msg">'+safe_message+'</td></tr>').insertBefore('#foot');
- var elem = document.getElementById('chatter');
- elem.scrollTop = elem.scrollHeight;
-
- } catch (error) {
- console.log(error);
- }
- };
- sock.onclose = function(event) {
- console.log('close');
- switch (event.code) {
- case 1006: //abnormal closure
- return setTimeout(connect, 1000);
- };
- };
-
-});
$(function() {
$('#input').keydown(function(e) {
if (e.keyCode === 13) {
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
- setMaybeNick($('#input').val());
- var sendObj = {
- message: $('#input').val(),
- };
-
- if (typeof nick === 'string') {
- sendObj.nick = nick;
- };
+ var input = ($('#input').val());
+ $('#input').val('')
- sock.send(JSON.stringify(sendObj));
- $('#input').val('');
- return;
+ var command = inputParser(input)
+ return (commands[command.method] || commands.badcommand)(settings, command.params)
}
});
diff --git a/webchat/public/functions.js b/webchat/public/functions.js
new file mode 100644
index 00000000..781fafce
--- /dev/null
+++ b/webchat/public/functions.js
@@ -0,0 +1,54 @@
+function inputParser (str) {
+ var match = /^\/([a-z]+)(?:\s+(.*\S))?\s*$/.exec(str)
+ if (match) {
+ return { method: match[1], params: match[2] }
+ } else {
+ return { method: 'msg', params: str }
+ }
+}
+
+function replaceURLWithHTMLLinks (text) {
+ var exp = /(\b(https?|ftp|file):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/ig;
+ return text.replace(exp,"<a class=chat_link href='$1'>$1</a>");
+}
+
+function getNicklistElement(name, type) {
+ var el;
+ $('.'+type+'_name').each(function (i,e) {
+ if (e.innerHTML === name) {
+ if (typeof el !== 'undefined') {
+ throw new Error('duplicate name: ' + name);
+ };
+ el = e;
+ };
+ });
+ return el;
+}
+
+function chatboxAppend (chat_from, chat_msg, type) {
+ type = type||'msg'
+ $('<tr><td class="date '+type+'_date">'+getCurTime()+'</td><td class="from '+type+'_from">'+chat_from+'</td><td class="msg '+type+'_msg">'+chat_msg+'</td></tr>').insertBefore('#foot');
+
+ var elem = document.getElementById('chatter');
+ elem.scrollTop = elem.scrollHeight;
+};
+
+function getCurTime () {
+ date = new Date;
+ h = date.getHours();
+ if(h<10)
+ {
+ h = "0"+h;
+ }
+ m = date.getMinutes();
+ if(m<10)
+ {
+ m = "0"+m;
+ }
+ s = date.getSeconds();
+ if(s<10)
+ {
+ s = "0"+s;
+ }
+ return ''+h+':'+m+':'+s;
+};
diff --git a/webchat/public/krebs.png b/webchat/public/krebs.png
index 263d5b1c..5762e7f4 100644
--- a/webchat/public/krebs.png
+++ b/webchat/public/krebs.png
Binary files differ
diff --git a/webchat/public/reset.css b/webchat/public/reset.css
index 65f68058..d369bc86 100644
--- a/webchat/public/reset.css
+++ b/webchat/public/reset.css
@@ -21,7 +21,6 @@ time, mark, audio, video {
border: 0;
font-size: 100%;
font: inherit;
- font-family: monospace;
vertical-align: baseline;
}
/* HTML5 display-role reset for older browsers */
@@ -33,6 +32,7 @@ body {
line-height: 1;
background-color: black;
color: white;
+ font-family: monospace;
}
ol, ul {
list-style: none;
@@ -54,17 +54,18 @@ q:before, q:after {
}
#input{
width: 100%;
- background-color: #555555;
+ background-color: #221111;
border: 1px solid black;
color: white;
}
-.chat_from {
+.from {
color:grey;
font-weight: bold;
text-align: right;
font-size:12px;
+ white-space: nowrap;
}
-.chat_from:after {
+.from:after {
content: ":";
padding-right: 6px;
}
@@ -87,16 +88,16 @@ q:before, q:after {
}
.chat_date,.chat_from,.chat_msg{
}
-.chat_msg{
+.msg{
width: 100%;
}
a {
color: red;
}
-.chat_date {
+.date {
color: green;
}
-.chat_date:after {
+.date:after {
content: "";
padding-right: 4px;
}
@@ -116,4 +117,10 @@ a {
font-size: 14px;
position: absolute;
bottom: 5px;
-} \ No newline at end of file
+}
+.join_msg {
+ color: #00FF00;
+}
+.quit_msg {
+ color: #FF0000;
+}
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..a7b76af3
--- /dev/null
+++ b/webchat/public/sockjs_client_transport.js
@@ -0,0 +1,25 @@
+
+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/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
+}