307 lines
8.2 KiB
JavaScript
307 lines
8.2 KiB
JavaScript
// Main cryptalk module. Will be called by bootstrap.js when the DOM is ready to interact with.
|
|
define('cryptalk', {
|
|
data: {
|
|
// If no host is given it will default to localhost.
|
|
host: ''
|
|
},
|
|
compiles: ['$'],
|
|
requires: ['templates','sound']
|
|
}, function ($, requires, data) {
|
|
var socket,
|
|
key,
|
|
room,
|
|
hash,
|
|
nick,
|
|
mute = false,
|
|
history = [],
|
|
history_pos = -1,
|
|
history_keep = 4,
|
|
|
|
// Collection of DOM components
|
|
components = {
|
|
chat: $('#chat'),
|
|
input: $('#input')
|
|
},
|
|
|
|
// Shortcut
|
|
templates = requires.templates;
|
|
sound = requires.sound;
|
|
|
|
// Adds a new message to the DOM
|
|
post = function (type, text, clearChat, clearBuffer, nick) {
|
|
var tpl = templates.post[type],
|
|
post = $.template(tpl, text && {
|
|
text: text,
|
|
nick: nick
|
|
});
|
|
|
|
// Always clear the input after a post
|
|
if (clearBuffer) {
|
|
components.input[0].value = '';
|
|
}
|
|
|
|
// Append the post to the chat DOM element
|
|
components.chat[clearChat ? 'html' : 'append'](post);
|
|
},
|
|
|
|
// Chat related commands
|
|
commands = {
|
|
help: function () {
|
|
post('motd', templates.help);
|
|
},
|
|
|
|
clear: function () {
|
|
components.chat.html('');
|
|
|
|
// Clear command history on clearing buffer
|
|
history = []; history_pos = -1;
|
|
},
|
|
|
|
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) {
|
|
// Make sure the key meets the length requirements
|
|
if (payload.length < 8) {
|
|
return post('error', templates.messages.key_weak);
|
|
}
|
|
|
|
// Set key
|
|
key = payload;
|
|
|
|
// Inform that the key has been set
|
|
post('info', (room ? templates.messages.key_ok_ready : templates.messages.key_ok_but_no_room));
|
|
},
|
|
|
|
nick: function (payload) {
|
|
// Make sure the nick meets the length requirements
|
|
if (payload.length < 2) {
|
|
return post('error', templates.messages.nick_short);
|
|
}
|
|
|
|
// Set nick
|
|
nick = payload;
|
|
|
|
// Inform that the key has been set
|
|
post('info', $.template(templates.messages.nick_set, { nick: nick}));
|
|
},
|
|
|
|
mute: function () {
|
|
// Set nick
|
|
mute = !mute;
|
|
|
|
// Inform that the key has been set
|
|
if( mute )
|
|
post('info', $.template(templates.messages.muted ));
|
|
else
|
|
post('info', $.template(templates.messages.unmuted ));
|
|
},
|
|
|
|
join: function (payload) {
|
|
return (
|
|
room
|
|
? post('error', $.template(templates.messages.already_in_room, { roomName: room}))
|
|
: socket.emit('room:join', payload)
|
|
);
|
|
},
|
|
|
|
generate: function (payload) {
|
|
return (
|
|
room
|
|
? post('error', $.template(templates.messages.already_in_room, { roomName: room}))
|
|
: socket.emit('room:generate')
|
|
);
|
|
}
|
|
},
|
|
|
|
// Push input buffer to command 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();
|
|
},
|
|
|
|
// 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.
|
|
if (components.input[0] !== $.activeElement()) {
|
|
return components.input.focus();
|
|
}
|
|
|
|
// Check for escape key, this does nothing but clear the input buffer and reset history position
|
|
if ( e.keyCode == 27 ) {
|
|
history_pos = -1;
|
|
components.input[0].value = '';
|
|
}
|
|
|
|
// 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; }
|
|
|
|
return components.input[0].value = (history_pos == -1) ? '' : history[history.length-1-history_pos];
|
|
}
|
|
|
|
// 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] === '/') {
|
|
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 }));
|
|
}
|
|
|
|
// Execute command handler
|
|
commands[command](payload);
|
|
|
|
// Clear input field
|
|
components.input[0].value = '';
|
|
|
|
// 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); components.input[0].value = '';
|
|
|
|
// 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 && nick != undefined) ? $.AES.encrypt(nick, room + key).toString() : false
|
|
});
|
|
|
|
// And clear the the buffer
|
|
components.input[0].value = '';
|
|
|
|
// Save to history
|
|
pushHistory(buffer);
|
|
}
|
|
|
|
|
|
|
|
};
|
|
|
|
// Connect to server
|
|
socket = $.Websocket.connect(data.host);
|
|
|
|
// Bind socket events
|
|
socket
|
|
.on('connect', function () {
|
|
$(document).on('keydown', onKeyDown);
|
|
components.input.focus();
|
|
})
|
|
|
|
.on('room:generated', function (data) {
|
|
socket.emit('room:join', data);
|
|
})
|
|
|
|
.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
|
|
history = []; history_pos = -1;
|
|
|
|
room = false;
|
|
})
|
|
|
|
.on('message:send', function (data) {
|
|
var decrypted = $.AES.decrypt(data.msg, room + key),
|
|
sanitized = $.escapeHtml(decrypted),
|
|
nick = (data.nick == undefined || !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);
|
|
if( !mute ) sound.playTones(sound.messages.message);
|
|
}
|
|
})
|
|
|
|
.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]);
|
|
}
|
|
|
|
// Play sound
|
|
if (sound.messages[sanitized] !== undefined && !mute ) sound.playTones(sound.messages[sanitized]);
|
|
|
|
} else {
|
|
post('error', templates.server.bogus);
|
|
}
|
|
} else {
|
|
post('error', templates.server.bogus);
|
|
}
|
|
});
|
|
|
|
// Post the help/welcome message
|
|
post('motd', templates.motd, true);
|
|
|
|
// It's possible to provide room and key using the hashtag.
|
|
// The room and key is then seperated by semicolon (room:key).
|
|
// If there is no semicolon present, the complete hash will be treated as the room name and the key has to be set manually.
|
|
if (hash = window.location.hash) {
|
|
parts = hash.slice(1).split(':');
|
|
|
|
parts[0] && commands.join(parts[0]);
|
|
parts[1] && commands.key(parts[1]);
|
|
}
|
|
}); |