Add JSCS and JSHint to tests and fix errors

This commit is contained in:
Alan Friedman 2016-02-21 10:58:42 -05:00
parent 4338f1aa8a
commit cfa5da5695
14 changed files with 183 additions and 140 deletions

3
.babelrc Normal file
View File

@ -0,0 +1,3 @@
{
"presets": ["es2015"]
}

8
.jscsrc Normal file
View File

@ -0,0 +1,8 @@
{
"preset": "google",
"esnext": true,
"requireCamelCaseOrUpperCaseIdentifiers": {"ignoreProperties": true},
"maxErrors": null,
"maximumLineLength": null,
"excludeFiles": ["src/public"]
}

2
.jshintignore Normal file
View File

@ -0,0 +1,2 @@
node_modules
src/public

3
.jshintrc Normal file
View File

@ -0,0 +1,3 @@
{
"esversion": 6
}

42
gulpfile.babel.js Normal file
View File

@ -0,0 +1,42 @@
import gulp from 'gulp';
import uglify from 'gulp-uglify';
import nodemon from 'gulp-nodemon';
import browserify from 'browserify';
import babel from 'babelify';
import source from 'vinyl-source-stream';
import buffer from 'vinyl-buffer';
import childProcess from 'child_process';
let spawn = childProcess.spawn;
gulp.task('bundle', function() {
return browserify('src/js/main.js', {
debug: true
}).transform(babel.configure({
presets: ['es2015']
})).bundle()
.pipe(source('main.js'))
.pipe(buffer())
.pipe(uglify())
.pipe(gulp.dest('src/public'));
});
gulp.task('start', function() {
nodemon({
script: 'index.js',
ext: 'css js mustache',
ignore: ['src/public/main.js'],
env: {
'NODE_ENV': 'development'
},
tasks: ['bundle']
});
});
gulp.task('test', function() {
let test = spawn(
'mocha',
['test', '--compilers', 'js:babel-core/register'],
{stdio: 'inherit'}
);
});

View File

@ -1,29 +0,0 @@
var gulp = require('gulp');
var uglify = require('gulp-uglify');
var nodemon = require('gulp-nodemon');
var browserify = require('browserify');
var babel = require('babelify');
var source = require('vinyl-source-stream');
var buffer = require('vinyl-buffer');
gulp.task('bundle', function() {
return browserify('src/js/main.js', { debug: true }).transform(babel.configure({
presets: ["es2015"]
})).bundle()
.pipe(source('main.js'))
.pipe(buffer())
.pipe(uglify())
.pipe(gulp.dest('src/public'))
});
gulp.task('start', function() {
nodemon({
script: 'index.js',
ext: 'css js mustache',
ignore: ['src/public/main.js'],
env: {
'NODE_ENV': 'development'
},
tasks: ['bundle']
})
});

View File

@ -1,3 +1,2 @@
require('babel-register')();
require('babel/register'); require('./src/app.js');
require('./src/app.js');

View File

@ -19,17 +19,19 @@
"uuid": "^2.0.1" "uuid": "^2.0.1"
}, },
"devDependencies": { "devDependencies": {
"babel-core": "^6.5.2",
"babel-preset-es2015": "^6.3.13", "babel-preset-es2015": "^6.3.13",
"babel-register": "^6.5.2",
"compression": "^1.6.0", "compression": "^1.6.0",
"gulp": "^3.9.1", "gulp": "^3.9.1",
"gulp-nodemon": "^2.0.6", "gulp-nodemon": "^2.0.6",
"jscs": "^2.10.1",
"jshint": "^2.9.1",
"mocha-jscs": "^4.2.0",
"mocha-jshint": "^2.3.1",
"vinyl-buffer": "^1.0.0", "vinyl-buffer": "^1.0.0",
"vinyl-source-stream": "^1.1.0" "vinyl-source-stream": "^1.1.0"
}, },
"scripts": {
"start": "gulp start",
"bundle": "gulp bundle"
},
"author": "Daniel Seripap", "author": "Daniel Seripap",
"license": "MIT" "license": "MIT"
} }

View File

@ -3,6 +3,10 @@
Simple encrypted web chat. Powered by [socket.io](http://socket.io) and the [web cryptography API](https://developer.mozilla.org/en-US/docs/Web/API/Window/crypto). Simple encrypted web chat. Powered by [socket.io](http://socket.io) and the [web cryptography API](https://developer.mozilla.org/en-US/docs/Web/API/Window/crypto).
### Installation ### Installation
# For es6 compatability, be sure you have the latest stable version of Node JS installed
npm install -g n
n stable
npm install npm install

View File

@ -7,7 +7,8 @@ export default class CryptoUtil {
backdrop: 'static', backdrop: 'static',
show: false, show: false,
keyboard: false keyboard: false
}) });
$('#no-crypto').modal('show'); $('#no-crypto').modal('show');
return; return;
} }
@ -28,7 +29,7 @@ export default class CryptoUtil {
} }
convertArrayBufferViewToString(buffer) { convertArrayBufferViewToString(buffer) {
let str = ""; let str = '';
for (let i = 0; i < buffer.byteLength; i++) { for (let i = 0; i < buffer.byteLength; i++) {
str += String.fromCharCode(buffer[i]); str += String.fromCharCode(buffer[i]);
} }
@ -36,39 +37,39 @@ export default class CryptoUtil {
return str; return str;
} }
createSigningKey() { createSigningKey() {
return this._crypto.subtle.generateKey( return this._crypto.subtle.generateKey(
{ {
name: "HMAC", name: 'HMAC',
hash: {name: "SHA-256"}, //can be "SHA-1", "SHA-256", "SHA-384", or "SHA-512" hash: {name: 'SHA-256'}, //can be 'SHA-1', 'SHA-256', 'SHA-384', or 'SHA-512'
//length: 256, //optional, if you want your key length to differ from the hash function's block length //length: 256, //optional, if you want your key length to differ from the hash function's block length
}, },
true, //whether the key is extractable (i.e. can be used in exportKey) true, //whether the key is extractable (i.e. can be used in exportKey)
["sign", "verify"] //can be any combination of "sign" and "verify" ['sign', 'verify'] //can be any combination of 'sign' and 'verify'
); );
} }
createPrimaryKeys() { createPrimaryKeys() {
return this._crypto.subtle.generateKey( return this._crypto.subtle.generateKey(
{ {
name: "RSA-OAEP", name: 'RSA-OAEP',
modulusLength: 2048, //can be 1024, 2048, or 4096 modulusLength: 2048, //can be 1024, 2048, or 4096
publicExponent: new Uint8Array([0x01, 0x00, 0x01]), publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
hash: {name: "SHA-256"}, //can be "SHA-1", "SHA-256", "SHA-384", or "SHA-512" hash: {name: 'SHA-256'}, //can be 'SHA-1', 'SHA-256', 'SHA-384', or 'SHA-512'
}, },
true, //whether the key is extractable (i.e. can be used in exportKey) true, //whether the key is extractable (i.e. can be used in exportKey)
["encrypt", "decrypt"] //must be ["encrypt", "decrypt"] or ["wrapKey", "unwrapKey"] ['encrypt', 'decrypt'] //must be ['encrypt', 'decrypt'] or ['wrapKey', 'unwrapKey']
); );
} }
createSecretKey() { createSecretKey() {
return this._crypto.subtle.generateKey( return this._crypto.subtle.generateKey(
{ {
name: "AES-CBC", name: 'AES-CBC',
length: 256, //can be 128, 192, or 256 length: 256, //can be 128, 192, or 256
}, },
true, //whether the key is extractable (i.e. can be used in exportKey) true, //whether the key is extractable (i.e. can be used in exportKey)
["encrypt", "decrypt"] //can be "encrypt", "decrypt", "wrapKey", or "unwrapKey" ['encrypt', 'decrypt'] //can be 'encrypt', 'decrypt', 'wrapKey', or 'unwrapKey'
); );
} }
@ -76,24 +77,24 @@ export default class CryptoUtil {
// Secret key will be recipient's public key // Secret key will be recipient's public key
return this._crypto.subtle.encrypt( return this._crypto.subtle.encrypt(
{ {
name: "RSA-OAEP", name: 'RSA-OAEP',
modulusLength: 2048, modulusLength: 2048,
publicExponent: new Uint8Array([0x01, 0x00, 0x01]), publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
hash: {name: "SHA-256"} hash: {name: 'SHA-256'}
}, },
secretKey, secretKey,
data //ArrayBuffer of data you want to encrypt data //ArrayBuffer of data you want to encrypt
); );
} }
decryptSecretKey(data, key) { decryptSecretKey(data, key) {
// key will be my private key // key will be my private key
return this._crypto.subtle.decrypt( return this._crypto.subtle.decrypt(
{ {
name: "RSA-OAEP", name: 'RSA-OAEP',
modulusLength: 2048, modulusLength: 2048,
publicExponent: new Uint8Array([0x01, 0x00, 0x01]), publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
hash: {name: "SHA-256"} hash: {name: 'SHA-256'}
//label: Uint8Array([...]) //optional //label: Uint8Array([...]) //optional
}, },
key, key,
@ -105,24 +106,24 @@ export default class CryptoUtil {
// Secret key will be recipient's public key // Secret key will be recipient's public key
return this._crypto.subtle.encrypt( return this._crypto.subtle.encrypt(
{ {
name: "RSA-OAEP", name: 'RSA-OAEP',
modulusLength: 2048, modulusLength: 2048,
publicExponent: new Uint8Array([0x01, 0x00, 0x01]), publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
hash: {name: "SHA-256"} hash: {name: 'SHA-256'}
}, },
signingKey, signingKey,
data //ArrayBuffer of data you want to encrypt data //ArrayBuffer of data you want to encrypt
); );
} }
decryptSigningKey(data, key) { decryptSigningKey(data, key) {
// key will be my private key // key will be my private key
return this._crypto.subtle.decrypt( return this._crypto.subtle.decrypt(
{ {
name: "RSA-OAEP", name: 'RSA-OAEP',
modulusLength: 2048, modulusLength: 2048,
publicExponent: new Uint8Array([0x01, 0x00, 0x01]), publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
hash: {name: "SHA-256"} hash: {name: 'SHA-256'}
//label: Uint8Array([...]) //optional //label: Uint8Array([...]) //optional
}, },
key, key,
@ -133,7 +134,7 @@ export default class CryptoUtil {
encryptMessage(data, secretKey, iv) { encryptMessage(data, secretKey, iv) {
return this._crypto.subtle.encrypt( return this._crypto.subtle.encrypt(
{ {
name: "AES-CBC", name: 'AES-CBC',
//Don't re-use initialization vectors! //Don't re-use initialization vectors!
//Always generate a new iv every time your encrypt! //Always generate a new iv every time your encrypt!
iv: iv, iv: iv,
@ -146,65 +147,65 @@ export default class CryptoUtil {
decryptMessage(data, secretKey, iv) { decryptMessage(data, secretKey, iv) {
return this._crypto.subtle.decrypt( return this._crypto.subtle.decrypt(
{ {
name: "AES-CBC", name: 'AES-CBC',
iv: iv, //The initialization vector you used to encrypt iv: iv, //The initialization vector you used to encrypt
}, },
secretKey, //from generateKey or importKey above secretKey, //from generateKey or importKey above
data //ArrayBuffer of the data data //ArrayBuffer of the data
); );
} }
importSecretKey(jwkData, format) { importSecretKey(jwkData, format) {
return this._crypto.subtle.importKey( return this._crypto.subtle.importKey(
format || "jwk", //can be "jwk" or "raw" format || 'jwk', //can be 'jwk' or 'raw'
//this is an example jwk key, "raw" would be an ArrayBuffer //this is an example jwk key, 'raw' would be an ArrayBuffer
jwkData, jwkData,
{ //this is the algorithm options { //this is the algorithm options
name: "AES-CBC", name: 'AES-CBC',
}, },
true, //whether the key is extractable (i.e. can be used in exportKey) true, //whether the key is extractable (i.e. can be used in exportKey)
["encrypt", "decrypt"] //can be "encrypt", "decrypt", "wrapKey", or "unwrapKey" ['encrypt', 'decrypt'] //can be 'encrypt', 'decrypt', 'wrapKey', or 'unwrapKey'
); );
} }
importPrimaryKey(jwkData, format) { importPrimaryKey(jwkData, format) {
// Will be someone's public key // Will be someone's public key
let hashObj = { let hashObj = {
name: "RSA-OAEP" name: 'RSA-OAEP'
}; };
if (!this._crypto.webkitSubtle) { if (!this._crypto.webkitSubtle) {
hashObj.hash = {name: "SHA-256"}; hashObj.hash = {name: 'SHA-256'};
} }
return this._crypto.subtle.importKey( return this._crypto.subtle.importKey(
format || "jwk", //can be "jwk" (public or private), "spki" (public only), or "pkcs8" (private only) format || 'jwk', //can be 'jwk' (public or private), 'spki' (public only), or 'pkcs8' (private only)
jwkData, jwkData,
hashObj, hashObj,
true, //whether the key is extractable (i.e. can be used in exportKey) true, //whether the key is extractable (i.e. can be used in exportKey)
["encrypt"] //"encrypt" or "wrapKey" for public key import or ['encrypt'] //'encrypt' or 'wrapKey' for public key import or
//"decrypt" or "unwrapKey" for private key imports //'decrypt' or 'unwrapKey' for private key imports
); );
} }
exportKey(key, format) { exportKey(key, format) {
// Will be public primary key or public signing key // Will be public primary key or public signing key
return this._crypto.subtle.exportKey( return this._crypto.subtle.exportKey(
format || "jwk", //can be "jwk" (public or private), "spki" (public only), or "pkcs8" (private only) format || 'jwk', //can be 'jwk' (public or private), 'spki' (public only), or 'pkcs8' (private only)
key //can be a publicKey or privateKey, as long as extractable was true key //can be a publicKey or privateKey, as long as extractable was true
); );
} }
importSigningKey(jwkData) { importSigningKey(jwkData) {
return this._crypto.subtle.importKey( return this._crypto.subtle.importKey(
"raw", //can be "jwk" (public or private), "spki" (public only), or "pkcs8" (private only) 'raw', //can be 'jwk' (public or private), 'spki' (public only), or 'pkcs8' (private only)
//this is an example jwk key, other key types are Uint8Array objects //this is an example jwk key, other key types are Uint8Array objects
jwkData, jwkData,
{ //these are the algorithm options { //these are the algorithm options
name: "HMAC", name: 'HMAC',
hash: {name: "SHA-256"}, //can be "SHA-1", "SHA-256", "SHA-384", or "SHA-512" hash: {name: 'SHA-256'}, //can be 'SHA-1', 'SHA-256', 'SHA-384', or 'SHA-512'
//length: 256, //optional, if you want your key length to differ from the hash function's block length //length: 256, //optional, if you want your key length to differ from the hash function's block length
}, },
true, //whether the key is extractable (i.e. can be used in exportKey) true, //whether the key is extractable (i.e. can be used in exportKey)
["verify"] //"verify" for public key import, "sign" for private key imports ['verify'] //'verify' for public key import, 'sign' for private key imports
); );
} }
@ -212,26 +213,25 @@ export default class CryptoUtil {
// Will use my private key // Will use my private key
return this._crypto.subtle.sign( return this._crypto.subtle.sign(
{ {
name: "HMAC", name: 'HMAC',
hash: {name: "SHA-256"} hash: {name: 'SHA-256'}
}, },
keyToSignWith, //from generateKey or importKey above keyToSignWith, //from generateKey or importKey above
data //ArrayBuffer of data you want to sign data //ArrayBuffer of data you want to sign
); );
} }
verifyKey(signature, data, keyToVerifyWith) { verifyKey(signature, data, keyToVerifyWith) {
// Will verify with sender's public key // Will verify with sender's public key
return this._crypto.subtle.verify( return this._crypto.subtle.verify(
{ {
name: "HMAC", name: 'HMAC',
hash: {name: "SHA-256"} hash: {name: 'SHA-256'}
}, },
keyToVerifyWith, //from generateKey or importKey above keyToVerifyWith, //from generateKey or importKey above
signature, //ArrayBuffer of the signature signature, //ArrayBuffer of the signature
data //ArrayBuffer of the data data //ArrayBuffer of the data
); );
} }
} }

View File

@ -17,7 +17,7 @@ $(function() {
'#e21400', '#ffe400', '#ff8f00', '#e21400', '#ffe400', '#ff8f00',
'#58dc00', '#dd9cff', '#4ae8c4', '#58dc00', '#dd9cff', '#4ae8c4',
'#3b88eb', '#f47777', '#d300e7', '#3b88eb', '#f47777', '#d300e7',
]; ];
let $window = $(window); let $window = $(window);
let $messages = $('.messages'); // Messages area let $messages = $('.messages'); // Messages area
@ -41,9 +41,9 @@ $(function() {
let keys = {}; let keys = {};
if (!roomId) return; if (!roomId) { return; }
$('input.share-text').val(document.location.protocol + "//" + document.location.host + roomId); $('input.share-text').val(document.location.protocol + '//' + document.location.host + roomId);
$('input.share-text').click(function() { $('input.share-text').click(function() {
$(this).focus(); $(this).focus();
@ -55,7 +55,7 @@ $(function() {
FastClick.attach(document.body); FastClick.attach(document.body);
function addParticipantsMessage (data) { function addParticipantsMessage(data) {
let message = ''; let message = '';
let headerMsg = ''; let headerMsg = '';
@ -63,7 +63,7 @@ $(function() {
} }
// Sets the client's username // Sets the client's username
function initChat () { function initChat() {
username = window.username; username = window.username;
// warn not incognitor // warn not incognitor
if (!fs) { if (!fs) {
@ -71,7 +71,7 @@ $(function() {
} else { } else {
fs(window.TEMPORARY, fs(window.TEMPORARY,
100, 100,
log.bind(log, "WARNING: Your browser is not in incognito mode!")); log.bind(log, 'WARNING: Your browser is not in incognito mode!'));
} }
// If the username is valid // If the username is valid
@ -88,7 +88,7 @@ $(function() {
private: data[0].privateKey private: data[0].privateKey
}; };
return Promise.all([ return Promise.all([
cryptoUtil.exportKey(data[0].publicKey, "spki") cryptoUtil.exportKey(data[0].publicKey, 'spki')
]); ]);
}) })
.then(function(exportedKeys) { .then(function(exportedKeys) {
@ -102,7 +102,7 @@ $(function() {
} }
// Log a message // Log a message
function log (message, options) { function log(message, options) {
let html = options && options.html === true || false; let html = options && options.html === true || false;
let $el; let $el;
if (html) { if (html) {
@ -114,8 +114,10 @@ $(function() {
} }
// Adds the visual chat message to the message list // Adds the visual chat message to the message list
function addChatMessage (data, options) { function addChatMessage(data, options) {
if (!data.message.trim().length) return; if (!data.message.trim().length) {
return;
}
// Don't fade the message in if there is an 'X was typing' // Don't fade the message in if there is an 'X was typing'
let $typingMessages = getTypingMessages(data); let $typingMessages = getTypingMessages(data);
@ -141,15 +143,15 @@ $(function() {
} }
// Adds the visual chat typing message // Adds the visual chat typing message
function addChatTyping (data) { function addChatTyping(data) {
data.typing = true; data.typing = true;
data.message = 'is typing'; data.message = 'is typing';
addChatMessage(data); addChatMessage(data);
} }
// Removes the visual chat typing message // Removes the visual chat typing message
function removeChatTyping (data) { function removeChatTyping(data) {
getTypingMessages(data).fadeOut(function () { getTypingMessages(data).fadeOut(function() {
$(this).remove(); $(this).remove();
}); });
} }
@ -159,7 +161,7 @@ $(function() {
// options.fade - If the element should fade-in (default = true) // options.fade - If the element should fade-in (default = true)
// options.prepend - If the element should prepend // options.prepend - If the element should prepend
// all other messages (default = false) // all other messages (default = false)
function addMessageElement (el, options) { function addMessageElement(el, options) {
let $el = $(el); let $el = $(el);
// Setup default options // Setup default options
@ -187,14 +189,14 @@ $(function() {
} }
// Prevents input from having injected markup // Prevents input from having injected markup
function cleanInput (input) { function cleanInput(input) {
let message = $('<div/>').html(input).text(); let message = $('<div/>').html(input).text();
message = Autolinker.link(message); message = Autolinker.link(message);
return message; return message;
} }
// Updates the typing event // Updates the typing event
function updateTyping () { function updateTyping() {
if (connected) { if (connected) {
if (!typing) { if (!typing) {
typing = true; typing = true;
@ -202,7 +204,7 @@ $(function() {
} }
lastTypingTime = (new Date()).getTime(); lastTypingTime = (new Date()).getTime();
setTimeout(function () { setTimeout(function() {
let typingTimer = (new Date()).getTime(); let typingTimer = (new Date()).getTime();
let timeDiff = typingTimer - lastTypingTime; let timeDiff = typingTimer - lastTypingTime;
if (timeDiff >= TYPING_TIMER_LENGTH && typing) { if (timeDiff >= TYPING_TIMER_LENGTH && typing) {
@ -214,18 +216,18 @@ $(function() {
} }
// Gets the 'X is typing' messages of a user // Gets the 'X is typing' messages of a user
function getTypingMessages (data) { function getTypingMessages(data) {
return $('.typing.message').filter(function (i) { return $('.typing.message').filter(function(i) {
return $(this).data('username') === data.username; return $(this).data('username') === data.username;
}); });
} }
// Gets the color of a username through our hash function // Gets the color of a username through our hash function
function getUsernameColor (username) { function getUsernameColor(username) {
// Compute hash code // Compute hash code
let hash = 7; let hash = 7;
for (let i = 0; i < username.length; i++) { for (let i = 0; i < username.length; i++) {
hash = username.charCodeAt(i) + (hash << 5) - hash; hash = username.charCodeAt(i) + (hash << 5) - hash;
} }
// Calculate color // Calculate color
let index = Math.abs(hash % COLORS.length); let index = Math.abs(hash % COLORS.length);
@ -234,7 +236,7 @@ $(function() {
// Keyboard events // Keyboard events
$window.keydown(function (event) { $window.keydown(function(event) {
// When the client hits ENTER on their keyboard and chat message input is focused // When the client hits ENTER on their keyboard and chat message input is focused
if (event.which === 13 && $('.inputMessage').is(':focus')) { if (event.which === 13 && $('.inputMessage').is(':focus')) {
sendMessage(); sendMessage();
@ -255,8 +257,8 @@ $(function() {
}); });
// Select message input when closing modal // Select message input when closing modal
$('.modal').on('hidden.bs.modal', function (e) { $('.modal').on('hidden.bs.modal', function(e) {
$inputMessage.focus(); $inputMessage.focus();
}); });
// Select message input when clicking message body, unless selecting text // Select message input when clicking message body, unless selecting text
@ -267,29 +269,29 @@ $(function() {
}); });
function getSelectedText() { function getSelectedText() {
var text = ""; var text = '';
if (typeof window.getSelection != "undefined") { if (typeof window.getSelection != 'undefined') {
text = window.getSelection().toString(); text = window.getSelection().toString();
} else if (typeof document.selection != "undefined" && document.selection.type == "Text") { } else if (typeof document.selection != 'undefined' && document.selection.type == 'Text') {
text = document.selection.createRange().text; text = document.selection.createRange().text;
} }
return text; return text;
} }
// Whenever the server emits 'login', log the login message // Whenever the server emits 'login', log the login message
socket.on('user joined', function (data) { socket.on('user joined', function(data) {
connected = true; connected = true;
addParticipantsMessage(data); addParticipantsMessage(data);
let importKeysPromises = []; let importKeysPromises = [];
// Import all user keys if not already there // Import all user keys if not already there
_.each(data.users, function(user) { _.each(data.users, function(user) {
if (!_.findWhere(users, {id: user.id})) { if (!_.findWhere(users, {id: user.id})) {
let promise = new Promise(function(resolve, reject) { let promise = new Promise(function(resolve, reject) {
let currentUser = user; let currentUser = user;
Promise.all([ Promise.all([
cryptoUtil.importPrimaryKey(currentUser.publicKey, "spki") cryptoUtil.importPrimaryKey(currentUser.publicKey, 'spki')
]) ])
.then(function(keys) { .then(function(keys) {
users.push({ users.push({
@ -320,14 +322,16 @@ $(function() {
log(data.username + ' joined'); log(data.username + ' joined');
renderParticipantsList(); renderParticipantsList();
}); });
}); });
// Sends a chat message // Sends a chat message
function sendMessage () { function sendMessage() {
// Don't send unless other users exist // Don't send unless other users exist
if (users.length <= 1) return; if (users.length <= 1) {
return;
}
let message = $inputMessage.val(); let message = $inputMessage.val();
// Prevent markup from being injected into the message // Prevent markup from being injected into the message
@ -335,7 +339,7 @@ $(function() {
// if there is a non-empty message and a socket connection // if there is a non-empty message and a socket connection
if (message && connected) { if (message && connected) {
$inputMessage.val(''); $inputMessage.val('');
$('#send-message-btn').removeClass('active'); $('#send-message-btn').removeClass('active');
addChatMessage({ addChatMessage({
username: username, username: username,
message: message message: message
@ -368,7 +372,7 @@ $(function() {
let secretKeyStr; let secretKeyStr;
// Export secret key // Export secret key
cryptoUtil.exportKey(secretKey, "raw") cryptoUtil.exportKey(secretKey, 'raw')
.then(function(data) { .then(function(data) {
return cryptoUtil.encryptSecretKey(data, thisUser.publicKey); return cryptoUtil.encryptSecretKey(data, thisUser.publicKey);
}) })
@ -376,7 +380,7 @@ $(function() {
let encData = new Uint8Array(encryptedSecretKey); let encData = new Uint8Array(encryptedSecretKey);
secretKeyStr = cryptoUtil.convertArrayBufferViewToString(encData); secretKeyStr = cryptoUtil.convertArrayBufferViewToString(encData);
// Export HMAC signing key // Export HMAC signing key
return cryptoUtil.exportKey(signingKey, "raw"); return cryptoUtil.exportKey(signingKey, 'raw');
}) })
.then(function(data) { .then(function(data) {
// Encrypt signing key with user's public key // Encrypt signing key with user's public key
@ -422,7 +426,7 @@ $(function() {
} }
// Whenever the server emits 'new message', update the chat body // Whenever the server emits 'new message', update the chat body
socket.on('new message', function (data) { socket.on('new message', function(data) {
// Don't show messages if no key // Don't show messages if no key
if (!windowHandler.isActive) { if (!windowHandler.isActive) {
windowHandler.notifyFavicon(); windowHandler.notifyFavicon();
@ -431,13 +435,13 @@ $(function() {
let message = data.message; let message = data.message;
let messageData = cryptoUtil.convertStringToArrayBufferView(message); let messageData = cryptoUtil.convertStringToArrayBufferView(message);
let username = data.username; let username = data.username;
let senderId = data.id let senderId = data.id;
let vector = data.vector; let vector = data.vector;
let vectorData = cryptoUtil.convertStringToArrayBufferView(vector); let vectorData = cryptoUtil.convertStringToArrayBufferView(vector);
let secretKeys = data.secretKeys; let secretKeys = data.secretKeys;
let decryptedMessageData; let decryptedMessageData;
let decryptedMessage; let decryptedMessage;
let mySecretKey = _.find(secretKeys, function(key) { let mySecretKey = _.find(secretKeys, function(key) {
return key.id === myUserId; return key.id === myUserId;
@ -449,7 +453,7 @@ $(function() {
cryptoUtil.decryptSecretKey(secretKeyArrayBuffer, keys.private) cryptoUtil.decryptSecretKey(secretKeyArrayBuffer, keys.private)
.then(function(data) { .then(function(data) {
return cryptoUtil.importSecretKey(new Uint8Array(data), "raw"); return cryptoUtil.importSecretKey(new Uint8Array(data), 'raw');
}) })
.then(function(data) { .then(function(data) {
let secretKey = data; let secretKey = data;
@ -457,11 +461,11 @@ $(function() {
}) })
.then(function(data) { .then(function(data) {
decryptedMessageData = data; decryptedMessageData = data;
decryptedMessage = cryptoUtil.convertArrayBufferViewToString(new Uint8Array(data)) decryptedMessage = cryptoUtil.convertArrayBufferViewToString(new Uint8Array(data));
return cryptoUtil.decryptSigningKey(signingKeyArrayBuffer, keys.private) return cryptoUtil.decryptSigningKey(signingKeyArrayBuffer, keys.private);
}) })
.then(function(data) { .then(function(data) {
return cryptoUtil.importSigningKey(new Uint8Array(data), "raw"); return cryptoUtil.importSigningKey(new Uint8Array(data), 'raw');
}) })
.then(function(data) { .then(function(data) {
let signingKey = data; let signingKey = data;
@ -472,13 +476,13 @@ $(function() {
addChatMessage({ addChatMessage({
username: username, username: username,
message: decryptedMessage message: decryptedMessage
}); });
} }
}); });
}); });
// Whenever the server emits 'user left', log it in the chat body // Whenever the server emits 'user left', log it in the chat body
socket.on('user left', function (data) { socket.on('user left', function(data) {
log(data.username + ' left'); log(data.username + ' left');
addParticipantsMessage(data); addParticipantsMessage(data);
removeChatTyping(data); removeChatTyping(data);
@ -489,12 +493,12 @@ $(function() {
}); });
// Whenever the server emits 'typing', show the typing message // Whenever the server emits 'typing', show the typing message
socket.on('typing', function (data) { socket.on('typing', function(data) {
addChatTyping(data); addChatTyping(data);
}); });
// Whenever the server emits 'stop typing', kill the typing message // Whenever the server emits 'stop typing', kill the typing message
socket.on('stop typing', function (data) { socket.on('stop typing', function(data) {
removeChatTyping(data); removeChatTyping(data);
}); });
@ -522,13 +526,13 @@ $(function() {
let li; let li;
if (user.username === window.username) { if (user.username === window.username) {
// User is me // User is me
li = $("<li>" + user.username + " <span class='you'>(you)</span></li>").css('color', getUsernameColor(user.username)); li = $('<li>' + user.username + ' <span class="you">(you)</span></li>').css('color', getUsernameColor(user.username));
} else { } else {
li = $("<li>" + user.username + "</li>").css('color', getUsernameColor(user.username)); li = $('<li>' + user.username + '</li>').css('color', getUsernameColor(user.username));
} }
$('#participants-modal ul.users') $('#participants-modal ul.users')
.append(li); .append(li);
}); });
} }
$('#send-message-btn').click(function() { $('#send-message-btn').click(function() {

View File

@ -4,8 +4,8 @@ export default class WindowHandler {
this.newMessages = 0; this.newMessages = 0;
this.favicon = new Favico({ this.favicon = new Favico({
animation:'pop', animation: 'pop',
type : 'rectangle' type: 'rectangle'
}); });
this.bindEvents(); this.bindEvents();
@ -26,13 +26,13 @@ export default class WindowHandler {
} }
bindEvents() { bindEvents() {
window.onfocus = () => { window.onfocus = () => {
this._isActive = true; this._isActive = true;
this.newMessages = 0; this.newMessages = 0;
this.favicon.reset(); this.favicon.reset();
}; };
window.onblur = () => { window.onblur = () => {
this._isActive = false; this._isActive = false;
}; };
} }

View File

@ -29,7 +29,7 @@ class Room {
}); });
socket.on('add user', (data) => { socket.on('add user', (data) => {
if (addedUser) return; if (addedUser) { return; }
data.id = uuid.v4(); data.id = uuid.v4();
this.users.push(data); this.users.push(data);

5
test/app.js Normal file
View File

@ -0,0 +1,5 @@
import mochaJSCS from 'mocha-jscs';
import mochaJSHint from 'mocha-jshint';
mochaJSCS();
mochaJSHint();