diff --git a/.babelrc b/.babelrc deleted file mode 100644 index af0f0c3..0000000 --- a/.babelrc +++ /dev/null @@ -1,3 +0,0 @@ -{ - "presets": ["es2015"] -} \ No newline at end of file diff --git a/.dockerignore b/.dockerignore deleted file mode 100644 index d499b6c..0000000 --- a/.dockerignore +++ /dev/null @@ -1,2 +0,0 @@ -Dockerfile -node_modules diff --git a/.ebextensions/node-settings.config b/.ebextensions/node-settings.config deleted file mode 100644 index 655e99d..0000000 --- a/.ebextensions/node-settings.config +++ /dev/null @@ -1,3 +0,0 @@ -option_settings: - aws:elasticbeanstalk:container:nodejs: - NodeCommand: "npm run-script start" \ No newline at end of file diff --git a/.ebextensions/proxy.config b/.ebextensions/proxy.config deleted file mode 100644 index f663fc3..0000000 --- a/.ebextensions/proxy.config +++ /dev/null @@ -1,75 +0,0 @@ -files: - /etc/nginx/conf.d/proxy.conf: - mode: "000644" - owner: root - group: root - content: | - upstream nodejs { - server 127.0.0.1:8081; - keepalive 256; - } - - server { - listen 8080; - - if ($http_x_forwarded_proto != "https") { - rewrite ^(.*)$ https://darkwire.io$REQUEST_URI permanent; - } - - if ($time_iso8601 ~ "^(\d{4})-(\d{2})-(\d{2})T(\d{2})") { - set $year $1; - set $month $2; - set $day $3; - set $hour $4; - } - - # Turn off default AWS logging - # access_log /var/log/nginx/healthd/application.log.$year-$month-$day-$hour healthd; - # access_log /var/log/nginx/access.log main; - - access_log off; - error_log off; - - location /socket.io/ { - proxy_pass http://nodejs; - proxy_http_version 1.1; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection "upgrade"; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - } - - location / { - proxy_pass http://nodejs; - proxy_http_version 1.1; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection "upgrade"; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - } - - gzip on; - gzip_comp_level 4; - gzip_types text/html text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript; - - location /static { - alias /var/app/current/static; - } - - } - - /opt/elasticbeanstalk/hooks/configdeploy/post/99_kill_default_nginx.sh: - mode: "000755" - owner: root - group: root - content: | - #!/bin/bash -xe - rm -f /etc/nginx/conf.d/00_elastic_beanstalk_proxy.conf - service nginx stop - service nginx start - -container_commands: - removeconfig: - command: "rm -f /tmp/deployment/config/#etc#nginx#conf.d#00_elastic_beanstalk_proxy.conf /etc/nginx/conf.d/00_elastic_beanstalk_proxy.conf" \ No newline at end of file diff --git a/.ebignore b/.ebignore deleted file mode 100644 index e09f477..0000000 --- a/.ebignore +++ /dev/null @@ -1,6 +0,0 @@ -node_modules/ -.DS_STORE -# Elastic Beanstalk Files -.elasticbeanstalk/* -!.elasticbeanstalk/*.cfg.yml -!.elasticbeanstalk/*.global.yml \ No newline at end of file diff --git a/.gitignore b/.gitignore index 3947ab5..8672b5c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,3 @@ .DS_Store -node_modules -npm-debug.log -src/public/main.js -src/.secret - -# Elastic Beanstalk Files -.elasticbeanstalk/* -!.elasticbeanstalk/*.cfg.yml -!.elasticbeanstalk/*.global.yml +server +client diff --git a/.jscsrc b/.jscsrc deleted file mode 100644 index f223c2b..0000000 --- a/.jscsrc +++ /dev/null @@ -1,8 +0,0 @@ -{ - "preset": "google", - "esnext": true, - "requireCamelCaseOrUpperCaseIdentifiers": {"ignoreProperties": true}, - "maxErrors": null, - "maximumLineLength": null, - "excludeFiles": ["src/public"] -} \ No newline at end of file diff --git a/.jshintignore b/.jshintignore deleted file mode 100644 index b921a69..0000000 --- a/.jshintignore +++ /dev/null @@ -1,2 +0,0 @@ -node_modules -src/public \ No newline at end of file diff --git a/.jshintrc b/.jshintrc deleted file mode 100644 index 53b202c..0000000 --- a/.jshintrc +++ /dev/null @@ -1,3 +0,0 @@ -{ - "esversion": 6 -} \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index 1422a90..47bb847 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,33 +1,3 @@ -sudo: required dist: trusty language: node_js -node_js: - - 5.2.0 -cache: - directories: - - node_modules -addons: - srcclr: true -apt: - sources: - - google-chrome - packages: - - google-chrome-stable - - google-chrome-beta -before_install: - - sudo apt-get update - - sudo apt-get install -y libappindicator1 fonts-liberation - - wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb - - sudo dpkg -i google-chrome*.deb - - wget http://chromedriver.storage.googleapis.com/2.24/chromedriver_linux64.zip - - unzip chromedriver_linux64 - - sudo mv chromedriver /usr/bin -before_script: - - export CHROME_BIN=/usr/bin/google-chrome - - "export DISPLAY=:99.0" - - "sh -e /etc/init.d/xvfb start" - - sleep 5 # give xvfb some time to start - - gulp bundle - - npm start & - - sleep 5 -script: npm run test-travis +script: echo "tests soon" diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 50ff18d..0000000 --- a/Dockerfile +++ /dev/null @@ -1,15 +0,0 @@ -FROM node:6.7 - -RUN npm install -g forever - -RUN mkdir -p /app -WORKDIR /app - -COPY package.json /app -RUN npm install - -COPY . /app -RUN npm run bundle - -EXPOSE 3000 -CMD ["npm", "start"] diff --git a/Procfile b/Procfile deleted file mode 100644 index d27462a..0000000 --- a/Procfile +++ /dev/null @@ -1 +0,0 @@ -web: npm run bundle && port=$PORT npm start diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..210cb27 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,19 @@ +version: '2' + +services: + dwserver: + image: darkwire/darkwire-server:latest + environment: + - NODE_ENV=production + ports: + - "3000:3000" + dwclient: + image: darkwire/darkwire-client:latest + ports: + - "80:80" + redis: + image: redis + ports: + - "6379:6379" + volumes: + - ./.data/redis:/var/lib/redis diff --git a/gulpfile.babel.js b/gulpfile.babel.js deleted file mode 100644 index c4e815f..0000000 --- a/gulpfile.babel.js +++ /dev/null @@ -1,81 +0,0 @@ -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('dev', function() { - return browserify('src/js/main.js', { - debug: true - }).transform(babel.configure({ - presets: ['es2015'] - })).bundle() - .pipe(source('main.js')) - .pipe(buffer()) - .pipe(gulp.dest('src/public')); -}); - -gulp.task('start', function() { - nodemon({ - script: 'index.js', - ext: 'css js mustache', - ignore: ['src/public/main.js', 'test'], - env: { - 'NODE_ENV': 'development' - }, - tasks: ['dev'] - }); -}); - -gulp.task('test', function() { - let lintTest = spawn( - 'node_modules/mocha/bin/mocha', - ['test/unit/lint.js', '--compilers', 'js:babel-core/register'], - {stdio: 'inherit'} - ); - - lintTest.on('exit', function() { - - let unitTest = spawn('node_modules/karma/bin/karma', ['start', '--single-run'], {stdio: 'inherit'}); - - unitTest.on('exit', function() { - - // Start app - let app = spawn('node', ['index.js']); - - app.stdout.on('data', function(data) { - console.log(String(data)); - }); - - let acceptanceTest = spawn( - 'node_modules/nightwatch/bin/nightwatch', - ['--test', 'test/acceptance/index.js', '--config', 'test/acceptance/nightwatch-local.json'], - {stdio: 'inherit'} - ); - - acceptanceTest.on('exit', function() { - // Kill app Node process when tests are done - app.kill(); - }); - - }); - }); - -}); diff --git a/index.js b/index.js deleted file mode 100644 index fa780d2..0000000 --- a/index.js +++ /dev/null @@ -1,2 +0,0 @@ -require('babel-register')(); -require('./src/app.js'); diff --git a/karma.conf.js b/karma.conf.js deleted file mode 100644 index 0ebe370..0000000 --- a/karma.conf.js +++ /dev/null @@ -1,79 +0,0 @@ -// Karma configuration -// Generated on Sat Feb 27 2016 15:39:33 GMT-0500 (EST) - -module.exports = function(config) { - config.set({ - - // base path that will be used to resolve all patterns (eg. files, exclude) - basePath: 'test/unit', - - // frameworks to use - // available frameworks: https://npmjs.org/browse/keyword/karma-adapter - frameworks: ['browserify', 'mocha'], - - // list of files / patterns to load in the browser - files: [ - 'index.js', - 'fixtures/**/*.html' - ], - - // list of files to exclude - exclude: [ - ], - - // preprocess matching files before serving them to the browser - // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor - preprocessors: { - '**/*.js': ['browserify'], - 'fixtures/**/*.html': ['html2js'] - }, - - // test results reporter to use - // possible values: 'dots', 'progress' - // available reporters: https://npmjs.org/browse/keyword/karma-reporter - reporters: ['mocha'], - - // web server port - port: 9876, - - // enable / disable colors in the output (reporters and logs) - colors: true, - - // level of logging - // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG - logLevel: config.LOG_INFO, - - // enable / disable watching file and executing tests whenever any file changes - autoWatch: true, - - // start these browsers - // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher - browsers: ['Chrome'], - customLaunchers: { - Chrome_without_sandbox: { - base: 'Chrome', - flags: ['--no-sandbox'] // with sandbox it fails under Docker - } - }, - // Continuous Integration mode - // if true, Karma captures browsers, runs the tests and exits - singleRun: false, - - // Concurrency level - // how many browser should be started simultaneous - concurrency: Infinity, - - plugin: ['karma-mocha-reporter'], - - browserify: { - debug: true, - plugin: ['proxyquireify/plugin'], - configure: function(bundle) { - bundle.once('prebundle', function() { - bundle.transform('babelify'); - }); - } - } - - }); -}; diff --git a/package.json b/package.json deleted file mode 100644 index 48235d9..0000000 --- a/package.json +++ /dev/null @@ -1,63 +0,0 @@ -{ - "name": "darkwire", - "version": "1.6.0", - "description": "Encrypted web socket chat", - "main": "index.js", - "dependencies": { - "babel": "^5.8.23", - "babelify": "^7.2.0", - "browserify": "^13.0.0", - "compression": "^1.6.0", - "express": "^4.13.3", - "forever": "^0.15.1", - "gulp": "^3.9.0", - "gulp-uglify": "^1.5.1", - "he": "^0.5.0", - "moment": "^2.11.2", - "mustache-express": "^1.2.2", - "sanitize-html": "^1.11.3", - "serve-favicon": "^2.3.0", - "shortid": "^2.2.4", - "slug": "^0.9.1", - "socket.io": "^1.4.0", - "underscore": "^1.8.3", - "uuid": "^2.0.1", - "babel-core": "^6.5.2", - "babel-preset-es2015": "^6.3.13", - "babel-register": "^6.5.2" - }, - "devDependencies": { - "compression": "^1.6.0", - "gulp": "^3.9.1", - "gulp-nodemon": "^2.0.6", - "jscs": "^2.10.1", - "jshint": "^2.9.1", - "karma": "^0.13.21", - "karma-browserify": "^5.0.2", - "karma-chrome-launcher": "^0.2.2", - "karma-html2js-preprocessor": "^0.1.0", - "karma-mocha": "^0.2.2", - "karma-mocha-reporter": "^1.2.3", - "mocha": "^2.4.5", - "mocha-jscs": "^4.2.0", - "mocha-jshint": "^2.3.1", - "nightwatch": "^0.8.16", - "proxyquireify": "^3.1.1", - "sinon": "^1.17.3", - "vinyl-buffer": "^1.0.0", - "vinyl-source-stream": "^1.1.0", - "watchify": "^3.7.0" - }, - "scripts": { - "start": "node index.js", - "prod": "npm run bundle && forever start -c \"npm start\" ./", - "dev": "npm run bundle && gulp start", - "bundle": "gulp bundle", - "test": "npm run bundle && gulp test", - "test-travis": "node_modules/mocha/bin/mocha test/unit/lint.js --compilers js:babel-core/register && node_modules/karma/bin/karma start --single-run && node_modules/nightwatch/bin/nightwatch --test test/acceptance/index.js --config test/acceptance/nightwatch.json -e chrome", - "predeploy": "npm run bundle", - "deploy": "eb deploy --staged" - }, - "author": "Daniel Seripap", - "license": "MIT" -} diff --git a/readme.md b/readme.md index ee16739..cb7c4c4 100644 --- a/readme.md +++ b/readme.md @@ -1,45 +1,54 @@ # Darkwire.io -[![Build Status](https://travis-ci.org/seripap/darkwire.io.svg?branch=master)](https://travis-ci.org/seripap/darkwire.io) [![GitHub release](https://img.shields.io/github/release/seripap/darkwire.io.svg)]() +[![GitHub release](https://img.shields.io/github/release/seripap/darkwire.io.svg)]() -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), the [web cryptography API](https://developer.mozilla.org/en-US/docs/Web/API/Window/crypto). -### Running a local copy -You can run a local copy of Darkwire via Docker through dockerhub. Versions are strictly controlled, [we recommend using the latest tagged version](https://github.com/seripap/darkwire.io/releases/latest) as older versions may pose some security issues. Our docker repository will **always** reference the latest version that is available on github. +### Darkwire Server + +Darkwire server is a Node.js application that requires redis. + +[darkwire-server](https://github.com/seripap/darkwire-server) + +### Darkwire Web Client + +The Darkwire.io web client is written in JavaScript with React JS and Redux. + +[darkwire-client](https://github.com/seripap/darkwire-client) + +### Running Darkwire Locally + +To quickly get up and running, we recommend using Docker and Docker Compose. ``` -$ docker run -d -p 80:3000 --name dakrwire darkwire/darkwire:latest +$ docker-compose pull && docker-compose up ``` -Docker is now running on local port 80. +Darkwire client will be binded to port 80 while the server is on port 3000. Go to [http://localhost](http://localhost). This will also start a redis instance running on port 6379. + +If running in a public environment, be sure the modify ENV_VARS via `docker-compose.yml`. -### Building Containers ``` -$ docker build -t darkwire . -# Running a local instance -$ docker run -p 80:3000 darkwire +environment: + - API_HOST=dwserver + - API_PROTOCOL=http + - API_PORT=3000 ``` -Darkwire is now online on local port 80. Default container port is 3000. +### Contributing to Darkwire -### Installation - # Using node@v6.7 - $ npm install - - # Starting dev environment - $ npm run dev +Run `setup-dev.sh` to automatically clone server/client files and install dependencies, or clone the client and server repositories. - # Running tests locally (Mac) - $ brew install chromedriver - $ npm test - # Start a local instance of darkwire - $ npm run bundle - $ npm start +1. Create a Pull Request for the respective service with detailed changes +2. Wait for review and/or merges - # Changing ports, default is 3000 - port=3000 npm start +### Security -Darkwire is now running on `http://localhost:3000` +Please report any security issues to `hello@darkwire.io`. + +### Legacy Darkwire + +If you are trying to access the legacy stable version of darkwire, you can use the oldest tag before `v2.0.0` which is `v1.6.0` or checkout the `legacy` branch. ### How it works diff --git a/setup-dev.sh b/setup-dev.sh new file mode 100755 index 0000000..9e07585 --- /dev/null +++ b/setup-dev.sh @@ -0,0 +1,5 @@ +mkdir client server +git clone https://github.com/seripap/darkwire-client client +git clone https://github.com/seripap/darkwire-server server +cd client && yarn +cd ../server && yarn diff --git a/src/app.js b/src/app.js deleted file mode 100644 index 72b71e0..0000000 --- a/src/app.js +++ /dev/null @@ -1,73 +0,0 @@ -import _ from 'underscore'; -import express from 'express'; -import mustacheExpress from 'mustache-express'; -import Io from 'socket.io'; -import http from 'http'; -import shortid from 'shortid'; -import favicon from 'serve-favicon'; -import compression from 'compression'; -import fs from 'fs'; - -import Room from './room'; - -let usage = 0; - -const $CONFIG = { - port: process.env.port || process.env.PORT || 3000 -}; - -const app = express(); -const server = http.createServer(app); -const io = Io(server); - -let rooms = []; - -app.use(compression()); -app.use(favicon(__dirname + '/public/favicon.ico')); -app.engine('mustache', mustacheExpress()); -app.set('view engine', 'mustache'); -app.set('views', __dirname + '/views'); -app.use(express.static(__dirname + '/public')); - -app.get('/health-check', (req, res) => res.send(200)); - -function generateNewRoom(req, res, id) { - const room = new Room(io, id); - rooms.push(room); - console.log(`rooms created: ${usage++}`); - - return res.redirect(`/${id}`); -} - -function stripName(name) { - const chatName = name.toLowerCase().replace(/[^A-Za-z0-9]/g, '-'); - if (chatName.length >= 50) { - return chatName.substr(0, 50); - } - - return chatName; -} - -app.get('/', (req, res) => generateNewRoom(req, res, stripName(shortid.generate()))); - -app.get('/:roomId', (req, res) => { - const roomId = stripName(req.params.roomId) || false; - - let roomExists = _.findWhere(rooms, {_id: roomId}) || false; - - if (roomExists) { - return res.render('index', { - APP: { - version: process.env.npm_package_version, - ip: req.headers['x-forwarded-for'] - }, - username: shortid.generate() - }); - } - - return generateNewRoom(req, res, roomId); -}); - -server.listen($CONFIG.port, () => { - console.log(`darkwire is online on port ${$CONFIG.port}.`); -}); diff --git a/src/js/app.js b/src/js/app.js deleted file mode 100644 index 2e58c7b..0000000 --- a/src/js/app.js +++ /dev/null @@ -1,319 +0,0 @@ -import _ from 'underscore'; -import Darkwire from './darkwire'; -import WindowHandler from './window'; -import Chat from './chat'; -import moment from 'moment'; -import sanitizeHtml from 'sanitize-html'; -import uuid from 'uuid'; -import he from 'he'; - -export default class App { - - constructor() { - this._roomId = window.location.pathname.length ? this.stripName(window.location.pathname) : null; - this._darkwire = new Darkwire(); - this._socket = io(this._roomId); - this._darkwire.connected = false; - this.init(); - } - - stripName(name) { - const chatName = name.replace('/','').toLowerCase().replace(/[^A-Za-z0-9]/g, '-'); - if (chatName.length >= 50) { - const limitedChatName = chatName.substr(0, 50); - window.history.replaceState({}, limitedChatName, `/${limitedChatName}`); - return `/${limitedChatName}`; - } - - return '/' + chatName; - } - - init() { - this._chat = new Chat(this._darkwire, this._socket); - - if (!this._roomId) { return; } - - $('input.share-text').val(document.location.protocol + '//' + document.location.host + this._roomId); - - $('input.share-text').click(() => { - $(this).focus(); - $(this).select(); - }); - - const windowHandler = new WindowHandler(this._darkwire, this._socket, this._chat); - - FastClick.attach(document.body); - - // Select message input when closing modal - $('.modal').on('hidden.bs.modal', (e) => { - this._chat.inputMessage.focus(); - }); - - this._socket.on('rated', () => { - this._chat.log('You are sending messages to fast, please slow down. Your last message was ignored.', { - error: true, - }); - }); - - // Whenever the server emits 'login', log the login message - this._socket.on('user joined', (data) => { - this._darkwire.connected = true; - this.addParticipantsMessage(data); - let importKeysPromises = this._darkwire.addUser(data); - Promise.all(importKeysPromises).then(() => { - // All users' keys have been imported - if (data.numUsers === 1) { - $('#first-modal').modal('show'); - } - - this._chat.log(data.username + ' joined'); - this.renderParticipantsList(); - }); - - }); - - this._socket.on('user update', (data) => { - this._darkwire.updateUser(data).then((oldUsername) => { - this._chat.log(oldUsername + ' changed name to ' + data.username, - { - classNames: 'changed-name' - }); - this.renderParticipantsList(); - }); - }); - - // Whenever the server emits 'new message', update the chat body - this._socket.on('new message', (data) => { - this._darkwire.decodeMessage(data).then((decodedMessage) => { - if (!windowHandler.isActive) { - windowHandler.notifyFavicon(); - this._darkwire.audio.play(); - } - - let data = { - username: decodedMessage.username, - message: decodedMessage.message.text, - messageType: decodedMessage.messageType, - additionalData: decodedMessage.message.additionalData - }; - this._chat.addChatMessage(data); - }); - - }); - - // Whenever the server emits 'user left', log it in the chat body - this._socket.on('user left', (data) => { - this._chat.log(data.username + ' left'); - this.addParticipantsMessage(data); - this._chat.removeChatTyping(data); - - this._darkwire.removeUser(data); - - this.renderParticipantsList(); - }); - - // Whenever the server emits 'typing', show the typing message - this._socket.on('typing', (data) => { - this._chat.addChatTyping(data); - }); - - // Whenever the server emits 'stop typing', kill the typing message - this._socket.on('stop typing', (data) => { - this._chat.removeChatTyping(data); - }); - - this._socket.on('disconnect', (data) => { - this._darkwire.connected = false; - this._chat.log('Disconnected from server, automatically reconnecting in 4 seconds.', { - error: true, - }); - this.retryConnection(); - }); - - this.initChat(); - - // Nav links - $('a#settings-nav').click(() => { - $('#settings-modal').modal('show'); - }); - - $('a#about-nav').click(() => { - $('#about-modal').modal('show'); - }); - - $('.navbar .participants').click(() => { - this.renderParticipantsList(); - $('#participants-modal').modal('show'); - }); - - $('#send-message-btn').click(() => { - handleMessageSending(); - this._socket.emit('stop typing'); - this._chat.typing = false; - }); - - $('.navbar-collapse ul li a').click(() => { - $('.navbar-toggle:visible').click(); - }); - - let audioSwitch = $('input.sound-enabled').bootstrapSwitch(); - - audioSwitch.on('switchChange.bootstrapSwitch', (event, state) => { - this._darkwire.audio.soundEnabled = state; - }); - - window.handleMessageSending = () => { - let message = this._chat.inputMessage; - let cleanedMessage = this.cleanInput(message.val()); - let slashCommand = this._chat.parseCommand(cleanedMessage); - - if (slashCommand) { - return this._chat.executeCommand(slashCommand, this); - } - - // Prevent markup from being injected into the message - this._darkwire.encodeMessage(cleanedMessage, 'text').then((socketData) => { - message.val(''); - $('#send-message-btn').removeClass('active'); - // Add escaped message since message did not come from the server - this._chat.addChatMessage({ - username: username, - message: escape(cleanedMessage) - }); - this._socket.emit('new message', socketData); - }).catch((err) => { - console.log(err); - }); - }; - - window.triggerFileTransfer = (context) => { - const fileId = context.getAttribute('data-file'); - if (fileId) { - return windowHandler.fileHandler.encodeFile(fileId); - } - - return this._chat.log('Requested file transfer is no longer valid. Please try again.', {error: true}); - }; - - window.triggerFileDestroy = (context) => { - const fileId = context.getAttribute('data-file'); - if (fileId) { - return windowHandler.fileHandler.destroyFile(fileId); - } - - return this._chat.log('Requested file transfer is no longer valid. Please try again.', {error: true}); - }; - - window.triggerFileDownload = (context) => { - const fileId = context.getAttribute('data-file'); - const file = this._darkwire.getFile(fileId); - windowHandler.fileHandler.createBlob(file.message, file.messageType).then((blob) => { - let url = windowHandler.fileHandler.createUrlFromBlob(blob); - - if (file) { - if (file.messageType.match('image.*')) { - let image = new Image(); - image.src = url; - this._chat.replaceMessage('#file-transfer-request-' + fileId, image); - } else { - let downloadLink = document.createElement('a'); - downloadLink.href = url; - downloadLink.target = '_blank'; - downloadLink.innerHTML = 'Download ' + file.additionalData.fileName; - this._chat.replaceMessage('#file-transfer-request-' + fileId, downloadLink); - } - } - - this._darkwire.encodeMessage('Accepted ' + file.additionalData.fileName + '', 'text').then((socketData) => { - this._socket.emit('new message', socketData); - }).catch((err) => { - console.log(err); - }); - - }); - }; - } - - // Prevents input from having injected markup - cleanInput(input) { - input = input.replace(/\r?\n/g, '
'); - return this.autolinker(he.encode(input)); - } - - // Adds rel=noopener noreferrer to autolinked links (Addresses #46 from @Mickael-van-der-Beek) - autolinker(sanitized) { - return Autolinker.link(sanitized, { - replaceFn: function(match) { - const tag = match.buildTag(); - tag.setAttr('rel', 'noopener noreferrer'); - - return tag; - } - }); - } - - // Sets the client's username - initChat() { - - // Warn if not incognito - // FileSystem API is disabled in incognito mode, so we can use that to check - let fs = window.requestFileSystem || window.webkitRequestFileSystem; - if (fs) { - fs(window.TEMPORARY, 100, () => { - this._chat.log('Your browser is not in incognito mode!', {warning: true}); - }); - } - - this._chat.log(moment().format('MMMM Do YYYY, h:mm:ss a'), {info: true}); - - $('#roomName').text(this._roomId); - $('#chatNameModal').text(this._roomId); - - this._darkwire.updateUsername(username).then((socketData) => { - this._chat.chatPage.show(); - this._chat.inputMessage.focus(); - this._socket.emit('add user', socketData); - }); - - $('#newRoom').on('click', (e) => { - e.preventDefault(); - const newWindow = window.open(); - newWindow.opener = null; - newWindow.location = window.location.protocol + '//' + window.location.host + '/' + uuid.v4().replace(/-/g,''); - }); - } - - addParticipantsMessage(data) { - let message = ''; - let headerMsg = ''; - const {numUsers} = data; - - if (numUsers === 0) { - window.location.reload(); - } - - $('#participants').text(numUsers); - } - - renderParticipantsList() { - $('#participants-modal ul.users').empty(); - _.each(this._darkwire.users, (user) => { - let li; - if (user.username === window.username) { - // User is me - li = $('
  • ' + user.username + ' (you)
  • ').css('color', this._chat.getUsernameColor(user.username)); - } else { - li = $('
  • ' + user.username + '
  • ').css('color', this._chat.getUsernameColor(user.username)); - } - $('#participants-modal ul.users') - .append(li); - }); - } - - retryConnection() { - window.setTimeout(() => { - window.location.reload(); - }, 4000); - } - -} diff --git a/src/js/audio.js b/src/js/audio.js deleted file mode 100644 index 3f441b7..0000000 --- a/src/js/audio.js +++ /dev/null @@ -1,22 +0,0 @@ -export default class AudioHandler { - constructor() { - this._beep = window.Audio && new window.Audio('beep.mp3') || false; - this._soundEnabled = true; - } - - get soundEnabled() { - return this._soundEnabled; - } - - set soundEnabled(state) { - this._soundEnabled = state; - return this; - } - - play() { - if (this._beep && this.soundEnabled) { - this._beep.play(); - } - return this; - } -} diff --git a/src/js/chat.js b/src/js/chat.js deleted file mode 100644 index ed2657d..0000000 --- a/src/js/chat.js +++ /dev/null @@ -1,404 +0,0 @@ -import _ from 'underscore'; -import sanitizeHtml from 'sanitize-html'; -import he from 'he'; -import moment from 'moment'; - -// TODO: Remove in v2.0 -let warned = false; - -export default class Chat { - constructor(darkwire, socket) { - this.usernamesInMemory = []; - this.FADE_TIME = 150; // ms - this.TYPING_TIMER_LENGTH = 400; // ms - this.typing = false; - this.lastTypingTime = null; - this.darkwire = darkwire; - this.socket = socket; - this.messages = $('.messages'); // Messages area - this.inputMessage = $('.inputMessage'); // Input message input box - this.chatPage = $('.chat.page'); - this.lastMessageTime = null; - this.bindEvents(); - } - - clear() { - let chatArea = $('.messages'); - return chatArea.fadeOut(200, () => { chatArea.empty().show(); }); - } - - // Log a message - log(message, options) { - let html = options && options.html === true || false; - let classNames = options && options.classNames ? options.classNames : ''; - let $el; - - let matchedUsernames = this.checkIfUsername(message.split(' ')); - - if (matchedUsernames.length > 0) { - for (let i = 0; i < matchedUsernames.length; i++) { - let usernameContainer = $('') - .text(matchedUsernames[i]) - .css('color', this.getUsernameColor(matchedUsernames[i])); - - // Match only the username - let matchedUsernameOnly = new RegExp('(' + matchedUsernames[i] + ')(?![^<]*>|[^<>]*<\/)', 'gm'); - message = message.replace(matchedUsernameOnly, usernameContainer.prop('outerHTML')); - } - } - - if (options && options.error) { - $el = $('
  • ').addClass(`log ${classNames}`).html('ERROR: ' + message); - } else if (options && options.warning) { - $el = $('
  • ').addClass(`log ${classNames}`).html('WARNING: ' + message); - } else if (options && options.notice) { - $el = $('
  • ').addClass(`log ${classNames}`).html('NOTICE: ' + message); - } else if (options && options.info) { - $el = $('
  • ').addClass(`log ${classNames}`).html(message); - } else { - $el = $('
  • ').addClass(`log ${classNames}`).html(message); - } - - this.addMessageElement($el, options); - } - - checkIfUsername(words) { - let matchedUsernames = []; - this.darkwire.users.forEach((user) => { - let usernameMatch = new RegExp('^' + user.username + '$'); - for (let i = 0; i < words.length; i++) { - let exactMatch = words[i].match(usernameMatch) || false; - let usernameInMemory = this.usernamesInMemory.indexOf(words[i]) > -1; - - if (exactMatch && exactMatch.length > -1 || usernameInMemory) { - if (!usernameInMemory) { - this.usernamesInMemory.push(words[i]); - } - matchedUsernames.push(words[i]); - } - } - }); - return matchedUsernames; - } - - // Gets the color of a username through our hash function - getUsernameColor(username) { - const COLORS = [ - '#e21400', '#ffe400', '#ff8f00', - '#58dc00', '#dd9cff', '#4ae8c4', - '#3b88eb', '#f47777', '#EEACB7', - '#D3FF3E', '#99CC33', '#999933', - '#996633', '#B8D5B8', '#7FFF38', - '#FADBBC', '#FAE2B7', '#EBE8AF', - ]; - // Compute hash code - let hash = 7; - for (let i = 0; i < username.length; i++) { - hash = username.charCodeAt(i) + (hash << 5) - hash; - } - // Calculate color - let index = Math.abs(hash % COLORS.length); - return COLORS[index]; - } - - bindEvents() { - var _this = this; - // Select message input when clicking message body, unless selecting text - this.messages.on('click', () => { - if (!this.getSelectedText()) { - this.inputMessage.focus(); - } - }); - - this.inputMessage.on('input propertychange paste change', function() { - _this.updateTyping(); - let message = $(this).val().trim(); - if (message.length) { - $('#send-message-btn').addClass('active'); - } else { - $('#send-message-btn').removeClass('active'); - } - }); - } - - // Updates the typing event - updateTyping() { - if (this.darkwire.connected) { - if (!this.typing) { - this.typing = true; - this.socket.emit('typing'); - } - this.lastTypingTime = (new Date()).getTime(); - - setTimeout(() => { - let typingTimer = (new Date()).getTime(); - let timeDiff = typingTimer - this.lastTypingTime; - if (timeDiff >= this.TYPING_TIMER_LENGTH && this.typing) { - this.socket.emit('stop typing'); - this.typing = false; - } - }, this.TYPING_TIMER_LENGTH); - } - } - - addChatTyping(data) { - data.typing = true; - data.message = 'is typing'; - this.addChatMessage(data); - } - - getSelectedText() { - let text = ''; - if (typeof window.getSelection != 'undefined') { - text = window.getSelection().toString(); - } else if (typeof document.selection != 'undefined' && document.selection.type == 'Text') { - text = document.selection.createRange().text; - } - return text; - } - - getTypingMessages(data) { - return $('.typing.message').filter(function(i) { - return $(this).data('username') === data.username; - }); - } - - removeChatTyping(data) { - this.getTypingMessages(data).fadeOut(function() { - $(this).remove(); - }); - } - - slashCommands(trigger) { - let validCommands = []; - let expectedParams = 0; - const triggerCommands = [{ - command: 'nick', - description: 'Changes nickname.', - paramaters: ['{username}'], - multiple: false, - usage: '/nick {username}', - action: () => { - - let newUsername = trigger.params[0] || false; - - if (newUsername.toString().length > 16) { - return this.log('Username cannot be greater than 16 characters.', {error: true}); - } - - // Remove things that arent digits or chars - newUsername = newUsername.replace(/[^A-Za-z0-9]/g, '-'); - - if (!newUsername.match(/^[A-Z]/i)) { - return this.log('Username must start with a letter.', {error: true}); - } - - if (!warned) { - warned = true; - return this.log('Changing your username is currently in beta and your new username will be sent over the wire in plain text, unecrypted. This will be fixed in v2.0. If you really want to do this, type the command again.', - { - warning: true, - classNames: 'change-username-warning' - }); - } - - this.darkwire.updateUsername(newUsername).then((socketData) => { - let modifiedSocketData = { - username: window.username, - newUsername: socketData.username - }; - - this.socket.emit('update user', modifiedSocketData); - window.username = username = socketData.username; - }).catch((err) => { - return this.log(err, {error: true}); - }); - } - }, { - command: 'help', - description: 'Shows a list of commands.', - paramaters: [], - multiple: false, - usage: '/help', - action: () => { - validCommands = validCommands.map((command) => { - return '/' + command; - }); - - this.log('Valid commands: ' + validCommands.sort().join(', '), {notice: true}); - } - }, { - command: 'me', - description: 'Invoke virtual action', - paramaters: ['{action}'], - multiple: true, - usage: '/me {action}', - action: () => { - - expectedParams = 100; - - let actionMessage = trigger.params.join(' '); - - this.darkwire.encodeMessage(actionMessage, 'action').then((socketData) => { - this.addChatMessage({ - username: username, - message: actionMessage, - messageType: 'action' - }); - this.socket.emit('new message', socketData); - }).catch((err) => { - console.log(err); - }); - } - }, { - command: 'clear', - description: 'Clears the chat screen', - paramaters: [], - multiple: true, - usage: '/clear', - action: () => { - this.clear(); - } - }]; - - const color = () => { - const hexTex = new RegExp(/(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)/i); - }; - - triggerCommands.forEach((command) => { - validCommands.push(command.command); - }); - - let commandToTrigger = _.findWhere(triggerCommands, {command: trigger.command}); - - if (commandToTrigger) { - expectedParams = commandToTrigger.paramaters.length; - if (expectedParams && trigger.params.length > expectedParams || expectedParams && trigger.params.length < expectedParams) { - if ((!commandToTrigger.multple && trigger.params.length < 1) || (trigger.params.length >= 1 && trigger.params[0] === '')) { - return this.log('Missing or too many paramater. Usage: ' + commandToTrigger.usage, {error: true}); - } - } - - return commandToTrigger.action.call(); - } - - this.log(trigger.command + ' is not a valid command. Type /help for a list of valid commands.', {error: true}); - return false; - } - - executeCommand(trigger) { - trigger = trigger || false; - if (trigger) { - let command = trigger.command; - this.slashCommands(trigger); - } - } - - parseCommand(cleanedMessage) { - let trigger = { - command: null, - params: [] - }; - - if (cleanedMessage.indexOf('/') === 0) { - this.inputMessage.val(''); - let parsedCommand = cleanedMessage.replace('/', '').split(' '); - trigger.command = sanitizeHtml(parsedCommand[0]) || null; - // Get params - if (parsedCommand.length >= 2) { - for (let i = 1; i < parsedCommand.length; i++) { - trigger.params.push(parsedCommand[i]); - } - } - - return trigger; - } - - return false; - } - - addChatMessage(data, options) { - if (!data.message.trim().length) { - return; - } - - let messageType = data.messageType || 'text'; - - // Don't fade the message in if there is an 'X was typing' - let $typingMessages = this.getTypingMessages(data); - options = options || {}; - if ($typingMessages.length !== 0) { - options.fade = false; - $typingMessages.remove(); - } - - let $usernameDiv = $('') - .text(data.username) - .css('color', this.getUsernameColor(data.username)); - - let $messageBodyDiv = $(''); - let timestamp = this.getTimestamp(data.typing); - - if (messageType === 'text' || messageType === 'action') { - if (messageType === 'action') { - $usernameDiv.css('color','').prepend('*'); - } - - let unescapedMessage = unescape(data.message); - let lineBreaks = /<br \/>/g; - unescapedMessage = unescapedMessage.replace(lineBreaks, '
    '); - $messageBodyDiv.html(unescapedMessage); - } else { - $messageBodyDiv.html(this.darkwire.addFileToQueue(data)); - } - - let typingClass = data.typing ? 'typing' : ''; - let actionClass = data.messageType === 'action' ? 'action' : ''; - - let $messageDiv = $('
  • ') - .data('username', data.username) - .addClass(typingClass) - .addClass(actionClass) - .append(timestamp, $usernameDiv, $messageBodyDiv); - - this.addMessageElement($messageDiv, options); - } - - getTimestamp(isTyping) { - if (isTyping) { - return false; - } - - let now = moment(new Date()).format('LT'); - - if (this.lastMessageTime === now) { - return false; - } - - this.lastMessageTime = now; - - return '' + now + ''; - - } - - addMessageElement(el, options) { - let $el = $(el); - - if (!options) { - options = {}; - } - - $el.hide().fadeIn(this.FADE_TIME); - this.messages.append($el); - - this.messages[0].scrollTop = this.messages[0].scrollHeight; // minus 60 for key - } - - replaceMessage(id, message) { - let container = $(id); - if (container) { - container.html(message); - } - } - -} diff --git a/src/js/crypto.js b/src/js/crypto.js deleted file mode 100644 index 7b1a79c..0000000 --- a/src/js/crypto.js +++ /dev/null @@ -1,237 +0,0 @@ -export default class CryptoUtil { - constructor() { - this._crypto = window.crypto || false; - - if (!this._crypto || (!this._crypto.subtle && !this._crypto.webkitSubtle)) { - $('#no-crypto').modal({ - backdrop: 'static', - show: false, - keyboard: false - }); - - $('#no-crypto').modal('show'); - return; - } - - } - - get crypto() { - return this._crypto; - } - - convertStringToArrayBufferView(str) { - let bytes = new Uint8Array(str.length); - for (let i = 0; i < str.length; i++) { - bytes[i] = str.charCodeAt(i); - } - - return bytes; - } - - convertArrayBufferViewToString(buffer) { - let str = ''; - for (let i = 0; i < buffer.byteLength; i++) { - str += String.fromCharCode(buffer[i]); - } - - return str; - } - - createSigningKey() { - return this._crypto.subtle.generateKey( - { - 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' - ); - } - - createPrimaryKeys() { - return this._crypto.subtle.generateKey( - { - 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' - }, - true, //whether the key is extractable (i.e. can be used in exportKey) - ['encrypt', 'decrypt'] //must be ['encrypt', 'decrypt'] or ['wrapKey', 'unwrapKey'] - ); - } - - createSecretKey() { - return this._crypto.subtle.generateKey( - { - 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' - ); - } - - encryptSecretKey(data, secretKey) { - // Secret key will be recipient's public key - return this._crypto.subtle.encrypt( - { - name: 'RSA-OAEP', - modulusLength: 2048, - publicExponent: new Uint8Array([0x01, 0x00, 0x01]), - hash: {name: 'SHA-256'} - }, - secretKey, - data //ArrayBuffer of data you want to encrypt - ); - } - - decryptSecretKey(data, key) { - // key will be my private key - return this._crypto.subtle.decrypt( - { - name: 'RSA-OAEP', - modulusLength: 2048, - publicExponent: new Uint8Array([0x01, 0x00, 0x01]), - hash: {name: 'SHA-256'} - //label: Uint8Array([...]) //optional - }, - key, - data //ArrayBuffer of the data - ); - } - - encryptSigningKey(data, signingKey) { - // Secret key will be recipient's public key - return this._crypto.subtle.encrypt( - { - name: 'RSA-OAEP', - modulusLength: 2048, - publicExponent: new Uint8Array([0x01, 0x00, 0x01]), - hash: {name: 'SHA-256'} - }, - signingKey, - data //ArrayBuffer of data you want to encrypt - ); - } - - decryptSigningKey(data, key) { - // key will be my private key - return this._crypto.subtle.decrypt( - { - name: 'RSA-OAEP', - modulusLength: 2048, - publicExponent: new Uint8Array([0x01, 0x00, 0x01]), - hash: {name: 'SHA-256'} - //label: Uint8Array([...]) //optional - }, - key, - data //ArrayBuffer of the data - ); - } - - encryptMessage(data, secretKey, iv) { - return this._crypto.subtle.encrypt( - { - name: 'AES-CBC', - //Don't re-use initialization vectors! - //Always generate a new iv every time your encrypt! - iv: iv, - }, - secretKey, //from generateKey or importKey above - data //ArrayBuffer of data you want to encrypt - ); - } - - decryptMessage(data, secretKey, iv) { - return this._crypto.subtle.decrypt( - { - name: 'AES-CBC', - iv: iv, //The initialization vector you used to encrypt - }, - secretKey, //from generateKey or importKey above - data //ArrayBuffer of the data - ); - } - - 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 - jwkData, - { //this is the algorithm options - 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' - ); - } - - importPrimaryKey(jwkData, format) { - // Will be someone's public key - let hashObj = { - name: 'RSA-OAEP' - }; - if (!this._crypto.webkitSubtle) { - 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) - 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 - ); - } - - 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) - 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) - //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' - //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 - ); - } - - signKey(data, keyToSignWith) { - // Will use my private key - return this._crypto.subtle.sign( - { - name: 'HMAC', - hash: {name: 'SHA-256'} - }, - keyToSignWith, //from generateKey or importKey above - data //ArrayBuffer of data you want to sign - ); - } - - verifyKey(signature, data, keyToVerifyWith) { - // Will verify with sender's public key - return this._crypto.subtle.verify( - { - name: 'HMAC', - hash: {name: 'SHA-256'} - }, - keyToVerifyWith, //from generateKey or importKey above - signature, //ArrayBuffer of the signature - data //ArrayBuffer of the data - ); - } - -} diff --git a/src/js/darkwire.js b/src/js/darkwire.js deleted file mode 100644 index e48af4e..0000000 --- a/src/js/darkwire.js +++ /dev/null @@ -1,345 +0,0 @@ -import _ from 'underscore'; -import AudioHandler from './audio'; -import CryptoUtil from './crypto'; - -export default class Darkwire { - constructor() { - this._audio = new AudioHandler(); - this._cryptoUtil = new CryptoUtil(); - this._myUserId = false; - this._connected = false; - this._users = []; - this._fileQueue = []; - this._keys = {}; - } - - getFile(id) { - let file = _.findWhere(this._fileQueue, {id: id}) || false; - - if (file) { - // TODO: Destroy object from memory when retrieved - } - - return file.data; - } - - get keys() { - return this._keys; - } - - set keys(keys) { - this._keys = keys; - return this._keys; - } - - get connected() { - return this._connected; - } - - set connected(state) { - this._connected = state; - return this._connected; - } - - get audio() { - return this._audio; - } - - get users() { - return this._users; - } - - getUserById(id) { - return _.findWhere(this._users, {id: id}); - } - - updateUser(data) { - return new Promise((resolve, reject) => { - let user = this.getUserById(data.id); - - if (!user) { - return reject('User cannot be found'); - } - - let oldUsername = user.username; - - user.username = data.username; - resolve(oldUsername); - }); - } - - addUser(data) { - let importKeysPromises = []; - // Import all user keys if not already there - _.each(data.users, (user) => { - if (!_.findWhere(this._users, {id: user.id})) { - let promise = new Promise((resolve, reject) => { - let currentUser = user; - Promise.all([ - this._cryptoUtil.importPrimaryKey(currentUser.publicKey, 'spki') - ]) - .then((keys) => { - this._users.push({ - id: currentUser.id, - username: currentUser.username, - publicKey: keys[0] - }); - resolve(); - }); - }); - - importKeysPromises.push(promise); - } - }); - - if (!this._myUserId) { - // Set my id if not already set - let me = _.findWhere(data.users, {username: username}); - this._myUserId = me.id; - } - - return importKeysPromises; - } - - createUser(resolve, reject, username) { - Promise.all([ - this._cryptoUtil.createPrimaryKeys() - ]) - .then((data) => { - this._keys = { - public: data[0].publicKey, - private: data[0].privateKey - }; - return Promise.all([ - this._cryptoUtil.exportKey(data[0].publicKey, 'spki') - ]); - }) - .then((exportedKeys) => { - if (!exportedKeys) { - return reject('Could not create a user session'); - } - - return resolve({ - username: username, - publicKey: exportedKeys[0] - }); - }); - } - - checkSessionUsernames(username) { - let matches = _.find(this._users, (users) => { - return username.toLowerCase() === users.username.toLowerCase(); - }); - - if (matches && matches.username) { - return matches; - } - - return false; - } - - removeUser(data) { - this._users = _.without(this._users, _.findWhere(this._users, {id: data.id})); - return this._users; - } - - updateUsername(username) { - let user = null; - - return new Promise((resolve, reject) => { - // Check if user is here - if (username) { - user = this.getUserById(this._myUserId); - } - - if (user) { - // User exists and is attempting to change username - // Check if anyone else is using the requested username - let userExists = this.checkSessionUsernames(username); - - if (userExists) { - // Someone else is using the username requested, allow reformatting - // if it is owned by the user, else reject the promise - if (userExists.id !== this._myUserId) { - return reject(username + ' is being used by someone else in this chat session.'); - } - } - - return resolve({ - username: username, - publicKey: user.publicKey - }); - } - - // User doesn't exist, create the user - return this.createUser(resolve, reject, username); - }); - } - - encodeMessage(message, messageType, additionalData) { - // Don't send unless other users exist - return new Promise((resolve, reject) => { - additionalData = additionalData || {}; - - // if there is a non-empty message and a socket connection - if (message && this._connected) { - let vector = this._cryptoUtil.crypto.getRandomValues(new Uint8Array(16)); - - let secretKey = null; - let secretKeys = null; - let messageData = null; - let signature = null; - let signingKey = null; - let encryptedMessageData = null; - let messageToEncode = { - text: escape(message), - additionalData: additionalData - }; - - messageToEncode = JSON.stringify(messageToEncode); - // Generate new secret key and vector for each message - this._cryptoUtil.createSecretKey() - .then((key) => { - secretKey = key; - return this._cryptoUtil.createSigningKey(); - }) - .then((key) => { - signingKey = key; - // Generate secretKey and encrypt with each user's public key - let promises = []; - _.each(this._users, (user) => { - // If not me - if (user.username !== window.username) { - let promise = new Promise((res, rej) => { - let thisUser = user; - - let secretKeyStr; - - // Export secret key - this._cryptoUtil.exportKey(secretKey, 'raw') - .then((data) => { - return this._cryptoUtil.encryptSecretKey(data, thisUser.publicKey); - }) - .then((encryptedSecretKey) => { - let encData = new Uint8Array(encryptedSecretKey); - secretKeyStr = this._cryptoUtil.convertArrayBufferViewToString(encData); - // Export HMAC signing key - return this._cryptoUtil.exportKey(signingKey, 'raw'); - }) - .then((data) => { - // Encrypt signing key with user's public key - return this._cryptoUtil.encryptSigningKey(data, thisUser.publicKey); - }) - .then((encryptedSigningKey) => { - let encData = new Uint8Array(encryptedSigningKey); - var str = this._cryptoUtil.convertArrayBufferViewToString(encData); - res({ - id: thisUser.id, - secretKey: secretKeyStr, - encryptedSigningKey: str - }); - }); - }); - promises.push(promise); - } - }); - return Promise.all(promises); - }) - .then((data) => { - secretKeys = data; - messageData = this._cryptoUtil.convertStringToArrayBufferView(messageToEncode); - return this._cryptoUtil.signKey(messageData, signingKey); - }) - .then((data) => { - signature = data; - return this._cryptoUtil.encryptMessage(messageData, secretKey, vector); - }) - .then((data) => { - encryptedMessageData = data; - let vct = this._cryptoUtil.convertArrayBufferViewToString(new Uint8Array(vector)); - let sig = this._cryptoUtil.convertArrayBufferViewToString(new Uint8Array(signature)); - let msg = this._cryptoUtil.convertArrayBufferViewToString(new Uint8Array(encryptedMessageData)); - - resolve({ - message: msg, - vector: vct, - messageType: messageType, - secretKeys: secretKeys, - signature: sig - }); - }); - } - - }); - } - - decodeMessage(data) { - return new Promise((resolve, reject) => { - let message = data.message; - let messageData = this._cryptoUtil.convertStringToArrayBufferView(message); - let username = data.username; - let senderId = data.id; - let vector = data.vector; - let vectorData = this._cryptoUtil.convertStringToArrayBufferView(vector); - let secretKeys = data.secretKeys; - let decryptedMessageData; - let decryptedMessage; - - let mySecretKey = _.find(secretKeys, (key) => { - return key.id === this._myUserId; - }); - let signature = data.signature; - let signatureData = this._cryptoUtil.convertStringToArrayBufferView(signature); - let secretKeyArrayBuffer = this._cryptoUtil.convertStringToArrayBufferView(mySecretKey.secretKey); - let signingKeyArrayBuffer = this._cryptoUtil.convertStringToArrayBufferView(mySecretKey.encryptedSigningKey); - - this._cryptoUtil.decryptSecretKey(secretKeyArrayBuffer, this._keys.private) - .then((data) => { - return this._cryptoUtil.importSecretKey(new Uint8Array(data), 'raw'); - }) - .then((data) => { - let secretKey = data; - return this._cryptoUtil.decryptMessage(messageData, secretKey, vectorData); - }) - .then((data) => { - decryptedMessageData = data; - decryptedMessage = JSON.parse(this._cryptoUtil.convertArrayBufferViewToString(new Uint8Array(data))); - return this._cryptoUtil.decryptSigningKey(signingKeyArrayBuffer, this._keys.private); - }) - .then((data) => { - return this._cryptoUtil.importSigningKey(new Uint8Array(data), 'raw'); - }) - .then((data) => { - let signingKey = data; - return this._cryptoUtil.verifyKey(signatureData, decryptedMessageData, signingKey); - }) - .then((bool) => { - if (bool) { - resolve({ - username: username, - message: decryptedMessage, - messageType: data.messageType, - }); - } - }); - }); - } - - generateMessage(fileId, fileName, messageType) { - let message = '
    is attempting to send you ' + fileName + ' (' + messageType + ')'; - message += '
    WARNING: We cannot strictly verify the integrity of this file, its recipients or its owners. By accepting this file, you are liable for any risks that may arise from reciving this file.'; - message += '
    Accept File
    '; - - return message; - } - - addFileToQueue(data) { - let fileData = { - id: data.additionalData.fileId, - data: data - }; - this._fileQueue.push(fileData); - return this.generateMessage(data.additionalData.fileId, data.additionalData.fileName, data.messageType); - } - -} diff --git a/src/js/fileHandler.js b/src/js/fileHandler.js deleted file mode 100644 index a64b561..0000000 --- a/src/js/fileHandler.js +++ /dev/null @@ -1,142 +0,0 @@ -import _ from 'underscore'; -import uuid from 'uuid'; - -export default class FileHandler { - constructor(darkwire, socket, chat) { - this.localFileQueue = []; - if (window.File && window.FileReader && window.FileList && window.Blob && window.btoa && window.atob && window.Blob && window.URL) { - this._isSupported = true; - this.darkwire = darkwire; - this.socket = socket; - this.chat = chat; - this.listen(); - } else { - this._isSupported = false; - } - } - - get isSupported() { - return this._isSupported; - } - - set isSupported(state) { - this._isSupported = state; - return this; - } - - confirmTransfer(event) { - const validFileTypes = ['png','jpg','jpeg','gif','zip','rar','gzip','pdf','txt','json','doc','docx','csv','js','html','css']; - const file = event.target.files && event.target.files[0]; - const fileName = this.sanitizeFileName(file.name); - - if (file) { - const fileExt = file.name.split('.').pop().toLowerCase(); - - if (validFileTypes.indexOf(fileExt) <= -1) { - alert('file type not supported'); - return false; - } - - // Support for only 1MB - if (file.size > 1000000) { - alert('Max filesize is 1MB.'); - return false; - } - let fileId = uuid.v4(); - - let confirmMessage = 'You are about to send ' + fileName + ' to all participants in this chat. Confirm | Cancel'; - let fileData = { - id: fileId, - file: file, - fileName: fileName - }; - this.localFileQueue.push(fileData); - this.chat.addChatMessage({ - username: username, - message: confirmMessage - }); - this.filesSent++; - - } - - return false; - } - - encodeFile(fileId) { - const fileData = _.findWhere(this.localFileQueue, {id: fileId}); - const file = fileData.file || false; - - if (file) { - // TODO: Remove file from local queue - } else { - return false; - } - - const reader = new FileReader(); - const fileType = file.type || 'file'; - - reader.onload = (readerEvent) => { - const base64 = window.btoa(readerEvent.target.result); - const additionalData = { - fileId: fileId, - fileName: this.sanitizeFileName(file.name) - }; - this.darkwire.encodeMessage(base64, fileType, additionalData).then((socketData) => { - this.chat.replaceMessage('#transfer-' + fileId, 'Sent ' + additionalData.fileName + ''); - this.socket.emit('new message', socketData); - }); - this.resetInput(); - }; - - reader.readAsBinaryString(file); - } - - destroyFile(fileId) { - const file = _.findWhere(this.localFileQueue, {id: fileId}); - this.localFileQueue = _.without(this.localFileQueue, file); - this.resetInput(); - return this.chat.replaceMessage('#transfer-' + fileId, 'The file transfer for ' + file.fileName + ' has been canceled.'); - } - - createBlob(base64, fileType) { - base64 = unescape(base64); - return new Promise((resolve, reject) => { - const sliceSize = 1024; - let byteCharacters = window.atob(base64); - let byteArrays = []; - - for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) { - let slice = byteCharacters.slice(offset, offset + sliceSize); - - let byteNumbers = new Array(slice.length); - for (let i = 0; i < slice.length; i++) { - byteNumbers[i] = slice.charCodeAt(i); - } - - let byteArray = new Uint8Array(byteNumbers); - - byteArrays.push(byteArray); - } - - resolve(new window.Blob(byteArrays, {type: fileType})); - }); - } - - createUrlFromBlob(blob) { - return window.URL.createObjectURL(blob); - } - - sanitizeFileName(str) { - return str.replace(/[<>]/ig, ''); - } - - listen() { - // browser API - document.getElementById('fileInput').addEventListener('change', this.confirmTransfer.bind(this), false); - return this; - } - - resetInput() { - document.getElementById('fileInput').value = ''; - } -} diff --git a/src/js/main.js b/src/js/main.js deleted file mode 100644 index 68313da..0000000 --- a/src/js/main.js +++ /dev/null @@ -1,5 +0,0 @@ -import App from './app'; - -$(() => { - let app = new App(); -}); diff --git a/src/js/window.js b/src/js/window.js deleted file mode 100644 index 99992d6..0000000 --- a/src/js/window.js +++ /dev/null @@ -1,86 +0,0 @@ -import FileHandler from './fileHandler'; - -export default class WindowHandler { - constructor(darkwire, socket, chat) { - this._isActive = false; - this.fileHandler = new FileHandler(darkwire, socket, chat); - this.socket = socket; - this.chat = chat; - this.keyMapping = []; - - this.newMessages = 0; - this.favicon = new Favico({ - animation: 'pop', - type: 'rectangle' - }); - - this.enableFileTransfer(); - this.bindEvents(); - } - - get isActive() { - return this._isActive; - } - - set isActive(active) { - this._isActive = active; - return this; - } - - notifyFavicon() { - this.newMessages++; - this.favicon.badge(this.newMessages); - } - - enableFileTransfer() { - if (this.fileHandler.isSupported) { - $('#send-file').click((e) => { - e.preventDefault(); - $('#fileInput').trigger('click'); - }); - } else { - $('#send-file').remove(); - $('#fileInput').remove(); - } - } - - bindEvents() { - window.onfocus = () => { - this._isActive = true; - this.newMessages = 0; - this.favicon.reset(); - }; - - window.onblur = () => { - this._isActive = false; - }; - - // Keyboard events - window.onkeydown = (event) => { - // When the client hits ENTER on their keyboard and chat message input is focused - if (event.which === 13 && !event.shiftKey && $('.inputMessage').is(':focus')) { - handleMessageSending(); - this.socket.emit('stop typing'); - this.chat.typing = false; - event.preventDefault(); - } else { - this.keyMapping[event.keyCode] = event.type === 'keydown'; - } - }; - - window.onkeyup = (event) => { - /** - * 17: CTRL - * 91: Left CMD - * 93: Right CMD - * 75: K - */ - if ((this.keyMapping[17] || this.keyMapping[91] || this.keyMapping[93]) && this.keyMapping[75]) { - this.chat.clear(); - } - this.keyMapping = []; - }; - - } - -} diff --git a/src/public/beep.mp3 b/src/public/beep.mp3 deleted file mode 100644 index 738c24f..0000000 Binary files a/src/public/beep.mp3 and /dev/null differ diff --git a/src/public/favicon.ico b/src/public/favicon.ico deleted file mode 100644 index a63f938..0000000 Binary files a/src/public/favicon.ico and /dev/null differ diff --git a/src/public/favicon.js b/src/public/favicon.js deleted file mode 100644 index bade9bc..0000000 --- a/src/public/favicon.js +++ /dev/null @@ -1,859 +0,0 @@ -/** - * @license MIT or GPL-2.0 - * @fileOverview Favico animations - * @author Miroslav Magda, http://blog.ejci.net - * @version 0.3.10 - */ - -/** - * Create new favico instance - * @param {Object} Options - * @return {Object} Favico object - * @example - * var favico = new Favico({ - * bgColor : '#d00', - * textColor : '#fff', - * fontFamily : 'sans-serif', - * fontStyle : 'bold', - * position : 'down', - * type : 'circle', - * animation : 'slide', - * dataUrl: function(url){}, - * win: top - * }); - */ -(function () { - - var Favico = (function (opt) { - 'use strict'; - opt = (opt) ? opt : {}; - var _def = { - bgColor: '#d00', - textColor: '#fff', - fontFamily: 'sans-serif', //Arial,Verdana,Times New Roman,serif,sans-serif,... - fontStyle: 'bold', //normal,italic,oblique,bold,bolder,lighter,100,200,300,400,500,600,700,800,900 - type: 'circle', - position: 'down', // down, up, left, leftup (upleft) - animation: 'slide', - elementId: false, - dataUrl: false, - win: window - }; - var _opt, _orig, _h, _w, _canvas, _context, _img, _ready, _lastBadge, _running, _readyCb, _stop, _browser, _animTimeout, _drawTimeout, _doc; - - _browser = {}; - _browser.ff = typeof InstallTrigger != 'undefined'; - _browser.chrome = !!window.chrome; - _browser.opera = !!window.opera || navigator.userAgent.indexOf('Opera') >= 0; - _browser.ie = /*@cc_on!@*/false; - _browser.safari = Object.prototype.toString.call(window.HTMLElement).indexOf('Constructor') > 0; - _browser.supported = (_browser.chrome || _browser.ff || _browser.opera); - - var _queue = []; - _readyCb = function () { - }; - _ready = _stop = false; - /** - * Initialize favico - */ - var init = function () { - //merge initial options - _opt = merge(_def, opt); - _opt.bgColor = hexToRgb(_opt.bgColor); - _opt.textColor = hexToRgb(_opt.textColor); - _opt.position = _opt.position.toLowerCase(); - _opt.animation = (animation.types['' + _opt.animation]) ? _opt.animation : _def.animation; - - _doc = _opt.win.document; - - var isUp = _opt.position.indexOf('up') > -1; - var isLeft = _opt.position.indexOf('left') > -1; - - //transform the animations - if (isUp || isLeft) { - for (var a in animation.types) { - for (var i = 0; i < animation.types[a].length; i++) { - var step = animation.types[a][i]; - - if (isUp) { - if (step.y < 0.6) { - step.y = step.y - 0.4; - } else { - step.y = step.y - 2 * step.y + (1 - step.w); - } - } - - if (isLeft) { - if (step.x < 0.6) { - step.x = step.x - 0.4; - } else { - step.x = step.x - 2 * step.x + (1 - step.h); - } - } - - animation.types[a][i] = step; - } - } - } - _opt.type = (type['' + _opt.type]) ? _opt.type : _def.type; - - _orig = link.getIcon(); - //create temp canvas - _canvas = document.createElement('canvas'); - //create temp image - _img = document.createElement('img'); - if (_orig.hasAttribute('href')) { - _img.setAttribute('crossOrigin', 'anonymous'); - //get width/height - _img.onload = function () { - _h = (_img.height > 0) ? _img.height : 32; - _w = (_img.width > 0) ? _img.width : 32; - _canvas.height = _h; - _canvas.width = _w; - _context = _canvas.getContext('2d'); - icon.ready(); - }; - _img.setAttribute('src', _orig.getAttribute('href')); - } else { - _img.onload = function () { - _h = 32; - _w = 32; - _img.height = _h; - _img.width = _w; - _canvas.height = _h; - _canvas.width = _w; - _context = _canvas.getContext('2d'); - icon.ready(); - }; - _img.setAttribute('src', ''); - } - - }; - /** - * Icon namespace - */ - var icon = {}; - /** - * Icon is ready (reset icon) and start animation (if ther is any) - */ - icon.ready = function () { - _ready = true; - icon.reset(); - _readyCb(); - }; - /** - * Reset icon to default state - */ - icon.reset = function () { - //reset - if (!_ready) { - return; - } - _queue = []; - _lastBadge = false; - _running = false; - _context.clearRect(0, 0, _w, _h); - _context.drawImage(_img, 0, 0, _w, _h); - //_stop=true; - link.setIcon(_canvas); - //webcam('stop'); - //video('stop'); - window.clearTimeout(_animTimeout); - window.clearTimeout(_drawTimeout); - }; - /** - * Start animation - */ - icon.start = function () { - if (!_ready || _running) { - return; - } - var finished = function () { - _lastBadge = _queue[0]; - _running = false; - if (_queue.length > 0) { - _queue.shift(); - icon.start(); - } else { - - } - }; - if (_queue.length > 0) { - _running = true; - var run = function () { - // apply options for this animation - ['type', 'animation', 'bgColor', 'textColor', 'fontFamily', 'fontStyle'].forEach(function (a) { - if (a in _queue[0].options) { - _opt[a] = _queue[0].options[a]; - } - }); - animation.run(_queue[0].options, function () { - finished(); - }, false); - }; - if (_lastBadge) { - animation.run(_lastBadge.options, function () { - run(); - }, true); - } else { - run(); - } - } - }; - - /** - * Badge types - */ - var type = {}; - var options = function (opt) { - opt.n = ((typeof opt.n) === 'number') ? Math.abs(opt.n | 0) : opt.n; - opt.x = _w * opt.x; - opt.y = _h * opt.y; - opt.w = _w * opt.w; - opt.h = _h * opt.h; - opt.len = ("" + opt.n).length; - return opt; - }; - /** - * Generate circle - * @param {Object} opt Badge options - */ - type.circle = function (opt) { - opt = options(opt); - var more = false; - if (opt.len === 2) { - opt.x = opt.x - opt.w * 0.4; - opt.w = opt.w * 1.4; - more = true; - } else if (opt.len >= 3) { - opt.x = opt.x - opt.w * 0.65; - opt.w = opt.w * 1.65; - more = true; - } - _context.clearRect(0, 0, _w, _h); - _context.drawImage(_img, 0, 0, _w, _h); - _context.beginPath(); - _context.font = _opt.fontStyle + " " + Math.floor(opt.h * (opt.n > 99 ? 0.85 : 1)) + "px " + _opt.fontFamily; - _context.textAlign = 'center'; - if (more) { - _context.moveTo(opt.x + opt.w / 2, opt.y); - _context.lineTo(opt.x + opt.w - opt.h / 2, opt.y); - _context.quadraticCurveTo(opt.x + opt.w, opt.y, opt.x + opt.w, opt.y + opt.h / 2); - _context.lineTo(opt.x + opt.w, opt.y + opt.h - opt.h / 2); - _context.quadraticCurveTo(opt.x + opt.w, opt.y + opt.h, opt.x + opt.w - opt.h / 2, opt.y + opt.h); - _context.lineTo(opt.x + opt.h / 2, opt.y + opt.h); - _context.quadraticCurveTo(opt.x, opt.y + opt.h, opt.x, opt.y + opt.h - opt.h / 2); - _context.lineTo(opt.x, opt.y + opt.h / 2); - _context.quadraticCurveTo(opt.x, opt.y, opt.x + opt.h / 2, opt.y); - } else { - _context.arc(opt.x + opt.w / 2, opt.y + opt.h / 2, opt.h / 2, 0, 2 * Math.PI); - } - _context.fillStyle = 'rgba(' + _opt.bgColor.r + ',' + _opt.bgColor.g + ',' + _opt.bgColor.b + ',' + opt.o + ')'; - _context.fill(); - _context.closePath(); - _context.beginPath(); - _context.stroke(); - _context.fillStyle = 'rgba(' + _opt.textColor.r + ',' + _opt.textColor.g + ',' + _opt.textColor.b + ',' + opt.o + ')'; - //_context.fillText((more) ? '9+' : opt.n, Math.floor(opt.x + opt.w / 2), Math.floor(opt.y + opt.h - opt.h * 0.15)); - if ((typeof opt.n) === 'number' && opt.n > 999) { - _context.fillText(((opt.n > 9999) ? 9 : Math.floor(opt.n / 1000)) + 'k+', Math.floor(opt.x + opt.w / 2), Math.floor(opt.y + opt.h - opt.h * 0.2)); - } else { - _context.fillText(opt.n, Math.floor(opt.x + opt.w / 2), Math.floor(opt.y + opt.h - opt.h * 0.15)); - } - _context.closePath(); - }; - /** - * Generate rectangle - * @param {Object} opt Badge options - */ - type.rectangle = function (opt) { - opt = options(opt); - var more = false; - if (opt.len === 2) { - opt.x = opt.x - opt.w * 0.4; - opt.w = opt.w * 1.4; - more = true; - } else if (opt.len >= 3) { - opt.x = opt.x - opt.w * 0.65; - opt.w = opt.w * 1.65; - more = true; - } - _context.clearRect(0, 0, _w, _h); - _context.drawImage(_img, 0, 0, _w, _h); - _context.beginPath(); - _context.font = _opt.fontStyle + " " + Math.floor(opt.h * (opt.n > 99 ? 0.9 : 1)) + "px " + _opt.fontFamily; - _context.textAlign = 'center'; - _context.fillStyle = 'rgba(' + _opt.bgColor.r + ',' + _opt.bgColor.g + ',' + _opt.bgColor.b + ',' + opt.o + ')'; - _context.fillRect(opt.x, opt.y, opt.w, opt.h); - _context.fillStyle = 'rgba(' + _opt.textColor.r + ',' + _opt.textColor.g + ',' + _opt.textColor.b + ',' + opt.o + ')'; - //_context.fillText((more) ? '9+' : opt.n, Math.floor(opt.x + opt.w / 2), Math.floor(opt.y + opt.h - opt.h * 0.15)); - if ((typeof opt.n) === 'number' && opt.n > 999) { - _context.fillText(((opt.n > 9999) ? 9 : Math.floor(opt.n / 1000)) + 'k+', Math.floor(opt.x + opt.w / 2), Math.floor(opt.y + opt.h - opt.h * 0.2)); - } else { - _context.fillText(opt.n, Math.floor(opt.x + opt.w / 2), Math.floor(opt.y + opt.h - opt.h * 0.15)); - } - _context.closePath(); - }; - - /** - * Set badge - */ - var badge = function (number, opts) { - opts = ((typeof opts) === 'string' ? { - animation: opts - } : opts) || {}; - _readyCb = function () { - try { - if (typeof (number) === 'number' ? (number > 0) : (number !== '')) { - var q = { - type: 'badge', - options: { - n: number - } - }; - if ('animation' in opts && animation.types['' + opts.animation]) { - q.options.animation = '' + opts.animation; - } - if ('type' in opts && type['' + opts.type]) { - q.options.type = '' + opts.type; - } - ['bgColor', 'textColor'].forEach(function (o) { - if (o in opts) { - q.options[o] = hexToRgb(opts[o]); - } - }); - ['fontStyle', 'fontFamily'].forEach(function (o) { - if (o in opts) { - q.options[o] = opts[o]; - } - }); - _queue.push(q); - if (_queue.length > 100) { - throw new Error('Too many badges requests in queue.'); - } - icon.start(); - } else { - icon.reset(); - } - } catch (e) { - throw new Error('Error setting badge. Message: ' + e.message); - } - }; - if (_ready) { - _readyCb(); - } - }; - - /** - * Set image as icon - */ - var image = function (imageElement) { - _readyCb = function () { - try { - var w = imageElement.width; - var h = imageElement.height; - var newImg = document.createElement('img'); - var ratio = (w / _w < h / _h) ? (w / _w) : (h / _h); - newImg.setAttribute('crossOrigin', 'anonymous'); - newImg.onload=function(){ - _context.clearRect(0, 0, _w, _h); - _context.drawImage(newImg, 0, 0, _w, _h); - link.setIcon(_canvas); - }; - newImg.setAttribute('src', imageElement.getAttribute('src')); - newImg.height = (h / ratio); - newImg.width = (w / ratio); - } catch (e) { - throw new Error('Error setting image. Message: ' + e.message); - } - }; - if (_ready) { - _readyCb(); - } - }; - /** - * Set video as icon - */ - var video = function (videoElement) { - _readyCb = function () { - try { - if (videoElement === 'stop') { - _stop = true; - icon.reset(); - _stop = false; - return; - } - //var w = videoElement.width; - //var h = videoElement.height; - //var ratio = (w / _w < h / _h) ? (w / _w) : (h / _h); - videoElement.addEventListener('play', function () { - drawVideo(this); - }, false); - - } catch (e) { - throw new Error('Error setting video. Message: ' + e.message); - } - }; - if (_ready) { - _readyCb(); - } - }; - /** - * Set video as icon - */ - var webcam = function (action) { - //UR - if (!window.URL || !window.URL.createObjectURL) { - window.URL = window.URL || {}; - window.URL.createObjectURL = function (obj) { - return obj; - }; - } - if (_browser.supported) { - var newVideo = false; - navigator.getUserMedia = navigator.getUserMedia || navigator.oGetUserMedia || navigator.msGetUserMedia || navigator.mozGetUserMedia || navigator.webkitGetUserMedia; - _readyCb = function () { - try { - if (action === 'stop') { - _stop = true; - icon.reset(); - _stop = false; - return; - } - newVideo = document.createElement('video'); - newVideo.width = _w; - newVideo.height = _h; - navigator.getUserMedia({ - video: true, - audio: false - }, function (stream) { - newVideo.src = URL.createObjectURL(stream); - newVideo.play(); - drawVideo(newVideo); - }, function () { - }); - } catch (e) { - throw new Error('Error setting webcam. Message: ' + e.message); - } - }; - if (_ready) { - _readyCb(); - } - } - - }; - - /** - * Draw video to context and repeat :) - */ - function drawVideo(video) { - if (video.paused || video.ended || _stop) { - return false; - } - //nasty hack for FF webcam (Thanks to Julian Ćwirko, kontakt@redsunmedia.pl) - try { - _context.clearRect(0, 0, _w, _h); - _context.drawImage(video, 0, 0, _w, _h); - } catch (e) { - - } - _drawTimeout = setTimeout(function () { - drawVideo(video); - }, animation.duration); - link.setIcon(_canvas); - } - - var link = {}; - /** - * Get icon from HEAD tag or create a new element - */ - link.getIcon = function () { - var elm = false; - //get link element - var getLink = function () { - var link = _doc.getElementsByTagName('head')[0].getElementsByTagName('link'); - for (var l = link.length, i = (l - 1); i >= 0; i--) { - if ((/(^|\s)icon(\s|$)/i).test(link[i].getAttribute('rel'))) { - return link[i]; - } - } - return false; - }; - if (_opt.element) { - elm = _opt.element; - } else if (_opt.elementId) { - //if img element identified by elementId - elm = _doc.getElementById(_opt.elementId); - elm.setAttribute('href', elm.getAttribute('src')); - } else { - //if link element - elm = getLink(); - if (elm === false) { - elm = _doc.createElement('link'); - elm.setAttribute('rel', 'icon'); - _doc.getElementsByTagName('head')[0].appendChild(elm); - } - } - elm.setAttribute('type', 'image/png'); - return elm; - }; - link.setIcon = function (canvas) { - var url = canvas.toDataURL('image/png'); - if (_opt.dataUrl) { - //if using custom exporter - _opt.dataUrl(url); - } - if (_opt.element) { - _opt.element.setAttribute('href', url); - _opt.element.setAttribute('src', url); - } else if (_opt.elementId) { - //if is attached to element (image) - var elm = _doc.getElementById(_opt.elementId); - elm.setAttribute('href', url); - elm.setAttribute('src', url); - } else { - //if is attached to fav icon - if (_browser.ff || _browser.opera) { - //for FF we need to "recreate" element, atach to dom and remove old - //var originalType = _orig.getAttribute('rel'); - var old = _orig; - _orig = _doc.createElement('link'); - //_orig.setAttribute('rel', originalType); - if (_browser.opera) { - _orig.setAttribute('rel', 'icon'); - } - _orig.setAttribute('rel', 'icon'); - _orig.setAttribute('type', 'image/png'); - _doc.getElementsByTagName('head')[0].appendChild(_orig); - _orig.setAttribute('href', url); - if (old.parentNode) { - old.parentNode.removeChild(old); - } - } else { - _orig.setAttribute('href', url); - } - } - }; - - //http://stackoverflow.com/questions/5623838/rgb-to-hex-and-hex-to-rgb#answer-5624139 - //HEX to RGB convertor - function hexToRgb(hex) { - var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i; - hex = hex.replace(shorthandRegex, function (m, r, g, b) { - return r + r + g + g + b + b; - }); - var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); - return result ? { - r: parseInt(result[1], 16), - g: parseInt(result[2], 16), - b: parseInt(result[3], 16) - } : false; - } - - /** - * Merge options - */ - function merge(def, opt) { - var mergedOpt = {}; - var attrname; - for (attrname in def) { - mergedOpt[attrname] = def[attrname]; - } - for (attrname in opt) { - mergedOpt[attrname] = opt[attrname]; - } - return mergedOpt; - } - - /** - * Cross-browser page visibility shim - * http://stackoverflow.com/questions/12536562/detect-whether-a-window-is-visible - */ - function isPageHidden() { - return _doc.hidden || _doc.msHidden || _doc.webkitHidden || _doc.mozHidden; - } - - /** - * @namespace animation - */ - var animation = {}; - /** - * Animation "frame" duration - */ - animation.duration = 40; - /** - * Animation types (none,fade,pop,slide) - */ - animation.types = {}; - animation.types.fade = [{ - x: 0.4, - y: 0.4, - w: 0.6, - h: 0.6, - o: 0.0 - }, { - x: 0.4, - y: 0.4, - w: 0.6, - h: 0.6, - o: 0.1 - }, { - x: 0.4, - y: 0.4, - w: 0.6, - h: 0.6, - o: 0.2 - }, { - x: 0.4, - y: 0.4, - w: 0.6, - h: 0.6, - o: 0.3 - }, { - x: 0.4, - y: 0.4, - w: 0.6, - h: 0.6, - o: 0.4 - }, { - x: 0.4, - y: 0.4, - w: 0.6, - h: 0.6, - o: 0.5 - }, { - x: 0.4, - y: 0.4, - w: 0.6, - h: 0.6, - o: 0.6 - }, { - x: 0.4, - y: 0.4, - w: 0.6, - h: 0.6, - o: 0.7 - }, { - x: 0.4, - y: 0.4, - w: 0.6, - h: 0.6, - o: 0.8 - }, { - x: 0.4, - y: 0.4, - w: 0.6, - h: 0.6, - o: 0.9 - }, { - x: 0.4, - y: 0.4, - w: 0.6, - h: 0.6, - o: 1.0 - }]; - animation.types.none = [{ - x: 0.4, - y: 0.4, - w: 0.6, - h: 0.6, - o: 1 - }]; - animation.types.pop = [{ - x: 1, - y: 1, - w: 0, - h: 0, - o: 1 - }, { - x: 0.9, - y: 0.9, - w: 0.1, - h: 0.1, - o: 1 - }, { - x: 0.8, - y: 0.8, - w: 0.2, - h: 0.2, - o: 1 - }, { - x: 0.7, - y: 0.7, - w: 0.3, - h: 0.3, - o: 1 - }, { - x: 0.6, - y: 0.6, - w: 0.4, - h: 0.4, - o: 1 - }, { - x: 0.5, - y: 0.5, - w: 0.5, - h: 0.5, - o: 1 - }, { - x: 0.4, - y: 0.4, - w: 0.6, - h: 0.6, - o: 1 - }]; - animation.types.popFade = [{ - x: 0.75, - y: 0.75, - w: 0, - h: 0, - o: 0 - }, { - x: 0.65, - y: 0.65, - w: 0.1, - h: 0.1, - o: 0.2 - }, { - x: 0.6, - y: 0.6, - w: 0.2, - h: 0.2, - o: 0.4 - }, { - x: 0.55, - y: 0.55, - w: 0.3, - h: 0.3, - o: 0.6 - }, { - x: 0.50, - y: 0.50, - w: 0.4, - h: 0.4, - o: 0.8 - }, { - x: 0.45, - y: 0.45, - w: 0.5, - h: 0.5, - o: 0.9 - }, { - x: 0.4, - y: 0.4, - w: 0.6, - h: 0.6, - o: 1 - }]; - animation.types.slide = [{ - x: 0.4, - y: 1, - w: 0.6, - h: 0.6, - o: 1 - }, { - x: 0.4, - y: 0.9, - w: 0.6, - h: 0.6, - o: 1 - }, { - x: 0.4, - y: 0.9, - w: 0.6, - h: 0.6, - o: 1 - }, { - x: 0.4, - y: 0.8, - w: 0.6, - h: 0.6, - o: 1 - }, { - x: 0.4, - y: 0.7, - w: 0.6, - h: 0.6, - o: 1 - }, { - x: 0.4, - y: 0.6, - w: 0.6, - h: 0.6, - o: 1 - }, { - x: 0.4, - y: 0.5, - w: 0.6, - h: 0.6, - o: 1 - }, { - x: 0.4, - y: 0.4, - w: 0.6, - h: 0.6, - o: 1 - }]; - /** - * Run animation - * @param {Object} opt Animation options - * @param {Object} cb Callabak after all steps are done - * @param {Object} revert Reverse order? true|false - * @param {Object} step Optional step number (frame bumber) - */ - animation.run = function (opt, cb, revert, step) { - var animationType = animation.types[isPageHidden() ? 'none' : _opt.animation]; - if (revert === true) { - step = (typeof step !== 'undefined') ? step : animationType.length - 1; - } else { - step = (typeof step !== 'undefined') ? step : 0; - } - cb = (cb) ? cb : function () { - }; - if ((step < animationType.length) && (step >= 0)) { - type[_opt.type](merge(opt, animationType[step])); - _animTimeout = setTimeout(function () { - if (revert) { - step = step - 1; - } else { - step = step + 1; - } - animation.run(opt, cb, revert, step); - }, animation.duration); - - link.setIcon(_canvas); - } else { - cb(); - return; - } - }; - //auto init - init(); - return { - badge: badge, - video: video, - image: image, - webcam: webcam, - reset: icon.reset, - browser: { - supported: _browser.supported - } - }; - }); - - // AMD / RequireJS - if (typeof define !== 'undefined' && define.amd) { - define([], function () { - return Favico; - }); - } - // CommonJS - else if (typeof module !== 'undefined' && module.exports) { - module.exports = Favico; - } - // included directly via - - - - - - - - - - - - - - - - - - - {{!-- socket.io automatically servers the client lib from the server at this path --}} - - - - - - - - - - - diff --git a/src/views/noop.mustache b/src/views/noop.mustache deleted file mode 100644 index e964682..0000000 --- a/src/views/noop.mustache +++ /dev/null @@ -1,10 +0,0 @@ - - - - - Nope - - - Room does not exist. - - diff --git a/src/views/partials/ip.mustache b/src/views/partials/ip.mustache deleted file mode 100644 index 6189b18..0000000 --- a/src/views/partials/ip.mustache +++ /dev/null @@ -1,4 +0,0 @@ -{{#APP.ip}} -

    Recognized IP: {{APP.ip}}*

    -

    *This IP is your public IP recognized by all web servers. If this IP and/or its location data seems familiar to you, we recommend using TOR or a proxy before starting a new chat session.

    -{{/APP.ip}} diff --git a/test/acceptance/app.js b/test/acceptance/app.js deleted file mode 100644 index 72185ca..0000000 --- a/test/acceptance/app.js +++ /dev/null @@ -1,195 +0,0 @@ -/*jshint -W030 */ -import App from '../../package.json'; -import moment from 'moment'; - -describe('Darkwire', () => { - - describe('Creating a room', () => { - - var testingRoom = null; - let browser; - - before((client, done) => { - browser = client - .url('http://localhost:3000/', () => { - done(); - }); - }); - - after((client, done) => { - browser.end(() => { - done(); - }); - }); - - it('Should show welcome modal', () => { - browser - .waitForElementVisible('#first-modal', 5000) - .expect.element('#first-modal').to.be.visible; - }); - - it('Should be started with NPM', () => { - browser.expect.element('#first-modal .modal-title').text.to.equal('Welcome to darkwire.io v' + App.version); - }); - - describe('Joining chat room', () => { - - before((client, done) => { - // Close modal - browser.click('#first-modal .modal-footer button'); - browser.url((result) => { - let urlSplit = result.value.split('/'); - testingRoom = urlSplit[urlSplit.length - 1]; - let url = 'http://localhost:3000/' + testingRoom; - browser.execute(() => { - window.open('http://localhost:3000/', '_blank'); - }, [], () => { - browser.windowHandles((result) => { - browser.switchWindow(result.value[1], () => { - browser.execute((id) => { - // Open a new chat room at the same URL so we have 2 participants - window.open('http://localhost:3000/' + id, '_self'); - }, [testingRoom], () => { - done(); - }); - }); - }); - }); - }); - }); - - it('Should not show welcome modal', () => { - browser.assert.hidden('#first-modal'); - }); - - describe('Sending chat message', () => { - let timestamp = null; - - before((client, done) => { - browser - .waitForElementPresent('ul.users li:nth-child(2)', 5000) - .waitForElementPresent('textarea.inputMessage', 5000) - .setValue('textarea.inputMessage', ['Hello world!', browser.Keys.RETURN], () => { - timestamp = moment(new Date()).format('LT'); - done(); - }); - }); - - it('Should send a message', () => { - browser.windowHandles((result) => { - browser.switchWindow(result.value[0], () => { - browser - .waitForElementVisible('span.messageBody', 5000) - .assert.containsText('span.messageBody', 'Hello world!'); - }); - }); - }); - - it('Should show correct timestamp', () => { - browser.windowHandles((result) => { - browser.switchWindow(result.value[0], () => { - browser - .waitForElementVisible('span.timestamp', 5000) - .assert.containsText('span.timestamp', timestamp); - }); - }); - }); - - }); - - describe('Slash Commands', () => { - - describe('/me', () => { - - before((client, done) => { - browser - .waitForElementPresent('textarea.inputMessage', 5000) - .clearValue('textarea.inputMessage') - .setValue('textarea.inputMessage', ['/me is no stranger to love', browser.Keys.RETURN]) - .waitForElementVisible('.action span.messageBody', 5000, () => { - done(); - }); - }); - - it('Should express an interactive action', () => { - browser.assert.containsText('.action span.messageBody', 'is no stranger to love'); - }); - - }); - - describe('/nick', () => { - - before((client, done) => { - browser - .waitForElementPresent('textarea.inputMessage', 5000) - .clearValue('textarea.inputMessage') - .setValue('textarea.inputMessage', ['/nick rickAnsley', browser.Keys.RETURN]) - .waitForElementVisible('.change-username-warning', 5000) - .clearValue('textarea.inputMessage') - .setValue('textarea.inputMessage', ['/nick rickAnsley', browser.Keys.RETURN]) - .waitForElementVisible('.log.changed-name', 5000, () => { - done(); - }); - }); - - it('Should change username', () => { - browser.assert.containsText('.log.changed-name', 'rickAnsley'); - }); - - }); - - describe('/clear', () => { - - before((client, done) => { - browser - .waitForElementPresent('textarea.inputMessage', 5000) - .clearValue('textarea.inputMessage') - .setValue('textarea.inputMessage', ['/clear', browser.Keys.RETURN], () => { - done(); - }); - }); - - it('Should clear chat buffer', () => { - browser.waitForElementNotPresent('.messages li', 5000); - }); - - }); - - }); - - describe('Before file transfer: Image: Confirm sending', () => { - - before((client, done) => { - browser.waitForElementPresent('#send-file', 5000, () => { - browser.execute(() => { - $('input[name="fileUploader"]').show(); - }, [], () => { - browser.waitForElementPresent('input[name="fileUploader"]', 5000, () => { - let testFile = __dirname + '/ricky.jpg'; - browser.setValue('input[name="fileUploader"]', testFile, (result) => { - done(); - }); - }); - }); - }); - }); - - it('Should prompt user confirmation', () => { - browser - .waitForElementVisible('span.messageBody .file-presend-prompt', 5000) - .assert.containsText('span.messageBody .file-presend-prompt', 'You are about to send ricky.jpg to all participants in this chat. Confirm | Cancel'); - }); - - it('Should show sent confirmation message', () => { - browser - .waitForElementVisible('a.file-trigger-confirm', 5000) - .click('a.file-trigger-confirm') - .waitForElementNotPresent('a.file-trigger-confirm', 5000) - .assert.containsText('.message .file-presend-prompt', 'Sent ricky.jpg'); - }); - - }); - }); - - }); -}); diff --git a/test/acceptance/bin/selenium-server-standalone-2.52.0.jar b/test/acceptance/bin/selenium-server-standalone-2.52.0.jar deleted file mode 100644 index f62a462..0000000 Binary files a/test/acceptance/bin/selenium-server-standalone-2.52.0.jar and /dev/null differ diff --git a/test/acceptance/index.js b/test/acceptance/index.js deleted file mode 100644 index 36104ea..0000000 --- a/test/acceptance/index.js +++ /dev/null @@ -1,2 +0,0 @@ -require('babel-register')(); -require('./app.js'); diff --git a/test/acceptance/nightwatch-local.json b/test/acceptance/nightwatch-local.json deleted file mode 100644 index a5fd87c..0000000 --- a/test/acceptance/nightwatch-local.json +++ /dev/null @@ -1,52 +0,0 @@ -{ - "src_folders" : ["test"], - "output_folder" : "reports", - "custom_commands_path" : "", - "custom_assertions_path" : "", - "page_objects_path" : "", - "globals_path" : "", - "test_runner" : "mocha", - "selenium" : { - "start_process" : true, - "server_path" : "test/acceptance/bin/selenium-server-standalone-2.52.0.jar", - "log_path" : false, - "host" : "127.0.0.1", - "port" : 4444, - "cli_args" : { - "webdriver.chrome.driver" : "/usr/local/bin/chromedriver", - "webdriver.ie.driver" : "" - } - }, - - "test_settings" : { - "default" : { - "launch_url" : "http://localhost", - "selenium_port" : 4444, - "selenium_host" : "localhost", - "silent": true, - "screenshots" : { - "enabled" : false, - "path" : "" - }, - "desiredCapabilities": { - "browserName": "chrome", - "javascriptEnabled": true, - "acceptSslCerts": true, - "chromeOptions" : { - "binary": "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" - } - } - }, - - "chrome" : { - "desiredCapabilities": { - "browserName": "chrome", - "javascriptEnabled": true, - "acceptSslCerts": true, - "chromeOptions" : { - "args" : ["-e", "--no-sandbox"] - } - } - } - } -} diff --git a/test/acceptance/nightwatch.json b/test/acceptance/nightwatch.json deleted file mode 100644 index 852be86..0000000 --- a/test/acceptance/nightwatch.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "src_folders" : ["test"], - "output_folder" : "reports", - "custom_commands_path" : "", - "custom_assertions_path" : "", - "page_objects_path" : "", - "globals_path" : "", - "test_runner" : "mocha", - "selenium" : { - "start_process" : true, - "server_path" : "test/acceptance/bin/selenium-server-standalone-2.52.0.jar", - "log_path" : false, - "host" : "127.0.0.1", - "port" : 4444, - "cli_args" : { - "webdriver.chrome.driver" : "/usr/bin/chromedriver", - "webdriver.ie.driver" : "" - } - }, - - "test_settings" : { - "default" : { - "launch_url" : "http://localhost", - "selenium_port" : 4444, - "selenium_host" : "localhost", - "silent": true, - "screenshots" : { - "enabled" : false, - "path" : "" - }, - "desiredCapabilities": { - "browserName": "firefox", - "javascriptEnabled": true, - "acceptSslCerts": true - } - }, - - "chrome" : { - "desiredCapabilities": { - "browserName": "chrome", - "javascriptEnabled": true, - "acceptSslCerts": true, - "chromeOptions" : { - "args" : ["-e", "--no-sandbox"] - } - } - } - } -} diff --git a/test/acceptance/ricky.jpg b/test/acceptance/ricky.jpg deleted file mode 100644 index 5bca4a8..0000000 Binary files a/test/acceptance/ricky.jpg and /dev/null differ diff --git a/test/app.js b/test/app.js deleted file mode 100644 index afbd0cf..0000000 --- a/test/app.js +++ /dev/null @@ -1,80 +0,0 @@ -import helpers from './helpers'; -import app from '../index'; -import mochaJSCS from 'mocha-jscs'; -import mochaJSHint from 'mocha-jshint'; - -const Browser = require('zombie'); -Browser.localhost('localhost', 3000); - -mochaJSCS(); -mochaJSHint(); - -describe('Visiting /', () => { - - const browser = new Browser(); - - before((done) => { - browser.on('active', () => { - // browser.evaluate needs a string, so this regex just extracts the body of the function as a string - browser.evaluate(helpers.polyfillCrypto.toString().match(/function[^{]+\{([\s\S]*)\}$/)[1]); - }); - - browser.visit('/', done); - }); - - it('should be successful', () => { - browser.assert.success(); - }); - - it('should show welcome modal', () => { - browser.assert.evaluate('$("#first-modal:visible").length', 1); - browser.assert.text('#first-modal h4.modal-title', 'Welcome to darkwire.io'); - }); - - describe('closing the initial modal', () => { - - before((done) => { - browser.pressButton('#first-modal .modal-footer button', done); - }); - - it('should close the modal and show the main chat page', () => { - browser.assert.evaluate('$("#first-modal:hidden").length', 1); - }); - - describe('opening another tab', () => { - - before((done) => { - let roomIdSplit = browser.url.split('/'); - let roomId = roomIdSplit[roomIdSplit.length - 1]; - browser.open(); - browser.tabs.current = 1; - browser.visit(`/${roomId}`, done); - }); - - it('should be successful', () => { - browser.assert.success(); - }); - - it('should not show welcome modal', () => { - browser.assert.evaluate('$("#first-modal.fade.in").length', 0); - }); - - describe('sending message', () => { - - before((done) => { - browser.fill('.inputMessage', 'Hello world'); - browser.click('span#send-message-btn', done); - }); - - it('should send message', () => { - browser.tabs.current = 0; - browser.assert.text('body', /Hello world/); - }); - - }); - - }); - - }); - -}); diff --git a/test/helpers.js b/test/helpers.js deleted file mode 100644 index e752691..0000000 --- a/test/helpers.js +++ /dev/null @@ -1,50 +0,0 @@ -var helpers = { - polyfillCrypto: () => { - window.crypto = { - subtle: { - generateKey: () => { - return new Promise((resolve, reject) => { - resolve({}); - }); - }, - exportKey: () => { - return new Promise((resolve, reject) => { - resolve([{}]); - }); - }, - importKey: () => { - return new Promise((resolve, reject) => { - resolve([{}]); - }); - }, - encrypt: () => { - return {}; - }, - decrypt: (opts, key, data) => { - if (opts.name === 'AES-CBC') { - // This means it's decrypted a message - return new Promise((resolve, reject) => { - // "Hello world" as an array buffer - resolve(new Uint8Array([72, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100])); - }); - } else { - return new Promise((resolve, reject) => { - resolve({}); - }); - } - }, - sign: () => { - return {}; - }, - verify: () => { - return true; - } - }, - getRandomValues: () => { - return [1,2,3,4]; - } - }; - } -}; - -module.exports = helpers; diff --git a/test/unit/app.js b/test/unit/app.js deleted file mode 100644 index 57e1662..0000000 --- a/test/unit/app.js +++ /dev/null @@ -1,48 +0,0 @@ -import assert from 'assert'; -import sinon from 'sinon'; -import io from 'socket.io-client/socket.io.js'; -import $ from '../../src/public/vendor/jquery-1.10.2.min.js'; -import Favico from '../../src/public/favicon'; -import Autolinker from '../../src/public/vendor/autolinker.min.js'; - -window.io = io; -window.$ = window.jQuery = $; -window.Favico = Favico; -window.Autolinker = Autolinker; -window.$.fn.bootstrapSwitch = () => { - return { - on: () => { - return; - } - }; -}; -window.FastClick = { - attach: () => { - return true; - } -}; - -var proxyquire = require('proxyquireify')(require); - -var stubs = {}; - -var App = proxyquire('../../src/js/app.js', stubs).default; - -describe('App', () => { - - describe('cleanInput', () => { - - let app; - - before(() => { - app = new App(); - }); - - it('should create HTML links from URLs with "rel=noopener noreferrer"', () => { - let input = app.cleanInput('cnn.com'); - assert.equal(input, 'cnn.com'); - }); - - }); - -}); diff --git a/test/unit/audio.js b/test/unit/audio.js deleted file mode 100644 index 6c6b1bb..0000000 --- a/test/unit/audio.js +++ /dev/null @@ -1,53 +0,0 @@ -import assert from 'assert'; -import AudioHandler from '../../src/js/audio.js'; -import sinon from 'sinon'; - -describe('Audio', () => { - - describe('playing sounds', () => { - - describe('when window.Audio is supported', () => { - - describe('when sound is enabled', () => { - let playStub; - - before(() => { - let audio = new AudioHandler(); - playStub = sinon.stub(audio._beep, 'play'); - audio.play(); - }); - - after(() => { - playStub.reset(); - }); - - it('should play sounds', () => { - assert(playStub.called); - }); - }); - - describe('sound is not enabled', () => { - let playStub; - - before(() => { - let audio = new AudioHandler(); - audio.soundEnabled = false; - playStub = sinon.stub(audio._beep, 'play'); - audio.play(); - }); - - after(() => { - playStub.reset(); - }); - - it('should not play sounds', () => { - assert(playStub.notCalled); - }); - - }); - - }); - - }); - -}); diff --git a/test/unit/darkwire.js b/test/unit/darkwire.js deleted file mode 100644 index 4764d10..0000000 --- a/test/unit/darkwire.js +++ /dev/null @@ -1,56 +0,0 @@ -import assert from 'assert'; -import sinon from 'sinon'; - -var proxyquire = require('proxyquireify')(require); - -let importPrimaryKeyStub = sinon.stub(); - -var stubs = { - './crypto': { - default: function() { - return { - importPrimaryKey: importPrimaryKeyStub - }; - } - } -}; - -var Darkwire = proxyquire('../../src/js/darkwire.js', stubs).default; - -describe('Darkwire', () => { - - describe('adding users', () => { - - before(() => { - document.body.innerHTML = window.__html__['fixtures/app.html']; - window.username = 'alan'; - let darkwire = new Darkwire(); - darkwire._myUserId = 3; - darkwire.addUser( - { - users: [ - { - id: 1, - username: 'user 1', - publicKey: {} - }, - { - id: 2, - username: 'user 2', - publicKey: {} - } - ] - } - ); - }); - - after(() => { - importPrimaryKeyStub.reset(); - }); - - it('should import each users key', () => { - assert.equal(importPrimaryKeyStub.callCount, 2); - }); - }); - -}); diff --git a/test/unit/fixtures/app.html b/test/unit/fixtures/app.html deleted file mode 100644 index 4c476a8..0000000 --- a/test/unit/fixtures/app.html +++ /dev/null @@ -1,3 +0,0 @@ -
    - - \ No newline at end of file diff --git a/test/unit/index.js b/test/unit/index.js deleted file mode 100644 index 8ee7b67..0000000 --- a/test/unit/index.js +++ /dev/null @@ -1,3 +0,0 @@ -require('./audio.js'); -require('./darkwire.js'); -require('./app.js'); diff --git a/test/unit/lint.js b/test/unit/lint.js deleted file mode 100644 index b40541c..0000000 --- a/test/unit/lint.js +++ /dev/null @@ -1,6 +0,0 @@ -import mochaJSCS from 'mocha-jscs'; -import mochaJSHint from 'mocha-jshint'; - -mochaJSCS(); -mochaJSHint(); -