From 43a44793c520bc71b18dcaada67362d80630129b Mon Sep 17 00:00:00 2001 From: Scott Nonnenberg Date: Fri, 6 Jul 2018 17:48:14 -0700 Subject: [PATCH] Remove jshint - move everything over to eslint Also removed all hints of previous linters --- .bowerrc | 4 - .eslintignore | 37 +- .jscsrc | 33 - .jshintrc | 73 -- .prettierignore | 2 +- Gruntfile.js | 285 +++--- about_preload.js | 2 + background.html | 4 - debug_log_preload.js | 2 + js/about_start.js | 6 +- js/chromium.js | 6 +- js/conversation_controller.js | 161 ++- js/debug_log_start.js | 7 +- js/delivery_receipts.js | 132 ++- js/expire.js | 15 +- js/focus_listener.js | 11 +- js/keychange_listener.js | 30 +- js/models/blockedNumbers.js | 16 +- js/permissions_popup_start.js | 8 +- js/read_receipts.js | 112 +-- js/read_syncs.js | 95 +- js/registration.js | 14 +- js/reliable_trigger.js | 44 +- js/rotate_signed_prekey_listener.js | 30 +- js/settings_start.js | 11 +- js/signal_protocol_store.js | 922 +++++++++--------- js/spell_check.js | 263 ++--- js/storage.js | 42 +- js/views/app_view.js | 91 +- js/views/attachment_preview_view.js | 6 +- js/views/banner_view.js | 12 +- js/views/confirmation_dialog_view.js | 16 +- js/views/contact_list_view.js | 1 - js/views/conversation_list_item_view.js | 28 +- js/views/conversation_list_view.js | 24 +- js/views/error_view.js | 13 - js/views/group_member_list_view.js | 12 +- js/views/group_update_view.js | 15 +- js/views/hint_view.js | 8 +- js/views/identicon_svg_view.js | 28 +- js/views/identity_key_send_error_view.js | 22 +- js/views/import_view.js | 158 ++- js/views/install_view.js | 114 ++- js/views/key_verification_view.js | 139 ++- js/views/last_seen_indicator_view.js | 19 +- js/views/list_view.js | 17 +- js/views/message_detail_view.js | 129 ++- js/views/message_list_view.js | 56 +- js/views/network_status_view.js | 62 +- js/views/new_group_update_view.js | 82 +- js/views/phone-input-view.js | 16 +- js/views/recipients_input_view.js | 46 +- js/views/recorder_view.js | 36 +- js/views/scroll_down_button_view.js | 20 +- js/views/standalone_registration_view.js | 49 +- js/views/toast_view.js | 10 +- js/views/whisper_view.js | 63 +- js/wall_clock_listener.js | 14 +- package.json | 4 +- permissions_popup_preload.js | 2 + prepare_import_build.js | 4 + settings_preload.js | 2 + styleguide.config.js | 2 +- test/index.html | 1 - ts/components/ContactListItem.tsx | 6 +- ts/components/conversation/ContactDetail.tsx | 1 + .../conversation/EmbeddedContact.tsx | 8 +- ts/components/conversation/Message.tsx | 42 +- ts/components/conversation/Notification.tsx | 4 +- ts/components/conversation/Quote.tsx | 8 +- yarn.lock | 110 +-- 71 files changed, 1837 insertions(+), 2030 deletions(-) delete mode 100644 .bowerrc delete mode 100644 .jscsrc delete mode 100644 .jshintrc delete mode 100644 js/views/error_view.js diff --git a/.bowerrc b/.bowerrc deleted file mode 100644 index 978041dc6ef9..000000000000 --- a/.bowerrc +++ /dev/null @@ -1,4 +0,0 @@ -{ - "directory": "components/", - "analytics": false -} diff --git a/.eslintignore b/.eslintignore index 4abdcc2192d3..23b77e9d57be 100644 --- a/.eslintignore +++ b/.eslintignore @@ -5,13 +5,9 @@ dist/** # these aren't ready yet, pulling files in one-by-one libtextsecure/** -js/*.js -js/models/**/*.js -js/views/**/*.js test/*.js test/models/*.js test/views/*.js -/*.js # Generated files js/components.js @@ -24,35 +20,12 @@ test/test.js # Third-party files js/Mp3LameEncoder.min.js js/WebAudioRecorderMp3.js +js/libphonenumber-util.js +js/libsignal-protocol-worker.js +libtextsecure/libsignal-protocol.js +libtextsecure/test/blanket_mocha.js +test/blanket_mocha.js # TypeScript generated files ts/**/*.js -# ES2015+ files -!libtextsecure/api.js -!js/background.js -!js/backup.js -!js/database.js -!js/logging.js -!js/models/conversations.js -!js/models/messages.js -!js/notifications.js -!js/expiring_messages.js -!js/views/attachment_view.js -!js/views/backbone_wrapper_view.js -!js/views/clear_data_view.js -!js/views/contact_list_view.js -!js/views/conversation_search_view.js -!js/views/conversation_view.js -!js/views/debug_log_view.js -!js/views/file_input_view.js -!js/views/inbox_view.js -!js/views/message_view.js -!js/views/settings_view.js -!js/views/timestamp_view.js -!test/backup_test.js -!test/views/attachment_view_test.js -!libtextsecure/message_receiver.js -!main.js -!preload.js -!prepare_build.js diff --git a/.jscsrc b/.jscsrc deleted file mode 100644 index 9c02ffffbff0..000000000000 --- a/.jscsrc +++ /dev/null @@ -1,33 +0,0 @@ -{ - "disallowMixedSpacesAndTabs": true, - "disallowTrailingWhitespace": true, - "disallowNewlineBeforeBlockStatements": true, - "requireCommaBeforeLineBreak": true, - "requireSemicolons": true, - "requireSpaceBeforeBlockStatements": true, - "disallowSpacesInNamedFunctionExpression": { - "beforeOpeningRoundBrace": true - }, - "requireSpacesInNamedFunctionExpression": { - "beforeOpeningCurlyBrace": true - }, - "requireCurlyBraces": [ - "if", - "else", - "for", - "while", - "do", - "try", - "catch" - ], - "requireSpaceAfterKeywords": [ - "if", - "else", - "for", - "while", - "case", - "try", - "typeof", - "return" - ] -} diff --git a/.jshintrc b/.jshintrc deleted file mode 100644 index 2ac841797407..000000000000 --- a/.jshintrc +++ /dev/null @@ -1,73 +0,0 @@ -{ - "maxerr" : 50, - "bitwise" : false, - "camelcase" : false, - "curly" : false, - "eqeqeq" : false, - "forin" : false, - "freeze" : false, - "immed" : false, - "indent" : 4, - "latedef" : false, - "newcap" : false, - "noarg" : false, - "noempty" : false, - "nonbsp" : false, - "nonew" : false, - "plusplus" : false, - "quotmark" : false, - "undef" : false, - "unused" : false, - "strict" : false, - "maxparams" : false, - "maxdepth" : false, - "maxstatements" : false, - "maxcomplexity" : false, - "maxlen" : false, - "asi" : false, - "boss" : false, - "debug" : false, - "eqnull" : false, - "es5" : false, - "esversion" : 6, - "esnext" : false, - "moz" : false, - "evil" : false, - "expr" : false, - "funcscope" : false, - "globalstrict" : false, - "iterator" : false, - "lastsemic" : false, - "laxbreak" : true, - "laxcomma" : false, - "loopfunc" : false, - "multistr" : false, - "noyield" : false, - "notypeof" : false, - "proto" : false, - "scripturl" : false, - "shadow" : false, - "sub" : false, - "supernew" : false, - "validthis" : false, - "browser" : false, - "browserify" : false, - "couch" : false, - "devel" : false, - "dojo" : false, - "jasmine" : false, - "jquery" : false, - "mocha" : false, - "mootools" : false, - "node" : false, - "nonstandard" : false, - "prototypejs" : false, - "qunit" : false, - "rhino" : false, - "shelljs" : false, - "worker" : false, - "wsh" : false, - "yui" : false, - "globals" : {}, - "-W100" : true -} diff --git a/.prettierignore b/.prettierignore index 56ff6fa6928e..bc68a54c7c19 100644 --- a/.prettierignore +++ b/.prettierignore @@ -6,7 +6,6 @@ config/local-*.json config/local.json dist/** js/components.js -js/libsignal-protocol-worker.js js/libtextsecure.js libtextsecure/components.js libtextsecure/test/test.js @@ -21,6 +20,7 @@ stylesheets/manifest.css components/** js/Mp3LameEncoder.min.js js/WebAudioRecorderMp3.js +js/libsignal-protocol-worker.js libtextsecure/libsignal-protocol.js libtextsecure/test/blanket_mocha.js test/blanket_mocha.js diff --git a/Gruntfile.js b/Gruntfile.js index 34563a4f0314..d54ed509c600 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -1,21 +1,29 @@ -var path = require('path'); -var packageJson = require('./package.json'); +const path = require('path'); +const packageJson = require('./package.json'); +const importOnce = require('node-sass-import-once'); +const rimraf = require('rimraf'); +const mkdirp = require('mkdirp'); +const spectron = require('spectron'); +const asar = require('asar'); +const fs = require('fs'); +const assert = require('assert'); -module.exports = function(grunt) { - 'use strict'; +/* eslint-disable more/no-then */ - var bower = grunt.file.readJSON('bower.json'); - var components = []; - for (var i in bower.concat.app) { +module.exports = grunt => { + const bower = grunt.file.readJSON('bower.json'); + const components = []; + // eslint-disable-next-line guard-for-in, no-restricted-syntax + for (const i in bower.concat.app) { components.push(bower.concat.app[i]); } - var libtextsecurecomponents = []; - for (i in bower.concat.libtextsecure) { + const libtextsecurecomponents = []; + // eslint-disable-next-line guard-for-in, no-restricted-syntax + for (const i in bower.concat.libtextsecure) { libtextsecurecomponents.push(bower.concat.libtextsecure[i]); } - var importOnce = require('node-sass-import-once'); grunt.loadNpmTasks('grunt-sass'); grunt.initConfig({ @@ -37,7 +45,7 @@ module.exports = function(grunt) { ], dest: 'test/test.js', }, - //TODO: Move errors back down? + // TODO: Move errors back down? libtextsecure: { options: { banner: ';(function() {\n', @@ -91,39 +99,6 @@ module.exports = function(grunt) { }, }, }, - jshint: { - files: [ - 'Gruntfile.js', - 'js/**/*.js', - '!js/background.js', - '!js/backup.js', - '!js/components.js', - '!js/database.js', - '!js/libsignal-protocol-worker.js', - '!js/libtextsecure.js', - '!js/logging.js', - '!js/expiring_messages.js', - '!js/modules/**/*.js', - '!js/Mp3LameEncoder.min.js', - '!js/settings_start.js', - '!js/signal_protocol_store.js', - '!js/views/clear_data_view.js', - '!js/views/conversation_search_view.js', - '!js/views/conversation_view.js', - '!js/views/debug_log_view.js', - '!js/views/file_input_view.js', - '!js/views/timestamp_view.js', - '!js/views/message_view.js', - '!js/views/settings_view.js', - '!js/views/contact_list_view.js', - '!js/models/conversations.js', - '!js/models/messages.js', - '!js/WebAudioRecorderMp3.js', - '!libtextsecure/message_receiver.js', - '_locales/**/*', - ], - options: { jshintrc: '.jshintrc' }, - }, copy: { deps: { files: [ @@ -151,10 +126,6 @@ module.exports = function(grunt) { files: ['./stylesheets/*.scss'], tasks: ['sass'], }, - scripts: { - files: ['<%= jshint.files %>'], - tasks: ['jshint'], - }, transpile: { files: ['./ts/**/*.ts'], tasks: ['exec:transpile'], @@ -173,41 +144,37 @@ module.exports = function(grunt) { }, 'test-release': { osx: { - archive: - 'mac/' + packageJson.productName + '.app/Contents/Resources/app.asar', - appUpdateYML: - 'mac/' + - packageJson.productName + - '.app/Contents/Resources/app-update.yml', - exe: - 'mac/' + - packageJson.productName + - '.app/Contents/MacOS/' + - packageJson.productName, + archive: `mac/${ + packageJson.productName + }.app/Contents/Resources/app.asar`, + appUpdateYML: `mac/${ + packageJson.productName + }.app/Contents/Resources/app-update.yml`, + exe: `mac/${packageJson.productName}.app/Contents/MacOS/${ + packageJson.productName + }`, }, mas: { archive: 'mas/Signal.app/Contents/Resources/app.asar', appUpdateYML: 'mac/Signal.app/Contents/Resources/app-update.yml', - exe: - 'mas/' + - packageJson.productName + - '.app/Contents/MacOS/' + - packageJson.productName, + exe: `mas/${packageJson.productName}.app/Contents/MacOS/${ + packageJson.productName + }`, }, linux: { archive: 'linux-unpacked/resources/app.asar', - exe: 'linux-unpacked/' + packageJson.name, + exe: `linux-unpacked/${packageJson.name}`, }, win: { archive: 'win-unpacked/resources/app.asar', appUpdateYML: 'win-unpacked/resources/app-update.yml', - exe: 'win-unpacked/' + packageJson.productName + '.exe', + exe: `win-unpacked/${packageJson.productName}.exe`, }, }, gitinfo: {}, // to be populated by grunt gitinfo }); - Object.keys(grunt.config.get('pkg').devDependencies).forEach(function(key) { + Object.keys(grunt.config.get('pkg').devDependencies).forEach(key => { if (/^grunt(?!(-cli)?$)/.test(key)) { // ignore grunt and grunt-cli grunt.loadNpmTasks(key); @@ -216,20 +183,16 @@ module.exports = function(grunt) { // Transifex does not understand placeholders, so this task patches all non-en // locales with missing placeholders - grunt.registerTask('locale-patch', function() { - var en = grunt.file.readJSON('_locales/en/messages.json'); - grunt.file.recurse('_locales', function( - abspath, - rootdir, - subdir, - filename - ) { + grunt.registerTask('locale-patch', () => { + const en = grunt.file.readJSON('_locales/en/messages.json'); + grunt.file.recurse('_locales', (abspath, rootdir, subdir, filename) => { if (subdir === 'en' || filename !== 'messages.json') { return; } - var messages = grunt.file.readJSON(abspath); + const messages = grunt.file.readJSON(abspath); - for (var key in messages) { + // eslint-disable-next-line no-restricted-syntax + for (const key in messages) { if (en[key] !== undefined && messages[key] !== undefined) { if ( en[key].placeholders !== undefined && @@ -240,32 +203,32 @@ module.exports = function(grunt) { } } - grunt.file.write(abspath, JSON.stringify(messages, null, 4) + '\n'); + grunt.file.write(abspath, `${JSON.stringify(messages, null, 4)}\n`); }); }); - grunt.registerTask('getExpireTime', function() { + grunt.registerTask('getExpireTime', () => { grunt.task.requires('gitinfo'); - var gitinfo = grunt.config.get('gitinfo'); - var commited = gitinfo.local.branch.current.lastCommitTime; - var time = Date.parse(commited) + 1000 * 60 * 60 * 24 * 90; + const gitinfo = grunt.config.get('gitinfo'); + const commited = gitinfo.local.branch.current.lastCommitTime; + const time = Date.parse(commited) + 1000 * 60 * 60 * 24 * 90; grunt.file.write( 'config/local-production.json', - JSON.stringify({ buildExpiration: time }) + '\n' + `${JSON.stringify({ buildExpiration: time })}\n` ); }); - grunt.registerTask('clean-release', function() { - require('rimraf').sync('release'); - require('mkdirp').sync('release'); + grunt.registerTask('clean-release', () => { + rimraf.sync('release'); + mkdirp.sync('release'); }); function runTests(environment, cb) { - var failure; - var Application = require('spectron').Application; - var electronBinary = + let failure; + const { Application } = spectron; + const electronBinary = process.platform === 'win32' ? 'electron.cmd' : 'electron'; - var app = new Application({ + const app = new Application({ path: path.join(__dirname, 'node_modules', '.bin', electronBinary), args: [path.join(__dirname, 'main.js')], env: { @@ -275,78 +238,71 @@ module.exports = function(grunt) { }); function getMochaResults() { + // eslint-disable-next-line no-undef return window.mochaResults; } app .start() - .then(function() { - return app.client.waitUntil( - function() { - return app.client.execute(getMochaResults).then(function(data) { - return Boolean(data.value); - }); - }, + .then(() => + app.client.waitUntil( + () => + app.client + .execute(getMochaResults) + .then(data => Boolean(data.value)), 10000, 'Expected to find window.mochaResults set!' - ); - }) - .then(function() { - return app.client.execute(getMochaResults); - }) - .then(function(data) { - var results = data.value; + ) + ) + .then(() => app.client.execute(getMochaResults)) + .then(data => { + const results = data.value; if (results.failures > 0) { console.error(results.reports); - failure = function() { - grunt.fail.fatal( - 'Found ' + results.failures + ' failing unit tests.' - ); - }; + failure = () => + grunt.fail.fatal(`Found ${results.failures} failing unit tests.`); return app.client.log('browser'); - } else { - grunt.log.ok(results.passes + ' tests passed.'); } + grunt.log.ok(`${results.passes} tests passed.`); + return null; }) - .then(function(logs) { + .then(logs => { if (logs) { console.error(); console.error('Because tests failed, printing browser logs:'); console.error(logs); } }) - .catch(function(error) { - failure = function() { + .catch(error => { + failure = () => grunt.fail.fatal( - 'Something went wrong: ' + error.message + ' ' + error.stack + `Something went wrong: ${error.message} ${error.stack}` ); - }; }) - .then(function() { + .then(() => { // We need to use the failure variable and this early stop to clean up before // shutting down. Grunt's fail methods are the only way to set the return value, // but they shut the process down immediately! if (failure) { console.log(); console.log('Main process logs:'); - return app.client.getMainProcessLogs().then(function(logs) { - logs.forEach(function(log) { + return app.client.getMainProcessLogs().then(logs => { + logs.forEach(log => { console.log(log); }); return app.stop(); }); - } else { - return app.stop(); } + return app.stop(); }) - .then(function() { + .then(() => { if (failure) { failure(); } cb(); }) - .catch(function(error) { + .catch(error => { console.error('Second-level error:', error.message, error.stack); if (failure) { failure(); @@ -355,9 +311,9 @@ module.exports = function(grunt) { }); } - grunt.registerTask('unit-tests', 'Run unit tests w/Electron', function() { - var environment = grunt.option('env') || 'test'; - var done = this.async(); + grunt.registerTask('unit-tests', 'Run unit tests w/Electron', () => { + const environment = grunt.option('env') || 'test'; + const done = this.async(); runTests(environment, done); }); @@ -365,102 +321,93 @@ module.exports = function(grunt) { grunt.registerTask( 'lib-unit-tests', 'Run libtextsecure unit tests w/Electron', - function() { - var environment = grunt.option('env') || 'test-lib'; - var done = this.async(); + () => { + const environment = grunt.option('env') || 'test-lib'; + const done = this.async(); runTests(environment, done); } ); - grunt.registerMultiTask('test-release', 'Test packaged releases', function() { - var dir = grunt.option('dir') || 'dist'; - var environment = grunt.option('env') || 'production'; - var asar = require('asar'); - var config = this.data; - var archive = [dir, config.archive].join('/'); - var files = [ + grunt.registerMultiTask('test-release', 'Test packaged releases', () => { + const dir = grunt.option('dir') || 'dist'; + const environment = grunt.option('env') || 'production'; + const config = this.data; + const archive = [dir, config.archive].join('/'); + const files = [ 'config/default.json', - 'config/' + environment + '.json', - 'config/local-' + environment + '.json', + `config/${environment}.json`, + `config/local-${environment}.json`, ]; console.log(this.target, archive); - var releaseFiles = files.concat(config.files || []); - releaseFiles.forEach(function(fileName) { + const releaseFiles = files.concat(config.files || []); + releaseFiles.forEach(fileName => { console.log(fileName); try { asar.statFile(archive, fileName); return true; } catch (e) { console.log(e); - throw new Error('Missing file ' + fileName); + throw new Error(`Missing file ${fileName}`); } }); if (config.appUpdateYML) { - var appUpdateYML = [dir, config.appUpdateYML].join('/'); - if (require('fs').existsSync(appUpdateYML)) { + const appUpdateYML = [dir, config.appUpdateYML].join('/'); + if (fs.existsSync(appUpdateYML)) { console.log('auto update ok'); } else { - throw new Error('Missing auto update config ' + appUpdateYML); + throw new Error(`Missing auto update config ${appUpdateYML}`); } } - var done = this.async(); + const done = this.async(); // A simple test to verify a visible window is opened with a title - var Application = require('spectron').Application; - var assert = require('assert'); + const { Application } = spectron; - var app = new Application({ + const app = new Application({ path: [dir, config.exe].join('/'), requireName: 'unused', }); app .start() - .then(function() { - return app.client.getWindowCount(); - }) - .then(function(count) { + .then(() => app.client.getWindowCount()) + .then(count => { assert.equal(count, 1); console.log('window opened'); }) - .then(function() { + .then(() => // Get the window's title - return app.client.getTitle(); - }) - .then(function(title) { + app.client.getTitle() + ) + .then(title => { // Verify the window's title assert.equal(title, packageJson.productName); console.log('title ok'); }) - .then(function() { + .then(() => { assert( - app.chromeDriver.logLines.indexOf('NODE_ENV ' + environment) > -1 + app.chromeDriver.logLines.indexOf(`NODE_ENV ${environment}`) > -1 ); console.log('environment ok'); }) .then( - function() { + () => // Successfully completed test - return app.stop(); - }, - function(error) { + app.stop(), + error => // Test failed! - return app.stop().then(function() { - grunt.fail.fatal( - 'Test failed: ' + error.message + ' ' + error.stack - ); - }); - } + app.stop().then(() => { + grunt.fail.fatal(`Test failed: ${error.message} ${error.stack}`); + }) ) .then(done); }); grunt.registerTask('tx', ['exec:tx-pull', 'locale-patch']); grunt.registerTask('dev', ['default', 'watch']); - grunt.registerTask('lint', ['jshint']); grunt.registerTask('test', ['unit-tests', 'lib-unit-tests']); grunt.registerTask('date', ['gitinfo', 'getExpireTime']); grunt.registerTask('default', [ diff --git a/about_preload.js b/about_preload.js index 1c76457d1c23..557d7ad32256 100644 --- a/about_preload.js +++ b/about_preload.js @@ -1,3 +1,5 @@ +/* global window */ + const { ipcRenderer } = require('electron'); const url = require('url'); const i18n = require('./js/modules/i18n'); diff --git a/background.html b/background.html index 17f724d6a529..5b26d1013ff8 100644 --- a/background.html +++ b/background.html @@ -471,9 +471,6 @@
- - diff --git a/debug_log_preload.js b/debug_log_preload.js index ba4b99985baa..bc2c3561bf4a 100644 --- a/debug_log_preload.js +++ b/debug_log_preload.js @@ -1,3 +1,5 @@ +/* global window */ + const { ipcRenderer } = require('electron'); const url = require('url'); const i18n = require('./js/modules/i18n'); diff --git a/js/about_start.js b/js/about_start.js index 01fe36f10de3..60e649ba1514 100644 --- a/js/about_start.js +++ b/js/about_start.js @@ -1,3 +1,5 @@ +/* global $: false */ + // Add version $('.version').text(`v${window.getVersion()}`); @@ -14,7 +16,9 @@ if (window.getAppInstance()) { $('.environment').text(states.join(' - ')); // Install the 'dismiss with escape key' handler -$(document).on('keyup', function(e) { +$(document).on('keyup', e => { + 'use strict'; + if (e.keyCode === 27) { window.closeAbout(); } diff --git a/js/chromium.js b/js/chromium.js index 7e02ebbbc20f..4e1567be4e87 100644 --- a/js/chromium.js +++ b/js/chromium.js @@ -1,10 +1,14 @@ +/* global extension: false */ + +// eslint-disable-next-line func-names (function() { 'use strict'; + // Browser specific functions for Chrom* window.extension = window.extension || {}; extension.windows = { - onClosed: function(callback) { + onClosed(callback) { window.addEventListener('beforeunload', callback); }, }; diff --git a/js/conversation_controller.js b/js/conversation_controller.js index 879dff9d5d05..ab4edd57075d 100644 --- a/js/conversation_controller.js +++ b/js/conversation_controller.js @@ -1,20 +1,20 @@ -/*global $, Whisper, Backbone, textsecure, extension*/ +/* global _, Whisper, Backbone, storage */ -// This script should only be included in background.html +/* eslint-disable more/no-then */ + +// eslint-disable-next-line func-names (function() { 'use strict'; window.Whisper = window.Whisper || {}; - var conversations = new Whisper.ConversationCollection(); - var inboxCollection = new (Backbone.Collection.extend({ - initialize: function() { + const conversations = new Whisper.ConversationCollection(); + const inboxCollection = new (Backbone.Collection.extend({ + initialize() { this.on('change:timestamp change:name change:number', this.sort); this.listenTo(conversations, 'add change:active_at', this.addActive); - this.listenTo(conversations, 'reset', function() { - this.reset([]); - }); + this.listenTo(conversations, 'reset', () => this.reset([])); this.on( 'add remove change:unreadCount', @@ -24,9 +24,9 @@ this.collator = new Intl.Collator(); }, - comparator: function(m1, m2) { - var timestamp1 = m1.get('timestamp'); - var timestamp2 = m2.get('timestamp'); + comparator(m1, m2) { + const timestamp1 = m1.get('timestamp'); + const timestamp2 = m2.get('timestamp'); if (timestamp1 && !timestamp2) { return -1; } @@ -37,57 +37,48 @@ return timestamp2 - timestamp1; } - var title1 = m1.getTitle().toLowerCase(); - var title2 = m2.getTitle().toLowerCase(); + const title1 = m1.getTitle().toLowerCase(); + const title2 = m2.getTitle().toLowerCase(); return this.collator.compare(title1, title2); }, - addActive: function(model) { + addActive(model) { if (model.get('active_at')) { this.add(model); } else { this.remove(model); } }, - updateUnreadCount: function() { - var newUnreadCount = _.reduce( - this.map(function(m) { - return m.get('unreadCount'); - }), - function(item, memo) { - return item + memo; - }, + updateUnreadCount() { + const newUnreadCount = _.reduce( + this.map(m => m.get('unreadCount')), + (item, memo) => item + memo, 0 ); storage.put('unreadCount', newUnreadCount); if (newUnreadCount > 0) { window.setBadgeCount(newUnreadCount); - window.document.title = window.getTitle() + ' (' + newUnreadCount + ')'; + window.document.title = `${window.getTitle()} (${newUnreadCount})`; } else { window.setBadgeCount(0); window.document.title = window.getTitle(); } window.updateTrayIcon(newUnreadCount); }, - startPruning: function() { - var halfHour = 30 * 60 * 1000; - this.interval = setInterval( - function() { - this.forEach(function(conversation) { - conversation.trigger('prune'); - }); - }.bind(this), - halfHour - ); + startPruning() { + const halfHour = 30 * 60 * 1000; + this.interval = setInterval(() => { + this.forEach(conversation => { + conversation.trigger('prune'); + }); + }, halfHour); }, }))(); - window.getInboxCollection = function() { - return inboxCollection; - }; + window.getInboxCollection = () => inboxCollection; window.ConversationController = { - get: function(id) { + get(id) { if (!this._initialFetchComplete) { throw new Error( 'ConversationController.get() needs complete initial fetch' @@ -97,13 +88,13 @@ return conversations.get(id); }, // Needed for some model setup which happens during the initial fetch() call below - getUnsafe: function(id) { + getUnsafe(id) { return conversations.get(id); }, - dangerouslyCreateAndAdd: function(attributes) { + dangerouslyCreateAndAdd(attributes) { return conversations.add(attributes); }, - getOrCreate: function(id, type) { + getOrCreate(id, type) { if (typeof id !== 'string') { throw new TypeError("'id' must be a string"); } @@ -120,18 +111,18 @@ ); } - var conversation = conversations.get(id); + let conversation = conversations.get(id); if (conversation) { return conversation; } conversation = conversations.add({ - id: id, - type: type, + id, + type, }); - conversation.initialPromise = new Promise(function(resolve, reject) { + conversation.initialPromise = new Promise((resolve, reject) => { if (!conversation.isValid()) { - var validationError = conversation.validationError || {}; + const validationError = conversation.validationError || {}; console.log( 'Contact is not valid. Not saving, but adding to collection:', conversation.idForLogging(), @@ -141,72 +132,64 @@ return resolve(conversation); } - var deferred = conversation.save(); + const deferred = conversation.save(); if (!deferred) { console.log('Conversation save failed! ', id, type); return reject(new Error('getOrCreate: Conversation save failed')); } - deferred.then(function() { + return deferred.then(() => { resolve(conversation); }, reject); }); return conversation; }, - getOrCreateAndWait: function(id, type) { - return this._initialPromise.then( - function() { - var conversation = this.getOrCreate(id, type); + getOrCreateAndWait(id, type) { + return this._initialPromise.then(() => { + const conversation = this.getOrCreate(id, type); - if (conversation) { - return conversation.initialPromise.then(function() { - return conversation; - }); - } + if (conversation) { + return conversation.initialPromise.then(() => conversation); + } - return Promise.reject( - new Error('getOrCreateAndWait: did not get conversation') - ); - }.bind(this) - ); - }, - getAllGroupsInvolvingId: function(id) { - var groups = new Whisper.GroupCollection(); - return groups.fetchGroups(id).then(function() { - return groups.map(function(group) { - return conversations.add(group); - }); + return Promise.reject( + new Error('getOrCreateAndWait: did not get conversation') + ); }); }, - loadPromise: function() { + getAllGroupsInvolvingId(id) { + const groups = new Whisper.GroupCollection(); + return groups + .fetchGroups(id) + .then(() => groups.map(group => conversations.add(group))); + }, + loadPromise() { return this._initialPromise; }, - reset: function() { + reset() { this._initialPromise = Promise.resolve(); conversations.reset([]); }, - load: function() { + load() { console.log('ConversationController: starting initial fetch'); - this._initialPromise = new Promise( - function(resolve, reject) { - conversations.fetch().then( - function() { - console.log('ConversationController: done with initial fetch'); - this._initialFetchComplete = true; - resolve(); - }.bind(this), - function(error) { - console.log( - 'ConversationController: initial fetch failed', - error && error.stack ? error.stack : error - ); - reject(error); - } - ); - }.bind(this) - ); + this._initialPromise = new Promise((resolve, reject) => { + conversations.fetch().then( + () => { + console.log('ConversationController: done with initial fetch'); + this._initialFetchComplete = true; + resolve(); + }, + error => { + console.log( + 'ConversationController: initial fetch failed', + error && error.stack ? error.stack : error + ); + reject(error); + } + ); + }); return this._initialPromise; }, diff --git a/js/debug_log_start.js b/js/debug_log_start.js index 24b12e2a7162..1a80b67cfe56 100644 --- a/js/debug_log_start.js +++ b/js/debug_log_start.js @@ -1,4 +1,9 @@ -$(document).on('keyup', function(e) { +/* global $: false */ +/* global Whisper: false */ + +$(document).on('keyup', e => { + 'use strict'; + if (e.keyCode === 27) { window.closeDebugLog(); } diff --git a/js/delivery_receipts.js b/js/delivery_receipts.js index 94bf07fe462a..8f61ac49d00b 100644 --- a/js/delivery_receipts.js +++ b/js/delivery_receipts.js @@ -1,97 +1,95 @@ +/* global Backbone: false */ +/* global Whisper: false */ +/* global ConversationController: false */ +/* global _: false */ + +/* eslint-disable more/no-then */ + +// eslint-disable-next-line func-names (function() { 'use strict'; + window.Whisper = window.Whisper || {}; Whisper.DeliveryReceipts = new (Backbone.Collection.extend({ - forMessage: function(conversation, message) { - var recipients; + forMessage(conversation, message) { + let recipients; if (conversation.isPrivate()) { recipients = [conversation.id]; } else { recipients = conversation.get('members') || []; } - var receipts = this.filter(function(receipt) { - return ( + const receipts = this.filter( + receipt => receipt.get('timestamp') === message.get('sent_at') && recipients.indexOf(receipt.get('source')) > -1 - ); - }); + ); this.remove(receipts); return receipts; }, - onReceipt: function(receipt) { - var messages = new Whisper.MessageCollection(); + onReceipt(receipt) { + const messages = new Whisper.MessageCollection(); return messages .fetchSentAt(receipt.get('timestamp')) - .then(function() { + .then(() => { if (messages.length === 0) { - return; + return null; } - var message = messages.find(function(message) { - return ( - !message.isIncoming() && - receipt.get('source') === message.get('conversationId') - ); - }); + const message = messages.find( + item => + !item.isIncoming() && + receipt.get('source') === item.get('conversationId') + ); if (message) { return message; } - var groups = new Whisper.GroupCollection(); - return groups.fetchGroups(receipt.get('source')).then(function() { - var ids = groups.pluck('id'); + const groups = new Whisper.GroupCollection(); + return groups.fetchGroups(receipt.get('source')).then(() => { + const ids = groups.pluck('id'); ids.push(receipt.get('source')); - return messages.find(function(message) { - return ( - !message.isIncoming() && - _.contains(ids, message.get('conversationId')) - ); - }); + return messages.find( + item => + !item.isIncoming() && + _.contains(ids, item.get('conversationId')) + ); }); }) - .then( - function(message) { - if (message) { - var deliveries = message.get('delivered') || 0; - var delivered_to = message.get('delivered_to') || []; - return new Promise( - function(resolve, reject) { - message - .save({ - delivered_to: _.union(delivered_to, [ - receipt.get('source'), - ]), - delivered: deliveries + 1, - }) - .then( - function() { - // notify frontend listeners - var conversation = ConversationController.get( - message.get('conversationId') - ); - if (conversation) { - conversation.trigger('delivered', message); - } + .then(message => { + if (message) { + const deliveries = message.get('delivered') || 0; + const deliveredTo = message.get('delivered_to') || []; + return new Promise((resolve, reject) => { + message + .save({ + delivered_to: _.union(deliveredTo, [receipt.get('source')]), + delivered: deliveries + 1, + }) + .then(() => { + // notify frontend listeners + const conversation = ConversationController.get( + message.get('conversationId') + ); + if (conversation) { + conversation.trigger('delivered', message); + } - this.remove(receipt); - resolve(); - }.bind(this), - reject - ); - }.bind(this) - ); - // TODO: consider keeping a list of numbers we've - // successfully delivered to? - } else { - console.log( - 'No message for delivery receipt', - receipt.get('source'), - receipt.get('timestamp') - ); - } - }.bind(this) - ) - .catch(function(error) { + this.remove(receipt); + resolve(); + }, reject); + }); + // TODO: consider keeping a list of numbers we've + // successfully delivered to? + } + console.log( + 'No message for delivery receipt', + receipt.get('source'), + receipt.get('timestamp') + ); + + return null; + }) + .catch(error => { console.log( 'DeliveryReceipts.onReceipt error:', error && error.stack ? error.stack : error diff --git a/js/expire.js b/js/expire.js index a9fad48d3ca1..db083c572690 100644 --- a/js/expire.js +++ b/js/expire.js @@ -1,16 +1,19 @@ +// eslint-disable-next-line func-names (function() { 'use strict'; - var BUILD_EXPIRATION = 0; + + let BUILD_EXPIRATION = 0; try { - BUILD_EXPIRATION = parseInt(window.getExpiration()); + BUILD_EXPIRATION = parseInt(window.getExpiration(), 10); if (BUILD_EXPIRATION) { console.log('Build expires: ', new Date(BUILD_EXPIRATION).toISOString()); } - } catch (e) {} + } catch (e) { + // nothing + } window.extension = window.extension || {}; - extension.expired = function() { - return BUILD_EXPIRATION && Date.now() > BUILD_EXPIRATION; - }; + window.extension.expired = () => + BUILD_EXPIRATION && Date.now() > BUILD_EXPIRATION; })(); diff --git a/js/focus_listener.js b/js/focus_listener.js index c76c9d11145b..c9a72823336e 100644 --- a/js/focus_listener.js +++ b/js/focus_listener.js @@ -1,15 +1,14 @@ +// eslint-disable-next-line func-names (function() { 'use strict'; - var windowFocused = false; - window.addEventListener('blur', function() { + let windowFocused = false; + window.addEventListener('blur', () => { windowFocused = false; }); - window.addEventListener('focus', function() { + window.addEventListener('focus', () => { windowFocused = true; }); - window.isFocused = function() { - return windowFocused; - }; + window.isFocused = () => windowFocused; })(); diff --git a/js/keychange_listener.js b/js/keychange_listener.js index b42df42cd7ed..0719d742c3a3 100644 --- a/js/keychange_listener.js +++ b/js/keychange_listener.js @@ -1,27 +1,31 @@ +/* global Whisper, SignalProtocolStore, ConversationController, _ */ + +/* eslint-disable more/no-then */ + +// eslint-disable-next-line func-names (function() { 'use strict'; + window.Whisper = window.Whisper || {}; Whisper.KeyChangeListener = { - init: function(signalProtocolStore) { + init(signalProtocolStore) { if (!(signalProtocolStore instanceof SignalProtocolStore)) { throw new Error('KeyChangeListener requires a SignalProtocolStore'); } - signalProtocolStore.on('keychange', function(id) { - ConversationController.getOrCreateAndWait(id, 'private').then(function( - conversation - ) { - conversation.addKeyChange(id); + signalProtocolStore.on('keychange', id => { + ConversationController.getOrCreateAndWait(id, 'private').then( + conversation => { + conversation.addKeyChange(id); - ConversationController.getAllGroupsInvolvingId(id).then(function( - groups - ) { - _.forEach(groups, function(group) { - group.addKeyChange(id); + ConversationController.getAllGroupsInvolvingId(id).then(groups => { + _.forEach(groups, group => { + group.addKeyChange(id); + }); }); - }); - }); + } + ); }); }, }; diff --git a/js/models/blockedNumbers.js b/js/models/blockedNumbers.js index 0950cc302f18..ab2e37dde436 100644 --- a/js/models/blockedNumbers.js +++ b/js/models/blockedNumbers.js @@ -1,12 +1,16 @@ +/* global storage, _ */ + +// eslint-disable-next-line func-names (function() { 'use strict'; - storage.isBlocked = function(number) { - var numbers = storage.get('blocked', []); + + storage.isBlocked = number => { + const numbers = storage.get('blocked', []); return _.include(numbers, number); }; - storage.addBlockedNumber = function(number) { - var numbers = storage.get('blocked', []); + storage.addBlockedNumber = number => { + const numbers = storage.get('blocked', []); if (_.include(numbers, number)) { return; } @@ -14,8 +18,8 @@ console.log('adding', number, 'to blocked list'); storage.put('blocked', numbers.concat(number)); }; - storage.removeBlockedNumber = function(number) { - var numbers = storage.get('blocked', []); + storage.removeBlockedNumber = number => { + const numbers = storage.get('blocked', []); if (!_.include(numbers, number)) { return; } diff --git a/js/permissions_popup_start.js b/js/permissions_popup_start.js index c66b63bf7783..f153a0bf96d3 100644 --- a/js/permissions_popup_start.js +++ b/js/permissions_popup_start.js @@ -1,4 +1,8 @@ -$(document).on('keyup', function(e) { +/* global $, Whisper, i18n */ + +$(document).on('keyup', e => { + 'use strict'; + if (e.keyCode === 27) { window.closePermissionsPopup(); } @@ -11,6 +15,8 @@ window.view = new Whisper.ConfirmationDialogView({ message: i18n('audioPermissionNeeded'), okText: i18n('allowAccess'), resolve: () => { + 'use strict'; + window.setMediaPermissions(true); window.closePermissionsPopup(); }, diff --git a/js/read_receipts.js b/js/read_receipts.js index 1180467fd2b4..e211a6dc4fe7 100644 --- a/js/read_receipts.js +++ b/js/read_receipts.js @@ -1,93 +1,89 @@ +/* global Whisper, Backbone, _, ConversationController */ + +/* eslint-disable more/no-then */ + +// eslint-disable-next-line func-names (function() { 'use strict'; + window.Whisper = window.Whisper || {}; Whisper.ReadReceipts = new (Backbone.Collection.extend({ - forMessage: function(conversation, message) { + forMessage(conversation, message) { if (!message.isOutgoing()) { return []; } - var ids = []; + let ids = []; if (conversation.isPrivate()) { ids = [conversation.id]; } else { ids = conversation.get('members'); } - var receipts = this.filter(function(receipt) { - return ( + const receipts = this.filter( + receipt => receipt.get('timestamp') === message.get('sent_at') && _.contains(ids, receipt.get('reader')) - ); - }); + ); if (receipts.length) { console.log('Found early read receipts for message'); this.remove(receipts); } return receipts; }, - onReceipt: function(receipt) { - var messages = new Whisper.MessageCollection(); + onReceipt(receipt) { + const messages = new Whisper.MessageCollection(); return messages .fetchSentAt(receipt.get('timestamp')) - .then(function() { + .then(() => { if (messages.length === 0) { - return; + return null; } - var message = messages.find(function(message) { - return ( - message.isOutgoing() && - receipt.get('reader') === message.get('conversationId') - ); - }); + const message = messages.find( + item => + item.isOutgoing() && + receipt.get('reader') === item.get('conversationId') + ); if (message) { return message; } - var groups = new Whisper.GroupCollection(); - return groups.fetchGroups(receipt.get('reader')).then(function() { - var ids = groups.pluck('id'); + const groups = new Whisper.GroupCollection(); + return groups.fetchGroups(receipt.get('reader')).then(() => { + const ids = groups.pluck('id'); ids.push(receipt.get('reader')); - return messages.find(function(message) { - return ( - message.isOutgoing() && - _.contains(ids, message.get('conversationId')) - ); - }); + return messages.find( + item => + item.isOutgoing() && _.contains(ids, item.get('conversationId')) + ); }); }) - .then( - function(message) { - if (message) { - var read_by = message.get('read_by') || []; - read_by.push(receipt.get('reader')); - return new Promise( - function(resolve, reject) { - message.save({ read_by: read_by }).then( - function() { - // notify frontend listeners - var conversation = ConversationController.get( - message.get('conversationId') - ); - if (conversation) { - conversation.trigger('read', message); - } + .then(message => { + if (message) { + const readBy = message.get('read_by') || []; + readBy.push(receipt.get('reader')); + return new Promise((resolve, reject) => { + message.save({ read_by: readBy }).then(() => { + // notify frontend listeners + const conversation = ConversationController.get( + message.get('conversationId') + ); + if (conversation) { + conversation.trigger('read', message); + } - this.remove(receipt); - resolve(); - }.bind(this), - reject - ); - }.bind(this) - ); - } else { - console.log( - 'No message for read receipt', - receipt.get('reader'), - receipt.get('timestamp') - ); - } - }.bind(this) - ) - .catch(function(error) { + this.remove(receipt); + resolve(); + }, reject); + }); + } + console.log( + 'No message for read receipt', + receipt.get('reader'), + receipt.get('timestamp') + ); + + return null; + }) + .catch(error => { console.log( 'ReadReceipts.onReceipt error:', error && error.stack ? error.stack : error diff --git a/js/read_syncs.js b/js/read_syncs.js index 55f1ce980bee..4f8054c2e1d6 100644 --- a/js/read_syncs.js +++ b/js/read_syncs.js @@ -1,9 +1,15 @@ +/* global Backbone, Whisper, ConversationController */ + +/* eslint-disable more/no-then */ + +// eslint-disable-next-line func-names (function() { 'use strict'; + window.Whisper = window.Whisper || {}; Whisper.ReadSyncs = new (Backbone.Collection.extend({ - forMessage: function(message) { - var receipt = this.findWhere({ + forMessage(message) { + const receipt = this.findWhere({ sender: message.get('source'), timestamp: message.get('sent_at'), }); @@ -12,52 +18,49 @@ this.remove(receipt); return receipt; } + + return null; }, - onReceipt: function(receipt) { - var messages = new Whisper.MessageCollection(); - return messages.fetchSentAt(receipt.get('timestamp')).then( - function() { - var message = messages.find(function(message) { - return ( - message.isIncoming() && - message.isUnread() && - message.get('source') === receipt.get('sender') - ); - }); - const notificationForMessage = message - ? Whisper.Notifications.findWhere({ messageId: message.id }) - : null; - const removedNotification = Whisper.Notifications.remove( - notificationForMessage - ); - const receiptSender = receipt.get('sender'); - const receiptTimestamp = receipt.get('timestamp'); - const wasMessageFound = Boolean(message); - const wasNotificationFound = Boolean(notificationForMessage); - const wasNotificationRemoved = Boolean(removedNotification); - console.log('Receive read sync:', { - receiptSender, - receiptTimestamp, - wasMessageFound, - wasNotificationFound, - wasNotificationRemoved, - }); - return message - ? message.markRead(receipt.get('read_at')).then( - function() { - // This notification may result in messages older than this one being - // marked read. We want those messages to have the same expire timer - // start time as this one, so we pass the read_at value through. - this.notifyConversation(message, receipt.get('read_at')); - this.remove(receipt); - }.bind(this) - ) - : Promise.resolve(); - }.bind(this) - ); + onReceipt(receipt) { + const messages = new Whisper.MessageCollection(); + return messages.fetchSentAt(receipt.get('timestamp')).then(() => { + const message = messages.find( + item => + item.isIncoming() && + item.isUnread() && + item.get('source') === receipt.get('sender') + ); + const notificationForMessage = message + ? Whisper.Notifications.findWhere({ messageId: message.id }) + : null; + const removedNotification = Whisper.Notifications.remove( + notificationForMessage + ); + const receiptSender = receipt.get('sender'); + const receiptTimestamp = receipt.get('timestamp'); + const wasMessageFound = Boolean(message); + const wasNotificationFound = Boolean(notificationForMessage); + const wasNotificationRemoved = Boolean(removedNotification); + console.log('Receive read sync:', { + receiptSender, + receiptTimestamp, + wasMessageFound, + wasNotificationFound, + wasNotificationRemoved, + }); + return message + ? message.markRead(receipt.get('read_at')).then(() => { + // This notification may result in messages older than this one being + // marked read. We want those messages to have the same expire timer + // start time as this one, so we pass the read_at value through. + this.notifyConversation(message, receipt.get('read_at')); + this.remove(receipt); + }) + : Promise.resolve(); + }); }, - notifyConversation: function(message, readAt) { - var conversation = ConversationController.get({ + notifyConversation(message, readAt) { + const conversation = ConversationController.get({ id: message.get('conversationId'), }); diff --git a/js/registration.js b/js/registration.js index bb068250eaf6..499e981bf4e7 100644 --- a/js/registration.js +++ b/js/registration.js @@ -1,23 +1,27 @@ +/* global storage, Whisper */ + +// eslint-disable-next-line func-names (function() { 'use strict'; + Whisper.Registration = { - markEverDone: function() { + markEverDone() { storage.put('chromiumRegistrationDoneEver', ''); }, - markDone: function() { + markDone() { this.markEverDone(); storage.put('chromiumRegistrationDone', ''); }, - isDone: function() { + isDone() { return storage.get('chromiumRegistrationDone') === ''; }, - everDone: function() { + everDone() { return ( storage.get('chromiumRegistrationDoneEver') === '' || storage.get('chromiumRegistrationDone') === '' ); }, - remove: function() { + remove() { storage.remove('chromiumRegistrationDone'); }, }; diff --git a/js/reliable_trigger.js b/js/reliable_trigger.js index b06eda5fa546..ccdb5fc25b92 100644 --- a/js/reliable_trigger.js +++ b/js/reliable_trigger.js @@ -1,41 +1,50 @@ +/* eslint-disable */ + +// This file was taken from Backbone and then modified. It does not conform to this +// project's standards. + (function() { + 'use strict'; + // Note: this is all the code required to customize Backbone's trigger() method to make // it resilient to exceptions thrown by event handlers. Indentation and code styles // were kept inline with the Backbone implementation for easier diffs. // The changes are: - // 1. added 'name' parameter to triggerEvents to give it access to the current event name - // 2. added try/catch handlers to triggerEvents with error logging inside every while loop + // 1. added 'name' parameter to triggerEvents to give it access to the + // current event name + // 2. added try/catch handlers to triggerEvents with error logging inside + // every while loop // And of course, we update the protoypes of Backbone.Model/Backbone.View as well as // Backbone.Events itself - var arr = []; + const arr = []; - var slice = arr.slice; + const slice = arr.slice; // Regular expression used to split event strings. - var eventSplitter = /\s+/; + const eventSplitter = /\s+/; // Implement fancy features of the Events API such as multiple event // names `"change blur"` and jQuery-style event maps `{change: action}` // in terms of the existing API. - var eventsApi = function(obj, action, name, rest) { + const eventsApi = function(obj, action, name, rest) { if (!name) return true; // Handle event maps. if (typeof name === 'object') { - for (var key in name) { - obj[action].apply(obj, [key, name[key]].concat(rest)); + for (const key in name) { + obj[action](...[key, name[key]].concat(rest)); } return false; } // Handle space separated event names. if (eventSplitter.test(name)) { - var names = name.split(eventSplitter); - for (var i = 0, l = names.length; i < l; i++) { - obj[action].apply(obj, [names[i]].concat(rest)); + const names = name.split(eventSplitter); + for (let i = 0, l = names.length; i < l; i++) { + obj[action](...[names[i]].concat(rest)); } return false; } @@ -46,14 +55,14 @@ // A difficult-to-believe, but optimized internal dispatch function for // triggering events. Tries to keep the usual cases speedy (most internal // Backbone events have 3 arguments). - var triggerEvents = function(events, name, args) { - var ev, + const triggerEvents = function(events, name, args) { + let ev, i = -1, l = events.length, a1 = args[0], a2 = args[1], a3 = args[2]; - var logError = function(error) { + const logError = function(error) { console.log( 'Model caught error triggering', name, @@ -106,7 +115,6 @@ logError(error); } } - return; } }; @@ -116,10 +124,10 @@ // receive the true name of the event as the first argument). function trigger(name) { if (!this._events) return this; - var args = slice.call(arguments, 1); + const args = slice.call(arguments, 1); if (!eventsApi(this, 'trigger', name, args)) return this; - var events = this._events[name]; - var allEvents = this._events.all; + const events = this._events[name]; + const allEvents = this._events.all; if (events) triggerEvents(events, name, args); if (allEvents) triggerEvents(allEvents, name, arguments); return this; diff --git a/js/rotate_signed_prekey_listener.js b/js/rotate_signed_prekey_listener.js index 44d01b7d0d1f..7004189b05b2 100644 --- a/js/rotate_signed_prekey_listener.js +++ b/js/rotate_signed_prekey_listener.js @@ -1,13 +1,17 @@ +/* global Whisper, storage, getAccountManager */ + +// eslint-disable-next-line func-names (function() { 'use strict'; + window.Whisper = window.Whisper || {}; - var ROTATION_INTERVAL = 48 * 60 * 60 * 1000; - var timeout; - var scheduledTime; + const ROTATION_INTERVAL = 48 * 60 * 60 * 1000; + let timeout; + let scheduledTime; function scheduleNextRotation() { - var now = Date.now(); - var nextTime = now + ROTATION_INTERVAL; + const now = Date.now(); + const nextTime = now + ROTATION_INTERVAL; storage.put('nextSignedKeyRotationTime', nextTime); } @@ -15,7 +19,7 @@ console.log('Rotating signed prekey...'); getAccountManager() .rotateSignedPreKey() - .catch(function() { + .catch(() => { console.log( 'rotateSignedPrekey() failed. Trying again in five seconds' ); @@ -32,7 +36,7 @@ console.log( 'We are offline; keys will be rotated when we are next online' ); - var listener = function() { + const listener = () => { window.removeEventListener('online', listener); run(); }; @@ -41,8 +45,8 @@ } function setTimeoutForNextRun() { - var now = Date.now(); - var time = storage.get('nextSignedKeyRotationTime', now); + const now = Date.now(); + const time = storage.get('nextSignedKeyRotationTime', now); if (scheduledTime !== time || !timeout) { console.log( @@ -52,7 +56,7 @@ } scheduledTime = time; - var waitTime = time - now; + let waitTime = time - now; if (waitTime < 0) { waitTime = 0; } @@ -61,9 +65,9 @@ timeout = setTimeout(runWhenOnline, waitTime); } - var initComplete; + let initComplete; Whisper.RotateSignedPreKeyListener = { - init: function(events, newVersion) { + init(events, newVersion) { if (initComplete) { console.log('Rotate signed prekey listener: Already initialized'); return; @@ -76,7 +80,7 @@ setTimeoutForNextRun(); } - events.on('timetravel', function() { + events.on('timetravel', () => { if (Whisper.Registration.isDone()) { setTimeoutForNextRun(); } diff --git a/js/settings_start.js b/js/settings_start.js index ef588678557b..0275e2e4af2e 100644 --- a/js/settings_start.js +++ b/js/settings_start.js @@ -1,4 +1,8 @@ -$(document).on('keyup', function(e) { +/* global $, Whisper */ + +$(document).on('keyup', e => { + 'use strict'; + if (e.keyCode === 27) { window.closeSettings(); } @@ -7,6 +11,7 @@ $(document).on('keyup', function(e) { const $body = $(document.body); $body.addClass(`${window.theme}-theme`); +// eslint-disable-next-line strict const getInitialData = async () => ({ deviceName: await window.getDeviceName(), @@ -23,7 +28,11 @@ const getInitialData = async () => ({ }); window.initialRequest = getInitialData(); + +// eslint-disable-next-line more/no-then window.initialRequest.then(data => { + 'use strict'; + window.initialData = data; window.view = new Whisper.SettingsView(); window.view.$el.appendTo($body); diff --git a/js/signal_protocol_store.js b/js/signal_protocol_store.js index d2439f094ef3..d062a7664974 100644 --- a/js/signal_protocol_store.js +++ b/js/signal_protocol_store.js @@ -1,12 +1,26 @@ +/* global dcodeIO: false */ +/* global Backbone: false */ +/* global Whisper: false */ +/* global _: false */ +/* global libsignal: false */ +/* global textsecure: false */ +/* global ConversationController: false */ +/* global wrapDeferred: false */ +/* global stringObject: false */ + +/* eslint-disable more/no-then, no-proto */ + +// eslint-disable-next-line func-names (function() { 'use strict'; - var TIMESTAMP_THRESHOLD = 5 * 1000; // 5 seconds - var Direction = { + + const TIMESTAMP_THRESHOLD = 5 * 1000; // 5 seconds + const Direction = { SENDING: 1, RECEIVING: 2, }; - var VerifiedStatus = { + const VerifiedStatus = { DEFAULT: 0, VERIFIED: 1, UNVERIFIED: 2, @@ -23,16 +37,16 @@ return false; } - var StaticByteBufferProto = new dcodeIO.ByteBuffer().__proto__; - var StaticArrayBufferProto = new ArrayBuffer().__proto__; - var StaticUint8ArrayProto = new Uint8Array().__proto__; + const StaticByteBufferProto = new dcodeIO.ByteBuffer().__proto__; + const StaticArrayBufferProto = new ArrayBuffer().__proto__; + const StaticUint8ArrayProto = new Uint8Array().__proto__; function isStringable(thing) { return ( thing === Object(thing) && - (thing.__proto__ == StaticArrayBufferProto || - thing.__proto__ == StaticUint8ArrayProto || - thing.__proto__ == StaticByteBufferProto) + (thing.__proto__ === StaticArrayBufferProto || + thing.__proto__ === StaticUint8ArrayProto || + thing.__proto__ === StaticByteBufferProto) ); } function convertToArrayBuffer(thing) { @@ -40,37 +54,35 @@ return undefined; } if (thing === Object(thing)) { - if (thing.__proto__ == StaticArrayBufferProto) { + if (thing.__proto__ === StaticArrayBufferProto) { return thing; } - //TODO: Several more cases here... + // TODO: Several more cases here... } if (thing instanceof Array) { // Assuming Uint16Array from curve25519 - var res = new ArrayBuffer(thing.length * 2); - var uint = new Uint16Array(res); - for (var i = 0; i < thing.length; i++) { + const res = new ArrayBuffer(thing.length * 2); + const uint = new Uint16Array(res); + for (let i = 0; i < thing.length; i += 1) { uint[i] = thing[i]; } return res; } - var str; + let str; if (isStringable(thing)) { str = stringObject(thing); - } else if (typeof thing == 'string') { + } else if (typeof thing === 'string') { str = thing; } else { throw new Error( - 'Tried to convert a non-stringable thing of type ' + - typeof thing + - ' to an array buffer' + `Tried to convert a non-stringable thing of type ${typeof thing} to an array buffer` ); } - var res = new ArrayBuffer(str.length); - var uint = new Uint8Array(res); - for (var i = 0; i < str.length; i++) { + const res = new ArrayBuffer(str.length); + const uint = new Uint8Array(res); + for (let i = 0; i < str.length; i += 1) { uint[i] = str.charCodeAt(i); } return res; @@ -83,45 +95,46 @@ if (ab1.byteLength !== ab2.byteLength) { return false; } - var result = 0; - var ta1 = new Uint8Array(ab1); - var ta2 = new Uint8Array(ab2); - for (var i = 0; i < ab1.byteLength; ++i) { - result = result | (ta1[i] ^ ta2[i]); + let result = 0; + const ta1 = new Uint8Array(ab1); + const ta2 = new Uint8Array(ab2); + for (let i = 0; i < ab1.byteLength; i += 1) { + // eslint-disable-next-line no-bitwise + result |= ta1[i] ^ ta2[i]; } return result === 0; } - var Model = Backbone.Model.extend({ database: Whisper.Database }); - var PreKey = Model.extend({ storeName: 'preKeys' }); - var PreKeyCollection = Backbone.Collection.extend({ + const Model = Backbone.Model.extend({ database: Whisper.Database }); + const PreKey = Model.extend({ storeName: 'preKeys' }); + const PreKeyCollection = Backbone.Collection.extend({ storeName: 'preKeys', database: Whisper.Database, model: PreKey, }); - var SignedPreKey = Model.extend({ storeName: 'signedPreKeys' }); - var SignedPreKeyCollection = Backbone.Collection.extend({ + const SignedPreKey = Model.extend({ storeName: 'signedPreKeys' }); + const SignedPreKeyCollection = Backbone.Collection.extend({ storeName: 'signedPreKeys', database: Whisper.Database, model: SignedPreKey, }); - var Session = Model.extend({ storeName: 'sessions' }); - var SessionCollection = Backbone.Collection.extend({ + const Session = Model.extend({ storeName: 'sessions' }); + const SessionCollection = Backbone.Collection.extend({ storeName: 'sessions', database: Whisper.Database, model: Session, - fetchSessionsForNumber: function(number) { - return this.fetch({ range: [number + '.1', number + '.' + ':'] }); + fetchSessionsForNumber(number) { + return this.fetch({ range: [`${number}.1`, `${number}.:`] }); }, }); - var Unprocessed = Model.extend({ storeName: 'unprocessed' }); - var UnprocessedCollection = Backbone.Collection.extend({ + const Unprocessed = Model.extend({ storeName: 'unprocessed' }); + const UnprocessedCollection = Backbone.Collection.extend({ storeName: 'unprocessed', database: Whisper.Database, model: Unprocessed, comparator: 'timestamp', }); - var IdentityRecord = Model.extend({ + const IdentityRecord = Model.extend({ storeName: 'identityKeys', validAttributes: [ 'id', @@ -131,18 +144,18 @@ 'verified', 'nonblockingApproval', ], - validate: function(attrs, options) { - var attributeNames = _.keys(attrs); - var validAttributes = this.validAttributes; - var allValid = _.all(attributeNames, function(attributeName) { - return _.contains(validAttributes, attributeName); - }); + validate(attrs) { + const attributeNames = _.keys(attrs); + const { validAttributes } = this; + const allValid = _.all(attributeNames, attributeName => + _.contains(validAttributes, attributeName) + ); if (!allValid) { return new Error('Invalid identity key attribute names'); } - var allPresent = _.all(validAttributes, function(attributeName) { - return _.contains(attributeNames, attributeName); - }); + const allPresent = _.all(validAttributes, attributeName => + _.contains(attributeNames, attributeName) + ); if (!allPresent) { return new Error('Missing identity key attributes'); } @@ -165,75 +178,77 @@ if (typeof attrs.nonblockingApproval !== 'boolean') { return new Error('Invalid identity key nonblockingApproval'); } + + return null; }, }); - var Group = Model.extend({ storeName: 'groups' }); - var Item = Model.extend({ storeName: 'items' }); + const Group = Model.extend({ storeName: 'groups' }); + const Item = Model.extend({ storeName: 'items' }); function SignalProtocolStore() {} SignalProtocolStore.prototype = { constructor: SignalProtocolStore, - getIdentityKeyPair: function() { - var item = new Item({ id: 'identityKey' }); - return new Promise(function(resolve, reject) { - item.fetch().then(function() { + getIdentityKeyPair() { + const item = new Item({ id: 'identityKey' }); + return new Promise((resolve, reject) => { + item.fetch().then(() => { resolve(item.get('value')); }, reject); }); }, - getLocalRegistrationId: function() { - var item = new Item({ id: 'registrationId' }); - return new Promise(function(resolve, reject) { - item.fetch().then(function() { + getLocalRegistrationId() { + const item = new Item({ id: 'registrationId' }); + return new Promise((resolve, reject) => { + item.fetch().then(() => { resolve(item.get('value')); }, reject); }); }, /* Returns a prekeypair object or undefined */ - loadPreKey: function(keyId) { - var prekey = new PreKey({ id: keyId }); - return new Promise(function(resolve) { + loadPreKey(keyId) { + const prekey = new PreKey({ id: keyId }); + return new Promise(resolve => { prekey.fetch().then( - function() { + () => { console.log('Successfully fetched prekey:', keyId); resolve({ pubKey: prekey.get('publicKey'), privKey: prekey.get('privateKey'), }); }, - function() { + () => { console.log('Failed to fetch prekey:', keyId); resolve(); } ); }); }, - storePreKey: function(keyId, keyPair) { - var prekey = new PreKey({ + storePreKey(keyId, keyPair) { + const prekey = new PreKey({ id: keyId, publicKey: keyPair.pubKey, privateKey: keyPair.privKey, }); - return new Promise(function(resolve) { - prekey.save().always(function() { + return new Promise(resolve => { + prekey.save().always(() => { resolve(); }); }); }, - removePreKey: function(keyId) { - var prekey = new PreKey({ id: keyId }); + removePreKey(keyId) { + const prekey = new PreKey({ id: keyId }); this.trigger('removePreKey'); - return new Promise(function(resolve) { - var deferred = prekey.destroy(); + return new Promise(resolve => { + const deferred = prekey.destroy(); if (!deferred) { return resolve(); } - return deferred.then(resolve, function(error) { + return deferred.then(resolve, error => { console.log( 'removePreKey error:', error && error.stack ? error.stack : error @@ -242,20 +257,20 @@ }); }); }, - clearPreKeyStore: function() { - return new Promise(function(resolve) { - var preKeys = new PreKeyCollection(); + clearPreKeyStore() { + return new Promise(resolve => { + const preKeys = new PreKeyCollection(); preKeys.sync('delete', preKeys, {}).always(resolve); }); }, /* Returns a signed keypair object or undefined */ - loadSignedPreKey: function(keyId) { - var prekey = new SignedPreKey({ id: keyId }); - return new Promise(function(resolve) { + loadSignedPreKey(keyId) { + const prekey = new SignedPreKey({ id: keyId }); + return new Promise(resolve => { prekey .fetch() - .then(function() { + .then(() => { console.log( 'Successfully fetched signed prekey:', prekey.get('id') @@ -268,139 +283,138 @@ confirmed: prekey.get('confirmed'), }); }) - .fail(function() { + .fail(() => { console.log('Failed to fetch signed prekey:', keyId); resolve(); }); }); }, - loadSignedPreKeys: function() { + loadSignedPreKeys() { if (arguments.length > 0) { return Promise.reject( new Error('loadSignedPreKeys takes no arguments') ); } - var signedPreKeys = new SignedPreKeyCollection(); - return new Promise(function(resolve) { - signedPreKeys.fetch().then(function() { + const signedPreKeys = new SignedPreKeyCollection(); + return new Promise(resolve => { + signedPreKeys.fetch().then(() => { resolve( - signedPreKeys.map(function(prekey) { - return { - pubKey: prekey.get('publicKey'), - privKey: prekey.get('privateKey'), - created_at: prekey.get('created_at'), - keyId: prekey.get('id'), - confirmed: prekey.get('confirmed'), - }; - }) + signedPreKeys.map(prekey => ({ + pubKey: prekey.get('publicKey'), + privKey: prekey.get('privateKey'), + created_at: prekey.get('created_at'), + keyId: prekey.get('id'), + confirmed: prekey.get('confirmed'), + })) ); }); }); }, - storeSignedPreKey: function(keyId, keyPair, confirmed) { - var prekey = new SignedPreKey({ + storeSignedPreKey(keyId, keyPair, confirmed) { + const prekey = new SignedPreKey({ id: keyId, publicKey: keyPair.pubKey, privateKey: keyPair.privKey, created_at: Date.now(), confirmed: Boolean(confirmed), }); - return new Promise(function(resolve) { - prekey.save().always(function() { + return new Promise(resolve => { + prekey.save().always(() => { resolve(); }); }); }, - removeSignedPreKey: function(keyId) { - var prekey = new SignedPreKey({ id: keyId }); - return new Promise(function(resolve, reject) { - var deferred = prekey.destroy(); + removeSignedPreKey(keyId) { + const prekey = new SignedPreKey({ id: keyId }); + return new Promise((resolve, reject) => { + const deferred = prekey.destroy(); if (!deferred) { return resolve(); } - deferred.then(resolve, reject); + return deferred.then(resolve, reject); }); }, - clearSignedPreKeysStore: function() { - return new Promise(function(resolve) { - var signedPreKeys = new SignedPreKeyCollection(); + clearSignedPreKeysStore() { + return new Promise(resolve => { + const signedPreKeys = new SignedPreKeyCollection(); signedPreKeys.sync('delete', signedPreKeys, {}).always(resolve); }); }, - loadSession: function(encodedNumber) { + loadSession(encodedNumber) { if (encodedNumber === null || encodedNumber === undefined) { throw new Error('Tried to get session for undefined/null number'); } - return new Promise(function(resolve) { - var session = new Session({ id: encodedNumber }); - session.fetch().always(function() { + return new Promise(resolve => { + const session = new Session({ id: encodedNumber }); + session.fetch().always(() => { resolve(session.get('record')); }); }); }, - storeSession: function(encodedNumber, record) { + storeSession(encodedNumber, record) { if (encodedNumber === null || encodedNumber === undefined) { throw new Error('Tried to put session for undefined/null number'); } - return new Promise(function(resolve) { - var number = textsecure.utils.unencodeNumber(encodedNumber)[0]; - var deviceId = parseInt( - textsecure.utils.unencodeNumber(encodedNumber)[1] + return new Promise(resolve => { + const number = textsecure.utils.unencodeNumber(encodedNumber)[0]; + const deviceId = parseInt( + textsecure.utils.unencodeNumber(encodedNumber)[1], + 10 ); - var session = new Session({ id: encodedNumber }); - session.fetch().always(function() { + const session = new Session({ id: encodedNumber }); + session.fetch().always(() => { session .save({ - record: record, - deviceId: deviceId, - number: number, + record, + deviceId, + number, }) - .fail(function(e) { + .fail(e => { console.log('Failed to save session', encodedNumber, e); }) - .always(function() { + .always(() => { resolve(); }); }); }); }, - getDeviceIds: function(number) { + getDeviceIds(number) { if (number === null || number === undefined) { throw new Error('Tried to get device ids for undefined/null number'); } - return new Promise(function(resolve) { - var sessions = new SessionCollection(); - sessions.fetchSessionsForNumber(number).always(function() { + return new Promise(resolve => { + const sessions = new SessionCollection(); + sessions.fetchSessionsForNumber(number).always(() => { resolve(sessions.pluck('deviceId')); }); }); }, - removeSession: function(encodedNumber) { + removeSession(encodedNumber) { console.log('deleting session for ', encodedNumber); - return new Promise(function(resolve) { - var session = new Session({ id: encodedNumber }); + return new Promise(resolve => { + const session = new Session({ id: encodedNumber }); session .fetch() - .then(function() { + .then(() => { session.destroy().then(resolve); }) .fail(resolve); }); }, - removeAllSessions: function(number) { + removeAllSessions(number) { if (number === null || number === undefined) { throw new Error('Tried to remove sessions for undefined/null number'); } - return new Promise(function(resolve, reject) { - var sessions = new SessionCollection(); - sessions.fetchSessionsForNumber(number).always(function() { - var promises = []; + return new Promise((resolve, reject) => { + const sessions = new SessionCollection(); + sessions.fetchSessionsForNumber(number).always(() => { + const promises = []; while (sessions.length > 0) { promises.push( - new Promise(function(res, rej) { + new Promise((res, rej) => { sessions .pop() .destroy() @@ -412,18 +426,18 @@ }); }); }, - archiveSiblingSessions: function(identifier) { - var address = libsignal.SignalProtocolAddress.fromString(identifier); - return this.getDeviceIds(address.getName()).then(function(deviceIds) { - var deviceIds = _.without(deviceIds, address.getDeviceId()); + archiveSiblingSessions(identifier) { + const address = libsignal.SignalProtocolAddress.fromString(identifier); + return this.getDeviceIds(address.getName()).then(deviceIds => { + const siblings = _.without(deviceIds, address.getDeviceId()); return Promise.all( - deviceIds.map(function(deviceId) { - var sibling = new libsignal.SignalProtocolAddress( + siblings.map(deviceId => { + const sibling = new libsignal.SignalProtocolAddress( address.getName(), deviceId ); console.log('closing session for', sibling.toString()); - var sessionCipher = new libsignal.SessionCipher( + const sessionCipher = new libsignal.SessionCipher( textsecure.storage.protocol, sibling ); @@ -432,57 +446,58 @@ ); }); }, - archiveAllSessions: function(number) { - return this.getDeviceIds(number).then(function(deviceIds) { - return Promise.all( - deviceIds.map(function(deviceId) { - var address = new libsignal.SignalProtocolAddress(number, deviceId); + archiveAllSessions(number) { + return this.getDeviceIds(number).then(deviceIds => + Promise.all( + deviceIds.map(deviceId => { + const address = new libsignal.SignalProtocolAddress( + number, + deviceId + ); console.log('closing session for', address.toString()); - var sessionCipher = new libsignal.SessionCipher( + const sessionCipher = new libsignal.SessionCipher( textsecure.storage.protocol, address ); return sessionCipher.closeOpenSessionForDevice(); }) - ); - }); + ) + ); }, - clearSessionStore: function() { - return new Promise(function(resolve) { - var sessions = new SessionCollection(); + clearSessionStore() { + return new Promise(resolve => { + const sessions = new SessionCollection(); sessions.sync('delete', sessions, {}).always(resolve); }); }, - isTrustedIdentity: function(identifier, publicKey, direction) { + isTrustedIdentity(identifier, publicKey, direction) { if (identifier === null || identifier === undefined) { throw new Error('Tried to get identity key for undefined/null key'); } - var number = textsecure.utils.unencodeNumber(identifier)[0]; - var isOurNumber = number === textsecure.storage.user.getNumber(); - var identityRecord = new IdentityRecord({ id: number }); - return new Promise(function(resolve) { + const number = textsecure.utils.unencodeNumber(identifier)[0]; + const isOurNumber = number === textsecure.storage.user.getNumber(); + const identityRecord = new IdentityRecord({ id: number }); + return new Promise(resolve => { identityRecord.fetch().always(resolve); - }).then( - function() { - var existing = identityRecord.get('publicKey'); + }).then(() => { + const existing = identityRecord.get('publicKey'); - if (isOurNumber) { - return equalArrayBuffers(existing, publicKey); - } + if (isOurNumber) { + return equalArrayBuffers(existing, publicKey); + } - switch (direction) { - case Direction.SENDING: - return this.isTrustedForSending(publicKey, identityRecord); - case Direction.RECEIVING: - return true; - default: - throw new Error('Unknown direction: ' + direction); - } - }.bind(this) - ); + switch (direction) { + case Direction.SENDING: + return this.isTrustedForSending(publicKey, identityRecord); + case Direction.RECEIVING: + return true; + default: + throw new Error(`Unknown direction: ${direction}`); + } + }); }, - isTrustedForSending: function(publicKey, identityRecord) { - var existing = identityRecord.get('publicKey'); + isTrustedForSending(publicKey, identityRecord) { + const existing = identityRecord.get('publicKey'); if (!existing) { console.log('isTrustedForSending: Nothing here, returning true...'); @@ -503,109 +518,104 @@ return true; }, - loadIdentityKey: function(identifier) { + loadIdentityKey(identifier) { if (identifier === null || identifier === undefined) { throw new Error('Tried to get identity key for undefined/null key'); } - var number = textsecure.utils.unencodeNumber(identifier)[0]; - return new Promise(function(resolve) { - var identityRecord = new IdentityRecord({ id: number }); - identityRecord.fetch().always(function() { + const number = textsecure.utils.unencodeNumber(identifier)[0]; + return new Promise(resolve => { + const identityRecord = new IdentityRecord({ id: number }); + identityRecord.fetch().always(() => { resolve(identityRecord.get('publicKey')); }); }); }, - saveIdentity: function(identifier, publicKey, nonblockingApproval) { + saveIdentity(identifier, publicKey, nonblockingApproval) { if (identifier === null || identifier === undefined) { throw new Error('Tried to put identity key for undefined/null key'); } if (!(publicKey instanceof ArrayBuffer)) { + // eslint-disable-next-line no-param-reassign publicKey = convertToArrayBuffer(publicKey); } if (typeof nonblockingApproval !== 'boolean') { + // eslint-disable-next-line no-param-reassign nonblockingApproval = false; } - var number = textsecure.utils.unencodeNumber(identifier)[0]; - return new Promise( - function(resolve, reject) { - var identityRecord = new IdentityRecord({ id: number }); - identityRecord.fetch().always( - function() { - var oldpublicKey = identityRecord.get('publicKey'); - if (!oldpublicKey) { - // Lookup failed, or the current key was removed, so save this one. - console.log('Saving new identity...'); - identityRecord - .save({ - publicKey: publicKey, - firstUse: true, - timestamp: Date.now(), - verified: VerifiedStatus.DEFAULT, - nonblockingApproval: nonblockingApproval, - }) - .then(function() { - resolve(false); - }, reject); - } else if (!equalArrayBuffers(oldpublicKey, publicKey)) { - console.log('Replacing existing identity...'); - var previousStatus = identityRecord.get('verified'); - var verifiedStatus; - if ( - previousStatus === VerifiedStatus.VERIFIED || - previousStatus === VerifiedStatus.UNVERIFIED - ) { - verifiedStatus = VerifiedStatus.UNVERIFIED; - } else { - verifiedStatus = VerifiedStatus.DEFAULT; - } - identityRecord - .save({ - publicKey: publicKey, - firstUse: false, - timestamp: Date.now(), - verified: verifiedStatus, - nonblockingApproval: nonblockingApproval, - }) - .then( - function() { - this.trigger('keychange', number); - this.archiveSiblingSessions(identifier).then(function() { - resolve(true); - }, reject); - }.bind(this), - reject - ); - } else if (this.isNonBlockingApprovalRequired(identityRecord)) { - console.log('Setting approval status...'); - identityRecord - .save({ - nonblockingApproval: nonblockingApproval, - }) - .then(function() { - resolve(false); - }, reject); - } else { + const number = textsecure.utils.unencodeNumber(identifier)[0]; + return new Promise((resolve, reject) => { + const identityRecord = new IdentityRecord({ id: number }); + identityRecord.fetch().always(() => { + const oldpublicKey = identityRecord.get('publicKey'); + if (!oldpublicKey) { + // Lookup failed, or the current key was removed, so save this one. + console.log('Saving new identity...'); + identityRecord + .save({ + publicKey, + firstUse: true, + timestamp: Date.now(), + verified: VerifiedStatus.DEFAULT, + nonblockingApproval, + }) + .then(() => { resolve(false); - } - }.bind(this) - ); - }.bind(this) - ); + }, reject); + } else if (!equalArrayBuffers(oldpublicKey, publicKey)) { + console.log('Replacing existing identity...'); + const previousStatus = identityRecord.get('verified'); + let verifiedStatus; + if ( + previousStatus === VerifiedStatus.VERIFIED || + previousStatus === VerifiedStatus.UNVERIFIED + ) { + verifiedStatus = VerifiedStatus.UNVERIFIED; + } else { + verifiedStatus = VerifiedStatus.DEFAULT; + } + identityRecord + .save({ + publicKey, + firstUse: false, + timestamp: Date.now(), + verified: verifiedStatus, + nonblockingApproval, + }) + .then(() => { + this.trigger('keychange', number); + this.archiveSiblingSessions(identifier).then(() => { + resolve(true); + }, reject); + }, reject); + } else if (this.isNonBlockingApprovalRequired(identityRecord)) { + console.log('Setting approval status...'); + identityRecord + .save({ + nonblockingApproval, + }) + .then(() => { + resolve(false); + }, reject); + } else { + resolve(false); + } + }); + }); }, - isNonBlockingApprovalRequired: function(identityRecord) { + isNonBlockingApprovalRequired(identityRecord) { return ( !identityRecord.get('firstUse') && Date.now() - identityRecord.get('timestamp') < TIMESTAMP_THRESHOLD && !identityRecord.get('nonblockingApproval') ); }, - saveIdentityWithAttributes: function(identifier, attributes) { + saveIdentityWithAttributes(identifier, attributes) { if (identifier === null || identifier === undefined) { throw new Error('Tried to put identity key for undefined/null key'); } - var number = textsecure.utils.unencodeNumber(identifier)[0]; - return new Promise(function(resolve, reject) { - var identityRecord = new IdentityRecord({ id: number }); + const number = textsecure.utils.unencodeNumber(identifier)[0]; + return new Promise((resolve, reject) => { + const identityRecord = new IdentityRecord({ id: number }); identityRecord.set(attributes); if (identityRecord.isValid()) { // false if invalid attributes @@ -615,34 +625,34 @@ } }); }, - setApproval: function(identifier, nonblockingApproval) { + setApproval(identifier, nonblockingApproval) { if (identifier === null || identifier === undefined) { throw new Error('Tried to set approval for undefined/null identifier'); } if (typeof nonblockingApproval !== 'boolean') { throw new Error('Invalid approval status'); } - var number = textsecure.utils.unencodeNumber(identifier)[0]; - return new Promise(function(resolve, reject) { - var identityRecord = new IdentityRecord({ id: number }); - identityRecord.fetch().then(function() { + const number = textsecure.utils.unencodeNumber(identifier)[0]; + return new Promise((resolve, reject) => { + const identityRecord = new IdentityRecord({ id: number }); + identityRecord.fetch().then(() => { identityRecord .save({ - nonblockingApproval: nonblockingApproval, + nonblockingApproval, }) .then( - function() { + () => { resolve(); }, - function() { + () => { // catch - reject(new Error('No identity record for ' + number)); + reject(new Error(`No identity record for ${number}`)); } ); }); }); }, - setVerified: function(identifier, verifiedStatus, publicKey) { + setVerified(identifier, verifiedStatus, publicKey) { if (identifier === null || identifier === undefined) { throw new Error('Tried to set verified for undefined/null key'); } @@ -652,10 +662,10 @@ if (arguments.length > 2 && !(publicKey instanceof ArrayBuffer)) { throw new Error('Invalid public key'); } - return new Promise(function(resolve, reject) { - var identityRecord = new IdentityRecord({ id: identifier }); + return new Promise((resolve, reject) => { + const identityRecord = new IdentityRecord({ id: identifier }); identityRecord.fetch().then( - function() { + () => { if ( !publicKey || equalArrayBuffers(identityRecord.get('publicKey'), publicKey) @@ -663,7 +673,7 @@ identityRecord.set({ verified: verifiedStatus }); if (identityRecord.isValid()) { - identityRecord.save({}).then(function() { + identityRecord.save({}).then(() => { resolve(); }, reject); } else { @@ -674,134 +684,117 @@ resolve(); } }, - function() { + () => { // catch - reject(new Error('No identity record for ' + identifier)); + reject(new Error(`No identity record for ${identifier}`)); } ); }); }, - getVerified: function(identifier) { + getVerified(identifier) { if (identifier === null || identifier === undefined) { throw new Error('Tried to set verified for undefined/null key'); } - return new Promise(function(resolve, reject) { - var identityRecord = new IdentityRecord({ id: identifier }); + return new Promise((resolve, reject) => { + const identityRecord = new IdentityRecord({ id: identifier }); identityRecord.fetch().then( - function() { - var verifiedStatus = identityRecord.get('verified'); + () => { + const verifiedStatus = identityRecord.get('verified'); if (validateVerifiedStatus(verifiedStatus)) { resolve(verifiedStatus); } else { resolve(VerifiedStatus.DEFAULT); } }, - function() { + () => { // catch - reject(new Error('No identity record for ' + identifier)); + reject(new Error(`No identity record for ${identifier}`)); } ); }); }, // Resolves to true if a new identity key was saved - processContactSyncVerificationState: function( - identifier, - verifiedStatus, - publicKey - ) { + processContactSyncVerificationState(identifier, verifiedStatus, publicKey) { if (verifiedStatus === VerifiedStatus.UNVERIFIED) { return this.processUnverifiedMessage( identifier, verifiedStatus, publicKey ); - } else { - return this.processVerifiedMessage( - identifier, - verifiedStatus, - publicKey - ); } + return this.processVerifiedMessage(identifier, verifiedStatus, publicKey); }, // This function encapsulates the non-Java behavior, since the mobile apps don't // currently receive contact syncs and therefore will see a verify sync with // UNVERIFIED status - processUnverifiedMessage: function(identifier, verifiedStatus, publicKey) { + processUnverifiedMessage(identifier, verifiedStatus, publicKey) { if (identifier === null || identifier === undefined) { throw new Error('Tried to set verified for undefined/null key'); } if (publicKey !== undefined && !(publicKey instanceof ArrayBuffer)) { throw new Error('Invalid public key'); } - return new Promise( - function(resolve, reject) { - var identityRecord = new IdentityRecord({ id: identifier }); - var isPresent = false; - var isEqual = false; - identityRecord - .fetch() - .then(function() { - isPresent = true; - if (publicKey) { - isEqual = equalArrayBuffers( + return new Promise((resolve, reject) => { + const identityRecord = new IdentityRecord({ id: identifier }); + let isPresent = false; + let isEqual = false; + identityRecord + .fetch() + .then(() => { + isPresent = true; + if (publicKey) { + isEqual = equalArrayBuffers( + publicKey, + identityRecord.get('publicKey') + ); + } + }) + .always(() => { + if ( + isPresent && + isEqual && + identityRecord.get('verified') !== VerifiedStatus.UNVERIFIED + ) { + return textsecure.storage.protocol + .setVerified(identifier, verifiedStatus, publicKey) + .then(resolve, reject); + } + + if (!isPresent || !isEqual) { + return textsecure.storage.protocol + .saveIdentityWithAttributes(identifier, { publicKey, - identityRecord.get('publicKey') - ); - } - }) - .always( - function() { - if ( - isPresent && - isEqual && - identityRecord.get('verified') !== VerifiedStatus.UNVERIFIED - ) { - return textsecure.storage.protocol - .setVerified(identifier, verifiedStatus, publicKey) - .then(resolve, reject); - } - - if (!isPresent || !isEqual) { - return textsecure.storage.protocol - .saveIdentityWithAttributes(identifier, { - publicKey: publicKey, - verified: verifiedStatus, - firstUse: false, - timestamp: Date.now(), - nonblockingApproval: true, - }) - .then( - function() { - if (isPresent && !isEqual) { - this.trigger('keychange', identifier); - return this.archiveAllSessions(identifier).then( - function() { - // true signifies that we overwrote a previous key with a new one - return resolve(true); - }, - reject - ); - } - - return resolve(); - }.bind(this), + verified: verifiedStatus, + firstUse: false, + timestamp: Date.now(), + nonblockingApproval: true, + }) + .then(() => { + if (isPresent && !isEqual) { + this.trigger('keychange', identifier); + return this.archiveAllSessions(identifier).then( + () => + // true signifies that we overwrote a previous key with a new one + resolve(true), reject ); - } + } - // The situation which could get us here is: - // 1. had a previous key - // 2. new key is the same - // 3. desired new status is same as what we had before - return resolve(); - }.bind(this) - ); - }.bind(this) - ); + return resolve(); + }, reject); + } + + // The situation which could get us here is: + // 1. had a previous key + // 2. new key is the same + // 3. desired new status is same as what we had before + return resolve(); + }); + }); }, // This matches the Java method as of // https://github.com/signalapp/Signal-Android/blob/d0bb68e1378f689e4d10ac6a46014164992ca4e4/src/org/thoughtcrime/securesms/util/IdentityUtil.java#L188 - processVerifiedMessage: function(identifier, verifiedStatus, publicKey) { + processVerifiedMessage(identifier, verifiedStatus, publicKey) { if (identifier === null || identifier === undefined) { throw new Error('Tried to set verified for undefined/null key'); } @@ -811,92 +804,83 @@ if (publicKey !== undefined && !(publicKey instanceof ArrayBuffer)) { throw new Error('Invalid public key'); } - return new Promise( - function(resolve, reject) { - var identityRecord = new IdentityRecord({ id: identifier }); - var isPresent = false; - var isEqual = false; - identityRecord - .fetch() - .then(function() { - isPresent = true; - if (publicKey) { - isEqual = equalArrayBuffers( + return new Promise((resolve, reject) => { + const identityRecord = new IdentityRecord({ id: identifier }); + let isPresent = false; + let isEqual = false; + identityRecord + .fetch() + .then(() => { + isPresent = true; + if (publicKey) { + isEqual = equalArrayBuffers( + publicKey, + identityRecord.get('publicKey') + ); + } + }) + .always(() => { + if (!isPresent && verifiedStatus === VerifiedStatus.DEFAULT) { + console.log('No existing record for default status'); + return resolve(); + } + + if ( + isPresent && + isEqual && + identityRecord.get('verified') !== VerifiedStatus.DEFAULT && + verifiedStatus === VerifiedStatus.DEFAULT + ) { + return textsecure.storage.protocol + .setVerified(identifier, verifiedStatus, publicKey) + .then(resolve, reject); + } + + if ( + verifiedStatus === VerifiedStatus.VERIFIED && + (!isPresent || + (isPresent && !isEqual) || + (isPresent && + identityRecord.get('verified') !== VerifiedStatus.VERIFIED)) + ) { + return textsecure.storage.protocol + .saveIdentityWithAttributes(identifier, { publicKey, - identityRecord.get('publicKey') - ); - } - }) - .always( - function() { - if (!isPresent && verifiedStatus === VerifiedStatus.DEFAULT) { - console.log('No existing record for default status'); - return resolve(); - } - - if ( - isPresent && - isEqual && - identityRecord.get('verified') !== VerifiedStatus.DEFAULT && - verifiedStatus === VerifiedStatus.DEFAULT - ) { - return textsecure.storage.protocol - .setVerified(identifier, verifiedStatus, publicKey) - .then(resolve, reject); - } - - if ( - verifiedStatus === VerifiedStatus.VERIFIED && - (!isPresent || - (isPresent && !isEqual) || - (isPresent && - identityRecord.get('verified') !== - VerifiedStatus.VERIFIED)) - ) { - return textsecure.storage.protocol - .saveIdentityWithAttributes(identifier, { - publicKey: publicKey, - verified: verifiedStatus, - firstUse: false, - timestamp: Date.now(), - nonblockingApproval: true, - }) - .then( - function() { - if (isPresent && !isEqual) { - this.trigger('keychange', identifier); - return this.archiveAllSessions(identifier).then( - function() { - // true signifies that we overwrote a previous key with a new one - return resolve(true); - }, - reject - ); - } - - return resolve(); - }.bind(this), + verified: verifiedStatus, + firstUse: false, + timestamp: Date.now(), + nonblockingApproval: true, + }) + .then(() => { + if (isPresent && !isEqual) { + this.trigger('keychange', identifier); + return this.archiveAllSessions(identifier).then( + () => + // true signifies that we overwrote a previous key with a new one + resolve(true), reject ); - } + } - // We get here if we got a new key and the status is DEFAULT. If the - // message is out of date, we don't want to lose whatever more-secure - // state we had before. - return resolve(); - }.bind(this) - ); - }.bind(this) - ); + return resolve(); + }, reject); + } + + // We get here if we got a new key and the status is DEFAULT. If the + // message is out of date, we don't want to lose whatever more-secure + // state we had before. + return resolve(); + }); + }); }, - isUntrusted: function(identifier) { + isUntrusted(identifier) { if (identifier === null || identifier === undefined) { throw new Error('Tried to set verified for undefined/null key'); } - return new Promise(function(resolve, reject) { - var identityRecord = new IdentityRecord({ id: identifier }); + return new Promise((resolve, reject) => { + const identityRecord = new IdentityRecord({ id: identifier }); identityRecord.fetch().then( - function() { + () => { if ( Date.now() - identityRecord.get('timestamp') < TIMESTAMP_THRESHOLD && @@ -908,14 +892,14 @@ resolve(false); } }, - function() { + () => { // catch - reject(new Error('No identity record for ' + identifier)); + reject(new Error(`No identity record for ${identifier}`)); } ); }); }, - removeIdentityKey: async function(number) { + async removeIdentityKey(number) { const identityRecord = new IdentityRecord({ id: number }); try { await wrapDeferred(identityRecord.fetch()); @@ -927,79 +911,75 @@ }, // Groups - getGroup: function(groupId) { + getGroup(groupId) { if (groupId === null || groupId === undefined) { throw new Error('Tried to get group for undefined/null id'); } - return new Promise(function(resolve) { - var group = new Group({ id: groupId }); - group.fetch().always(function() { + return new Promise(resolve => { + const group = new Group({ id: groupId }); + group.fetch().always(() => { resolve(group.get('data')); }); }); }, - putGroup: function(groupId, group) { + putGroup(groupId, group) { if (groupId === null || groupId === undefined) { throw new Error('Tried to put group key for undefined/null id'); } if (group === null || group === undefined) { throw new Error('Tried to put undefined/null group object'); } - var group = new Group({ id: groupId, data: group }); - return new Promise(function(resolve) { - group.save().always(resolve); + const newGroup = new Group({ id: groupId, data: group }); + return new Promise(resolve => { + newGroup.save().always(resolve); }); }, - removeGroup: function(groupId) { + removeGroup(groupId) { if (groupId === null || groupId === undefined) { throw new Error('Tried to remove group key for undefined/null id'); } - return new Promise(function(resolve) { - var group = new Group({ id: groupId }); + return new Promise(resolve => { + const group = new Group({ id: groupId }); group.destroy().always(resolve); }); }, // Not yet processed messages - for resiliency - getAllUnprocessed: function() { - var collection; - return new Promise(function(resolve, reject) { + getAllUnprocessed() { + let collection; + return new Promise((resolve, reject) => { collection = new UnprocessedCollection(); return collection.fetch().then(resolve, reject); - }).then(function() { + }).then(() => // Return a plain array of plain objects - return collection.map(model => model.attributes); - }); + collection.map(model => model.attributes) + ); }, - addUnprocessed: function(data) { - return new Promise(function(resolve, reject) { - var unprocessed = new Unprocessed(data); + addUnprocessed(data) { + return new Promise((resolve, reject) => { + const unprocessed = new Unprocessed(data); return unprocessed.save().then(resolve, reject); }); }, - updateUnprocessed: function(id, updates) { - return new Promise( - function(resolve, reject) { - var unprocessed = new Unprocessed({ - id: id, - }); - return unprocessed.fetch().then(function() { - return unprocessed.save(updates).then(resolve, reject); - }, reject); - }.bind(this) - ); + updateUnprocessed(id, updates) { + return new Promise((resolve, reject) => { + const unprocessed = new Unprocessed({ + id, + }); + return unprocessed + .fetch() + .then(() => unprocessed.save(updates).then(resolve, reject), reject); + }); }, - removeUnprocessed: function(id) { - return new Promise( - function(resolve, reject) { - var unprocessed = new Unprocessed({ - id: id, - }); - return unprocessed.destroy().then(resolve, reject); - }.bind(this) - ); + removeUnprocessed(id) { + return new Promise((resolve, reject) => { + const unprocessed = new Unprocessed({ + id, + }); + return unprocessed.destroy().then(resolve, reject); + }); }, - removeAllData: function() { + removeAllData() { // First the in-memory caches: window.storage.reset(); // items store ConversationController.reset(); // conversations store @@ -1007,7 +987,7 @@ // Then, the entire database: return Whisper.Database.clear(); }, - removeAllConfiguration: function() { + removeAllConfiguration() { // First the in-memory cache for the items store: window.storage.reset(); diff --git a/js/spell_check.js b/js/spell_check.js index d27a2e57c324..b2b4814e6c4c 100644 --- a/js/spell_check.js +++ b/js/spell_check.js @@ -1,152 +1,153 @@ -(function() { - var electron = require('electron'); - var remote = electron.remote; - var app = remote.app; - var webFrame = electron.webFrame; - var path = require('path'); +/* global require, process, _ */ - var osLocale = require('os-locale'); - var os = require('os'); - var semver = require('semver'); - var spellchecker = require('spellchecker'); +/* eslint-disable strict */ - // `remote.require` since `Menu` is a main-process module. - var buildEditorContextMenu = remote.require('electron-editor-context-menu'); +const electron = require('electron'); - var EN_VARIANT = /^en/; +const osLocale = require('os-locale'); +const os = require('os'); +const semver = require('semver'); +const spellchecker = require('spellchecker'); - // Prevent the spellchecker from showing contractions as errors. - var ENGLISH_SKIP_WORDS = [ - 'ain', - 'couldn', - 'didn', - 'doesn', - 'hadn', - 'hasn', - 'mightn', - 'mustn', - 'needn', - 'oughtn', - 'shan', - 'shouldn', - 'wasn', - 'weren', - 'wouldn', - ]; +const { remote, webFrame } = electron; - function setupLinux(locale) { - if (process.env.HUNSPELL_DICTIONARIES || locale !== 'en_US') { - // apt-get install hunspell- can be run for easy access to other dictionaries - var location = process.env.HUNSPELL_DICTIONARIES || '/usr/share/hunspell'; +// `remote.require` since `Menu` is a main-process module. +const buildEditorContextMenu = remote.require('electron-editor-context-menu'); - console.log( - 'Detected Linux. Setting up spell check with locale', - locale, - 'and dictionary location', - location - ); - spellchecker.setDictionary(locale, location); - } else { - console.log('Detected Linux. Using default en_US spell check dictionary'); - } - } +const EN_VARIANT = /^en/; - function setupWin7AndEarlier(locale) { - if (process.env.HUNSPELL_DICTIONARIES || locale !== 'en_US') { - var location = process.env.HUNSPELL_DICTIONARIES; +// Prevent the spellchecker from showing contractions as errors. +const ENGLISH_SKIP_WORDS = [ + 'ain', + 'couldn', + 'didn', + 'doesn', + 'hadn', + 'hasn', + 'mightn', + 'mustn', + 'needn', + 'oughtn', + 'shan', + 'shouldn', + 'wasn', + 'weren', + 'wouldn', +]; - console.log( - 'Detected Windows 7 or below. Setting up spell-check with locale', - locale, - 'and dictionary location', - location - ); - spellchecker.setDictionary(locale, location); - } else { - console.log( - 'Detected Windows 7 or below. Using default en_US spell check dictionary' - ); - } - } +function setupLinux(locale) { + if (process.env.HUNSPELL_DICTIONARIES || locale !== 'en_US') { + // apt-get install hunspell- can be run for easy access + // to other dictionaries + const location = process.env.HUNSPELL_DICTIONARIES || '/usr/share/hunspell'; - // We load locale this way and not via app.getLocale() because this call returns - // 'es_ES' and not just 'es.' And hunspell requires the fully-qualified locale. - var locale = osLocale.sync().replace('-', '_'); - - // The LANG environment variable is how node spellchecker finds its default language: - // https://github.com/atom/node-spellchecker/blob/59d2d5eee5785c4b34e9669cd5d987181d17c098/lib/spellchecker.js#L29 - if (!process.env.LANG) { - process.env.LANG = locale; - } - - if (process.platform === 'linux') { - setupLinux(locale); - } else if ( - process.platform === 'windows' && - semver.lt(os.release(), '8.0.0') - ) { - setupWin7AndEarlier(locale); + console.log( + 'Detected Linux. Setting up spell check with locale', + locale, + 'and dictionary location', + location + ); + spellchecker.setDictionary(locale, location); } else { - // OSX and Windows 8+ have OS-level spellcheck APIs - console.log('Using OS-level spell check API with locale', process.env.LANG); + console.log('Detected Linux. Using default en_US spell check dictionary'); } +} - var simpleChecker = (window.spellChecker = { - spellCheck: function(text) { - return !this.isMisspelled(text); - }, - isMisspelled: function(text) { - var misspelled = spellchecker.isMisspelled(text); +function setupWin7AndEarlier(locale) { + if (process.env.HUNSPELL_DICTIONARIES || locale !== 'en_US') { + const location = process.env.HUNSPELL_DICTIONARIES; - // The idea is to make this as fast as possible. For the many, many calls which - // don't result in the red squiggly, we minimize the number of checks. - if (!misspelled) { - return false; - } + console.log( + 'Detected Windows 7 or below. Setting up spell-check with locale', + locale, + 'and dictionary location', + location + ); + spellchecker.setDictionary(locale, location); + } else { + console.log( + 'Detected Windows 7 or below. Using default en_US spell check dictionary' + ); + } +} - // Only if we think we've found an error do we check the locale and skip list. - if (locale.match(EN_VARIANT) && _.contains(ENGLISH_SKIP_WORDS, text)) { - return false; - } +// We load locale this way and not via app.getLocale() because this call returns +// 'es_ES' and not just 'es.' And hunspell requires the fully-qualified locale. +const locale = osLocale.sync().replace('-', '_'); - return true; - }, - getSuggestions: function(text) { - return spellchecker.getCorrectionsForMisspelling(text); - }, - add: function(text) { - spellchecker.add(text); - }, - }); +// The LANG environment variable is how node spellchecker finds its default language: +// https://github.com/atom/node-spellchecker/blob/59d2d5eee5785c4b34e9669cd5d987181d17c098/lib/spellchecker.js#L29 +if (!process.env.LANG) { + process.env.LANG = locale; +} - webFrame.setSpellCheckProvider( - 'en-US', - // Not sure what this parameter (`autoCorrectWord`) does: https://github.com/atom/electron/issues/4371 - // The documentation for `webFrame.setSpellCheckProvider` passes `true` so we do too. - true, - simpleChecker - ); +if (process.platform === 'linux') { + setupLinux(locale); +} else if (process.platform === 'windows' && semver.lt(os.release(), '8.0.0')) { + setupWin7AndEarlier(locale); +} else { + // OSX and Windows 8+ have OS-level spellcheck APIs + console.log('Using OS-level spell check API with locale', process.env.LANG); +} - window.addEventListener('contextmenu', function(e) { - // Only show the context menu in text editors. - if (!e.target.closest('textarea, input, [contenteditable="true"]')) { - return; +const simpleChecker = { + spellCheck(text) { + return !this.isMisspelled(text); + }, + isMisspelled(text) { + const misspelled = spellchecker.isMisspelled(text); + + // The idea is to make this as fast as possible. For the many, many calls which + // don't result in the red squiggly, we minimize the number of checks. + if (!misspelled) { + return false; } - var selectedText = window.getSelection().toString(); - var isMisspelled = selectedText && simpleChecker.isMisspelled(selectedText); - var spellingSuggestions = - isMisspelled && simpleChecker.getSuggestions(selectedText).slice(0, 5); - var menu = buildEditorContextMenu({ - isMisspelled: isMisspelled, - spellingSuggestions: spellingSuggestions, - }); + // Only if we think we've found an error do we check the locale and skip list. + if (locale.match(EN_VARIANT) && _.contains(ENGLISH_SKIP_WORDS, text)) { + return false; + } - // The 'contextmenu' event is emitted after 'selectionchange' has fired but possibly before the - // visible selection has changed. Try to wait to show the menu until after that, otherwise the - // visible selection will update after the menu dismisses and look weird. - setTimeout(function() { - menu.popup(remote.getCurrentWindow()); - }, 30); + return true; + }, + getSuggestions(text) { + return spellchecker.getCorrectionsForMisspelling(text); + }, + add(text) { + spellchecker.add(text); + }, +}; + +window.spellChecker = simpleChecker; + +webFrame.setSpellCheckProvider( + 'en-US', + // Not sure what this parameter (`autoCorrectWord`) does: https://github.com/atom/electron/issues/4371 + // The documentation for `webFrame.setSpellCheckProvider` passes `true` so we do too. + true, + simpleChecker +); + +window.addEventListener('contextmenu', e => { + // Only show the context menu in text editors. + if (!e.target.closest('textarea, input, [contenteditable="true"]')) { + return; + } + + const selectedText = window.getSelection().toString(); + const isMisspelled = selectedText && simpleChecker.isMisspelled(selectedText); + const spellingSuggestions = + isMisspelled && simpleChecker.getSuggestions(selectedText).slice(0, 5); + const menu = buildEditorContextMenu({ + isMisspelled, + spellingSuggestions, }); -})(); + + // The 'contextmenu' event is emitted after 'selectionchange' has fired + // but possibly before the visible selection has changed. Try to wait + // to show the menu until after that, otherwise the visible selection + // will update after the menu dismisses and look weird. + setTimeout(() => { + menu.popup(remote.getCurrentWindow()); + }, 30); +}); diff --git a/js/storage.js b/js/storage.js index 93f427edacac..86aeea73e195 100644 --- a/js/storage.js +++ b/js/storage.js @@ -1,58 +1,64 @@ +/* global Backbone, Whisper */ + +/* eslint-disable more/no-then */ + +// eslint-disable-next-line func-names (function() { 'use strict'; + window.Whisper = window.Whisper || {}; - var Item = Backbone.Model.extend({ + const Item = Backbone.Model.extend({ database: Whisper.Database, storeName: 'items', }); - var ItemCollection = Backbone.Collection.extend({ + const ItemCollection = Backbone.Collection.extend({ model: Item, storeName: 'items', database: Whisper.Database, }); - var ready = false; - var items = new ItemCollection(); - items.on('reset', function() { + let ready = false; + const items = new ItemCollection(); + items.on('reset', () => { ready = true; }); window.storage = { - /***************************** + /** *************************** *** Base Storage Routines *** - *****************************/ - put: function(key, value) { + **************************** */ + put(key, value) { if (value === undefined) { throw new Error('Tried to store undefined'); } if (!ready) { console.log('Called storage.put before storage is ready. key:', key); } - var item = items.add({ id: key, value: value }, { merge: true }); - return new Promise(function(resolve, reject) { + const item = items.add({ id: key, value }, { merge: true }); + return new Promise((resolve, reject) => { item.save().then(resolve, reject); }); }, - get: function(key, defaultValue) { - var item = items.get('' + key); + get(key, defaultValue) { + const item = items.get(`${key}`); if (!item) { return defaultValue; } return item.get('value'); }, - remove: function(key) { - var item = items.get('' + key); + remove(key) { + const item = items.get(`${key}`); if (item) { items.remove(item); - return new Promise(function(resolve, reject) { + return new Promise((resolve, reject) => { item.destroy().then(resolve, reject); }); } return Promise.resolve(); }, - onready: function(callback) { + onready(callback) { if (ready) { callback(); } else { @@ -60,7 +66,7 @@ } }, - fetch: function() { + fetch() { return new Promise((resolve, reject) => { items .fetch({ reset: true }) @@ -76,7 +82,7 @@ }); }, - reset: function() { + reset() { items.reset(); }, }; diff --git a/js/views/app_view.js b/js/views/app_view.js index eff7b1a2e1d3..26420b6b5bd3 100644 --- a/js/views/app_view.js +++ b/js/views/app_view.js @@ -1,10 +1,15 @@ +/* global Backbone, Whisper, storage, _, ConversationController, $ */ + +/* eslint-disable more/no-then */ + +// eslint-disable-next-line func-names (function() { 'use strict'; window.Whisper = window.Whisper || {}; Whisper.AppView = Backbone.View.extend({ - initialize: function(options) { + initialize() { this.inboxView = null; this.installView = null; @@ -15,38 +20,41 @@ 'click .openInstaller': 'openInstaller', // NetworkStatusView has this button openInbox: 'openInbox', }, - applyTheme: function() { - var theme = storage.get('theme-setting') || 'light'; + applyTheme() { + const theme = storage.get('theme-setting') || 'light'; this.$el .removeClass('light-theme') .removeClass('dark-theme') .addClass(`${theme}-theme`); }, - applyHideMenu: function() { - var hideMenuBar = storage.get('hide-menu-bar', false); + applyHideMenu() { + const hideMenuBar = storage.get('hide-menu-bar', false); window.setAutoHideMenuBar(hideMenuBar); window.setMenuBarVisibility(!hideMenuBar); }, - openView: function(view) { + openView(view) { this.el.innerHTML = ''; this.el.append(view.el); this.delegateEvents(); }, - openDebugLog: function() { + openDebugLog() { this.closeDebugLog(); this.debugLogView = new Whisper.DebugLogView(); this.debugLogView.$el.appendTo(this.el); }, - closeDebugLog: function() { + closeDebugLog() { if (this.debugLogView) { this.debugLogView.remove(); this.debugLogView = null; } }, - openImporter: function() { + openImporter() { window.addSetupMenuItems(); this.resetViews(); - var importView = (this.importView = new Whisper.ImportView()); + + const importView = new Whisper.ImportView(); + this.importView = importView; + this.listenTo( importView, 'light-import', @@ -54,21 +62,19 @@ ); this.openView(this.importView); }, - finishLightImport: function() { - var options = { + finishLightImport() { + const options = { hasExistingData: true, }; this.openInstaller(options); }, - closeImporter: function() { + closeImporter() { if (this.importView) { this.importView.remove(); this.importView = null; } }, - openInstaller: function(options) { - options = options || {}; - + openInstaller(options = {}) { // If we're in the middle of import, we don't want to show the menu options // allowing the user to switch to other ways to set up the app. If they // switched back and forth in the middle of a light import, they'd lose all @@ -78,16 +84,18 @@ } this.resetViews(); - var installView = (this.installView = new Whisper.InstallView(options)); + const installView = new Whisper.InstallView(options); + this.installView = installView; + this.openView(this.installView); }, - closeInstaller: function() { + closeInstaller() { if (this.installView) { this.installView.remove(); this.installView = null; } }, - openStandalone: function() { + openStandalone() { if (window.getEnvironment() !== 'production') { window.addSetupMenuItems(); this.resetViews(); @@ -95,19 +103,18 @@ this.openView(this.standaloneView); } }, - closeStandalone: function() { + closeStandalone() { if (this.standaloneView) { this.standaloneView.remove(); this.standaloneView = null; } }, - resetViews: function() { + resetViews() { this.closeInstaller(); this.closeImporter(); this.closeStandalone(); }, - openInbox: function(options) { - options = options || {}; + openInbox(options = {}) { // The inbox can be created before the 'empty' event fires or afterwards. If // before, it's straightforward: the onEmpty() handler below updates the // view directly, and we're in good shape. If we create the inbox late, we @@ -130,44 +137,38 @@ // this.initialLoadComplete between the start of this method and the // creation of inboxView. this.inboxView = new Whisper.InboxView({ - model: self, - window: window, + window, initialLoadComplete: options.initialLoadComplete, }); - return ConversationController.loadPromise().then( - function() { - this.openView(this.inboxView); - }.bind(this) - ); - } else { - if (!$.contains(this.el, this.inboxView.el)) { + return ConversationController.loadPromise().then(() => { this.openView(this.inboxView); - } - window.focus(); // FIXME - return Promise.resolve(); + }); } + if (!$.contains(this.el, this.inboxView.el)) { + this.openView(this.inboxView); + } + window.focus(); // FIXME + return Promise.resolve(); }, - onEmpty: function() { - var view = this.inboxView; + onEmpty() { + const view = this.inboxView; this.initialLoadComplete = true; if (view) { view.onEmpty(); } }, - onProgress: function(count) { - var view = this.inboxView; + onProgress(count) { + const view = this.inboxView; if (view) { view.onProgress(count); } }, - openConversation: function(conversation) { + openConversation(conversation) { if (conversation) { - this.openInbox().then( - function() { - this.inboxView.openConversation(null, conversation); - }.bind(this) - ); + this.openInbox().then(() => { + this.inboxView.openConversation(null, conversation); + }); } }, }); diff --git a/js/views/attachment_preview_view.js b/js/views/attachment_preview_view.js index 53122db2d5a3..94c8091cc707 100644 --- a/js/views/attachment_preview_view.js +++ b/js/views/attachment_preview_view.js @@ -1,11 +1,15 @@ +/* global Whisper */ + +// eslint-disable-next-line func-names (function() { 'use strict'; + window.Whisper = window.Whisper || {}; Whisper.AttachmentPreviewView = Whisper.View.extend({ className: 'attachment-preview', templateName: 'attachment-preview', - render_attributes: function() { + render_attributes() { return { source: this.src }; }, }); diff --git a/js/views/banner_view.js b/js/views/banner_view.js index ae23b216f9b6..61ed44e0bee4 100644 --- a/js/views/banner_view.js +++ b/js/views/banner_view.js @@ -1,5 +1,9 @@ +/* global Whisper */ + +// eslint-disable-next-line func-names (function() { 'use strict'; + window.Whisper = window.Whisper || {}; Whisper.BannerView = Whisper.View.extend({ @@ -9,7 +13,7 @@ 'click .dismiss': 'onDismiss', 'click .body': 'onClick', }, - initialize: function(options) { + initialize(options) { this.message = options.message; this.callbacks = { onDismiss: options.onDismiss, @@ -17,16 +21,16 @@ }; this.render(); }, - render_attributes: function() { + render_attributes() { return { message: this.message, }; }, - onDismiss: function(e) { + onDismiss(e) { this.callbacks.onDismiss(); e.stopPropagation(); }, - onClick: function() { + onClick() { this.callbacks.onClick(); }, }); diff --git a/js/views/confirmation_dialog_view.js b/js/views/confirmation_dialog_view.js index 6d5b5569d09a..c2081d7336ba 100644 --- a/js/views/confirmation_dialog_view.js +++ b/js/views/confirmation_dialog_view.js @@ -1,11 +1,15 @@ +/* global Whisper, i18n */ + +// eslint-disable-next-line func-names (function() { 'use strict'; + window.Whisper = window.Whisper || {}; Whisper.ConfirmationDialogView = Whisper.View.extend({ className: 'confirmation-dialog modal', templateName: 'confirmation-dialog', - initialize: function(options) { + initialize(options) { this.message = options.message; this.hideCancel = options.hideCancel; @@ -22,7 +26,7 @@ 'click .ok': 'ok', 'click .cancel': 'cancel', }, - render_attributes: function() { + render_attributes() { return { message: this.message, showCancel: !this.hideCancel, @@ -30,24 +34,24 @@ ok: this.okText, }; }, - ok: function() { + ok() { this.remove(); if (this.resolve) { this.resolve(); } }, - cancel: function() { + cancel() { this.remove(); if (this.reject) { this.reject(); } }, - onKeyup: function(event) { + onKeyup(event) { if (event.key === 'Escape' || event.key === 'Esc') { this.cancel(); } }, - focusCancel: function() { + focusCancel() { this.$('.cancel').focus(); }, }); diff --git a/js/views/contact_list_view.js b/js/views/contact_list_view.js index 547c13b084f6..b9fb836c80be 100644 --- a/js/views/contact_list_view.js +++ b/js/views/contact_list_view.js @@ -1,5 +1,4 @@ /* global Whisper: false */ -/* global i18n: false */ /* global textsecure: false */ // eslint-disable-next-line func-names diff --git a/js/views/conversation_list_item_view.js b/js/views/conversation_list_item_view.js index 1b5ef792d8e9..b4bf605ab419 100644 --- a/js/views/conversation_list_item_view.js +++ b/js/views/conversation_list_item_view.js @@ -1,18 +1,22 @@ +/* global Whisper, _, extension, Backbone, Mustache */ + +// eslint-disable-next-line func-names (function() { 'use strict'; + window.Whisper = window.Whisper || {}; // list of conversations, showing user/group and last message sent Whisper.ConversationListItemView = Whisper.View.extend({ tagName: 'div', - className: function() { - return 'conversation-list-item contact ' + this.model.cid; + className() { + return `conversation-list-item contact ${this.model.cid}`; }, templateName: 'conversation-preview', events: { click: 'select', }, - initialize: function() { + initialize() { // auto update this.listenTo( this.model, @@ -22,7 +26,7 @@ this.listenTo(this.model, 'destroy', this.remove); // auto update this.listenTo(this.model, 'opened', this.markSelected); // auto update - var updateLastMessage = _.debounce( + const updateLastMessage = _.debounce( this.model.updateLastMessage.bind(this.model), 1000 ); @@ -33,23 +37,21 @@ ); this.listenTo(this.model, 'newmessage', updateLastMessage); - extension.windows.onClosed( - function() { - this.stopListening(); - }.bind(this) - ); + extension.windows.onClosed(() => { + this.stopListening(); + }); this.timeStampView = new Whisper.TimestampView({ brief: true }); this.model.updateLastMessage(); }, - markSelected: function() { + markSelected() { this.$el .addClass('selected') .siblings('.selected') .removeClass('selected'); }, - select: function(e) { + select() { this.markSelected(); this.$el.trigger('select', this.model); }, @@ -66,7 +68,7 @@ Backbone.View.prototype.remove.call(this); }, - render: function() { + render() { const lastMessage = this.model.get('lastMessage'); this.$el.html( @@ -117,7 +119,7 @@ this.$('.last-message').append(this.bodyView.el); } - var unread = this.model.get('unreadCount'); + const unread = this.model.get('unreadCount'); if (unread > 0) { this.$el.addClass('unread'); } else { diff --git a/js/views/conversation_list_view.js b/js/views/conversation_list_view.js index b46a43dbbd0c..ab9dea207c81 100644 --- a/js/views/conversation_list_view.js +++ b/js/views/conversation_list_view.js @@ -1,12 +1,16 @@ +/* global Whisper, getInboxCollection */ + +// eslint-disable-next-line func-names (function() { 'use strict'; + window.Whisper = window.Whisper || {}; Whisper.ConversationListView = Whisper.ListView.extend({ tagName: 'div', itemView: Whisper.ConversationListItemView, - updateLocation: function(conversation) { - var $el = this.$('.' + conversation.cid); + updateLocation(conversation) { + const $el = this.$(`.${conversation.cid}`); if (!$el || !$el.length) { console.log( @@ -23,11 +27,11 @@ return; } - var $allConversations = this.$('.conversation-list-item'); - var inboxCollection = getInboxCollection(); - var index = inboxCollection.indexOf(conversation); + const $allConversations = this.$('.conversation-list-item'); + const inboxCollection = getInboxCollection(); + const index = inboxCollection.indexOf(conversation); - var elIndex = $allConversations.index($el); + const elIndex = $allConversations.index($el); if (elIndex < 0) { console.log( 'updateLocation: did not find index for conversation', @@ -43,8 +47,8 @@ } else if (index === this.collection.length - 1) { this.$el.append($el); } else { - var targetConversation = inboxCollection.at(index - 1); - var target = this.$('.' + targetConversation.cid); + const targetConversation = inboxCollection.at(index - 1); + const target = this.$(`.${targetConversation.cid}`); $el.insertAfter(target); } @@ -54,8 +58,8 @@ }); } }, - removeItem: function(conversation) { - var $el = this.$('.' + conversation.cid); + removeItem(conversation) { + const $el = this.$(`.${conversation.cid}`); if ($el && $el.length > 0) { $el.remove(); } diff --git a/js/views/error_view.js b/js/views/error_view.js deleted file mode 100644 index 041d334f7cce..000000000000 --- a/js/views/error_view.js +++ /dev/null @@ -1,13 +0,0 @@ -(function() { - 'use strict'; - - window.Whisper = window.Whisper || {}; - - var ErrorView = Whisper.View.extend({ - className: 'error', - templateName: 'generic-error', - render_attributes: function() { - return this.model; - }, - }); -})(); diff --git a/js/views/group_member_list_view.js b/js/views/group_member_list_view.js index 5b7201e85501..50a121cdc7e3 100644 --- a/js/views/group_member_list_view.js +++ b/js/views/group_member_list_view.js @@ -1,12 +1,16 @@ +/* global Whisper, i18n */ + +// eslint-disable-next-line func-names (function() { 'use strict'; + window.Whisper = window.Whisper || {}; // TODO: take a title string which could replace the 'members' header Whisper.GroupMemberList = Whisper.View.extend({ className: 'group-member-list panel', templateName: 'group-member-list', - initialize: function(options) { + initialize(options) { this.needVerify = options.needVerify; this.render(); @@ -22,15 +26,15 @@ this.$('.container').append(this.member_list_view.el); }, - render_attributes: function() { - var summary; + render_attributes() { + let summary; if (this.needVerify) { summary = i18n('membersNeedingVerification'); } return { members: i18n('groupMembers'), - summary: summary, + summary, }; }, }); diff --git a/js/views/group_update_view.js b/js/views/group_update_view.js index 0cff0b992750..f3515647c9c5 100644 --- a/js/views/group_update_view.js +++ b/js/views/group_update_view.js @@ -1,3 +1,6 @@ +/* global Backbone, Whisper */ + +// eslint-disable-next-line func-names (function() { 'use strict'; @@ -6,19 +9,19 @@ Whisper.GroupUpdateView = Backbone.View.extend({ tagName: 'div', className: 'group-update', - render: function() { - //TODO l10n + render() { + // TODO l10n if (this.model.left) { - this.$el.text(this.model.left + ' left the group'); + this.$el.text(`${this.model.left} left the group`); return this; } - var messages = ['Updated the group.']; + const messages = ['Updated the group.']; if (this.model.name) { - messages.push("Title is now '" + this.model.name + "'."); + messages.push(`Title is now '${this.model.name}'.`); } if (this.model.joined) { - messages.push(this.model.joined.join(', ') + ' joined the group'); + messages.push(`${this.model.joined.join(', ')} joined the group`); } this.$el.text(messages.join(' ')); diff --git a/js/views/hint_view.js b/js/views/hint_view.js index 63b07d965a5e..8d1a54813f72 100644 --- a/js/views/hint_view.js +++ b/js/views/hint_view.js @@ -1,13 +1,17 @@ +/* global Whisper */ + +// eslint-disable-next-line func-names (function() { 'use strict'; + window.Whisper = window.Whisper || {}; Whisper.HintView = Whisper.View.extend({ templateName: 'hint', - initialize: function(options) { + initialize(options) { this.content = options.content; }, - render_attributes: function() { + render_attributes() { return { content: this.content }; }, }); diff --git a/js/views/identicon_svg_view.js b/js/views/identicon_svg_view.js index 31b2669c3655..e044f0bd4aaf 100644 --- a/js/views/identicon_svg_view.js +++ b/js/views/identicon_svg_view.js @@ -1,5 +1,9 @@ +/* global Whisper, loadImage */ + +// eslint-disable-next-line func-names (function() { 'use strict'; + window.Whisper = window.Whisper || {}; /* @@ -7,26 +11,26 @@ */ Whisper.IdenticonSVGView = Whisper.View.extend({ templateName: 'identicon-svg', - initialize: function(options) { + initialize(options) { this.render_attributes = options; this.render_attributes.color = COLORS[this.render_attributes.color]; }, - getSVGUrl: function() { - var html = this.render().$el.html(); - var svg = new Blob([html], { type: 'image/svg+xml;charset=utf-8' }); + getSVGUrl() { + const html = this.render().$el.html(); + const svg = new Blob([html], { type: 'image/svg+xml;charset=utf-8' }); return URL.createObjectURL(svg); }, - getDataUrl: function() { - var svgurl = this.getSVGUrl(); - return new Promise(function(resolve) { - var img = document.createElement('img'); - img.onload = function() { - var canvas = loadImage.scale(img, { + getDataUrl() { + const svgurl = this.getSVGUrl(); + return new Promise(resolve => { + const img = document.createElement('img'); + img.onload = () => { + const canvas = loadImage.scale(img, { canvas: true, maxWidth: 100, maxHeight: 100, }); - var ctx = canvas.getContext('2d'); + const ctx = canvas.getContext('2d'); ctx.drawImage(img, 0, 0); URL.revokeObjectURL(svgurl); resolve(canvas.toDataURL('image/png')); @@ -37,7 +41,7 @@ }, }); - var COLORS = { + const COLORS = { red: '#EF5350', pink: '#EC407A', purple: '#AB47BC', diff --git a/js/views/identity_key_send_error_view.js b/js/views/identity_key_send_error_view.js index b39a7293ab81..f067af3f2360 100644 --- a/js/views/identity_key_send_error_view.js +++ b/js/views/identity_key_send_error_view.js @@ -1,11 +1,15 @@ +/* global Whisper, i18n */ + +// eslint-disable-next-line func-names (function() { 'use strict'; + window.Whisper = window.Whisper || {}; Whisper.IdentityKeySendErrorPanelView = Whisper.View.extend({ className: 'identity-key-send-error panel', templateName: 'identity-key-send-error', - initialize: function(options) { + initialize(options) { this.listenBack = options.listenBack; this.resetPanel = options.resetPanel; @@ -17,31 +21,31 @@ 'click .send-anyway': 'sendAnyway', 'click .cancel': 'cancel', }, - showSafetyNumber: function() { - var view = new Whisper.KeyVerificationPanelView({ + showSafetyNumber() { + const view = new Whisper.KeyVerificationPanelView({ model: this.model, }); this.listenBack(view); }, - sendAnyway: function() { + sendAnyway() { this.resetPanel(); this.trigger('send-anyway'); }, - cancel: function() { + cancel() { this.resetPanel(); }, - render_attributes: function() { - var send = i18n('sendAnyway'); + render_attributes() { + let send = i18n('sendAnyway'); if (this.wasUnverified && !this.model.isUnverified()) { send = i18n('resend'); } - var errorExplanation = i18n('identityKeyErrorOnSend', [ + const errorExplanation = i18n('identityKeyErrorOnSend', [ this.model.getTitle(), this.model.getTitle(), ]); return { - errorExplanation: errorExplanation, + errorExplanation, showSafetyNumber: i18n('showSafetyNumber'), sendAnyway: send, cancel: i18n('cancel'), diff --git a/js/views/import_view.js b/js/views/import_view.js index 70764b2657ee..0e6cb5a1a1b6 100644 --- a/js/views/import_view.js +++ b/js/views/import_view.js @@ -1,37 +1,43 @@ +/* global Whisper, storage, i18n, ConversationController */ + +/* eslint-disable more/no-then */ + +// eslint-disable-next-line func-names (function() { 'use strict'; + window.Whisper = window.Whisper || {}; - var State = { + const State = { IMPORTING: 1, COMPLETE: 2, LIGHT_COMPLETE: 3, }; - var IMPORT_STARTED = 'importStarted'; - var IMPORT_COMPLETE = 'importComplete'; - var IMPORT_LOCATION = 'importLocation'; + const IMPORT_STARTED = 'importStarted'; + const IMPORT_COMPLETE = 'importComplete'; + const IMPORT_LOCATION = 'importLocation'; Whisper.Import = { - isStarted: function() { + isStarted() { return Boolean(storage.get(IMPORT_STARTED)); }, - isComplete: function() { + isComplete() { return Boolean(storage.get(IMPORT_COMPLETE)); }, - isIncomplete: function() { + isIncomplete() { return this.isStarted() && !this.isComplete(); }, - start: function() { + start() { return storage.put(IMPORT_STARTED, true); }, - complete: function() { + complete() { return storage.put(IMPORT_COMPLETE, true); }, - saveLocation: function(location) { + saveLocation(location) { return storage.put(IMPORT_LOCATION, location); }, - reset: function() { + reset() { return Whisper.Database.clear(); }, }; @@ -45,7 +51,7 @@ 'click .cancel': 'onCancel', 'click .register': 'onRegister', }, - initialize: function() { + initialize() { if (Whisper.Import.isIncomplete()) { this.error = true; } @@ -53,7 +59,7 @@ this.render(); this.pending = Promise.resolve(); }, - render_attributes: function() { + render_attributes() { if (this.error) { return { isError: true, @@ -64,9 +70,9 @@ }; } - var restartButton = i18n('importCompleteStartButton'); - var registerButton = i18n('importCompleteLinkButton'); - var step = 'step2'; + let restartButton = i18n('importCompleteStartButton'); + let registerButton = i18n('importCompleteLinkButton'); + let step = 'step2'; if (this.state === State.IMPORTING) { step = 'step3'; @@ -89,22 +95,22 @@ isStep4: step === 'step4', completeHeader: i18n('importCompleteHeader'), - restartButton: restartButton, - registerButton: registerButton, + restartButton, + registerButton, }; }, - onRestart: function() { + onRestart() { return window.restart(); }, - onCancel: function() { + onCancel() { this.trigger('cancel'); }, - onImport: function() { + onImport() { window.Signal.Backup.getDirectoryForImport().then( - function(directory) { + directory => { this.doImport(directory); - }.bind(this), - function(error) { + }, + error => { if (error.name !== 'ChooseError') { console.log( 'Error choosing directory:', @@ -114,13 +120,13 @@ } ); }, - onRegister: function() { + onRegister() { // AppView listens for this, and opens up InstallView to the QR code step to // finish setting this device up. this.trigger('light-import'); }, - doImport: function(directory) { + doImport(directory) { window.removeSetupMenuItems(); this.error = null; @@ -129,70 +135,64 @@ // Wait for prior database interaction to complete this.pending = this.pending - .then(function() { + .then(() => // For resilience to interruption, clear database both before and on failure - return Whisper.Import.reset(); - }) - .then(function() { - return Promise.all([ + Whisper.Import.reset() + ) + .then(() => + Promise.all([ Whisper.Import.start(), window.Signal.Backup.importFromDirectory(directory), - ]); - }) - .then( - function(results) { - var importResult = results[1]; - - // A full import changes so much we need a restart of the app - if (importResult.fullImport) { - return this.finishFullImport(directory); - } - - // A light import just brings in contacts, groups, and messages. And we need a - // normal link to finish the process. - return this.finishLightImport(directory); - }.bind(this) + ]) ) - .catch( - function(error) { - console.log( - 'Error importing:', - error && error.stack ? error.stack : error - ); + .then(results => { + const importResult = results[1]; - this.error = error || new Error('Something went wrong!'); - this.state = null; - this.render(); + // A full import changes so much we need a restart of the app + if (importResult.fullImport) { + return this.finishFullImport(directory); + } - return Whisper.Import.reset(); - }.bind(this) - ); + // A light import just brings in contacts, groups, and messages. And we need a + // normal link to finish the process. + return this.finishLightImport(directory); + }) + .catch(error => { + console.log( + 'Error importing:', + error && error.stack ? error.stack : error + ); + + this.error = error || new Error('Something went wrong!'); + this.state = null; + this.render(); + + return Whisper.Import.reset(); + }); }, - finishLightImport: function(directory) { + finishLightImport(directory) { ConversationController.reset(); return ConversationController.load() - .then(function() { - return Promise.all([ + .then(() => + Promise.all([ Whisper.Import.saveLocation(directory), Whisper.Import.complete(), - ]); - }) - .then( - function() { - this.state = State.LIGHT_COMPLETE; - this.render(); - }.bind(this) - ); + ]) + ) + .then(() => { + this.state = State.LIGHT_COMPLETE; + this.render(); + }); }, - finishFullImport: function(directory) { + finishFullImport(directory) { // Catching in-memory cache up with what's in indexeddb now... // NOTE: this fires storage.onready, listened to across the app. We'll restart // to complete the install to start up cleanly with everything now in the DB. return storage .fetch() - .then(function() { - return Promise.all([ + .then(() => + Promise.all([ // Clearing any migration-related state inherited from the Chrome App storage.remove('migrationState'), storage.remove('migrationEnabled'), @@ -201,14 +201,12 @@ Whisper.Import.saveLocation(directory), Whisper.Import.complete(), - ]); - }) - .then( - function() { - this.state = State.COMPLETE; - this.render(); - }.bind(this) - ); + ]) + ) + .then(() => { + this.state = State.COMPLETE; + this.render(); + }); }, }); })(); diff --git a/js/views/install_view.js b/js/views/install_view.js index c3c7c5371fda..00e3287ebdf2 100644 --- a/js/views/install_view.js +++ b/js/views/install_view.js @@ -1,8 +1,14 @@ +/* global Whisper, i18n, getAccountManager, $, textsecure, QRCode */ + +/* eslint-disable more/no-then */ + +// eslint-disable-next-line func-names (function() { 'use strict'; + window.Whisper = window.Whisper || {}; - var Steps = { + const Steps = { INSTALL_SIGNAL: 2, SCAN_QR_CODE: 3, ENTER_NAME: 4, @@ -11,9 +17,9 @@ NETWORK_ERROR: 'NetworkError', }; - var DEVICE_NAME_SELECTOR = 'input.device-name'; - var CONNECTION_ERROR = -1; - var TOO_MANY_DEVICES = 411; + const DEVICE_NAME_SELECTOR = 'input.device-name'; + const CONNECTION_ERROR = -1; + const TOO_MANY_DEVICES = 411; Whisper.InstallView = Whisper.View.extend({ templateName: 'link-flow-template', @@ -23,9 +29,7 @@ 'click .finish': 'finishLinking', // the actual next step happens in confirmNumber() on submit form #link-phone }, - initialize: function(options) { - options = options || {}; - + initialize(options = {}) { this.selectStep(Steps.SCAN_QR_CODE); this.connect(); this.on('disconnected', this.reconnect); @@ -34,18 +38,18 @@ this.shouldRetainData = Whisper.Registration.everDone() || options.hasExistingData; }, - render_attributes: function() { - var errorMessage; + render_attributes() { + let errorMessage; if (this.error) { if ( this.error.name === 'HTTPError' && - this.error.code == TOO_MANY_DEVICES + this.error.code === TOO_MANY_DEVICES ) { errorMessage = i18n('installTooManyDevices'); } else if ( this.error.name === 'HTTPError' && - this.error.code == CONNECTION_ERROR + this.error.code === CONNECTION_ERROR ) { errorMessage = i18n('installConnectionFailed'); } else if (this.error.message === 'websocket closed') { @@ -78,11 +82,11 @@ syncing: i18n('initialSync'), }; }, - selectStep: function(step) { + selectStep(step) { this.step = step; this.render(); }, - connect: function() { + connect() { this.error = null; this.selectStep(Steps.SCAN_QR_CODE); this.clearQR(); @@ -91,7 +95,7 @@ this.timeout = null; } - var accountManager = getAccountManager(); + const accountManager = getAccountManager(); accountManager .registerSecondDevice( @@ -100,7 +104,7 @@ ) .catch(this.handleDisconnect.bind(this)); }, - handleDisconnect: function(e) { + handleDisconnect(e) { console.log('provisioning failed', e.stack); this.error = e; @@ -115,20 +119,20 @@ throw e; } }, - reconnect: function() { + reconnect() { if (this.timeout) { clearTimeout(this.timeout); this.timeout = null; } this.timeout = setTimeout(this.connect.bind(this), 10000); }, - clearQR: function() { + clearQR() { this.$('#qr img').remove(); this.$('#qr canvas').remove(); this.$('#qr .container').show(); this.$('#qr').removeClass('ready'); }, - setProvisioningUrl: function(url) { + setProvisioningUrl(url) { if ($('#qr').length === 0) { console.log('Did not find #qr element in the DOM!'); return; @@ -139,63 +143,57 @@ this.$('#qr').removeAttr('title'); this.$('#qr').addClass('ready'); }, - setDeviceNameDefault: function() { - var deviceName = textsecure.storage.user.getDeviceName(); + setDeviceNameDefault() { + const deviceName = textsecure.storage.user.getDeviceName(); this.$(DEVICE_NAME_SELECTOR).val(deviceName || window.getHostName()); this.$(DEVICE_NAME_SELECTOR).focus(); }, - finishLinking: function() { + finishLinking() { // We use a form so we get submit-on-enter behavior this.$('#link-phone').submit(); }, - confirmNumber: function(number) { - var tsp = textsecure.storage.protocol; + confirmNumber() { + const tsp = textsecure.storage.protocol; window.removeSetupMenuItems(); this.selectStep(Steps.ENTER_NAME); this.setDeviceNameDefault(); - return new Promise( - function(resolve, reject) { - this.$('#link-phone').submit( - function(e) { - e.stopPropagation(); - e.preventDefault(); + return new Promise(resolve => { + this.$('#link-phone').submit(e => { + e.stopPropagation(); + e.preventDefault(); - var name = this.$(DEVICE_NAME_SELECTOR).val(); - name = name.replace(/\0/g, ''); // strip unicode null - if (name.trim().length === 0) { - this.$(DEVICE_NAME_SELECTOR).focus(); - return; - } + let name = this.$(DEVICE_NAME_SELECTOR).val(); + name = name.replace(/\0/g, ''); // strip unicode null + if (name.trim().length === 0) { + this.$(DEVICE_NAME_SELECTOR).focus(); + return null; + } - this.selectStep(Steps.PROGRESS_BAR); + this.selectStep(Steps.PROGRESS_BAR); - var finish = function() { - resolve(name); - }; + const finish = () => resolve(name); - // Delete all data from database unless we're in the middle - // of a re-link, or we are finishing a light import. Without this, - // app restarts at certain times can cause weird things to happen, - // like data from a previous incomplete light import showing up - // after a new install. - if (this.shouldRetainData) { - return finish(); - } + // Delete all data from database unless we're in the middle + // of a re-link, or we are finishing a light import. Without this, + // app restarts at certain times can cause weird things to happen, + // like data from a previous incomplete light import showing up + // after a new install. + if (this.shouldRetainData) { + return finish(); + } - tsp.removeAllData().then(finish, function(error) { - console.log( - 'confirmNumber: error clearing database', - error && error.stack ? error.stack : error - ); - finish(); - }); - }.bind(this) - ); - }.bind(this) - ); + return tsp.removeAllData().then(finish, error => { + console.log( + 'confirmNumber: error clearing database', + error && error.stack ? error.stack : error + ); + finish(); + }); + }); + }); }, }); })(); diff --git a/js/views/key_verification_view.js b/js/views/key_verification_view.js index 7e21df4aaf8f..d4ada6563a23 100644 --- a/js/views/key_verification_view.js +++ b/js/views/key_verification_view.js @@ -1,5 +1,11 @@ +/* global Whisper, textsecure, QRCode, dcodeIO, libsignal, i18n, _ */ + +/* eslint-disable more/no-then */ + +// eslint-disable-next-line func-names (function() { 'use strict'; + window.Whisper = window.Whisper || {}; Whisper.KeyVerificationPanelView = Whisper.View.extend({ @@ -8,58 +14,54 @@ events: { 'click button.verify': 'toggleVerified', }, - initialize: function(options) { + initialize(options) { this.ourNumber = textsecure.storage.user.getNumber(); if (options.newKey) { this.theirKey = options.newKey; } - this.loadKeys().then( - function() { - this.listenTo(this.model, 'change', this.render); - }.bind(this) - ); + this.loadKeys().then(() => { + this.listenTo(this.model, 'change', this.render); + }); }, - loadKeys: function() { + loadKeys() { return Promise.all([this.loadTheirKey(), this.loadOurKey()]) .then(this.generateSecurityNumber.bind(this)) .then(this.render.bind(this)); - //.then(this.makeQRCode.bind(this)); + // .then(this.makeQRCode.bind(this)); }, - makeQRCode: function() { + makeQRCode() { // Per Lilia: We can't turn this on until it generates a Latin1 string, as is // required by the mobile clients. new QRCode(this.$('.qr')[0]).makeCode( dcodeIO.ByteBuffer.wrap(this.ourKey).toString('base64') ); }, - loadTheirKey: function() { - return textsecure.storage.protocol.loadIdentityKey(this.model.id).then( - function(theirKey) { + loadTheirKey() { + return textsecure.storage.protocol + .loadIdentityKey(this.model.id) + .then(theirKey => { this.theirKey = theirKey; - }.bind(this) - ); + }); }, - loadOurKey: function() { - return textsecure.storage.protocol.loadIdentityKey(this.ourNumber).then( - function(ourKey) { + loadOurKey() { + return textsecure.storage.protocol + .loadIdentityKey(this.ourNumber) + .then(ourKey => { this.ourKey = ourKey; - }.bind(this) - ); + }); }, - generateSecurityNumber: function() { + generateSecurityNumber() { return new libsignal.FingerprintGenerator(5200) .createFor(this.ourNumber, this.ourKey, this.model.id, this.theirKey) - .then( - function(securityNumber) { - this.securityNumber = securityNumber; - }.bind(this) - ); + .then(securityNumber => { + this.securityNumber = securityNumber; + }); }, - onSafetyNumberChanged: function() { + onSafetyNumberChanged() { this.model.getProfiles().then(this.loadKeys.bind(this)); - var dialog = new Whisper.ConfirmationDialogView({ + const dialog = new Whisper.ConfirmationDialogView({ message: i18n('changedRightAfterVerify', [ this.model.getTitle(), this.model.getTitle(), @@ -70,65 +72,62 @@ dialog.$el.insertBefore(this.el); dialog.focusCancel(); }, - toggleVerified: function() { + toggleVerified() { this.$('button.verify').attr('disabled', true); this.model .toggleVerified() - .catch( - function(result) { - if (result instanceof Error) { - if (result.name === 'OutgoingIdentityKeyError') { - this.onSafetyNumberChanged(); - } else { - console.log('failed to toggle verified:', result.stack); - } + .catch(result => { + if (result instanceof Error) { + if (result.name === 'OutgoingIdentityKeyError') { + this.onSafetyNumberChanged(); } else { - var keyError = _.some(result.errors, function(error) { - return error.name === 'OutgoingIdentityKeyError'; - }); - if (keyError) { - this.onSafetyNumberChanged(); - } else { - _.forEach(result.errors, function(error) { - console.log('failed to toggle verified:', error.stack); - }); - } + console.log('failed to toggle verified:', result.stack); } - }.bind(this) - ) - .then( - function() { - this.$('button.verify').removeAttr('disabled'); - }.bind(this) - ); + } else { + const keyError = _.some( + result.errors, + error => error.name === 'OutgoingIdentityKeyError' + ); + if (keyError) { + this.onSafetyNumberChanged(); + } else { + _.forEach(result.errors, error => { + console.log('failed to toggle verified:', error.stack); + }); + } + } + }) + .then(() => { + this.$('button.verify').removeAttr('disabled'); + }); }, - render_attributes: function() { - var s = this.securityNumber; - var chunks = []; - for (var i = 0; i < s.length; i += 5) { + render_attributes() { + const s = this.securityNumber; + const chunks = []; + for (let i = 0; i < s.length; i += 5) { chunks.push(s.substring(i, i + 5)); } - var name = this.model.getTitle(); - var yourSafetyNumberWith = i18n('yourSafetyNumberWith', name); - var isVerified = this.model.isVerified(); - var verifyButton = isVerified ? i18n('unverify') : i18n('verify'); - var verifiedStatus = isVerified + const name = this.model.getTitle(); + const yourSafetyNumberWith = i18n( + 'yourSafetyNumberWith', + this.model.getTitle() + ); + const isVerified = this.model.isVerified(); + const verifyButton = isVerified ? i18n('unverify') : i18n('verify'); + const verifiedStatus = isVerified ? i18n('isVerified', name) : i18n('isNotVerified', name); return { learnMore: i18n('learnMore'), theirKeyUnknown: i18n('theirIdentityUnknown'), - yourSafetyNumberWith: i18n( - 'yourSafetyNumberWith', - this.model.getTitle() - ), + yourSafetyNumberWith, verifyHelp: i18n('verifyHelp', this.model.getTitle()), - verifyButton: verifyButton, + verifyButton, hasTheirKey: this.theirKey !== undefined, - chunks: chunks, - isVerified: isVerified, - verifiedStatus: verifiedStatus, + chunks, + isVerified, + verifiedStatus, }; }, }); diff --git a/js/views/last_seen_indicator_view.js b/js/views/last_seen_indicator_view.js index 4f6101e98fc0..5c95398a2768 100644 --- a/js/views/last_seen_indicator_view.js +++ b/js/views/last_seen_indicator_view.js @@ -1,34 +1,35 @@ +/* global Whisper, i18n */ + +// eslint-disable-next-line func-names (function() { 'use strict'; - window.Whisper = window.Whisper || {}; - var FIVE_SECONDS = 5 * 1000; + window.Whisper = window.Whisper || {}; Whisper.LastSeenIndicatorView = Whisper.View.extend({ className: 'last-seen-indicator-view', templateName: 'last-seen-indicator-view', - initialize: function(options) { - options = options || {}; + initialize(options = {}) { this.count = options.count || 0; }, - increment: function(count) { + increment(count) { this.count += count; this.render(); }, - getCount: function() { + getCount() { return this.count; }, - render_attributes: function() { - var unreadMessages = + render_attributes() { + const unreadMessages = this.count === 1 ? i18n('unreadMessage') : i18n('unreadMessages', [this.count]); return { - unreadMessages: unreadMessages, + unreadMessages, }; }, }); diff --git a/js/views/list_view.js b/js/views/list_view.js index 8c65253803d7..94abf7784698 100644 --- a/js/views/list_view.js +++ b/js/views/list_view.js @@ -1,5 +1,9 @@ +/* global Backbone, Whisper, _ */ + +// eslint-disable-next-line func-names (function() { 'use strict'; + window.Whisper = window.Whisper || {}; /* @@ -9,27 +13,28 @@ Whisper.ListView = Backbone.View.extend({ tagName: 'ul', itemView: Backbone.View, - initialize: function(options) { + initialize(options) { this.options = options || {}; this.listenTo(this.collection, 'add', this.addOne); this.listenTo(this.collection, 'reset', this.addAll); }, - addOne: function(model) { + addOne(model) { if (this.itemView) { - var options = _.extend({}, this.options.toInclude, { model: model }); - var view = new this.itemView(options); + const options = _.extend({}, this.options.toInclude, { model }); + // eslint-disable-next-line new-cap + const view = new this.itemView(options); this.$el.append(view.render().el); this.$el.trigger('add'); } }, - addAll: function() { + addAll() { this.$el.html(''); this.collection.each(this.addOne, this); }, - render: function() { + render() { this.addAll(); return this; }, diff --git a/js/views/message_detail_view.js b/js/views/message_detail_view.js index 2e9a63456d91..02a93b561ae4 100644 --- a/js/views/message_detail_view.js +++ b/js/views/message_detail_view.js @@ -1,32 +1,40 @@ +/* global Whisper, i18n, _, ConversationController, Mustache, moment */ + +/* eslint-disable more/no-then */ + +// eslint-disable-next-line func-names (function() { 'use strict'; + window.Whisper = window.Whisper || {}; - var ContactView = Whisper.View.extend({ + const ContactView = Whisper.View.extend({ className: 'contact-detail', templateName: 'contact-detail', - initialize: function(options) { + initialize(options) { this.listenBack = options.listenBack; this.resetPanel = options.resetPanel; this.message = options.message; - var newIdentity = i18n('newIdentity'); - this.errors = _.map(options.errors, function(error) { + const newIdentity = i18n('newIdentity'); + this.errors = _.map(options.errors, error => { if (error.name === 'OutgoingIdentityKeyError') { + // eslint-disable-next-line no-param-reassign error.message = newIdentity; } return error; }); - this.outgoingKeyError = _.find(this.errors, function(error) { - return error.name === 'OutgoingIdentityKeyError'; - }); + this.outgoingKeyError = _.find( + this.errors, + error => error.name === 'OutgoingIdentityKeyError' + ); }, events: { click: 'onClick', }, - onClick: function() { + onClick() { if (this.outgoingKeyError) { - var view = new Whisper.IdentityKeySendErrorPanelView({ + const view = new Whisper.IdentityKeySendErrorPanelView({ model: this.model, listenBack: this.listenBack, resetPanel: this.resetPanel, @@ -40,41 +48,33 @@ view.$('.cancel').focus(); } }, - forceSend: function() { + forceSend() { this.model .updateVerified() - .then( - function() { - if (this.model.isUnverified()) { - return this.model.setVerifiedDefault(); - } - }.bind(this) - ) - .then( - function() { - return this.model.isUntrusted(); - }.bind(this) - ) - .then( - function(untrusted) { - if (untrusted) { - return this.model.setApproved(); - } - }.bind(this) - ) - .then( - function() { - this.message.resend(this.outgoingKeyError.number); - }.bind(this) - ); + .then(() => { + if (this.model.isUnverified()) { + return this.model.setVerifiedDefault(); + } + return null; + }) + .then(() => this.model.isUntrusted()) + .then(untrusted => { + if (untrusted) { + return this.model.setApproved(); + } + return null; + }) + .then(() => { + this.message.resend(this.outgoingKeyError.number); + }); }, - onSendAnyway: function() { + onSendAnyway() { if (this.outgoingKeyError) { this.forceSend(); } }, - render_attributes: function() { - var showButton = Boolean(this.outgoingKeyError); + render_attributes() { + const showButton = Boolean(this.outgoingKeyError); return { status: this.message.getStatus(this.model.id), @@ -90,7 +90,7 @@ Whisper.MessageDetailView = Whisper.View.extend({ className: 'message-detail panel', templateName: 'message-detail', - initialize: function(options) { + initialize(options) { this.listenBack = options.listenBack; this.resetPanel = options.resetPanel; @@ -103,22 +103,22 @@ events: { 'click button.delete': 'onDelete', }, - onDelete: function() { - var dialog = new Whisper.ConfirmationDialogView({ + onDelete() { + const dialog = new Whisper.ConfirmationDialogView({ message: i18n('deleteWarning'), okText: i18n('delete'), - resolve: function() { + resolve: () => { this.model.destroy(); this.resetPanel(); - }.bind(this), + }, }); this.$el.prepend(dialog.el); dialog.focusCancel(); }, - getContacts: function() { + getContacts() { // Return the set of models to be rendered in this view - var ids; + let ids; if (this.model.isIncoming()) { ids = [this.model.get('source')]; } else if (this.model.isOutgoing()) { @@ -130,13 +130,13 @@ } } return Promise.all( - ids.map(function(number) { - return ConversationController.getOrCreateAndWait(number, 'private'); - }) + ids.map(number => + ConversationController.getOrCreateAndWait(number, 'private') + ) ); }, - renderContact: function(contact) { - var view = new ContactView({ + renderContact(contact) { + const view = new ContactView({ model: contact, errors: this.grouped[contact.id], listenBack: this.listenBack, @@ -145,12 +145,10 @@ }).render(); this.$('.contacts').append(view.el); }, - render: function() { - var errorsWithoutNumber = _.reject(this.model.get('errors'), function( - error - ) { - return Boolean(error.number); - }); + render() { + const errorsWithoutNumber = _.reject(this.model.get('errors'), error => + Boolean(error.number) + ); this.$el.html( Mustache.render(_.result(this, 'template', ''), { @@ -171,19 +169,14 @@ this.grouped = _.groupBy(this.model.get('errors'), 'number'); - this.getContacts().then( - function(contacts) { - _.sortBy( - contacts, - function(c) { - var prefix = this.grouped[c.id] ? '0' : '1'; - // this prefix ensures that contacts with errors are listed first; - // otherwise it's alphabetical - return prefix + c.getTitle(); - }.bind(this) - ).forEach(this.renderContact.bind(this)); - }.bind(this) - ); + this.getContacts().then(contacts => { + _.sortBy(contacts, c => { + const prefix = this.grouped[c.id] ? '0' : '1'; + // this prefix ensures that contacts with errors are listed first; + // otherwise it's alphabetical + return prefix + c.getTitle(); + }).forEach(this.renderContact.bind(this)); + }); }, }); })(); diff --git a/js/views/message_list_view.js b/js/views/message_list_view.js index 0bd8aeb9105f..947cf3feb19a 100644 --- a/js/views/message_list_view.js +++ b/js/views/message_list_view.js @@ -1,5 +1,9 @@ +/* global Whisper, _ */ + +// eslint-disable-next-line func-names (function() { 'use strict'; + window.Whisper = window.Whisper || {}; Whisper.MessageListView = Whisper.ListView.extend({ @@ -9,17 +13,14 @@ events: { scroll: 'onScroll', }, - initialize: function() { + initialize() { Whisper.ListView.prototype.initialize.call(this); - this.triggerLazyScroll = _.debounce( - function() { - this.$el.trigger('lazyScroll'); - }.bind(this), - 500 - ); + this.triggerLazyScroll = _.debounce(() => { + this.$el.trigger('lazyScroll'); + }, 500); }, - onScroll: function() { + onScroll() { this.measureScrollPosition(); if (this.$el.scrollTop() === 0) { this.$el.trigger('loadMore'); @@ -32,10 +33,10 @@ this.triggerLazyScroll(); }, - atBottom: function() { + atBottom() { return this.bottomOffset < 30; }, - measureScrollPosition: function() { + measureScrollPosition() { if (this.el.scrollHeight === 0) { // hidden return; @@ -45,10 +46,10 @@ this.scrollHeight = this.el.scrollHeight; this.bottomOffset = this.scrollHeight - this.scrollPosition; }, - resetScrollPosition: function() { + resetScrollPosition() { this.$el.scrollTop(this.scrollPosition - this.$el.outerHeight()); }, - scrollToBottomIfNeeded: function() { + scrollToBottomIfNeeded() { // This is counter-intuitive. Our current bottomOffset is reflective of what // we last measured, not necessarily the current state. And this is called // after we just made a change to the DOM: inserting a message, or an image @@ -58,25 +59,26 @@ this.scrollToBottom(); } }, - scrollToBottom: function() { + scrollToBottom() { this.$el.scrollTop(this.el.scrollHeight); this.measureScrollPosition(); }, - addOne: function(model) { - var view; + addOne(model) { + let view; if (model.isExpirationTimerUpdate()) { - view = new Whisper.ExpirationTimerUpdateView({ model: model }).render(); + view = new Whisper.ExpirationTimerUpdateView({ model }).render(); } else if (model.get('type') === 'keychange') { - view = new Whisper.KeyChangeView({ model: model }).render(); + view = new Whisper.KeyChangeView({ model }).render(); } else if (model.get('type') === 'verified-change') { - view = new Whisper.VerifiedChangeView({ model: model }).render(); + view = new Whisper.VerifiedChangeView({ model }).render(); } else { - view = new this.itemView({ model: model }).render(); + // eslint-disable-next-line new-cap + view = new this.itemView({ model }).render(); this.listenTo(view, 'beforeChangeHeight', this.measureScrollPosition); this.listenTo(view, 'afterChangeHeight', this.scrollToBottomIfNeeded); } - var index = this.collection.indexOf(model); + const index = this.collection.indexOf(model); this.measureScrollPosition(); if (model.get('unread') && !this.atBottom()) { @@ -91,20 +93,20 @@ this.$el.prepend(view.el); } else { // insert - var next = this.$('#' + this.collection.at(index + 1).id); - var prev = this.$('#' + this.collection.at(index - 1).id); + const next = this.$(`#${this.collection.at(index + 1).id}`); + const prev = this.$(`#${this.collection.at(index - 1).id}`); if (next.length > 0) { view.$el.insertBefore(next); } else if (prev.length > 0) { view.$el.insertAfter(prev); } else { // scan for the right spot - var elements = this.$el.children(); + const elements = this.$el.children(); if (elements.length > 0) { - for (var i = 0; i < elements.length; ++i) { - var m = this.collection.get(elements[i].id); - var m_index = this.collection.indexOf(m); - if (m_index > index) { + for (let i = 0; i < elements.length; i += 1) { + const m = this.collection.get(elements[i].id); + const mIndex = this.collection.indexOf(m); + if (mIndex > index) { view.$el.insertBefore(elements[i]); break; } diff --git a/js/views/network_status_view.js b/js/views/network_status_view.js index 9c612811d3b6..cd67a7b8a9f0 100644 --- a/js/views/network_status_view.js +++ b/js/views/network_status_view.js @@ -1,3 +1,6 @@ +/* global Whisper, extension, Backbone, moment, i18n */ + +// eslint-disable-next-line func-names (function() { 'use strict'; @@ -6,15 +9,13 @@ Whisper.NetworkStatusView = Whisper.View.extend({ className: 'network-status', templateName: 'networkStatus', - initialize: function() { + initialize() { this.$el.hide(); this.renderIntervalHandle = setInterval(this.update.bind(this), 5000); - extension.windows.onClosed( - function() { - clearInterval(this.renderIntervalHandle); - }.bind(this) - ); + extension.windows.onClosed(() => { + clearInterval(this.renderIntervalHandle); + }); setTimeout(this.finishConnectingGracePeriod.bind(this), 5000); @@ -27,29 +28,29 @@ this.model = new Backbone.Model(); this.listenTo(this.model, 'change', this.onChange); }, - onReconnectTimer: function() { + onReconnectTimer() { this.setSocketReconnectInterval(60000); }, - finishConnectingGracePeriod: function() { + finishConnectingGracePeriod() { this.withinConnectingGracePeriod = false; }, - setSocketReconnectInterval: function(millis) { + setSocketReconnectInterval(millis) { this.socketReconnectWaitDuration = moment.duration(millis); }, - navigatorOnLine: function() { + navigatorOnLine() { return navigator.onLine; }, - getSocketStatus: function() { + getSocketStatus() { return window.getSocketStatus(); }, - getNetworkStatus: function() { - var message = ''; - var instructions = ''; - var hasInterruption = false; - var action = null; - var buttonClass = null; + getNetworkStatus() { + let message = ''; + let instructions = ''; + let hasInterruption = false; + let action = null; + let buttonClass = null; - var socketStatus = this.getSocketStatus(); + const socketStatus = this.getSocketStatus(); switch (socketStatus) { case WebSocket.CONNECTING: message = i18n('connecting'); @@ -58,12 +59,13 @@ case WebSocket.OPEN: this.setSocketReconnectInterval(null); break; - case WebSocket.CLOSING: + case WebSocket.CLOSED: message = i18n('disconnected'); instructions = i18n('checkNetworkConnection'); hasInterruption = true; break; - case WebSocket.CLOSED: + case WebSocket.CLOSING: + default: message = i18n('disconnected'); instructions = i18n('checkNetworkConnection'); hasInterruption = true; @@ -71,7 +73,7 @@ } if ( - socketStatus == WebSocket.CONNECTING && + socketStatus === WebSocket.CONNECTING && !this.withinConnectingGracePeriod ) { hasInterruption = true; @@ -94,21 +96,21 @@ } return { - message: message, - instructions: instructions, - hasInterruption: hasInterruption, - action: action, - buttonClass: buttonClass, + message, + instructions, + hasInterruption, + action, + buttonClass, }; }, - update: function() { - var status = this.getNetworkStatus(); + update() { + const status = this.getNetworkStatus(); this.model.set(status); }, - render_attributes: function() { + render_attributes() { return this.model.attributes; }, - onChange: function() { + onChange() { this.render(); if (this.model.attributes.hasInterruption) { this.$el.slideDown(); diff --git a/js/views/new_group_update_view.js b/js/views/new_group_update_view.js index cce50c239a95..afb1c88e2b26 100644 --- a/js/views/new_group_update_view.js +++ b/js/views/new_group_update_view.js @@ -1,12 +1,18 @@ +/* global Whisper, _ */ + +/* eslint-disable more/no-then */ + +// eslint-disable-next-line func-names (function() { 'use strict'; + window.Whisper = window.Whisper || {}; Whisper.NewGroupUpdateView = Whisper.View.extend({ tagName: 'div', className: 'new-group-update', templateName: 'new-group-update', - initialize: function(options) { + initialize(options) { this.render(); this.avatarInput = new Whisper.FileInputView({ el: this.$('.group-avatar'), @@ -14,15 +20,13 @@ }); this.recipients_view = new Whisper.RecipientsInputView(); - this.listenTo(this.recipients_view.typeahead, 'sync', function() { - this.model.contactCollection.models.forEach( - function(model) { - if (this.recipients_view.typeahead.get(model)) { - this.recipients_view.typeahead.remove(model); - } - }.bind(this) - ); - }); + this.listenTo(this.recipients_view.typeahead, 'sync', () => + this.model.contactCollection.models.forEach(model => { + if (this.recipients_view.typeahead.get(model)) { + this.recipients_view.typeahead.remove(model); + } + }) + ); this.recipients_view.$el.insertBefore(this.$('.container')); this.member_list_view = new Whisper.ContactListView({ @@ -38,49 +42,47 @@ 'focusin input.search': 'showResults', 'focusout input.search': 'hideResults', }, - hideResults: function() { + hideResults() { this.$('.results').hide(); }, - showResults: function() { + showResults() { this.$('.results').show(); }, - goBack: function() { + goBack() { this.trigger('back'); }, - render_attributes: function() { + render_attributes() { return { name: this.model.getTitle(), avatar: this.model.getAvatar(), }; }, - send: function() { - return this.avatarInput.getThumbnail().then( - function(avatarFile) { - var now = Date.now(); - var attrs = { - timestamp: now, - active_at: now, - name: this.$('.name').val(), - members: _.union( - this.model.get('members'), - this.recipients_view.recipients.pluck('id') - ), - }; - if (avatarFile) { - attrs.avatar = avatarFile; - } - this.model.set(attrs); - var group_update = this.model.changed; - this.model.save(); + send() { + return this.avatarInput.getThumbnail().then(avatarFile => { + const now = Date.now(); + const attrs = { + timestamp: now, + active_at: now, + name: this.$('.name').val(), + members: _.union( + this.model.get('members'), + this.recipients_view.recipients.pluck('id') + ), + }; + if (avatarFile) { + attrs.avatar = avatarFile; + } + this.model.set(attrs); + const groupUpdate = this.model.changed; + this.model.save(); - if (group_update.avatar) { - this.model.trigger('change:avatar'); - } + if (groupUpdate.avatar) { + this.model.trigger('change:avatar'); + } - this.model.updateGroup(group_update); - this.goBack(); - }.bind(this) - ); + this.model.updateGroup(groupUpdate); + this.goBack(); + }); }, }); })(); diff --git a/js/views/phone-input-view.js b/js/views/phone-input-view.js index 60d0030633f2..e9537f901d54 100644 --- a/js/views/phone-input-view.js +++ b/js/views/phone-input-view.js @@ -1,26 +1,30 @@ +/* global libphonenumber, Whisper */ + +// eslint-disable-next-line func-names (function() { 'use strict'; + window.Whisper = window.Whisper || {}; Whisper.PhoneInputView = Whisper.View.extend({ tagName: 'div', className: 'phone-input', templateName: 'phone-number', - initialize: function() { + initialize() { this.$('input.number').intlTelInput(); }, events: { change: 'validateNumber', keyup: 'validateNumber', }, - validateNumber: function() { - var input = this.$('input.number'); - var regionCode = this.$('li.active') + validateNumber() { + const input = this.$('input.number'); + const regionCode = this.$('li.active') .attr('data-country-code') .toUpperCase(); - var number = input.val(); + const number = input.val(); - var parsedNumber = libphonenumber.util.parseNumber(number, regionCode); + const parsedNumber = libphonenumber.util.parseNumber(number, regionCode); if (parsedNumber.isValidNumber) { this.$('.number-container').removeClass('invalid'); this.$('.number-container').addClass('valid'); diff --git a/js/views/recipients_input_view.js b/js/views/recipients_input_view.js index bc84d7c89613..fe228138cdc5 100644 --- a/js/views/recipients_input_view.js +++ b/js/views/recipients_input_view.js @@ -1,8 +1,12 @@ +/* global Whisper, Backbone, ConversationController */ + +// eslint-disable-next-line func-names (function() { 'use strict'; + window.Whisper = window.Whisper || {}; - var ContactsTypeahead = Backbone.TypeaheadCollection.extend({ + const ContactsTypeahead = Backbone.TypeaheadCollection.extend({ typeaheadAttributes: [ 'name', 'e164_number', @@ -12,7 +16,7 @@ database: Whisper.Database, storeName: 'conversations', model: Whisper.Conversation, - fetchContacts: function() { + fetchContacts() { return this.fetch({ reset: true, conditions: { type: 'private' } }); }, }); @@ -24,17 +28,17 @@ 'click .remove': 'removeModel', }, templateName: 'contact_pill', - initialize: function() { - var error = this.model.validate(this.model.attributes); + initialize() { + const error = this.model.validate(this.model.attributes); if (error) { this.$el.addClass('error'); } }, - removeModel: function() { + removeModel() { this.$el.trigger('remove', { modelId: this.model.id }); this.remove(); }, - render_attributes: function() { + render_attributes() { return { name: this.model.getTitle() }; }, }); @@ -55,7 +59,7 @@ Whisper.RecipientsInputView = Whisper.View.extend({ className: 'recipients-input', templateName: 'recipients-input', - initialize: function(options) { + initialize(options) { if (options) { this.placeholder = options.placeholder; } @@ -81,7 +85,7 @@ // View to display the matched contacts from typeahead this.typeahead_view = new Whisper.SuggestionListView({ collection: new Whisper.ConversationCollection([], { - comparator: function(m) { + comparator(m) { return m.getTitle().toLowerCase(); }, }), @@ -91,7 +95,7 @@ this.listenTo(this.typeahead, 'reset', this.filterContacts); }, - render_attributes: function() { + render_attributes() { return { placeholder: this.placeholder || 'name or phone number' }; }, @@ -102,8 +106,8 @@ 'remove .recipient': 'removeRecipient', }, - filterContacts: function(e) { - var query = this.$input.val(); + filterContacts() { + const query = this.$input.val(); if (query.length) { if (this.maybeNumber(query)) { this.new_contact_view.model.set('id', query); @@ -117,7 +121,7 @@ } }, - initNewContact: function() { + initNewContact() { if (this.new_contact_view) { this.new_contact_view.undelegateEvents(); this.new_contact_view.$el.hide(); @@ -132,47 +136,45 @@ }).render(); }, - addNewRecipient: function() { + addNewRecipient() { this.recipients.add(this.new_contact_view.model); this.initNewContact(); this.resetTypeahead(); }, - addRecipient: function(e, conversation) { + addRecipient(e, conversation) { this.recipients.add(this.typeahead.remove(conversation.id)); this.resetTypeahead(); }, - removeRecipient: function(e, data) { - var model = this.recipients.remove(data.modelId); + removeRecipient(e, data) { + const model = this.recipients.remove(data.modelId); if (!model.get('newContact')) { this.typeahead.add(model); } this.filterContacts(); }, - reset: function() { + reset() { this.delegateEvents(); this.typeahead_view.delegateEvents(); this.recipients_view.delegateEvents(); this.new_contact_view.delegateEvents(); this.typeahead.add( - this.recipients.filter(function(model) { - return !model.get('newContact'); - }) + this.recipients.filter(model => !model.get('newContact')) ); this.recipients.reset([]); this.resetTypeahead(); this.typeahead.fetchContacts(); }, - resetTypeahead: function() { + resetTypeahead() { this.new_contact_view.$el.hide(); this.$input.val('').focus(); this.typeahead_view.collection.reset([]); }, - maybeNumber: function(number) { + maybeNumber(number) { return number.match(/^\+?[0-9]*$/); }, }); diff --git a/js/views/recorder_view.js b/js/views/recorder_view.js index dba138e2eb48..4770f8af860a 100644 --- a/js/views/recorder_view.js +++ b/js/views/recorder_view.js @@ -1,11 +1,17 @@ +/* global Whisper, moment, WebAudioRecorder */ + +/* eslint-disable more/no-then */ + +// eslint-disable-next-line func-names (function() { 'use strict'; + window.Whisper = window.Whisper || {}; Whisper.RecorderView = Whisper.View.extend({ className: 'recorder clearfix', templateName: 'recorder', - initialize: function() { + initialize() { this.startTime = Date.now(); this.interval = setInterval(this.updateTime.bind(this), 1000); this.start(); @@ -15,16 +21,16 @@ 'click .finish': 'finish', close: 'close', }, - updateTime: function() { - var duration = moment.duration(Date.now() - this.startTime, 'ms'); - var minutes = '' + Math.trunc(duration.asMinutes()); - var seconds = '' + duration.seconds(); + updateTime() { + const duration = moment.duration(Date.now() - this.startTime, 'ms'); + const minutes = `${Math.trunc(duration.asMinutes())}`; + let seconds = `${duration.seconds()}`; if (seconds.length < 2) { - seconds = '0' + seconds; + seconds = `0${seconds}`; } - this.$('.time').text(minutes + ':' + seconds); + this.$('.time').text(`${minutes}:${seconds}`); }, - close: function() { + close() { // Note: the 'close' event can be triggered by InboxView, when the user clicks // anywhere outside the recording pane. @@ -44,7 +50,7 @@ this.source = null; if (this.context) { - this.context.close().then(function() { + this.context.close().then(() => { console.log('audio context closed'); }); } @@ -53,16 +59,16 @@ this.remove(); this.trigger('closed'); }, - finish: function() { + finish() { this.recorder.finishRecording(); this.close(); }, - handleBlob: function(recorder, blob) { + handleBlob(recorder, blob) { if (blob) { this.trigger('send', blob); } }, - start: function() { + start() { this.context = new AudioContext(); this.input = this.context.createGain(); this.recorder = new WebAudioRecorder(this.input, { @@ -73,15 +79,15 @@ this.recorder.onError = this.onError; navigator.webkitGetUserMedia( { audio: true }, - function(stream) { + stream => { this.source = this.context.createMediaStreamSource(stream); this.source.connect(this.input); - }.bind(this), + }, this.onError.bind(this) ); this.recorder.startRecording(); }, - onError: function(error) { + onError(error) { // Protect against out-of-band errors, which can happen if the user revokes media // permissions after successfully accessing the microphone. if (!this.recorder) { diff --git a/js/views/scroll_down_button_view.js b/js/views/scroll_down_button_view.js index de9eb87057f2..741342c4fbb1 100644 --- a/js/views/scroll_down_button_view.js +++ b/js/views/scroll_down_button_view.js @@ -1,26 +1,28 @@ +/* global Whisper, i18n */ + +// eslint-disable-next-line func-names (function() { 'use strict'; + window.Whisper = window.Whisper || {}; Whisper.ScrollDownButtonView = Whisper.View.extend({ className: 'scroll-down-button-view', templateName: 'scroll-down-button-view', - initialize: function(options) { - options = options || {}; + initialize(options = {}) { this.count = options.count || 0; }, - increment: function(count) { - count = count || 0; + increment(count = 0) { this.count += count; this.render(); }, - render_attributes: function() { - var cssClass = this.count > 0 ? 'new-messages' : ''; + render_attributes() { + const cssClass = this.count > 0 ? 'new-messages' : ''; - var moreBelow = i18n('scrollDown'); + let moreBelow = i18n('scrollDown'); if (this.count > 1) { moreBelow = i18n('messagesBelow'); } else if (this.count === 1) { @@ -28,8 +30,8 @@ } return { - cssClass: cssClass, - moreBelow: moreBelow, + cssClass, + moreBelow, }; }, }); diff --git a/js/views/standalone_registration_view.js b/js/views/standalone_registration_view.js index 05328ac2c8a2..fac597e1b5d5 100644 --- a/js/views/standalone_registration_view.js +++ b/js/views/standalone_registration_view.js @@ -1,16 +1,22 @@ +/* global Whisper, $, getAccountManager, textsecure */ + +/* eslint-disable more/no-then */ + +// eslint-disable-next-line func-names (function() { 'use strict'; + window.Whisper = window.Whisper || {}; Whisper.StandaloneRegistrationView = Whisper.View.extend({ templateName: 'standalone', className: 'full-screen-flow', - initialize: function() { + initialize() { this.accountManager = getAccountManager(); this.render(); - var number = textsecure.storage.user.getNumber(); + const number = textsecure.storage.user.getNumber(); if (number) { this.$('input.number').val(number); } @@ -26,58 +32,59 @@ 'change #code': 'onChangeCode', 'click #verifyCode': 'verifyCode', }, - verifyCode: function(e) { - var number = this.phoneView.validateNumber(); - var verificationCode = $('#code') + verifyCode() { + const number = this.phoneView.validateNumber(); + const verificationCode = $('#code') .val() .replace(/\D+/g, ''); this.accountManager .registerSingleDevice(number, verificationCode) - .then( - function() { - this.$el.trigger('openInbox'); - }.bind(this) - ) + .then(() => { + this.$el.trigger('openInbox'); + }) .catch(this.log.bind(this)); }, - log: function(s) { + log(s) { console.log(s); this.$('#status').text(s); }, - validateCode: function() { - var verificationCode = $('#code') + validateCode() { + const verificationCode = $('#code') .val() .replace(/\D/g, ''); - if (verificationCode.length == 6) { + + if (verificationCode.length === 6) { return verificationCode; } + + return null; }, - displayError: function(error) { + displayError(error) { this.$('#error') .hide() .text(error) .addClass('in') .fadeIn(); }, - onValidation: function() { + onValidation() { if (this.$('#number-container').hasClass('valid')) { this.$('#request-sms, #request-voice').removeAttr('disabled'); } else { this.$('#request-sms, #request-voice').prop('disabled', 'disabled'); } }, - onChangeCode: function() { + onChangeCode() { if (!this.validateCode()) { this.$('#code').addClass('invalid'); } else { this.$('#code').removeClass('invalid'); } }, - requestVoice: function() { + requestVoice() { window.removeSetupMenuItems(); this.$('#error').hide(); - var number = this.phoneView.validateNumber(); + const number = this.phoneView.validateNumber(); if (number) { this.accountManager .requestVoiceVerification(number) @@ -89,10 +96,10 @@ this.$('#number-container').addClass('invalid'); } }, - requestSMSVerification: function() { + requestSMSVerification() { window.removeSetupMenuItems(); $('#error').hide(); - var number = this.phoneView.validateNumber(); + const number = this.phoneView.validateNumber(); if (number) { this.accountManager .requestSMSVerification(number) diff --git a/js/views/toast_view.js b/js/views/toast_view.js index 9733f7adc9ee..ae90feb2527b 100644 --- a/js/views/toast_view.js +++ b/js/views/toast_view.js @@ -1,19 +1,23 @@ +/* global Whisper, Mustache, _ */ + +// eslint-disable-next-line func-names (function() { 'use strict'; + window.Whisper = window.Whisper || {}; Whisper.ToastView = Whisper.View.extend({ className: 'toast', templateName: 'toast', - initialize: function() { + initialize() { this.$el.hide(); }, - close: function() { + close() { this.$el.fadeOut(this.remove.bind(this)); }, - render: function() { + render() { this.$el.html( Mustache.render( _.result(this, 'template', ''), diff --git a/js/views/whisper_view.js b/js/views/whisper_view.js index b9c572abd3a8..4ac7c6f7ea21 100644 --- a/js/views/whisper_view.js +++ b/js/views/whisper_view.js @@ -1,3 +1,5 @@ +/* global Whisper, Backbone, Mustache, _, $ */ + /* * Whisper.View * @@ -17,64 +19,57 @@ * 4. Provides some common functionality, e.g. confirmation dialog * */ + +// eslint-disable-next-line func-names (function() { 'use strict'; + window.Whisper = window.Whisper || {}; Whisper.View = Backbone.View.extend( { - constructor: function() { - Backbone.View.apply(this, arguments); + constructor(...params) { + Backbone.View.call(this, ...params); Mustache.parse(_.result(this, 'template')); }, - render_attributes: function() { + render_attributes() { return _.result(this.model, 'attributes', {}); }, - render_partials: function() { + render_partials() { return Whisper.View.Templates; }, - template: function() { + template() { if (this.templateName) { return Whisper.View.Templates[this.templateName]; } return ''; }, - render: function() { - var attrs = _.result(this, 'render_attributes', {}); - var template = _.result(this, 'template', ''); - var partials = _.result(this, 'render_partials', ''); + render() { + const attrs = _.result(this, 'render_attributes', {}); + const template = _.result(this, 'template', ''); + const partials = _.result(this, 'render_partials', ''); this.$el.html(Mustache.render(template, attrs, partials)); return this; }, - confirm: function(message, okText) { - return new Promise( - function(resolve, reject) { - var dialog = new Whisper.ConfirmationDialogView({ - message: message, - okText: okText, - resolve: resolve, - reject: reject, - }); - this.$el.append(dialog.el); - }.bind(this) - ); - }, - i18n_with_links: function() { - var args = Array.prototype.slice.call(arguments); - for (var i = 1; i < args.length; ++i) { - args[i] = - 'class="link" href="' + encodeURI(args[i]) + '" target="_blank"'; - } - return i18n(args[0], args.slice(1)); + confirm(message, okText) { + return new Promise((resolve, reject) => { + const dialog = new Whisper.ConfirmationDialogView({ + message, + okText, + resolve, + reject, + }); + this.$el.append(dialog.el); + }); }, }, { // Class attributes - Templates: (function() { - var templates = {}; - $('script[type="text/x-tmpl-mustache"]').each(function(i, el) { - var $el = $(el); - var id = $el.attr('id'); + Templates: (() => { + const templates = {}; + $('script[type="text/x-tmpl-mustache"]').each((i, el) => { + const $el = $(el); + const id = $el.attr('id'); templates[id] = $el.html(); }); return templates; diff --git a/js/wall_clock_listener.js b/js/wall_clock_listener.js index c1cba447076a..f8e21a48ccef 100644 --- a/js/wall_clock_listener.js +++ b/js/wall_clock_listener.js @@ -1,12 +1,16 @@ +/* global Whisper */ + +// eslint-disable-next-line func-names (function() { 'use strict'; + window.Whisper = window.Whisper || {}; - var lastTime; - var interval = 1000; - var events; + let lastTime; + const interval = 1000; + let events; function checkTime() { - var currentTime = Date.now(); + const currentTime = Date.now(); if (currentTime > lastTime + interval * 2) { events.trigger('timetravel'); } @@ -14,7 +18,7 @@ } Whisper.WallClockListener = { - init: function(_events) { + init(_events) { events = _events; lastTime = Date.now(); setInterval(checkTime, interval); diff --git a/package.json b/package.json index f933cacc553a..2ff7f1a5a0ba 100644 --- a/package.json +++ b/package.json @@ -30,9 +30,8 @@ "test-node": "mocha --recursive test/app test/modules ts/test", "test-node-coverage": "nyc --reporter=lcov --reporter=text mocha --recursive test/app test/modules ts/test", "eslint": "eslint .", - "jshint": "yarn grunt jshint", "lint": "yarn format --list-different && yarn lint-windows", - "lint-windows": "yarn eslint && yarn grunt lint && yarn tslint", + "lint-windows": "yarn eslint && yarn tslint", "tslint": "tslint --format stylish --project .", "format": "prettier --write \"*.{css,js,json,md,scss,ts,tsx}\" \"./**/*.{css,js,json,md,scss,ts,tsx}\"", "transpile": "tsc", @@ -122,7 +121,6 @@ "grunt-cli": "^1.2.0", "grunt-contrib-concat": "^1.0.1", "grunt-contrib-copy": "^1.0.0", - "grunt-contrib-jshint": "^1.1.0", "grunt-contrib-watch": "^1.0.0", "grunt-exec": "^3.0.0", "grunt-gitinfo": "^0.1.7", diff --git a/permissions_popup_preload.js b/permissions_popup_preload.js index ae7e26a54b21..b8ae48a5fb7c 100644 --- a/permissions_popup_preload.js +++ b/permissions_popup_preload.js @@ -1,3 +1,5 @@ +/* global window */ + const { ipcRenderer } = require('electron'); const url = require('url'); const i18n = require('./js/modules/i18n'); diff --git a/prepare_import_build.js b/prepare_import_build.js index b20aacdc6f37..408f9977a5b4 100644 --- a/prepare_import_build.js +++ b/prepare_import_build.js @@ -41,11 +41,15 @@ _.set(defaultConfig, IMPORT_PATH, IMPORT_END_VALUE); console.log('prepare_import_build: updating package.json'); const MAC_ASSET_PATH = 'build.mac.artifactName'; +// eslint-disable-next-line no-template-curly-in-string const MAC_ASSET_START_VALUE = '${name}-mac-${version}.${ext}'; +// eslint-disable-next-line no-template-curly-in-string const MAC_ASSET_END_VALUE = '${name}-mac-${version}-import.${ext}'; const WIN_ASSET_PATH = 'build.win.artifactName'; +// eslint-disable-next-line no-template-curly-in-string const WIN_ASSET_START_VALUE = '${name}-win-${version}.${ext}'; +// eslint-disable-next-line no-template-curly-in-string const WIN_ASSET_END_VALUE = '${name}-win-${version}-import.${ext}'; checkValue(packageJson, MAC_ASSET_PATH, MAC_ASSET_START_VALUE); diff --git a/settings_preload.js b/settings_preload.js index f020ad17f81c..9fbb2db07b0f 100644 --- a/settings_preload.js +++ b/settings_preload.js @@ -1,3 +1,5 @@ +/* global window */ + const { ipcRenderer } = require('electron'); const url = require('url'); const i18n = require('./js/modules/i18n'); diff --git a/styleguide.config.js b/styleguide.config.js index eece210d2539..3989a8947efd 100644 --- a/styleguide.config.js +++ b/styleguide.config.js @@ -1,5 +1,5 @@ -const webpack = require('webpack'); const path = require('path'); +// eslint-disable-next-line import/no-extraneous-dependencies const typescriptSupport = require('react-docgen-typescript'); const propsParser = typescriptSupport.withCustomConfig('./tsconfig.json').parse; diff --git a/test/index.html b/test/index.html index 63704f5252e6..a15e21ac5063 100644 --- a/test/index.html +++ b/test/index.html @@ -511,7 +511,6 @@ - diff --git a/ts/components/ContactListItem.tsx b/ts/components/ContactListItem.tsx index faec618774b1..4ef71c75e49d 100644 --- a/ts/components/ContactListItem.tsx +++ b/ts/components/ContactListItem.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import classnames from 'classnames'; +import classNames from 'classnames'; import { Emojify } from './conversation/Emojify'; @@ -37,7 +37,7 @@ export class ContactListItem extends React.Component { return (
{
{