Format all source code using Prettier

This commit is contained in:
Daniel Gasienica 2018-04-27 17:25:04 -04:00
parent b4dee3f30b
commit 1dd87ad197
149 changed files with 17847 additions and 15439 deletions

View File

@ -2,29 +2,24 @@
module.exports = {
settings: {
'import/core-modules': [
'electron'
]
'import/core-modules': ['electron'],
},
extends: [
'airbnb-base',
'prettier',
],
extends: ['airbnb-base', 'prettier'],
plugins: [
'mocha',
'more',
],
plugins: ['mocha', 'more'],
rules: {
'comma-dangle': ['error', {
'comma-dangle': [
'error',
{
arrays: 'always-multiline',
objects: 'always-multiline',
imports: 'always-multiline',
exports: 'always-multiline',
functions: 'never',
}],
},
],
// prevents us from accidentally checking in exclusive tests (`.only`):
'mocha/no-exclusive-tests': 'error',
@ -44,7 +39,11 @@ module.exports = {
// consistently place operators at end of line except ternaries
'operator-linebreak': 'error',
'quotes': ['error', 'single', { 'avoidEscape': true, 'allowTemplateLiterals': false }],
quotes: [
'error',
'single',
{ avoidEscape: true, allowTemplateLiterals: false },
],
// Prettier overrides:
'arrow-parens': 'off',

View File

@ -13,11 +13,13 @@ module.exports = function(grunt) {
var libtextsecurecomponents = [];
for (i in bower.concat.libtextsecure) {
libtextsecurecomponents.push('components/' + bower.concat.libtextsecure[i] + '/**/*.js');
libtextsecurecomponents.push(
'components/' + bower.concat.libtextsecure[i] + '/**/*.js'
);
}
var importOnce = require("node-sass-import-once");
grunt.loadNpmTasks("grunt-sass");
var importOnce = require('node-sass-import-once');
grunt.loadNpmTasks('grunt-sass');
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
@ -34,15 +36,15 @@ module.exports = function(grunt) {
src: [
'components/mocha/mocha.js',
'components/chai/chai.js',
'test/_test.js'
'test/_test.js',
],
dest: 'test/test.js',
},
//TODO: Move errors back down?
libtextsecure: {
options: {
banner: ";(function() {\n",
footer: "})();\n",
banner: ';(function() {\n',
footer: '})();\n',
},
src: [
'libtextsecure/errors.js',
@ -77,21 +79,21 @@ module.exports = function(grunt) {
'components/mock-socket/dist/mock-socket.js',
'components/mocha/mocha.js',
'components/chai/chai.js',
'libtextsecure/test/_test.js'
'libtextsecure/test/_test.js',
],
dest: 'libtextsecure/test/test.js',
}
},
},
sass: {
options: {
sourceMap: true,
importer: importOnce
importer: importOnce,
},
dev: {
files: {
"stylesheets/manifest.css": "stylesheets/manifest.scss"
}
}
'stylesheets/manifest.css': 'stylesheets/manifest.scss',
},
},
},
jshint: {
files: [
@ -117,7 +119,7 @@ module.exports = function(grunt) {
'!js/models/messages.js',
'!js/WebAudioRecorderMp3.js',
'!libtextsecure/message_receiver.js',
'_locales/**/*'
'_locales/**/*',
],
options: { jshintrc: '.jshintrc' },
},
@ -130,32 +132,33 @@ module.exports = function(grunt) {
'protos/*',
'js/**',
'stylesheets/*.css',
'!js/register.js'
'!js/register.js',
],
res: [
'images/**/*',
'fonts/*',
]
res: ['images/**/*', 'fonts/*'],
},
copy: {
deps: {
files: [{
files: [
{
src: 'components/mp3lameencoder/lib/Mp3LameEncoder.js',
dest: 'js/Mp3LameEncoder.min.js'
}, {
dest: 'js/Mp3LameEncoder.min.js',
},
{
src: 'components/webaudiorecorder/lib/WebAudioRecorderMp3.js',
dest: 'js/WebAudioRecorderMp3.js'
}, {
dest: 'js/WebAudioRecorderMp3.js',
},
{
src: 'components/jquery/dist/jquery.js',
dest: 'js/jquery.js'
}],
dest: 'js/jquery.js',
},
],
},
res: {
files: [{ expand: true, dest: 'dist/', src: ['<%= dist.res %>'] }],
},
src: {
files: [{ expand: true, dest: 'dist/', src: ['<%= dist.src %>'] }],
}
},
},
jscs: {
all: {
@ -179,69 +182,82 @@ module.exports = function(grunt) {
'!test/blanket_mocha.js',
'!test/modules/**/*.js',
'!test/test.js',
]
}
],
},
},
watch: {
sass: {
files: ['./stylesheets/*.scss'],
tasks: ['sass']
tasks: ['sass'],
},
libtextsecure: {
files: ['./libtextsecure/*.js', './libtextsecure/storage/*.js'],
tasks: ['concat:libtextsecure']
tasks: ['concat:libtextsecure'],
},
dist: {
files: ['<%= dist.src %>', '<%= dist.res %>'],
tasks: ['copy_dist']
tasks: ['copy_dist'],
},
scripts: {
files: ['<%= jshint.files %>'],
tasks: ['jshint']
tasks: ['jshint'],
},
style: {
files: ['<%= jscs.all.src %>'],
tasks: ['jscs']
tasks: ['jscs'],
},
transpile: {
files: ['./ts/**/*.ts'],
tasks: ['exec:transpile']
}
tasks: ['exec:transpile'],
},
},
exec: {
'tx-pull': {
cmd: 'tx pull'
cmd: 'tx pull',
},
'transpile': {
transpile: {
cmd: 'npm run transpile',
}
},
},
'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
},
gitinfo: {}, // to be populated by grunt gitinfo
});
Object.keys(grunt.config.get('pkg').devDependencies).forEach(function(key) {
if (/^grunt(?!(-cli)?$)/.test(key)) { // ignore grunt and grunt-cli
if (/^grunt(?!(-cli)?$)/.test(key)) {
// ignore grunt and grunt-cli
grunt.loadNpmTasks(key);
}
});
@ -250,7 +266,12 @@ module.exports = function(grunt) {
// 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.file.recurse('_locales', function(
abspath,
rootdir,
subdir,
filename
) {
if (subdir === 'en' || filename !== 'messages.json') {
return;
}
@ -258,7 +279,10 @@ module.exports = function(grunt) {
for (var key in messages) {
if (en[key] !== undefined && messages[key] !== undefined) {
if (en[key].placeholders !== undefined && messages[key].placeholders === undefined){
if (
en[key].placeholders !== undefined &&
messages[key].placeholders === undefined
) {
messages[key].placeholders = en[key].placeholders;
}
}
@ -273,8 +297,10 @@ module.exports = function(grunt) {
var gitinfo = grunt.config.get('gitinfo');
var commited = gitinfo.local.branch.current.lastCommitTime;
var time = Date.parse(commited) + 1000 * 60 * 60 * 24 * 90;
grunt.file.write('config/local-production.json',
JSON.stringify({ buildExpiration: time }) + '\n');
grunt.file.write(
'config/local-production.json',
JSON.stringify({ buildExpiration: time }) + '\n'
);
});
grunt.registerTask('clean-release', function() {
@ -290,35 +316,45 @@ module.exports = function(grunt) {
var gitinfo = grunt.config.get('gitinfo');
var https = require('https');
var urlBase = "https://s3-us-west-1.amazonaws.com/signal-desktop-builds";
var urlBase = 'https://s3-us-west-1.amazonaws.com/signal-desktop-builds';
var keyBase = 'signalapp/Signal-Desktop';
var sha = gitinfo.local.branch.current.SHA;
var files = [{
var files = [
{
zip: packageJson.name + '-' + packageJson.version + '.zip',
extractedTo: 'linux'
}];
extractedTo: 'linux',
},
];
var extract = require('extract-zip');
var download = function(url, dest, extractedTo, cb) {
var file = fs.createWriteStream(dest);
var request = https.get(url, function(response) {
var request = https
.get(url, function(response) {
if (response.statusCode !== 200) {
cb(response.statusCode);
} else {
response.pipe(file);
file.on('finish', function() {
file.close(function() {
extract(dest, {dir: path.join(__dirname, 'release', extractedTo)}, cb);
extract(
dest,
{ dir: path.join(__dirname, 'release', extractedTo) },
cb
);
});
});
}
}).on('error', function(err) { // Handle errors
})
.on('error', function(err) {
// Handle errors
fs.unlink(dest); // Delete the file async. (But we don't check the result)
if (cb) cb(err.message);
});
};
Promise.all(files.map(function(item) {
Promise.all(
files.map(function(item) {
var key = [keyBase, sha, 'dist', item.zip].join('/');
var url = [urlBase, key].join('/');
var dest = 'release/' + item.zip;
@ -334,7 +370,8 @@ module.exports = function(grunt) {
}
});
});
})).then(function(results) {
})
).then(function(results) {
results.forEach(function(error) {
if (error) {
grunt.fail.warn('Failed to fetch some release artifacts');
@ -347,59 +384,77 @@ module.exports = function(grunt) {
function runTests(environment, cb) {
var failure;
var Application = require('spectron').Application;
var electronBinary = process.platform === 'win32' ? 'electron.cmd' : 'electron';
var electronBinary =
process.platform === 'win32' ? 'electron.cmd' : 'electron';
var app = new Application({
path: path.join(__dirname, 'node_modules', '.bin', electronBinary),
args: [path.join(__dirname, 'main.js')],
env: {
NODE_ENV: environment
}
NODE_ENV: environment,
},
});
function getMochaResults() {
return window.mochaResults;
}
app.start().then(function() {
return app.client.waitUntil(function() {
app
.start()
.then(function() {
return app.client.waitUntil(
function() {
return app.client.execute(getMochaResults).then(function(data) {
return Boolean(data.value);
});
}, 10000, 'Expected to find window.mochaResults set!');
}).then(function() {
},
10000,
'Expected to find window.mochaResults set!'
);
})
.then(function() {
return app.client.execute(getMochaResults);
}).then(function(data) {
})
.then(function(data) {
var results = data.value;
if (results.failures > 0) {
console.error(results.reports);
failure = function() {
grunt.fail.fatal('Found ' + results.failures + ' failing unit tests.');
grunt.fail.fatal(
'Found ' + results.failures + ' failing unit tests.'
);
};
return app.client.log('browser');
} else {
grunt.log.ok(results.passes + ' tests passed.');
}
}).then(function(logs) {
})
.then(function(logs) {
if (logs) {
console.error();
console.error('Because tests failed, printing browser logs:');
console.error(logs);
}
}).catch(function (error) {
})
.catch(function(error) {
failure = function() {
grunt.fail.fatal('Something went wrong: ' + error.message + ' ' + error.stack);
grunt.fail.fatal(
'Something went wrong: ' + error.message + ' ' + error.stack
);
};
}).then(function () {
})
.then(function() {
// 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!
return app.stop();
}).then(function() {
})
.then(function() {
if (failure) {
failure();
}
cb();
}).catch(function (error) {
})
.catch(function(error) {
console.error('Second-level error:', error.message, error.stack);
if (failure) {
failure();
@ -415,12 +470,16 @@ module.exports = function(grunt) {
runTests(environment, done);
});
grunt.registerTask('lib-unit-tests', 'Run libtextsecure unit tests w/Electron', function() {
grunt.registerTask(
'lib-unit-tests',
'Run libtextsecure unit tests w/Electron',
function() {
var environment = grunt.option('env') || 'test-lib';
var done = this.async();
runTests(environment, done);
});
}
);
grunt.registerMultiTask('test-release', 'Test packaged releases', function() {
var dir = grunt.option('dir') || 'dist';
@ -431,7 +490,7 @@ module.exports = function(grunt) {
var files = [
'config/default.json',
'config/' + environment + '.json',
'config/local-' + environment + '.json'
'config/local-' + environment + '.json',
];
console.log(this.target, archive);
@ -443,16 +502,16 @@ module.exports = function(grunt) {
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)) {
console.log("auto update ok");
console.log('auto update ok');
} else {
throw new Error("Missing auto update config " + appUpdateYML);
throw new Error('Missing auto update config ' + appUpdateYML);
}
}
@ -462,33 +521,48 @@ module.exports = function(grunt) {
var assert = require('assert');
var app = new Application({
path: [dir, config.exe].join('/')
path: [dir, config.exe].join('/'),
});
app.start().then(function () {
app
.start()
.then(function() {
return app.client.getWindowCount();
}).then(function (count) {
})
.then(function(count) {
assert.equal(count, 1);
console.log('window opened');
}).then(function () {
})
.then(function() {
// Get the window's title
return app.client.getTitle();
}).then(function (title) {
})
.then(function(title) {
// Verify the window's title
assert.equal(title, packageJson.productName);
console.log('title ok');
}).then(function () {
assert(app.chromeDriver.logLines.indexOf('NODE_ENV ' + environment) > -1);
})
.then(function() {
assert(
app.chromeDriver.logLines.indexOf('NODE_ENV ' + environment) > -1
);
console.log('environment ok');
}).then(function () {
})
.then(
function() {
// Successfully completed test
return app.stop();
}, function (error) {
},
function(error) {
// Test failed!
return app.stop().then(function() {
grunt.fail.fatal('Test failed: ' + error.message + ' ' + error.stack);
grunt.fail.fatal(
'Test failed: ' + error.message + ' ' + error.stack
);
});
}).then(done);
}
)
.then(done);
});
grunt.registerTask('tx', ['exec:tx-pull', 'locale-patch']);
@ -497,9 +571,16 @@ module.exports = function(grunt) {
grunt.registerTask('test', ['unit-tests', 'lib-unit-tests']);
grunt.registerTask('copy_dist', ['gitinfo', 'copy:res', 'copy:src']);
grunt.registerTask('date', ['gitinfo', 'getExpireTime']);
grunt.registerTask('prep-release', ['gitinfo', 'clean-release', 'fetch-release']);
grunt.registerTask(
'default',
['concat', 'copy:deps', 'sass', 'date', 'exec:transpile']
);
grunt.registerTask('prep-release', [
'gitinfo',
'clean-release',
'fetch-release',
]);
grunt.registerTask('default', [
'concat',
'copy:deps',
'sass',
'date',
'exec:transpile',
]);
};

View File

@ -11,7 +11,7 @@
/* global Whisper: false */
/* global wrapDeferred: false */
;(async function() {
(async function() {
'use strict';
const { IdleDetector, MessageDataMigrator } = Signal.Workflow;
@ -65,7 +65,9 @@
var USERNAME = storage.get('number_id');
var PASSWORD = storage.get('password');
accountManager = new textsecure.AccountManager(
SERVER_URL, USERNAME, PASSWORD
SERVER_URL,
USERNAME,
PASSWORD
);
accountManager.addEventListener('registration', function() {
Whisper.Registration.markDone();
@ -105,18 +107,20 @@
if (!isMigrationWithoutIndexComplete) {
const database = Migrations0DatabaseWithAttachmentData.getDatabase();
const batchWithoutIndex = await MessageDataMigrator.processNextBatchWithoutIndex({
const batchWithoutIndex = await MessageDataMigrator.processNextBatchWithoutIndex(
{
databaseName: database.name,
minDatabaseVersion: database.version,
numMessagesPerBatch: NUM_MESSAGES_PER_BATCH,
upgradeMessageSchema,
});
}
);
console.log('Upgrade message schema (without index):', batchWithoutIndex);
isMigrationWithoutIndexComplete = batchWithoutIndex.done;
}
const areAllMigrationsComplete = isMigrationWithIndexComplete &&
isMigrationWithoutIndexComplete;
const areAllMigrationsComplete =
isMigrationWithIndexComplete && isMigrationWithoutIndexComplete;
if (areAllMigrationsComplete) {
idleDetector.stop();
}
@ -188,7 +192,9 @@
});
cancelInitializationMessage();
var appView = window.owsDesktopApp.appView = new Whisper.AppView({el: $('body')});
var appView = (window.owsDesktopApp.appView = new Whisper.AppView({
el: $('body'),
}));
Whisper.WallClockListener.init(Whisper.events);
Whisper.ExpiringMessagesListener.init(Whisper.events);
@ -200,7 +206,7 @@
Whisper.RotateSignedPreKeyListener.init(Whisper.events, newVersion);
connect();
appView.openInbox({
initialLoadComplete: initialLoadComplete
initialLoadComplete: initialLoadComplete,
});
} else if (window.config.importMode) {
appView.openImporter();
@ -214,7 +220,7 @@
Whisper.events.on('showSettings', () => {
if (!appView || !appView.inboxView) {
console.log(
'background: Event: \'showSettings\':' +
"background: Event: 'showSettings':" +
' Expected `appView.inboxView` to exist.'
);
return;
@ -238,7 +244,7 @@
appView.openConversation(conversation);
} else {
appView.openInbox({
initialLoadComplete: initialLoadComplete
initialLoadComplete: initialLoadComplete,
});
}
});
@ -258,7 +264,6 @@
}
});
var disconnectTimer = null;
function onOffline() {
console.log('offline');
@ -294,7 +299,9 @@
function isSocketOnline() {
var socketStatus = window.getSocketStatus();
return socketStatus === WebSocket.CONNECTING || socketStatus === WebSocket.OPEN;
return (
socketStatus === WebSocket.CONNECTING || socketStatus === WebSocket.OPEN
);
}
function disconnect() {
@ -317,14 +324,20 @@
window.addEventListener('offline', onOffline);
}
if (connectCount === 0 && !navigator.onLine) {
console.log('Starting up offline; will connect when we have network access');
console.log(
'Starting up offline; will connect when we have network access'
);
window.addEventListener('online', onOnline);
onEmpty(); // this ensures that the loading screen is dismissed
return;
}
if (!Whisper.Registration.everDone()) { return; }
if (Whisper.Import.isIncomplete()) { return; }
if (!Whisper.Registration.everDone()) {
return;
}
if (Whisper.Import.isIncomplete()) {
return;
}
if (messageReceiver) {
messageReceiver.close();
@ -343,7 +356,11 @@
// initialize the socket and start listening for messages
messageReceiver = new textsecure.MessageReceiver(
SERVER_URL, USERNAME, PASSWORD, mySignalingKey, options
SERVER_URL,
USERNAME,
PASSWORD,
mySignalingKey,
options
);
messageReceiver.addEventListener('message', onMessageReceived);
messageReceiver.addEventListener('delivery', onDeliveryReceipt);
@ -359,7 +376,10 @@
messageReceiver.addEventListener('configuration', onConfiguration);
window.textsecure.messaging = new textsecure.MessageSender(
SERVER_URL, USERNAME, PASSWORD, CDN_URL
SERVER_URL,
USERNAME,
PASSWORD,
CDN_URL
);
// Because v0.43.2 introduced a bug that lost contact details, v0.43.4 introduces
@ -406,7 +426,7 @@
});
if (Whisper.Import.isComplete()) {
textsecure.messaging.sendRequestConfigurationSyncMessage().catch((e) => {
textsecure.messaging.sendRequestConfigurationSyncMessage().catch(e => {
console.log(e);
});
}
@ -416,8 +436,10 @@
const shouldSkipAttachmentMigrationForNewUsers = firstRun === true;
if (shouldSkipAttachmentMigrationForNewUsers) {
const database = Migrations0DatabaseWithAttachmentData.getDatabase();
const connection =
await Signal.Database.open(database.name, database.version);
const connection = await Signal.Database.open(
database.name,
database.version
);
await Signal.Settings.markAttachmentMigrationComplete(connection);
}
idleDetector.start();
@ -471,7 +493,7 @@
}
var c = new Whisper.Conversation({
id: id
id: id,
});
var error = c.validateNumber();
if (error) {
@ -501,18 +523,21 @@
}
}
return wrapDeferred(conversation.save({
return wrapDeferred(
conversation.save({
name: details.name,
avatar: details.avatar,
color: details.color,
active_at: activeAt,
})).then(function() {
})
).then(function() {
const { expireTimer } = details;
const isValidExpireTimer = typeof expireTimer === 'number';
if (!isValidExpireTimer) {
console.log(
'Ignore invalid expire timer.',
'Expected numeric `expireTimer`, got:', expireTimer
'Expected numeric `expireTimer`, got:',
expireTimer
);
return;
}
@ -542,10 +567,7 @@
})
.then(ev.confirm)
.catch(function(error) {
console.log(
'onContactReceived error:',
Errors.toLogFormat(error)
);
console.log('onContactReceived error:', Errors.toLogFormat(error));
});
}
@ -553,7 +575,9 @@
var details = ev.groupDetails;
var id = details.id;
return ConversationController.getOrCreateAndWait(id, 'group').then(function(conversation) {
return ConversationController.getOrCreateAndWait(id, 'group').then(function(
conversation
) {
var updates = {
name: details.name,
members: details.members,
@ -573,13 +597,15 @@
updates.left = true;
}
return wrapDeferred(conversation.save(updates)).then(function() {
return wrapDeferred(conversation.save(updates))
.then(function() {
const { expireTimer } = details;
const isValidExpireTimer = typeof expireTimer === 'number';
if (!isValidExpireTimer) {
console.log(
'Ignore invalid expire timer.',
'Expected numeric `expireTimer`, got:', expireTimer
'Expected numeric `expireTimer`, got:',
expireTimer
);
return;
}
@ -592,7 +618,8 @@
receivedAt,
{ fromSync: true }
);
}).then(ev.confirm);
})
.then(ev.confirm);
});
}
@ -605,25 +632,23 @@
});
// Matches event data from `libtextsecure` `MessageReceiver::handleSentMessage`:
const getDescriptorForSent = ({ message, destination }) => (
const getDescriptorForSent = ({ message, destination }) =>
message.group
? getGroupDescriptor(message.group)
: { type: Message.PRIVATE, id: destination }
);
: { type: Message.PRIVATE, id: destination };
// Matches event data from `libtextsecure` `MessageReceiver::handleDataMessage`:
const getDescriptorForReceived = ({ message, source }) => (
const getDescriptorForReceived = ({ message, source }) =>
message.group
? getGroupDescriptor(message.group)
: { type: Message.PRIVATE, id: source }
);
: { type: Message.PRIVATE, id: source };
function createMessageHandler({
createMessage,
getMessageDescriptor,
handleProfileUpdate,
}) {
return async (event) => {
return async event => {
const { data, confirm } = event;
const messageDescriptor = getMessageDescriptor(data);
@ -647,11 +672,9 @@
messageDescriptor.id,
messageDescriptor.type
);
return message.handleDataMessage(
upgradedMessage,
event.confirm,
{ initialLoadComplete }
);
return message.handleDataMessage(upgradedMessage, event.confirm, {
initialLoadComplete,
});
};
}
@ -677,7 +700,10 @@
});
// Sent:
async function handleMessageSentProfileUpdate({ confirm, messageDescriptor }) {
async function handleMessageSentProfileUpdate({
confirm,
messageDescriptor,
}) {
const conversation = await ConversationController.getOrCreateAndWait(
messageDescriptor.id,
messageDescriptor.type
@ -716,9 +742,9 @@
value: [
message.get('source'),
message.get('sourceDevice'),
message.get('sent_at')
]
}
message.get('sent_at'),
],
},
};
fetcher.fetch(options).always(function() {
@ -742,7 +768,7 @@
received_at: data.receivedAt || Date.now(),
conversationId: data.source,
type: 'incoming',
unread : 1
unread: 1,
});
return message;
@ -752,25 +778,33 @@
var error = ev.error;
console.log('background onError:', Errors.toLogFormat(error));
if (error.name === 'HTTPError' && (error.code == 401 || error.code == 403)) {
if (
error.name === 'HTTPError' &&
(error.code == 401 || error.code == 403)
) {
Whisper.events.trigger('unauthorized');
console.log('Client is no longer authorized; deleting local configuration');
console.log(
'Client is no longer authorized; deleting local configuration'
);
Whisper.Registration.remove();
var previousNumberId = textsecure.storage.get('number_id');
textsecure.storage.protocol.removeAllConfiguration().then(function() {
textsecure.storage.protocol.removeAllConfiguration().then(
function() {
// These two bits of data are important to ensure that the app loads up
// the conversation list, instead of showing just the QR code screen.
Whisper.Registration.markEverDone();
textsecure.storage.put('number_id', previousNumberId);
console.log('Successfully cleared local configuration');
}, function(error) {
},
function(error) {
console.log(
'Something went wrong clearing local configuration',
error && error.stack ? error.stack : error
);
});
}
);
return;
}
@ -800,15 +834,19 @@
return message.saveErrors(error).then(function() {
var id = message.get('conversationId');
return ConversationController.getOrCreateAndWait(id, 'private').then(function(conversation) {
return ConversationController.getOrCreateAndWait(id, 'private').then(
function(conversation) {
conversation.set({
active_at: Date.now(),
unreadCount: conversation.get('unreadCount') + 1
unreadCount: conversation.get('unreadCount') + 1,
});
var conversation_timestamp = conversation.get('timestamp');
var message_timestamp = message.get('timestamp');
if (!conversation_timestamp || message_timestamp > conversation_timestamp) {
if (
!conversation_timestamp ||
message_timestamp > conversation_timestamp
) {
conversation.set({ timestamp: message.get('sent_at') });
}
@ -822,7 +860,8 @@
return new Promise(function(resolve, reject) {
conversation.save().then(resolve, reject);
});
});
}
);
});
}
@ -860,7 +899,7 @@
var receipt = Whisper.ReadSyncs.add({
sender: sender,
timestamp: timestamp,
read_at : read_at
read_at: read_at,
});
receipt.on('remove', ev.confirm);
@ -875,14 +914,11 @@
var state;
var c = new Whisper.Conversation({
id: number
id: number,
});
var error = c.validateNumber();
if (error) {
console.log(
'Invalid verified sync received:',
Errors.toLogFormat(error)
);
console.log('Invalid verified sync received:', Errors.toLogFormat(error));
return;
}
@ -898,14 +934,19 @@
break;
}
console.log('got verified sync for', number, state,
ev.viaContactSync ? 'via contact sync' : '');
console.log(
'got verified sync for',
number,
state,
ev.viaContactSync ? 'via contact sync' : ''
);
return ConversationController.getOrCreateAndWait(number, 'private').then(function(contact) {
return ConversationController.getOrCreateAndWait(number, 'private').then(
function(contact) {
var options = {
viaSyncMessage: true,
viaContactSync: ev.viaContactSync,
key: key
key: key,
};
if (state === 'VERIFIED') {
@ -915,7 +956,8 @@
} else {
return contact.setUnverified(options).then(ev.confirm);
}
});
}
);
}
function onDeliveryReceipt(ev) {
@ -928,7 +970,7 @@
var receipt = Whisper.DeliveryReceipts.add({
timestamp: deliveryReceipt.timestamp,
source: deliveryReceipt.source
source: deliveryReceipt.source,
});
ev.confirm();

View File

@ -9,6 +9,6 @@
extension.windows = {
onClosed: function(callback) {
window.addEventListener('beforeunload', callback);
}
},
};
}());
})();

View File

@ -19,7 +19,8 @@
this.reset([]);
});
this.on('add remove change:unreadCount',
this.on(
'add remove change:unreadCount',
_.debounce(this.updateUnreadCount.bind(this), 1000)
);
this.startPruning();
@ -52,17 +53,20 @@
},
updateUnreadCount: function() {
var newUnreadCount = _.reduce(
this.map(function(m) { return m.get('unreadCount'); }),
this.map(function(m) {
return m.get('unreadCount');
}),
function(item, memo) {
return item + memo;
},
0
);
storage.put("unreadCount", newUnreadCount);
storage.put('unreadCount', newUnreadCount);
if (newUnreadCount > 0) {
window.setBadgeCount(newUnreadCount);
window.document.title = window.config.title + " (" + newUnreadCount + ")";
window.document.title =
window.config.title + ' (' + newUnreadCount + ')';
} else {
window.setBadgeCount(0);
window.document.title = window.config.title;
@ -71,12 +75,15 @@
},
startPruning: function() {
var halfHour = 30 * 60 * 1000;
this.interval = setInterval(function() {
this.interval = setInterval(
function() {
this.forEach(function(conversation) {
conversation.trigger('prune');
});
}.bind(this), halfHour);
}
}.bind(this),
halfHour
);
},
}))();
window.getInboxCollection = function() {
@ -86,7 +93,9 @@
window.ConversationController = {
get: function(id) {
if (!this._initialFetchComplete) {
throw new Error('ConversationController.get() needs complete initial fetch');
throw new Error(
'ConversationController.get() needs complete initial fetch'
);
}
return conversations.get(id);
@ -104,11 +113,15 @@
}
if (type !== 'private' && type !== 'group') {
throw new TypeError(`'type' must be 'private' or 'group'; got: '${type}'`);
throw new TypeError(
`'type' must be 'private' or 'group'; got: '${type}'`
);
}
if (!this._initialFetchComplete) {
throw new Error('ConversationController.get() needs complete initial fetch');
throw new Error(
'ConversationController.get() needs complete initial fetch'
);
}
var conversation = conversations.get(id);
@ -118,7 +131,7 @@
conversation = conversations.add({
id: id,
type: type
type: type,
});
conversation.initialPromise = new Promise(function(resolve, reject) {
if (!conversation.isValid()) {
@ -146,7 +159,8 @@
return conversation;
},
getOrCreateAndWait: function(id, type) {
return this._initialPromise.then(function() {
return this._initialPromise.then(
function() {
var conversation = this.getOrCreate(id, type);
if (conversation) {
@ -158,7 +172,8 @@
return Promise.reject(
new Error('getOrCreateAndWait: did not get conversation')
);
}.bind(this));
}.bind(this)
);
},
getAllGroupsInvolvingId: function(id) {
var groups = new Whisper.GroupCollection();
@ -178,21 +193,26 @@
load: function() {
console.log('ConversationController: starting initial fetch');
this._initialPromise = new Promise(function(resolve, reject) {
conversations.fetch().then(function() {
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) {
}.bind(this),
function(error) {
console.log(
'ConversationController: initial fetch failed',
error && error.stack ? error.stack : error
);
reject(error);
});
}.bind(this));
}
);
}.bind(this)
);
return this._initialPromise;
}
},
};
})();

View File

@ -24,13 +24,13 @@
};
function clearStores(db, names) {
return new Promise(((resolve, reject) => {
return new Promise((resolve, reject) => {
const storeNames = names || db.objectStoreNames;
console.log('Clearing these indexeddb stores:', storeNames);
const transaction = db.transaction(storeNames, 'readwrite');
let finished = false;
const finish = (via) => {
const finish = via => {
console.log('clearing all stores done via', via);
if (finished) {
resolve();
@ -50,7 +50,7 @@
let count = 0;
// can't use built-in .forEach because db.objectStoreNames is not a plain array
_.forEach(storeNames, (storeName) => {
_.forEach(storeNames, storeName => {
const store = transaction.objectStore(storeName);
const request = store.clear();
@ -72,7 +72,7 @@
);
};
});
}));
});
}
Whisper.Database.open = () => {
@ -80,7 +80,7 @@
const { version } = migrations[migrations.length - 1];
const DBOpenRequest = window.indexedDB.open(Whisper.Database.id, version);
return new Promise(((resolve, reject) => {
return new Promise((resolve, reject) => {
// these two event handlers act on the IDBDatabase object,
// when the database is opened successfully, or not
DBOpenRequest.onerror = reject;
@ -91,7 +91,7 @@
// been created before, or a new version number has been
// submitted via the window.indexedDB.open line above
DBOpenRequest.onupgradeneeded = reject;
}));
});
};
Whisper.Database.clear = async () => {
@ -99,7 +99,7 @@
return clearStores(db);
};
Whisper.Database.clearStores = async (storeNames) => {
Whisper.Database.clearStores = async storeNames => {
const db = await Whisper.Database.open();
return clearStores(db, storeNames);
};
@ -107,7 +107,7 @@
Whisper.Database.close = () => window.wrapDeferred(Backbone.sync('closeall'));
Whisper.Database.drop = () =>
new Promise(((resolve, reject) => {
new Promise((resolve, reject) => {
const request = window.indexedDB.deleteDatabase(Whisper.Database.id);
request.onblocked = () => {
@ -121,7 +121,7 @@
};
request.onsuccess = resolve;
}));
});
Whisper.Database.migrations = getPlaceholderMigrations();
}());
})();

View File

@ -1,7 +1,7 @@
/*
* vim: ts=4:sw=4:expandtab
*/
;(function() {
(function() {
'use strict';
window.Whisper = window.Whisper || {};
@ -14,39 +14,60 @@
recipients = conversation.get('members') || [];
}
var receipts = this.filter(function(receipt) {
return (receipt.get('timestamp') === message.get('sent_at')) &&
(recipients.indexOf(receipt.get('source')) > -1);
return (
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();
return messages.fetchSentAt(receipt.get('timestamp')).then(function() {
if (messages.length === 0) { return; }
return messages
.fetchSentAt(receipt.get('timestamp'))
.then(function() {
if (messages.length === 0) {
return;
}
var message = messages.find(function(message) {
return (!message.isIncoming() && receipt.get('source') === message.get('conversationId'));
return (
!message.isIncoming() &&
receipt.get('source') === message.get('conversationId')
);
});
if (message) { return message; }
if (message) {
return message;
}
var groups = new Whisper.GroupCollection();
return groups.fetchGroups(receipt.get('source')).then(function() {
var ids = groups.pluck('id');
ids.push(receipt.get('source'));
return messages.find(function(message) {
return (!message.isIncoming() &&
_.contains(ids, message.get('conversationId')));
return (
!message.isIncoming() &&
_.contains(ids, message.get('conversationId'))
);
});
});
}).then(function(message) {
})
.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() {
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')
@ -57,8 +78,11 @@
this.remove(receipt);
resolve();
}.bind(this), reject);
}.bind(this));
}.bind(this),
reject
);
}.bind(this)
);
// TODO: consider keeping a list of numbers we've
// successfully delivered to?
} else {
@ -68,12 +92,14 @@
receipt.get('timestamp')
);
}
}.bind(this)).catch(function(error) {
}.bind(this)
)
.catch(function(error) {
console.log(
'DeliveryReceipts.onReceipt error:',
error && error.stack ? error.stack : error
);
});
}
},
}))();
})();

View File

@ -2,7 +2,7 @@
* vim: ts=4:sw=4:expandtab
*/
;(function() {
(function() {
'use strict';
window.emoji_util = window.emoji_util || {};
@ -39,17 +39,13 @@
var emojiCount = self.getCountOfAllMatches(str, self.rx_unified);
if (emojiCount > 8) {
return '';
}
else if (emojiCount > 6) {
} else if (emojiCount > 6) {
return 'small';
}
else if (emojiCount > 4) {
} else if (emojiCount > 4) {
return 'medium';
}
else if (emojiCount > 2) {
} else if (emojiCount > 2) {
return 'large';
}
else {
} else {
return 'jumbo';
}
};
@ -83,7 +79,8 @@
window.emoji = new EmojiConvertor();
emoji.init_colons();
emoji.img_sets.apple.path = 'node_modules/emoji-datasource-apple/img/apple/64/';
emoji.img_sets.apple.path =
'node_modules/emoji-datasource-apple/img/apple/64/';
emoji.include_title = true;
emoji.replace_mode = 'img';
emoji.supports_css = false; // needed to avoid spans with background-image
@ -95,5 +92,4 @@
$el.html(emoji.signalReplace($el.html()));
};
})();

View File

@ -1,16 +1,16 @@
;(function() {
(function() {
'use strict';
var BUILD_EXPIRATION = 0;
try {
BUILD_EXPIRATION = parseInt(window.config.buildExpiration);
if (BUILD_EXPIRATION) {
console.log("Build expires: ", new Date(BUILD_EXPIRATION).toISOString());
console.log('Build expires: ', new Date(BUILD_EXPIRATION).toISOString());
}
} catch (e) {}
window.extension = window.extension || {};
extension.expired = function() {
return (BUILD_EXPIRATION && Date.now() > BUILD_EXPIRATION);
return BUILD_EXPIRATION && Date.now() > BUILD_EXPIRATION;
};
})();

View File

@ -1,8 +1,7 @@
/*
* vim: ts=4:sw=4:expandtab
*/
;(function() {
(function() {
'use strict';
window.Whisper = window.Whisper || {};
@ -36,10 +35,14 @@
var wait = expires_at - Date.now();
// In the past
if (wait < 0) { wait = 0; }
if (wait < 0) {
wait = 0;
}
// Too far in the future, since it's limited to a 32-bit value
if (wait > 2147483647) { wait = 2147483647; }
if (wait > 2147483647) {
wait = 2147483647;
}
clearTimeout(timeout);
timeout = setTimeout(destroyExpiredMessages, wait);
@ -53,20 +56,23 @@
checkExpiringMessages();
events.on('timetravel', throttledCheckExpiringMessages);
},
update: throttledCheckExpiringMessages
update: throttledCheckExpiringMessages,
};
var TimerOption = Backbone.Model.extend({
getName: function() {
return i18n([
'timerOption', this.get('time'), this.get('unit'),
].join('_')) || moment.duration(this.get('time'), this.get('unit')).humanize();
return (
i18n(['timerOption', this.get('time'), this.get('unit')].join('_')) ||
moment.duration(this.get('time'), this.get('unit')).humanize()
);
},
getAbbreviated: function() {
return i18n([
'timerOption', this.get('time'), this.get('unit'), 'abbreviated'
].join('_'));
}
return i18n(
['timerOption', this.get('time'), this.get('unit'), 'abbreviated'].join(
'_'
)
);
},
});
Whisper.ExpirationTimerOptions = new (Backbone.Collection.extend({
model: TimerOption,
@ -75,8 +81,9 @@
seconds = 0;
}
var o = this.findWhere({ seconds: seconds });
if (o) { return o.getName(); }
else {
if (o) {
return o.getName();
} else {
return [seconds, 'seconds'].join(' ');
}
},
@ -85,12 +92,14 @@
seconds = 0;
}
var o = this.findWhere({ seconds: seconds });
if (o) { return o.getAbbreviated(); }
else {
if (o) {
return o.getAbbreviated();
} else {
return [seconds, 's'].join('');
}
}
}))([
},
}))(
[
[0, 'seconds'],
[5, 'seconds'],
[10, 'seconds'],
@ -108,8 +117,8 @@
return {
time: o[0],
unit: o[1],
seconds: duration.asSeconds()
seconds: duration.asSeconds(),
};
}));
})
);
})();

View File

@ -2,7 +2,7 @@
* vim: ts=4:sw=4:expandtab
*/
;(function () {
(function() {
'use strict';
window.Whisper = window.Whisper || {};
@ -13,16 +13,20 @@
}
signalProtocolStore.on('keychange', function(id) {
ConversationController.getOrCreateAndWait(id, 'private').then(function(conversation) {
ConversationController.getOrCreateAndWait(id, 'private').then(function(
conversation
) {
conversation.addKeyChange(id);
ConversationController.getAllGroupsInvolvingId(id).then(function(groups) {
ConversationController.getAllGroupsInvolvingId(id).then(function(
groups
) {
_.forEach(groups, function(group) {
group.addKeyChange(id);
});
});
});
});
}
},
};
}());
})();

View File

@ -1,8 +1,8 @@
/*
* vim: ts=4:sw=4:expandtab
*/
;(function() {
"use strict";
(function() {
'use strict';
/*
* This file extends the libphonenumber object with a set of phonenumbery
@ -17,7 +17,7 @@
var parsedNumber = libphonenumber.parse(number);
return libphonenumber.getRegionCodeForNumber(parsedNumber);
} catch (e) {
return "ZZ";
return 'ZZ';
}
},
@ -25,13 +25,13 @@
var parsedNumber = libphonenumber.parse(number);
return {
country_code: parsedNumber.values_[1],
national_number: parsedNumber.values_[2]
national_number: parsedNumber.values_[2],
};
},
getCountryCode: function(regionCode) {
var cc = libphonenumber.getCountryCodeForRegion(regionCode);
return (cc !== 0) ? cc : "";
return cc !== 0 ? cc : '';
},
parseNumber: function(number, defaultRegionCode) {
@ -43,7 +43,10 @@
regionCode: libphonenumber.getRegionCodeForNumber(parsedNumber),
countryCode: '' + parsedNumber.getCountryCode(),
nationalNumber: '' + parsedNumber.getNationalNumber(),
e164: libphonenumber.format(parsedNumber, libphonenumber.PhoneNumberFormat.E164)
e164: libphonenumber.format(
parsedNumber,
libphonenumber.PhoneNumberFormat.E164
),
};
} catch (ex) {
return { error: ex, isValidNumber: false };
@ -52,244 +55,244 @@
getAllRegionCodes: function() {
return {
"AD":"Andorra",
"AE":"United Arab Emirates",
"AF":"Afghanistan",
"AG":"Antigua and Barbuda",
"AI":"Anguilla",
"AL":"Albania",
"AM":"Armenia",
"AO":"Angola",
"AR":"Argentina",
"AS":"AmericanSamoa",
"AT":"Austria",
"AU":"Australia",
"AW":"Aruba",
"AX":"Åland Islands",
"AZ":"Azerbaijan",
"BA":"Bosnia and Herzegovina",
"BB":"Barbados",
"BD":"Bangladesh",
"BE":"Belgium",
"BF":"Burkina Faso",
"BG":"Bulgaria",
"BH":"Bahrain",
"BI":"Burundi",
"BJ":"Benin",
"BL":"Saint Barthélemy",
"BM":"Bermuda",
"BN":"Brunei Darussalam",
"BO":"Bolivia, Plurinational State of",
"BR":"Brazil",
"BS":"Bahamas",
"BT":"Bhutan",
"BW":"Botswana",
"BY":"Belarus",
"BZ":"Belize",
"CA":"Canada",
"CC":"Cocos (Keeling) Islands",
"CD":"Congo, The Democratic Republic of the",
"CF":"Central African Republic",
"CG":"Congo",
"CH":"Switzerland",
"CI":"Cote d'Ivoire",
"CK":"Cook Islands",
"CL":"Chile",
"CM":"Cameroon",
"CN":"China",
"CO":"Colombia",
"CR":"Costa Rica",
"CU":"Cuba",
"CV":"Cape Verde",
"CX":"Christmas Island",
"CY":"Cyprus",
"CZ":"Czech Republic",
"DE":"Germany",
"DJ":"Djibouti",
"DK":"Denmark",
"DM":"Dominica",
"DO":"Dominican Republic",
"DZ":"Algeria",
"EC":"Ecuador",
"EE":"Estonia",
"EG":"Egypt",
"ER":"Eritrea",
"ES":"Spain",
"ET":"Ethiopia",
"FI":"Finland",
"FJ":"Fiji",
"FK":"Falkland Islands (Malvinas)",
"FM":"Micronesia, Federated States of",
"FO":"Faroe Islands",
"FR":"France",
"GA":"Gabon",
"GB":"United Kingdom",
"GD":"Grenada",
"GE":"Georgia",
"GF":"French Guiana",
"GG":"Guernsey",
"GH":"Ghana",
"GI":"Gibraltar",
"GL":"Greenland",
"GM":"Gambia",
"GN":"Guinea",
"GP":"Guadeloupe",
"GQ":"Equatorial Guinea",
"GR":"Ελλάδα",
"GT":"Guatemala",
"GU":"Guam",
"GW":"Guinea-Bissau",
"GY":"Guyana",
"HK":"Hong Kong",
"HN":"Honduras",
"HR":"Croatia",
"HT":"Haiti",
"HU":"Magyarország",
"ID":"Indonesia",
"IE":"Ireland",
"IL":"Israel",
"IM":"Isle of Man",
"IN":"India",
"IO":"British Indian Ocean Territory",
"IQ":"Iraq",
"IR":"Iran, Islamic Republic of",
"IS":"Iceland",
"IT":"Italy",
"JE":"Jersey",
"JM":"Jamaica",
"JO":"Jordan",
"JP":"Japan",
"KE":"Kenya",
"KG":"Kyrgyzstan",
"KH":"Cambodia",
"KI":"Kiribati",
"KM":"Comoros",
"KN":"Saint Kitts and Nevis",
"KP":"Korea, Democratic People's Republic of",
"KR":"Korea, Republic of",
"KW":"Kuwait",
"KY":"Cayman Islands",
"KZ":"Kazakhstan",
"LA":"Lao People's Democratic Republic",
"LB":"Lebanon",
"LC":"Saint Lucia",
"LI":"Liechtenstein",
"LK":"Sri Lanka",
"LR":"Liberia",
"LS":"Lesotho",
"LT":"Lithuania",
"LU":"Luxembourg",
"LV":"Latvia",
"LY":"Libyan Arab Jamahiriya",
"MA":"Morocco",
"MC":"Monaco",
"MD":"Moldova, Republic of",
"ME":"Црна Гора",
"MF":"Saint Martin",
"MG":"Madagascar",
"MH":"Marshall Islands",
"MK":"Macedonia, The Former Yugoslav Republic of",
"ML":"Mali",
"MM":"Myanmar",
"MN":"Mongolia",
"MO":"Macao",
"MP":"Northern Mariana Islands",
"MQ":"Martinique",
"MR":"Mauritania",
"MS":"Montserrat",
"MT":"Malta",
"MU":"Mauritius",
"MV":"Maldives",
"MW":"Malawi",
"MX":"Mexico",
"MY":"Malaysia",
"MZ":"Mozambique",
"NA":"Namibia",
"NC":"New Caledonia",
"NE":"Niger",
"NF":"Norfolk Island",
"NG":"Nigeria",
"NI":"Nicaragua",
"NL":"Netherlands",
"NO":"Norway",
"NP":"Nepal",
"NR":"Nauru",
"NU":"Niue",
"NZ":"New Zealand",
"OM":"Oman",
"PA":"Panama",
"PE":"Peru",
"PF":"French Polynesia",
"PG":"Papua New Guinea",
"PH":"Philippines",
"PK":"Pakistan",
"PL":"Polska",
"PM":"Saint Pierre and Miquelon",
"PR":"Puerto Rico",
"PS":"Palestinian Territory, Occupied",
"PT":"Portugal",
"PW":"Palau",
"PY":"Paraguay",
"QA":"Qatar",
"RE":"Réunion",
"RO":"Romania",
"RS":"Србија",
"RU":"Russia",
"RW":"Rwanda",
"SA":"Saudi Arabia",
"SB":"Solomon Islands",
"SC":"Seychelles",
"SD":"Sudan",
"SE":"Sweden",
"SG":"Singapore",
"SH":"Saint Helena, Ascension and Tristan Da Cunha",
"SI":"Slovenia",
"SJ":"Svalbard and Jan Mayen",
"SK":"Slovakia",
"SL":"Sierra Leone",
"SM":"San Marino",
"SN":"Senegal",
"SO":"Somalia",
"SR":"Suriname",
"ST":"Sao Tome and Principe",
"SV":"El Salvador",
"SY":"Syrian Arab Republic",
"SZ":"Swaziland",
"TC":"Turks and Caicos Islands",
"TD":"Chad",
"TG":"Togo",
"TH":"Thailand",
"TJ":"Tajikistan",
"TK":"Tokelau",
"TL":"Timor-Leste",
"TM":"Turkmenistan",
"TN":"Tunisia",
"TO":"Tonga",
"TR":"Turkey",
"TT":"Trinidad and Tobago",
"TV":"Tuvalu",
"TW":"Taiwan, Province of China",
"TZ":"Tanzania, United Republic of",
"UA":"Ukraine",
"UG":"Uganda",
"US":"United States",
"UY":"Uruguay",
"UZ":"Uzbekistan",
"VA":"Holy See (Vatican City State)",
"VC":"Saint Vincent and the Grenadines",
"VE":"Venezuela",
"VG":"Virgin Islands, British",
"VI":"Virgin Islands, U.S.",
"VN":"Viet Nam",
"VU":"Vanuatu",
"WF":"Wallis and Futuna",
"WS":"Samoa",
"YE":"Yemen",
"YT":"Mayotte",
"ZA":"South Africa",
"ZM":"Zambia",
"ZW":"Zimbabwe"
AD: 'Andorra',
AE: 'United Arab Emirates',
AF: 'Afghanistan',
AG: 'Antigua and Barbuda',
AI: 'Anguilla',
AL: 'Albania',
AM: 'Armenia',
AO: 'Angola',
AR: 'Argentina',
AS: 'AmericanSamoa',
AT: 'Austria',
AU: 'Australia',
AW: 'Aruba',
AX: 'Åland Islands',
AZ: 'Azerbaijan',
BA: 'Bosnia and Herzegovina',
BB: 'Barbados',
BD: 'Bangladesh',
BE: 'Belgium',
BF: 'Burkina Faso',
BG: 'Bulgaria',
BH: 'Bahrain',
BI: 'Burundi',
BJ: 'Benin',
BL: 'Saint Barthélemy',
BM: 'Bermuda',
BN: 'Brunei Darussalam',
BO: 'Bolivia, Plurinational State of',
BR: 'Brazil',
BS: 'Bahamas',
BT: 'Bhutan',
BW: 'Botswana',
BY: 'Belarus',
BZ: 'Belize',
CA: 'Canada',
CC: 'Cocos (Keeling) Islands',
CD: 'Congo, The Democratic Republic of the',
CF: 'Central African Republic',
CG: 'Congo',
CH: 'Switzerland',
CI: "Cote d'Ivoire",
CK: 'Cook Islands',
CL: 'Chile',
CM: 'Cameroon',
CN: 'China',
CO: 'Colombia',
CR: 'Costa Rica',
CU: 'Cuba',
CV: 'Cape Verde',
CX: 'Christmas Island',
CY: 'Cyprus',
CZ: 'Czech Republic',
DE: 'Germany',
DJ: 'Djibouti',
DK: 'Denmark',
DM: 'Dominica',
DO: 'Dominican Republic',
DZ: 'Algeria',
EC: 'Ecuador',
EE: 'Estonia',
EG: 'Egypt',
ER: 'Eritrea',
ES: 'Spain',
ET: 'Ethiopia',
FI: 'Finland',
FJ: 'Fiji',
FK: 'Falkland Islands (Malvinas)',
FM: 'Micronesia, Federated States of',
FO: 'Faroe Islands',
FR: 'France',
GA: 'Gabon',
GB: 'United Kingdom',
GD: 'Grenada',
GE: 'Georgia',
GF: 'French Guiana',
GG: 'Guernsey',
GH: 'Ghana',
GI: 'Gibraltar',
GL: 'Greenland',
GM: 'Gambia',
GN: 'Guinea',
GP: 'Guadeloupe',
GQ: 'Equatorial Guinea',
GR: 'Ελλάδα',
GT: 'Guatemala',
GU: 'Guam',
GW: 'Guinea-Bissau',
GY: 'Guyana',
HK: 'Hong Kong',
HN: 'Honduras',
HR: 'Croatia',
HT: 'Haiti',
HU: 'Magyarország',
ID: 'Indonesia',
IE: 'Ireland',
IL: 'Israel',
IM: 'Isle of Man',
IN: 'India',
IO: 'British Indian Ocean Territory',
IQ: 'Iraq',
IR: 'Iran, Islamic Republic of',
IS: 'Iceland',
IT: 'Italy',
JE: 'Jersey',
JM: 'Jamaica',
JO: 'Jordan',
JP: 'Japan',
KE: 'Kenya',
KG: 'Kyrgyzstan',
KH: 'Cambodia',
KI: 'Kiribati',
KM: 'Comoros',
KN: 'Saint Kitts and Nevis',
KP: "Korea, Democratic People's Republic of",
KR: 'Korea, Republic of',
KW: 'Kuwait',
KY: 'Cayman Islands',
KZ: 'Kazakhstan',
LA: "Lao People's Democratic Republic",
LB: 'Lebanon',
LC: 'Saint Lucia',
LI: 'Liechtenstein',
LK: 'Sri Lanka',
LR: 'Liberia',
LS: 'Lesotho',
LT: 'Lithuania',
LU: 'Luxembourg',
LV: 'Latvia',
LY: 'Libyan Arab Jamahiriya',
MA: 'Morocco',
MC: 'Monaco',
MD: 'Moldova, Republic of',
ME: 'Црна Гора',
MF: 'Saint Martin',
MG: 'Madagascar',
MH: 'Marshall Islands',
MK: 'Macedonia, The Former Yugoslav Republic of',
ML: 'Mali',
MM: 'Myanmar',
MN: 'Mongolia',
MO: 'Macao',
MP: 'Northern Mariana Islands',
MQ: 'Martinique',
MR: 'Mauritania',
MS: 'Montserrat',
MT: 'Malta',
MU: 'Mauritius',
MV: 'Maldives',
MW: 'Malawi',
MX: 'Mexico',
MY: 'Malaysia',
MZ: 'Mozambique',
NA: 'Namibia',
NC: 'New Caledonia',
NE: 'Niger',
NF: 'Norfolk Island',
NG: 'Nigeria',
NI: 'Nicaragua',
NL: 'Netherlands',
NO: 'Norway',
NP: 'Nepal',
NR: 'Nauru',
NU: 'Niue',
NZ: 'New Zealand',
OM: 'Oman',
PA: 'Panama',
PE: 'Peru',
PF: 'French Polynesia',
PG: 'Papua New Guinea',
PH: 'Philippines',
PK: 'Pakistan',
PL: 'Polska',
PM: 'Saint Pierre and Miquelon',
PR: 'Puerto Rico',
PS: 'Palestinian Territory, Occupied',
PT: 'Portugal',
PW: 'Palau',
PY: 'Paraguay',
QA: 'Qatar',
RE: 'Réunion',
RO: 'Romania',
RS: 'Србија',
RU: 'Russia',
RW: 'Rwanda',
SA: 'Saudi Arabia',
SB: 'Solomon Islands',
SC: 'Seychelles',
SD: 'Sudan',
SE: 'Sweden',
SG: 'Singapore',
SH: 'Saint Helena, Ascension and Tristan Da Cunha',
SI: 'Slovenia',
SJ: 'Svalbard and Jan Mayen',
SK: 'Slovakia',
SL: 'Sierra Leone',
SM: 'San Marino',
SN: 'Senegal',
SO: 'Somalia',
SR: 'Suriname',
ST: 'Sao Tome and Principe',
SV: 'El Salvador',
SY: 'Syrian Arab Republic',
SZ: 'Swaziland',
TC: 'Turks and Caicos Islands',
TD: 'Chad',
TG: 'Togo',
TH: 'Thailand',
TJ: 'Tajikistan',
TK: 'Tokelau',
TL: 'Timor-Leste',
TM: 'Turkmenistan',
TN: 'Tunisia',
TO: 'Tonga',
TR: 'Turkey',
TT: 'Trinidad and Tobago',
TV: 'Tuvalu',
TW: 'Taiwan, Province of China',
TZ: 'Tanzania, United Republic of',
UA: 'Ukraine',
UG: 'Uganda',
US: 'United States',
UY: 'Uruguay',
UZ: 'Uzbekistan',
VA: 'Holy See (Vatican City State)',
VC: 'Saint Vincent and the Grenadines',
VE: 'Venezuela',
VG: 'Virgin Islands, British',
VI: 'Virgin Islands, U.S.',
VN: 'Viet Nam',
VU: 'Vanuatu',
WF: 'Wallis and Futuna',
WS: 'Samoa',
YE: 'Yemen',
YT: 'Mayotte',
ZA: 'South Africa',
ZM: 'Zambia',
ZW: 'Zimbabwe',
};
} // getAllRegionCodes
}, // getAllRegionCodes
}; // libphonenumber.util
})();

View File

@ -34,7 +34,7 @@ function log(...args) {
console._log(...consoleArgs);
// To avoid [Object object] in our log since console.log handles non-strings smoothly
const str = args.map((item) => {
const str = args.map(item => {
if (typeof item !== 'string') {
try {
return JSON.stringify(item);
@ -55,7 +55,6 @@ if (window.console) {
console.log = log;
}
// The mechanics of preparing a log for publish
function getHeader() {
@ -85,7 +84,7 @@ function format(entries) {
}
function fetch() {
return new Promise((resolve) => {
return new Promise(resolve => {
ipc.send('fetch-log');
ipc.on('fetched-log', (event, text) => {
@ -103,14 +102,16 @@ const publish = debuglogs.upload;
// Anyway, the default process.stdout stream goes to the command-line, not the devtools.
const logger = bunyan.createLogger({
name: 'log',
streams: [{
streams: [
{
level: 'debug',
stream: {
write(entry) {
console._log(formatLine(JSON.parse(entry)));
},
},
}],
},
],
});
// The Bunyan API: https://github.com/trentm/node-bunyan#log-method-api
@ -137,6 +138,8 @@ window.onerror = (message, script, line, col, error) => {
window.log.error(`Top-level unhandled error: ${errorInfo}`);
};
window.addEventListener('unhandledrejection', (rejectionEvent) => {
window.log.error(`Top-level unhandled promise rejection: ${rejectionEvent.reason}`);
window.addEventListener('unhandledrejection', rejectionEvent => {
window.log.error(
`Top-level unhandled promise rejection: ${rejectionEvent.reason}`
);
});

View File

@ -124,27 +124,34 @@
},
safeGetVerified() {
const promise = textsecure.storage.protocol.getVerified(this.id);
return promise.catch(() => textsecure.storage.protocol.VerifiedStatus.DEFAULT);
return promise.catch(
() => textsecure.storage.protocol.VerifiedStatus.DEFAULT
);
},
updateVerified() {
if (this.isPrivate()) {
return Promise.all([
this.safeGetVerified(),
this.initialPromise,
]).then((results) => {
return Promise.all([this.safeGetVerified(), this.initialPromise]).then(
results => {
const trust = results[0];
// we don't return here because we don't need to wait for this to finish
this.save({ verified: trust });
});
}
);
}
const promise = this.fetchContacts();
return promise.then(() => Promise.all(this.contactCollection.map((contact) => {
return promise
.then(() =>
Promise.all(
this.contactCollection.map(contact => {
if (!contact.isMe()) {
return contact.updateVerified();
}
return Promise.resolve();
}))).then(this.onMemberVerifiedChange.bind(this));
})
)
)
.then(this.onMemberVerifiedChange.bind(this));
},
setVerifiedDefault(options) {
const { DEFAULT } = this.verifiedEnum;
@ -160,16 +167,19 @@
},
_setVerified(verified, providedOptions) {
const options = providedOptions || {};
_.defaults(options, { viaSyncMessage: false, viaContactSync: false, key: null });
_.defaults(options, {
viaSyncMessage: false,
viaContactSync: false,
key: null,
});
const {
VERIFIED,
UNVERIFIED,
} = this.verifiedEnum;
const { VERIFIED, UNVERIFIED } = this.verifiedEnum;
if (!this.isPrivate()) {
throw new Error('You cannot verify a group conversation. ' +
'You must verify individual contacts.');
throw new Error(
'You cannot verify a group conversation. ' +
'You must verify individual contacts.'
);
}
const beginningVerified = this.get('verified');
@ -187,10 +197,14 @@
}
let keychange;
return promise.then((updatedKey) => {
return promise
.then(updatedKey => {
keychange = updatedKey;
return new Promise((resolve => this.save({ verified }).always(resolve)));
}).then(() => {
return new Promise(resolve =>
this.save({ verified }).always(resolve)
);
})
.then(() => {
// Three situations result in a verification notice in the conversation:
// 1) The message came from an explicit verification in another client (not
// a contact sync)
@ -199,14 +213,14 @@
// 3) Our local verification status is VERIFIED and it hasn't changed,
// but the key did change (Key1/VERIFIED to Key2/VERIFIED - but we don't
// want to show DEFAULT->DEFAULT or UNVERIFIED->UNVERIFIED)
if (!options.viaContactSync ||
if (
!options.viaContactSync ||
(beginningVerified !== verified && verified !== UNVERIFIED) ||
(keychange && verified === VERIFIED)) {
return this.addVerifiedChange(
this.id,
verified === VERIFIED,
{ local: !options.viaSyncMessage }
);
(keychange && verified === VERIFIED)
) {
return this.addVerifiedChange(this.id, verified === VERIFIED, {
local: !options.viaSyncMessage,
});
}
if (!options.viaSyncMessage) {
return this.sendVerifySyncMessage(this.id, verified);
@ -216,20 +230,21 @@
},
sendVerifySyncMessage(number, state) {
const promise = textsecure.storage.protocol.loadIdentityKey(number);
return promise.then(key => textsecure.messaging.syncVerification(
number,
state,
key
));
return promise.then(key =>
textsecure.messaging.syncVerification(number, state, key)
);
},
getIdentityKeys() {
const lookup = {};
if (this.isPrivate()) {
return textsecure.storage.protocol.loadIdentityKey(this.id).then((key) => {
return textsecure.storage.protocol
.loadIdentityKey(this.id)
.then(key => {
lookup[this.id] = key;
return lookup;
}).catch((error) => {
})
.catch(error => {
console.log(
'getIdentityKeys error for conversation',
this.idForLogging(),
@ -240,27 +255,25 @@
}
const promises = this.contactCollection.map(contact =>
textsecure.storage.protocol.loadIdentityKey(contact.id).then(
(key) => {
key => {
lookup[contact.id] = key;
},
(error) => {
error => {
console.log(
'getIdentityKeys error for group member',
contact.idForLogging(),
error && error.stack ? error.stack : error
);
}
));
)
);
return Promise.all(promises).then(() => lookup);
},
replay(error, message) {
const replayable = new textsecure.ReplayableError(error);
return replayable.replay(message.attributes).catch((e) => {
console.log(
'replay error:',
e && e.stack ? e.stack : e
);
return replayable.replay(message.attributes).catch(e => {
console.log('replay error:', e && e.stack ? e.stack : e);
});
},
decryptOldIncomingKeyErrors() {
@ -270,19 +283,25 @@
}
console.log('decryptOldIncomingKeyErrors start for', this.idForLogging());
const messages = this.messageCollection.filter((message) => {
const messages = this.messageCollection.filter(message => {
const errors = message.get('errors');
if (!errors || !errors[0]) {
return false;
}
const error = _.find(errors, e => e.name === 'IncomingIdentityKeyError');
const error = _.find(
errors,
e => e.name === 'IncomingIdentityKeyError'
);
return Boolean(error);
});
const markComplete = () => {
console.log('decryptOldIncomingKeyErrors complete for', this.idForLogging());
return new Promise((resolve) => {
console.log(
'decryptOldIncomingKeyErrors complete for',
this.idForLogging()
);
return new Promise(resolve => {
this.save({ decryptedOldIncomingKeyErrors: true }).always(resolve);
});
};
@ -296,12 +315,16 @@
messages.length,
'messages to process'
);
const safeDelete = message => new Promise((resolve) => {
const safeDelete = message =>
new Promise(resolve => {
message.destroy().always(resolve);
});
const promise = this.getIdentityKeys();
return promise.then(lookup => Promise.all(_.map(messages, (message) => {
return promise
.then(lookup =>
Promise.all(
_.map(messages, message => {
const source = message.get('source');
const error = _.find(
message.get('errors'),
@ -314,16 +337,22 @@
}
if (constantTimeEqualArrayBuffers(key, error.identityKey)) {
return this.replay(error, message).then(() => safeDelete(message));
return this.replay(error, message).then(() =>
safeDelete(message)
);
}
return Promise.resolve();
}))).catch((error) => {
})
)
)
.catch(error => {
console.log(
'decryptOldIncomingKeyErrors error:',
error && error.stack ? error.stack : error
);
}).then(markComplete);
})
.then(markComplete);
},
isVerified() {
if (this.isPrivate()) {
@ -333,7 +362,7 @@
return false;
}
return this.contactCollection.every((contact) => {
return this.contactCollection.every(contact => {
if (contact.isMe()) {
return true;
}
@ -343,14 +372,16 @@
isUnverified() {
if (this.isPrivate()) {
const verified = this.get('verified');
return verified !== this.verifiedEnum.VERIFIED &&
verified !== this.verifiedEnum.DEFAULT;
return (
verified !== this.verifiedEnum.VERIFIED &&
verified !== this.verifiedEnum.DEFAULT
);
}
if (!this.contactCollection.length) {
return true;
}
return this.contactCollection.any((contact) => {
return this.contactCollection.any(contact => {
if (contact.isMe()) {
return false;
}
@ -363,23 +394,29 @@
? new Backbone.Collection([this])
: new Backbone.Collection();
}
return new Backbone.Collection(this.contactCollection.filter((contact) => {
return new Backbone.Collection(
this.contactCollection.filter(contact => {
if (contact.isMe()) {
return false;
}
return contact.isUnverified();
}));
})
);
},
setApproved() {
if (!this.isPrivate()) {
throw new Error('You cannot set a group conversation as trusted. ' +
'You must set individual contacts as trusted.');
throw new Error(
'You cannot set a group conversation as trusted. ' +
'You must set individual contacts as trusted.'
);
}
return textsecure.storage.protocol.setApproval(this.id, true);
},
safeIsUntrusted() {
return textsecure.storage.protocol.isUntrusted(this.id).catch(() => false);
return textsecure.storage.protocol
.isUntrusted(this.id)
.catch(() => false);
},
isUntrusted() {
if (this.isPrivate()) {
@ -389,18 +426,20 @@
return Promise.resolve(false);
}
return Promise.all(this.contactCollection.map((contact) => {
return Promise.all(
this.contactCollection.map(contact => {
if (contact.isMe()) {
return false;
}
return contact.safeIsUntrusted();
})).then(results => _.any(results, result => result));
})
).then(results => _.any(results, result => result));
},
getUntrusted() {
// This is a bit ugly because isUntrusted() is async. Could do the work to cache
// it locally, but we really only need it for this call.
if (this.isPrivate()) {
return this.isUntrusted().then((untrusted) => {
return this.isUntrusted().then(untrusted => {
if (untrusted) {
return new Backbone.Collection([this]);
}
@ -408,20 +447,24 @@
return new Backbone.Collection();
});
}
return Promise.all(this.contactCollection.map((contact) => {
return Promise.all(
this.contactCollection.map(contact => {
if (contact.isMe()) {
return [false, contact];
}
return Promise.all([contact.isUntrusted(), contact]);
})).then((results) => {
const filtered = _.filter(results, (result) => {
})
).then(results => {
const filtered = _.filter(results, result => {
const untrusted = result[0];
return untrusted;
});
return new Backbone.Collection(_.map(filtered, (result) => {
return new Backbone.Collection(
_.map(filtered, result => {
const contact = result[1];
return contact;
}));
})
);
});
},
onMemberVerifiedChange() {
@ -461,7 +504,9 @@
_.defaults(options, { local: true });
if (this.isMe()) {
console.log('refusing to add verified change advisory for our own number');
console.log(
'refusing to add verified change advisory for our own number'
);
return;
}
@ -488,8 +533,8 @@
message.save().then(this.trigger.bind(this, 'newmessage', message));
if (this.isPrivate()) {
ConversationController.getAllGroupsInvolvingId(id).then((groups) => {
_.forEach(groups, (group) => {
ConversationController.getAllGroupsInvolvingId(id).then(groups => {
_.forEach(groups, group => {
group.addVerifiedChange(id, verified, options);
});
});
@ -512,31 +557,36 @@
// Lastly, we don't send read syncs for any message marked read due to a read
// sync. That's a notification explosion we don't need.
return this.queueJob(() => this.markRead(
message.get('received_at'),
{ sendReadReceipts: false }
));
return this.queueJob(() =>
this.markRead(message.get('received_at'), { sendReadReceipts: false })
);
},
getUnread() {
const conversationId = this.id;
const unreadMessages = new Whisper.MessageCollection();
return new Promise((resolve => unreadMessages.fetch({
return new Promise(resolve =>
unreadMessages
.fetch({
index: {
// 'unread' index
name: 'unread',
lower: [conversationId],
upper: [conversationId, Number.MAX_VALUE],
},
}).always(() => {
})
.always(() => {
resolve(unreadMessages);
})));
})
);
},
validate(attributes) {
const required = ['id', 'type'];
const missing = _.filter(required, attr => !attributes[attr]);
if (missing.length) { return `Conversation must have ${missing}`; }
if (missing.length) {
return `Conversation must have ${missing}`;
}
if (attributes.type !== 'private' && attributes.type !== 'group') {
return `Invalid conversation type: ${attributes.type}`;
@ -572,7 +622,12 @@
const name = this.get('name');
if (typeof name === 'string') {
tokens.push(name.toLowerCase());
tokens = tokens.concat(name.trim().toLowerCase().split(/[\s\-_()+]+/));
tokens = tokens.concat(
name
.trim()
.toLowerCase()
.split(/[\s\-_()+]+/)
);
}
if (this.isPrivate()) {
const regionCode = storage.get('regionCode');
@ -633,7 +688,9 @@
type: contentType,
});
const thumbnail = Signal.Util.GoogleChrome.isImageTypeSupported(contentType)
const thumbnail = Signal.Util.GoogleChrome.isImageTypeSupported(
contentType
)
? await Whisper.FileInputView.makeImageThumbnail(128, objectUrl)
: await Whisper.FileInputView.makeVideoThumbnail(128, objectUrl);
@ -661,7 +718,8 @@
author: contact.id,
id: quotedMessage.get('sent_at'),
text: quotedMessage.get('body'),
attachments: await Promise.all((attachments || []).map(async (attachment) => {
attachments: await Promise.all(
(attachments || []).map(async attachment => {
const { contentType } = attachment;
const willMakeThumbnail =
Signal.Util.GoogleChrome.isImageTypeSupported(contentType) ||
@ -674,7 +732,8 @@
? await this.makeThumbnailAttachment(attachment)
: null,
};
})),
})
),
};
},
@ -721,7 +780,9 @@
case Message.GROUP:
return textsecure.messaging.sendMessageToGroup;
default:
throw new TypeError(`Invalid conversation type: '${conversationType}'`);
throw new TypeError(
`Invalid conversation type: '${conversationType}'`
);
}
})();
@ -730,9 +791,11 @@
profileKey = storage.get('profileKey');
}
const attachmentsWithData =
await Promise.all(messageWithSchema.attachments.map(loadAttachmentData));
message.send(sendFunction(
const attachmentsWithData = await Promise.all(
messageWithSchema.attachments.map(loadAttachmentData)
);
message.send(
sendFunction(
this.get('id'),
body,
attachmentsWithData,
@ -740,7 +803,8 @@
now,
this.get('expireTimer'),
profileKey
));
)
);
});
},
@ -749,13 +813,16 @@
await collection.fetchConversation(this.id, 1);
const lastMessage = collection.at(0);
const lastMessageUpdate = window.Signal.Types.Conversation.createLastMessageUpdate({
const lastMessageUpdate = window.Signal.Types.Conversation.createLastMessageUpdate(
{
currentLastMessageText: this.get('lastMessage') || null,
currentTimestamp: this.get('timestamp') || null,
lastMessage: lastMessage ? lastMessage.toJSON() : null,
lastMessageNotificationText: lastMessage
? lastMessage.getNotificationText() : null,
});
? lastMessage.getNotificationText()
: null,
}
);
this.set(lastMessageUpdate);
@ -779,8 +846,10 @@
if (!expireTimer) {
expireTimer = null;
}
if (this.get('expireTimer') === expireTimer ||
(!expireTimer && !this.get('expireTimer'))) {
if (
this.get('expireTimer') === expireTimer ||
(!expireTimer && !this.get('expireTimer'))
) {
return Promise.resolve();
}
@ -881,12 +950,14 @@
received_at: now,
group_update: groupUpdate,
});
message.send(textsecure.messaging.updateGroup(
message.send(
textsecure.messaging.updateGroup(
this.id,
this.get('name'),
this.get('avatar'),
this.get('members')
));
)
);
},
leaveGroup() {
@ -909,25 +980,30 @@
_.defaults(options, { sendReadReceipts: true });
const conversationId = this.id;
Whisper.Notifications.remove(Whisper.Notifications.where({
Whisper.Notifications.remove(
Whisper.Notifications.where({
conversationId,
}));
})
);
return this.getUnread().then((providedUnreadMessages) => {
return this.getUnread().then(providedUnreadMessages => {
let unreadMessages = providedUnreadMessages;
const promises = [];
const oldUnread = unreadMessages.filter(message =>
message.get('received_at') <= newestUnreadDate);
const oldUnread = unreadMessages.filter(
message => message.get('received_at') <= newestUnreadDate
);
let read = _.map(oldUnread, (providedM) => {
let read = _.map(oldUnread, providedM => {
let m = providedM;
if (this.messageCollection.get(m.id)) {
m = this.messageCollection.get(m.id);
} else {
console.log('Marked a message as read in the database, but ' +
'it was not in messageCollection.');
console.log(
'Marked a message as read in the database, but ' +
'it was not in messageCollection.'
);
}
promises.push(m.markRead());
const errors = m.get('errors');
@ -962,7 +1038,9 @@
if (storage.get('read-receipt-setting')) {
_.each(_.groupBy(read, 'sender'), (receipts, sender) => {
const timestamps = _.map(receipts, 'timestamp');
promises.push(textsecure.messaging.sendReadReceipts(sender, timestamps));
promises.push(
textsecure.messaging.sendReadReceipts(sender, timestamps)
);
});
}
}
@ -990,21 +1068,22 @@
getProfile(id) {
if (!textsecure.messaging) {
const message = 'Conversation.getProfile: textsecure.messaging not available';
const message =
'Conversation.getProfile: textsecure.messaging not available';
return Promise.reject(new Error(message));
}
return textsecure.messaging.getProfile(id).then((profile) => {
return textsecure.messaging
.getProfile(id)
.then(profile => {
const identityKey = dcodeIO.ByteBuffer.wrap(
profile.identityKey,
'base64'
).toArrayBuffer();
return textsecure.storage.protocol.saveIdentity(
`${id}.1`,
identityKey,
false
).then((changed) => {
return textsecure.storage.protocol
.saveIdentity(`${id}.1`, identityKey, false)
.then(changed => {
if (changed) {
// save identity will close all sessions except for .1, so we
// must close that one manually.
@ -1017,18 +1096,20 @@
return sessionCipher.closeOpenSessionForDevice();
}
return Promise.resolve();
}).then(() => {
})
.then(() => {
const c = ConversationController.get(id);
return Promise.all([
c.setProfileName(profile.name),
c.setProfileAvatar(profile.avatar),
]).then(
// success
() => new Promise((resolve, reject) => {
() =>
new Promise((resolve, reject) => {
c.save().then(resolve, reject);
}),
// fail
(e) => {
e => {
if (e.name === 'ProfileDecryptError') {
// probably the profile key has changed.
console.log(
@ -1041,7 +1122,8 @@
}
);
});
}).catch((error) => {
})
.catch(error => {
console.log(
'getProfile error:',
error && error.stack ? error.stack : error
@ -1056,10 +1138,15 @@
try {
// decode
const data = dcodeIO.ByteBuffer.wrap(encryptedName, 'base64').toArrayBuffer();
const data = dcodeIO.ByteBuffer.wrap(
encryptedName,
'base64'
).toArrayBuffer();
// decrypt
return textsecure.crypto.decryptProfileName(data, key).then((decrypted) => {
return textsecure.crypto
.decryptProfileName(data, key)
.then(decrypted => {
// encode
const name = dcodeIO.ByteBuffer.wrap(decrypted).toString('utf8');
@ -1075,13 +1162,13 @@
return Promise.resolve();
}
return textsecure.messaging.getAvatar(avatarPath).then((avatar) => {
return textsecure.messaging.getAvatar(avatarPath).then(avatar => {
const key = this.get('profileKey');
if (!key) {
return Promise.resolve();
}
// decrypt
return textsecure.crypto.decryptProfile(avatar, key).then((decrypted) => {
return textsecure.crypto.decryptProfile(avatar, key).then(decrypted => {
// set
this.set({
profileAvatar: {
@ -1125,9 +1212,11 @@
const first = attachments[0];
const { thumbnail, contentType } = first;
return thumbnail ||
return (
thumbnail ||
Signal.Util.GoogleChrome.isImageTypeSupported(contentType) ||
Signal.Util.GoogleChrome.isVideoTypeSupported(contentType);
Signal.Util.GoogleChrome.isVideoTypeSupported(contentType)
);
},
forceRender(message) {
message.trigger('change', message);
@ -1163,14 +1252,18 @@
return false;
}
if (!Signal.Util.GoogleChrome.isImageTypeSupported(first.contentType) &&
!Signal.Util.GoogleChrome.isVideoTypeSupported(first.contentType)) {
if (
!Signal.Util.GoogleChrome.isImageTypeSupported(first.contentType) &&
!Signal.Util.GoogleChrome.isVideoTypeSupported(first.contentType)
) {
return false;
}
const collection = new Whisper.MessageCollection();
await collection.fetchSentAt(id);
const queryMessage = collection.find(m => this.doesMessageMatch(id, author, m));
const queryMessage = collection.find(m =>
this.doesMessageMatch(id, author, m)
);
if (!queryMessage) {
return false;
@ -1206,8 +1299,10 @@
return;
}
if (!Signal.Util.GoogleChrome.isImageTypeSupported(first.contentType) &&
!Signal.Util.GoogleChrome.isVideoTypeSupported(first.contentType)) {
if (
!Signal.Util.GoogleChrome.isImageTypeSupported(first.contentType) &&
!Signal.Util.GoogleChrome.isVideoTypeSupported(first.contentType)
) {
return;
}
@ -1267,7 +1362,7 @@
async processQuotes(messages) {
const lookup = this.makeMessagesLookup(messages);
const promises = messages.map(async (message) => {
const promises = messages.map(async message => {
const { quote } = message.attributes;
if (!quote) {
return;
@ -1350,11 +1445,16 @@
}
const members = this.get('members') || [];
const promises = members.map(number =>
ConversationController.getOrCreateAndWait(number, 'private'));
ConversationController.getOrCreateAndWait(number, 'private')
);
return Promise.all(promises).then((contacts) => {
_.forEach(contacts, (contact) => {
this.listenTo(contact, 'change:verified', this.onMemberVerifiedChange);
return Promise.all(promises).then(contacts => {
_.forEach(contacts, contact => {
this.listenTo(
contact,
'change:verified',
this.onMemberVerifiedChange
);
});
this.contactCollection.reset(contacts);
@ -1362,17 +1462,19 @@
},
destroyMessages() {
this.messageCollection.fetch({
this.messageCollection
.fetch({
index: {
// 'conversation' index on [conversationId, received_at]
name: 'conversation',
lower: [this.id],
upper: [this.id, Number.MAX_VALUE],
},
}).then(() => {
})
.then(() => {
const { models } = this.messageCollection;
this.messageCollection.reset([]);
_.each(models, (message) => {
_.each(models, message => {
message.destroy();
});
this.save({
@ -1460,10 +1562,9 @@
this.revokeAvatarUrl();
const avatar = this.get('avatar') || this.get('profileAvatar');
if (avatar) {
this.avatarUrl = URL.createObjectURL(new Blob(
[avatar.data],
{ type: avatar.contentType }
));
this.avatarUrl = URL.createObjectURL(
new Blob([avatar.data], { type: avatar.contentType })
);
} else {
this.avatarUrl = null;
}
@ -1507,7 +1608,7 @@
},
getNotificationIcon() {
return new Promise((resolve) => {
return new Promise(resolve => {
const avatar = this.getAvatar();
if (avatar.url) {
resolve(avatar.url);
@ -1523,8 +1624,11 @@
}
const conversationId = this.id;
return ConversationController.getOrCreateAndWait(message.get('source'), 'private')
.then(sender => sender.getNotificationIcon().then((iconUrl) => {
return ConversationController.getOrCreateAndWait(
message.get('source'),
'private'
).then(sender =>
sender.getNotificationIcon().then(iconUrl => {
console.log('adding notification');
Whisper.Notifications.add({
title: sender.getTitle(),
@ -1534,7 +1638,8 @@
conversationId,
messageId: message.id,
});
}));
})
);
},
hashCode() {
if (this.hash === undefined) {
@ -1545,7 +1650,7 @@
let hash = 0;
for (let i = 0; i < string.length; i += 1) {
// eslint-disable-next-line no-bitwise
hash = ((hash << 5) - hash) + string.charCodeAt(i);
hash = (hash << 5) - hash + string.charCodeAt(i);
// eslint-disable-next-line no-bitwise
hash &= hash; // Convert to 32bit integer
}
@ -1566,9 +1671,17 @@
},
destroyAll() {
return Promise.all(this.models.map(m => new Promise((resolve, reject) => {
m.destroy().then(resolve).fail(reject);
})));
return Promise.all(
this.models.map(
m =>
new Promise((resolve, reject) => {
m
.destroy()
.then(resolve)
.fail(reject);
})
)
);
},
search(providedQuery) {
@ -1578,7 +1691,7 @@
const lastCharCode = query.charCodeAt(query.length - 1);
const nextChar = String.fromCharCode(lastCharCode + 1);
const upper = query.slice(0, -1) + nextChar;
return new Promise((resolve) => {
return new Promise(resolve => {
this.fetch({
index: {
name: 'search', // 'search' index on tokens array
@ -1593,7 +1706,7 @@
},
fetchAlphabetical() {
return new Promise((resolve) => {
return new Promise(resolve => {
this.fetch({
index: {
name: 'search', // 'search' index on tokens array
@ -1604,7 +1717,7 @@
},
fetchGroups(number) {
return new Promise((resolve) => {
return new Promise(resolve => {
this.fetch({
index: {
name: 'group',
@ -1623,7 +1736,7 @@
storeName: 'conversations',
model: Whisper.Conversation,
fetchGroups(number) {
return new Promise((resolve) => {
return new Promise(resolve => {
this.fetch({
index: {
name: 'group',
@ -1633,4 +1746,4 @@
});
},
});
}());
})();

View File

@ -32,10 +32,13 @@
this.on('unload', this.unload);
this.setToExpire();
this.VOICE_FLAG = textsecure.protobuf.AttachmentPointer.Flags.VOICE_MESSAGE;
this.VOICE_FLAG =
textsecure.protobuf.AttachmentPointer.Flags.VOICE_MESSAGE;
},
idForLogging() {
return `${this.get('source')}.${this.get('sourceDevice')} ${this.get('sent_at')}`;
return `${this.get('source')}.${this.get('sourceDevice')} ${this.get(
'sent_at'
)}`;
},
defaults() {
return {
@ -56,12 +59,13 @@
return !!(this.get('flags') & flag);
},
isExpirationTimerUpdate() {
const flag = textsecure.protobuf.DataMessage.Flags.EXPIRATION_TIMER_UPDATE;
const flag =
textsecure.protobuf.DataMessage.Flags.EXPIRATION_TIMER_UPDATE;
// eslint-disable-next-line no-bitwise
return !!(this.get('flags') & flag);
},
isGroupUpdate() {
return !!(this.get('group_update'));
return !!this.get('group_update');
},
isIncoming() {
return this.get('type') === 'incoming';
@ -116,7 +120,10 @@
messages.push(i18n('titleIsNow', groupUpdate.name));
}
if (groupUpdate.joined && groupUpdate.joined.length) {
const names = _.map(groupUpdate.joined, this.getNameForNumber.bind(this));
const names = _.map(
groupUpdate.joined,
this.getNameForNumber.bind(this)
);
if (names.length > 1) {
messages.push(i18n('multipleJoinedTheGroup', names.join(', ')));
} else {
@ -186,7 +193,7 @@
}
const quote = this.get('quote');
const attachments = (quote && quote.attachments) || [];
attachments.forEach((attachment) => {
attachments.forEach(attachment => {
if (attachment.thumbnail && attachment.thumbnail.objectUrl) {
URL.revokeObjectURL(attachment.thumbnail.objectUrl);
// eslint-disable-next-line no-param-reassign
@ -269,7 +276,8 @@
return {
attachments: (quote.attachments || []).map(attachment =>
this.processAttachment(attachment, objectUrl)),
this.processAttachment(attachment, objectUrl)
),
authorColor,
authorProfileName,
authorTitle,
@ -342,7 +350,8 @@
send(promise) {
this.trigger('pending');
return promise.then((result) => {
return promise
.then(result => {
const now = Date.now();
this.trigger('done');
if (result.dataMessage) {
@ -355,7 +364,8 @@
expirationStartTimestamp: now,
});
this.sendSyncMessage();
}).catch((result) => {
})
.catch(result => {
const now = Date.now();
this.trigger('done');
if (result.dataMessage) {
@ -383,12 +393,14 @@
});
promises.push(this.sendSyncMessage());
}
promises = promises.concat(_.map(result.errors, (error) => {
promises = promises.concat(
_.map(result.errors, error => {
if (error.name === 'OutgoingIdentityKeyError') {
const c = ConversationController.get(error.number);
promises.push(c.getProfiles());
}
}));
})
);
}
return Promise.all(promises).then(() => {
@ -423,12 +435,14 @@
if (this.get('synced') || !dataMessage) {
return Promise.resolve();
}
return textsecure.messaging.sendSyncMessage(
return textsecure.messaging
.sendSyncMessage(
dataMessage,
this.get('sent_at'),
this.get('destination'),
this.get('expirationStartTimestamp')
).then(() => {
)
.then(() => {
this.save({ synced: true, dataMessage: null });
});
});
@ -440,17 +454,19 @@
if (!(errors instanceof Array)) {
errors = [errors];
}
errors.forEach((e) => {
errors.forEach(e => {
console.log(
'Message.saveErrors:',
e && e.reason ? e.reason : null,
e && e.stack ? e.stack : e
);
});
errors = errors.map((e) => {
if (e.constructor === Error ||
errors = errors.map(e => {
if (
e.constructor === Error ||
e.constructor === TypeError ||
e.constructor === ReferenceError) {
e.constructor === ReferenceError
) {
return _.pick(e, 'name', 'message', 'code', 'number', 'reason');
}
return e;
@ -463,17 +479,19 @@
hasNetworkError() {
const error = _.find(
this.get('errors'),
e => (e.name === 'MessageError' ||
e =>
e.name === 'MessageError' ||
e.name === 'OutgoingMessageError' ||
e.name === 'SendMessageNetworkError' ||
e.name === 'SignedPreKeyRotationError')
e.name === 'SignedPreKeyRotationError'
);
return !!error;
},
removeOutgoingErrors(number) {
const errors = _.partition(
this.get('errors'),
e => e.number === number &&
e =>
e.number === number &&
(e.name === 'MessageError' ||
e.name === 'OutgoingMessageError' ||
e.name === 'SendMessageNetworkError' ||
@ -484,11 +502,13 @@
return errors[0][0];
},
isReplayableError(e) {
return (e.name === 'MessageError' ||
return (
e.name === 'MessageError' ||
e.name === 'OutgoingMessageError' ||
e.name === 'SendMessageNetworkError' ||
e.name === 'SignedPreKeyRotationError' ||
e.name === 'OutgoingIdentityKeyError');
e.name === 'OutgoingIdentityKeyError'
);
},
resend(number) {
const error = this.removeOutgoingErrors(number);
@ -513,7 +533,9 @@
const GROUP_TYPES = textsecure.protobuf.GroupContext.Type;
const conversation = ConversationController.get(conversationId);
return conversation.queueJob(() => new Promise((resolve) => {
return conversation.queueJob(
() =>
new Promise(resolve => {
const now = new Date().getTime();
let attributes = { type: 'private' };
if (dataMessage.group) {
@ -528,13 +550,15 @@
groupId: dataMessage.group.id,
name: dataMessage.group.name,
avatar: dataMessage.group.avatar,
members: _.union(dataMessage.group.members, conversation.get('members')),
members: _.union(
dataMessage.group.members,
conversation.get('members')
),
};
groupUpdate = conversation.changedAttributes(_.pick(
dataMessage.group,
'name',
'avatar'
)) || {};
groupUpdate =
conversation.changedAttributes(
_.pick(dataMessage.group, 'name', 'avatar')
) || {};
const difference = _.difference(
attributes.members,
conversation.get('members')
@ -553,7 +577,10 @@
} else {
groupUpdate = { left: source };
}
attributes.members = _.without(conversation.get('members'), source);
attributes.members = _.without(
conversation.get('members'),
source
);
}
if (groupUpdate !== null) {
@ -574,10 +601,15 @@
schemaVersion: dataMessage.schemaVersion,
});
if (type === 'outgoing') {
const receipts = Whisper.DeliveryReceipts.forMessage(conversation, message);
receipts.forEach(() => message.set({
const receipts = Whisper.DeliveryReceipts.forMessage(
conversation,
message
);
receipts.forEach(() =>
message.set({
delivered: (message.get('delivered') || 0) + 1,
}));
})
);
}
attributes.active_at = now;
conversation.set(attributes);
@ -611,15 +643,19 @@
if (!message.isEndSession() && !message.isGroupUpdate()) {
if (dataMessage.expireTimer) {
if (dataMessage.expireTimer !== conversation.get('expireTimer')) {
if (
dataMessage.expireTimer !== conversation.get('expireTimer')
) {
conversation.updateExpirationTimer(
dataMessage.expireTimer, source,
dataMessage.expireTimer,
source,
message.get('received_at')
);
}
} else if (conversation.get('expireTimer')) {
conversation.updateExpirationTimer(
null, source,
null,
source,
message.get('received_at')
);
}
@ -627,8 +663,14 @@
if (type === 'incoming') {
const readSync = Whisper.ReadSyncs.forMessage(message);
if (readSync) {
if (message.get('expireTimer') && !message.get('expirationStartTimestamp')) {
message.set('expirationStartTimestamp', readSync.get('read_at'));
if (
message.get('expireTimer') &&
!message.get('expirationStartTimestamp')
) {
message.set(
'expirationStartTimestamp',
readSync.get('read_at')
);
}
}
if (readSync || message.isExpirationTimerUpdate()) {
@ -638,12 +680,18 @@
// know about.
Whisper.ReadSyncs.notifyConversation(message);
} else {
conversation.set('unreadCount', conversation.get('unreadCount') + 1);
conversation.set(
'unreadCount',
conversation.get('unreadCount') + 1
);
}
}
if (type === 'outgoing') {
const reads = Whisper.ReadReceipts.forMessage(conversation, message);
const reads = Whisper.ReadReceipts.forMessage(
conversation,
message
);
if (reads.length) {
const readBy = reads.map(receipt => receipt.get('reader'));
message.set({
@ -655,7 +703,10 @@
}
const conversationTimestamp = conversation.get('timestamp');
if (!conversationTimestamp || message.get('sent_at') > conversationTimestamp) {
if (
!conversationTimestamp ||
message.get('sent_at') > conversationTimestamp
) {
conversation.set({
lastMessage: message.getNotificationText(),
timestamp: message.get('sent_at'),
@ -672,15 +723,20 @@
ConversationController.getOrCreateAndWait(
source,
'private'
).then((sender) => {
).then(sender => {
sender.setProfileKey(profileKey);
});
}
}
const handleError = (error) => {
const handleError = error => {
const errorForLog = error && error.stack ? error.stack : error;
console.log('handleDataMessage', message.idForLogging(), 'error:', errorForLog);
console.log(
'handleDataMessage',
message.idForLogging(),
'error:',
errorForLog
);
return resolve();
};
@ -695,11 +751,14 @@
// line's trigger() call, we might have marked all messages unread in the
// database. This message might already be read!
const previousUnread = message.get('unread');
return message.fetch().then(() => {
return message.fetch().then(
() => {
try {
if (previousUnread !== message.get('unread')) {
console.log('Caught race condition on new message read state! ' +
'Manually starting timers.');
console.log(
'Caught race condition on new message read state! ' +
'Manually starting timers.'
);
// We call markRead() even though the message is already marked read
// because we need to start expiration timers, etc.
message.markRead();
@ -717,7 +776,8 @@
} catch (e) {
return handleError(e);
}
}, () => {
},
() => {
try {
console.log(
'handleDataMessage: Message',
@ -730,19 +790,23 @@
} catch (e) {
return handleError(e);
}
});
}
);
}, handleError);
}, handleError);
}));
})
);
},
markRead(readAt) {
this.unset('unread');
if (this.get('expireTimer') && !this.get('expirationStartTimestamp')) {
this.set('expirationStartTimestamp', readAt || Date.now());
}
Whisper.Notifications.remove(Whisper.Notifications.where({
Whisper.Notifications.remove(
Whisper.Notifications.where({
messageId: this.id,
}));
})
);
return new Promise((resolve, reject) => {
this.save().then(resolve, reject);
});
@ -760,7 +824,7 @@
const now = Date.now();
const start = this.get('expirationStartTimestamp');
const delta = this.get('expireTimer') * 1000;
let msFromNow = (start + delta) - now;
let msFromNow = start + delta - now;
if (msFromNow < 0) {
msFromNow = 0;
}
@ -784,7 +848,6 @@
console.log('message', this.get('sent_at'), 'expires at', expiresAt);
}
},
});
Whisper.MessageCollection = Backbone.Collection.extend({
@ -804,19 +867,29 @@
}
},
destroyAll() {
return Promise.all(this.models.map(m => new Promise((resolve, reject) => {
m.destroy().then(resolve).fail(reject);
})));
return Promise.all(
this.models.map(
m =>
new Promise((resolve, reject) => {
m
.destroy()
.then(resolve)
.fail(reject);
})
)
);
},
fetchSentAt(timestamp) {
return new Promise((resolve => this.fetch({
return new Promise(resolve =>
this.fetch({
index: {
// 'receipt' index on sent_at
name: 'receipt',
only: timestamp,
},
}).always(resolve)));
}).always(resolve)
);
},
getLoadedUnreadCount() {
@ -841,7 +914,7 @@
if (unreadCount > 0) {
startingLoadedUnread = this.getLoadedUnreadCount();
}
return new Promise((resolve) => {
return new Promise(resolve => {
let upper;
if (this.length === 0) {
// fetch the most recent messages first
@ -893,4 +966,4 @@
});
},
});
}());
})();

View File

@ -20,7 +20,9 @@ exports.autoOrientImage = (fileOrBlobOrURL, options = {}) => {
);
return new Promise((resolve, reject) => {
loadImage(fileOrBlobOrURL, (canvasOrError) => {
loadImage(
fileOrBlobOrURL,
canvasOrError => {
if (canvasOrError.type === 'error') {
const error = new Error('autoOrientImage: Failed to process image');
error.cause = canvasOrError;
@ -35,6 +37,8 @@ exports.autoOrientImage = (fileOrBlobOrURL, options = {}) => {
);
resolve(dataURL);
}, optionsWithDefaults);
},
optionsWithDefaults
);
});
};

View File

@ -23,12 +23,7 @@ const electronRemote = require('electron').remote;
const Attachment = require('./types/attachment');
const crypto = require('./crypto');
const {
dialog,
BrowserWindow,
} = electronRemote;
const { dialog, BrowserWindow } = electronRemote;
module.exports = {
getDirectoryForExport,
@ -44,7 +39,6 @@ module.exports = {
_getConversationLoggingName,
};
function stringify(object) {
// eslint-disable-next-line no-restricted-syntax
for (const key in object) {
@ -69,10 +63,12 @@ function unstringify(object) {
// eslint-disable-next-line no-restricted-syntax
for (const key in object) {
const val = object[key];
if (val &&
if (
val &&
val.type === 'ArrayBuffer' &&
val.encoding === 'base64' &&
typeof val.data === 'string') {
typeof val.data === 'string'
) {
object[key] = dcodeIO.ByteBuffer.wrap(val.data, 'base64').toArrayBuffer();
} else if (val instanceof Object) {
object[key] = unstringify(object[key]);
@ -86,7 +82,9 @@ function createOutputStream(writer) {
return {
write(string) {
// eslint-disable-next-line more/no-then
wait = wait.then(() => new Promise((resolve) => {
wait = wait.then(
() =>
new Promise(resolve => {
if (writer.write(string)) {
resolve();
return;
@ -98,7 +96,8 @@ function createOutputStream(writer) {
// We don't register for the 'error' event here, only in close(). Otherwise,
// we'll get "Possible EventEmitter memory leak detected" warnings.
}));
})
);
return wait;
},
async close() {
@ -141,7 +140,7 @@ function exportContactsAndGroups(db, fileWriter) {
stream.write('{');
_.each(storeNames, (storeName) => {
_.each(storeNames, storeName => {
// Both the readwrite permission and the multi-store transaction are required to
// keep this function working. They serve to serialize all of these transactions,
// one per store to be exported.
@ -167,7 +166,7 @@ function exportContactsAndGroups(db, fileWriter) {
reject
);
};
request.onsuccess = async (event) => {
request.onsuccess = async event => {
if (count === 0) {
console.log('cursor opened');
stream.write(`"${storeName}": [`);
@ -180,10 +179,7 @@ function exportContactsAndGroups(db, fileWriter) {
}
// Preventing base64'd images from reaching the disk, making db.json too big
const item = _.omit(
cursor.value,
['avatar', 'profileAvatar']
);
const item = _.omit(cursor.value, ['avatar', 'profileAvatar']);
const jsonString = JSON.stringify(stringify(item));
stream.write(jsonString);
@ -235,10 +231,7 @@ function importFromJsonString(db, jsonString, targetPath, options) {
groupLookup: {},
});
const {
conversationLookup,
groupLookup,
} = options;
const { conversationLookup, groupLookup } = options;
const result = {
fullImport: true,
};
@ -269,7 +262,7 @@ function importFromJsonString(db, jsonString, targetPath, options) {
console.log('Importing to these stores:', storeNames.join(', '));
let finished = false;
const finish = (via) => {
const finish = via => {
console.log('non-messages import done via', via);
if (finished) {
resolve(result);
@ -287,7 +280,7 @@ function importFromJsonString(db, jsonString, targetPath, options) {
};
transaction.oncomplete = finish.bind(null, 'transaction complete');
_.each(storeNames, (storeName) => {
_.each(storeNames, storeName => {
console.log('Importing items for store', storeName);
if (!importObject[storeName].length) {
@ -316,7 +309,7 @@ function importFromJsonString(db, jsonString, targetPath, options) {
}
};
_.each(importObject[storeName], (toAdd) => {
_.each(importObject[storeName], toAdd => {
toAdd = unstringify(toAdd);
const haveConversationAlready =
@ -365,7 +358,7 @@ function createDirectory(parent, name) {
return;
}
fs.mkdir(targetDir, (error) => {
fs.mkdir(targetDir, error => {
if (error) {
reject(error);
return;
@ -377,7 +370,7 @@ function createDirectory(parent, name) {
}
function createFileAndWriter(parent, name) {
return new Promise((resolve) => {
return new Promise(resolve => {
const sanitized = _sanitizeFileName(name);
const targetPath = path.join(parent, sanitized);
const options = {
@ -430,7 +423,6 @@ function _trimFileName(filename) {
return `${name.join('.').slice(0, 24)}.${extension}`;
}
function _getExportAttachmentFileName(message, index, attachment) {
if (attachment.fileName) {
return _trimFileName(attachment.fileName);
@ -440,7 +432,9 @@ function _getExportAttachmentFileName(message, index, attachment) {
if (attachment.contentType) {
const components = attachment.contentType.split('/');
name += `.${components.length > 1 ? components[1] : attachment.contentType}`;
name += `.${
components.length > 1 ? components[1] : attachment.contentType
}`;
}
return name;
@ -477,14 +471,11 @@ async function readAttachment(dir, attachment, name, options) {
}
async function writeThumbnail(attachment, options) {
const {
dir,
const { dir, message, index, key, newKey } = options;
const filename = `${_getAnonymousAttachmentFileName(
message,
index,
key,
newKey,
} = options;
const filename = `${_getAnonymousAttachmentFileName(message, index)}-thumbnail`;
index
)}-thumbnail`;
const target = path.join(dir, filename);
const { thumbnail } = attachment;
@ -504,26 +495,28 @@ async function writeThumbnails(rawQuotedAttachments, options) {
const { name } = options;
const { loadAttachmentData } = Signal.Migrations;
const promises = rawQuotedAttachments.map(async (attachment) => {
const promises = rawQuotedAttachments.map(async attachment => {
if (!attachment || !attachment.thumbnail || !attachment.thumbnail.path) {
return attachment;
}
return Object.assign(
{},
attachment,
{ thumbnail: await loadAttachmentData(attachment.thumbnail) }
);
return Object.assign({}, attachment, {
thumbnail: await loadAttachmentData(attachment.thumbnail),
});
});
const attachments = await Promise.all(promises);
try {
await Promise.all(_.map(
attachments,
(attachment, index) => writeThumbnail(attachment, Object.assign({}, options, {
await Promise.all(
_.map(attachments, (attachment, index) =>
writeThumbnail(
attachment,
Object.assign({}, options, {
index,
}))
));
})
)
)
);
} catch (error) {
console.log(
'writeThumbnails: error exporting conversation',
@ -536,13 +529,7 @@ async function writeThumbnails(rawQuotedAttachments, options) {
}
async function writeAttachment(attachment, options) {
const {
dir,
message,
index,
key,
newKey,
} = options;
const { dir, message, index, key, newKey } = options;
const filename = _getAnonymousAttachmentFileName(message, index);
const target = path.join(dir, filename);
if (!Attachment.hasData(attachment)) {
@ -562,11 +549,13 @@ async function writeAttachments(rawAttachments, options) {
const { loadAttachmentData } = Signal.Migrations;
const attachments = await Promise.all(rawAttachments.map(loadAttachmentData));
const promises = _.map(
attachments,
(attachment, index) => writeAttachment(attachment, Object.assign({}, options, {
const promises = _.map(attachments, (attachment, index) =>
writeAttachment(
attachment,
Object.assign({}, options, {
index,
}))
})
)
);
try {
await Promise.all(promises);
@ -582,12 +571,7 @@ async function writeAttachments(rawAttachments, options) {
}
async function writeEncryptedAttachment(target, data, options = {}) {
const {
key,
newKey,
filename,
dir,
} = options;
const { key, newKey, filename, dir } = options;
if (fs.existsSync(target)) {
if (newKey) {
@ -613,13 +597,7 @@ function _sanitizeFileName(filename) {
async function exportConversation(db, conversation, options) {
options = options || {};
const {
name,
dir,
attachmentsDir,
key,
newKey,
} = options;
const { name, dir, attachmentsDir, key, newKey } = options;
if (!name) {
throw new Error('Need a name!');
}
@ -670,7 +648,7 @@ async function exportConversation(db, conversation, options) {
reject
);
};
request.onsuccess = async (event) => {
request.onsuccess = async event => {
const cursor = event.target.result;
if (cursor) {
const message = cursor.value;
@ -688,13 +666,12 @@ async function exportConversation(db, conversation, options) {
// eliminate attachment data from the JSON, since it will go to disk
// Note: this is for legacy messages only, which stored attachment data in the db
message.attachments = _.map(
attachments,
attachment => _.omit(attachment, ['data'])
message.attachments = _.map(attachments, attachment =>
_.omit(attachment, ['data'])
);
// completely drop any attachments in messages cached in error objects
// TODO: move to lodash. Sadly, a number of the method signatures have changed!
message.errors = _.map(message.errors, (error) => {
message.errors = _.map(message.errors, error => {
if (error && error.args) {
error.args = [];
}
@ -709,7 +686,8 @@ async function exportConversation(db, conversation, options) {
console.log({ backupMessage: message });
if (attachments && attachments.length > 0) {
const exportAttachments = () => writeAttachments(attachments, {
const exportAttachments = () =>
writeAttachments(attachments, {
dir: attachmentsDir,
name,
message,
@ -723,7 +701,8 @@ async function exportConversation(db, conversation, options) {
const quoteThumbnails = message.quote && message.quote.attachments;
if (quoteThumbnails && quoteThumbnails.length > 0) {
const exportQuoteThumbnails = () => writeThumbnails(quoteThumbnails, {
const exportQuoteThumbnails = () =>
writeThumbnails(quoteThumbnails, {
dir: attachmentsDir,
name,
message,
@ -739,11 +718,7 @@ async function exportConversation(db, conversation, options) {
cursor.continue();
} else {
try {
await Promise.all([
stream.write(']}'),
promiseChain,
stream.close(),
]);
await Promise.all([stream.write(']}'), promiseChain, stream.close()]);
} catch (error) {
console.log(
'exportConversation: error exporting conversation',
@ -791,12 +766,7 @@ function _getConversationLoggingName(conversation) {
function exportConversations(db, options) {
options = options || {};
const {
messagesDir,
attachmentsDir,
key,
newKey,
} = options;
const { messagesDir, attachmentsDir, key, newKey } = options;
if (!messagesDir) {
return Promise.reject(new Error('Need a messages directory!'));
@ -828,7 +798,7 @@ function exportConversations(db, options) {
reject
);
};
request.onsuccess = async (event) => {
request.onsuccess = async event => {
const cursor = event.target.result;
if (cursor && cursor.value) {
const conversation = cursor.value;
@ -873,7 +843,7 @@ function getDirectory(options) {
buttonLabel: options.buttonLabel,
};
dialog.showOpenDialog(browserWindow, dialogOptions, (directory) => {
dialog.showOpenDialog(browserWindow, dialogOptions, directory => {
if (!directory || !directory[0]) {
const error = new Error('Error choosing directory');
error.name = 'ChooseError';
@ -940,7 +910,7 @@ async function saveAllMessages(db, rawMessages) {
return new Promise((resolve, reject) => {
let finished = false;
const finish = (via) => {
const finish = via => {
console.log('messages done saving via', via);
if (finished) {
resolve();
@ -962,7 +932,7 @@ async function saveAllMessages(db, rawMessages) {
const { conversationId } = messages[0];
let count = 0;
_.forEach(messages, (message) => {
_.forEach(messages, message => {
const request = store.put(message, message.id);
request.onsuccess = () => {
count += 1;
@ -997,11 +967,7 @@ async function importConversation(db, dir, options) {
options = options || {};
_.defaults(options, { messageLookup: {} });
const {
messageLookup,
attachmentsDir,
key,
} = options;
const { messageLookup, attachmentsDir, key } = options;
let conversationId = 'unknown';
let total = 0;
@ -1018,11 +984,13 @@ async function importConversation(db, dir, options) {
const json = JSON.parse(contents);
if (json.messages && json.messages.length) {
conversationId = `[REDACTED]${(json.messages[0].conversationId || '').slice(-3)}`;
conversationId = `[REDACTED]${(json.messages[0].conversationId || '').slice(
-3
)}`;
}
total = json.messages.length;
const messages = _.filter(json.messages, (message) => {
const messages = _.filter(json.messages, message => {
message = unstringify(message);
if (messageLookup[getMessageKey(message)]) {
@ -1031,7 +999,9 @@ async function importConversation(db, dir, options) {
}
const hasAttachments = message.attachments && message.attachments.length;
const hasQuotedAttachments = message.quote && message.quote.attachments &&
const hasQuotedAttachments =
message.quote &&
message.quote.attachments &&
message.quote.attachments.length > 0;
if (hasAttachments || hasQuotedAttachments) {
@ -1039,8 +1009,8 @@ async function importConversation(db, dir, options) {
const getName = attachmentsDir
? _getAnonymousAttachmentFileName
: _getExportAttachmentFileName;
const parentDir = attachmentsDir ||
path.join(dir, message.received_at.toString());
const parentDir =
attachmentsDir || path.join(dir, message.received_at.toString());
await loadAttachments(parentDir, getName, {
message,
@ -1075,12 +1045,13 @@ async function importConversations(db, dir, options) {
const contents = await getDirContents(dir);
let promiseChain = Promise.resolve();
_.forEach(contents, (conversationDir) => {
_.forEach(contents, conversationDir => {
if (!fs.statSync(conversationDir).isDirectory()) {
return;
}
const loadConversation = () => importConversation(db, conversationDir, options);
const loadConversation = () =>
importConversation(db, conversationDir, options);
// eslint-disable-next-line more/no-then
promiseChain = promiseChain.then(loadConversation);
@ -1142,7 +1113,7 @@ function assembleLookup(db, storeName, keyFunction) {
reject
);
};
request.onsuccess = (event) => {
request.onsuccess = event => {
const cursor = event.target.result;
if (cursor && cursor.value) {
lookup[keyFunction(cursor.value)] = true;
@ -1175,7 +1146,7 @@ function createZip(zipDir, targetDir) {
resolve(target);
});
archive.on('warning', (error) => {
archive.on('warning', error => {
console.log(`Archive generation warning: ${error.stack}`);
});
archive.on('error', reject);
@ -1247,10 +1218,13 @@ async function exportToDirectory(directory, options) {
const attachmentsDir = await createDirectory(directory, 'attachments');
await exportContactAndGroupsToFile(db, stagingDir);
await exportConversations(db, Object.assign({}, options, {
await exportConversations(
db,
Object.assign({}, options, {
messagesDir: stagingDir,
attachmentsDir,
}));
})
);
const zip = await createZip(encryptionDir, stagingDir);
await encryptFile(zip, path.join(directory, 'messages.zip'), options);
@ -1302,7 +1276,9 @@ async function importFromDirectory(directory, options) {
if (fs.existsSync(zipPath)) {
// we're in the world of an encrypted, zipped backup
if (!options.key) {
throw new Error('Importing an encrypted backup; decryption key is required!');
throw new Error(
'Importing an encrypted backup; decryption key is required!'
);
}
let stagingDir;

View File

@ -19,8 +19,15 @@ async function encryptSymmetric(key, plaintext) {
const cipherKey = await _hmac_SHA256(key, nonce);
const macKey = await _hmac_SHA256(key, cipherKey);
const cipherText = await _encrypt_aes256_CBC_PKCSPadding(cipherKey, iv, plaintext);
const mac = _getFirstBytes(await _hmac_SHA256(macKey, cipherText), MAC_LENGTH);
const cipherText = await _encrypt_aes256_CBC_PKCSPadding(
cipherKey,
iv,
plaintext
);
const mac = _getFirstBytes(
await _hmac_SHA256(macKey, cipherText),
MAC_LENGTH
);
return _concatData([nonce, cipherText, mac]);
}
@ -39,9 +46,14 @@ async function decryptSymmetric(key, data) {
const cipherKey = await _hmac_SHA256(key, nonce);
const macKey = await _hmac_SHA256(key, cipherKey);
const ourMac = _getFirstBytes(await _hmac_SHA256(macKey, cipherText), MAC_LENGTH);
const ourMac = _getFirstBytes(
await _hmac_SHA256(macKey, cipherText),
MAC_LENGTH
);
if (!constantTimeEqual(theirMac, ourMac)) {
throw new Error('decryptSymmetric: Failed to decrypt; MAC verification failed');
throw new Error(
'decryptSymmetric: Failed to decrypt; MAC verification failed'
);
}
return _decrypt_aes256_CBC_PKCSPadding(cipherKey, iv, cipherText);
@ -61,7 +73,6 @@ function constantTimeEqual(left, right) {
return result === 0;
}
async function _hmac_SHA256(key, data) {
const extractable = false;
const cryptoKey = await window.crypto.subtle.importKey(
@ -72,7 +83,11 @@ async function _hmac_SHA256(key, data) {
['sign']
);
return window.crypto.subtle.sign({ name: 'HMAC', hash: 'SHA-256' }, cryptoKey, data);
return window.crypto.subtle.sign(
{ name: 'HMAC', hash: 'SHA-256' },
cryptoKey,
data
);
}
async function _encrypt_aes256_CBC_PKCSPadding(key, iv, data) {
@ -101,7 +116,6 @@ async function _decrypt_aes256_CBC_PKCSPadding(key, iv, data) {
return window.crypto.subtle.decrypt({ name: 'AES-CBC', iv }, cryptoKey, data);
}
function _getRandomBytes(n) {
const bytes = new Uint8Array(n);
window.crypto.getRandomValues(bytes);

View File

@ -6,14 +6,12 @@
const { isObject, isNumber } = require('lodash');
exports.open = (name, version, { onUpgradeNeeded } = {}) => {
const request = indexedDB.open(name, version);
return new Promise((resolve, reject) => {
request.onblocked = () =>
reject(new Error('Database blocked'));
request.onblocked = () => reject(new Error('Database blocked'));
request.onupgradeneeded = (event) => {
request.onupgradeneeded = event => {
const hasRequestedSpecificVersion = isNumber(version);
if (!hasRequestedSpecificVersion) {
return;
@ -26,14 +24,17 @@ exports.open = (name, version, { onUpgradeNeeded } = {}) => {
return;
}
reject(new Error('Database upgrade required:' +
` oldVersion: ${oldVersion}, newVersion: ${newVersion}`));
reject(
new Error(
'Database upgrade required:' +
` oldVersion: ${oldVersion}, newVersion: ${newVersion}`
)
);
};
request.onerror = event =>
reject(event.target.error);
request.onerror = event => reject(event.target.error);
request.onsuccess = (event) => {
request.onsuccess = event => {
const connection = event.target.result;
resolve(connection);
};
@ -47,7 +48,7 @@ exports.completeTransaction = transaction =>
transaction.addEventListener('complete', () => resolve());
});
exports.getVersion = async (name) => {
exports.getVersion = async name => {
const connection = await exports.open(name);
const { version } = connection;
connection.close();
@ -61,9 +62,7 @@ exports.getCount = async ({ store } = {}) => {
const request = store.count();
return new Promise((resolve, reject) => {
request.onerror = event =>
reject(event.target.error);
request.onsuccess = event =>
resolve(event.target.result);
request.onerror = event => reject(event.target.error);
request.onsuccess = event => resolve(event.target.result);
});
};

View File

@ -18,7 +18,6 @@ const Message = require('./types/message');
const { deferredToPromise } = require('./deferred_to_promise');
const { sleep } = require('./sleep');
// See: https://en.wikipedia.org/wiki/Fictitious_telephone_number#North_American_Numbering_Plan
const SENDER_ID = '+12126647665';
@ -27,8 +26,10 @@ exports.createConversation = async ({
numMessages,
WhisperMessage,
} = {}) => {
if (!isObject(ConversationController) ||
!isFunction(ConversationController.getOrCreateAndWait)) {
if (
!isObject(ConversationController) ||
!isFunction(ConversationController.getOrCreateAndWait)
) {
throw new TypeError("'ConversationController' is required");
}
@ -40,8 +41,10 @@ exports.createConversation = async ({
throw new TypeError("'WhisperMessage' is required");
}
const conversation =
await ConversationController.getOrCreateAndWait(SENDER_ID, 'private');
const conversation = await ConversationController.getOrCreateAndWait(
SENDER_ID,
'private'
);
conversation.set({
active_at: Date.now(),
unread: numMessages,
@ -50,13 +53,15 @@ exports.createConversation = async ({
const conversationId = conversation.get('id');
await Promise.all(range(0, numMessages).map(async (index) => {
await Promise.all(
range(0, numMessages).map(async index => {
await sleep(index * 100);
console.log(`Create message ${index + 1}`);
const messageAttributes = await createRandomMessage({ conversationId });
const message = new WhisperMessage(messageAttributes);
return deferredToPromise(message.save());
}));
})
);
};
const SAMPLE_MESSAGES = [
@ -88,7 +93,8 @@ const createRandomMessage = async ({ conversationId } = {}) => {
const hasAttachment = Math.random() <= ATTACHMENT_SAMPLE_RATE;
const attachments = hasAttachment
? [await createRandomInMemoryAttachment()] : [];
? [await createRandomInMemoryAttachment()]
: [];
const type = sample(['incoming', 'outgoing']);
const commonProperties = {
attachments,
@ -145,7 +151,7 @@ const createFileEntry = fileName => ({
fileName,
contentType: fileNameToContentType(fileName),
});
const fileNameToContentType = (fileName) => {
const fileNameToContentType = fileName => {
const fileExtension = path.extname(fileName).toLowerCase();
switch (fileExtension) {
case '.gif':

View File

@ -3,7 +3,6 @@
const FormData = require('form-data');
const got = require('got');
const BASE_URL = 'https://debuglogs.org';
// Workaround: Submitting `FormData` using native `FormData::submit` procedure
@ -12,7 +11,7 @@ const BASE_URL = 'https://debuglogs.org';
// https://github.com/sindresorhus/got/pull/466
const submitFormData = (form, url) =>
new Promise((resolve, reject) => {
form.submit(url, (error) => {
form.submit(url, error => {
if (error) {
return reject(error);
}
@ -22,7 +21,7 @@ const submitFormData = (form, url) =>
});
// upload :: String -> Promise URL
exports.upload = async (content) => {
exports.upload = async content => {
const signedForm = await got.get(BASE_URL, { json: true });
const { fields, url } = signedForm.body;

View File

@ -2,11 +2,10 @@ const addUnhandledErrorHandler = require('electron-unhandled');
const Errors = require('./types/errors');
// addHandler :: Unit -> Unit
exports.addHandler = () => {
addUnhandledErrorHandler({
logger: (error) => {
logger: error => {
console.error(
'Uncaught error or unhandled promise rejection:',
Errors.toLogFormat(error)

View File

@ -11,7 +11,9 @@ exports.setup = (locale, messages) => {
function getMessage(key, substitutions) {
const entry = messages[key];
if (!entry) {
console.error(`i18n: Attempted to get translation for nonexistent key '${key}'`);
console.error(
`i18n: Attempted to get translation for nonexistent key '${key}'`
);
return '';
}

View File

@ -2,7 +2,6 @@
const EventEmitter = require('events');
const POLL_INTERVAL_MS = 5 * 1000;
const IDLE_THRESHOLD_MS = 20;
@ -35,14 +34,17 @@ class IdleDetector extends EventEmitter {
_scheduleNextCallback() {
this._clearScheduledCallbacks();
this.handle = window.requestIdleCallback((deadline) => {
this.handle = window.requestIdleCallback(deadline => {
const { didTimeout } = deadline;
const timeRemaining = deadline.timeRemaining();
const isIdle = timeRemaining >= IDLE_THRESHOLD_MS;
if (isIdle || didTimeout) {
this.emit('idle', { timestamp: Date.now(), didTimeout, timeRemaining });
}
this.timeoutId = setTimeout(() => this._scheduleNextCallback(), POLL_INTERVAL_MS);
this.timeoutId = setTimeout(
() => this._scheduleNextCallback(),
POLL_INTERVAL_MS
);
});
}
}

View File

@ -7,7 +7,7 @@ function createLink(url, text, attrs = {}) {
const html = [];
html.push('<a ');
html.push(`href="${url}"`);
Object.keys(attrs).forEach((key) => {
Object.keys(attrs).forEach(key => {
html.push(` ${key}="${attrs[key]}"`);
});
html.push('>');
@ -23,7 +23,7 @@ module.exports = (text, attrs = {}) => {
const result = [];
let last = 0;
matchData.forEach((match) => {
matchData.forEach(match => {
if (last < match.index) {
result.push(text.slice(last, match.index));
}

View File

@ -6,20 +6,13 @@
/* global IDBKeyRange */
const {
isFunction,
isNumber,
isObject,
isString,
last,
} = require('lodash');
const { isFunction, isNumber, isObject, isString, last } = require('lodash');
const database = require('./database');
const Message = require('./types/message');
const settings = require('./settings');
const { deferredToPromise } = require('./deferred_to_promise');
const MESSAGES_STORE_NAME = 'messages';
exports.processNext = async ({
@ -29,12 +22,16 @@ exports.processNext = async ({
upgradeMessageSchema,
} = {}) => {
if (!isFunction(BackboneMessage)) {
throw new TypeError("'BackboneMessage' (Whisper.Message) constructor is required");
throw new TypeError(
"'BackboneMessage' (Whisper.Message) constructor is required"
);
}
if (!isFunction(BackboneMessageCollection)) {
throw new TypeError("'BackboneMessageCollection' (Whisper.MessageCollection)" +
' constructor is required');
throw new TypeError(
"'BackboneMessageCollection' (Whisper.MessageCollection)" +
' constructor is required'
);
}
if (!isNumber(numMessagesPerBatch)) {
@ -48,16 +45,18 @@ exports.processNext = async ({
const startTime = Date.now();
const fetchStartTime = Date.now();
const messagesRequiringSchemaUpgrade =
await _fetchMessagesRequiringSchemaUpgrade({
const messagesRequiringSchemaUpgrade = await _fetchMessagesRequiringSchemaUpgrade(
{
BackboneMessageCollection,
count: numMessagesPerBatch,
});
}
);
const fetchDuration = Date.now() - fetchStartTime;
const upgradeStartTime = Date.now();
const upgradedMessages =
await Promise.all(messagesRequiringSchemaUpgrade.map(upgradeMessageSchema));
const upgradedMessages = await Promise.all(
messagesRequiringSchemaUpgrade.map(upgradeMessageSchema)
);
const upgradeDuration = Date.now() - upgradeStartTime;
const saveStartTime = Date.now();
@ -109,8 +108,10 @@ exports.dangerouslyProcessAllWithoutIndex = async ({
minDatabaseVersion,
});
if (!isValidDatabaseVersion) {
throw new Error(`Expected database version (${databaseVersion})` +
` to be at least ${minDatabaseVersion}`);
throw new Error(
`Expected database version (${databaseVersion})` +
` to be at least ${minDatabaseVersion}`
);
}
// NOTE: Even if we make this async using `then`, requesting `count` on an
@ -132,10 +133,13 @@ exports.dangerouslyProcessAllWithoutIndex = async ({
break;
}
numCumulativeMessagesProcessed += status.numMessagesProcessed;
console.log('Upgrade message schema:', Object.assign({}, status, {
console.log(
'Upgrade message schema:',
Object.assign({}, status, {
numTotalMessages,
numCumulativeMessagesProcessed,
}));
})
);
}
console.log('Close database connection');
@ -181,8 +185,10 @@ const _getConnection = async ({ databaseName, minDatabaseVersion }) => {
const databaseVersion = connection.version;
const isValidDatabaseVersion = databaseVersion >= minDatabaseVersion;
if (!isValidDatabaseVersion) {
throw new Error(`Expected database version (${databaseVersion})` +
` to be at least ${minDatabaseVersion}`);
throw new Error(
`Expected database version (${databaseVersion})` +
` to be at least ${minDatabaseVersion}`
);
}
return connection;
@ -205,29 +211,33 @@ const _processBatch = async ({
throw new TypeError("'numMessagesPerBatch' is required");
}
const isAttachmentMigrationComplete =
await settings.isAttachmentMigrationComplete(connection);
const isAttachmentMigrationComplete = await settings.isAttachmentMigrationComplete(
connection
);
if (isAttachmentMigrationComplete) {
return {
done: true,
};
}
const lastProcessedIndex =
await settings.getAttachmentMigrationLastProcessedIndex(connection);
const lastProcessedIndex = await settings.getAttachmentMigrationLastProcessedIndex(
connection
);
const fetchUnprocessedMessagesStartTime = Date.now();
const unprocessedMessages =
await _dangerouslyFetchMessagesRequiringSchemaUpgradeWithoutIndex({
const unprocessedMessages = await _dangerouslyFetchMessagesRequiringSchemaUpgradeWithoutIndex(
{
connection,
count: numMessagesPerBatch,
lastIndex: lastProcessedIndex,
});
}
);
const fetchDuration = Date.now() - fetchUnprocessedMessagesStartTime;
const upgradeStartTime = Date.now();
const upgradedMessages =
await Promise.all(unprocessedMessages.map(upgradeMessageSchema));
const upgradedMessages = await Promise.all(
unprocessedMessages.map(upgradeMessageSchema)
);
const upgradeDuration = Date.now() - upgradeStartTime;
const saveMessagesStartTime = Date.now();
@ -266,12 +276,12 @@ const _processBatch = async ({
};
};
const _saveMessageBackbone = ({ BackboneMessage } = {}) => (message) => {
const _saveMessageBackbone = ({ BackboneMessage } = {}) => message => {
const backboneMessage = new BackboneMessage(message);
return deferredToPromise(backboneMessage.save());
};
const _saveMessage = ({ transaction } = {}) => (message) => {
const _saveMessage = ({ transaction } = {}) => message => {
if (!isObject(transaction)) {
throw new TypeError("'transaction' is required");
}
@ -279,18 +289,20 @@ const _saveMessage = ({ transaction } = {}) => (message) => {
const messagesStore = transaction.objectStore(MESSAGES_STORE_NAME);
const request = messagesStore.put(message, message.id);
return new Promise((resolve, reject) => {
request.onsuccess = () =>
resolve();
request.onerror = event =>
reject(event.target.error);
request.onsuccess = () => resolve();
request.onerror = event => reject(event.target.error);
});
};
const _fetchMessagesRequiringSchemaUpgrade =
async ({ BackboneMessageCollection, count } = {}) => {
const _fetchMessagesRequiringSchemaUpgrade = async ({
BackboneMessageCollection,
count,
} = {}) => {
if (!isFunction(BackboneMessageCollection)) {
throw new TypeError("'BackboneMessageCollection' (Whisper.MessageCollection)" +
' constructor is required');
throw new TypeError(
"'BackboneMessageCollection' (Whisper.MessageCollection)" +
' constructor is required'
);
}
if (!isNumber(count)) {
@ -298,7 +310,9 @@ const _fetchMessagesRequiringSchemaUpgrade =
}
const collection = new BackboneMessageCollection();
return new Promise(resolve => collection.fetch({
return new Promise(resolve =>
collection
.fetch({
limit: count,
index: {
name: 'schemaVersion',
@ -306,17 +320,22 @@ const _fetchMessagesRequiringSchemaUpgrade =
excludeUpper: true,
order: 'desc',
},
}).always(() => {
})
.always(() => {
const models = collection.models || [];
const messages = models.map(model => model.toJSON());
resolve(messages);
}));
})
);
};
// NOTE: Named dangerous because it is not as efficient as using our
// `messages` `schemaVersion` index:
const _dangerouslyFetchMessagesRequiringSchemaUpgradeWithoutIndex =
({ connection, count, lastIndex } = {}) => {
const _dangerouslyFetchMessagesRequiringSchemaUpgradeWithoutIndex = ({
connection,
count,
lastIndex,
} = {}) => {
if (!isObject(connection)) {
throw new TypeError("'connection' is required");
}
@ -341,7 +360,7 @@ const _dangerouslyFetchMessagesRequiringSchemaUpgradeWithoutIndex =
return new Promise((resolve, reject) => {
const items = [];
const request = messagesStore.openCursor(range);
request.onsuccess = (event) => {
request.onsuccess = event => {
const cursor = event.target.result;
const hasMoreData = Boolean(cursor);
if (!hasMoreData || items.length === count) {
@ -352,8 +371,7 @@ const _dangerouslyFetchMessagesRequiringSchemaUpgradeWithoutIndex =
items.push(item);
cursor.continue();
};
request.onerror = event =>
reject(event.target.error);
request.onerror = event => reject(event.target.error);
});
};

View File

@ -1,4 +1,4 @@
exports.run = (transaction) => {
exports.run = transaction => {
const messagesStore = transaction.objectStore('messages');
console.log("Create message attachment metadata index: 'hasAttachments'");
@ -8,12 +8,10 @@ exports.run = (transaction) => {
{ unique: false }
);
['hasVisualMediaAttachments', 'hasFileAttachments'].forEach((name) => {
['hasVisualMediaAttachments', 'hasFileAttachments'].forEach(name => {
console.log(`Create message attachment metadata index: '${name}'`);
messagesStore.createIndex(
name,
['conversationId', 'received_at', name],
{ unique: false }
);
messagesStore.createIndex(name, ['conversationId', 'received_at', name], {
unique: false,
});
});
};

View File

@ -1,23 +1,22 @@
const Migrations0DatabaseWithAttachmentData =
require('./migrations_0_database_with_attachment_data');
const Migrations1DatabaseWithoutAttachmentData =
require('./migrations_1_database_without_attachment_data');
const Migrations0DatabaseWithAttachmentData = require('./migrations_0_database_with_attachment_data');
const Migrations1DatabaseWithoutAttachmentData = require('./migrations_1_database_without_attachment_data');
exports.getPlaceholderMigrations = () => {
const last0MigrationVersion =
Migrations0DatabaseWithAttachmentData.getLatestVersion();
const last1MigrationVersion =
Migrations1DatabaseWithoutAttachmentData.getLatestVersion();
const last0MigrationVersion = Migrations0DatabaseWithAttachmentData.getLatestVersion();
const last1MigrationVersion = Migrations1DatabaseWithoutAttachmentData.getLatestVersion();
const lastMigrationVersion = last1MigrationVersion || last0MigrationVersion;
return [{
return [
{
version: lastMigrationVersion,
migrate() {
throw new Error('Unexpected invocation of placeholder migration!' +
throw new Error(
'Unexpected invocation of placeholder migration!' +
'\n\nMigrations must explicitly be run upon application startup instead' +
' of implicitly via Backbone IndexedDB adapter at any time.');
' of implicitly via Backbone IndexedDB adapter at any time.'
);
},
}];
},
];
};

View File

@ -3,7 +3,6 @@ const { isString, last } = require('lodash');
const { runMigrations } = require('./run_migrations');
const Migration18 = require('./18');
// IMPORTANT: The migrations below are run on a database that may be very large
// due to attachments being directly stored inside the database. Please avoid
// any expensive operations, e.g. modifying all messages / attachments, etc., as
@ -20,7 +19,9 @@ const migrations = [
unique: false,
});
messages.createIndex('receipt', 'sent_at', { unique: false });
messages.createIndex('unread', ['conversationId', 'unread'], { unique: false });
messages.createIndex('unread', ['conversationId', 'unread'], {
unique: false,
});
messages.createIndex('expires_at', 'expires_at', { unique: false });
const conversations = transaction.db.createObjectStore('conversations');
@ -59,7 +60,7 @@ const migrations = [
const identityKeys = transaction.objectStore('identityKeys');
const request = identityKeys.openCursor();
const promises = [];
request.onsuccess = (event) => {
request.onsuccess = event => {
const cursor = event.target.result;
if (cursor) {
const attributes = cursor.value;
@ -67,14 +68,16 @@ const migrations = [
attributes.firstUse = false;
attributes.nonblockingApproval = false;
attributes.verified = 0;
promises.push(new Promise(((resolve, reject) => {
promises.push(
new Promise((resolve, reject) => {
const putRequest = identityKeys.put(attributes, attributes.id);
putRequest.onsuccess = resolve;
putRequest.onerror = (e) => {
putRequest.onerror = e => {
console.log(e);
reject(e);
};
})));
})
);
cursor.continue();
} else {
// no more results
@ -84,7 +87,7 @@ const migrations = [
});
}
};
request.onerror = (event) => {
request.onerror = event => {
console.log(event);
};
},
@ -129,7 +132,9 @@ const migrations = [
const messagesStore = transaction.objectStore('messages');
console.log('Create index from attachment schema version to attachment');
messagesStore.createIndex('schemaVersion', 'schemaVersion', { unique: false });
messagesStore.createIndex('schemaVersion', 'schemaVersion', {
unique: false,
});
const duration = Date.now() - start;

View File

@ -4,7 +4,6 @@ const db = require('../database');
const settings = require('../settings');
const { runMigrations } = require('./run_migrations');
// IMPORTANT: Add new migrations that need to traverse entire database, e.g.
// messages store, below. Whenever we need this, we need to force attachment
// migration on startup:
@ -20,7 +19,9 @@ const migrations = [
exports.run = async ({ Backbone, database } = {}) => {
const { canRun } = await exports.getStatus({ database });
if (!canRun) {
throw new Error('Cannot run migrations on database without attachment data');
throw new Error(
'Cannot run migrations on database without attachment data'
);
}
await runMigrations({ Backbone, database });
@ -28,8 +29,9 @@ exports.run = async ({ Backbone, database } = {}) => {
exports.getStatus = async ({ database } = {}) => {
const connection = await db.open(database.id, database.version);
const isAttachmentMigrationComplete =
await settings.isAttachmentMigrationComplete(connection);
const isAttachmentMigrationComplete = await settings.isAttachmentMigrationComplete(
connection
);
const hasMigrations = migrations.length > 0;
const canRun = isAttachmentMigrationComplete && hasMigrations;

View File

@ -1,29 +1,27 @@
/* eslint-env browser */
const {
head,
isFunction,
isObject,
isString,
last,
} = require('lodash');
const { head, isFunction, isObject, isString, last } = require('lodash');
const db = require('../database');
const { deferredToPromise } = require('../deferred_to_promise');
const closeDatabaseConnection = ({ Backbone } = {}) =>
deferredToPromise(Backbone.sync('closeall'));
exports.runMigrations = async ({ Backbone, database } = {}) => {
if (!isObject(Backbone) || !isObject(Backbone.Collection) ||
!isFunction(Backbone.Collection.extend)) {
if (
!isObject(Backbone) ||
!isObject(Backbone.Collection) ||
!isFunction(Backbone.Collection.extend)
) {
throw new TypeError("'Backbone' is required");
}
if (!isObject(database) || !isString(database.id) ||
!Array.isArray(database.migrations)) {
if (
!isObject(database) ||
!isString(database.id) ||
!Array.isArray(database.migrations)
) {
throw new TypeError("'database' is required");
}
@ -56,7 +54,7 @@ exports.runMigrations = async ({ Backbone, database } = {}) => {
await closeDatabaseConnection({ Backbone });
};
const getMigrationVersions = (database) => {
const getMigrationVersions = database => {
if (!isObject(database) || !Array.isArray(database.migrations)) {
throw new TypeError("'database' is required");
}
@ -64,8 +62,12 @@ const getMigrationVersions = (database) => {
const firstMigration = head(database.migrations);
const lastMigration = last(database.migrations);
const firstVersion = firstMigration ? parseInt(firstMigration.version, 10) : null;
const lastVersion = lastMigration ? parseInt(lastMigration.version, 10) : null;
const firstVersion = firstMigration
? parseInt(firstMigration.version, 10)
: null;
const lastVersion = lastMigration
? parseInt(lastMigration.version, 10)
: null;
return { firstVersion, lastVersion };
};

View File

@ -1,10 +1,7 @@
/* eslint-env node */
exports.isMacOS = () =>
process.platform === 'darwin';
exports.isMacOS = () => process.platform === 'darwin';
exports.isLinux = () =>
process.platform === 'linux';
exports.isLinux = () => process.platform === 'linux';
exports.isWindows = () =>
process.platform === 'win32';
exports.isWindows = () => process.platform === 'win32';

View File

@ -6,22 +6,20 @@ const path = require('path');
const { compose } = require('lodash/fp');
const { escapeRegExp } = require('lodash');
const APP_ROOT_PATH = path.join(__dirname, '..', '..', '..');
const PHONE_NUMBER_PATTERN = /\+\d{7,12}(\d{3})/g;
const GROUP_ID_PATTERN = /(group\()([^)]+)(\))/g;
const REDACTION_PLACEHOLDER = '[REDACTED]';
// _redactPath :: Path -> String -> String
exports._redactPath = (filePath) => {
exports._redactPath = filePath => {
if (!is.string(filePath)) {
throw new TypeError("'filePath' must be a string");
}
const filePathPattern = exports._pathToRegExp(filePath);
return (text) => {
return text => {
if (!is.string(text)) {
throw new TypeError("'text' must be a string");
}
@ -35,7 +33,7 @@ exports._redactPath = (filePath) => {
};
// _pathToRegExp :: Path -> Maybe RegExp
exports._pathToRegExp = (filePath) => {
exports._pathToRegExp = filePath => {
try {
const pathWithNormalizedSlashes = filePath.replace(/\//g, '\\');
const pathWithEscapedSlashes = filePath.replace(/\\/g, '\\\\');
@ -47,7 +45,9 @@ exports._pathToRegExp = (filePath) => {
pathWithNormalizedSlashes,
pathWithEscapedSlashes,
urlEncodedPath,
].map(escapeRegExp).join('|');
]
.map(escapeRegExp)
.join('|');
return new RegExp(patternString, 'g');
} catch (error) {
return null;
@ -56,7 +56,7 @@ exports._pathToRegExp = (filePath) => {
// Public API
// redactPhoneNumbers :: String -> String
exports.redactPhoneNumbers = (text) => {
exports.redactPhoneNumbers = text => {
if (!is.string(text)) {
throw new TypeError("'text' must be a string");
}
@ -65,7 +65,7 @@ exports.redactPhoneNumbers = (text) => {
};
// redactGroupIds :: String -> String
exports.redactGroupIds = (text) => {
exports.redactGroupIds = text => {
if (!is.string(text)) {
throw new TypeError("'text' must be a string");
}

View File

@ -1,6 +1,5 @@
const { isObject, isString } = require('lodash');
const ITEMS_STORE_NAME = 'items';
const LAST_PROCESSED_INDEX_KEY = 'attachmentMigration_lastProcessedIndex';
const IS_MIGRATION_COMPLETE_KEY = 'attachmentMigration_isComplete';
@ -37,8 +36,7 @@ exports._getItem = (connection, key) => {
const itemsStore = transaction.objectStore(ITEMS_STORE_NAME);
const request = itemsStore.get(key);
return new Promise((resolve, reject) => {
request.onerror = event =>
reject(event.target.error);
request.onerror = event => reject(event.target.error);
request.onsuccess = event =>
resolve(event.target.result ? event.target.result.value : null);
@ -58,11 +56,9 @@ exports._setItem = (connection, key, value) => {
const itemsStore = transaction.objectStore(ITEMS_STORE_NAME);
const request = itemsStore.put({ id: key, value }, key);
return new Promise((resolve, reject) => {
request.onerror = event =>
reject(event.target.error);
request.onerror = event => reject(event.target.error);
request.onsuccess = () =>
resolve();
request.onsuccess = () => resolve();
});
};
@ -79,10 +75,8 @@ exports._deleteItem = (connection, key) => {
const itemsStore = transaction.objectStore(ITEMS_STORE_NAME);
const request = itemsStore.delete(key);
return new Promise((resolve, reject) => {
request.onerror = event =>
reject(event.target.error);
request.onerror = event => reject(event.target.error);
request.onsuccess = () =>
resolve();
request.onsuccess = () => resolve();
});
};

View File

@ -1,4 +1,3 @@
/* global setTimeout */
exports.sleep = ms =>
new Promise(resolve => setTimeout(resolve, ms));
exports.sleep = ms => new Promise(resolve => setTimeout(resolve, ms));

View File

@ -3,7 +3,6 @@ const is = require('@sindresorhus/is');
const Errors = require('./types/errors');
const Settings = require('./settings');
exports.syncReadReceiptConfiguration = async ({
deviceId,
sendRequestConfigurationSyncMessage,

View File

@ -1,4 +1,4 @@
exports.stringToArrayBuffer = (string) => {
exports.stringToArrayBuffer = string => {
if (typeof string !== 'string') {
throw new TypeError("'string' must be a string");
}

View File

@ -2,9 +2,15 @@ const is = require('@sindresorhus/is');
const AttachmentTS = require('../../../ts/types/Attachment');
const MIME = require('../../../ts/types/MIME');
const { arrayBufferToBlob, blobToArrayBuffer, dataURLToBlob } = require('blob-util');
const {
arrayBufferToBlob,
blobToArrayBuffer,
dataURLToBlob,
} = require('blob-util');
const { autoOrientImage } = require('../auto_orient_image');
const { migrateDataToFileSystem } = require('./attachment/migrate_data_to_file_system');
const {
migrateDataToFileSystem,
} = require('./attachment/migrate_data_to_file_system');
// // Incoming message attachment fields
// {
@ -30,7 +36,7 @@ const { migrateDataToFileSystem } = require('./attachment/migrate_data_to_file_s
// Returns true if `rawAttachment` is a valid attachment based on our current schema.
// Over time, we can expand this definition to become more narrow, e.g. require certain
// fields, etc.
exports.isValid = (rawAttachment) => {
exports.isValid = rawAttachment => {
// NOTE: We cannot use `_.isPlainObject` because `rawAttachment` is
// deserialized by protobuf:
if (!rawAttachment) {
@ -41,12 +47,15 @@ exports.isValid = (rawAttachment) => {
};
// Upgrade steps
exports.autoOrientJPEG = async (attachment) => {
exports.autoOrientJPEG = async attachment => {
if (!MIME.isJPEG(attachment.contentType)) {
return attachment;
}
const dataBlob = await arrayBufferToBlob(attachment.data, attachment.contentType);
const dataBlob = await arrayBufferToBlob(
attachment.data,
attachment.contentType
);
const newDataBlob = await dataURLToBlob(await autoOrientImage(dataBlob));
const newDataArrayBuffer = await blobToArrayBuffer(newDataBlob);
@ -76,7 +85,7 @@ const INVALID_CHARACTERS_PATTERN = new RegExp(
// NOTE: Expose synchronous version to do property-based testing using `testcheck`,
// which currently doesnt support async testing:
// https://github.com/leebyron/testcheck-js/issues/45
exports._replaceUnicodeOrderOverridesSync = (attachment) => {
exports._replaceUnicodeOrderOverridesSync = attachment => {
if (!is.string(attachment.fileName)) {
return attachment;
}
@ -95,9 +104,12 @@ exports._replaceUnicodeOrderOverridesSync = (attachment) => {
exports.replaceUnicodeOrderOverrides = async attachment =>
exports._replaceUnicodeOrderOverridesSync(attachment);
exports.removeSchemaVersion = (attachment) => {
exports.removeSchemaVersion = attachment => {
if (!exports.isValid(attachment)) {
console.log('Attachment.removeSchemaVersion: Invalid input attachment:', attachment);
console.log(
'Attachment.removeSchemaVersion: Invalid input attachment:',
attachment
);
return attachment;
}
@ -115,12 +127,12 @@ exports.hasData = attachment =>
// loadData :: (RelativePath -> IO (Promise ArrayBuffer))
// Attachment ->
// IO (Promise Attachment)
exports.loadData = (readAttachmentData) => {
exports.loadData = readAttachmentData => {
if (!is.function(readAttachmentData)) {
throw new TypeError("'readAttachmentData' must be a function");
}
return async (attachment) => {
return async attachment => {
if (!exports.isValid(attachment)) {
throw new TypeError("'attachment' is not valid");
}
@ -142,12 +154,12 @@ exports.loadData = (readAttachmentData) => {
// deleteData :: (RelativePath -> IO Unit)
// Attachment ->
// IO Unit
exports.deleteData = (deleteAttachmentData) => {
exports.deleteData = deleteAttachmentData => {
if (!is.function(deleteAttachmentData)) {
throw new TypeError("'deleteAttachmentData' must be a function");
}
return async (attachment) => {
return async attachment => {
if (!exports.isValid(attachment)) {
throw new TypeError("'attachment' is not valid");
}

View File

@ -1,10 +1,4 @@
const {
isArrayBuffer,
isFunction,
isUndefined,
omit,
} = require('lodash');
const { isArrayBuffer, isFunction, isUndefined, omit } = require('lodash');
// type Context :: {
// writeNewAttachmentData :: ArrayBuffer -> Promise (IO Path)
@ -13,7 +7,10 @@ const {
// migrateDataToFileSystem :: Attachment ->
// Context ->
// Promise Attachment
exports.migrateDataToFileSystem = async (attachment, { writeNewAttachmentData } = {}) => {
exports.migrateDataToFileSystem = async (
attachment,
{ writeNewAttachmentData } = {}
) => {
if (!isFunction(writeNewAttachmentData)) {
throw new TypeError("'writeNewAttachmentData' must be a function");
}
@ -28,15 +25,16 @@ exports.migrateDataToFileSystem = async (attachment, { writeNewAttachmentData }
const isValidData = isArrayBuffer(data);
if (!isValidData) {
throw new TypeError('Expected `attachment.data` to be an array buffer;' +
` got: ${typeof attachment.data}`);
throw new TypeError(
'Expected `attachment.data` to be an array buffer;' +
` got: ${typeof attachment.data}`
);
}
const path = await writeNewAttachmentData(data);
const attachmentWithoutData = omit(
Object.assign({}, attachment, { path }),
['data']
);
const attachmentWithoutData = omit(Object.assign({}, attachment, { path }), [
'data',
]);
return attachmentWithoutData;
};

View File

@ -1,5 +1,5 @@
// toLogFormat :: Error -> String
exports.toLogFormat = (error) => {
exports.toLogFormat = error => {
if (!error) {
return error;
}

View File

@ -3,9 +3,9 @@ const { isFunction, isString, omit } = require('lodash');
const Attachment = require('./attachment');
const Errors = require('./errors');
const SchemaVersion = require('./schema_version');
const { initializeAttachmentMetadata } =
require('../../../ts/types/message/initializeAttachmentMetadata');
const {
initializeAttachmentMetadata,
} = require('../../../ts/types/message/initializeAttachmentMetadata');
const GROUP = 'group';
const PRIVATE = 'private';
@ -37,19 +37,17 @@ const INITIAL_SCHEMA_VERSION = 0;
// how we do database migrations:
exports.CURRENT_SCHEMA_VERSION = 5;
// Public API
exports.GROUP = GROUP;
exports.PRIVATE = PRIVATE;
// Placeholder until we have stronger preconditions:
exports.isValid = () =>
true;
exports.isValid = () => true;
// Schema
exports.initializeSchemaVersion = (message) => {
const isInitialized = SchemaVersion.isValid(message.schemaVersion) &&
message.schemaVersion >= 1;
exports.initializeSchemaVersion = message => {
const isInitialized =
SchemaVersion.isValid(message.schemaVersion) && message.schemaVersion >= 1;
if (isInitialized) {
return message;
}
@ -59,27 +57,23 @@ exports.initializeSchemaVersion = (message) => {
: 0;
const hasAttachments = numAttachments > 0;
if (!hasAttachments) {
return Object.assign(
{},
message,
{ schemaVersion: INITIAL_SCHEMA_VERSION }
);
return Object.assign({}, message, {
schemaVersion: INITIAL_SCHEMA_VERSION,
});
}
// All attachments should have the same schema version, so we just pick
// the first one:
const firstAttachment = message.attachments[0];
const inheritedSchemaVersion = SchemaVersion.isValid(firstAttachment.schemaVersion)
const inheritedSchemaVersion = SchemaVersion.isValid(
firstAttachment.schemaVersion
)
? firstAttachment.schemaVersion
: INITIAL_SCHEMA_VERSION;
const messageWithInitialSchema = Object.assign(
{},
message,
{
const messageWithInitialSchema = Object.assign({}, message, {
schemaVersion: inheritedSchemaVersion,
attachments: message.attachments.map(Attachment.removeSchemaVersion),
}
);
});
return messageWithInitialSchema;
};
@ -98,7 +92,10 @@ exports._withSchemaVersion = (schemaVersion, upgrade) => {
return async (message, context) => {
if (!exports.isValid(message)) {
console.log('Message._withSchemaVersion: Invalid input message:', message);
console.log(
'Message._withSchemaVersion: Invalid input message:',
message
);
return message;
}
@ -138,15 +135,10 @@ exports._withSchemaVersion = (schemaVersion, upgrade) => {
return message;
}
return Object.assign(
{},
upgradedMessage,
{ schemaVersion }
);
return Object.assign({}, upgradedMessage, { schemaVersion });
};
};
// Public API
// _mapAttachments :: (Attachment -> Promise Attachment) ->
// (Message, Context) ->
@ -154,19 +146,24 @@ exports._withSchemaVersion = (schemaVersion, upgrade) => {
exports._mapAttachments = upgradeAttachment => async (message, context) => {
const upgradeWithContext = attachment =>
upgradeAttachment(attachment, context);
const attachments = await Promise.all(message.attachments.map(upgradeWithContext));
const attachments = await Promise.all(
message.attachments.map(upgradeWithContext)
);
return Object.assign({}, message, { attachments });
};
// _mapQuotedAttachments :: (QuotedAttachment -> Promise QuotedAttachment) ->
// (Message, Context) ->
// Promise Message
exports._mapQuotedAttachments = upgradeAttachment => async (message, context) => {
exports._mapQuotedAttachments = upgradeAttachment => async (
message,
context
) => {
if (!message.quote) {
return message;
}
const upgradeWithContext = async (attachment) => {
const upgradeWithContext = async attachment => {
const { thumbnail } = attachment;
if (!thumbnail) {
return attachment;
@ -185,7 +182,9 @@ exports._mapQuotedAttachments = upgradeAttachment => async (message, context) =>
const quotedAttachments = (message.quote && message.quote.attachments) || [];
const attachments = await Promise.all(quotedAttachments.map(upgradeWithContext));
const attachments = await Promise.all(
quotedAttachments.map(upgradeWithContext)
);
return Object.assign({}, message, {
quote: Object.assign({}, message.quote, {
attachments,
@ -193,8 +192,7 @@ exports._mapQuotedAttachments = upgradeAttachment => async (message, context) =>
});
};
const toVersion0 = async message =>
exports.initializeSchemaVersion(message);
const toVersion0 = async message => exports.initializeSchemaVersion(message);
const toVersion1 = exports._withSchemaVersion(
1,
@ -241,25 +239,28 @@ exports.upgradeSchema = async (rawMessage, { writeNewAttachmentData } = {}) => {
return message;
};
exports.createAttachmentLoader = (loadAttachmentData) => {
exports.createAttachmentLoader = loadAttachmentData => {
if (!isFunction(loadAttachmentData)) {
throw new TypeError('`loadAttachmentData` is required');
}
return async message => (Object.assign({}, message, {
attachments: await Promise.all(message.attachments.map(loadAttachmentData)),
}));
return async message =>
Object.assign({}, message, {
attachments: await Promise.all(
message.attachments.map(loadAttachmentData)
),
});
};
// createAttachmentDataWriter :: (RelativePath -> IO Unit)
// Message ->
// IO (Promise Message)
exports.createAttachmentDataWriter = (writeExistingAttachmentData) => {
exports.createAttachmentDataWriter = writeExistingAttachmentData => {
if (!isFunction(writeExistingAttachmentData)) {
throw new TypeError("'writeExistingAttachmentData' must be a function");
}
return async (rawMessage) => {
return async rawMessage => {
if (!exports.isValid(rawMessage)) {
throw new TypeError("'rawMessage' is not valid");
}
@ -282,17 +283,21 @@ exports.createAttachmentDataWriter = (writeExistingAttachmentData) => {
return message;
}
(attachments || []).forEach((attachment) => {
(attachments || []).forEach(attachment => {
if (!Attachment.hasData(attachment)) {
throw new TypeError("'attachment.data' is required during message import");
throw new TypeError(
"'attachment.data' is required during message import"
);
}
if (!isString(attachment.path)) {
throw new TypeError("'attachment.path' is required during message import");
throw new TypeError(
"'attachment.path' is required during message import"
);
}
});
const writeThumbnails = exports._mapQuotedAttachments(async (thumbnail) => {
const writeThumbnails = exports._mapQuotedAttachments(async thumbnail => {
const { data, path } = thumbnail;
// we want to be bulletproof to thumbnails without data
@ -315,10 +320,12 @@ exports.createAttachmentDataWriter = (writeExistingAttachmentData) => {
{},
await writeThumbnails(message),
{
attachments: await Promise.all((attachments || []).map(async (attachment) => {
attachments: await Promise.all(
(attachments || []).map(async attachment => {
await writeExistingAttachmentData(attachment);
return omit(attachment, ['data']);
})),
})
),
}
);

View File

@ -1,5 +1,3 @@
const { isNumber } = require('lodash');
exports.isValid = value =>
isNumber(value) && value >= 0;
exports.isValid = value => isNumber(value) && value >= 0;

View File

@ -1,4 +1,3 @@
const OS = require('../os');
exports.isAudioNotificationSupported = () =>
!OS.isLinux();
exports.isAudioNotificationSupported = () => !OS.isLinux();

View File

@ -2,7 +2,6 @@
/* global i18n: false */
const OPTIMIZATION_MESSAGE_DISPLAY_THRESHOLD = 1000; // milliseconds
const setMessage = () => {

View File

@ -2,7 +2,7 @@
* vim: ts=4:sw=4:expandtab
*/
;(function() {
(function() {
'use strict';
window.Whisper = window.Whisper || {};
const { Settings } = window.Signal.Types;
@ -11,7 +11,7 @@
OFF: 'off',
COUNT: 'count',
NAME: 'name',
MESSAGE : 'message'
MESSAGE: 'message',
};
Whisper.Notifications = new (Backbone.Collection.extend({
@ -27,15 +27,18 @@
update: function() {
const { isEnabled } = this;
const isFocused = window.isFocused();
const isAudioNotificationEnabled = storage.get('audio-notification') || false;
const isAudioNotificationEnabled =
storage.get('audio-notification') || false;
const isAudioNotificationSupported = Settings.isAudioNotificationSupported();
const shouldPlayNotificationSound = isAudioNotificationSupported &&
isAudioNotificationEnabled;
const shouldPlayNotificationSound =
isAudioNotificationSupported && isAudioNotificationEnabled;
const numNotifications = this.length;
console.log(
'Update notifications:',
{isFocused, isEnabled, numNotifications, shouldPlayNotificationSound}
);
console.log('Update notifications:', {
isFocused,
isEnabled,
numNotifications,
shouldPlayNotificationSound,
});
if (!isEnabled) {
return;
@ -69,7 +72,7 @@
// http://docs.translatehouse.org/projects/localization-guide/en/latest/l10n/pluralforms.html
var newMessageCount = [
numNotifications,
numNotifications === 1 ? i18n('newMessage') : i18n('newMessages')
numNotifications === 1 ? i18n('newMessage') : i18n('newMessages'),
].join(' ');
var last = this.last();
@ -111,7 +114,10 @@
silent: !shouldPlayNotificationSound,
});
notification.onclick = this.onClick.bind(this, last.get('conversationId'));
notification.onclick = this.onClick.bind(
this,
last.get('conversationId')
);
}
// We don't want to notify the user about these same messages again

View File

@ -1,7 +1,7 @@
/*
* vim: ts=4:sw=4:expandtab
*/
;(function() {
(function() {
'use strict';
window.Whisper = window.Whisper || {};
Whisper.ReadReceipts = new (Backbone.Collection.extend({
@ -16,8 +16,10 @@
ids = conversation.get('members');
}
var receipts = this.filter(function(receipt) {
return receipt.get('timestamp') === message.get('sent_at')
&& _.contains(ids, receipt.get('reader'));
return (
receipt.get('timestamp') === message.get('sent_at') &&
_.contains(ids, receipt.get('reader'))
);
});
if (receipts.length) {
console.log('Found early read receipts for message');
@ -27,28 +29,43 @@
},
onReceipt: function(receipt) {
var messages = new Whisper.MessageCollection();
return messages.fetchSentAt(receipt.get('timestamp')).then(function() {
if (messages.length === 0) { return; }
return messages
.fetchSentAt(receipt.get('timestamp'))
.then(function() {
if (messages.length === 0) {
return;
}
var message = messages.find(function(message) {
return (message.isOutgoing() && receipt.get('reader') === message.get('conversationId'));
return (
message.isOutgoing() &&
receipt.get('reader') === message.get('conversationId')
);
});
if (message) { return message; }
if (message) {
return message;
}
var groups = new Whisper.GroupCollection();
return groups.fetchGroups(receipt.get('reader')).then(function() {
var ids = groups.pluck('id');
ids.push(receipt.get('reader'));
return messages.find(function(message) {
return (message.isOutgoing() &&
_.contains(ids, message.get('conversationId')));
return (
message.isOutgoing() &&
_.contains(ids, message.get('conversationId'))
);
});
});
}).then(function(message) {
})
.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() {
return new Promise(
function(resolve, reject) {
message.save({ read_by: read_by }).then(
function() {
// notify frontend listeners
var conversation = ConversationController.get(
message.get('conversationId')
@ -59,8 +76,11 @@
this.remove(receipt);
resolve();
}.bind(this), reject);
}.bind(this));
}.bind(this),
reject
);
}.bind(this)
);
} else {
console.log(
'No message for read receipt',
@ -68,7 +88,9 @@
receipt.get('timestamp')
);
}
}.bind(this)).catch(function(error) {
}.bind(this)
)
.catch(function(error) {
console.log(
'ReadReceipts.onReceipt error:',
error && error.stack ? error.stack : error

View File

@ -1,14 +1,14 @@
/*
* vim: ts=4:sw=4:expandtab
*/
;(function() {
(function() {
'use strict';
window.Whisper = window.Whisper || {};
Whisper.ReadSyncs = new (Backbone.Collection.extend({
forMessage: function(message) {
var receipt = this.findWhere({
sender: message.get('source'),
timestamp: message.get('sent_at')
timestamp: message.get('sent_at'),
});
if (receipt) {
console.log('Found early read sync for message');
@ -18,27 +18,35 @@
},
onReceipt: function(receipt) {
var messages = new Whisper.MessageCollection();
return messages.fetchSentAt(receipt.get('timestamp')).then(function() {
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'));
return (
message.isIncoming() &&
message.isUnread() &&
message.get('source') === receipt.get('sender')
);
});
if (message) {
return message.markRead(receipt.get('read_at')).then(function() {
return message.markRead(receipt.get('read_at')).then(
function() {
this.notifyConversation(message);
this.remove(receipt);
}.bind(this));
}.bind(this)
);
} else {
console.log(
'No message for read sync',
receipt.get('sender'), receipt.get('timestamp')
receipt.get('sender'),
receipt.get('timestamp')
);
}
}.bind(this));
}.bind(this)
);
},
notifyConversation: function(message) {
var conversation = ConversationController.get({
id: message.get('conversationId')
id: message.get('conversationId'),
});
if (conversation) {

View File

@ -15,11 +15,13 @@
return storage.get('chromiumRegistrationDone') === '';
},
everDone: function() {
return storage.get('chromiumRegistrationDoneEver') === '' ||
storage.get('chromiumRegistrationDone') === '';
return (
storage.get('chromiumRegistrationDoneEver') === '' ||
storage.get('chromiumRegistrationDone') === ''
);
},
remove: function() {
storage.remove('chromiumRegistrationDone');
}
},
};
}());
})();

View File

@ -49,17 +49,26 @@
// triggering events. Tries to keep the usual cases speedy (most internal
// Backbone events have 3 arguments).
var triggerEvents = function(events, name, args) {
var ev, i = -1, l = events.length, a1 = args[0], a2 = args[1], a3 = args[2];
var ev,
i = -1,
l = events.length,
a1 = args[0],
a2 = args[1],
a3 = args[2];
var logError = function(error) {
console.log('Model caught error triggering', name, 'event:', error && error.stack ? error.stack : error);
console.log(
'Model caught error triggering',
name,
'event:',
error && error.stack ? error.stack : error
);
};
switch (args.length) {
case 0:
while (++i < l) {
try {
(ev = events[i]).callback.call(ev.ctx);
}
catch (error) {
} catch (error) {
logError(error);
}
}
@ -68,8 +77,7 @@
while (++i < l) {
try {
(ev = events[i]).callback.call(ev.ctx, a1);
}
catch (error) {
} catch (error) {
logError(error);
}
}
@ -78,8 +86,7 @@
while (++i < l) {
try {
(ev = events[i]).callback.call(ev.ctx, a1, a2);
}
catch (error) {
} catch (error) {
logError(error);
}
}
@ -88,8 +95,7 @@
while (++i < l) {
try {
(ev = events[i]).callback.call(ev.ctx, a1, a2, a3);
}
catch (error) {
} catch (error) {
logError(error);
}
}
@ -98,8 +104,7 @@
while (++i < l) {
try {
(ev = events[i]).callback.apply(ev.ctx, args);
}
catch (error) {
} catch (error) {
logError(error);
}
}
@ -122,10 +127,5 @@
return this;
}
Backbone.Model.prototype.trigger
= Backbone.View.prototype.trigger
= Backbone.Collection.prototype.trigger
= Backbone.Events.trigger
= trigger;
Backbone.Model.prototype.trigger = Backbone.View.prototype.trigger = Backbone.Collection.prototype.trigger = Backbone.Events.trigger = trigger;
})();

View File

@ -2,7 +2,7 @@
* vim: ts=4:sw=4:expandtab
*/
;(function () {
(function() {
'use strict';
window.Whisper = window.Whisper || {};
var ROTATION_INTERVAL = 48 * 60 * 60 * 1000;
@ -17,8 +17,12 @@
function run() {
console.log('Rotating signed prekey...');
getAccountManager().rotateSignedPreKey().catch(function() {
console.log('rotateSignedPrekey() failed. Trying again in five seconds');
getAccountManager()
.rotateSignedPreKey()
.catch(function() {
console.log(
'rotateSignedPrekey() failed. Trying again in five seconds'
);
setTimeout(runWhenOnline, 5000);
});
scheduleNextRotation();
@ -29,7 +33,9 @@
if (navigator.onLine) {
run();
} else {
console.log('We are offline; keys will be rotated when we are next online');
console.log(
'We are offline; keys will be rotated when we are next online'
);
var listener = function() {
window.removeEventListener('online', listener);
run();
@ -79,6 +85,6 @@
setTimeoutForNextRun();
}
});
}
},
};
}());
})();

File diff suppressed because it is too large Load Diff

View File

@ -31,7 +31,7 @@
'shouldn',
'wasn',
'weren',
'wouldn'
'wouldn',
];
function setupLinux(locale) {
@ -39,7 +39,12 @@
// apt-get install hunspell-<locale> can be run for easy access to other dictionaries
var location = process.env.HUNSPELL_DICTIONARIES || '/usr/share/hunspell';
console.log('Detected Linux. Setting up spell check with locale', locale, 'and dictionary location', location);
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');
@ -50,10 +55,17 @@
if (process.env.HUNSPELL_DICTIONARIES || locale !== 'en_US') {
var location = process.env.HUNSPELL_DICTIONARIES;
console.log('Detected Windows 7 or below. Setting up spell-check with locale', locale, 'and dictionary location', location);
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');
console.log(
'Detected Windows 7 or below. Using default en_US spell check dictionary'
);
}
}
@ -69,14 +81,17 @@
if (process.platform === 'linux') {
setupLinux(locale);
} else if (process.platform === 'windows' && semver.lt(os.release(), '8.0.0')) {
} 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);
}
var simpleChecker = window.spellChecker = {
var simpleChecker = (window.spellChecker = {
spellCheck: function(text) {
return !this.isMisspelled(text);
},
@ -101,8 +116,8 @@
},
add: function(text) {
spellchecker.add(text);
}
};
},
});
webFrame.setSpellCheckProvider(
'en-US',
@ -120,7 +135,8 @@
var selectedText = window.getSelection().toString();
var isMisspelled = selectedText && simpleChecker.isMisspelled(selectedText);
var spellingSuggestions = isMisspelled && simpleChecker.getSuggestions(selectedText).slice(0, 5);
var spellingSuggestions =
isMisspelled && simpleChecker.getSuggestions(selectedText).slice(0, 5);
var menu = buildEditorContextMenu({
isMisspelled: isMisspelled,
spellingSuggestions: spellingSuggestions,

View File

@ -1,12 +1,12 @@
/*
* vim: ts=4:sw=4:expandtab
*/
;(function() {
(function() {
'use strict';
window.Whisper = window.Whisper || {};
var Item = Backbone.Model.extend({
database: Whisper.Database,
storeName: 'items'
storeName: 'items',
});
var ItemCollection = Backbone.Collection.extend({
model: Item,
@ -16,14 +16,16 @@
var ready = false;
var items = new ItemCollection();
items.on('reset', function() { ready = true; });
items.on('reset', function() {
ready = true;
});
window.storage = {
/*****************************
*** Base Storage Routines ***
*****************************/
put: function(key, value) {
if (value === undefined) {
throw new Error("Tried to store undefined");
throw new Error('Tried to store undefined');
}
if (!ready) {
console.log('Called storage.put before storage is ready. key:', key);
@ -35,7 +37,7 @@
},
get: function(key, defaultValue) {
var item = items.get("" + key);
var item = items.get('' + key);
if (!item) {
return defaultValue;
}
@ -43,7 +45,7 @@
},
remove: function(key) {
var item = items.get("" + key);
var item = items.get('' + key);
if (item) {
items.remove(item);
return new Promise(function(resolve, reject) {
@ -63,16 +65,23 @@
fetch: function() {
return new Promise((resolve, reject) => {
items.fetch({reset: true})
.fail(() => reject(new Error('Failed to fetch from storage.' +
' This may be due to an unexpected database version.')))
items
.fetch({ reset: true })
.fail(() =>
reject(
new Error(
'Failed to fetch from storage.' +
' This may be due to an unexpected database version.'
)
)
)
.always(resolve);
});
},
reset: function() {
items.reset();
}
},
};
window.textsecure = window.textsecure || {};
window.textsecure.storage = window.textsecure.storage || {};

View File

@ -13,13 +13,14 @@
},
events: {
'click .openInstaller': 'openInstaller', // NetworkStatusView has this button
'openInbox': 'openInbox',
openInbox: 'openInbox',
'change-theme': 'applyTheme',
'change-hide-menu': 'applyHideMenu',
},
applyTheme: function() {
var theme = storage.get('theme-setting') || 'android';
this.$el.removeClass('ios')
this.$el
.removeClass('ios')
.removeClass('android-dark')
.removeClass('android')
.addClass(theme);
@ -30,7 +31,7 @@
window.setMenuBarVisibility(!hideMenuBar);
},
openView: function(view) {
this.el.innerHTML = "";
this.el.innerHTML = '';
this.el.append(view.el);
this.delegateEvents();
},
@ -48,13 +49,17 @@
openImporter: function() {
window.addSetupMenuItems();
this.resetViews();
var importView = this.importView = new Whisper.ImportView();
this.listenTo(importView, 'light-import', this.finishLightImport.bind(this));
var importView = (this.importView = new Whisper.ImportView());
this.listenTo(
importView,
'light-import',
this.finishLightImport.bind(this)
);
this.openView(this.importView);
},
finishLightImport: function() {
var options = {
hasExistingData: true
hasExistingData: true,
};
this.openInstaller(options);
},
@ -76,7 +81,7 @@
}
this.resetViews();
var installView = this.installView = new Whisper.InstallView(options);
var installView = (this.installView = new Whisper.InstallView(options));
this.openView(this.installView);
},
closeInstaller: function() {
@ -130,11 +135,13 @@
this.inboxView = new Whisper.InboxView({
model: self,
window: window,
initialLoadComplete: options.initialLoadComplete
initialLoadComplete: options.initialLoadComplete,
});
return ConversationController.loadPromise().then(function() {
return ConversationController.loadPromise().then(
function() {
this.openView(this.inboxView);
}.bind(this));
}.bind(this)
);
} else {
if (!$.contains(this.el, this.inboxView.el)) {
this.openView(this.inboxView);
@ -159,9 +166,11 @@
},
openConversation: function(conversation) {
if (conversation) {
this.openInbox().then(function() {
this.openInbox().then(
function() {
this.inboxView.openConversation(null, conversation);
}.bind(this));
}.bind(this)
);
}
},
});

View File

@ -10,6 +10,6 @@
templateName: 'attachment-preview',
render_attributes: function() {
return { source: this.src };
}
},
});
})();

View File

@ -62,10 +62,7 @@
const VideoView = MediaView.extend({ tagName: 'video' });
// Blacklist common file types known to be unsupported in Chrome
const unsupportedFileTypes = [
'audio/aiff',
'video/quicktime',
];
const unsupportedFileTypes = ['audio/aiff', 'video/quicktime'];
Whisper.AttachmentView = Backbone.View.extend({
tagName: 'div',
@ -123,7 +120,10 @@
},
isVoiceMessage() {
// eslint-disable-next-line no-bitwise
if (this.model.flags & textsecure.protobuf.AttachmentPointer.Flags.VOICE_MESSAGE) {
if (
this.model.flags &
textsecure.protobuf.AttachmentPointer.Flags.VOICE_MESSAGE
) {
return true;
}
@ -241,4 +241,4 @@
this.trigger('update');
},
});
}());
})();

View File

@ -16,13 +16,13 @@
this.message = options.message;
this.callbacks = {
onDismiss: options.onDismiss,
onClick: options.onClick
onClick: options.onClick,
};
this.render();
},
render_attributes: function() {
return {
message: this.message
message: this.message,
};
},
onDismiss: function(e) {
@ -31,6 +31,6 @@
},
onClick: function() {
this.callbacks.onClick();
}
},
});
})();

View File

@ -21,7 +21,7 @@
this.render();
},
events: {
'keyup': 'onKeyup',
keyup: 'onKeyup',
'click .ok': 'ok',
'click .cancel': 'cancel',
},
@ -30,7 +30,7 @@
message: this.message,
showCancel: !this.hideCancel,
cancel: this.cancelText,
ok: this.okText
ok: this.okText,
};
},
ok: function() {
@ -52,6 +52,6 @@
},
focusCancel: function() {
this.$('.cancel').focus();
}
},
});
})();

View File

@ -12,7 +12,7 @@
className: 'contact',
templateName: 'contact',
events: {
'click': 'showIdentity'
click: 'showIdentity',
},
initialize: function(options) {
this.ourNumber = textsecure.storage.user.getNumber();
@ -25,7 +25,7 @@
return {
title: i18n('me'),
number: this.model.getNumber(),
avatar: this.model.getAvatar()
avatar: this.model.getAvatar(),
};
}
@ -36,7 +36,7 @@
avatar: this.model.getAvatar(),
profileName: this.model.getProfileName(),
isVerified: this.model.isVerified(),
verified: i18n('verified')
verified: i18n('verified'),
};
},
showIdentity: function() {
@ -44,10 +44,10 @@
return;
}
var view = new Whisper.KeyVerificationPanelView({
model: this.model
model: this.model,
});
this.listenBack(view);
}
})
},
}),
});
})();

View File

@ -13,27 +13,43 @@
},
templateName: 'conversation-preview',
events: {
'click': 'select'
click: 'select',
},
initialize: function() {
// auto update
this.listenTo(this.model, 'change', _.debounce(this.render.bind(this), 1000));
this.listenTo(
this.model,
'change',
_.debounce(this.render.bind(this), 1000)
);
this.listenTo(this.model, 'destroy', this.remove); // auto update
this.listenTo(this.model, 'opened', this.markSelected); // auto update
var updateLastMessage = _.debounce(this.model.updateLastMessage.bind(this.model), 1000);
this.listenTo(this.model.messageCollection, 'add remove', updateLastMessage);
var updateLastMessage = _.debounce(
this.model.updateLastMessage.bind(this.model),
1000
);
this.listenTo(
this.model.messageCollection,
'add remove',
updateLastMessage
);
this.listenTo(this.model, 'newmessage', updateLastMessage);
extension.windows.onClosed(function() {
extension.windows.onClosed(
function() {
this.stopListening();
}.bind(this));
}.bind(this)
);
this.timeStampView = new Whisper.TimestampView({ brief: true });
this.model.updateLastMessage();
},
markSelected: function() {
this.$el.addClass('selected').siblings('.selected').removeClass('selected');
this.$el
.addClass('selected')
.siblings('.selected')
.removeClass('selected');
},
select: function(e) {
@ -43,15 +59,19 @@
render: function() {
this.$el.html(
Mustache.render(_.result(this,'template', ''), {
Mustache.render(
_.result(this, 'template', ''),
{
title: this.model.getTitle(),
last_message: this.model.get('lastMessage'),
last_message_timestamp: this.model.get('timestamp'),
number: this.model.getNumber(),
avatar: this.model.getAvatar(),
profileName: this.model.getProfileName(),
unreadCount: this.model.get('unreadCount')
}, this.render_partials())
unreadCount: this.model.get('unreadCount'),
},
this.render_partials()
)
);
this.timeStampView.setElement(this.$('.last-timestamp'));
this.timeStampView.update();
@ -67,7 +87,6 @@
}
return this;
}
},
});
})();

View File

@ -56,6 +56,6 @@
if ($el && $el.length > 0) {
$el.remove();
}
}
},
});
})();

View File

@ -8,8 +8,7 @@
window.Whisper = window.Whisper || {};
const isSearchable = conversation =>
conversation.isSearchable();
const isSearchable = conversation => conversation.isSearchable();
Whisper.NewContactView = Whisper.View.extend({
templateName: 'new-contact',
@ -46,7 +45,9 @@
// View to display the matched contacts from typeahead
this.typeahead_view = new Whisper.ConversationListView({
collection: new Whisper.ConversationCollection([], {
comparator(m) { return m.getTitle().toLowerCase(); },
comparator(m) {
return m.getTitle().toLowerCase();
},
}),
});
this.$el.append(this.typeahead_view.el);
@ -75,8 +76,11 @@
/* eslint-disable more/no-then */
this.pending = this.pending.then(() =>
this.typeahead.search(query).then(() => {
this.typeahead_view.collection.reset(this.typeahead.filter(isSearchable));
}));
this.typeahead_view.collection.reset(
this.typeahead.filter(isSearchable)
);
})
);
/* eslint-enable more/no-then */
this.trigger('show');
} else {
@ -105,8 +109,10 @@
}
const newConversationId = this.new_contact_view.model.id;
const conversation =
await ConversationController.getOrCreateAndWait(newConversationId, 'private');
const conversation = await ConversationController.getOrCreateAndWait(
newConversationId,
'private'
);
this.trigger('open', conversation);
this.initNewContact();
this.resetTypeahead();
@ -129,7 +135,9 @@
// eslint-disable-next-line more/no-then
this.typeahead.fetchAlphabetical().then(() => {
if (this.typeahead.length > 0) {
this.typeahead_view.collection.reset(this.typeahead.filter(isSearchable));
this.typeahead_view.collection.reset(
this.typeahead.filter(isSearchable)
);
} else {
this.showHints();
}
@ -163,4 +171,4 @@
return number.replace(/[\s-.()]*/g, '').match(/^\+?[0-9]*$/);
},
});
}());
})();

View File

@ -120,20 +120,32 @@
this.listenTo(this.model, 'destroy', this.stopListening);
this.listenTo(this.model, 'change:verified', this.onVerifiedChange);
this.listenTo(this.model, 'change:color', this.updateColor);
this.listenTo(this.model, 'change:avatar change:profileAvatar', this.updateAvatar);
this.listenTo(
this.model,
'change:avatar change:profileAvatar',
this.updateAvatar
);
this.listenTo(this.model, 'newmessage', this.addMessage);
this.listenTo(this.model, 'delivered', this.updateMessage);
this.listenTo(this.model, 'read', this.updateMessage);
this.listenTo(this.model, 'opened', this.onOpened);
this.listenTo(this.model, 'expired', this.onExpired);
this.listenTo(this.model, 'prune', this.onPrune);
this.listenTo(this.model.messageCollection, 'expired', this.onExpiredCollection);
this.listenTo(
this.model.messageCollection,
'expired',
this.onExpiredCollection
);
this.listenTo(
this.model.messageCollection,
'scroll-to-message',
this.scrollToMessage
);
this.listenTo(this.model.messageCollection, 'reply', this.setQuoteMessage);
this.listenTo(
this.model.messageCollection,
'reply',
this.setQuoteMessage
);
this.lazyUpdateVerified = _.debounce(
this.model.updateVerified.bind(this.model),
@ -247,7 +259,7 @@
return;
}
const oneHourAgo = Date.now() - (60 * 60 * 1000);
const oneHourAgo = Date.now() - 60 * 60 * 1000;
if (this.isHidden() && this.lastActivity < oneHourAgo) {
this.unload('inactivity');
} else if (this.view.atBottom()) {
@ -301,7 +313,7 @@
this.remove();
this.model.messageCollection.forEach((model) => {
this.model.messageCollection.forEach(model => {
model.trigger('unload');
});
this.model.messageCollection.reset([]);
@ -333,19 +345,21 @@
);
this.model.messageCollection.remove(models);
_.forEach(models, (model) => {
_.forEach(models, model => {
model.trigger('unload');
});
},
markAllAsVerifiedDefault(unverified) {
return Promise.all(unverified.map((contact) => {
return Promise.all(
unverified.map(contact => {
if (contact.isUnverified()) {
return contact.setVerifiedDefault();
}
return null;
}));
})
);
},
markAllAsApproved(untrusted) {
@ -404,7 +418,10 @@
}
},
toggleMicrophone() {
if (this.$('.send-message').val().length > 0 || this.fileInput.hasFiles()) {
if (
this.$('.send-message').val().length > 0 ||
this.fileInput.hasFiles()
) {
this.$('.capture-audio').hide();
} else {
this.$('.capture-audio').show();
@ -495,11 +512,13 @@
const statusPromise = this.throttledGetProfiles();
// eslint-disable-next-line more/no-then
this.statusFetch = statusPromise.then(() => this.model.updateVerified().then(() => {
this.statusFetch = statusPromise.then(() =>
this.model.updateVerified().then(() => {
this.onVerifiedChange();
this.statusFetch = null;
console.log('done with status fetch');
}));
})
);
// We schedule our catch-up decrypt right after any in-progress fetch of
// messages from the database, then ensure that the loading screen is only
@ -587,20 +606,25 @@
const conversationId = this.model.get('id');
const WhisperMessageCollection = Whisper.MessageCollection;
const rawMedia = await Signal.Backbone.Conversation.fetchVisualMediaAttachments({
const rawMedia = await Signal.Backbone.Conversation.fetchVisualMediaAttachments(
{
conversationId,
count: DEFAULT_MEDIA_FETCH_COUNT,
WhisperMessageCollection,
});
const documents = await Signal.Backbone.Conversation.fetchFileAttachments({
}
);
const documents = await Signal.Backbone.Conversation.fetchFileAttachments(
{
conversationId,
count: DEFAULT_DOCUMENTS_FETCH_COUNT,
WhisperMessageCollection,
});
}
);
// NOTE: Could we show grid previews from disk as well?
const loadMessages = Signal.Components.Types.Message
.loadWithObjectURL(Signal.Migrations.loadMessage);
const loadMessages = Signal.Components.Types.Message.loadWithObjectURL(
Signal.Migrations.loadMessage
);
const media = await loadMessages(rawMedia);
const { getAbsoluteAttachmentPath } = Signal.Migrations;
@ -624,13 +648,15 @@
case 'media': {
const mediaWithObjectURL = media.map(mediaMessage =>
Object.assign(
{},
mediaMessage,
{ objectURL: getAbsoluteAttachmentPath(mediaMessage.attachments[0].path) }
));
const selectedIndex = media.findIndex(mediaMessage =>
mediaMessage.id === message.id);
Object.assign({}, mediaMessage, {
objectURL: getAbsoluteAttachmentPath(
mediaMessage.attachments[0].path
),
})
);
const selectedIndex = media.findIndex(
mediaMessage => mediaMessage.id === message.id
);
this.lightboxGalleryView = new Whisper.ReactWrapperView({
Component: Signal.Components.LightboxGallery,
props: {
@ -684,7 +710,7 @@
// We need to iterate here because unseen non-messages do not contribute to
// the badge number, but should be reflected in the indicator's count.
this.model.messageCollection.forEach((model) => {
this.model.messageCollection.forEach(model => {
if (!model.get('unread')) {
return;
}
@ -744,7 +770,7 @@
const delta = endingHeight - startingHeight;
const height = this.view.outerHeight;
const newScrollPosition = (this.view.scrollPosition + delta) - height;
const newScrollPosition = this.view.scrollPosition + delta - height;
this.view.$el.scrollTop(newScrollPosition);
}, 1);
},
@ -759,15 +785,17 @@
// Avoiding await, since we want to capture the promise and make it available via
// this.inProgressFetch
// eslint-disable-next-line more/no-then
this.inProgressFetch = this.model.fetchContacts()
this.inProgressFetch = this.model
.fetchContacts()
.then(() => this.model.fetchMessages())
.then(() => {
this.$('.bar-container').hide();
this.model.messageCollection.where({ unread: 1 }).forEach((m) => {
this.model.messageCollection.where({ unread: 1 }).forEach(m => {
m.fetch();
});
this.inProgressFetch = null;
}).catch((error) => {
})
.catch(error => {
console.log(
'fetchMessages error:',
error && error.stack ? error.stack : error
@ -820,8 +848,10 @@
// The conversation is visible, but window is not focused
if (!this.lastSeenIndicator) {
this.resetLastSeenIndicator({ scroll: false });
} else if (this.view.atBottom() &&
this.model.get('unreadCount') === this.lastSeenIndicator.getCount()) {
} else if (
this.view.atBottom() &&
this.model.get('unreadCount') === this.lastSeenIndicator.getCount()
) {
// The count check ensures that the last seen indicator is still in
// sync with the real number of unread, so we can scroll to it.
// We only do this if we're at the bottom, because that signals that
@ -1215,9 +1245,8 @@
}),
});
const selector = storage.get('theme-setting') === 'ios'
? '.bottom-bar'
: '.send';
const selector =
storage.get('theme-setting') === 'ios' ? '.bottom-bar' : '.send';
this.$(selector).prepend(this.quoteView.el);
this.updateMessageFieldSize({});
@ -1275,7 +1304,7 @@
},
replace_colons(str) {
return str.replace(emoji.rx_colons, (m) => {
return str.replace(emoji.rx_colons, m => {
const idx = m.substr(1, m.length - 2);
const val = emoji.map.colons[idx];
if (val) {
@ -1310,7 +1339,12 @@
updateMessageFieldSize(event) {
const keyCode = event.which || event.keyCode;
if (keyCode === 13 && !event.altKey && !event.shiftKey && !event.ctrlKey) {
if (
keyCode === 13 &&
!event.altKey &&
!event.shiftKey &&
!event.ctrlKey
) {
// enter pressed - submit the form now
event.preventDefault();
this.$('.bottom-bar form').submit();
@ -1329,7 +1363,8 @@
? this.quoteView.$el.outerHeight(includeMargin)
: 0;
const height = this.$messageField.outerHeight() +
const height =
this.$messageField.outerHeight() +
$attachmentPreviews.outerHeight() +
this.$emojiPanelContainer.outerHeight() +
quoteHeight +
@ -1350,8 +1385,10 @@
},
isHidden() {
return this.$el.css('display') === 'none' ||
this.$('.panel').css('display') === 'none';
return (
this.$el.css('display') === 'none' ||
this.$('.panel').css('display') === 'none'
);
},
});
}());
})();

View File

@ -27,7 +27,7 @@
this.$('textarea').val(i18n('loading'));
// eslint-disable-next-line more/no-then
window.log.fetch().then((text) => {
window.log.fetch().then(text => {
this.$('textarea').val(text);
});
},
@ -63,7 +63,9 @@
});
this.$('.loading').removeClass('loading');
view.render();
this.$('.link').focus().select();
this.$('.link')
.focus()
.select();
},
});
}());
})();

View File

@ -11,6 +11,6 @@
templateName: 'generic-error',
render_attributes: function() {
return this.model;
}
},
});
})();

View File

@ -29,7 +29,7 @@
});
function makeImageThumbnail(size, objectUrl) {
return new Promise(((resolve, reject) => {
return new Promise((resolve, reject) => {
const img = document.createElement('img');
img.onerror = reject;
img.onload = () => {
@ -60,18 +60,20 @@
resolve(blob);
};
img.src = objectUrl;
}));
});
}
function makeVideoScreenshot(objectUrl) {
return new Promise(((resolve, reject) => {
return new Promise((resolve, reject) => {
const video = document.createElement('video');
function capture() {
const canvas = document.createElement('canvas');
canvas.width = video.videoWidth;
canvas.height = video.videoHeight;
canvas.getContext('2d').drawImage(video, 0, 0, canvas.width, canvas.height);
canvas
.getContext('2d')
.drawImage(video, 0, 0, canvas.width, canvas.height);
const image = window.dataURLToBlobSync(canvas.toDataURL('image/png'));
@ -81,7 +83,7 @@
}
video.addEventListener('canplay', capture);
video.addEventListener('error', (error) => {
video.addEventListener('error', error => {
console.log(
'makeVideoThumbnail error',
Signal.Types.Errors.toLogFormat(error)
@ -90,7 +92,7 @@
});
video.src = objectUrl;
}));
});
}
function blobToArrayBuffer(blob) {
@ -123,7 +125,7 @@
className: 'file-input',
initialize(options) {
this.$input = this.$('input[type=file]');
this.$input.click((e) => {
this.$input.click(e => {
e.stopPropagation();
});
this.thumb = new Whisper.AttachmentPreviewView();
@ -146,15 +148,18 @@
e.preventDefault();
// hack
if (this.window && this.window.chrome && this.window.chrome.fileSystem) {
this.window.chrome.fileSystem.chooseEntry({ type: 'openFile' }, (entry) => {
this.window.chrome.fileSystem.chooseEntry(
{ type: 'openFile' },
entry => {
if (!entry) {
return;
}
entry.file((file) => {
entry.file(file => {
this.file = file;
this.previewImages();
});
});
}
);
} else {
this.$input.click();
}
@ -178,14 +183,16 @@
},
autoScale(file) {
if (file.type.split('/')[0] !== 'image' ||
if (
file.type.split('/')[0] !== 'image' ||
file.type === 'image/gif' ||
file.type === 'image/tiff') {
file.type === 'image/tiff'
) {
// nothing to do
return Promise.resolve(file);
}
return new Promise(((resolve, reject) => {
return new Promise((resolve, reject) => {
const url = URL.createObjectURL(file);
const img = document.createElement('img');
img.onerror = reject;
@ -195,13 +202,19 @@
const maxSize = 6000 * 1024;
const maxHeight = 4096;
const maxWidth = 4096;
if (img.width <= maxWidth && img.height <= maxHeight && file.size <= maxSize) {
if (
img.width <= maxWidth &&
img.height <= maxHeight &&
file.size <= maxSize
) {
resolve(file);
return;
}
const canvas = loadImage.scale(img, {
canvas: true, maxWidth, maxHeight,
canvas: true,
maxWidth,
maxHeight,
});
let quality = 0.95;
@ -209,8 +222,10 @@
let blob;
do {
i -= 1;
blob = window.dataURLToBlobSync(canvas.toDataURL('image/jpeg', quality));
quality = (quality * maxSize) / blob.size;
blob = window.dataURLToBlobSync(
canvas.toDataURL('image/jpeg', quality)
);
quality = quality * maxSize / blob.size;
// NOTE: During testing with a large image, we observed the
// `quality` value being > 1. Should we clamp it to [0.5, 1.0]?
// See: https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toBlob#Syntax
@ -222,7 +237,7 @@
resolve(blob);
};
img.src = url;
}));
});
},
async previewImages() {
@ -271,21 +286,25 @@
const blob = await this.autoScale(file);
let limitKb = 1000000;
const blobType = file.type === 'image/gif'
? 'gif'
: contentType.split('/')[0];
const blobType =
file.type === 'image/gif' ? 'gif' : contentType.split('/')[0];
switch (blobType) {
case 'image':
limitKb = 6000; break;
limitKb = 6000;
break;
case 'gif':
limitKb = 25000; break;
limitKb = 25000;
break;
case 'audio':
limitKb = 100000; break;
limitKb = 100000;
break;
case 'video':
limitKb = 100000; break;
limitKb = 100000;
break;
default:
limitKb = 100000; break;
limitKb = 100000;
break;
}
if ((blob.size / 1024).toFixed(4) >= limitKb) {
const units = ['kB', 'MB', 'GB'];
@ -310,7 +329,9 @@
},
getFiles() {
const files = this.file ? [this.file] : Array.from(this.$input.prop('files'));
const files = this.file
? [this.file]
: Array.from(this.$input.prop('files'));
const promise = Promise.all(files.map(file => this.getFile(file)));
this.clearForm();
return promise;
@ -325,7 +346,7 @@
? textsecure.protobuf.AttachmentPointer.Flags.VOICE_MESSAGE
: null;
const setFlags = flags => (attachment) => {
const setFlags = flags => attachment => {
const newAttachment = Object.assign({}, attachment);
if (flags) {
newAttachment.flags = flags;
@ -345,9 +366,11 @@
// Scale and crop an image to 256px square
const size = 256;
const file = this.file || this.$input.prop('files')[0];
if (file === undefined ||
if (
file === undefined ||
file.type.split('/')[0] !== 'image' ||
file.type === 'image/gif') {
file.type === 'image/gif'
) {
// nothing to do
return Promise.resolve();
}
@ -362,9 +385,9 @@
// File -> Promise Attachment
readFile(file) {
return new Promise(((resolve, reject) => {
return new Promise((resolve, reject) => {
const FR = new FileReader();
FR.onload = (e) => {
FR.onload = e => {
resolve({
data: e.target.result,
contentType: file.type,
@ -375,7 +398,7 @@
FR.onerror = reject;
FR.onabort = reject;
FR.readAsArrayBuffer(file);
}));
});
},
clearForm() {
@ -390,9 +413,14 @@
},
deleteFiles(e) {
if (e) { e.stopPropagation(); }
if (e) {
e.stopPropagation();
}
this.clearForm();
this.$input.wrap('<form>').parent('form').trigger('reset');
this.$input
.wrap('<form>')
.parent('form')
.trigger('reset');
this.$input.unwrap();
this.file = null;
this.$input.trigger('change');
@ -450,4 +478,4 @@
Whisper.FileInputView.makeImageThumbnail = makeImageThumbnail;
Whisper.FileInputView.makeVideoThumbnail = makeVideoThumbnail;
Whisper.FileInputView.makeVideoScreenshot = makeVideoScreenshot;
}());
})();

View File

@ -18,8 +18,8 @@
collection: this.model,
className: 'members',
toInclude: {
listenBack: options.listenBack
}
listenBack: options.listenBack,
},
});
this.member_list_view.render();
@ -33,8 +33,8 @@
return {
members: i18n('groupMembers'),
summary: summary
summary: summary,
};
}
},
});
})();

View File

@ -7,8 +7,8 @@
window.Whisper = window.Whisper || {};
Whisper.GroupUpdateView = Backbone.View.extend({
tagName: "div",
className: "group-update",
tagName: 'div',
className: 'group-update',
render: function() {
//TODO l10n
if (this.model.left) {
@ -27,7 +27,6 @@
this.$el.text(messages.join(' '));
return this;
}
},
});
})();

View File

@ -12,6 +12,6 @@
},
render_attributes: function() {
return { content: this.content };
}
},
});
})();

View File

@ -25,7 +25,9 @@
var img = document.createElement('img');
img.onload = function() {
var canvas = loadImage.scale(img, {
canvas: true, maxWidth: 100, maxHeight: 100
canvas: true,
maxWidth: 100,
maxHeight: 100,
});
var ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0);
@ -35,7 +37,7 @@
img.src = svgurl;
});
}
},
});
var COLORS = {
@ -53,7 +55,6 @@
orange: '#FF9800',
deep_orange: '#FF5722',
amber: '#FFB300',
blue_grey : '#607D8B'
blue_grey: '#607D8B',
};
})();

View File

@ -18,11 +18,11 @@
events: {
'click .show-safety-number': 'showSafetyNumber',
'click .send-anyway': 'sendAnyway',
'click .cancel': 'cancel'
'click .cancel': 'cancel',
},
showSafetyNumber: function() {
var view = new Whisper.KeyVerificationPanelView({
model: this.model
model: this.model,
});
this.listenBack(view);
},
@ -39,13 +39,16 @@
send = i18n('resend');
}
var errorExplanation = i18n('identityKeyErrorOnSend', [this.model.getTitle(), this.model.getTitle()]);
var errorExplanation = i18n('identityKeyErrorOnSend', [
this.model.getTitle(),
this.model.getTitle(),
]);
return {
errorExplanation: errorExplanation,
showSafetyNumber: i18n('showSafetyNumber'),
sendAnyway: send,
cancel : i18n('cancel')
cancel: i18n('cancel'),
};
}
},
});
})();

View File

@ -36,7 +36,7 @@
},
reset: function() {
return Whisper.Database.clear();
}
},
};
Whisper.ImportView = Whisper.View.extend({
@ -102,16 +102,19 @@
this.trigger('cancel');
},
onImport: function() {
window.Signal.Backup.getDirectoryForImport().then(function(directory) {
window.Signal.Backup.getDirectoryForImport().then(
function(directory) {
this.doImport(directory);
}.bind(this), function(error) {
}.bind(this),
function(error) {
if (error.name !== 'ChooseError') {
console.log(
'Error choosing directory:',
error && error.stack ? error.stack : error
);
}
});
}
);
},
onRegister: function() {
// AppView listens for this, and opens up InstallView to the QR code step to
@ -127,15 +130,19 @@
this.render();
// Wait for prior database interaction to complete
this.pending = this.pending.then(function() {
this.pending = this.pending
.then(function() {
// For resilience to interruption, clear database both before and on failure
return Whisper.Import.reset();
}).then(function() {
})
.then(function() {
return Promise.all([
Whisper.Import.start(),
window.Signal.Backup.importFromDirectory(directory)
window.Signal.Backup.importFromDirectory(directory),
]);
}).then(function(results) {
})
.then(
function(results) {
var importResult = results[1];
// A full import changes so much we need a restart of the app
@ -146,34 +153,46 @@
// 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);
}.bind(this)
)
.catch(
function(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();
}.bind(this));
}.bind(this)
);
},
finishLightImport: function(directory) {
ConversationController.reset();
return ConversationController.load().then(function() {
return ConversationController.load()
.then(function() {
return Promise.all([
Whisper.Import.saveLocation(directory),
Whisper.Import.complete(),
]);
}).then(function() {
})
.then(
function() {
this.state = State.LIGHT_COMPLETE;
this.render();
}.bind(this));
}.bind(this)
);
},
finishFullImport: function(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()
return storage
.fetch()
.then(function() {
return Promise.all([
// Clearing any migration-related state inherited from the Chrome App
@ -183,12 +202,15 @@
storage.remove('migrationStorageLocation'),
Whisper.Import.saveLocation(directory),
Whisper.Import.complete()
Whisper.Import.complete(),
]);
}).then(function() {
})
.then(
function() {
this.state = State.COMPLETE;
this.render();
}.bind(this));
}
}.bind(this)
);
},
});
})();

View File

@ -15,7 +15,10 @@
open(conversation) {
const id = `conversation-${conversation.cid}`;
if (id !== this.el.firstChild.id) {
this.$el.first().find('video, audio').each(function pauseMedia() {
this.$el
.first()
.find('video, audio')
.each(function pauseMedia() {
this.pause();
});
let $el = this.$(`#${id}`);
@ -65,7 +68,6 @@
},
});
Whisper.AppLoadingScreen = Whisper.View.extend({
templateName: 'app-loading-screen',
className: 'app-loading-screen',
@ -147,7 +149,8 @@
);
this.networkStatusView = new Whisper.NetworkStatusView();
this.$el.find('.network-status-container')
this.$el
.find('.network-status-container')
.append(this.networkStatusView.render().el);
extension.windows.onClosed(() => {
@ -194,7 +197,8 @@
default:
console.log(
'Whisper.InboxView::startConnectionListener:',
'Unknown web socket status:', status
'Unknown web socket status:',
status
);
break;
}
@ -254,7 +258,9 @@
openConversation(e, conversation) {
this.searchView.hideHints();
if (conversation) {
this.conversation_stack.open(ConversationController.get(conversation.id));
this.conversation_stack.open(
ConversationController.get(conversation.id)
);
this.focusConversation();
}
},
@ -279,4 +285,4 @@
};
},
});
}());
})();

View File

@ -34,23 +34,24 @@
this.on('disconnected', this.reconnect);
// Keep data around if it's a re-link, or the middle of a light import
this.shouldRetainData = Whisper.Registration.everDone() || options.hasExistingData;
this.shouldRetainData =
Whisper.Registration.everDone() || options.hasExistingData;
},
render_attributes: function() {
var errorMessage;
if (this.error) {
if (this.error.name === 'HTTPError'
&& this.error.code == TOO_MANY_DEVICES) {
if (
this.error.name === 'HTTPError' &&
this.error.code == TOO_MANY_DEVICES
) {
errorMessage = i18n('installTooManyDevices');
}
else if (this.error.name === 'HTTPError'
&& this.error.code == CONNECTION_ERROR) {
} else if (
this.error.name === 'HTTPError' &&
this.error.code == CONNECTION_ERROR
) {
errorMessage = i18n('installConnectionFailed');
}
else if (this.error.message === 'websocket closed') {
} else if (this.error.message === 'websocket closed') {
// AccountManager.registerSecondDevice uses this specific
// 'websocket closed' error message
errorMessage = i18n('installConnectionFailed');
@ -95,10 +96,12 @@
var accountManager = getAccountManager();
accountManager.registerSecondDevice(
accountManager
.registerSecondDevice(
this.setProvisioningUrl.bind(this),
this.confirmNumber.bind(this)
).catch(this.handleDisconnect.bind(this));
)
.catch(this.handleDisconnect.bind(this));
},
handleDisconnect: function(e) {
console.log('provisioning failed', e.stack);
@ -108,9 +111,10 @@
if (e.message === 'websocket closed') {
this.trigger('disconnected');
} else if (e.name !== 'HTTPError'
|| (e.code !== CONNECTION_ERROR && e.code !== TOO_MANY_DEVICES)) {
} else if (
e.name !== 'HTTPError' ||
(e.code !== CONNECTION_ERROR && e.code !== TOO_MANY_DEVICES)
) {
throw e;
}
},
@ -155,8 +159,10 @@
this.selectStep(Steps.ENTER_NAME);
this.setDeviceNameDefault();
return new Promise(function(resolve, reject) {
this.$('#link-phone').submit(function(e) {
return new Promise(
function(resolve, reject) {
this.$('#link-phone').submit(
function(e) {
e.stopPropagation();
e.preventDefault();
@ -189,8 +195,10 @@
);
finish();
});
}.bind(this));
}.bind(this));
}.bind(this)
);
}.bind(this)
);
},
});
})();

View File

@ -17,15 +17,15 @@
this.theirKey = options.newKey;
}
this.loadKeys().then(function() {
this.loadKeys().then(
function() {
this.listenTo(this.model, 'change', this.render);
}.bind(this));
}.bind(this)
);
},
loadKeys: function() {
return Promise.all([
this.loadTheirKey(),
this.loadOurKey(),
]).then(this.generateSecurityNumber.bind(this))
return Promise.all([this.loadTheirKey(), this.loadOurKey()])
.then(this.generateSecurityNumber.bind(this))
.then(this.render.bind(this));
//.then(this.makeQRCode.bind(this));
},
@ -37,32 +37,37 @@
);
},
loadTheirKey: function() {
return textsecure.storage.protocol.loadIdentityKey(
this.model.id
).then(function(theirKey) {
return textsecure.storage.protocol.loadIdentityKey(this.model.id).then(
function(theirKey) {
this.theirKey = theirKey;
}.bind(this));
}.bind(this)
);
},
loadOurKey: function() {
return textsecure.storage.protocol.loadIdentityKey(
this.ourNumber
).then(function(ourKey) {
return textsecure.storage.protocol.loadIdentityKey(this.ourNumber).then(
function(ourKey) {
this.ourKey = ourKey;
}.bind(this));
}.bind(this)
);
},
generateSecurityNumber: function() {
return new libsignal.FingerprintGenerator(5200).createFor(
this.ourNumber, this.ourKey, this.model.id, this.theirKey
).then(function(securityNumber) {
return new libsignal.FingerprintGenerator(5200)
.createFor(this.ourNumber, this.ourKey, this.model.id, this.theirKey)
.then(
function(securityNumber) {
this.securityNumber = securityNumber;
}.bind(this));
}.bind(this)
);
},
onSafetyNumberChanged: function() {
this.model.getProfiles().then(this.loadKeys.bind(this));
var dialog = new Whisper.ConfirmationDialogView({
message: i18n('changedRightAfterVerify', [this.model.getTitle(), this.model.getTitle()]),
hideCancel: true
message: i18n('changedRightAfterVerify', [
this.model.getTitle(),
this.model.getTitle(),
]),
hideCancel: true,
});
dialog.$el.insertBefore(this.el);
@ -70,7 +75,10 @@
},
toggleVerified: function() {
this.$('button.verify').attr('disabled', true);
this.model.toggleVerified().catch(function(result) {
this.model
.toggleVerified()
.catch(
function(result) {
if (result instanceof Error) {
if (result.name === 'OutgoingIdentityKeyError') {
this.onSafetyNumberChanged();
@ -89,9 +97,13 @@
});
}
}
}.bind(this)).then(function() {
}.bind(this)
)
.then(
function() {
this.$('button.verify').removeAttr('disabled');
}.bind(this));
}.bind(this)
);
},
render_attributes: function() {
var s = this.securityNumber;
@ -103,19 +115,24 @@
var yourSafetyNumberWith = i18n('yourSafetyNumberWith', name);
var isVerified = this.model.isVerified();
var verifyButton = isVerified ? i18n('unverify') : i18n('verify');
var verifiedStatus = isVerified ? i18n('isVerified', name) : i18n('isNotVerified', name);
var verifiedStatus = isVerified
? i18n('isVerified', name)
: i18n('isNotVerified', name);
return {
learnMore: i18n('learnMore'),
theirKeyUnknown: i18n('theirIdentityUnknown'),
yourSafetyNumberWith : i18n('yourSafetyNumberWith', this.model.getTitle()),
yourSafetyNumberWith: i18n(
'yourSafetyNumberWith',
this.model.getTitle()
),
verifyHelp: i18n('verifyHelp', this.model.getTitle()),
verifyButton: verifyButton,
hasTheirKey: this.theirKey !== undefined,
chunks: chunks,
isVerified: isVerified,
verifiedStatus : verifiedStatus
verifiedStatus: verifiedStatus,
};
}
},
});
})();

View File

@ -25,12 +25,14 @@
},
render_attributes: function() {
var unreadMessages = this.count === 1 ? i18n('unreadMessage')
var unreadMessages =
this.count === 1
? i18n('unreadMessage')
: i18n('unreadMessages', [this.count]);
return {
unreadMessages: unreadMessages
unreadMessages: unreadMessages,
};
}
},
});
})();

View File

@ -35,6 +35,6 @@
render: function() {
this.addAll();
return this;
}
},
});
})();

View File

@ -25,14 +25,14 @@
});
},
events: {
'click': 'onClick'
click: 'onClick',
},
onClick: function() {
if (this.outgoingKeyError) {
var view = new Whisper.IdentityKeySendErrorPanelView({
model: this.model,
listenBack: this.listenBack,
resetPanel: this.resetPanel
resetPanel: this.resetPanel,
});
this.listenTo(view, 'send-anyway', this.onSendAnyway);
@ -44,19 +44,32 @@
}
},
forceSend: function() {
this.model.updateVerified().then(function() {
this.model
.updateVerified()
.then(
function() {
if (this.model.isUnverified()) {
return this.model.setVerifiedDefault();
}
}.bind(this)).then(function() {
}.bind(this)
)
.then(
function() {
return this.model.isUntrusted();
}.bind(this)).then(function(untrusted) {
}.bind(this)
)
.then(
function(untrusted) {
if (untrusted) {
return this.model.setApproved();
}
}.bind(this)).then(function() {
}.bind(this)
)
.then(
function() {
this.message.resend(this.outgoingKeyError.number);
}.bind(this));
}.bind(this)
);
},
onSendAnyway: function() {
if (this.outgoingKeyError) {
@ -72,9 +85,9 @@
avatar: this.model.getAvatar(),
errors: this.errors,
showErrorButton: showButton,
errorButtonLabel : i18n('view')
errorButtonLabel: i18n('view'),
};
}
},
});
Whisper.MessageDetailView = Whisper.View.extend({
@ -91,7 +104,7 @@
this.listenTo(this.model, 'change', this.render);
},
events: {
'click button.delete': 'onDelete'
'click button.delete': 'onDelete',
},
onDelete: function() {
var dialog = new Whisper.ConfirmationDialogView({
@ -100,7 +113,7 @@
resolve: function() {
this.model.destroy();
this.resetPanel();
}.bind(this)
}.bind(this),
});
this.$el.prepend(dialog.el);
@ -119,9 +132,11 @@
ids = this.conversation.getRecipients();
}
}
return Promise.all(ids.map(function(number) {
return Promise.all(
ids.map(function(number) {
return ConversationController.getOrCreateAndWait(number, 'private');
}));
})
);
},
renderContact: function(contact) {
var view = new ContactView({
@ -129,18 +144,23 @@
errors: this.grouped[contact.id],
listenBack: this.listenBack,
resetPanel: this.resetPanel,
message: this.model
message: this.model,
}).render();
this.$('.contacts').append(view.el);
},
render: function() {
var errorsWithoutNumber = _.reject(this.model.get('errors'), function(error) {
var errorsWithoutNumber = _.reject(this.model.get('errors'), function(
error
) {
return Boolean(error.number);
});
this.$el.html(Mustache.render(_.result(this, 'template', ''), {
this.$el.html(
Mustache.render(_.result(this, 'template', ''), {
sent_at: moment(this.model.get('sent_at')).format('LLLL'),
received_at : this.model.isIncoming() ? moment(this.model.get('received_at')).format('LLLL') : null,
received_at: this.model.isIncoming()
? moment(this.model.get('received_at')).format('LLLL')
: null,
tofrom: this.model.isIncoming() ? i18n('from') : i18n('to'),
errors: errorsWithoutNumber,
title: i18n('messageDetail'),
@ -148,21 +168,26 @@
received: i18n('received'),
errorLabel: i18n('error'),
deleteLabel: i18n('deleteMessage'),
retryDescription: i18n('retryDescription')
}));
retryDescription: i18n('retryDescription'),
})
);
this.view.$el.prependTo(this.$('.message-container'));
this.grouped = _.groupBy(this.model.get('errors'), 'number');
this.getContacts().then(function(contacts) {
_.sortBy(contacts, function(c) {
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));
}
}.bind(this)
).forEach(this.renderContact.bind(this));
}.bind(this)
);
},
});
})();

View File

@ -10,14 +10,17 @@
className: 'message-list',
itemView: Whisper.MessageView,
events: {
'scroll': 'onScroll',
scroll: 'onScroll',
},
initialize: function() {
Whisper.ListView.prototype.initialize.call(this);
this.triggerLazyScroll = _.debounce(function() {
this.triggerLazyScroll = _.debounce(
function() {
this.$el.trigger('lazyScroll');
}.bind(this), 500);
}.bind(this),
500
);
},
onScroll: function() {
this.measureScrollPosition();
@ -36,7 +39,8 @@
return this.bottomOffset < 30;
},
measureScrollPosition: function() {
if (this.el.scrollHeight === 0) { // hidden
if (this.el.scrollHeight === 0) {
// hidden
return;
}
this.outerHeight = this.$el.outerHeight();

View File

@ -71,7 +71,10 @@
const elapsed = (totalTime - remainingTime) / totalTime;
this.$('.sand').css('transform', `translateY(${elapsed * 100}%)`);
this.$el.css('display', 'inline-block');
this.timeout = setTimeout(this.update.bind(this), Math.max(totalTime / 100, 500));
this.timeout = setTimeout(
this.update.bind(this),
Math.max(totalTime / 100, 500)
);
}
return this;
},
@ -195,9 +198,17 @@
this.listenTo(this.model, 'change:body', this.render);
this.listenTo(this.model, 'change:delivered', this.renderDelivered);
this.listenTo(this.model, 'change:read_by', this.renderRead);
this.listenTo(this.model, 'change:expirationStartTimestamp', this.renderExpiring);
this.listenTo(
this.model,
'change:expirationStartTimestamp',
this.renderExpiring
);
this.listenTo(this.model, 'change', this.onChange);
this.listenTo(this.model, 'change:flags change:group_update', this.renderControl);
this.listenTo(
this.model,
'change:flags change:group_update',
this.renderControl
);
this.listenTo(this.model, 'destroy', this.onDestroy);
this.listenTo(this.model, 'unload', this.onUnload);
this.listenTo(this.model, 'expired', this.onExpired);
@ -225,7 +236,7 @@
this.model.get('errors'),
this.model.isReplayableError.bind(this.model)
);
_.map(retrys, 'number').forEach((number) => {
_.map(retrys, 'number').forEach(number => {
this.model.resend(number);
});
},
@ -251,7 +262,7 @@
},
onExpired() {
this.$el.addClass('expired');
this.$el.find('.bubble').one('webkitAnimationEnd animationend', (e) => {
this.$el.find('.bubble').one('webkitAnimationEnd animationend', e => {
if (e.target === this.$('.bubble')[0]) {
this.remove();
}
@ -284,8 +295,9 @@
// as our tests rely on `onUnload` synchronously removing the view from
// the DOM.
// eslint-disable-next-line more/no-then
this.loadAttachmentViews()
.then(views => views.forEach(view => view.unload()));
this.loadAttachmentViews().then(views =>
views.forEach(view => view.unload())
);
// No need to handle this one, since it listens to 'unload' itself:
// this.timerView
@ -321,7 +333,9 @@
}
},
renderDelivered() {
if (this.model.get('delivered')) { this.$el.addClass('delivered'); }
if (this.model.get('delivered')) {
this.$el.addClass('delivered');
}
},
renderRead() {
if (!_.isEmpty(this.model.get('read_by'))) {
@ -345,7 +359,9 @@
}
if (_.size(errors) > 0) {
if (this.model.isIncoming()) {
this.$('.content').text(this.model.getDescription()).addClass('error-message');
this.$('.content')
.text(this.model.getDescription())
.addClass('error-message');
}
this.errorIconView = new ErrorIconView({ model: errors[0] });
this.errorIconView.render().$el.appendTo(this.$('.bubble'));
@ -354,7 +370,9 @@
if (!el || el.length === 0) {
this.$('.inner-bubble').append("<div class='content'></div>");
}
this.$('.content').text(i18n('noContents')).addClass('error-message');
this.$('.content')
.text(i18n('noContents'))
.addClass('error-message');
}
this.$('.meta .hasRetry').remove();
@ -461,18 +479,24 @@
const hasAttachments = attachments && attachments.length > 0;
const hasBody = this.hasTextContents();
this.$el.html(Mustache.render(_.result(this, 'template', ''), {
this.$el.html(
Mustache.render(
_.result(this, 'template', ''),
{
message: this.model.get('body'),
hasBody,
timestamp: this.model.get('sent_at'),
sender: (contact && contact.getTitle()) || '',
avatar: (contact && contact.getAvatar()),
profileName: (contact && contact.getProfileName()),
avatar: contact && contact.getAvatar(),
profileName: contact && contact.getProfileName(),
innerBubbleClasses: this.isImageWithoutCaption() ? '' : 'with-tail',
hoverIcon: !hasErrors,
hasAttachments,
reply: i18n('replyToMessage'),
}, this.render_partials()));
},
this.render_partials()
)
);
this.timeStampView.setElement(this.$('.timestamp'));
this.timeStampView.update();
@ -498,7 +522,9 @@
// as our code / Backbone seems to rely on `render` synchronously returning
// `this` instead of `Promise MessageView` (this):
// eslint-disable-next-line more/no-then
this.loadAttachmentViews().then(views => this.renderAttachmentViews(views));
this.loadAttachmentViews().then(views =>
this.renderAttachmentViews(views)
);
return this;
},
@ -523,8 +549,10 @@
}
const attachments = this.model.get('attachments') || [];
const loadedAttachmentViews = Promise.all(attachments.map(attachment =>
new Promise(async (resolve) => {
const loadedAttachmentViews = Promise.all(
attachments.map(
attachment =>
new Promise(async resolve => {
const attachmentWithData = await loadAttachmentData(attachment);
const view = new Whisper.AttachmentView({
model: attachmentWithData,
@ -538,7 +566,9 @@
});
view.render();
})));
})
)
);
// Memoize attachment views to avoid double loading:
this.loadedAttachmentViews = loadedAttachmentViews;
@ -550,8 +580,10 @@
},
renderAttachmentView(view) {
if (!view.updated) {
throw new Error('Invariant violation:' +
' Cannot render an attachment view that isnt ready');
throw new Error(
'Invariant violation:' +
' Cannot render an attachment view that isnt ready'
);
}
const parent = this.$('.attachments')[0];
@ -570,4 +602,4 @@
this.trigger('afterChangeHeight');
},
});
}());
})();

View File

@ -10,9 +10,11 @@
this.$el.hide();
this.renderIntervalHandle = setInterval(this.update.bind(this), 5000);
extension.windows.onClosed(function () {
extension.windows.onClosed(
function() {
clearInterval(this.renderIntervalHandle);
}.bind(this));
}.bind(this)
);
setTimeout(this.finishConnectingGracePeriod.bind(this), 5000);
@ -34,10 +36,13 @@
setSocketReconnectInterval: function(millis) {
this.socketReconnectWaitDuration = moment.duration(millis);
},
navigatorOnLine: function() { return navigator.onLine; },
getSocketStatus: function() { return window.getSocketStatus(); },
navigatorOnLine: function() {
return navigator.onLine;
},
getSocketStatus: function() {
return window.getSocketStatus();
},
getNetworkStatus: function() {
var message = '';
var instructions = '';
var hasInterruption = false;
@ -65,11 +70,16 @@
break;
}
if (socketStatus == WebSocket.CONNECTING && !this.withinConnectingGracePeriod) {
if (
socketStatus == WebSocket.CONNECTING &&
!this.withinConnectingGracePeriod
) {
hasInterruption = true;
}
if (this.socketReconnectWaitDuration.asSeconds() > 0) {
instructions = i18n('attemptingReconnection', [this.socketReconnectWaitDuration.asSeconds()]);
instructions = i18n('attemptingReconnection', [
this.socketReconnectWaitDuration.asSeconds(),
]);
}
if (!this.navigatorOnLine()) {
hasInterruption = true;
@ -88,7 +98,7 @@
instructions: instructions,
hasInterruption: hasInterruption,
action: action,
buttonClass: buttonClass
buttonClass: buttonClass,
};
},
update: function() {
@ -102,13 +112,9 @@
this.render();
if (this.model.attributes.hasInterruption) {
this.$el.slideDown();
}
else {
} else {
this.$el.hide();
}
}
},
});
})();

View File

@ -6,29 +6,31 @@
window.Whisper = window.Whisper || {};
Whisper.NewGroupUpdateView = Whisper.View.extend({
tagName: "div",
tagName: 'div',
className: 'new-group-update',
templateName: 'new-group-update',
initialize: function(options) {
this.render();
this.avatarInput = new Whisper.FileInputView({
el: this.$('.group-avatar'),
window: options.window
window: options.window,
});
this.recipients_view = new Whisper.RecipientsInputView();
this.listenTo(this.recipients_view.typeahead, 'sync', function() {
this.model.contactCollection.models.forEach(function(model) {
this.model.contactCollection.models.forEach(
function(model) {
if (this.recipients_view.typeahead.get(model)) {
this.recipients_view.typeahead.remove(model);
}
}.bind(this));
}.bind(this)
);
});
this.recipients_view.$el.insertBefore(this.$('.container'));
this.member_list_view = new Whisper.ContactListView({
collection: this.model.contactCollection,
className: 'members'
className: 'members',
});
this.member_list_view.render();
this.$('.scrollable').append(this.member_list_view.el);
@ -51,17 +53,21 @@
render_attributes: function() {
return {
name: this.model.getTitle(),
avatar: this.model.getAvatar()
avatar: this.model.getAvatar(),
};
},
send: function() {
return this.avatarInput.getThumbnail().then(function(avatarFile) {
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'))
members: _.union(
this.model.get('members'),
this.recipients_view.recipients.pluck('id')
),
};
if (avatarFile) {
attrs.avatar = avatarFile;
@ -76,7 +82,8 @@
this.model.updateGroup(group_update);
this.goBack();
}.bind(this));
}
}.bind(this)
);
},
});
})();

View File

@ -13,12 +13,14 @@
this.$('input.number').intlTelInput();
},
events: {
'change': 'validateNumber',
'keyup': 'validateNumber'
change: 'validateNumber',
keyup: 'validateNumber',
},
validateNumber: function() {
var input = this.$('input.number');
var regionCode = this.$('li.active').attr('data-country-code').toUpperCase();
var regionCode = this.$('li.active')
.attr('data-country-code')
.toUpperCase();
var number = input.val();
var parsedNumber = libphonenumber.util.parseNumber(number, regionCode);
@ -31,6 +33,6 @@
input.trigger('validation');
return parsedNumber.e164;
}
},
});
})();

View File

@ -44,4 +44,4 @@
Backbone.View.prototype.remove.call(this);
},
});
}());
})();

View File

@ -10,21 +10,21 @@
'name',
'e164_number',
'national_number',
'international_number'
'international_number',
],
database: Whisper.Database,
storeName: 'conversations',
model: Whisper.Conversation,
fetchContacts: function() {
return this.fetch({ reset: true, conditions: { type: 'private' } });
}
},
});
Whisper.ContactPillView = Whisper.View.extend({
tagName: 'span',
className: 'recipient',
events: {
'click .remove': 'removeModel'
'click .remove': 'removeModel',
},
templateName: 'contact_pill',
initialize: function() {
@ -39,11 +39,11 @@
},
render_attributes: function() {
return { name: this.model.getTitle() };
}
},
});
Whisper.RecipientListView = Whisper.ListView.extend({
itemView: Whisper.ContactPillView
itemView: Whisper.ContactPillView,
});
Whisper.SuggestionView = Whisper.ConversationListItemView.extend({
@ -52,7 +52,7 @@
});
Whisper.SuggestionListView = Whisper.ConversationListView.extend({
itemView: Whisper.SuggestionView
itemView: Whisper.SuggestionView,
});
Whisper.RecipientsInputView = Whisper.View.extend({
@ -68,13 +68,13 @@
// Collection of recipients selected for the new message
this.recipients = new Whisper.ConversationCollection([], {
comparator: false
comparator: false,
});
// View to display the selected recipients
this.recipients_view = new Whisper.RecipientListView({
collection: this.recipients,
el: this.$('.recipients')
el: this.$('.recipients'),
});
// Collection of contacts to match user input against
@ -84,17 +84,18 @@
// View to display the matched contacts from typeahead
this.typeahead_view = new Whisper.SuggestionListView({
collection: new Whisper.ConversationCollection([], {
comparator: function(m) { return m.getTitle().toLowerCase(); }
})
comparator: function(m) {
return m.getTitle().toLowerCase();
},
}),
});
this.$('.contacts').append(this.typeahead_view.el);
this.initNewContact();
this.listenTo(this.typeahead, 'reset', this.filterContacts);
},
render_attributes: function() {
return { placeholder: this.placeholder || "name or phone number" };
return { placeholder: this.placeholder || 'name or phone number' };
},
events: {
@ -113,9 +114,7 @@
} else {
this.new_contact_view.$el.hide();
}
this.typeahead_view.collection.reset(
this.typeahead.typeahead(query)
);
this.typeahead_view.collection.reset(this.typeahead.typeahead(query));
} else {
this.resetTypeahead();
}
@ -131,8 +130,8 @@
el: this.$new_contact,
model: ConversationController.create({
type: 'private',
newContact: true
})
newContact: true,
}),
}).render();
},
@ -176,10 +175,8 @@
this.typeahead_view.collection.reset([]);
},
maybeNumber: function(number) {
return number.match(/^\+?[0-9]*$/);
}
},
});
})();

View File

@ -16,7 +16,7 @@
events: {
'click .close': 'close',
'click .finish': 'finish',
'close': 'close'
close: 'close',
},
updateTime: function() {
var duration = moment.duration(Date.now() - this.startTime, 'ms');
@ -62,19 +62,23 @@
this.input = this.context.createGain();
this.recorder = new WebAudioRecorder(this.input, {
encoding: 'mp3',
workerDir: 'js/' // must end with slash
workerDir: 'js/', // must end with slash
});
this.recorder.onComplete = this.handleBlob.bind(this);
this.recorder.onError = this.onError;
navigator.webkitGetUserMedia({ audio: true }, function(stream) {
navigator.webkitGetUserMedia(
{ audio: true },
function(stream) {
this.source = this.context.createMediaStreamSource(stream);
this.source.connect(this.input);
}.bind(this), this.onError.bind(this));
}.bind(this),
this.onError.bind(this)
);
this.recorder.startRecording();
},
onError: function(error) {
console.log(error.stack);
this.close();
}
},
});
})();

View File

@ -32,8 +32,8 @@
return {
cssClass: cssClass,
moreBelow: moreBelow
moreBelow: moreBelow,
};
}
},
});
})();

View File

@ -20,7 +20,7 @@
this.populate();
},
events: {
'change': 'change'
change: 'change',
},
change: function(e) {
var value = e.target.checked;
@ -43,7 +43,7 @@
this.populate();
},
events: {
'change': 'change'
change: 'change',
},
change: function(e) {
var value = this.$(e.target).val();
@ -67,26 +67,26 @@
new RadioButtonGroupView({
el: this.$('.notification-settings'),
defaultValue: 'message',
name: 'notification-setting'
name: 'notification-setting',
});
new RadioButtonGroupView({
el: this.$('.theme-settings'),
defaultValue: 'android',
name: 'theme-setting',
event: 'change-theme'
event: 'change-theme',
});
if (Settings.isAudioNotificationSupported()) {
new CheckboxView({
el: this.$('.audio-notification-setting'),
defaultValue: false,
name: 'audio-notification'
name: 'audio-notification',
});
}
new CheckboxView({
el: this.$('.menu-bar-setting'),
defaultValue: false,
name: 'hide-menu-bar',
event: 'change-hide-menu'
event: 'change-hide-menu',
});
if (textsecure.storage.user.getDeviceId() != '1') {
var syncView = new SyncView().render();
@ -160,10 +160,7 @@
},
async clearAllData() {
try {
await Promise.all([
Logs.deleteAll(),
Database.drop(),
]);
await Promise.all([Logs.deleteAll(), Database.drop()]);
} catch (error) {
console.log(
'Something went wrong deleting all data:',
@ -193,7 +190,7 @@
templateName: 'syncSettings',
className: 'syncSettings',
events: {
'click .sync': 'sync'
'click .sync': 'sync',
},
enable: function() {
this.$('.sync').text(i18n('syncNow'));
@ -223,7 +220,7 @@
syncRequest.addEventListener('success', this.onsuccess.bind(this));
syncRequest.addEventListener('timeout', this.ontimeout.bind(this));
} else {
console.log("Tried to sync from device 1");
console.log('Tried to sync from device 1');
}
},
render_attributes: function() {
@ -231,7 +228,7 @@
sync: i18n('sync'),
syncNow: i18n('syncNow'),
syncExplanation: i18n('syncExplanation'),
syncFailed: i18n('syncFailed')
syncFailed: i18n('syncFailed'),
};
var date = storage.get('synced_at');
if (date) {
@ -241,6 +238,6 @@
attrs.syncTime = date.toLocaleTimeString();
}
return attrs;
}
},
});
})();

View File

@ -17,7 +17,9 @@
if (number) {
this.$('input.number').val(number);
}
this.phoneView = new Whisper.PhoneInputView({el: this.$('#phone-number-input')});
this.phoneView = new Whisper.PhoneInputView({
el: this.$('#phone-number-input'),
});
this.$('#error').hide();
},
events: {
@ -29,24 +31,37 @@
},
verifyCode: function(e) {
var number = this.phoneView.validateNumber();
var verificationCode = $('#code').val().replace(/\D+/g, '');
var verificationCode = $('#code')
.val()
.replace(/\D+/g, '');
this.accountManager.registerSingleDevice(number, verificationCode).then(function() {
this.accountManager
.registerSingleDevice(number, verificationCode)
.then(
function() {
this.$el.trigger('openInbox');
}.bind(this)).catch(this.log.bind(this));
}.bind(this)
)
.catch(this.log.bind(this));
},
log: function(s) {
console.log(s);
this.$('#status').text(s);
},
validateCode: function() {
var verificationCode = $('#code').val().replace(/\D/g, '');
var verificationCode = $('#code')
.val()
.replace(/\D/g, '');
if (verificationCode.length == 6) {
return verificationCode;
}
},
displayError: function(error) {
this.$('#error').hide().text(error).addClass('in').fadeIn();
this.$('#error')
.hide()
.text(error)
.addClass('in')
.fadeIn();
},
onValidation: function() {
if (this.$('#number-container').hasClass('valid')) {
@ -67,8 +82,12 @@
this.$('#error').hide();
var number = this.phoneView.validateNumber();
if (number) {
this.accountManager.requestVoiceVerification(number).catch(this.displayError.bind(this));
this.$('#step2').addClass('in').fadeIn();
this.accountManager
.requestVoiceVerification(number)
.catch(this.displayError.bind(this));
this.$('#step2')
.addClass('in')
.fadeIn();
} else {
this.$('#number-container').addClass('invalid');
}
@ -78,11 +97,15 @@
$('#error').hide();
var number = this.phoneView.validateNumber();
if (number) {
this.accountManager.requestSMSVerification(number).catch(this.displayError.bind(this));
this.$('#step2').addClass('in').fadeIn();
this.accountManager
.requestSMSVerification(number)
.catch(this.displayError.bind(this));
this.$('#step2')
.addClass('in')
.fadeIn();
} else {
this.$('#number-container').addClass('invalid');
}
}
},
});
})();

View File

@ -13,7 +13,7 @@
this.clearTimeout();
var millis_now = Date.now();
var millis = this.$el.data('timestamp');
if (millis === "") {
if (millis === '') {
return;
}
if (millis >= millis_now) {
@ -27,7 +27,9 @@
var millis_since = millis_now - millis;
if (this.delay) {
if (this.delay < 0) { this.delay = 1000; }
if (this.delay < 0) {
this.delay = 1000;
}
this.timeout = setTimeout(this.update.bind(this), this.delay);
}
},
@ -47,22 +49,34 @@
this.delay = null;
return timestamp.format(this._format.M);
} else if (timediff.days() > 0) {
this.delay = moment(timestamp).add(timediff.days() + 1,'d').diff(now);
this.delay = moment(timestamp)
.add(timediff.days() + 1, 'd')
.diff(now);
return timestamp.format(this._format.d);
} else if (timediff.hours() > 1) {
this.delay = moment(timestamp).add(timediff.hours() + 1,'h').diff(now);
this.delay = moment(timestamp)
.add(timediff.hours() + 1, 'h')
.diff(now);
return this.relativeTime(timediff.hours(), 'h');
} else if (timediff.hours() === 1) {
this.delay = moment(timestamp).add(timediff.hours() + 1,'h').diff(now);
this.delay = moment(timestamp)
.add(timediff.hours() + 1, 'h')
.diff(now);
return this.relativeTime(timediff.hours(), 'h');
} else if (timediff.minutes() > 1) {
this.delay = moment(timestamp).add(timediff.minutes() + 1,'m').diff(now);
this.delay = moment(timestamp)
.add(timediff.minutes() + 1, 'm')
.diff(now);
return this.relativeTime(timediff.minutes(), 'm');
} else if (timediff.minutes() === 1) {
this.delay = moment(timestamp).add(timediff.minutes() + 1,'m').diff(now);
this.delay = moment(timestamp)
.add(timediff.minutes() + 1, 'm')
.diff(now);
return this.relativeTime(timediff.minutes(), 'm');
} else {
this.delay = moment(timestamp).add(1,'m').diff(now);
this.delay = moment(timestamp)
.add(1, 'm')
.diff(now);
return this.relativeTime(timediff.seconds(), 's');
}
},
@ -70,19 +84,19 @@
return moment.duration(number, string).humanize();
},
_format: {
y: "ll",
M: i18n('timestampFormat_M') || "MMM D",
d: "ddd"
}
y: 'll',
M: i18n('timestampFormat_M') || 'MMM D',
d: 'ddd',
},
});
Whisper.ExtendedTimestampView = Whisper.TimestampView.extend({
relativeTime: function(number, string, isFuture) {
return moment.duration(-1 * number, string).humanize(string !== 's');
},
_format: {
y: "lll",
M: (i18n('timestampFormat_M') || "MMM D") + ' LT',
d: "ddd LT"
}
y: 'lll',
M: (i18n('timestampFormat_M') || 'MMM D') + ' LT',
d: 'ddd LT',
},
});
})();

View File

@ -17,12 +17,14 @@
},
render: function() {
this.$el.html(Mustache.render(
this.$el.html(
Mustache.render(
_.result(this, 'template', ''),
_.result(this, 'render_attributes', '')
));
)
);
this.$el.show();
setTimeout(this.close.bind(this), 2000);
}
},
});
})();

View File

@ -23,7 +23,8 @@
'use strict';
window.Whisper = window.Whisper || {};
Whisper.View = Backbone.View.extend({
Whisper.View = Backbone.View.extend(
{
constructor: function() {
Backbone.View.apply(this, arguments);
Mustache.parse(_.result(this, 'template'));
@ -48,24 +49,28 @@
return this;
},
confirm: function(message, okText) {
return new Promise(function(resolve, reject) {
return new Promise(
function(resolve, reject) {
var dialog = new Whisper.ConfirmationDialogView({
message: message,
okText: okText,
resolve: resolve,
reject: reject
reject: reject,
});
this.$el.append(dialog.el);
}.bind(this));
}.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"';
args[i] =
'class="link" href="' + encodeURI(args[i]) + '" target="_blank"';
}
return i18n(args[0], args.slice(1));
}
},{
},
},
{
// Class attributes
Templates: (function() {
var templates = {};
@ -75,6 +80,7 @@
templates[id] = $el.html();
});
return templates;
}())
});
})(),
}
);
})();

View File

@ -2,7 +2,7 @@
* vim: ts=4:sw=4:expandtab
*/
;(function () {
(function() {
'use strict';
window.Whisper = window.Whisper || {};
@ -11,7 +11,7 @@
var events;
function checkTime() {
var currentTime = Date.now();
if (currentTime > (lastTime + interval * 2)) {
if (currentTime > lastTime + interval * 2) {
events.trigger('timetravel');
}
lastTime = currentTime;
@ -22,6 +22,6 @@
events = _events;
lastTime = Date.now();
setInterval(checkTime, interval);
}
},
};
}());
})();

115
main.js
View File

@ -6,13 +6,7 @@ const _ = require('lodash');
const electron = require('electron');
const semver = require('semver');
const {
BrowserWindow,
app,
Menu,
shell,
ipcMain: ipc,
} = electron;
const { BrowserWindow, app, Menu, shell, ipcMain: ipc } = electron;
const packageJson = require('./package.json');
@ -27,7 +21,9 @@ const { createTemplate } = require('./app/menu');
GlobalErrors.addHandler();
const appUserModelId = `org.whispersystems.${packageJson.name}`;
console.log('Set Windows Application User Model ID (AUMID)', { appUserModelId });
console.log('Set Windows Application User Model ID (AUMID)', {
appUserModelId,
});
app.setAppUserModelId(appUserModelId);
// Keep a global reference of the window object, if you don't, the window will
@ -41,13 +37,13 @@ function getMainWindow() {
// Tray icon and related objects
let tray = null;
const startInTray = process.argv.some(arg => arg === '--start-in-tray');
const usingTrayIcon = startInTray || process.argv.some(arg => arg === '--use-tray-icon');
const usingTrayIcon =
startInTray || process.argv.some(arg => arg === '--use-tray-icon');
const config = require('./app/config');
const importMode = process.argv.some(arg => arg === '--import') || config.get('import');
const importMode =
process.argv.some(arg => arg === '--import') || config.get('import');
const development = config.environment === 'development';
@ -107,7 +103,12 @@ const WINDOWS_8 = '8.0.0';
const osRelease = os.release();
const polyfillNotifications =
os.platform() === 'win32' && semver.lt(osRelease, WINDOWS_8);
console.log('OS Release:', osRelease, '- notifications polyfill?', polyfillNotifications);
console.log(
'OS Release:',
osRelease,
'- notifications polyfill?',
polyfillNotifications
);
function prepareURL(pathSegments) {
return url.format({
@ -146,7 +147,6 @@ function captureClicks(window) {
window.webContents.on('new-window', handleUrl);
}
const DEFAULT_WIDTH = 800;
const DEFAULT_HEIGHT = 610;
const MIN_WIDTH = 640;
@ -160,22 +160,28 @@ function isVisible(window, bounds) {
const boundsHeight = _.get(bounds, 'height') || DEFAULT_HEIGHT;
// requiring BOUNDS_BUFFER pixels on the left or right side
const rightSideClearOfLeftBound = (window.x + window.width >= boundsX + BOUNDS_BUFFER);
const leftSideClearOfRightBound = (window.x <= (boundsX + boundsWidth) - BOUNDS_BUFFER);
const rightSideClearOfLeftBound =
window.x + window.width >= boundsX + BOUNDS_BUFFER;
const leftSideClearOfRightBound =
window.x <= boundsX + boundsWidth - BOUNDS_BUFFER;
// top can't be offscreen, and must show at least BOUNDS_BUFFER pixels at bottom
const topClearOfUpperBound = window.y >= boundsY;
const topClearOfLowerBound = (window.y <= (boundsY + boundsHeight) - BOUNDS_BUFFER);
const topClearOfLowerBound =
window.y <= boundsY + boundsHeight - BOUNDS_BUFFER;
return rightSideClearOfLeftBound &&
return (
rightSideClearOfLeftBound &&
leftSideClearOfRightBound &&
topClearOfUpperBound &&
topClearOfLowerBound;
topClearOfLowerBound
);
}
function createWindow() {
const { screen } = electron;
const windowOptions = Object.assign({
const windowOptions = Object.assign(
{
show: !startInTray, // allow to start minimised in tray
width: DEFAULT_WIDTH,
height: DEFAULT_HEIGHT,
@ -188,7 +194,16 @@ function createWindow() {
preload: path.join(__dirname, 'preload.js'),
},
icon: path.join(__dirname, 'images', 'icon_256.png'),
}, _.pick(windowConfig, ['maximized', 'autoHideMenuBar', 'width', 'height', 'x', 'y']));
},
_.pick(windowConfig, [
'maximized',
'autoHideMenuBar',
'width',
'height',
'x',
'y',
])
);
if (!_.isNumber(windowOptions.width) || windowOptions.width < MIN_WIDTH) {
windowOptions.width = DEFAULT_WIDTH;
@ -203,7 +218,7 @@ function createWindow() {
delete windowOptions.autoHideMenuBar;
}
const visibleOnAnyScreen = _.some(screen.getAllDisplays(), (display) => {
const visibleOnAnyScreen = _.some(screen.getAllDisplays(), display => {
if (!_.isNumber(windowOptions.x) || !_.isNumber(windowOptions.y)) {
return false;
}
@ -220,7 +235,10 @@ function createWindow() {
delete windowOptions.fullscreen;
}
logger.info('Initializing BrowserWindow config: %s', JSON.stringify(windowOptions));
logger.info(
'Initializing BrowserWindow config: %s',
JSON.stringify(windowOptions)
);
// Create the browser window.
mainWindow = new BrowserWindow(windowOptions);
@ -249,7 +267,10 @@ function createWindow() {
windowConfig.fullscreen = true;
}
logger.info('Updating BrowserWindow config: %s', JSON.stringify(windowConfig));
logger.info(
'Updating BrowserWindow config: %s',
JSON.stringify(windowConfig)
);
userConfig.set('window', windowConfig);
}
@ -263,7 +284,7 @@ function createWindow() {
});
// Ingested in preload.js via a sendSync call
ipc.on('locale-data', (event) => {
ipc.on('locale-data', event => {
// eslint-disable-next-line no-param-reassign
event.returnValue = locale.messages;
});
@ -271,7 +292,9 @@ function createWindow() {
if (config.environment === 'test') {
mainWindow.loadURL(prepareURL([__dirname, 'test', 'index.html']));
} else if (config.environment === 'test-lib') {
mainWindow.loadURL(prepareURL([__dirname, 'libtextsecure', 'test', 'index.html']));
mainWindow.loadURL(
prepareURL([__dirname, 'libtextsecure', 'test', 'index.html'])
);
} else {
mainWindow.loadURL(prepareURL([__dirname, 'background.html']));
}
@ -283,16 +306,19 @@ function createWindow() {
captureClicks(mainWindow);
mainWindow.webContents.on('will-navigate', (e) => {
mainWindow.webContents.on('will-navigate', e => {
logger.info('will-navigate');
e.preventDefault();
});
// Emitted when the window is about to be closed.
mainWindow.on('close', (e) => {
mainWindow.on('close', e => {
// If the application is terminating, just do the default
if (windowState.shouldQuit() ||
config.environment === 'test' || config.environment === 'test-lib') {
if (
windowState.shouldQuit() ||
config.environment === 'test' ||
config.environment === 'test-lib'
) {
return;
}
@ -337,7 +363,9 @@ function showSettings() {
}
function openReleaseNotes() {
shell.openExternal(`https://github.com/signalapp/Signal-Desktop/releases/tag/v${app.getVersion()}`);
shell.openExternal(
`https://github.com/signalapp/Signal-Desktop/releases/tag/v${app.getVersion()}`
);
}
function openNewBugForm() {
@ -345,7 +373,9 @@ function openNewBugForm() {
}
function openSupportPage() {
shell.openExternal('https://support.signal.org/hc/en-us/categories/202319038-Desktop');
shell.openExternal(
'https://support.signal.org/hc/en-us/categories/202319038-Desktop'
);
}
function openForums() {
@ -370,7 +400,6 @@ function setupAsStandalone() {
}
}
let aboutWindow;
function showAbout() {
if (aboutWindow) {
@ -416,9 +445,12 @@ app.on('ready', () => {
// NOTE: Temporarily allow `then` until we convert the entire file to `async` / `await`:
/* eslint-disable more/no-then */
let loggingSetupError;
logging.initialize().catch((error) => {
logging
.initialize()
.catch(error => {
loggingSetupError = error;
}).then(async () => {
})
.then(async () => {
/* eslint-enable more/no-then */
logger = logging.getLogger();
logger.info('app ready');
@ -428,7 +460,8 @@ app.on('ready', () => {
}
if (!locale) {
const appLocale = process.env.NODE_ENV === 'test' ? 'en' : app.getLocale();
const appLocale =
process.env.NODE_ENV === 'test' ? 'en' : app.getLocale();
locale = loadLocale({ appLocale, logger });
}
@ -472,7 +505,6 @@ function setupMenu(options) {
Menu.setApplicationMenu(menu);
}
app.on('before-quit', () => {
windowState.markShouldQuit();
});
@ -481,9 +513,11 @@ app.on('before-quit', () => {
app.on('window-all-closed', () => {
// On OS X it is common for applications and their menu bar
// to stay active until the user quits explicitly with Cmd + Q
if (process.platform !== 'darwin' ||
if (
process.platform !== 'darwin' ||
config.environment === 'test' ||
config.environment === 'test-lib') {
config.environment === 'test-lib'
) {
app.quit();
}
});
@ -504,7 +538,7 @@ app.on('activate', () => {
// Defense in depth. We never intend to open webviews, so this prevents it completely.
app.on('web-contents-created', (createEvent, win) => {
win.on('will-attach-webview', (attachEvent) => {
win.on('will-attach-webview', attachEvent => {
attachEvent.preventDefault();
});
});
@ -523,7 +557,6 @@ ipc.on('add-setup-menu-items', () => {
});
});
ipc.on('draw-attention', () => {
if (process.platform === 'darwin') {
app.dock.bounce();

View File

@ -12,7 +12,6 @@ const { deferredToPromise } = require('./js/modules/deferred_to_promise');
const { app } = electron.remote;
window.PROTO_ROOT = 'protos';
window.config = require('url').parse(window.location.toString(), true).query;
@ -21,8 +20,7 @@ window.wrapDeferred = deferredToPromise;
const ipc = electron.ipcRenderer;
window.config.localeMessages = ipc.sendSync('locale-data');
window.setBadgeCount = count =>
ipc.send('set-badge-count', count);
window.setBadgeCount = count => ipc.send('set-badge-count', count);
window.drawAttention = () => {
console.log('draw attention');
@ -44,8 +42,7 @@ window.restart = () => {
ipc.send('restart');
};
window.closeAbout = () =>
ipc.send('close-about');
window.closeAbout = () => ipc.send('close-about');
window.updateTrayIcon = unreadCount =>
ipc.send('update-tray-icon', unreadCount);
@ -70,11 +67,9 @@ ipc.on('show-settings', () => {
Whisper.events.trigger('showSettings');
});
window.addSetupMenuItems = () =>
ipc.send('add-setup-menu-items');
window.addSetupMenuItems = () => ipc.send('add-setup-menu-items');
window.removeSetupMenuItems = () =>
ipc.send('remove-setup-menu-items');
window.removeSetupMenuItems = () => ipc.send('remove-setup-menu-items');
// We pull these dependencies in now, from here, because they have Node.js dependencies
@ -101,8 +96,7 @@ window.emojiData = require('emoji-datasource');
window.EmojiPanel = require('emoji-panel');
window.filesize = require('filesize');
window.libphonenumber = require('google-libphonenumber').PhoneNumberUtil.getInstance();
window.libphonenumber.PhoneNumberFormat =
require('google-libphonenumber').PhoneNumberFormat;
window.libphonenumber.PhoneNumberFormat = require('google-libphonenumber').PhoneNumberFormat;
window.loadImage = require('blueimp-load-image');
window.nodeBuffer = Buffer;
@ -136,11 +130,15 @@ window.moment.locale(locale);
// ES2015+ modules
const attachmentsPath = Attachments.getPath(app.getPath('userData'));
const getAbsoluteAttachmentPath = Attachments.createAbsolutePathGetter(attachmentsPath);
const getAbsoluteAttachmentPath = Attachments.createAbsolutePathGetter(
attachmentsPath
);
const deleteAttachmentData = Attachments.createDeleter(attachmentsPath);
const readAttachmentData = Attachments.createReader(attachmentsPath);
const writeNewAttachmentData = Attachments.createWriterForNew(attachmentsPath);
const writeExistingAttachmentData = Attachments.createWriterForExisting(attachmentsPath);
const writeExistingAttachmentData = Attachments.createWriterForExisting(
attachmentsPath
);
const loadAttachmentData = Attachment.loadData(readAttachmentData);
@ -151,8 +149,9 @@ const upgradeSchemaContext = {
const upgradeMessageSchema = message =>
Message.upgradeSchema(message, upgradeSchemaContext);
const { getPlaceholderMigrations } =
require('./js/modules/migrations/get_placeholder_migrations');
const {
getPlaceholderMigrations,
} = require('./js/modules/migrations/get_placeholder_migrations');
const { IdleDetector } = require('./js/modules/idle_detector');
window.Signal = {};
@ -167,12 +166,12 @@ window.Signal.Logs = require('./js/modules/logs');
// React components
const { Lightbox } = require('./ts/components/Lightbox');
const { LightboxGallery } = require('./ts/components/LightboxGallery');
const { MediaGallery } =
require('./ts/components/conversation/media-gallery/MediaGallery');
const {
MediaGallery,
} = require('./ts/components/conversation/media-gallery/MediaGallery');
const { Quote } = require('./ts/components/conversation/Quote');
const MediaGalleryMessage =
require('./ts/components/conversation/media-gallery/types/Message');
const MediaGalleryMessage = require('./ts/components/conversation/media-gallery/types/Message');
window.Signal.Components = {
Lightbox,
@ -185,18 +184,20 @@ window.Signal.Components = {
};
window.Signal.Migrations = {};
window.Signal.Migrations.deleteAttachmentData =
Attachment.deleteData(deleteAttachmentData);
window.Signal.Migrations.deleteAttachmentData = Attachment.deleteData(
deleteAttachmentData
);
window.Signal.Migrations.getPlaceholderMigrations = getPlaceholderMigrations;
window.Signal.Migrations.writeMessageAttachments =
Message.createAttachmentDataWriter(writeExistingAttachmentData);
window.Signal.Migrations.writeMessageAttachments = Message.createAttachmentDataWriter(
writeExistingAttachmentData
);
window.Signal.Migrations.getAbsoluteAttachmentPath = getAbsoluteAttachmentPath;
window.Signal.Migrations.loadAttachmentData = loadAttachmentData;
window.Signal.Migrations.loadMessage = Message.createAttachmentLoader(loadAttachmentData);
window.Signal.Migrations.Migrations0DatabaseWithAttachmentData =
require('./js/modules/migrations/migrations_0_database_with_attachment_data');
window.Signal.Migrations.Migrations1DatabaseWithoutAttachmentData =
require('./js/modules/migrations/migrations_1_database_without_attachment_data');
window.Signal.Migrations.loadMessage = Message.createAttachmentLoader(
loadAttachmentData
);
window.Signal.Migrations.Migrations0DatabaseWithAttachmentData = require('./js/modules/migrations/migrations_0_database_with_attachment_data');
window.Signal.Migrations.Migrations1DatabaseWithoutAttachmentData = require('./js/modules/migrations/migrations_1_database_without_attachment_data');
window.Signal.Migrations.upgradeMessageSchema = upgradeMessageSchema;
window.Signal.OS = require('./js/modules/os');
@ -218,8 +219,7 @@ window.Signal.Views.Initialization = require('./js/modules/views/initialization'
window.Signal.Workflow = {};
window.Signal.Workflow.IdleDetector = IdleDetector;
window.Signal.Workflow.MessageDataMigrator =
require('./js/modules/messages_data_migrator');
window.Signal.Workflow.MessageDataMigrator = require('./js/modules/messages_data_migrator');
// We pull this in last, because the native module involved appears to be sensitive to
// /tmp mounted as noexec on Linux.

View File

@ -3,7 +3,6 @@ const _ = require('lodash');
const packageJson = require('./package.json');
const { version } = packageJson;
const beta = /beta/;
@ -37,7 +36,6 @@ const STARTUP_WM_CLASS_PATH = 'build.linux.desktop.StartupWMClass';
const PRODUCTION_STARTUP_WM_CLASS = 'Signal';
const BETA_STARTUP_WM_CLASS = 'Signal Beta';
// -------
function checkValue(object, objectPath, expected) {

View File

@ -56,5 +56,8 @@ _.set(packageJson, WIN_ASSET_PATH, WIN_ASSET_END_VALUE);
// ---
fs.writeFileSync('./config/default.json', JSON.stringify(defaultConfig, null, ' '));
fs.writeFileSync(
'./config/default.json',
JSON.stringify(defaultConfig, null, ' ')
);
fs.writeFileSync('./package.json', JSON.stringify(packageJson, null, ' '));

View File

@ -2,7 +2,6 @@ const webpack = require('webpack');
const path = require('path');
const typescriptSupport = require('react-docgen-typescript');
const propsParser = typescriptSupport.withCustomConfig('./tsconfig.json').parse;
module.exports = {
@ -37,9 +36,7 @@ module.exports = {
// Exposes necessary utilities in the global scope for all readme code snippets
util: 'ts/styleguide/StyleGuideUtil',
},
contextDependencies: [
path.join(__dirname, 'ts/styleguide'),
],
contextDependencies: [path.join(__dirname, 'ts/styleguide')],
// We don't want one long, single page
pagePerSection: true,
// Expose entire repository to the styleguidist server, primarily for stylesheets
@ -49,11 +46,13 @@ module.exports = {
// https://react-styleguidist.js.org/docs/configuration.html#template
template: {
head: {
links: [{
links: [
{
rel: 'stylesheet',
type: 'text/css',
href: '/stylesheets/manifest.css',
}],
},
],
},
body: {
// Brings in all the necessary components to boostrap Backbone views
@ -157,10 +156,7 @@ module.exports = {
resolve: {
// Necessary to enable the absolute path used in the context option above
modules: [
__dirname,
path.join(__dirname, 'node_modules'),
],
modules: [__dirname, path.join(__dirname, 'node_modules')],
extensions: ['.tsx'],
},
@ -168,7 +164,7 @@ module.exports = {
rules: [
{
test: /\.tsx?$/,
loader: 'ts-loader'
loader: 'ts-loader',
},
{
// To test handling of attachments, we need arraybuffers in memory

View File

@ -7,7 +7,7 @@ module.exports = {
},
globals: {
assert: true
assert: true,
},
parserOptions: {
@ -16,11 +16,14 @@ module.exports = {
rules: {
// We still get the value of this rule, it just allows for dev deps
'import/no-extraneous-dependencies': ['error', {
devDependencies: true
}],
'import/no-extraneous-dependencies': [
'error',
{
devDependencies: true,
},
],
// We want to keep each test structured the same, even if its contents are tiny
'arrow-body-style': 'off',
}
},
};

View File

@ -27,7 +27,7 @@ window.PROTO_ROOT = '../protos';
result: false,
message: err.message,
stack: err.stack,
titles: flattenTitles(test)
titles: flattenTitles(test),
});
});
@ -37,7 +37,7 @@ window.PROTO_ROOT = '../protos';
SauceReporter.prototype = OriginalReporter.prototype;
mocha.reporter(SauceReporter);
}());
})();
// Override the database id.
window.Whisper = window.Whisper || {};
@ -49,7 +49,7 @@ Whisper.Database.id = 'test';
*/
function assertEqualArrayBuffers(ab1, ab2) {
assert.deepEqual(new Uint8Array(ab1), new Uint8Array(ab2));
};
}
function hexToArrayBuffer(str) {
var ret = new ArrayBuffer(str.length / 2);
@ -58,12 +58,14 @@ function hexToArrayBuffer(str) {
array[i] = parseInt(str.substr(i * 2, 2), 16);
}
return ret;
};
}
/* Delete the database before running any tests */
before(function(done) {
var idbReq = indexedDB.deleteDatabase('test');
idbReq.onsuccess = function() { done(); };
idbReq.onsuccess = function() {
done();
};
});
async function clearDatabase(done) {
@ -80,5 +82,5 @@ async function clearDatabase(done) {
await messages.destroyAll();
if (done) {
done();
};
}
}

Some files were not shown because too many files have changed in this diff Show More