cryptalk/public/js/cryptalk_modules/cryptalk.js

549 lines
14 KiB
JavaScript
Raw Normal View History

2014-09-21 13:53:57 -04:00
// Main cryptalk module
define({
2014-09-18 12:21:07 -04:00
compiles: ['$'],
2014-09-24 11:20:20 -04:00
requires: ['mediator','hosts', 'templates', 'audio', 'fandango','notifications', 'sounds', 'win']
2014-09-18 12:21:07 -04:00
}, function ($, requires, data) {
var socket,
key,
host,
2014-09-18 12:21:07 -04:00
room,
hash,
2014-09-18 13:17:41 -04:00
nick,
2014-09-23 13:54:36 -04:00
mute = false,
2014-09-21 08:55:24 -04:00
settings = {},
2014-09-21 08:55:24 -04:00
2014-09-21 08:00:55 -04:00
history = [],
history_pos = -1,
history_keep = 4,
2014-09-21 08:20:25 -04:00
history_timer,
2014-09-18 12:21:07 -04:00
// Collection of DOM components
components = {
chat: $('#chat'),
input: $('#input'),
inputWrapper: $('#input_wrapper')
2014-09-18 12:21:07 -04:00
},
// Shortcut
2014-09-23 12:41:59 -04:00
hosts = requires.hosts,
2014-09-22 16:11:13 -04:00
fandango = requires.fandango,
2014-09-24 11:20:20 -04:00
mediator = requires.mediator,
2014-09-22 16:11:13 -04:00
templates = requires.templates,
2014-09-23 13:54:36 -04:00
sounds = requires.sounds,
2014-09-23 14:16:02 -04:00
win = requires.win,
2014-09-18 12:21:07 -04:00
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();
},
2014-09-22 16:11:13 -04:00
showNotification = function (type, nick, text) {
2014-09-23 13:54:36 -04:00
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
2014-09-24 11:20:20 -04:00
mediator.emit('notification:send',
2014-09-23 13:54:36 -04:00
{
title: title.substring(0, 20),
body: text.substring(0, 80),
icon: icon
});
// Emit sound
2014-09-24 11:20:20 -04:00
if ( type == 'message' ) mediator.emit('audio:play',sounds.message);
2014-09-23 13:54:36 -04:00
2014-09-22 16:11:13 -04:00
},
2014-09-18 12:21:07 -04:00
// Adds a new message to the DOM
2014-09-18 13:17:41 -04:00
post = function (type, text, clearChat, clearBuffer, nick) {
2014-09-22 16:11:13 -04:00
2014-09-18 12:21:07 -04:00
var tpl = templates.post[type],
post,
data = fandango.merge({}, settings, {
nick: nick,
2014-09-23 13:54:36 -04:00
room: room
2014-09-18 12:21:07 -04:00
});
data.text = $.template(text, data);
post = $.template(tpl, data);
2014-09-18 12:21:07 -04:00
// Always clear the input after a post
if (clearBuffer) {
2014-09-21 10:33:52 -04:00
clearInput();
2014-09-18 12:21:07 -04:00
}
2014-09-24 11:20:20 -04:00
showNotification(type, nick, text);
2014-09-22 16:11:13 -04:00
2014-09-18 12:21:07 -04:00
// Append the post to the chat DOM element
components.chat[clearChat ? 'html' : 'append'](post);
},
// Chat related commands
commands = {
help: function (payload, done) {
2014-09-18 15:38:15 -04:00
post('motd', templates.help);
done();
},
host: function () {
post('info', JSON.stringify(host || {}));
},
hosts: function (force, done) {
var i = 0,
2014-09-23 12:41:59 -04:00
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();
}
2014-09-24 11:20:20 -04:00
};
};
//
force = (force && force.toLowerCase() === 'force');
// Loop through all the hosts
2014-09-24 11:20:20 -04:00
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();
return post('error', $.template(templates.messages.already_connected, {
host: host.name || 'localhost'
}));
}
if ($.isDigits(toHost)) {
2014-09-24 11:20:20 -04:00
if ((host = hosts.hosts[+toHost])) {
if (host.settings) {
settings = host.settings;
} else {
request = host.path;
}
} else {
return post('error', 'Undefined host index: ' + toHost);
}
} 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 () {
return post('error', 'Could not fetch host settings: ' + request);
});
}
// 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
});
// Bind socket events
socket
.on('room:generated', function (data) {
var sanitized = $.escapeHtml(data);
post('server', $.template(templates.server.room_generated, { payload: sanitized }));
socket.emit('room:join', sanitized);
})
.on('room:joined', function (data) {
room = data;
post('info', $.template(templates.messages.joined_room, { roomName: room }));
// Automatically count persons on join
socket.emit('room:count');
})
.on('room:left', function () {
post('info', $.template(templates.messages.left_room, { roomName: room }));
// Clear history on leaving room
clearHistory();
room = false;
})
.on('message:send', function (data) {
var decrypted = $.AES.decrypt(data.msg, room + key),
sanitized = $.escapeHtml(decrypted),
2014-09-24 11:20:20 -04:00
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'
}));
})
.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) {
2014-09-24 11:20:20 -04:00
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();
2014-09-18 12:21:07 -04:00
},
clear: function () {
components.chat.html('');
2014-09-21 08:00:55 -04:00
// Clear command history on clearing buffer
2014-09-21 08:20:25 -04:00
clearHistory();
2014-09-18 12:21:07 -04:00
},
leave: function () {
2014-09-21 13:53:57 -04:00
if (room) {
2014-09-18 13:17:41 -04:00
socket.emit('room:leave', room);
} else {
post('error', templates.messages.leave_from_nowhere);
}
2014-09-18 12:21:07 -04:00
},
2014-09-19 13:25:16 -04:00
count: function () {
2014-09-21 13:53:57 -04:00
if (room) {
2014-09-19 13:25:16 -04:00
socket.emit('room:count');
} else {
post('error', templates.messages.not_in_room);
}
},
2014-09-18 12:21:07 -04:00
key: function (payload) {
if (!host) {
return post('error', templates.messages.key_no_host);
}
2014-09-18 12:21:07 -04:00
// Make sure the key meets the length requirements
if (payload.length > settings.key_maxLen) {
return post('error', templates.messages.key_to_long);
} else if (payload.length < settings.key_minLen) {
return post('error', templates.messages.key_to_short);
2014-09-18 12:21:07 -04:00
}
// 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));
},
2014-09-18 13:17:41 -04:00
nick: function (payload) {
// Make sure the key meets the length requirements
if (payload.length > settings.nick_maxLen) {
return post('error', templates.messages.nick_to_long);
} else if (payload.length < settings.nick_minLen) {
return post('error', templates.messages.nick_to_short);
2014-09-18 13:17:41 -04:00
}
// Set nick
nick = payload;
// Inform that the key has been set
post('info', $.template(templates.messages.nick_set, { nick: nick}));
},
2014-09-20 09:26:43 -04:00
mute: function () {
2014-09-23 13:54:36 -04:00
mute = true;
2014-09-23 14:16:02 -04:00
return post('info', templates.messages.muted);
2014-09-23 13:54:36 -04:00
},
2014-09-20 09:26:43 -04:00
2014-09-23 13:54:36 -04:00
unmute: function () {
mute = false;
2014-09-23 14:16:02 -04:00
return post('info', templates.messages.unmuted);
},
title: function (payload) {
win.setTitle(payload);
return post('info', $.template(templates.messages.title_set, { title: payload}));
2014-09-20 09:26:43 -04:00
},
2014-09-18 12:21:07 -04:00
join: function (payload) {
if (!host) {
return post('error', templates.messages.join_no_host);
}
2014-09-18 12:21:07 -04:00
return (
room
? post('error', templates.messages.already_in_room)
2014-09-18 12:21:07 -04:00
: socket.emit('room:join', payload)
);
},
2014-09-18 13:17:41 -04:00
generate: function (payload) {
return (
room
? post('error', templates.messages.already_in_room)
2014-09-18 13:17:41 -04:00
: socket.emit('room:generate')
);
2014-09-18 12:21:07 -04:00
}
},
2014-09-21 08:20:25 -04:00
// Push input buffer to history
2014-09-21 08:00:55 -04:00
pushHistory = function (b) {
history.push(b);
// Shift oldest buffer if we have more than we should keep
2014-09-21 13:53:57 -04:00
if (history.length > history_keep) {
history.shift();
}
2014-09-21 08:00:55 -04:00
},
2014-09-21 08:20:25 -04:00
// Clear input buffer history
clearHistory = function() {
history = [];
history_pos = -1;
},
2014-09-21 10:33:52 -04:00
// Clear input buffer
clearInput = function() {
2014-09-21 13:53:57 -04:00
fandango.subordinate(function () {
components.input[0].value = '';
});
2014-09-21 10:33:52 -04:00
},
2014-09-21 08:00:55 -04:00
2014-09-18 12:21:07 -04:00
// Handler for the document`s keyDown-event.
onKeyDown = function (e) {
var buffer,
parts,
payload,
2014-09-21 08:00:55 -04:00
command,
save;
2014-09-18 12:21:07 -04:00
// The Document object is bound to this element.
// If the active element is not the input, focus on it and exit the function.
2014-09-21 08:55:24 -04:00
// Ignore this when ctrl and/or alt is pressed!
if (!e.ctrlKey && !e.altKey && components.input[0] !== $.activeElement()) {
2014-09-18 12:21:07 -04:00
return components.input.focus();
}
2014-09-21 08:20:25 -04:00
// Reset command history clear timer
clearTimeout(history_timer);
2014-09-21 13:53:57 -04:00
history_timer = setTimeout(clearHistory, 60000);
2014-09-21 08:20:25 -04:00
2014-09-21 08:00:55 -04:00
// Check for escape key, this does nothing but clear the input buffer and reset history position
if (e.keyCode == 27) {
2014-09-21 08:00:55 -04:00
history_pos = -1;
2014-09-21 10:33:52 -04:00
clearInput();
return;
2014-09-21 08:00:55 -04:00
}
// Check for up or down-keys, they handle the history position
if (e.keyCode == 38 || e.keyCode == 40) {
2014-09-21 08:00:55 -04:00
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; }
2014-09-21 08:55:24 -04:00
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;
}
2014-09-21 08:00:55 -04:00
2014-09-18 12:21:07 -04:00
// 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;
}
2014-09-21 08:00:55 -04:00
// Reset current history position to 0 (last command)
history_pos = -1;
2014-09-18 12:21:07 -04:00
// Handle command
if ((buffer[0] || buffer.slice(0, 1)) === '/') {
2014-09-18 12:21:07 -04:00
parts = $.ssplit(buffer.slice(1), ' ');
command = parts[0];
payload = parts[1];
// Check that there is an handler for this command
if (!commands[command]) {
2014-09-21 08:00:55 -04:00
pushHistory(buffer);
2014-09-18 12:21:07 -04:00
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);
}
2014-09-18 15:38:15 -04:00
// Clear input field
2014-09-21 10:33:52 -04:00
clearInput();
2014-09-18 15:38:15 -04:00
2014-09-21 08:00:55 -04:00
// Save to history
if(command !== 'key') {
pushHistory(buffer);
}
2014-09-18 12:21:07 -04:00
} else /* Handle ordinary message */ {
2014-09-21 08:00:55 -04:00
if (!room || !key) {
// Push buffer to history and clear input field
2014-09-21 10:33:52 -04:00
pushHistory(buffer); clearInput();
2014-09-18 12:21:07 -04:00
2014-09-21 08:00:55 -04:00
// 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);
2014-09-18 12:21:07 -04:00
}
// Before sending the message.
// Encrypt message using room UUID as salt and key as pepper.
socket.emit('message:send', {
room: room,
2014-09-18 13:17:41 -04:00
msg: $.AES.encrypt(buffer, room + key).toString(),
2014-09-24 11:20:20 -04:00
nick: nick ? $.AES.encrypt(nick, room + key).toString() : false
2014-09-18 12:21:07 -04:00
});
2014-09-21 08:00:55 -04:00
// And clear the the buffer
2014-09-21 10:33:52 -04:00
clearInput();
2014-09-21 08:00:55 -04:00
// Save to history
pushHistory(buffer);
2014-09-18 12:21:07 -04:00
}
2014-09-21 14:45:20 -04:00
};
2014-09-21 08:00:55 -04:00
// Bind the necessary DOM events
$(document).on('keydown', onKeyDown);
// Put focus on the message input
components.input.focus();
2014-09-21 14:45:20 -04:00
// Post the help/welcome message
post('motd', templates.motd, true);
2014-09-21 08:00:55 -04:00
2014-09-23 13:54:36 -04:00
// Route mediator messages
2014-09-24 11:20:20 -04:00
mediator.on('window:focused',function() {
mediator.emit('audio:off');
mediator.emit('notification:off');
2014-09-23 13:54:36 -04:00
});
2014-09-24 11:20:20 -04:00
mediator.on('window:blurred',function() {
if( !mute ) mediator.emit('audio:on');
mediator.emit('notification:on');
2014-09-23 13:54:36 -04:00
});
2014-09-18 12:21:07 -04:00
2014-09-23 13:54:36 -04:00
unlockInput();
2014-09-22 16:11:13 -04:00
// 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.
2014-09-23 12:41:59 -04:00
commands.connect(hosts.autoconnect, function() {
2014-09-22 16:11:13 -04:00
if (host && (hash = window.location.hash)) {
parts = hash.slice(1).split(':');
parts[0] && commands.join(parts[0]);
parts[1] && commands.key(parts[1]);
}
});
2014-09-21 14:45:20 -04:00
2014-09-18 12:21:07 -04:00
});