Major move towards castrato awareness

This commit is contained in:
Hexagon 2014-09-26 14:46:35 +02:00
parent 507df4ea58
commit 4f4f7c4325
12 changed files with 706 additions and 763 deletions

View File

@ -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 );
});

View File

@ -0,0 +1,3 @@
// Licenced under MIT - castrato - ©2014 Pehr Boman <github.com/unkelpehr>
(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<b.length];(l[a]||(l[a]=[])).push(d);if(m[a])for(e=0,d=[d];(b=m[a][e++])&&(k(0,0,b[0],b[1],d),!c););}function g(e,a,b){var c=0;if(toSubs=l[a])for(;a=toSubs[c++];)a[0]!==e||b&&b!==a[1]||toSubs.splice(--c,1)}function k(e,a,b,c,d){var g=d||l[a]||[],h=g.length,f=h,k=h,n=[],p;if(k)for(p=c?function(a){a&&n.push(a);--f||(c(n,h),c=0)}:
q;d=g[--k];)d[1](b,d[2]?p:f--);!f&&c&&c(n,h);e&&(m[a]||(m[a]=[])).push([b,c])}var r=0,l={},m={},q=function(){};return function(){var e=r++;return{emit:function(a,b,c,d){!0!==a&&!1!==a&&(d=c,c=b,b=a,a=!1);k(a,b,c,d);return this},on:function(a,b){f(e,a,b);return this},once:function(a,b){f(e,a,function d(f,h){g(e,a,d);b(f,1<b.length?h:h())},!0);return this},off:function(a,b){g(e,a,b);return this}}}}());

View File

@ -0,0 +1,85 @@
/*
Accepts:
mediator.on('command:help', ...);
mediator.on('command:nick', ...);
mediator.on('command:key', ...);
mediator.on('command:clear', ...);
Emits:
mediator.emit('nick:changed',...);
mediator.emit('key:changed',...);
mediator.emit('console:clear',...);
mediator.emit('console:info',...);
mediator.emit('console:error',...);
*/
define(
{
compiles: ['$'],
requires: ['castrato','settings','templates']
}, function ($, requires, data) {
var
// Private properties
nick,
key,
// Require shortcuts
mediator = requires.castrato,
settings = requires.settings,
templates = requires.templates,
setKey = 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 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);
});

View File

@ -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 <enter>
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);
});

View File

@ -1,553 +1,48 @@
// 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 <enter>
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);
// 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});
// 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)) {
});
// 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] && commands.join(parts[0]);
parts[1] && commands.key(parts[1]);
parts[0] && mediator.emit('command:join',parts[0]);
parts[1] && mediator.emit('command:key',parts[1]);
}
});

View File

@ -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);
});

View File

@ -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;
}
};
};
}())));

View File

@ -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,

View File

@ -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);
});

View File

@ -1,8 +1,23 @@
define({
client: {
title: "Cryptalk - Online"
},
title: "Cryptalk - Online",
motd: '<pre>\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' +
'----------------------------------------------------------------------' +
'</pre>',
nick: {
maxLen: 20,
@ -16,6 +31,6 @@ define({
notifications: {
maxOnePerMs: 3000
}
},
});

View File

@ -1,22 +1,6 @@
// The templating function only supports variables.
// Define a variable as so: {variable_name}
define({
motd: '<pre>\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' +
'----------------------------------------------------------------------' +
'</pre>',
help: '<pre> \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.',

View File

@ -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 = {},
@ -35,5 +37,8 @@ define(['mediator'],function (mediator){
window.observe("focusout", blurCallback);
}
mediator.on('window:title',exports.setTitle);
return exports;
});