From 138eff9c9661835f52758974a62f994fede7f065 Mon Sep 17 00:00:00 2001 From: Hexagon Date: Thu, 18 Sep 2014 18:21:07 +0200 Subject: [PATCH] Initial commit --- public/css/default.css | 80 +++++++++ public/index.html | 27 +++ public/js/bootstrap.js | 30 ++++ public/js/cryptalk_modules/$.js | 129 ++++++++++++++ public/js/cryptalk_modules/cryptalk.js | 187 +++++++++++++++++++++ public/js/cryptalk_modules/templates.js | 50 ++++++ public/js/vendor/aes.v3.1.2.min.js | 10 ++ public/js/vendor/domReady.v2.0.1.min.js | 26 +++ public/js/vendor/fandango.v20140918.min.js | 23 +++ public/js/vendor/socket.io.v0.9.16.min.js | 2 + server.js | 39 +++++ 11 files changed, 603 insertions(+) create mode 100644 public/css/default.css create mode 100644 public/index.html create mode 100644 public/js/bootstrap.js create mode 100644 public/js/cryptalk_modules/$.js create mode 100644 public/js/cryptalk_modules/cryptalk.js create mode 100644 public/js/cryptalk_modules/templates.js create mode 100644 public/js/vendor/aes.v3.1.2.min.js create mode 100644 public/js/vendor/domReady.v2.0.1.min.js create mode 100644 public/js/vendor/fandango.v20140918.min.js create mode 100644 public/js/vendor/socket.io.v0.9.16.min.js create mode 100644 server.js diff --git a/public/css/default.css b/public/css/default.css new file mode 100644 index 0000000..0b178a7 --- /dev/null +++ b/public/css/default.css @@ -0,0 +1,80 @@ +/*------------------------------------*\ + GENERIC +\*------------------------------------*/ +html { + -webkit-box-sizing: border-box; /* Safari/Chrome, other WebKit */ + -moz-box-sizing: border-box; /* Firefox, other Gecko */ + box-sizing: border-box; /* Opera/IE 8+ */ +} + +*, *:before, *:after { + box-sizing: inherit; + + font-family: monospace, 'Courier New'; + font-size: 12px; +} + +body, html { + min-height: 100%; + background-color: #000000; + overflow: hidden; + padding: 0px; + margin:0px; + color: #00DD00; +} + +/*------------------------------------*\ + CHAT +\*------------------------------------*/ +#chat { + left: 0; + right: 0; + bottom: 40px; + position: absolute; + list-style-type: none; + padding:0; + margin:0; +} + +/* Messages */ +#chat li { + white-space: pre; + padding: 2px 10px; +} + +/* Message types */ +/* the `i` element hold the actual message */ +#chat i { + font-style: normal; +} +#chat i.info { color: #7777ff; } +#chat i.server { color: #999999; } +#chat i.error { color: #ff7777; } +#chat i.message { color: #eeeeee; } + +/*------------------------------------*\ + INPUT +\*------------------------------------*/ +#input_wrapper { + right:0; + bottom:0; + left:0; + position: absolute; + + height:40px; +} + +#input { + top: 0; + bottom: 0; + width: 100%; + position: absolute; + + border: 0; + outline: 0; + + padding: 5px 5px 5px 15px; + + color: #00dd00; + background-color:#141414; +} \ No newline at end of file diff --git a/public/index.html b/public/index.html new file mode 100644 index 0000000..b286b2c --- /dev/null +++ b/public/index.html @@ -0,0 +1,27 @@ + + + + + Cryptalk + + + + + + + + + + +
+ +
+ + + + + + + \ No newline at end of file diff --git a/public/js/bootstrap.js b/public/js/bootstrap.js new file mode 100644 index 0000000..ec63521 --- /dev/null +++ b/public/js/bootstrap.js @@ -0,0 +1,30 @@ +// This Javascript file is the only file besides fandango.js that will be fetched through DOM. + +// Setup fandango +fandango.defaults({ + baseUrl: 'js/cryptalk_modules/', + paths: { + websocket: 'https://cdnjs.cloudflare.com/ajax/libs/socket.io/0.9.16/socket.io.min.js', + aes: 'https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.2/rollups/aes.js', + domReady: 'https://cdnjs.cloudflare.com/ajax/libs/require-domReady/2.0.1/domReady.min.js' + }, + + // CryptoJs AES does not support AMD modules. We'll have to create a shim. + shim: { + aes: { + exports: function () { // String (the global variable name) or a function; returning the desired variable. + return CryptoJS.AES; + } + } + } +}); + +// Fetch our modules asynchronously, when the DOM is finished loading. +//define('bootstrap_module', ['domReady'], function (domReady) { +// domReady(function () { +// require(['cryptalk']); +// }); +//}); + +// No need to wait for DOM - the Javascript is at the bottom +require(['cryptalk']); \ No newline at end of file diff --git a/public/js/cryptalk_modules/$.js b/public/js/cryptalk_modules/$.js new file mode 100644 index 0000000..8683209 --- /dev/null +++ b/public/js/cryptalk_modules/$.js @@ -0,0 +1,129 @@ +define('$', ['fandango', 'websocket', 'aes'], function (fandango, websocket, aes) { + var exports = { + selector: 0, + utilities: {}, + prototype: {} + }, + + // Shortcuts + utils = exports.utilities, + proto = exports.prototype, + + each = fandango.each; + + // The DOM selector engine + exports.selector = function (selector) { + var match, + matches = []; + + if (selector === document) { + matches.push(document); + } else { + selector = selector.slice(1); + + if (match = document.getElementById(selector)) { + matches.push(match); + } + } + + // Must ALWAYS return a native array: [] + return matches; + }; + + // Namespace AES + utils.AES = { + decrypt: aes.decrypt, + encrypt: aes.encrypt + }; + + // Namespace encode + utils.AES = { + decrypt: function (string, fgh) { + return aes.decrypt(string, fgh).toString(CryptoJS.enc.Utf8); + }, + encrypt: function (string, fgh) { + return aes.encrypt(string, fgh).toString(); + }, + }; + + // Namespace websocket + utils.Websocket = { + connect: websocket.connect, + on: websocket.on + }; + + utils.ssplit = function (string, seperator) { + var components = string.split(seperator); + return [components.shift(), components.join(seperator)]; + }; + + utils.activeElement = function () { + try { return document.activeElement; } catch (e) {} + } + + /** + * A very simple implementation of sprintf() + * @param {} str [description] + * @param {[type]} map [description] + * @return {[type]} [description] + */ + utils.template = function (str, map) { + return str && str.replace(/{(\w+)}/gi, function(outer, inner) { + return map.hasOwnProperty(inner) ? map[inner] : outer /* '' */; + }); + }; + + // Originating from mustasche.js + // https://github.com/janl/mustache.js/blob/master/mustache.js#L43 + utils.escapeHtml = (function () { + var pattern = /[&<>"'\/]/g, + entities = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''', + '/': '/' + }; + + return function (string) { + return String(string).replace(pattern, function (s) { + return entities[s]; + }); + }; + }()); + + // Extremely naive implementations of .html() and .append() + proto.html = function (string) { + each(this, function (element) { + element.innerHTML = string; + }); + + return this; + }; + + proto.append = function (string) { + each(this, function (element) { + element.innerHTML += string; + }); + + return this; + }; + + // Extremely naive implementations of .on() + proto.on = function (eventName, callback) { + each(this, function (element) { + element.addEventListener(eventName, callback); + }); + + return this; + }; + + proto.focus = function () { + this[0].focus(); + + return this; + }; + + return exports; +}) \ No newline at end of file diff --git a/public/js/cryptalk_modules/cryptalk.js b/public/js/cryptalk_modules/cryptalk.js new file mode 100644 index 0000000..75cc3f4 --- /dev/null +++ b/public/js/cryptalk_modules/cryptalk.js @@ -0,0 +1,187 @@ +// Main cryptalk module. Will be called by bootstrap.js when the DOM is ready to interact with. +define('cryptalk', { + data: { + // If no host is given it will default to localhost. + host: 'http://www.huset.nu:8080' + }, + compiles: ['$'], + requires: ['templates'] +}, function ($, requires, data) { + var socket, + key, + room, + hash, + + // Collection of DOM components + components = { + chat: $('#chat'), + input: $('#input') + }, + + // Shortcut + templates = requires.templates, + + // Adds a new message to the DOM + post = function (type, text, clearChat, clearBuffer) { + var tpl = templates.post[type], + post = $.template(tpl, text && { + text: text + }); + + // Always clear the input after a post + if (clearBuffer) { + components.input[0].value = ''; + } + + // Append the post to the chat DOM element + components.chat[clearChat ? 'html' : 'append'](post); + }, + + // Chat related commands + commands = { + help: function () { + post('info', templates.help); + }, + + clear: function () { + components.chat.html(''); + components.input[0].value = ''; + }, + + leave: function () { + socket.emit('room:leave', room); + }, + + key: function (payload) { + // Make sure the key meets the length requirements + if (payload.length < 8) { + return post('info', templates.messages.key_weak); + } + + // Set key + key = payload; + + // Inform that the key has been set + post('info', (room ? templates.messages.key_ok_ready : templates.messages.key_ok_but_no_room)); + }, + + join: function (payload) { + return ( + room + ? post('info', $.template(templates.messages.already_in_room, { roomName: room})) + : socket.emit('room:join', payload) + ); + }, + + create: function (payload) { + socket.emit('room:create'); + } + }, + + // Handler for the document`s keyDown-event. + onKeyDown = function (e) { + var buffer, + parts, + payload, + command; + + // The Document object is bound to this element. + // If the active element is not the input, focus on it and exit the function. + if (components.input[0] !== $.activeElement()) { + return components.input.focus(); + } + + // Return immediatly if the buffer is empty or if the hit key was not + if (e.keyCode !== 13 || !(buffer = components.input[0].value)) { + return; + } + + // Handle command + if (buffer[0] === '/') { + parts = $.ssplit(buffer.slice(1), ' '); + command = parts[0]; + payload = parts[1]; + + // Check that there is an handler for this command + if (!commands[command]) { + return post('error', $.template(templates.messages.unrecognized_command, { commandName: command })); + } + + // Execute command handler + commands[command](payload); + } else /* Handle ordinary message */ { + // Make sure that the users has joined a room + if (!room) { + return post('error', templates.messages.msg_no_room); + } + + // And that a valid key is set + if (!key) { + return post('error', templates.messages.msg_no_key); + } + + // Before sending the message. + // Encrypt message using room UUID as salt and key as pepper. + socket.emit('message:send', { + room: room, + msg: $.AES.encrypt(buffer, room + key) + }); + + // Adn the the buffer + components.input[0].value = ''; + } + }; + + // Connect to server + socket = $.Websocket.connect(data.host); + + // Bind socket events + socket + .on('connect', function () { + $(document).on('keydown', onKeyDown); + components.input.focus(); + }) + + .on('room:created', function (data) { + socket.emit('room:join', data); + }) + + .on('room:joined', function (data) { + room = data; + post('info', $.template(templates.messages.joined_room, { roomName: room })); + }) + + .on('room:left', function () { + post('info', $.template(templates.messages.left_room, { roomName: room })); + room = false; + }) + + .on('message:send', function (data) { + var decrypted = $.AES.decrypt(data, room + key), + sanitized = $.escapeHtml(decrypted); + + if (!decrypted) { + post('info', templates.messages.unable_to_decrypt); + } else { + // Post the message, but do not clear either the chat nor the buffer. + post('message', sanitized, false, false); + } + }) + + .on('message:server', function (data) { + post('server', data); + }); + + // Post the help/welcome message + post('info', templates.help, true); + + // It's possible to provide room and key using the hashtag. + // The room and key is then seperated by semicolon (room:key). + // If there is no semicolon present, the complete hash will be treated as the room name and the key has to be set manually. + if (hash = window.location.hash) { + parts = hash.slice(1).split(':'); + + parts[0] && commands.join(parts[0]); + parts[1] && commands.key(parts[1]); + } +}); \ No newline at end of file diff --git a/public/js/cryptalk_modules/templates.js b/public/js/cryptalk_modules/templates.js new file mode 100644 index 0000000..1f87fea --- /dev/null +++ b/public/js/cryptalk_modules/templates.js @@ -0,0 +1,50 @@ +// The templating function only supports variables. +// Define a variable as so: {variable_name} +define({ + help: '' + + '
  • \n' + + 'Cryptalk, encrypted instant chat. \n' + + ' \n' + + '---------------------------------------------------------------------------------- \n' + + ' \n' + + 'Available commands: \n' + + ' /create Creates a room \n' + + ' /join RoomId Joins a room \n' + + ' /leave RoomId Leaves a room \n' + + ' /key OurStrongPassphrase Sets the password used for \n' + + ' encryption/decryption \n' + + ' /clear Clears on-screen buffer \n' + + ' /help This \n' + + ' \n' + + ' Besides that, it\'s just to talk! \n' + + ' \n' + + 'Code available for review at https://www.github.com/hexagon/cryptalk \n' + + ' \n' + + '--------------------------------------------------------------------------------- \n' + + '
  • ', + + post: { + info: '
  • INF> {text}
  • ', + server: '
  • SRV> {text}
  • ', + error: '
  • ERR> {text}
  • ', + message: '
  • MSG> {text}
  • ' + }, + + messages: { + key_weak: 'Hmm, that\'s a weak key, try again...', + key_ok_ready: 'Key set, you can now start communicating.', + key_ok_but_no_room: 'Key set, you can now join a room and start communicating.', + msg_no_room: 'You have to join a room before sending messages. See /help.', + msg_no_key: 'You have to set an encryption key before sending a message. See /help.', + + // Available variables: 'commandName' + unrecognized_command: 'Unrecognized command: "{commandName}"', + + // Available variables: 'roomName' + joined_room: 'Joined room {roomName}', + left_room: 'Left room {roomName}', + already_in_room: 'You are already in room {roomName}, stoopid.', + + unable_to_decrypt: 'Unabled to decrypt received message, keys does not match.' + } +}); \ No newline at end of file diff --git a/public/js/vendor/aes.v3.1.2.min.js b/public/js/vendor/aes.v3.1.2.min.js new file mode 100644 index 0000000..6834e42 --- /dev/null +++ b/public/js/vendor/aes.v3.1.2.min.js @@ -0,0 +1,10 @@ +/* +CryptoJS v3.1.2 +code.google.com/p/crypto-js +(c) 2009-2013 by Jeff Mott. All rights reserved. +code.google.com/p/crypto-js/wiki/License +*/ +(function(){for(var q=CryptoJS,x=q.lib.BlockCipher,r=q.algo,j=[],y=[],z=[],A=[],B=[],C=[],s=[],u=[],v=[],w=[],g=[],k=0;256>k;k++)g[k]=128>k?k<<1:k<<1^283;for(var n=0,l=0,k=0;256>k;k++){var f=l^l<<1^l<<2^l<<3^l<<4,f=f>>>8^f&255^99;j[n]=f;y[f]=n;var t=g[n],D=g[t],E=g[D],b=257*g[f]^16843008*f;z[n]=b<<24|b>>>8;A[n]=b<<16|b>>>16;B[n]=b<<8|b>>>24;C[n]=b;b=16843009*E^65537*D^257*t^16843008*n;s[f]=b<<24|b>>>8;u[f]=b<<16|b>>>16;v[f]=b<<8|b>>>24;w[f]=b;n?(n=t^g[g[g[E^t]]],l^=g[g[l]]):n=l=1}var F=[0,1,2,4,8, +16,32,64,128,27,54],r=r.AES=x.extend({_doReset:function(){for(var c=this._key,e=c.words,a=c.sigBytes/4,c=4*((this._nRounds=a+6)+1),b=this._keySchedule=[],h=0;h>>24]<<24|j[d>>>16&255]<<16|j[d>>>8&255]<<8|j[d&255]):(d=d<<8|d>>>24,d=j[d>>>24]<<24|j[d>>>16&255]<<16|j[d>>>8&255]<<8|j[d&255],d^=F[h/a|0]<<24);b[h]=b[h-a]^d}e=this._invKeySchedule=[];for(a=0;aa||4>=h?d:s[j[d>>>24]]^u[j[d>>>16&255]]^v[j[d>>> +8&255]]^w[j[d&255]]},encryptBlock:function(c,e){this._doCryptBlock(c,e,this._keySchedule,z,A,B,C,j)},decryptBlock:function(c,e){var a=c[e+1];c[e+1]=c[e+3];c[e+3]=a;this._doCryptBlock(c,e,this._invKeySchedule,s,u,v,w,y);a=c[e+1];c[e+1]=c[e+3];c[e+3]=a},_doCryptBlock:function(c,e,a,b,h,d,j,m){for(var n=this._nRounds,f=c[e]^a[0],g=c[e+1]^a[1],k=c[e+2]^a[2],p=c[e+3]^a[3],l=4,t=1;t>>24]^h[g>>>16&255]^d[k>>>8&255]^j[p&255]^a[l++],r=b[g>>>24]^h[k>>>16&255]^d[p>>>8&255]^j[f&255]^a[l++],s= +b[k>>>24]^h[p>>>16&255]^d[f>>>8&255]^j[g&255]^a[l++],p=b[p>>>24]^h[f>>>16&255]^d[g>>>8&255]^j[k&255]^a[l++],f=q,g=r,k=s;q=(m[f>>>24]<<24|m[g>>>16&255]<<16|m[k>>>8&255]<<8|m[p&255])^a[l++];r=(m[g>>>24]<<24|m[k>>>16&255]<<16|m[p>>>8&255]<<8|m[f&255])^a[l++];s=(m[k>>>24]<<24|m[p>>>16&255]<<16|m[f>>>8&255]<<8|m[g&255])^a[l++];p=(m[p>>>24]<<24|m[f>>>16&255]<<16|m[g>>>8&255]<<8|m[k&255])^a[l++];c[e]=q;c[e+1]=r;c[e+2]=s;c[e+3]=p},keySize:8});q.AES=x._createHelper(r)})(); \ No newline at end of file diff --git a/public/js/vendor/domReady.v2.0.1.min.js b/public/js/vendor/domReady.v2.0.1.min.js new file mode 100644 index 0000000..f61f2b9 --- /dev/null +++ b/public/js/vendor/domReady.v2.0.1.min.js @@ -0,0 +1,26 @@ +/* + +MIT License +----------- + +Copyright (c) 2010-2011, The Dojo Foundation + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ +define(function(){"use strict";function e(e){var t;for(t=0;e.length>t;t+=1)e[t](h)}function t(){var t=c;l&&t.length&&(c=[],e(t))}function i(){l||(l=!0,o&&clearInterval(o),t())}function n(e){return l?e(h):c.push(e),n}var r,s,o,a="undefined"!=typeof window&&window.document,l=!a,h=a?document:null,c=[];if(a){if(document.addEventListener)document.addEventListener("DOMContentLoaded",i,!1),window.addEventListener("load",i,!1);else if(window.attachEvent){window.attachEvent("onload",i),s=document.createElement("div");try{r=null===window.frameElement}catch(u){}s.doScroll&&r&&window.external&&(o=setInterval(function(){try{s.doScroll(),i()}catch(e){}},30))}"complete"===document.readyState&&i()}return n.version="2.0.1",n.load=function(e,t,i,r){r.isBuild?i(null):n(i)},n}); \ No newline at end of file diff --git a/public/js/vendor/fandango.v20140918.min.js b/public/js/vendor/fandango.v20140918.min.js new file mode 100644 index 0000000..fe61dd3 --- /dev/null +++ b/public/js/vendor/fandango.v20140918.min.js @@ -0,0 +1,23 @@ +(function(l){var y={"http://":1,"https://":1,"file://":1},u={deepCopy:!0,baseUrl:"",namespace:"default",timeout:1E3,paths:{}},g={};(function(){this.is=function(){var f=Object.prototype.hasOwnProperty,b=Object.prototype.toString,d={array:Array.isArray||function(a){return"[object Array]"==b.call(a)},arraylike:function(a){if(!a||!a.length&&0!==a.length||d.window(a))return!1;var c=a.length;return 1===a.nodeType||d.array(a)||!d["function"](a)&&(0===c||"number"===typeof c&&0a&&Math.floor(a)===a},iterable:function(a){try{1 in obj}catch(c){return!1}return!0},nan:function(a){return d.number(a)&&a!=+a},number:function(a){return!isNaN(parseFloat(a))&&isFinite(a)},object:function(a){return a===Object(a)}, +primitive:function(a){return!0===a||!1===a||null==a||!!{string:1,number:1}[typeof a]},string:function(a){return"string"==typeof a||a instanceof String},undefined:function(a){return void 0===a},untyped:function(a){if(!a||a.nodeType||"[object Object]"!==b.call(a)||d.window(a))return!1;try{if(a.constructor&&!f.call(a,"constructor")&&!f.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}for(var e in a);return void 0===e||f.call(a,e)},window:function(a){return null!=a&&a==a.window}, +empty:function(a){if(a){if(g.is(a,"array"))return 0===a.length;for(var c in a)if(f.call(a,c))return!1}return!0}},e=0,c=["Arguments","Date","Function","RegExp"];for(;4>e;e++)d[c[e].toLowerCase()]=function(a){a="[object "+a+"]";return function(c){return b.call(c)==a}}(c[e]);d.args=d.arguments;d.bool=d["boolean"];d.plain=d.untyped;return function(a,c){if(d["function"](c))return a===c(a);if(!d.string(c))return a===c;if((c=c.toLowerCase())&&d[c])return d[c](a);throw'Unknown type "'+c+'"';}}();this.each= +function(){var f=Array.prototype.some;return function(b,d,e){var c,a;if(void 0===b)return obj;e=e||g;if(f&&b.some===f)return b.some(d,e),b;if(g.is(b,"array")||g.is(b,"arraylike")){c=0;for(a=b.length;c=b.load.length){for(;d=b.onResolved.shift();)d([]);return b.shared}for(;!b.error&&(e=b.load[d++]);)e=b.prefix+e+b.suffix,f[e]?onProgress(b,e,document.getElementById("script::"+e))():(f[e]=1,c=createScriptNode(e),useAttachEvent?c.attachEvent("onreadystatechange",onProgress(b,e,c,!0)):(b.loadListener=onProgress(b,e,c),b.errorListener=onError(b,e,c),c.addEventListener("load",b.loadListener,!1),c.addEventListener("error",b.errorListener,!1)),c.src=e,headElement.appendChild(c))}; +unload=function(b){for(var d=0,e,c;e=b[d++];)(c=document.getElementById("script::"+e))&&c.parentElement.removeChild(c),f[e]=0};return function(b,d){var e,c={load:b,loaded:[],prefix:"",suffix:"",onResolved:[],onRejected:[],onProgress:[],onAlways:[],error:null,shared:e};void 0!==d&&!0!==d||load(c);e={prefix:function(a){c.prefix=a;return e},suffix:function(a){c.suffix=a;return e},now:function(){load(c);return e},and:function(a){c.load=c.load.concat(a);return e},unload:function(a){unload(a);return e}, +isLoaded:function(a){return isLoaded(c,a)},done:function(){c.onResolved=c.onResolved.concat(nativeSlice.call(arguments));return e},fail:function(){c.onRejected=c.onRejected.concat(nativeSlice.call(arguments));return e},progress:function(){c.onProgress=c.onProgress.concat(nativeSlice.call(arguments));return e},always:function(){c.onAlways=c.onAlways.concat(nativeSlice.call(arguments));return e}};e["try"]=e.done;e["catch"]=e.fail;e["finally"]=e.always;e.add=e.and;return e}}(),E=function(){function f(a){var c= +function(){return[]},b={},e={},d,f;for(f=0;d=a[f++];)d.selector&&(c=d.selector),d.prototype&&("function"===typeof d.prototype?d.prototype.call(e):g.merge(e,d.prototype)),d.utilities&&("function"===typeof d.utilities?d.utilities.call(b):g.merge(b,d.utilities));var k=function(a,b,e){var d=0;a=c(a,b);this.context=b||document;this.identify=e;for(this.length=a.length;dr.length&&r.push(p.data? +g.merge(p.deepCopy,{},p.data,h.data):h.data),m>r.length&&r.push(g.merge(p.deepCopy,{},p,h))),p.exports=q?h.factory.apply(p.context,r)||{}:h.factory,void 0===h.exports&&(h.exports=l?p.exports?g.merge(!0,{},l,p.exports):l:p.exports);h.state=3;if(a[b])for(;a[b].length;)a[b].pop()(b)}function d(d,f){var l,h=f.length,q,m,k=[],r={},n,x=[],p,t=void 0!==c[d].timeout?c[d].timeout:u.timeout,s=function(a){1===c[d].state&&(k.push(a),--h||(t&&clearTimeout(p),b(d)))};for(l=0;n=f[l++];)if(q=c[n],!q||1===q.state)(a[n]= +a[n]||[]).push(s),q||((m=u.paths[n])?g.is(m,"array")&&(m=m[0]):m=n,m=v(m),r[m]=n,x.push(m));else if(3===q.state)k.push(n);else if(2===q.state)throw Error('Could not instantiate "'+d+'"; dependency "'+n+'" has been rejected.');if(!((h=f.length)-k.length))return b(d);0 MIT Licensed */ +var io="undefined"==typeof module?{}:module.exports;(function(){(function(a,b){var c=a;c.version="0.9.16",c.protocol=1,c.transports=[],c.j=[],c.sockets={},c.connect=function(a,d){var e=c.util.parseUri(a),f,g;b&&b.location&&(e.protocol=e.protocol||b.location.protocol.slice(0,-1),e.host=e.host||(b.document?b.document.domain:b.location.hostname),e.port=e.port||b.location.port),f=c.util.uniqueUri(e);var h={host:e.host,secure:"https"==e.protocol,port:e.port||("https"==e.protocol?443:80),query:e.query||""};c.util.merge(h,d);if(h["force new connection"]||!c.sockets[f])g=new c.Socket(h);return!h["force new connection"]&&g&&(c.sockets[f]=g),g=g||c.sockets[f],g.of(e.path.length>1?e.path:"")}})("object"==typeof module?module.exports:this.io={},this),function(a,b){var c=a.util={},d=/^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/,e=["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"];c.parseUri=function(a){var b=d.exec(a||""),c={},f=14;while(f--)c[e[f]]=b[f]||"";return c},c.uniqueUri=function(a){var c=a.protocol,d=a.host,e=a.port;return"document"in b?(d=d||document.domain,e=e||(c=="https"&&document.location.protocol!=="https:"?443:document.location.port)):(d=d||"localhost",!e&&c=="https"&&(e=443)),(c||"http")+"://"+d+":"+(e||80)},c.query=function(a,b){var d=c.chunkQuery(a||""),e=[];c.merge(d,c.chunkQuery(b||""));for(var f in d)d.hasOwnProperty(f)&&e.push(f+"="+d[f]);return e.length?"?"+e.join("&"):""},c.chunkQuery=function(a){var b={},c=a.split("&"),d=0,e=c.length,f;for(;db.length?a:b,f=a.length>b.length?b:a;for(var g=0,h=f.length;g0&&a.splice(0,1)[0]!=c.transport.name);a.length?h(a):c.publish("connect_failed")}}},c.options["connect timeout"]))})}c.sessionid=d,c.closeTimeout=f*1e3,c.heartbeatTimeout=e*1e3,c.transports||(c.transports=c.origTransports=g?b.util.intersect(g.split(","),c.options.transports):c.options.transports),c.setHeartbeatTimeout(),h(c.transports),c.once("connect",function(){clearTimeout(c.connectTimeoutTimer),a&&typeof a=="function"&&a()})}),this},d.prototype.setHeartbeatTimeout=function(){clearTimeout(this.heartbeatTimeoutTimer);if(this.transport&&!this.transport.heartbeats())return;var a=this;this.heartbeatTimeoutTimer=setTimeout(function(){a.transport.onClose()},this.heartbeatTimeout)},d.prototype.packet=function(a){return this.connected&&!this.doBuffer?this.transport.packet(a):this.buffer.push(a),this},d.prototype.setBuffer=function(a){this.doBuffer=a,!a&&this.connected&&this.buffer.length&&(this.options.manualFlush||this.flushBuffer())},d.prototype.flushBuffer=function(){this.transport.payload(this.buffer),this.buffer=[]},d.prototype.disconnect=function(){if(this.connected||this.connecting)this.open&&this.of("").packet({type:"disconnect"}),this.onDisconnect("booted");return this},d.prototype.disconnectSync=function(){var a=b.util.request(),c=["http"+(this.options.secure?"s":"")+":/",this.options.host+":"+this.options.port,this.options.resource,b.protocol,"",this.sessionid].join("/")+"/?disconnect=1";a.open("GET",c,!1),a.send(null),this.onDisconnect("booted")},d.prototype.isXDomain=function(){var a=c.location.port||("https:"==c.location.protocol?443:80);return this.options.host!==c.location.hostname||this.options.port!=a},d.prototype.onConnect=function(){this.connected||(this.connected=!0,this.connecting=!1,this.doBuffer||this.setBuffer(!1),this.emit("connect"))},d.prototype.onOpen=function(){this.open=!0},d.prototype.onClose=function(){this.open=!1,clearTimeout(this.heartbeatTimeoutTimer)},d.prototype.onPacket=function(a){this.of(a.endpoint).onPacket(a)},d.prototype.onError=function(a){a&&a.advice&&a.advice==="reconnect"&&(this.connected||this.connecting)&&(this.disconnect(),this.options.reconnect&&this.reconnect()),this.publish("error",a&&a.reason?a.reason:a)},d.prototype.onDisconnect=function(a){var b=this.connected,c=this.connecting;this.connected=!1,this.connecting=!1,this.open=!1;if(b||c)this.transport.close(),this.transport.clearTimeouts(),b&&(this.publish("disconnect",a),"booted"!=a&&this.options.reconnect&&!this.reconnecting&&this.reconnect())},d.prototype.reconnect=function(){function e(){if(a.connected){for(var b in a.namespaces)a.namespaces.hasOwnProperty(b)&&""!==b&&a.namespaces[b].packet({type:"connect"});a.publish("reconnect",a.transport.name,a.reconnectionAttempts)}clearTimeout(a.reconnectionTimer),a.removeListener("connect_failed",f),a.removeListener("connect",f),a.reconnecting=!1,delete a.reconnectionAttempts,delete a.reconnectionDelay,delete a.reconnectionTimer,delete a.redoTransports,a.options["try multiple transports"]=c}function f(){if(!a.reconnecting)return;if(a.connected)return e();if(a.connecting&&a.reconnecting)return a.reconnectionTimer=setTimeout(f,1e3);a.reconnectionAttempts++>=b?a.redoTransports?(a.publish("reconnect_failed"),e()):(a.on("connect_failed",f),a.options["try multiple transports"]=!0,a.transports=a.origTransports,a.transport=a.getTransport(),a.redoTransports=!0,a.connect()):(a.reconnectionDelay=10:!1},c.xdomainCheck=function(){return!0},typeof window!="undefined"&&(WEB_SOCKET_DISABLE_AUTO_INITIALIZATION=!0),b.transports.push("flashsocket")}("undefined"!=typeof io?io.Transport:module.exports,"undefined"!=typeof io?io:module.parent.exports);if("undefined"!=typeof window)var swfobject=function(){function A(){if(t)return;try{var a=i.getElementsByTagName("body")[0].appendChild(Q("span"));a.parentNode.removeChild(a)}catch(b){return}t=!0;var c=l.length;for(var d=0;d0)for(var c=0;c0){var g=P(d);if(g)if(S(m[c].swfVersion)&&!(y.wk&&y.wk<312))U(d,!0),e&&(f.success=!0,f.ref=G(d),e(f));else if(m[c].expressInstall&&H()){var h={};h.data=m[c].expressInstall,h.width=g.getAttribute("width")||"0",h.height=g.getAttribute("height")||"0",g.getAttribute("class")&&(h.styleclass=g.getAttribute("class")),g.getAttribute("align")&&(h.align=g.getAttribute("align"));var i={},j=g.getElementsByTagName("param"),k=j.length;for(var l=0;l');h.outerHTML='"+k+"",n[n.length]=c.id,g=P(c.id)}else{var m=Q(b);m.setAttribute("type",e);for(var o in c)c[o]!=Object.prototype[o]&&(o.toLowerCase()=="styleclass"?m.setAttribute("class",c[o]):o.toLowerCase()!="classid"&&m.setAttribute(o,c[o]));for(var p in d)d[p]!=Object.prototype[p]&&p.toLowerCase()!="movie"&&M(m,p,d[p]);h.parentNode.replaceChild(m,h),g=m}}return g}function M(a,b,c){var d=Q("param");d.setAttribute("name",b),d.setAttribute("value",c),a.appendChild(d)}function N(a){var b=P(a);b&&b.nodeName=="OBJECT"&&(y.ie&&y.win?(b.style.display="none",function(){b.readyState==4?O(a):setTimeout(arguments.callee,10)}()):b.parentNode.removeChild(b))}function O(a){var b=P(a);if(b){for(var c in b)typeof b[c]=="function"&&(b[c]=null);b.parentNode.removeChild(b)}}function P(a){var b=null;try{b=i.getElementById(a)}catch(c){}return b}function Q(a){return i.createElement(a)}function R(a,b,c){a.attachEvent(b,c),o[o.length]=[a,b,c]}function S(a){var b=y.pv,c=a.split(".");return c[0]=parseInt(c[0],10),c[1]=parseInt(c[1],10)||0,c[2]=parseInt(c[2],10)||0,b[0]>c[0]||b[0]==c[0]&&b[1]>c[1]||b[0]==c[0]&&b[1]==c[1]&&b[2]>=c[2]?!0:!1}function T(c,d,e,f){if(y.ie&&y.mac)return;var g=i.getElementsByTagName("head")[0];if(!g)return;var h=e&&typeof e=="string"?e:"screen";f&&(v=null,w=null);if(!v||w!=h){var j=Q("style");j.setAttribute("type","text/css"),j.setAttribute("media",h),v=g.appendChild(j),y.ie&&y.win&&typeof i.styleSheets!=a&&i.styleSheets.length>0&&(v=i.styleSheets[i.styleSheets.length-1]),w=h}y.ie&&y.win?v&&typeof v.addRule==b&&v.addRule(c,d):v&&typeof i.createTextNode!=a&&v.appendChild(i.createTextNode(c+" {"+d+"}"))}function U(a,b){if(!x)return;var c=b?"visible":"hidden";t&&P(a)?P(a).style.visibility=c:T("#"+a,"visibility:"+c)}function V(b){var c=/[\\\"<>\.;]/,d=c.exec(b)!=null;return d&&typeof encodeURIComponent!=a?encodeURIComponent(b):b}var a="undefined",b="object",c="Shockwave Flash",d="ShockwaveFlash.ShockwaveFlash",e="application/x-shockwave-flash",f="SWFObjectExprInst",g="onreadystatechange",h=window,i=document,j=navigator,k=!1,l=[D],m=[],n=[],o=[],p,q,r,s,t=!1,u=!1,v,w,x=!0,y=function(){var f=typeof i.getElementById!=a&&typeof i.getElementsByTagName!=a&&typeof i.createElement!=a,g=j.userAgent.toLowerCase(),l=j.platform.toLowerCase(),m=l?/win/.test(l):/win/.test(g),n=l?/mac/.test(l):/mac/.test(g),o=/webkit/.test(g)?parseFloat(g.replace(/^.*webkit\/(\d+(\.\d+)?).*$/,"$1")):!1,p=!1,q=[0,0,0],r=null;if(typeof j.plugins!=a&&typeof j.plugins[c]==b)r=j.plugins[c].description,r&&(typeof j.mimeTypes==a||!j.mimeTypes[e]||!!j.mimeTypes[e].enabledPlugin)&&(k=!0,p=!1,r=r.replace(/^.*\s+(\S+\s+\S+$)/,"$1"),q[0]=parseInt(r.replace(/^(.*)\..*$/,"$1"),10),q[1]=parseInt(r.replace(/^.*\.(.*)\s.*$/,"$1"),10),q[2]=/[a-zA-Z]/.test(r)?parseInt(r.replace(/^.*[a-zA-Z]+(.*)$/,"$1"),10):0);else if(typeof h[["Active"].concat("Object").join("X")]!=a)try{var s=new(window[["Active"].concat("Object").join("X")])(d);s&&(r=s.GetVariable("$version"),r&&(p=!0,r=r.split(" ")[1].split(","),q=[parseInt(r[0],10),parseInt(r[1],10),parseInt(r[2],10)]))}catch(t){}return{w3:f,pv:q,wk:o,ie:p,win:m,mac:n}}(),z=function(){if(!y.w3)return;(typeof i.readyState!=a&&i.readyState=="complete"||typeof i.readyState==a&&(i.getElementsByTagName("body")[0]||i.body))&&A(),t||(typeof i.addEventListener!=a&&i.addEventListener("DOMContentLoaded",A,!1),y.ie&&y.win&&(i.attachEvent(g,function(){i.readyState=="complete"&&(i.detachEvent(g,arguments.callee),A())}),h==top&&function(){if(t)return;try{i.documentElement.doScroll("left")}catch(a){setTimeout(arguments.callee,0);return}A()}()),y.wk&&function(){if(t)return;if(!/loaded|complete/.test(i.readyState)){setTimeout(arguments.callee,0);return}A()}(),C(A))}(),W=function(){y.ie&&y.win&&window.attachEvent("onunload",function(){var a=o.length;for(var b=0;b= 10.0.0 is required.");return}location.protocol=="file:"&&a.error("WARNING: web-socket-js doesn't work in file:///... URL unless you set Flash Security Settings properly. Open the page via Web server i.e. http://..."),WebSocket=function(a,b,c,d,e){var f=this;f.__id=WebSocket.__nextId++,WebSocket.__instances[f.__id]=f,f.readyState=WebSocket.CONNECTING,f.bufferedAmount=0,f.__events={},b?typeof b=="string"&&(b=[b]):b=[],setTimeout(function(){WebSocket.__addTask(function(){WebSocket.__flash.create(f.__id,a,b,c||null,d||0,e||null)})},0)},WebSocket.prototype.send=function(a){if(this.readyState==WebSocket.CONNECTING)throw"INVALID_STATE_ERR: Web Socket connection has not been established";var b=WebSocket.__flash.send(this.__id,encodeURIComponent(a));return b<0?!0:(this.bufferedAmount+=b,!1)},WebSocket.prototype.close=function(){if(this.readyState==WebSocket.CLOSED||this.readyState==WebSocket.CLOSING)return;this.readyState=WebSocket.CLOSING,WebSocket.__flash.close(this.__id)},WebSocket.prototype.addEventListener=function(a,b,c){a in this.__events||(this.__events[a]=[]),this.__events[a].push(b)},WebSocket.prototype.removeEventListener=function(a,b,c){if(!(a in this.__events))return;var d=this.__events[a];for(var e=d.length-1;e>=0;--e)if(d[e]===b){d.splice(e,1);break}},WebSocket.prototype.dispatchEvent=function(a){var b=this.__events[a.type]||[];for(var c=0;c"),this.doc.close(),this.doc.parentWindow.s=this;var a=this.doc.createElement("div");a.className="socketio",this.doc.body.appendChild(a),this.iframe=this.doc.createElement("iframe"),a.appendChild(this.iframe);var c=this,d=b.util.query(this.socket.options.query,"t="+ +(new Date));this.iframe.src=this.prepareUrl()+d,b.util.on(window,"unload",function(){c.destroy()})},c.prototype._=function(a,b){a=a.replace(/\\\//g,"/"),this.onData(a);try{var c=b.getElementsByTagName("script")[0];c.parentNode.removeChild(c)}catch(d){}},c.prototype.destroy=function(){if(this.iframe){try{this.iframe.src="about:blank"}catch(a){}this.doc=null,this.iframe.parentNode.removeChild(this.iframe),this.iframe=null,CollectGarbage()}},c.prototype.close=function(){return this.destroy(),b.Transport.XHR.prototype.close.call(this)},c.check=function(a){if(typeof window!="undefined"&&["Active"].concat("Object").join("X")in window)try{var c=new(window[["Active"].concat("Object").join("X")])("htmlfile");return c&&b.Transport.XHR.check(a)}catch(d){}return!1},c.xdomainCheck=function(){return!1},b.transports.push("htmlfile")}("undefined"!=typeof io?io.Transport:module.exports,"undefined"!=typeof io?io:module.parent.exports),function(a,b,c){function d(){b.Transport.XHR.apply(this,arguments)}function e(){}a["xhr-polling"]=d,b.util.inherit(d,b.Transport.XHR),b.util.merge(d,b.Transport.XHR),d.prototype.name="xhr-polling",d.prototype.heartbeats=function(){return!1},d.prototype.open=function(){var a=this;return b.Transport.XHR.prototype.open.call(a),!1},d.prototype.get=function(){function b(){this.readyState==4&&(this.onreadystatechange=e,this.status==200?(a.onData(this.responseText),a.get()):a.onClose())}function d(){this.onload=e,this.onerror=e,a.retryCounter=1,a.onData(this.responseText),a.get()}function f(){a.retryCounter++,!a.retryCounter||a.retryCounter>3?a.onClose():a.get()}if(!this.isOpen)return;var a=this;this.xhr=this.request(),c.XDomainRequest&&this.xhr instanceof XDomainRequest?(this.xhr.onload=d,this.xhr.onerror=f):this.xhr.onreadystatechange=b,this.xhr.send(null)},d.prototype.onClose=function(){b.Transport.XHR.prototype.onClose.call(this);if(this.xhr){this.xhr.onreadystatechange=this.xhr.onload=this.xhr.onerror=e;try{this.xhr.abort()}catch(a){}this.xhr=null}},d.prototype.ready=function(a,c){var d=this;b.util.defer(function(){c.call(d)})},b.transports.push("xhr-polling")}("undefined"!=typeof io?io.Transport:module.exports,"undefined"!=typeof io?io:module.parent.exports,this),function(a,b,c){function e(a){b.Transport["xhr-polling"].apply(this,arguments),this.index=b.j.length;var c=this;b.j.push(function(a){c._(a)})}var d=c.document&&"MozAppearance"in c.document.documentElement.style;a["jsonp-polling"]=e,b.util.inherit(e,b.Transport["xhr-polling"]),e.prototype.name="jsonp-polling",e.prototype.post=function(a){function i(){j(),c.socket.setBuffer(!1)}function j(){c.iframe&&c.form.removeChild(c.iframe);try{h=document.createElement('