diff --git a/public/js/cryptalk_modules/audio.js b/public/js/cryptalk_modules/audio.js index b1a1304..41ebeab 100644 --- a/public/js/cryptalk_modules/audio.js +++ b/public/js/cryptalk_modules/audio.js @@ -1,26 +1,31 @@ /* Usage: - mediator.emit('audio:play',message); - mediator.emit('audio:on'); - mediator.emit('audio:off'); + mediator.on('audio:play', playTones ); + mediator.on('audio:on', on ); + mediator.on('audio:off', off ); + mediator.on('audio:mute', mute ); + mediator.on('audio:unmute', unmute ); */ // Sounds module, used for emitting those annoying bl-up sounds when receiving a message -define(['queue','mediator'], function (queue,mediator) { +define(['queue','castrato','templates'], function (queue,mediator,templates) { - var ac = false, + var + // Private variables + ac = false, enabled = true, + muted = false, - // Recursive function for playing tones, takes an array of [tone,start_ms,duration_ms] - entries - // i is only used for recursion + // Recursive function for playing tones + // accepts an array of [tone,start_ms,duration_ms] - entries playTones = function (tones, i) { - + // Parameter defaults i = (i === undefined) ? 0 : i; - // Stop if we've reached the end of iteration, and require ac - if (!ac || !enabled || !(i < Object.keys(tones).length)) { + // Stop if we've reached the end of iteration, and require ac, also stop if we're muted + if (!ac || !enabled || !(i < Object.keys(tones).length) || muted) { return; } @@ -56,14 +61,28 @@ define(['queue','mediator'], function (queue,mediator) { off = function() { enabled = false; + }, + + mute = function() { + muted = true; + mediator.emit('console:info',templates.messages.muted); + }, + + unmute = function() { + muted = false; + mediator.emit('console:info',templates.messages.unmuted); }; + // Find audio context if (window.AudioContext || window.webkitAudioContext) { ac = new (window.AudioContext || window.webkitAudioContext); } - - mediator.on('audio:play', function (message) { playTones(message); }); - mediator.on('audio:on', function (message) { on(); }); - mediator.on('audio:off', function (message) { off(); }); - + + // Connect events + mediator.on('audio:play', function(tones) {playTones(tones); } ); + mediator.on('audio:on', on ); + mediator.on('audio:off', off ); + mediator.on('audio:mute', mute ); + mediator.on('audio:unmute', unmute ); + }); \ No newline at end of file diff --git a/public/js/cryptalk_modules/castrato.js b/public/js/cryptalk_modules/castrato.js new file mode 100644 index 0000000..a5ebb3d --- /dev/null +++ b/public/js/cryptalk_modules/castrato.js @@ -0,0 +1,3 @@ +// Licenced under MIT - castrato - ©2014 Pehr Boman +(function(f,g){"function"===typeof define&&define.amd?define([],g()):"object"===typeof exports?module.exports=g:f.castrato=g})(this,function(){function f(e,a,b,c){var d=[e,b,1 settings.key.maxLen) { + return mediator.emit('console:error',templates.messages.key_to_long); + } else if (payload.length < settings.key.minLen) { + return mediator.emit('console:error',templates.messages.key_to_short); + } + + // Set key + key = payload; + + // Keep other modules informed + mediator.emit('key:changed',key); + + // Inform that the key has been set + return mediator.emit('console:info', templates.messages.key_ok ); + }, + + help = function (payload, done) { mediator.emit('console:motd', templates.help); }, + + clear = function () { mediator.emit('console:clear'); }, + + nick = function (payload) { + + // Make sure the nick meets the length requirements + if (payload.length > settings.nick.maxLen) { + return mediator('console:error', $.template(templates.messages.nick_to_long, { nick_maxLen: settings.nick.maxLen } )); + } else if (payload.length < settings.nick.minLen) { + return mediator('console:error', $.template(templates.messages.nick_to_short, {nick_minLen: settings.nick.minLen } )); + } + + // Set nick + nick = payload; + + // Keep other modules informed + mediator.emit('nick:changed',nick); + + // Inform that the nick has been set + mediator.emit('console:info', $.template(templates.messages.nick_set, { nick: $.escapeHtml(nick)})); + + }; + + mediator.on('command:help', help); + mediator.on('command:clear', clear); + mediator.on('command:nick', nick); + mediator.on('command:key', setKey); + +}); \ No newline at end of file diff --git a/public/js/cryptalk_modules/console.js b/public/js/cryptalk_modules/console.js new file mode 100644 index 0000000..28c315a --- /dev/null +++ b/public/js/cryptalk_modules/console.js @@ -0,0 +1,194 @@ +/* + + Accepts: + mediator.on('console:clear', clear); + mediator.on('console:motd', motd); + mediator.on('console:info', info); + mediator.on('console:error', error); + mediator.on('console:server', server); + mediator.on('console:message', message); + mediator.on('console:lockinput', lockInput); + mediator.on('console:unlockinput', unlockInput); + mediator.on('console:param', param); + + Emits: + mediator.emit('notification:send',...); + mediator.emit('audio:play',...); + ToDo +*/ +define( + { + compiles: ['$'], + requires: ['castrato','fandango','settings','templates','sounds','room','notifications','audio'] + }, function ($, requires, data) { + + var + + // Require shortcuts + fandango = requires.fandango, + mediator = requires.castrato, + settings = requires.settings, + templates = requires.templates, + sounds = requires.sounds, + + // Collection of DOM components + components = { + chat: $('#chat'), + input: $('#input'), + inputWrapper: $('#input_wrapper') + }, + + // Collection of parameters + parameters = {}, + + // Adds a new message to the DOM + post = function (type, text, nick) { + + var tpl = templates.post[type], + post, + data = fandango.merge({}, settings, { + nick: nick + }); + + data.text = $.template(text, data); + post = $.template(tpl, data); + + // Request a notification + showNotification(type, nick, text); + + // Append the post to the chat DOM element + components.chat['append'](post); + + }, + + param = function (p) { + parameters = fandango.merge({}, parameters, p ); + }, + + showNotification = function (type, nick, text) { + + var title = (type!='message') ? 'Cryptalk' : nick, + icon = (type == 'message') ? 'gfx/icon_128x128.png' : (type == 'error') ? 'gfx/icon_128x128_error.png' : 'gfx/icon_128x128_info.png'; + + // Emit notification + mediator.emit('notification:send', + { + title: title.substring(0, 20), + body: text.substring(0, 80), + icon: icon + }); + + // Emit sound + if ( type == 'message' ) mediator.emit('audio:play',sounds.message); + + }, + + motd = function (payload, done) { post('motd', payload); }, + info = function (payload, done) { post('info', payload); }, + error = function (payload, done) { post('error', payload); }, + message = function (payload, done) { post('message', payload.message , payload.nick ); }, + server = function (payload, done) { post('server', payload); }, + + clear = function () { + fandango.subordinate(function () { + components.input[0].value = ''; + }); + }, + + lockInput = function () { + components.input[0].setAttribute('disabled', 'disabled'); + components.inputWrapper[0].className = 'loading'; + }, + + unlockInput = function () { + components.input[0].removeAttribute('disabled'); + components.inputWrapper[0].className = ''; + components.input.focus(); + }, + + // Handler for the document`s keyDown-event. + onKeyDown = function (e) { + var buffer, + parts, + payload, + command, + save; + + // The Document object is bound to this element. + // If the active element is not the input, focus on it and exit the function. + // Ignore this when ctrl and/or alt is pressed! + if (!e.ctrlKey && !e.altKey && 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] || buffer.slice(0, 1)) === '/') { + parts = $.ssplit(buffer.slice(1), ' '); + command = parts[0]; + payload = parts[1]; + + // Shout this command to all modules + mediator.emit( + 'command:'+command, + payload, + function(retvals,recipients) { + if(recipients == 0) { + return post('error', $.template(templates.messages.unrecognized_command, { commandName: command })); + } else { + clear(); + } + } + ); + + } else /* Handle ordinary message */ { + + if(!parameters.room || !parameters.key ) { + // Make sure that the user has joined a room and the key is set + return (!parameters.room) ? post('error', templates.messages.msg_no_room) : post('error', templates.messages.msg_no_key); + } + + console.log(parameters.room); + + // Before sending the message. + // Encrypt message using room UUID as salt and key as pepper. + mediator.emit( + 'socket:emit', + { + data: 'message:send', + payload: { + room: $.SHA1(parameters.room), + msg: $.AES.encrypt(buffer, $.SHA1(parameters.room) + parameters.key).toString(), + nick: parameters.nick ? $.AES.encrypt(parameters.nick, $.SHA1(parameters.room) + parameters.key).toString() : false + } + } + ); + + // And clear the the buffer + clear(); + + } + }; + + // Bind the necessary DOM events + $(document).on('keydown', onKeyDown);; + + // Put focus on the message input + components.input.focus(); + + // Connect events + mediator.on('console:clear', clear); + mediator.on('console:motd', motd); + mediator.on('console:info', info); + mediator.on('console:error', error); + mediator.on('console:server', server); + mediator.on('console:message', message); + mediator.on('console:lockinput', lockInput); + mediator.on('console:unlockinput', unlockInput); + mediator.on('console:param', param); + +}); \ No newline at end of file diff --git a/public/js/cryptalk_modules/cryptalk.js b/public/js/cryptalk_modules/cryptalk.js index 700a87c..e918ae3 100644 --- a/public/js/cryptalk_modules/cryptalk.js +++ b/public/js/cryptalk_modules/cryptalk.js @@ -1,554 +1,49 @@ // Main cryptalk module define({ compiles: ['$'], - requires: ['mediator', 'hosts', 'templates', 'audio', 'fandango','notifications', 'sounds', 'window'] + requires: ['castrato','console','host','client'] }, function ($, requires, data) { - var socket, - key, - host, - room, - room_raw, - hash, - nick, - mute = false, - - settings = {}, - - history = [], - history_pos = -1, - history_keep = 4, - history_timer, - - // Collection of DOM components - components = { - chat: $('#chat'), - input: $('#input'), - inputWrapper: $('#input_wrapper') - }, - - // Shortcut - hosts = requires.hosts, - fandango = requires.fandango, - mediator = requires.mediator, - templates = requires.templates, - sounds = requires.sounds, - win = requires.window, - - lockInput = function () { - components.input[0].setAttribute('disabled', 'disabled'); - components.inputWrapper[0].className = 'loading'; - }, - - unlockInput = function () { - components.input[0].removeAttribute('disabled'); - components.inputWrapper[0].className = ''; - components.input.focus(); - }, - - showNotification = function (type, nick, text) { - - var title = (type!='message') ? 'Cryptalk' : nick, - icon = (type == 'message') ? 'gfx/icon_128x128.png' : (type == 'error') ? 'gfx/icon_128x128_error.png' : 'gfx/icon_128x128_info.png'; - - // Emit notification - mediator.emit('notification:send', - { - title: title.substring(0, 20), - body: text.substring(0, 80), - icon: icon - }); - - // Emit sound - if ( type == 'message' ) mediator.emit('audio:play',sounds.message); - - }, - - // Adds a new message to the DOM - post = function (type, text, clearChat, clearBuffer, nick) { - - var tpl = templates.post[type], - post, - data = fandango.merge({}, settings, { - nick: nick, - room: room - }); - - data.text = $.template(text, data); - post = $.template(tpl, data); - - // Always clear the input after a post - if (clearBuffer) { - clearInput(); - } - - showNotification(type, nick, text); - - // Append the post to the chat DOM element - components.chat[clearChat ? 'html' : 'append'](post); - }, - - // Chat related commands - commands = { - help: function (payload, done) { - post('motd', templates.help); - done(); - }, - - host: function () { - post('info', JSON.stringify(host || {})); - }, - - hosts: function (force, done) { - var i = 0, - left = hosts.hosts.length, - host, - strhosts = '\n', - callback = function (host, index, isUp) { - return function (hostSettings) { - host.settings = (isUp ? hostSettings : 0); - - strhosts += $.template(templates.messages[(isUp ? 'host_available' : 'host_unavailable')], { - name: host.name, - path: host.path, - index: index - }); - - if (--left === 0) { - post('info', strhosts); - done(); - } - }; - }; - - // - force = (force && force.toLowerCase() === 'force'); - - // Loop through all the hosts - while ((host = hosts.hosts[i])) { - if (!force && host.settings !== undefined) { - if (host.settings) { - callback(host, i, 1)(); - } else { - callback(host, i, 0)(); - } - } else { - require([host.path], callback(host, i, 1), callback(host, i, 0)); - } - - i++; - } - }, - - connect: function (toHost, done) { - var request; - - if (host && host.connected) { - done(); - post('error', $.template(templates.messages.already_connected, { - host: host.name || 'localhost' - })); - return done(); - } - - if ($.isDigits(toHost)) { - if ((host = hosts.hosts[+toHost])) { - if (host.settings) { - settings = host.settings; - } else { - request = host.path; - } - } else { - post('error', 'Undefined host index: ' + toHost); - return done(); - } - } else if (fandango.is(toHost, 'untyped')) { - settings = toHost.settings; - } else { // Assume string - request = toHost; - } - - if (request) { - return require([request], function (settings) { - host.settings = settings; - commands.connect(toHost, done); - }, function () { - post('error', 'Could not fetch host settings: ' + request); - return done(); - }); - } - - // Push 'Connecting...' message - post('info', $.template(templates.messages.connecting, { - host: host.name || 'localhost' - })); - - // The one and only socket - socket = $.Websocket.connect(host.host, { - forceNew: true, - 'force new connection': true - }); - - // Set window title - win.setTitle(settings.client.title); - - // Bind socket events - socket - - .on('room:joined', function (data) { - room = $.escapeHtml(data); - - post('info', $.template(templates.messages.joined_room, { roomName: $.escapeHtml(room_raw) })); - - // Automatically count persons on join - socket.emit('room:count'); - }) - - .on('room:left', function () { - post('info', $.template(templates.messages.left_room, { roomName: $.escapeHtml(room_raw) })); - - // Clear history on leaving room - clearHistory(); - - room = false; - room_raw = ""; - }) - - .on('message:send', function (data) { - var decrypted = $.AES.decrypt(data.msg, room + key), - sanitized = $.escapeHtml(decrypted), - nick = !data.nick ? templates.default_nick : $.escapeHtml($.AES.decrypt(data.nick, room + key)); - - if (!decrypted) { - post('error', templates.messages.unable_to_decrypt); - } else { - post('message', sanitized, false, false, nick); - } - }) - - .on('message:server', function (data) { - if( data.msg ) { - var sanitized = $.escapeHtml(data.msg); - if( templates.server[sanitized] ) { - if( data.payload !== undefined ) { - var sanitized_payload = $.escapeHtml(data.payload); - post('server', $.template(templates.server[sanitized], { payload: sanitized_payload })); - } else { - post('server', templates.server[sanitized]); - } - } else { - post('error', templates.server.bogus); - } - } else { - post('error', templates.server.bogus); - } - }) - - .on('connect', function () { - // Tell the user that the chat is ready to interact with - post('info', $.template(templates.messages.connected, { - host: host.name || 'localhost' - })); - - host.connected = 1; - - done(); - }) - - .on('disconnect', function () { - room = 0; - key = 0; - host.connected = 0; - - // Tell the user that the chat is ready to interact with - post('info', $.template(templates.messages.disconnected, { - host: host.name || 'localhost' - })); - - // Revert title - win.setTitle(templates.client.title); - }) - - .on('error', function () { - room = 0; - key = 0; - host.connected = 0; - post('error', templates.messages.socket_error); - done(); - }); - }, - - reconnect: function (foo, done) { - if (host) { - if (host.connected) { - commands.disconnect(); - commands.connect(host, done); - } else { - commands.connect(host, done); - } - } else { - done(); - return post('error', templates.messages.reconnect_no_host); - } - }, - - disconnect: function () { - socket.disconnect(); - }, - - clear: function () { - components.chat.html(''); - - // Clear command history on clearing buffer - clearHistory(); - }, - - leave: function () { - if (room) { - socket.emit('room:leave', room); - } else { - post('error', templates.messages.leave_from_nowhere); - } - }, - - count: function () { - if (room) { - socket.emit('room:count'); - } else { - post('error', templates.messages.not_in_room); - } - }, - - key: function (payload) { - if (!host) { - return post('error', templates.messages.key_no_host); - } - - // Make sure the key meets the length requirements - if (payload.length > settings.key.maxLen) { - return post('error', $.template(templates.messages.key_to_long, { key_maxLen: settings.key_maxLen } )); - } else if (payload.length < settings.key.minLen) { - return post('error', $.template(templates.messages.key_to_short, { key_maxLen: settings.key_minLen } )); - } - - // 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)); - }, - - nick: function (payload) { - // Make sure the key meets the length requirements - if (payload.length > settings.nick.maxLen) { - return post('error', $.template(templates.messages.nick_to_long, { nick_maxLen: settings.nick.maxLen } )); - } else if (payload.length < settings.nick.minLen) { - return post('error', $.template(templates.messages.nick_to_short, {nick_minLen: settings.nick.minLen } )); - } - - // Set nick - nick = payload; - - // Inform that the key has been set - post('info', $.template(templates.messages.nick_set, { nick: $.escapeHtml(nick)})); - }, - - mute: function () { - mute = true; - return post('info', templates.messages.muted); - }, - - unmute: function () { - mute = false; - return post('info', templates.messages.unmuted); - }, - - title: function (payload) { - win.setTitle(payload); - return post('info', $.template(templates.messages.title_set, { title: $.escapeHtml(payload)})); - }, - - join: function (payload) { - if (!host) { - return post('error', templates.messages.join_no_host); - } - - if (room) { - return post('error', templates.messages.already_in_room); - } else { - room_raw = payload; - return socket.emit('room:join', $.SHA1(payload)) - } - } - - }, - - // Push input buffer to history - pushHistory = function (b) { - history.push(b); - - // Shift oldest buffer if we have more than we should keep - if (history.length > history_keep) { - history.shift(); - } - }, - - // Clear input buffer history - clearHistory = function() { - history = []; - history_pos = -1; - }, - - // Clear input buffer - clearInput = function() { - fandango.subordinate(function () { - components.input[0].value = ''; - }); - }, - - // Handler for the document`s keyDown-event. - onKeyDown = function (e) { - var buffer, - parts, - payload, - command, - save; - - // The Document object is bound to this element. - // If the active element is not the input, focus on it and exit the function. - // Ignore this when ctrl and/or alt is pressed! - if (!e.ctrlKey && !e.altKey && components.input[0] !== $.activeElement()) { - return components.input.focus(); - } - - // Reset command history clear timer - clearTimeout(history_timer); - history_timer = setTimeout(clearHistory, 60000); - - // Check for escape key, this does nothing but clear the input buffer and reset history position - if (e.keyCode == 27) { - history_pos = -1; - clearInput(); - - return; - } - - // Check for up or down-keys, they handle the history position - if (e.keyCode == 38 || e.keyCode == 40) { - - if (e.keyCode == 38 ) { history_pos = (history_pos > history.length - 2) ? -1 : history_pos = history_pos + 1; } - else { history_pos = (history_pos <= 0) ? -1 : history_pos = history_pos - 1; } - - var input = components.input[0]; - input.value = (history_pos == -1) ? '' : history[history.length-1-history_pos]; - - // Wierd hack to move caret to end of input-box - setTimeout(function() {if(input.setSelectionRange) input.setSelectionRange(input.value.length, input.value.length);}, 0); - - return; - } - - // Return immediatly if the buffer is empty or if the hit key was not - if (e.keyCode !== 13 || !(buffer = components.input[0].value)) { - return; - } - - // Reset current history position to 0 (last command) - history_pos = -1; - - // Handle command - if ((buffer[0] || buffer.slice(0, 1)) === '/') { - parts = $.ssplit(buffer.slice(1), ' '); - command = parts[0]; - payload = parts[1]; - - // Check that there is an handler for this command - if (!commands[command]) { - pushHistory(buffer); - return post('error', $.template(templates.messages.unrecognized_command, { commandName: command })); - } - - // Some commands are asynchrounous; - // If the command expects more than one argument, the second argument is a callback that is called when the command is done. - if (commands[command].length > 1) { - // Lock the input from further interaction - lockInput(); - - // Execute command handler with callback function. - commands[command](payload, unlockInput); - } else { - // Execute normally. - commands[command](payload); - } - - // Clear input field - clearInput(); - - // Save to history - if(command !== 'key') { - pushHistory(buffer); - } - - } else /* Handle ordinary message */ { - - if (!room || !key) { - // Push buffer to history and clear input field - pushHistory(buffer); clearInput(); - - // Make sure that the user has joined a room and the key is set - return (!room) ? post('error', templates.messages.msg_no_room) : 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).toString(), - nick: nick ? $.AES.encrypt(nick, room + key).toString() : false - }); - - // And clear the the buffer - clearInput(); - - // Save to history - pushHistory(buffer); - } - }; - - // Bind the necessary DOM events - $(document).on('keydown', onKeyDown); - - // Put focus on the message input - components.input.focus(); - - // Post the help/welcome message - post('motd', templates.motd, true); + // Require shortcut + var mediator = requires.castrato; // Route mediator messages mediator.on('window:focused',function() { mediator.emit('audio:off'); mediator.emit('notification:off'); }); + mediator.on('window:blurred',function() { - if( !mute ) mediator.emit('audio:on'); + mediator.emit('audio:on'); mediator.emit('notification:on'); }); - unlockInput(); + mediator.on('command:mute', function () { mediator.emit('audio:mute'); } ); + mediator.on('command:unmute', function () { mediator.emit('audio:unmute'); } ); - // Revert title - win.setTitle(templates.client.title); - - // 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. - commands.connect(hosts.autoconnect, function() { - if (host && (hash = window.location.hash)) { - parts = hash.slice(1).split(':'); - - parts[0] && commands.join(parts[0]); - parts[1] && commands.key(parts[1]); - } + // Help console and host keep track of current states + mediator.on('room:changed', function(room) { + mediator.emit('console:param',{ room: room}); + mediator.emit('host:param',{ room: room}); + }); + mediator.on('nick:changed', function(nick) { + mediator.emit('console:param',{ nick: nick}); + }); + mediator.on('key:changed', function(key) { + mediator.emit('console:param',{ key: key}); + mediator.emit('host:param',{ key: key}); + }); -}); + // Connect to the default host + mediator.emit('command:connect', undefined, function() { + // Join room and set key if a hash in the format #Room:Key has been provided + if (hash = window.location.hash) { + parts = hash.slice(1).split(':'); + + parts[0] && mediator.emit('command:join',parts[0]); + parts[1] && mediator.emit('command:key',parts[1]); + } + }); + +}); \ No newline at end of file diff --git a/public/js/cryptalk_modules/host.js b/public/js/cryptalk_modules/host.js new file mode 100644 index 0000000..2df3fc3 --- /dev/null +++ b/public/js/cryptalk_modules/host.js @@ -0,0 +1,269 @@ +/* + + Accepts: + mediator.on('command:host', host); + mediator.on('command:hosts', hosts); + mediator.on('command:connect', connect); + mediator.on('command:disconnect', disconnect); + mediator.on('command:reconnect', disconnect); + + Emits: + mediator.on('socket:emit', emit); +*/ +define( + { + compiles: ['$'], + requires: ['fandango','castrato','settings','templates','hosts','window'] + }, function ($, requires, data) { + + var + + // Private properties + socket, + host, + + // Require shortcuts + fandango = requires.fandango, + mediator = requires.castrato, + settings = requires.settings, + templates = requires.templates, + hostconfig = requires.hosts, + + // Collection of parameters + parameters = {}, + + emit = function(payload) { + // Route message from mediator to socket + console.log('EMIT:',payload.data,payload.payload); + if(socket) socket.emit(payload.data,payload.payload); + }, + + host = function () { + mediator.emit('info', JSON.stringify(host || {})); + }, + + hosts = function (force, done) { + + var i = 0, + left = hostconfig.hosts.length, + host, + strhosts = '\n', + callback = function (host, index, isUp) { + return function (hostSettings) { + host.settings = (isUp ? hostSettings : 0); + + strhosts += $.template(templates.messages[(isUp ? 'host_available' : 'host_unavailable')], { + name: host.name, + path: host.path, + index: index + }); + + if (--left === 0) { + mediator.emit('info', strhosts); + done(); + } + }; + }; + + force = (force && force.toLowerCase() === 'force'); + + // Loop through all the hosts + while ((host = hostconfig.hosts[i])) { + if (!force && host.settings !== undefined) { + if (host.settings) { + callback(host, i, 1)(); + } else { + callback(host, i, 0)(); + } + } else { + require([host.path], callback(host, i, 1), callback(host, i, 0)); + } + + i++; + } + }, + + connect = function (toHost, done) { + + mediator.emit('console:lockinput'); + + var + request, + + // Use hostconfig.autoconnect as default host + toHost = (toHost == undefined) ? hostconfig.autoconnect : toHost; + + if (host && host.connected) { + mediator.emit('console:error', $.template(templates.messages.already_connected, { + host: host.name || 'localhost' + })); + mediator.emit('console:unlockinput'); + return; + } + + if ($.isDigits(toHost)) { + if ((host = hostconfig.hosts[+toHost])) { + if (host.settings) { + settings = host.settings; + } else { + request = host.path; + } + } else { + mediator.emit('console:error', 'Undefined host index: ' + toHost); + mediator.emit('console:unlockinput'); + return; + } + + } else if (fandango.is(toHost, 'untyped')) { + settings = toHost.settings; + } else { // Assume string + request = toHost; + } + + if (request) { + return require([request], function (settings) { + host.settings = settings; + return connect(toHost, done); + }, function () { + mediator.emit('console:error', 'Could not fetch host settings: ' + request); + mediator.emit('console:unlockinput'); + return; + }); + } + + // Push 'Connecting...' message + mediator.emit('console:info', $.template(templates.messages.connecting, {host: host.name || 'localhost'})); + + // Show motd (placed here to enable server specific motds in future) + mediator.emit('console:motd', host.settings.motd); + + // The one and only socket + socket = $.Websocket.connect(host.host, { + forceNew: true, + 'force new connection': true + }); + + // Bind socket events + socket + .on('room:joined', function (data) { + + mediator.emit('console:info', $.template(templates.messages.joined_room, { roomName: $.escapeHtml(parameters.room) } )); + + // Automatically count persons on join + socket.emit('room:count'); + }) + .on('room:left', function () { + mediator.emit('console:info', $.template(templates.messages.left_room, { roomName: $.escapeHtml(parameters.room) } )); + mediator.emit('room:changed',false); + }) + + .on('message:send', function (data) { + var decrypted = $.AES.decrypt(data.msg, $.SHA1(parameters.room) + parameters.key), + sanitized = $.escapeHtml(decrypted), + nick = !data.nick ? templates.default_nick : $.escapeHtml($.AES.decrypt(data.nick, $.SHA1(parameters.room) + parameters.key)); + + if (!decrypted) { + mediator.emit('console:error', templates.messages.unable_to_decrypt); + } else { + mediator.emit('console:message', { message: sanitized, nick: nick } ); + } + }) + + .on('message:server', function (data) { + if( data.msg ) { + var sanitized = $.escapeHtml(data.msg); + if( templates.server[sanitized] ) { + if( data.payload !== undefined ) { + var sanitized_payload = $.escapeHtml(data.payload); + mediator.emit('console:server', $.template(templates.server[sanitized], { payload: sanitized_payload })); + } else { + mediator.emit('console:server', templates.server[sanitized]); + } + } else { + mediator.emit('console:error', templates.server.bogus); + } + } else { + mediator.emit('console:error', templates.server.bogus); + } + }) + + .on('connect', function () { + // Tell the user that the chat is ready to interact with + mediator.emit('console:info', $.template(templates.messages.connected, { + host: host.name || 'localhost' + })); + + // Set window title + mediator.emit('window:title', host.settings.title); + + // Unlock input + mediator.emit('console:unlockinput'); + + done(); + + host.connected = 1; + }) + + .on('disconnect', function () { + room = 0; + key = 0; + host.connected = 0; + + // Tell the user that the chat is ready to interact with + mediator.emit('console:info', $.template(templates.messages.disconnected, { + host: host.name || 'localhost' + })); + + // Revert title + mediator.emit('room:changed',undefined); + mediator.emit('window:title',templates.client.title); + }) + + .on('error', function () { + room = 0; + key = 0; + host.connected = 0; + mediator.emit('console:error', templates.messages.socket_error); + + // Unlock input + mediator.emit('console:unlockinput'); + }); + + return; + }, + + reconnect = function (foo, done) { + if (host) { + if (host.connected) { + disconnect(); + connect(host, done); + } else { + connect(host, done); + } + } else { + done(); + return mediator.emit('console:error', templates.messages.reconnect_no_host); + } + }, + + disconnect = function () { + socket.disconnect(); + }, + + param = function (p) { + parameters = fandango.merge({}, parameters, p ); + console.log(p); + console.log(parameters); + }; + + mediator.on('command:host', host); + mediator.on('command:hosts', hosts); + mediator.on('command:connect', connect); + mediator.on('command:disconnect', disconnect); + mediator.on('command:reconnect', disconnect); + + mediator.on('socket:emit', emit); + mediator.on('host:param', param); + + +}); \ No newline at end of file diff --git a/public/js/cryptalk_modules/mediator.js b/public/js/cryptalk_modules/mediator.js deleted file mode 100644 index eb9e4f0..0000000 --- a/public/js/cryptalk_modules/mediator.js +++ /dev/null @@ -1,188 +0,0 @@ -(function (self, factory) { - if (typeof define === 'function' && define.amd) { - // AMD. Register as an anonymous module. - define([], factory); - } else if (typeof exports === 'object') { // Node - module.exports = factory(); - } else { - // Attaches to the current context - self.castrato = factory; - } -}(this, (function () { - var - /** - * Contains the next unique node id. - * - * @property index - * @type {Integer} - * @private - */ - index = 0, - - /** - * Contains all subscriptions - * - * @property subs - * @type {Object} - * @private - */ - subs = {}, - - /** - * An empty function. - * This method does not accept any arguments. - * - * @property noop - * @type {function} - * @private - */ - noop = function () {}; - - /** - * Creates a new entry in the `subs` object. - * - * @method on - * @private - * @param {Integer} fromId The unique subscriber ID. - * @param {String} event The event to subscribe to. - * @param {Function} func A function to execute when the event is triggered. - */ - function on (fromId, event, func) { - (subs[event] || (subs[event] = [])).push([fromId, func, func.length]); - } - - /** - * Removes all event handlers originating from `fromId` and optionally filter by handler. - * - * @method off - * @private - * @param {Integer} fromId The unique subscriber ID. - * @param {String} event The event to unsubscribe from. - * @param {Function} [func=null] The original handler that was attached to this event. If not passed, all subscriptions will be removed. - */ - function off (fromId, event, func) { - var sub, - i = 0, - toSubs = subs[event]; - - if (toSubs) { - while ((sub = toSubs[i++])) { - if (sub[0] === fromId && (!func || func === sub[1])) { - toSubs.splice(--i, 1); - } - } - } - } - - /** - * Loops through all subscriptions, calling all handlers attached to given `event`. - * - * @method emit - * @private - * @param {Integer} fromId The unique subscriber ID. - * @param {String} event The event to emit - * @param {Object} [data=undefined] Parameters to pass along to the event handler. - * @param {Function} [func=undefined] A function to execute when all event handlers has returned. - */ - function emit (persistent, event, data, func) { - var sub, - toSubs = subs[event] || [], - total = toSubs.length, - left = total, - loop = total, - answers = [], - done; - - if (loop) { - done = !func ? noop : function (data) { - if (data) { - answers.push(data); - } - - if (!--left) { - func(answers, total); - func = 0; - } - }; - - while ((sub = toSubs[--loop])) { - sub[1](data, (sub[2] > 1) ? done : left--); - } - } - - // `func` get destructed when called. - // It has to be called at least once - even if no one was subscribing. - // Execute it if it still exists. - if (func) { - func(answers, total); - } - } - - return function () { - var nodeId = index++; - - return { - /** - * Execute all handlers attached to the given event. - * - * @method emit - * @param {String} event The event to emit - * @param {Object} [data=undefined] Parameters to pass along to the event handler. - * @param {Function} [func=undefined] A function to execute when all event handlers has returned. - * @return {Object} `this` - * @example - * $.emit('something'); - * $.emit('something', { foo: 'bar' }); - * $.emit('something', { foo: 'bar' }, function (data, subscribers) { - * console.log('Emit done, a total of ' + subscribers + ' subscribers returned: ', data); - * }); - */ - emit: function (persistent, event, data, func) { - // emit('something', { data: true }, function () {}); - if (persistent !== true || persistent !== false) { - func = data; - data = event; - event = persistent; - persistent = false; - } - - emit(persistent, event, data, func); - - return this; - }, - - /** - * Attach an event handler function for one event. - * - * @method on - * @param {String} event The event to subscribe to. - * @param {Function} func A function to execute when the event is triggered. - * @return {Object} `this` - * @example - * $.on('something', function (data) { - * console.log('Got something!', data); - * }); - */ - on: function (event, func) { - on(nodeId, event, func); - return this; - }, - - /** - * Removes an event handler function for one event. - * - * @method off - * @param {String} event The event to unsubscribe from. - * @param {Function} [func=null] The original handler that was attached to this event. If not passed, all subscriptions will be removed. - * @return {Object} `this` - * @example - * $.off('something'); - * $.off('something else', handler); - */ - off: function (event) { - off(nodeId, event); - return this; - } - }; - }; -}()))); \ No newline at end of file diff --git a/public/js/cryptalk_modules/notifications.js b/public/js/cryptalk_modules/notifications.js index 8b267ba..277305d 100644 --- a/public/js/cryptalk_modules/notifications.js +++ b/public/js/cryptalk_modules/notifications.js @@ -15,7 +15,7 @@ Usage mediator.emit('notification:off'); */ -define(['mediator','window','settings'], function (mediator, win, settings) { +define(['castrato','window','settings'], function (mediator, win, settings) { var enabled = true, diff --git a/public/js/cryptalk_modules/room.js b/public/js/cryptalk_modules/room.js new file mode 100644 index 0000000..9c5421a --- /dev/null +++ b/public/js/cryptalk_modules/room.js @@ -0,0 +1,63 @@ +/* + + Accepts: + mediator.on('command:join', ...); + mediator.on('command:leave', ...); + mediator.on('command:key', ...); + + Emits: + mediator.emit('room:changed',...); + mediator.emit('console:error',...); + mediator.emit('socket:emit',...); + +*/ + +define( + { + compiles: ['$'], + requires: ['castrato','settings','templates'] + }, function ($, requires, data) { + + var + + // Private properties + room=false, + + // Require shortcuts + mediator = requires.castrato, + settings = requires.settings, + templates = requires.templates, + + join = function(payload) { + if (room !== false) { + mediator.emit('console:error',$.template(templates.messages.already_in_room, { room: room })); + } else { + room = payload; + mediator.emit('room:changed', room ); + mediator.emit('socket:emit',{ data: 'room:join' , payload: $.SHA1(room) } ); + } + }, + + leave = function() { + if (room !== false) { + mediator.emit('socket:emit',{ data: 'room:leave' , payload: $.SHA1(room) } ); + room = false; + } else { + mediator.emit('console:error',templates.messages.leave_from_nowhere); + } + }, + + count = function() { + if (room) { + mediator.emit('socket:emit','room:count'); + } else { + mediator.emit('console:error', templates.messages.not_in_room); + } + }; + + // Connect events + mediator.on('command:join', join); + mediator.on('command:leave', leave); + mediator.on('command:count', count); + +}); \ No newline at end of file diff --git a/public/js/cryptalk_modules/settings.js b/public/js/cryptalk_modules/settings.js index 4b52116..dfe07cf 100644 --- a/public/js/cryptalk_modules/settings.js +++ b/public/js/cryptalk_modules/settings.js @@ -1,8 +1,23 @@ define({ - client: { - title: "Cryptalk - Online" - }, + title: "Cryptalk - Online", + + motd: '
\n\n' +
+		'▄████▄   ██▀███ ▓██   ██▓ ██▓███  ▄▄▄█████▓ ▄▄▄       ██▓     ██ ▄█▀  \n' +
+		'▒██▀ ▀█  ▓██ ▒ ██▒▒██  ██▒▓██░  ██▒▓  ██▒ ▓▒▒████▄    ▓██▒     ██▄█▒  \n' +
+		'▒▓█    ▄ ▓██ ░▄█ ▒ ▒██ ██░▓██░ ██▓▒▒ ▓██░ ▒░▒██  ▀█▄  ▒██░    ▓███▄░  \n' +
+		'▒▓▓▄ ▄██▒▒██▀▀█▄   ░ ▐██▓░▒██▄█▓▒ ▒░ ▓██▓ ░ ░██▄▄▄▄██ ▒██░    ▓██ █▄  \n' +
+		'▒ ▓███▀ ░░██▓ ▒██▒ ░ ██▒▓░▒██▒ ░  ░  ▒██▒ ░  ▓█   ▓██▒░██████▒▒██▒ █▄ \n' +
+		'░ ░▒ ▒  ░░ ▒▓ ░▒▓░  ██▒▒▒ ▒▓▒░ ░  ░  ▒ ░░    ▒▒   ▓▒█░░ ▒░▓  ░▒ ▒▒ ▓▒ \n' +
+		'  ░  ▒     ░▒ ░ ▒░▓██ ░▒░ ░▒ ░         ░      ▒   ▒▒ ░░ ░ ▒  ░░ ░▒ ▒░ \n' +
+		'░          ░░   ░ ▒ ▒ ░░  ░░         ░        ░   ▒     ░ ░   ░ ░░ ░  \n' +
+		'░ ░         ░     ░ ░                             ░  ░    ░  ░░  ░    \n' +
+		'░                 ░ ░                                                 \n' +
+		'                                  https://github.com/hexagon/cryptalk \n' +
+		'                                                                      \n' +
+		' Tip of the day: /help                                                \n' +
+		'----------------------------------------------------------------------' +
+		'
', nick: { maxLen: 20, @@ -16,6 +31,6 @@ define({ notifications: { maxOnePerMs: 3000 - } + }, }); \ No newline at end of file diff --git a/public/js/cryptalk_modules/templates.js b/public/js/cryptalk_modules/templates.js index d2ac4ef..17ac774 100644 --- a/public/js/cryptalk_modules/templates.js +++ b/public/js/cryptalk_modules/templates.js @@ -1,22 +1,6 @@ // The templating function only supports variables. // Define a variable as so: {variable_name} define({ - motd: '
\n\n' +
-		'▄████▄   ██▀███ ▓██   ██▓ ██▓███  ▄▄▄█████▓ ▄▄▄       ██▓     ██ ▄█▀  \n' +
-		'▒██▀ ▀█  ▓██ ▒ ██▒▒██  ██▒▓██░  ██▒▓  ██▒ ▓▒▒████▄    ▓██▒     ██▄█▒  \n' +
-		'▒▓█    ▄ ▓██ ░▄█ ▒ ▒██ ██░▓██░ ██▓▒▒ ▓██░ ▒░▒██  ▀█▄  ▒██░    ▓███▄░  \n' +
-		'▒▓▓▄ ▄██▒▒██▀▀█▄   ░ ▐██▓░▒██▄█▓▒ ▒░ ▓██▓ ░ ░██▄▄▄▄██ ▒██░    ▓██ █▄  \n' +
-		'▒ ▓███▀ ░░██▓ ▒██▒ ░ ██▒▓░▒██▒ ░  ░  ▒██▒ ░  ▓█   ▓██▒░██████▒▒██▒ █▄ \n' +
-		'░ ░▒ ▒  ░░ ▒▓ ░▒▓░  ██▒▒▒ ▒▓▒░ ░  ░  ▒ ░░    ▒▒   ▓▒█░░ ▒░▓  ░▒ ▒▒ ▓▒ \n' +
-		'  ░  ▒     ░▒ ░ ▒░▓██ ░▒░ ░▒ ░         ░      ▒   ▒▒ ░░ ░ ▒  ░░ ░▒ ▒░ \n' +
-		'░          ░░   ░ ▒ ▒ ░░  ░░         ░        ░   ▒     ░ ░   ░ ░░ ░  \n' +
-		'░ ░         ░     ░ ░                             ░  ░    ░  ░░  ░    \n' +
-		'░                 ░ ░                                                 \n' +
-		'                                  https://github.com/hexagon/cryptalk \n' +
-		'                                                                      \n' +
-		' Tip of the day: /help                                                \n' +
-		'----------------------------------------------------------------------' +
-		'
', help: '
                                                                \n' +
 		'Cryptalk, encrypted instant chat.                                      \n' +
@@ -70,8 +54,7 @@ define({
 	messages: {
 		key_to_short: 			'Hmm, that\'s a weak key, try again...',
 		key_to_long: 			'Man that\'s a long key. Make it a tad short, \'kay?',
-		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.',
+		key_ok: 				'Key set, you can now start communicating.',
 		key_no_host: 			'You have to connect to a host before setting the key.',
 
 		join_no_host: 			'You have to connect to a host before joining a room.',
diff --git a/public/js/cryptalk_modules/window.js b/public/js/cryptalk_modules/window.js
index 76a6a25..02e0984 100644
--- a/public/js/cryptalk_modules/window.js
+++ b/public/js/cryptalk_modules/window.js
@@ -1,4 +1,6 @@
 /*
+	Accepts:
+		'window:title'
 
 	Emits:
 		'window:focused'
@@ -9,7 +11,7 @@
 		window.setTitle(title);
 
 */
-define(['mediator'],function (mediator){
+define(['castrato'],function (mediator){
  
 	var exports = {},
 
@@ -34,6 +36,9 @@ define(['mediator'],function (mediator){
 		window.observe("focusin", focusCallback);
 		window.observe("focusout", blurCallback);
 	}
+
+	mediator.on('window:title',exports.setTitle);
  
 	return exports;
+
 });
\ No newline at end of file