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');

View File

@ -19,17 +19,19 @@
"uuid": "^2.0.1"
},
"devDependencies": {
"babel-core": "^6.5.2",
"babel-preset-es2015": "^6.3.13",
"babel-register": "^6.5.2",
"compression": "^1.6.0",
"gulp": "^3.9.1",
"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-source-stream": "^1.1.0"
},
"scripts": {
"start": "gulp start",
"bundle": "gulp bundle"
},
"author": "Daniel Seripap",
"license": "MIT"
}

View File

@ -4,6 +4,10 @@ Simple encrypted web chat. Powered by [socket.io](http://socket.io) and the [web
### Installation
# For es6 compatability, be sure you have the latest stable version of Node JS installed
npm install -g n
n stable
npm install
# Bundle JS files

View File

@ -7,7 +7,8 @@ export default class CryptoUtil {
backdrop: 'static',
show: false,
keyboard: false
})
});
$('#no-crypto').modal('show');
return;
}
@ -28,7 +29,7 @@ export default class CryptoUtil {
}
convertArrayBufferViewToString(buffer) {
let str = "";
let str = '';
for (let i = 0; i < buffer.byteLength; i++) {
str += String.fromCharCode(buffer[i]);
}
@ -39,36 +40,36 @@ export default class CryptoUtil {
createSigningKey() {
return this._crypto.subtle.generateKey(
{
name: "HMAC",
hash: {name: "SHA-256"}, //can be "SHA-1", "SHA-256", "SHA-384", or "SHA-512"
name: 'HMAC',
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
},
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() {
return this._crypto.subtle.generateKey(
{
name: "RSA-OAEP",
name: 'RSA-OAEP',
modulusLength: 2048, //can be 1024, 2048, or 4096
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)
["encrypt", "decrypt"] //must be ["encrypt", "decrypt"] or ["wrapKey", "unwrapKey"]
['encrypt', 'decrypt'] //must be ['encrypt', 'decrypt'] or ['wrapKey', 'unwrapKey']
);
}
createSecretKey() {
return this._crypto.subtle.generateKey(
{
name: "AES-CBC",
name: 'AES-CBC',
length: 256, //can be 128, 192, or 256
},
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,10 +77,10 @@ export default class CryptoUtil {
// Secret key will be recipient's public key
return this._crypto.subtle.encrypt(
{
name: "RSA-OAEP",
name: 'RSA-OAEP',
modulusLength: 2048,
publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
hash: {name: "SHA-256"}
hash: {name: 'SHA-256'}
},
secretKey,
data //ArrayBuffer of data you want to encrypt
@ -90,10 +91,10 @@ export default class CryptoUtil {
// key will be my private key
return this._crypto.subtle.decrypt(
{
name: "RSA-OAEP",
name: 'RSA-OAEP',
modulusLength: 2048,
publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
hash: {name: "SHA-256"}
hash: {name: 'SHA-256'}
//label: Uint8Array([...]) //optional
},
key,
@ -105,10 +106,10 @@ export default class CryptoUtil {
// Secret key will be recipient's public key
return this._crypto.subtle.encrypt(
{
name: "RSA-OAEP",
name: 'RSA-OAEP',
modulusLength: 2048,
publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
hash: {name: "SHA-256"}
hash: {name: 'SHA-256'}
},
signingKey,
data //ArrayBuffer of data you want to encrypt
@ -119,10 +120,10 @@ export default class CryptoUtil {
// key will be my private key
return this._crypto.subtle.decrypt(
{
name: "RSA-OAEP",
name: 'RSA-OAEP',
modulusLength: 2048,
publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
hash: {name: "SHA-256"}
hash: {name: 'SHA-256'}
//label: Uint8Array([...]) //optional
},
key,
@ -133,7 +134,7 @@ export default class CryptoUtil {
encryptMessage(data, secretKey, iv) {
return this._crypto.subtle.encrypt(
{
name: "AES-CBC",
name: 'AES-CBC',
//Don't re-use initialization vectors!
//Always generate a new iv every time your encrypt!
iv: iv,
@ -146,7 +147,7 @@ export default class CryptoUtil {
decryptMessage(data, secretKey, iv) {
return this._crypto.subtle.decrypt(
{
name: "AES-CBC",
name: 'AES-CBC',
iv: iv, //The initialization vector you used to encrypt
},
secretKey, //from generateKey or importKey above
@ -156,55 +157,55 @@ export default class CryptoUtil {
importSecretKey(jwkData, format) {
return this._crypto.subtle.importKey(
format || "jwk", //can be "jwk" or "raw"
//this is an example jwk key, "raw" would be an ArrayBuffer
format || 'jwk', //can be 'jwk' or 'raw'
//this is an example jwk key, 'raw' would be an ArrayBuffer
jwkData,
{ //this is the algorithm options
name: "AES-CBC",
name: 'AES-CBC',
},
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) {
// Will be someone's public key
let hashObj = {
name: "RSA-OAEP"
name: 'RSA-OAEP'
};
if (!this._crypto.webkitSubtle) {
hashObj.hash = {name: "SHA-256"};
hashObj.hash = {name: 'SHA-256'};
}
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,
hashObj,
true, //whether the key is extractable (i.e. can be used in exportKey)
["encrypt"] //"encrypt" or "wrapKey" for public key import or
//"decrypt" or "unwrapKey" for private key imports
['encrypt'] //'encrypt' or 'wrapKey' for public key import or
//'decrypt' or 'unwrapKey' for private key imports
);
}
exportKey(key, format) {
// Will be public primary key or public signing key
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
);
}
importSigningKey(jwkData) {
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
jwkData,
{ //these are the algorithm options
name: "HMAC",
hash: {name: "SHA-256"}, //can be "SHA-1", "SHA-256", "SHA-384", or "SHA-512"
name: 'HMAC',
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
},
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,8 +213,8 @@ export default class CryptoUtil {
// Will use my private key
return this._crypto.subtle.sign(
{
name: "HMAC",
hash: {name: "SHA-256"}
name: 'HMAC',
hash: {name: 'SHA-256'}
},
keyToSignWith, //from generateKey or importKey above
data //ArrayBuffer of data you want to sign
@ -224,8 +225,8 @@ export default class CryptoUtil {
// Will verify with sender's public key
return this._crypto.subtle.verify(
{
name: "HMAC",
hash: {name: "SHA-256"}
name: 'HMAC',
hash: {name: 'SHA-256'}
},
keyToVerifyWith, //from generateKey or importKey above
signature, //ArrayBuffer of the signature
@ -233,5 +234,4 @@ export default class CryptoUtil {
);
}
}

View File

@ -41,9 +41,9 @@ $(function() {
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() {
$(this).focus();
@ -55,7 +55,7 @@ $(function() {
FastClick.attach(document.body);
function addParticipantsMessage (data) {
function addParticipantsMessage(data) {
let message = '';
let headerMsg = '';
@ -63,7 +63,7 @@ $(function() {
}
// Sets the client's username
function initChat () {
function initChat() {
username = window.username;
// warn not incognitor
if (!fs) {
@ -71,7 +71,7 @@ $(function() {
} else {
fs(window.TEMPORARY,
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
@ -88,7 +88,7 @@ $(function() {
private: data[0].privateKey
};
return Promise.all([
cryptoUtil.exportKey(data[0].publicKey, "spki")
cryptoUtil.exportKey(data[0].publicKey, 'spki')
]);
})
.then(function(exportedKeys) {
@ -102,7 +102,7 @@ $(function() {
}
// Log a message
function log (message, options) {
function log(message, options) {
let html = options && options.html === true || false;
let $el;
if (html) {
@ -114,8 +114,10 @@ $(function() {
}
// Adds the visual chat message to the message list
function addChatMessage (data, options) {
if (!data.message.trim().length) return;
function addChatMessage(data, options) {
if (!data.message.trim().length) {
return;
}
// Don't fade the message in if there is an 'X was typing'
let $typingMessages = getTypingMessages(data);
@ -141,15 +143,15 @@ $(function() {
}
// Adds the visual chat typing message
function addChatTyping (data) {
function addChatTyping(data) {
data.typing = true;
data.message = 'is typing';
addChatMessage(data);
}
// Removes the visual chat typing message
function removeChatTyping (data) {
getTypingMessages(data).fadeOut(function () {
function removeChatTyping(data) {
getTypingMessages(data).fadeOut(function() {
$(this).remove();
});
}
@ -159,7 +161,7 @@ $(function() {
// options.fade - If the element should fade-in (default = true)
// options.prepend - If the element should prepend
// all other messages (default = false)
function addMessageElement (el, options) {
function addMessageElement(el, options) {
let $el = $(el);
// Setup default options
@ -187,14 +189,14 @@ $(function() {
}
// Prevents input from having injected markup
function cleanInput (input) {
function cleanInput(input) {
let message = $('<div/>').html(input).text();
message = Autolinker.link(message);
return message;
}
// Updates the typing event
function updateTyping () {
function updateTyping() {
if (connected) {
if (!typing) {
typing = true;
@ -202,7 +204,7 @@ $(function() {
}
lastTypingTime = (new Date()).getTime();
setTimeout(function () {
setTimeout(function() {
let typingTimer = (new Date()).getTime();
let timeDiff = typingTimer - lastTypingTime;
if (timeDiff >= TYPING_TIMER_LENGTH && typing) {
@ -214,14 +216,14 @@ $(function() {
}
// Gets the 'X is typing' messages of a user
function getTypingMessages (data) {
return $('.typing.message').filter(function (i) {
function getTypingMessages(data) {
return $('.typing.message').filter(function(i) {
return $(this).data('username') === data.username;
});
}
// Gets the color of a username through our hash function
function getUsernameColor (username) {
function getUsernameColor(username) {
// Compute hash code
let hash = 7;
for (let i = 0; i < username.length; i++) {
@ -234,7 +236,7 @@ $(function() {
// Keyboard events
$window.keydown(function (event) {
$window.keydown(function(event) {
// When the client hits ENTER on their keyboard and chat message input is focused
if (event.which === 13 && $('.inputMessage').is(':focus')) {
sendMessage();
@ -255,7 +257,7 @@ $(function() {
});
// Select message input when closing modal
$('.modal').on('hidden.bs.modal', function (e) {
$('.modal').on('hidden.bs.modal', function(e) {
$inputMessage.focus();
});
@ -267,17 +269,17 @@ $(function() {
});
function getSelectedText() {
var text = "";
if (typeof window.getSelection != "undefined") {
var text = '';
if (typeof window.getSelection != 'undefined') {
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;
}
return text;
}
// Whenever the server emits 'login', log the login message
socket.on('user joined', function (data) {
socket.on('user joined', function(data) {
connected = true;
addParticipantsMessage(data);
@ -289,7 +291,7 @@ $(function() {
let promise = new Promise(function(resolve, reject) {
let currentUser = user;
Promise.all([
cryptoUtil.importPrimaryKey(currentUser.publicKey, "spki")
cryptoUtil.importPrimaryKey(currentUser.publicKey, 'spki')
])
.then(function(keys) {
users.push({
@ -325,9 +327,11 @@ $(function() {
});
// Sends a chat message
function sendMessage () {
function sendMessage() {
// Don't send unless other users exist
if (users.length <= 1) return;
if (users.length <= 1) {
return;
}
let message = $inputMessage.val();
// Prevent markup from being injected into the message
@ -368,7 +372,7 @@ $(function() {
let secretKeyStr;
// Export secret key
cryptoUtil.exportKey(secretKey, "raw")
cryptoUtil.exportKey(secretKey, 'raw')
.then(function(data) {
return cryptoUtil.encryptSecretKey(data, thisUser.publicKey);
})
@ -376,7 +380,7 @@ $(function() {
let encData = new Uint8Array(encryptedSecretKey);
secretKeyStr = cryptoUtil.convertArrayBufferViewToString(encData);
// Export HMAC signing key
return cryptoUtil.exportKey(signingKey, "raw");
return cryptoUtil.exportKey(signingKey, 'raw');
})
.then(function(data) {
// Encrypt signing key with user's public key
@ -422,7 +426,7 @@ $(function() {
}
// 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
if (!windowHandler.isActive) {
windowHandler.notifyFavicon();
@ -432,7 +436,7 @@ $(function() {
let message = data.message;
let messageData = cryptoUtil.convertStringToArrayBufferView(message);
let username = data.username;
let senderId = data.id
let senderId = data.id;
let vector = data.vector;
let vectorData = cryptoUtil.convertStringToArrayBufferView(vector);
let secretKeys = data.secretKeys;
@ -449,7 +453,7 @@ $(function() {
cryptoUtil.decryptSecretKey(secretKeyArrayBuffer, keys.private)
.then(function(data) {
return cryptoUtil.importSecretKey(new Uint8Array(data), "raw");
return cryptoUtil.importSecretKey(new Uint8Array(data), 'raw');
})
.then(function(data) {
let secretKey = data;
@ -457,11 +461,11 @@ $(function() {
})
.then(function(data) {
decryptedMessageData = data;
decryptedMessage = cryptoUtil.convertArrayBufferViewToString(new Uint8Array(data))
return cryptoUtil.decryptSigningKey(signingKeyArrayBuffer, keys.private)
decryptedMessage = cryptoUtil.convertArrayBufferViewToString(new Uint8Array(data));
return cryptoUtil.decryptSigningKey(signingKeyArrayBuffer, keys.private);
})
.then(function(data) {
return cryptoUtil.importSigningKey(new Uint8Array(data), "raw");
return cryptoUtil.importSigningKey(new Uint8Array(data), 'raw');
})
.then(function(data) {
let signingKey = data;
@ -478,7 +482,7 @@ $(function() {
});
// 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');
addParticipantsMessage(data);
removeChatTyping(data);
@ -489,12 +493,12 @@ $(function() {
});
// Whenever the server emits 'typing', show the typing message
socket.on('typing', function (data) {
socket.on('typing', function(data) {
addChatTyping(data);
});
// Whenever the server emits 'stop typing', kill the typing message
socket.on('stop typing', function (data) {
socket.on('stop typing', function(data) {
removeChatTyping(data);
});
@ -522,9 +526,9 @@ $(function() {
let li;
if (user.username === window.username) {
// 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 {
li = $("<li>" + user.username + "</li>").css('color', getUsernameColor(user.username));
li = $('<li>' + user.username + '</li>').css('color', getUsernameColor(user.username));
}
$('#participants-modal ul.users')
.append(li);

View File

@ -4,8 +4,8 @@ export default class WindowHandler {
this.newMessages = 0;
this.favicon = new Favico({
animation:'pop',
type : 'rectangle'
animation: 'pop',
type: 'rectangle'
});
this.bindEvents();

View File

@ -29,7 +29,7 @@ class Room {
});
socket.on('add user', (data) => {
if (addedUser) return;
if (addedUser) { return; }
data.id = uuid.v4();
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();