increase prettier maxWidth to 100

pull/1576/head
Audric Ackermann 3 years ago
parent 6896cffd75
commit f7581cf4eb
No known key found for this signature in database
GPG Key ID: 999F434D76324AD4

@ -46,11 +46,7 @@ module.exports = {
// Use LF to stay consistent // Use LF to stay consistent
'linebreak-style': ['error', 'unix'], 'linebreak-style': ['error', 'unix'],
quotes: [ quotes: ['error', 'single', { avoidEscape: true, allowTemplateLiterals: true }],
'error',
'single',
{ avoidEscape: true, allowTemplateLiterals: true },
],
// Prettier overrides: // Prettier overrides:
'arrow-parens': 'off', 'arrow-parens': 'off',

@ -3,4 +3,5 @@ module.exports = {
trailingComma: 'es5', trailingComma: 'es5',
bracketSpacing: true, bracketSpacing: true,
arrowParens: 'avoid', arrowParens: 'avoid',
printWidth: 100,
}; };

@ -59,11 +59,7 @@ module.exports = grunt => {
dest: 'libloki/test/components.js', dest: 'libloki/test/components.js',
}, },
test: { test: {
src: [ src: ['node_modules/mocha/mocha.js', 'node_modules/chai/chai.js', 'test/_test.js'],
'node_modules/mocha/mocha.js',
'node_modules/chai/chai.js',
'test/_test.js',
],
dest: 'test/test.js', dest: 'test/test.js',
}, },
// TODO: Move errors back down? // TODO: Move errors back down?
@ -90,19 +86,11 @@ module.exports = grunt => {
dest: 'js/libtextsecure.js', dest: 'js/libtextsecure.js',
}, },
libloki: { libloki: {
src: [ src: ['libloki/crypto.js', 'libloki/service_nodes.js', 'libloki/storage.js'],
'libloki/crypto.js',
'libloki/service_nodes.js',
'libloki/storage.js',
],
dest: 'js/libloki.js', dest: 'js/libloki.js',
}, },
lokitest: { lokitest: {
src: [ src: ['node_modules/mocha/mocha.js', 'node_modules/chai/chai.js', 'libloki/test/_test.js'],
'node_modules/mocha/mocha.js',
'node_modules/chai/chai.js',
'libloki/test/_test.js',
],
dest: 'libloki/test/test.js', dest: 'libloki/test/test.js',
}, },
libtextsecuretest: { libtextsecuretest: {
@ -155,12 +143,7 @@ module.exports = grunt => {
tasks: ['sass'], tasks: ['sass'],
}, },
transpile: { transpile: {
files: [ files: ['./ts/**/*.ts', './ts/**/*.tsx', './ts/**/**/*.tsx', './test/ts/**.ts'],
'./ts/**/*.ts',
'./ts/**/*.tsx',
'./ts/**/**/*.tsx',
'./test/ts/**.ts',
],
tasks: ['exec:transpile'], tasks: ['exec:transpile'],
}, },
}, },
@ -222,10 +205,7 @@ module.exports = grunt => {
// eslint-disable-next-line no-restricted-syntax // eslint-disable-next-line no-restricted-syntax
for (const key in messages) { for (const key in messages) {
if (en[key] !== undefined && messages[key] !== undefined) { if (en[key] !== undefined && messages[key] !== undefined) {
if ( if (en[key].placeholders !== undefined && messages[key].placeholders === undefined) {
en[key].placeholders !== undefined &&
messages[key].placeholders === undefined
) {
messages[key].placeholders = en[key].placeholders; messages[key].placeholders = en[key].placeholders;
} }
} }
@ -274,8 +254,7 @@ module.exports = grunt => {
function runTests(environment, cb) { function runTests(environment, cb) {
let failure; let failure;
const { Application } = spectron; const { Application } = spectron;
const electronBinary = const electronBinary = process.platform === 'win32' ? 'electron.cmd' : 'electron';
process.platform === 'win32' ? 'electron.cmd' : 'electron';
const app = new Application({ const app = new Application({
path: path.join(__dirname, 'node_modules', '.bin', electronBinary), path: path.join(__dirname, 'node_modules', '.bin', electronBinary),
args: [path.join(__dirname, 'main.js')], args: [path.join(__dirname, 'main.js')],
@ -284,9 +263,7 @@ module.exports = grunt => {
}, },
requireName: 'unused', requireName: 'unused',
chromeDriverArgs: [ chromeDriverArgs: [
`remote-debugging-port=${Math.floor( `remote-debugging-port=${Math.floor(Math.random() * (9999 - 9000) + 9000)}`,
Math.random() * (9999 - 9000) + 9000
)}`,
], ],
}); });
@ -299,10 +276,7 @@ module.exports = grunt => {
.start() .start()
.then(() => .then(() =>
app.client.waitUntil( app.client.waitUntil(
() => () => app.client.execute(getMochaResults).then(data => Boolean(data.value)),
app.client
.execute(getMochaResults)
.then(data => Boolean(data.value)),
25000, 25000,
'Expected to find window.mochaResults set!' 'Expected to find window.mochaResults set!'
) )
@ -312,8 +286,7 @@ module.exports = grunt => {
const results = data.value; const results = data.value;
if (results.failures > 0) { if (results.failures > 0) {
console.error(results.reports); console.error(results.reports);
failure = () => failure = () => grunt.fail.fatal(`Found ${results.failures} failing unit tests.`);
grunt.fail.fatal(`Found ${results.failures} failing unit tests.`);
return app.client.log('browser'); return app.client.log('browser');
} }
grunt.log.ok(`${results.passes} tests passed.`); grunt.log.ok(`${results.passes} tests passed.`);
@ -327,10 +300,7 @@ module.exports = grunt => {
} }
}) })
.catch(error => { .catch(error => {
failure = () => failure = () => grunt.fail.fatal(`Something went wrong: ${error.message} ${error.stack}`);
grunt.fail.fatal(
`Something went wrong: ${error.message} ${error.stack}`
);
}) })
.then(() => { .then(() => {
// We need to use the failure variable and this early stop to clean up before // We need to use the failure variable and this early stop to clean up before
@ -371,16 +341,12 @@ module.exports = grunt => {
}); });
} }
grunt.registerTask( grunt.registerTask('unit-tests', 'Run unit tests w/Electron', function thisNeeded() {
'unit-tests', const environment = grunt.option('env') || 'test';
'Run unit tests w/Electron', const done = this.async();
function thisNeeded() {
const environment = grunt.option('env') || 'test';
const done = this.async();
runTests(environment, done); runTests(environment, done);
} });
);
grunt.registerTask( grunt.registerTask(
'lib-unit-tests', 'lib-unit-tests',
@ -393,117 +359,97 @@ module.exports = grunt => {
} }
); );
grunt.registerTask( grunt.registerTask('loki-unit-tests', 'Run loki unit tests w/Electron', function thisNeeded() {
'loki-unit-tests', const environment = grunt.option('env') || 'test-loki';
'Run loki unit tests w/Electron', const done = this.async();
function thisNeeded() {
const environment = grunt.option('env') || 'test-loki';
const done = this.async();
runTests(environment, done); runTests(environment, done);
} });
);
grunt.registerMultiTask( grunt.registerMultiTask('test-release', 'Test packaged releases', function thisNeeded() {
'test-release', const dir = grunt.option('dir') || 'release';
'Test packaged releases', const environment = grunt.option('env') || 'production';
function thisNeeded() { const config = this.data;
const dir = grunt.option('dir') || 'release'; const archive = [dir, config.archive].join('/');
const environment = grunt.option('env') || 'production'; const files = [
const config = this.data; 'config/default.json',
const archive = [dir, config.archive].join('/'); `config/${environment}.json`,
const files = [ `config/local-${environment}.json`,
'config/default.json', ];
`config/${environment}.json`,
`config/local-${environment}.json`,
];
console.log(this.target, archive); console.log(this.target, archive);
const releaseFiles = files.concat(config.files || []); const releaseFiles = files.concat(config.files || []);
releaseFiles.forEach(fileName => { releaseFiles.forEach(fileName => {
console.log(fileName); console.log(fileName);
try { try {
asar.statFile(archive, fileName); asar.statFile(archive, fileName);
return true; return true;
} catch (e) { } catch (e) {
console.log(e); console.log(e);
throw new Error(`Missing file ${fileName}`); throw new Error(`Missing file ${fileName}`);
} }
}); });
if (config.appUpdateYML) { if (config.appUpdateYML) {
const appUpdateYML = [dir, config.appUpdateYML].join('/'); const appUpdateYML = [dir, config.appUpdateYML].join('/');
if (fs.existsSync(appUpdateYML)) { if (fs.existsSync(appUpdateYML)) {
console.log('auto update ok'); console.log('auto update ok');
} else { } else {
throw new Error(`Missing auto update config ${appUpdateYML}`); throw new Error(`Missing auto update config ${appUpdateYML}`);
}
} }
}
const done = this.async(); const done = this.async();
// A simple test to verify a visible window is opened with a title // A simple test to verify a visible window is opened with a title
const { Application } = spectron; const { Application } = spectron;
const app = new Application({ const app = new Application({
path: [dir, config.exe].join('/'), path: [dir, config.exe].join('/'),
requireName: 'unused', requireName: 'unused',
chromeDriverArgs: [ chromeDriverArgs: [
`remote-debugging-port=${Math.floor( `remote-debugging-port=${Math.floor(Math.random() * (9999 - 9000) + 9000)}`,
Math.random() * (9999 - 9000) + 9000 ],
)}`, });
],
});
app app
.start() .start()
.then(() => app.client.getWindowCount()) .then(() => app.client.getWindowCount())
.then(count => { .then(count => {
assert.equal(count, 1); assert.equal(count, 1);
console.log('window opened'); console.log('window opened');
}) })
.then(() => .then(() =>
// Get the window's title // Get the window's title
app.client.getTitle() app.client.getTitle()
) )
.then(title => { .then(title => {
// TODO: restore once fixed on win // TODO: restore once fixed on win
if (this.target !== 'win') { if (this.target !== 'win') {
// Verify the window's title // Verify the window's title
assert.equal(title, packageJson.productName); assert.equal(title, packageJson.productName);
console.log('title ok'); console.log('title ok');
} }
}) })
.then(() => { .then(() => {
assert( assert(app.chromeDriver.logLines.indexOf(`NODE_ENV ${environment}`) > -1);
app.chromeDriver.logLines.indexOf(`NODE_ENV ${environment}`) > -1 console.log('environment ok');
); })
console.log('environment ok'); .then(
}) () =>
.then( // Successfully completed test
() => app.stop(),
// Successfully completed test error =>
app.stop(), // Test failed!
error => app.stop().then(() => {
// Test failed! grunt.fail.fatal(`Test failed: ${error.message} ${error.stack}`);
app.stop().then(() => { })
grunt.fail.fatal(`Test failed: ${error.message} ${error.stack}`); )
}) .then(done);
) });
.then(done);
}
);
grunt.registerTask('tx', [ grunt.registerTask('tx', ['exec:tx-pull-new', 'exec:tx-pull', 'locale-patch']);
'exec:tx-pull-new',
'exec:tx-pull',
'locale-patch',
]);
grunt.registerTask('dev', ['default', 'watch']); grunt.registerTask('dev', ['default', 'watch']);
grunt.registerTask('test', [ grunt.registerTask('test', ['unit-tests', 'lib-unit-tests', 'loki-unit-tests']);
'unit-tests',
'lib-unit-tests',
'loki-unit-tests',
]);
grunt.registerTask('date', ['gitinfo', 'getExpireTime']); grunt.registerTask('date', ['gitinfo', 'getExpireTime']);
grunt.registerTask('default', [ grunt.registerTask('default', [
'exec:build-protobuf', 'exec:build-protobuf',

@ -8,8 +8,4 @@ interface Options {
allowMalformedOnStartup: boolean; allowMalformedOnStartup: boolean;
} }
export function start( export function start(name: string, targetPath: string, options: Options): BaseConfig;
name: string,
targetPath: string,
options: Options
): BaseConfig;

@ -18,9 +18,7 @@ function start(name, targetPath, options = {}) {
console.log(`config/get: Successfully read ${name} config file`); console.log(`config/get: Successfully read ${name} config file`);
if (!cachedValue) { if (!cachedValue) {
console.log( console.log(`config/get: ${name} config value was falsy, cache is now empty object`);
`config/get: ${name} config value was falsy, cache is now empty object`
);
cachedValue = Object.create(null); cachedValue = Object.create(null);
} }
} catch (error) { } catch (error) {
@ -28,9 +26,7 @@ function start(name, targetPath, options = {}) {
throw error; throw error;
} }
console.log( console.log(`config/get: Did not find ${name} config file, cache is now empty object`);
`config/get: Did not find ${name} config file, cache is now empty object`
);
cachedValue = Object.create(null); cachedValue = Object.create(null);
} }

@ -36,10 +36,8 @@ const config = require('config');
config.environment = environment; config.environment = environment;
// Log resulting env vars in use by config // Log resulting env vars in use by config
['NODE_ENV', 'NODE_APP_INSTANCE', 'NODE_CONFIG_DIR', 'NODE_CONFIG'].forEach( ['NODE_ENV', 'NODE_APP_INSTANCE', 'NODE_CONFIG_DIR', 'NODE_CONFIG'].forEach(s => {
s => { console.log(`${s} ${config.util.getEnv(s)}`);
console.log(`${s} ${config.util.getEnv(s)}`); });
}
);
module.exports = config; module.exports = config;

@ -13,13 +13,7 @@ function normalizeLocaleName(locale) {
function getLocaleMessages(locale) { function getLocaleMessages(locale) {
const onDiskLocale = locale.replace('-', '_'); const onDiskLocale = locale.replace('-', '_');
const targetFile = path.join( const targetFile = path.join(__dirname, '..', '_locales', onDiskLocale, 'messages.json');
__dirname,
'..',
'_locales',
onDiskLocale,
'messages.json'
);
return JSON.parse(fs.readFileSync(targetFile, 'utf-8')); return JSON.parse(fs.readFileSync(targetFile, 'utf-8'));
} }
@ -49,9 +43,7 @@ function load({ appLocale, logger } = {}) {
// We start with english, then overwrite that with anything present in locale // We start with english, then overwrite that with anything present in locale
messages = _.merge(english, messages); messages = _.merge(english, messages);
} catch (e) { } catch (e) {
logger.error( logger.error(`Problem loading messages for locale ${localeName} ${e.stack}`);
`Problem loading messages for locale ${localeName} ${e.stack}`
);
logger.error('Falling back to en locale'); logger.error('Falling back to en locale');
localeName = 'en'; localeName = 'en';

@ -120,10 +120,7 @@ async function cleanupLogs(logPath) {
await eliminateOldEntries(files, earliestDate); await eliminateOldEntries(files, earliestDate);
} catch (error) { } catch (error) {
console.error( console.error('Error cleaning logs; deleting and starting over from scratch.', error.stack);
'Error cleaning logs; deleting and starting over from scratch.',
error.stack
);
// delete and re-create the log directory // delete and re-create the log directory
await deleteAllLogs(logPath); await deleteAllLogs(logPath);
@ -151,26 +148,24 @@ function eliminateOutOfDateFiles(logPath, date) {
return Promise.all( return Promise.all(
_.map(paths, target => _.map(paths, target =>
Promise.all([readFirstLine(target), readLastLines(target, 2)]).then( Promise.all([readFirstLine(target), readLastLines(target, 2)]).then(results => {
results => { const start = results[0];
const start = results[0]; const end = results[1].split('\n');
const end = results[1].split('\n');
const file = {
const file = { path: target,
path: target, start: isLineAfterDate(start, date),
start: isLineAfterDate(start, date), end:
end: isLineAfterDate(end[end.length - 1], date) ||
isLineAfterDate(end[end.length - 1], date) || isLineAfterDate(end[end.length - 2], date),
isLineAfterDate(end[end.length - 2], date), };
};
if (!file.start && !file.end) {
if (!file.start && !file.end) { fs.unlinkSync(file.path);
fs.unlinkSync(file.path);
}
return file;
} }
)
return file;
})
) )
); );
} }
@ -181,10 +176,7 @@ function eliminateOldEntries(files, date) {
return Promise.all( return Promise.all(
_.map(files, file => _.map(files, file =>
fetchLog(file.path).then(lines => { fetchLog(file.path).then(lines => {
const recent = _.filter( const recent = _.filter(lines, line => new Date(line.time).getTime() >= earliest);
lines,
line => new Date(line.time).getTime() >= earliest
);
const text = _.map(recent, line => JSON.stringify(line)).join('\n'); const text = _.map(recent, line => JSON.stringify(line)).join('\n');
return fs.writeFileSync(file.path, `${text}\n`); return fs.writeFileSync(file.path, `${text}\n`);

@ -39,9 +39,7 @@ function installPermissionsHandler({ session, userConfig }) {
// they've already been used successfully. // they've already been used successfully.
session.defaultSession.setPermissionRequestHandler(null); session.defaultSession.setPermissionRequestHandler(null);
session.defaultSession.setPermissionRequestHandler( session.defaultSession.setPermissionRequestHandler(_createPermissionHandler(userConfig));
_createPermissionHandler(userConfig)
);
} }
module.exports = { module.exports = {

@ -32,19 +32,13 @@ function _createFileHandler({ userDataPath, installPath, isWindows }) {
const properCasing = isWindows ? realPath.toLowerCase() : realPath; const properCasing = isWindows ? realPath.toLowerCase() : realPath;
if (!path.isAbsolute(realPath)) { if (!path.isAbsolute(realPath)) {
console.log( console.log(`Warning: denying request to non-absolute path '${realPath}'`);
`Warning: denying request to non-absolute path '${realPath}'`
);
return callback(); return callback();
} }
if ( if (
!properCasing.startsWith( !properCasing.startsWith(isWindows ? userDataPath.toLowerCase() : userDataPath) &&
isWindows ? userDataPath.toLowerCase() : userDataPath !properCasing.startsWith(isWindows ? installPath.toLowerCase() : installPath)
) &&
!properCasing.startsWith(
isWindows ? installPath.toLowerCase() : installPath
)
) { ) {
console.log( console.log(
`Warning: denying request to path '${realPath}' (userDataPath: '${userDataPath}', installPath: '${installPath}')` `Warning: denying request to path '${realPath}' (userDataPath: '${userDataPath}', installPath: '${installPath}')`
@ -58,12 +52,7 @@ function _createFileHandler({ userDataPath, installPath, isWindows }) {
}; };
} }
function installFileHandler({ function installFileHandler({ protocol, userDataPath, installPath, isWindows }) {
protocol,
userDataPath,
installPath,
isWindows,
}) {
protocol.interceptFileProtocol( protocol.interceptFileProtocol(
'file', 'file',
_createFileHandler({ userDataPath, installPath, isWindows }) _createFileHandler({ userDataPath, installPath, isWindows })

@ -7,15 +7,7 @@ const { redactAll } = require('../js/modules/privacy');
const { remove: removeUserConfig } = require('./user_config'); const { remove: removeUserConfig } = require('./user_config');
const pify = require('pify'); const pify = require('pify');
const { const { map, isString, fromPairs, forEach, last, isEmpty, isObject } = require('lodash');
map,
isString,
fromPairs,
forEach,
last,
isEmpty,
isObject,
} = require('lodash');
// To get long stack traces // To get long stack traces
// https://github.com/mapbox/node-sqlite3/wiki/API#sqlite3verbose // https://github.com/mapbox/node-sqlite3/wiki/API#sqlite3verbose
@ -1113,11 +1105,7 @@ async function updateLokiSchema(instance) {
`Current loki schema version: ${lokiSchemaVersion};`, `Current loki schema version: ${lokiSchemaVersion};`,
`Most recent schema version: ${LOKI_SCHEMA_VERSIONS.length};` `Most recent schema version: ${LOKI_SCHEMA_VERSIONS.length};`
); );
for ( for (let index = 0, max = LOKI_SCHEMA_VERSIONS.length; index < max; index += 1) {
let index = 0, max = LOKI_SCHEMA_VERSIONS.length;
index < max;
index += 1
) {
const runSchemaUpdate = LOKI_SCHEMA_VERSIONS[index]; const runSchemaUpdate = LOKI_SCHEMA_VERSIONS[index];
// Yes, we really want to do this asynchronously, in order // Yes, we really want to do this asynchronously, in order
@ -1127,9 +1115,7 @@ async function updateLokiSchema(instance) {
} }
async function getLokiSchemaVersion(instance) { async function getLokiSchemaVersion(instance) {
const result = await instance.get( const result = await instance.get('SELECT MAX(version) as version FROM loki_schema;');
'SELECT MAX(version) as version FROM loki_schema;'
);
if (!result || !result.version) { if (!result || !result.version) {
return 0; return 0;
} }
@ -1219,10 +1205,7 @@ async function initialize({ configDir, key, messages, passwordAttempt }) {
} }
console.log('Database startup error:', error.stack); console.log('Database startup error:', error.stack);
const buttonIndex = dialog.showMessageBox({ const buttonIndex = dialog.showMessageBox({
buttons: [ buttons: [messages.copyErrorAndQuit.message, messages.clearAllData.message],
messages.copyErrorAndQuit.message,
messages.clearAllData.message,
],
defaultId: 0, defaultId: 0,
detail: redactAll(error.stack), detail: redactAll(error.stack),
message: messages.databaseError.message, message: messages.databaseError.message,
@ -1231,9 +1214,7 @@ async function initialize({ configDir, key, messages, passwordAttempt }) {
}); });
if (buttonIndex === 0) { if (buttonIndex === 0) {
clipboard.writeText( clipboard.writeText(`Database startup error:\n\n${redactAll(error.stack)}`);
`Database startup error:\n\n${redactAll(error.stack)}`
);
} else { } else {
await close(); await close();
await removeDB(); await removeDB();
@ -1389,12 +1370,9 @@ async function createOrUpdate(table, data, instance) {
} }
async function getById(table, id, instance) { async function getById(table, id, instance) {
const row = await (db || instance).get( const row = await (db || instance).get(`SELECT * FROM ${table} WHERE id = $id;`, {
`SELECT * FROM ${table} WHERE id = $id;`, $id: id,
{ });
$id: id,
}
);
if (!row) { if (!row) {
return null; return null;
@ -1414,10 +1392,7 @@ async function removeById(table, id) {
} }
// Our node interface doesn't seem to allow you to replace one single ? with an array // Our node interface doesn't seem to allow you to replace one single ? with an array
await db.run( await db.run(`DELETE FROM ${table} WHERE id IN ( ${id.map(() => '?').join(', ')} );`, id);
`DELETE FROM ${table} WHERE id IN ( ${id.map(() => '?').join(', ')} );`,
id
);
} }
async function removeAllFromTable(table) { async function removeAllFromTable(table) {
@ -1427,12 +1402,9 @@ async function removeAllFromTable(table) {
// Conversations // Conversations
async function getSwarmNodesForPubkey(pubkey) { async function getSwarmNodesForPubkey(pubkey) {
const row = await db.get( const row = await db.get(`SELECT * FROM ${NODES_FOR_PUBKEY_TABLE} WHERE pubkey = $pubkey;`, {
`SELECT * FROM ${NODES_FOR_PUBKEY_TABLE} WHERE pubkey = $pubkey;`, $pubkey: pubkey,
{ });
$pubkey: pubkey,
}
);
if (!row) { if (!row) {
return []; return [];
@ -1463,9 +1435,7 @@ async function getConversationCount() {
const row = await db.get(`SELECT count(*) from ${CONVERSATIONS_TABLE};`); const row = await db.get(`SELECT count(*) from ${CONVERSATIONS_TABLE};`);
if (!row) { if (!row) {
throw new Error( throw new Error(`getConversationCount: Unable to get count of ${CONVERSATIONS_TABLE}`);
`getConversationCount: Unable to get count of ${CONVERSATIONS_TABLE}`
);
} }
return row['count(*)']; return row['count(*)'];
@ -1563,9 +1533,7 @@ async function removeConversation(id) {
// Our node interface doesn't seem to allow you to replace one single ? with an array // Our node interface doesn't seem to allow you to replace one single ? with an array
await db.run( await db.run(
`DELETE FROM ${CONVERSATIONS_TABLE} WHERE id IN ( ${id `DELETE FROM ${CONVERSATIONS_TABLE} WHERE id IN ( ${id.map(() => '?').join(', ')} );`,
.map(() => '?')
.join(', ')} );`,
id id
); );
} }
@ -1590,12 +1558,9 @@ async function savePublicServerToken(data) {
// open groups v1 only // open groups v1 only
async function getPublicServerTokenByServerUrl(serverUrl) { async function getPublicServerTokenByServerUrl(serverUrl) {
const row = await db.get( const row = await db.get(`SELECT * FROM ${SERVERS_TOKEN_TABLE} WHERE serverUrl = $serverUrl;`, {
`SELECT * FROM ${SERVERS_TOKEN_TABLE} WHERE serverUrl = $serverUrl;`, $serverUrl: serverUrl,
{ });
$serverUrl: serverUrl,
}
);
if (!row) { if (!row) {
return null; return null;
@ -1605,12 +1570,9 @@ async function getPublicServerTokenByServerUrl(serverUrl) {
} }
async function getConversationById(id) { async function getConversationById(id) {
const row = await db.get( const row = await db.get(`SELECT * FROM ${CONVERSATIONS_TABLE} WHERE id = $id;`, {
`SELECT * FROM ${CONVERSATIONS_TABLE} WHERE id = $id;`, $id: id,
{ });
$id: id,
}
);
if (!row) { if (!row) {
return null; return null;
@ -1620,16 +1582,12 @@ async function getConversationById(id) {
} }
async function getAllConversations() { async function getAllConversations() {
const rows = await db.all( const rows = await db.all(`SELECT json FROM ${CONVERSATIONS_TABLE} ORDER BY id ASC;`);
`SELECT json FROM ${CONVERSATIONS_TABLE} ORDER BY id ASC;`
);
return map(rows, row => jsonToObject(row.json)); return map(rows, row => jsonToObject(row.json));
} }
async function getAllConversationIds() { async function getAllConversationIds() {
const rows = await db.all( const rows = await db.all(`SELECT id FROM ${CONVERSATIONS_TABLE} ORDER BY id ASC;`);
`SELECT id FROM ${CONVERSATIONS_TABLE} ORDER BY id ASC;`
);
return map(rows, row => row.id); return map(rows, row => row.id);
} }
@ -1715,11 +1673,7 @@ async function searchMessages(query, { limit } = {}) {
})); }));
} }
async function searchMessagesInConversation( async function searchMessagesInConversation(query, conversationId, { limit } = {}) {
query,
conversationId,
{ limit } = {}
) {
const rows = await db.all( const rows = await db.all(
`SELECT `SELECT
messages.json, messages.json,
@ -1748,9 +1702,7 @@ async function getMessageCount() {
const row = await db.get(`SELECT count(*) from ${MESSAGES_TABLE};`); const row = await db.get(`SELECT count(*) from ${MESSAGES_TABLE};`);
if (!row) { if (!row) {
throw new Error( throw new Error(`getMessageCount: Unable to get count of ${MESSAGES_TABLE}`);
`getMessageCount: Unable to get count of ${MESSAGES_TABLE}`
);
} }
return row['count(*)']; return row['count(*)'];
@ -1962,9 +1914,7 @@ async function removeMessage(id) {
// Our node interface doesn't seem to allow you to replace one single ? with an array // Our node interface doesn't seem to allow you to replace one single ? with an array
await db.run( await db.run(
`DELETE FROM ${MESSAGES_TABLE} WHERE id IN ( ${id `DELETE FROM ${MESSAGES_TABLE} WHERE id IN ( ${id.map(() => '?').join(', ')} );`,
.map(() => '?')
.join(', ')} );`,
id id
); );
} }
@ -1975,9 +1925,7 @@ async function getMessageIdsFromServerIds(serverIds, conversationId) {
} }
// Sanitize the input as we're going to use it directly in the query // Sanitize the input as we're going to use it directly in the query
const validIds = serverIds const validIds = serverIds.map(id => Number(id)).filter(n => !Number.isNaN(n));
.map(id => Number(id))
.filter(n => !Number.isNaN(n));
/* /*
Sqlite3 doesn't have a good way to have `IN` query with another query. Sqlite3 doesn't have a good way to have `IN` query with another query.
@ -2009,16 +1957,12 @@ async function getMessageById(id) {
} }
async function getAllMessages() { async function getAllMessages() {
const rows = await db.all( const rows = await db.all(`SELECT json FROM ${MESSAGES_TABLE} ORDER BY id ASC;`);
`SELECT json FROM ${MESSAGES_TABLE} ORDER BY id ASC;`
);
return map(rows, row => jsonToObject(row.json)); return map(rows, row => jsonToObject(row.json));
} }
async function getAllMessageIds() { async function getAllMessageIds() {
const rows = await db.all( const rows = await db.all(`SELECT id FROM ${MESSAGES_TABLE} ORDER BY id ASC;`);
`SELECT id FROM ${MESSAGES_TABLE} ORDER BY id ASC;`
);
return map(rows, row => row.id); return map(rows, row => row.id);
} }
@ -2115,13 +2059,10 @@ async function getMessagesBySentAt(sentAt) {
} }
async function getLastHashBySnode(convoId, snode) { async function getLastHashBySnode(convoId, snode) {
const row = await db.get( const row = await db.get('SELECT * FROM lastHashes WHERE snode = $snode AND id = $id;', {
'SELECT * FROM lastHashes WHERE snode = $snode AND id = $id;', $snode: snode,
{ $id: convoId,
$snode: snode, });
$id: convoId,
}
);
if (!row) { if (!row) {
return null; return null;
@ -2132,9 +2073,7 @@ async function getLastHashBySnode(convoId, snode) {
async function getSeenMessagesByHashList(hashes) { async function getSeenMessagesByHashList(hashes) {
const rows = await db.all( const rows = await db.all(
`SELECT * FROM seenMessages WHERE hash IN ( ${hashes `SELECT * FROM seenMessages WHERE hash IN ( ${hashes.map(() => '?').join(', ')} );`,
.map(() => '?')
.join(', ')} );`,
hashes hashes
); );
@ -2224,13 +2163,7 @@ async function updateUnprocessedAttempts(id, attempts) {
}); });
} }
async function updateUnprocessedWithData(id, data = {}) { async function updateUnprocessedWithData(id, data = {}) {
const { const { source, sourceDevice, serverTimestamp, decrypted, senderIdentity } = data;
source,
sourceDevice,
serverTimestamp,
decrypted,
senderIdentity,
} = data;
await db.run( await db.run(
`UPDATE unprocessed SET `UPDATE unprocessed SET
@ -2270,9 +2203,7 @@ async function getUnprocessedCount() {
} }
async function getAllUnprocessed() { async function getAllUnprocessed() {
const rows = await db.all( const rows = await db.all('SELECT * FROM unprocessed ORDER BY timestamp ASC;');
'SELECT * FROM unprocessed ORDER BY timestamp ASC;'
);
return rows; return rows;
} }
@ -2288,10 +2219,7 @@ async function removeUnprocessed(id) {
} }
// Our node interface doesn't seem to allow you to replace one single ? with an array // Our node interface doesn't seem to allow you to replace one single ? with an array
await db.run( await db.run(`DELETE FROM unprocessed WHERE id IN ( ${id.map(() => '?').join(', ')} );`, id);
`DELETE FROM unprocessed WHERE id IN ( ${id.map(() => '?').join(', ')} );`,
id
);
} }
async function removeAllUnprocessed() { async function removeAllUnprocessed() {
@ -2318,9 +2246,7 @@ async function getNextAttachmentDownloadJobs(limit, options = {}) {
async function saveAttachmentDownloadJob(job) { async function saveAttachmentDownloadJob(job) {
const { id, pending, timestamp } = job; const { id, pending, timestamp } = job;
if (!id) { if (!id) {
throw new Error( throw new Error('saveAttachmentDownloadJob: Provided job did not have a truthy id');
'saveAttachmentDownloadJob: Provided job did not have a truthy id'
);
} }
await db.run( await db.run(
@ -2344,18 +2270,13 @@ async function saveAttachmentDownloadJob(job) {
); );
} }
async function setAttachmentDownloadJobPending(id, pending) { async function setAttachmentDownloadJobPending(id, pending) {
await db.run( await db.run('UPDATE attachment_downloads SET pending = $pending WHERE id = $id;', {
'UPDATE attachment_downloads SET pending = $pending WHERE id = $id;', $id: id,
{ $pending: pending,
$id: id, });
$pending: pending,
}
);
} }
async function resetAttachmentDownloadPending() { async function resetAttachmentDownloadPending() {
await db.run( await db.run('UPDATE attachment_downloads SET pending = 0 WHERE pending != 0;');
'UPDATE attachment_downloads SET pending = 0 WHERE pending != 0;'
);
} }
async function removeAttachmentDownloadJob(id) { async function removeAttachmentDownloadJob(id) {
return removeById(ATTACHMENT_DOWNLOADS_TABLE, id); return removeById(ATTACHMENT_DOWNLOADS_TABLE, id);
@ -2400,10 +2321,7 @@ async function removeAllConversations() {
await removeAllFromTable(CONVERSATIONS_TABLE); await removeAllFromTable(CONVERSATIONS_TABLE);
} }
async function getMessagesWithVisualMediaAttachments( async function getMessagesWithVisualMediaAttachments(conversationId, { limit }) {
conversationId,
{ limit }
) {
const rows = await db.all( const rows = await db.all(
`SELECT json FROM ${MESSAGES_TABLE} WHERE `SELECT json FROM ${MESSAGES_TABLE} WHERE
conversationId = $conversationId AND conversationId = $conversationId AND
@ -2507,9 +2425,7 @@ async function removeKnownAttachments(allAttachments) {
const chunkSize = 50; const chunkSize = 50;
const total = await getMessageCount(); const total = await getMessageCount();
console.log( console.log(`removeKnownAttachments: About to iterate through ${total} messages`);
`removeKnownAttachments: About to iterate through ${total} messages`
);
let count = 0; let count = 0;
let complete = false; let complete = false;
@ -2626,28 +2542,19 @@ async function removePrefixFromGroupConversations(instance) {
); );
// We have another conversation with the same future name. // We have another conversation with the same future name.
// We decided to keep only the conversation with the higher number of messages // We decided to keep only the conversation with the higher number of messages
const countMessagesOld = await getMessagesCountByConversation( const countMessagesOld = await getMessagesCountByConversation(instance, oldId, {
instance, limit: Number.MAX_VALUE,
oldId, });
{ limit: Number.MAX_VALUE } const countMessagesNew = await getMessagesCountByConversation(instance, newId, {
); limit: Number.MAX_VALUE,
const countMessagesNew = await getMessagesCountByConversation( });
instance,
newId,
{ limit: Number.MAX_VALUE }
);
console.log( console.log(`countMessagesOld: ${countMessagesOld}, countMessagesNew: ${countMessagesNew}`);
`countMessagesOld: ${countMessagesOld}, countMessagesNew: ${countMessagesNew}`
);
const deleteId = countMessagesOld > countMessagesNew ? newId : oldId; const deleteId = countMessagesOld > countMessagesNew ? newId : oldId;
await instance.run( await instance.run(`DELETE FROM ${CONVERSATIONS_TABLE} WHERE id = $id;`, {
`DELETE FROM ${CONVERSATIONS_TABLE} WHERE id = $id;`, $id: deleteId,
{ });
$id: deleteId,
}
);
} }
const morphedObject = { const morphedObject = {
@ -2703,8 +2610,7 @@ function remove05PrefixFromStringIfNeeded(str) {
async function updateExistingClosedGroupToClosedGroup(instance) { async function updateExistingClosedGroupToClosedGroup(instance) {
// the migration is called only once, so all current groups not being open groups are v1 closed group. // the migration is called only once, so all current groups not being open groups are v1 closed group.
const allClosedGroupV1 = const allClosedGroupV1 = (await getAllClosedGroupConversations(instance)) || [];
(await getAllClosedGroupConversations(instance)) || [];
await Promise.all( await Promise.all(
allClosedGroupV1.map(async groupV1 => { allClosedGroupV1.map(async groupV1 => {
@ -2751,9 +2657,7 @@ async function getAllEncryptionKeyPairsForGroup(groupPublicKey) {
} }
async function getAllEncryptionKeyPairsForGroupRaw(groupPublicKey) { async function getAllEncryptionKeyPairsForGroupRaw(groupPublicKey) {
const pubkeyAsString = groupPublicKey.key const pubkeyAsString = groupPublicKey.key ? groupPublicKey.key : groupPublicKey;
? groupPublicKey.key
: groupPublicKey;
const rows = await db.all( const rows = await db.all(
`SELECT * FROM ${CLOSED_GROUP_V2_KEY_PAIRS_TABLE} WHERE groupPublicKey = $groupPublicKey ORDER BY timestamp ASC;`, `SELECT * FROM ${CLOSED_GROUP_V2_KEY_PAIRS_TABLE} WHERE groupPublicKey = $groupPublicKey ORDER BY timestamp ASC;`,
{ {
@ -2772,11 +2676,7 @@ async function getLatestClosedGroupEncryptionKeyPair(groupPublicKey) {
return rows[rows.length - 1]; return rows[rows.length - 1];
} }
async function addClosedGroupEncryptionKeyPair( async function addClosedGroupEncryptionKeyPair(groupPublicKey, keypair, instance) {
groupPublicKey,
keypair,
instance
) {
const timestamp = Date.now(); const timestamp = Date.now();
await (db || instance).run( await (db || instance).run(
@ -2803,9 +2703,7 @@ async function isKeyPairAlreadySaved(
) { ) {
const allKeyPairs = await getAllEncryptionKeyPairsForGroup(groupPublicKey); const allKeyPairs = await getAllEncryptionKeyPairsForGroup(groupPublicKey);
return (allKeyPairs || []).some( return (allKeyPairs || []).some(
k => k => newKeyPairInHex.publicHex === k.publicHex && newKeyPairInHex.privateHex === k.privateHex
newKeyPairInHex.publicHex === k.publicHex &&
newKeyPairInHex.privateHex === k.privateHex
); );
} }
@ -2882,10 +2780,7 @@ async function saveV2OpenGroupRoom(opengroupsv2Room) {
} }
async function removeV2OpenGroupRoom(conversationId) { async function removeV2OpenGroupRoom(conversationId) {
await db.run( await db.run(`DELETE FROM ${OPEN_GROUP_ROOMS_V2_TABLE} WHERE conversationId = $conversationId`, {
`DELETE FROM ${OPEN_GROUP_ROOMS_V2_TABLE} WHERE conversationId = $conversationId`, $conversationId: conversationId,
{ });
$conversationId: conversationId,
}
);
} }

@ -24,18 +24,14 @@ function initialize() {
try { try {
const fn = sql[callName]; const fn = sql[callName];
if (!fn) { if (!fn) {
throw new Error( throw new Error(`sql channel: ${callName} is not an available function`);
`sql channel: ${callName} is not an available function`
);
} }
const result = await fn(...args); const result = await fn(...args);
event.sender.send(`${SQL_CHANNEL_KEY}-done`, jobId, null, result); event.sender.send(`${SQL_CHANNEL_KEY}-done`, jobId, null, result);
} catch (error) { } catch (error) {
const errorForDisplay = error && error.stack ? error.stack : error; const errorForDisplay = error && error.stack ? error.stack : error;
console.log( console.log(`sql channel error with call ${callName}: ${errorForDisplay}`);
`sql channel error with call ${callName}: ${errorForDisplay}`
);
// FIXME this line cause the test-integration to fail and we probably don't need it during test // FIXME this line cause the test-integration to fail and we probably don't need it during test
if (!process.env.NODE_ENV.includes('test-integration')) { if (!process.env.NODE_ENV.includes('test-integration')) {
event.sender.send(`${SQL_CHANNEL_KEY}-done`, jobId, errorForDisplay); event.sender.send(`${SQL_CHANNEL_KEY}-done`, jobId, errorForDisplay);

@ -9,12 +9,7 @@ let tray = null;
function createTrayIcon(getMainWindow, messages) { function createTrayIcon(getMainWindow, messages) {
// A smaller icon is needed on macOS // A smaller icon is needed on macOS
const iconSize = process.platform === 'darwin' ? '16' : '256'; const iconSize = process.platform === 'darwin' ? '16' : '256';
const iconNoNewMessages = path.join( const iconNoNewMessages = path.join(__dirname, '..', 'images', `icon_${iconSize}.png`);
__dirname,
'..',
'images',
`icon_${iconSize}.png`
);
tray = new Tray(iconNoNewMessages); tray = new Tray(iconNoNewMessages);
@ -65,8 +60,7 @@ function createTrayIcon(getMainWindow, messages) {
trayContextMenu = Menu.buildFromTemplate([ trayContextMenu = Menu.buildFromTemplate([
{ {
id: 'toggleWindowVisibility', id: 'toggleWindowVisibility',
label: label: messages[mainWindow.isVisible() ? 'appMenuHide' : 'show'].message,
messages[mainWindow.isVisible() ? 'appMenuHide' : 'show'].message,
click: tray.toggleWindowVisibility, click: tray.toggleWindowVisibility,
}, },
{ {

@ -27,10 +27,7 @@ if (config.has(storageProfile)) {
} }
if (storageProfile) { if (storageProfile) {
const userData = path.join( const userData = path.join(app.getPath('appData'), `Session-${storageProfile}`);
app.getPath('appData'),
`Session-${storageProfile}`
);
app.setPath('userData', userData); app.setPath('userData', userData);
} }

@ -19,15 +19,8 @@ module.exports = async function(context) {
const executableName = context.packager.executableName; const executableName = context.packager.executableName;
const sourceExecutable = path.join(context.appOutDir, executableName); const sourceExecutable = path.join(context.appOutDir, executableName);
const targetExecutable = path.join( const targetExecutable = path.join(context.appOutDir, `${executableName}-bin`);
context.appOutDir, const launcherScript = path.join(context.appOutDir, 'resources', 'launcher-script.sh');
`${executableName}-bin`
);
const launcherScript = path.join(
context.appOutDir,
'resources',
'launcher-script.sh'
);
const chromeSandbox = path.join(context.appOutDir, 'chrome-sandbox'); const chromeSandbox = path.join(context.appOutDir, 'chrome-sandbox');
return Promise.all([ return Promise.all([

@ -21,16 +21,10 @@ exports.default = async function notarizing(context) {
log('Notarizing mac application'); log('Notarizing mac application');
const appName = context.packager.appInfo.productFilename; const appName = context.packager.appInfo.productFilename;
const { const { SIGNING_APPLE_ID, SIGNING_APP_PASSWORD, SIGNING_TEAM_ID } = process.env;
SIGNING_APPLE_ID,
SIGNING_APP_PASSWORD,
SIGNING_TEAM_ID,
} = process.env;
if (isEmpty(SIGNING_APPLE_ID) || isEmpty(SIGNING_APP_PASSWORD)) { if (isEmpty(SIGNING_APPLE_ID) || isEmpty(SIGNING_APP_PASSWORD)) {
log( log('SIGNING_APPLE_ID or SIGNING_APP_PASSWORD not set.\nTerminating noratization.');
'SIGNING_APPLE_ID or SIGNING_APP_PASSWORD not set.\nTerminating noratization.'
);
return; return;
} }

@ -168,10 +168,7 @@
// Ensure accounts created prior to 1.0.0-beta8 do have their // Ensure accounts created prior to 1.0.0-beta8 do have their
// 'primaryDevicePubKey' defined. // 'primaryDevicePubKey' defined.
if ( if (Whisper.Registration.isDone() && !storage.get('primaryDevicePubKey', null)) {
Whisper.Registration.isDone() &&
!storage.get('primaryDevicePubKey', null)
) {
storage.put( storage.put(
'primaryDevicePubKey', 'primaryDevicePubKey',
window.libsession.Utils.UserUtils.getOurPubKeyStrFromCache() window.libsession.Utils.UserUtils.getOurPubKeyStrFromCache()
@ -226,9 +223,7 @@
await storage.put('version', currentVersion); await storage.put('version', currentVersion);
if (newVersion) { if (newVersion) {
window.log.info( window.log.info(`New version detected: ${currentVersion}; previous: ${lastVersion}`);
`New version detected: ${currentVersion}; previous: ${lastVersion}`
);
await window.Signal.Data.cleanupOrphanedAttachments(); await window.Signal.Data.cleanupOrphanedAttachments();
@ -265,31 +260,26 @@
} }
}); });
Whisper.events.on( Whisper.events.on('deleteLocalPublicMessages', async ({ messageServerIds, conversationId }) => {
'deleteLocalPublicMessages', if (!Array.isArray(messageServerIds)) {
async ({ messageServerIds, conversationId }) => { return;
if (!Array.isArray(messageServerIds)) { }
return; const messageIds = await window.Signal.Data.getMessageIdsFromServerIds(
} messageServerIds,
const messageIds = await window.Signal.Data.getMessageIdsFromServerIds( conversationId
messageServerIds, );
conversationId if (messageIds.length === 0) {
); return;
if (messageIds.length === 0) {
return;
}
const conversation = window
.getConversationController()
.get(conversationId);
messageIds.forEach(id => {
if (conversation) {
conversation.removeMessage(id);
}
window.Signal.Data.removeMessage(id);
});
} }
);
const conversation = window.getConversationController().get(conversationId);
messageIds.forEach(id => {
if (conversation) {
conversation.removeMessage(id);
}
window.Signal.Data.removeMessage(id);
});
});
function manageExpiringData() { function manageExpiringData() {
window.Signal.Data.cleanSeenMessages(); window.Signal.Data.cleanSeenMessages();
@ -303,9 +293,7 @@
window.log.info('Cleanup: starting...'); window.log.info('Cleanup: starting...');
const results = await Promise.all([ const results = await Promise.all([window.Signal.Data.getOutgoingWithoutExpiresAt()]);
window.Signal.Data.getOutgoingWithoutExpiresAt(),
]);
// Combine the models // Combine the models
const messagesForCleanup = results.reduce( const messagesForCleanup = results.reduce(
@ -313,29 +301,20 @@
[] []
); );
window.log.info( window.log.info(`Cleanup: Found ${messagesForCleanup.length} messages for cleanup`);
`Cleanup: Found ${messagesForCleanup.length} messages for cleanup`
);
await Promise.all( await Promise.all(
messagesForCleanup.map(async message => { messagesForCleanup.map(async message => {
const delivered = message.get('delivered'); const delivered = message.get('delivered');
const sentAt = message.get('sent_at'); const sentAt = message.get('sent_at');
const expirationStartTimestamp = message.get( const expirationStartTimestamp = message.get('expirationStartTimestamp');
'expirationStartTimestamp'
);
if (message.hasErrors()) { if (message.hasErrors()) {
return; return;
} }
if (delivered) { if (delivered) {
window.log.info( window.log.info(`Cleanup: Starting timer for delivered message ${sentAt}`);
`Cleanup: Starting timer for delivered message ${sentAt}` message.set('expirationStartTimestamp', expirationStartTimestamp || sentAt);
);
message.set(
'expirationStartTimestamp',
expirationStartTimestamp || sentAt
);
await message.setToExpire(); await message.setToExpire();
return; return;
} }
@ -485,13 +464,11 @@
profileKey profileKey
); );
const avatarPointer = await libsession.Utils.AttachmentUtils.uploadAvatar( const avatarPointer = await libsession.Utils.AttachmentUtils.uploadAvatar({
{ ...dataResized,
...dataResized, data: encryptedData,
data: encryptedData, size: encryptedData.byteLength,
size: encryptedData.byteLength, });
}
);
({ url } = avatarPointer); ({ url } = avatarPointer);
@ -512,12 +489,8 @@
avatar: newAvatarPath, avatar: newAvatarPath,
}); });
await conversation.commit(); await conversation.commit();
window.libsession.Utils.UserUtils.setLastProfileUpdateTimestamp( window.libsession.Utils.UserUtils.setLastProfileUpdateTimestamp(Date.now());
Date.now() await window.libsession.Utils.SyncUtils.forceSyncConfigurationNowIfNeeded(true);
);
await window.libsession.Utils.SyncUtils.forceSyncConfigurationNowIfNeeded(
true
);
} catch (error) { } catch (error) {
window.log.error( window.log.error(
'showEditProfileDialog Error ensuring that image is properly sized:', 'showEditProfileDialog Error ensuring that image is properly sized:',
@ -531,12 +504,8 @@
}); });
// might be good to not trigger a sync if the name did not change // might be good to not trigger a sync if the name did not change
await conversation.commit(); await conversation.commit();
window.libsession.Utils.UserUtils.setLastProfileUpdateTimestamp( window.libsession.Utils.UserUtils.setLastProfileUpdateTimestamp(Date.now());
Date.now() await window.libsession.Utils.SyncUtils.forceSyncConfigurationNowIfNeeded(true);
);
await window.libsession.Utils.SyncUtils.forceSyncConfigurationNowIfNeeded(
true
);
} }
// inform all your registered public servers // inform all your registered public servers
@ -551,9 +520,7 @@
.getConversationController() .getConversationController()
.getConversations() .getConversations()
.filter(convo => convo.isPublic()) .filter(convo => convo.isPublic())
.forEach(convo => .forEach(convo => convo.trigger('ourAvatarChanged', { url, profileKey }));
convo.trigger('ourAvatarChanged', { url, profileKey })
);
} }
}, },
}); });
@ -640,46 +607,37 @@
} }
}); });
Whisper.events.on( Whisper.events.on('publicChatInvitationAccepted', async (serverAddress, channelId) => {
'publicChatInvitationAccepted', // To some degree this has been copy-pasted
async (serverAddress, channelId) => { // form connection_to_server_dialog_view.js:
// To some degree this has been copy-pasted const rawServerUrl = serverAddress.replace(/^https?:\/\//i, '').replace(/[/\\]+$/i, '');
// form connection_to_server_dialog_view.js: const sslServerUrl = `https://${rawServerUrl}`;
const rawServerUrl = serverAddress const conversationId = `publicChat:${channelId}@${rawServerUrl}`;
.replace(/^https?:\/\//i, '')
.replace(/[/\\]+$/i, '');
const sslServerUrl = `https://${rawServerUrl}`;
const conversationId = `publicChat:${channelId}@${rawServerUrl}`;
const conversationExists = window
.getConversationController()
.get(conversationId);
if (conversationExists) {
window.log.warn('We are already a member of this public chat');
window.libsession.Utils.ToastUtils.pushAlreadyMemberOpenGroup();
return; const conversationExists = window.getConversationController().get(conversationId);
} if (conversationExists) {
window.log.warn('We are already a member of this public chat');
window.libsession.Utils.ToastUtils.pushAlreadyMemberOpenGroup();
const conversation = await window return;
.getConversationController()
.getOrCreateAndWait(conversationId, 'group');
await conversation.setPublicSource(sslServerUrl, channelId);
const channelAPI = await window.lokiPublicChatAPI.findOrCreateChannel(
sslServerUrl,
channelId,
conversationId
);
if (!channelAPI) {
window.log.warn(`Could not connect to ${serverAddress}`);
return;
}
window.inboxStore.dispatch(
window.actionsCreators.openConversationExternal(conversationId)
);
} }
);
const conversation = await window
.getConversationController()
.getOrCreateAndWait(conversationId, 'group');
await conversation.setPublicSource(sslServerUrl, channelId);
const channelAPI = await window.lokiPublicChatAPI.findOrCreateChannel(
sslServerUrl,
channelId,
conversationId
);
if (!channelAPI) {
window.log.warn(`Could not connect to ${serverAddress}`);
return;
}
window.inboxStore.dispatch(window.actionsCreators.openConversationExternal(conversationId));
});
Whisper.events.on('leaveGroup', async groupConvo => { Whisper.events.on('leaveGroup', async groupConvo => {
if (appView) { if (appView) {
@ -690,9 +648,7 @@
Whisper.Notifications.on('click', (id, messageId) => { Whisper.Notifications.on('click', (id, messageId) => {
window.showWindow(); window.showWindow();
if (id) { if (id) {
window.inboxStore.dispatch( window.inboxStore.dispatch(window.actionsCreators.openConversationExternal(id, messageId));
window.actionsCreators.openConversationExternal(id, messageId)
);
} else { } else {
appView.openInbox({ appView.openInbox({
initialLoadComplete, initialLoadComplete,
@ -802,9 +758,7 @@
window.addEventListener('offline', onOffline); window.addEventListener('offline', onOffline);
} }
if (connectCount === 0 && !navigator.onLine) { if (connectCount === 0 && !navigator.onLine) {
window.log.warn( window.log.warn('Starting up offline; will connect when we have network access');
'Starting up offline; will connect when we have network access'
);
window.addEventListener('online', onOnline); window.addEventListener('online', onOnline);
onEmpty(); // this ensures that the loading screen is dismissed onEmpty(); // this ensures that the loading screen is dismissed
return; return;
@ -841,14 +795,8 @@
initAPIs(); initAPIs();
await initSpecialConversations(); await initSpecialConversations();
messageReceiver = new textsecure.MessageReceiver(); messageReceiver = new textsecure.MessageReceiver();
messageReceiver.addEventListener( messageReceiver.addEventListener('message', window.DataMessageReceiver.handleMessageEvent);
'message', messageReceiver.addEventListener('sent', window.DataMessageReceiver.handleMessageEvent);
window.DataMessageReceiver.handleMessageEvent
);
messageReceiver.addEventListener(
'sent',
window.DataMessageReceiver.handleMessageEvent
);
messageReceiver.addEventListener('reconnect', onReconnect); messageReceiver.addEventListener('reconnect', onReconnect);
messageReceiver.addEventListener('configuration', onConfiguration); messageReceiver.addEventListener('configuration', onConfiguration);
// messageReceiver.addEventListener('typing', onTyping); // messageReceiver.addEventListener('typing', onTyping);

@ -64,11 +64,7 @@
}; };
request.onerror = () => { request.onerror = () => {
Whisper.Database.handleDOMException( Whisper.Database.handleDOMException('clearStores request error', request.error, reject);
'clearStores request error',
request.error,
reject
);
}; };
}); });
}); });

@ -35,23 +35,19 @@
} }
const message = messages.find( const message = messages.find(
item => item => !item.isIncoming() && originalSource === item.get('conversationId')
!item.isIncoming() && originalSource === item.get('conversationId')
); );
if (message) { if (message) {
return message; return message;
} }
const groups = await window.Signal.Data.getAllGroupsInvolvingId( const groups = await window.Signal.Data.getAllGroupsInvolvingId(originalSource);
originalSource
);
const ids = groups.pluck('id'); const ids = groups.pluck('id');
ids.push(originalSource); ids.push(originalSource);
const target = messages.find( const target = messages.find(
item => item => !item.isIncoming() && _.contains(ids, item.get('conversationId'))
!item.isIncoming() && _.contains(ids, item.get('conversationId'))
); );
if (!target) { if (!target) {
return null; return null;
@ -61,14 +57,9 @@
}, },
async onReceipt(receipt) { async onReceipt(receipt) {
try { try {
const messages = await window.Signal.Data.getMessagesBySentAt( const messages = await window.Signal.Data.getMessagesBySentAt(receipt.get('timestamp'));
receipt.get('timestamp')
);
const message = await this.getTargetMessage( const message = await this.getTargetMessage(receipt.get('source'), messages);
receipt.get('source'),
messages
);
if (!message) { if (!message) {
window.log.info( window.log.info(
'No message for delivery receipt', 'No message for delivery receipt',
@ -80,9 +71,7 @@
const deliveries = message.get('delivered') || 0; const deliveries = message.get('delivered') || 0;
const deliveredTo = message.get('delivered_to') || []; const deliveredTo = message.get('delivered_to') || [];
const expirationStartTimestamp = message.get( const expirationStartTimestamp = message.get('expirationStartTimestamp');
'expirationStartTimestamp'
);
message.set({ message.set({
delivered_to: _.union(deliveredTo, [receipt.get('source')]), delivered_to: _.union(deliveredTo, [receipt.get('source')]),
delivered: deliveries + 1, delivered: deliveries + 1,
@ -98,9 +87,7 @@
} }
// notify frontend listeners // notify frontend listeners
const conversation = window const conversation = window.getConversationController().get(message.get('conversationId'));
.getConversationController()
.get(message.get('conversationId'));
if (conversation) { if (conversation) {
conversation.updateLastMessage(); conversation.updateLastMessage();
} }

@ -21,9 +21,7 @@
window.libsession.Utils.UserUtils.getOurPubKeyStrFromCache(); window.libsession.Utils.UserUtils.getOurPubKeyStrFromCache();
} catch (e) { } catch (e) {
// give it a minute // give it a minute
log.warn( log.warn('Could not check to see if newer version is available cause our pubkey is not set');
'Could not check to see if newer version is available cause our pubkey is not set'
);
nextWaitSeconds = 60; nextWaitSeconds = 60;
setTimeout(async () => { setTimeout(async () => {
await checkForUpgrades(); await checkForUpgrades();
@ -81,9 +79,7 @@
if (expiredVersion !== null) { if (expiredVersion !== null) {
return res(expiredVersion); return res(expiredVersion);
} }
log.info( log.info(`Delaying sending checks for ${nextWaitSeconds}s, no version yet`);
`Delaying sending checks for ${nextWaitSeconds}s, no version yet`
);
setTimeout(waitForVersion, nextWaitSeconds * 1000); setTimeout(waitForVersion, nextWaitSeconds * 1000);
return true; return true;
} }
@ -112,9 +108,7 @@
let timestamp = NaN; let timestamp = NaN;
try { try {
const res = await window.tokenlessFileServerAdnAPI.serverRequest( const res = await window.tokenlessFileServerAdnAPI.serverRequest('loki/v1/time');
'loki/v1/time'
);
if (res.ok) { if (res.ok) {
timestamp = res.response; timestamp = res.response;
} }

@ -81,10 +81,7 @@
clearTimeout(timeout); clearTimeout(timeout);
timeout = setTimeout(destroyExpiredMessages, wait); timeout = setTimeout(destroyExpiredMessages, wait);
} }
const throttledCheckExpiringMessages = _.throttle( const throttledCheckExpiringMessages = _.throttle(checkExpiringMessages, 1000);
checkExpiringMessages,
1000
);
Whisper.ExpiringMessagesListener = { Whisper.ExpiringMessagesListener = {
nextExpiration: null, nextExpiration: null,
@ -103,11 +100,7 @@
); );
}, },
getAbbreviated() { getAbbreviated() {
return i18n( return i18n(['timerOption', this.get('time'), this.get('unit'), 'abbreviated'].join('_'));
['timerOption', this.get('time'), this.get('unit'), 'abbreviated'].join(
'_'
)
);
}, },
}); });
Whisper.ExpirationTimerOptions = new (Backbone.Collection.extend({ Whisper.ExpirationTimerOptions = new (Backbone.Collection.extend({

@ -31,10 +31,7 @@
throw new Error('Tried to store undefined'); throw new Error('Tried to store undefined');
} }
if (!ready) { if (!ready) {
window.log.warn( window.log.warn('Called storage.put before storage is ready. key:', key);
'Called storage.put before storage is ready. key:',
key
);
} }
const item = items.add({ id: key, value }, { merge: true }); const item = items.add({ id: key, value }, { merge: true });
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {

@ -136,9 +136,7 @@ async function _runJob(job) {
try { try {
if (!job || !attachment || !messageId) { if (!job || !attachment || !messageId) {
throw new Error( throw new Error(`_runJob: Key information required for job was missing. Job id: ${id}`);
`_runJob: Key information required for job was missing. Job id: ${id}`
);
} }
const found = await getMessageById(messageId); const found = await getMessageById(messageId);
@ -166,20 +164,14 @@ async function _runJob(job) {
); );
await _finishJob(message, id); await _finishJob(message, id);
await _addAttachmentToMessage( await _addAttachmentToMessage(message, _markAttachmentAsError(attachment), { type, index });
message,
_markAttachmentAsError(attachment),
{ type, index }
);
return; return;
} }
throw error; throw error;
} }
const upgradedAttachment = await Signal.Migrations.processNewAttachment( const upgradedAttachment = await Signal.Migrations.processNewAttachment(downloaded);
downloaded
);
await _addAttachmentToMessage(message, upgradedAttachment, { type, index }); await _addAttachmentToMessage(message, upgradedAttachment, { type, index });
@ -194,11 +186,7 @@ async function _runJob(job) {
); );
await _finishJob(message, id); await _finishJob(message, id);
await _addAttachmentToMessage( await _addAttachmentToMessage(message, _markAttachmentAsError(attachment), { type, index });
message,
_markAttachmentAsError(attachment),
{ type, index }
);
return; return;
} }
@ -267,9 +255,7 @@ async function _addAttachmentToMessage(message, attachment, { type, index }) {
if (type === 'preview') { if (type === 'preview') {
const preview = message.get('preview'); const preview = message.get('preview');
if (!preview || preview.length <= index) { if (!preview || preview.length <= index) {
throw new Error( throw new Error(`_addAttachmentToMessage: preview didn't exist or ${index} was too large`);
`_addAttachmentToMessage: preview didn't exist or ${index} was too large`
);
} }
const item = preview[index]; const item = preview[index];
if (!item) { if (!item) {
@ -282,9 +268,7 @@ async function _addAttachmentToMessage(message, attachment, { type, index }) {
if (type === 'contact') { if (type === 'contact') {
const contact = message.get('contact'); const contact = message.get('contact');
if (!contact || contact.length <= index) { if (!contact || contact.length <= index) {
throw new Error( throw new Error(`_addAttachmentToMessage: contact didn't exist or ${index} was too large`);
`_addAttachmentToMessage: contact didn't exist or ${index} was too large`
);
} }
const item = contact[index]; const item = contact[index];
if (item && item.avatar && item.avatar.avatar) { if (item && item.avatar && item.avatar.avatar) {
@ -312,9 +296,7 @@ async function _addAttachmentToMessage(message, attachment, { type, index }) {
const item = attachments[index]; const item = attachments[index];
if (!item) { if (!item) {
throw new Error( throw new Error(`_addAttachmentToMessage: attachment ${index} was falsey`);
`_addAttachmentToMessage: attachment ${index} was falsey`
);
} }
_replaceAttachment(item, 'thumbnail', attachment, logPrefix); _replaceAttachment(item, 'thumbnail', attachment, logPrefix);
return; return;

@ -31,10 +31,7 @@ exports.autoOrientImage = (fileOrBlobOrURL, options = {}) => {
} }
const canvas = canvasOrError; const canvas = canvasOrError;
const dataURL = canvas.toDataURL( const dataURL = canvas.toDataURL(optionsWithDefaults.type, optionsWithDefaults.quality);
optionsWithDefaults.type,
optionsWithDefaults.quality
);
resolve(dataURL); resolve(dataURL);
}, },

@ -174,8 +174,7 @@ async function importConversationsFromJSON(conversations, options) {
for (let i = 0, max = conversations.length; i < max; i += 1) { for (let i = 0, max = conversations.length; i < max; i += 1) {
const toAdd = unstringify(conversations[i]); const toAdd = unstringify(conversations[i]);
const haveConversationAlready = const haveConversationAlready = conversationLookup[getConversationKey(toAdd)];
conversationLookup[getConversationKey(toAdd)];
if (haveConversationAlready) { if (haveConversationAlready) {
skipCount += 1; skipCount += 1;
@ -186,23 +185,14 @@ async function importConversationsFromJSON(conversations, options) {
count += 1; count += 1;
// eslint-disable-next-line no-await-in-loop // eslint-disable-next-line no-await-in-loop
const migrated = await window.Signal.Types.Conversation.migrateConversation( const migrated = await window.Signal.Types.Conversation.migrateConversation(toAdd, {
toAdd, writeNewAttachmentData,
{ });
writeNewAttachmentData,
}
);
// eslint-disable-next-line no-await-in-loop // eslint-disable-next-line no-await-in-loop
await window.Signal.Data.saveConversation(migrated); await window.Signal.Data.saveConversation(migrated);
} }
window.log.info( window.log.info('Done importing conversations:', 'Total count:', count, 'Skipped:', skipCount);
'Done importing conversations:',
'Total count:',
count,
'Skipped:',
skipCount
);
} }
async function importFromJsonString(jsonString, targetPath, options) { async function importFromJsonString(jsonString, targetPath, options) {
@ -229,9 +219,7 @@ async function importFromJsonString(jsonString, targetPath, options) {
delete importObject.sessions; delete importObject.sessions;
delete importObject.unprocessed; delete importObject.unprocessed;
window.log.info( window.log.info('This is a light import; contacts, groups and messages only');
'This is a light import; contacts, groups and messages only'
);
} }
// We mutate the on-disk backup to prevent the user from importing client // We mutate the on-disk backup to prevent the user from importing client
@ -260,9 +248,7 @@ async function importFromJsonString(jsonString, targetPath, options) {
_.map(remainingStoreNames, async storeName => { _.map(remainingStoreNames, async storeName => {
const save = SAVE_FUNCTIONS[storeName]; const save = SAVE_FUNCTIONS[storeName];
if (!_.isFunction(save)) { if (!_.isFunction(save)) {
throw new Error( throw new Error(`importFromJsonString: Didn't have save function for store ${storeName}`);
`importFromJsonString: Didn't have save function for store ${storeName}`
);
} }
window.log.info(`Importing items for store ${storeName}`); window.log.info(`Importing items for store ${storeName}`);
@ -279,12 +265,7 @@ async function importFromJsonString(jsonString, targetPath, options) {
await save(toAdd); await save(toAdd);
} }
window.log.info( window.log.info('Done importing to store', storeName, 'Total count:', toImport.length);
'Done importing to store',
storeName,
'Total count:',
toImport.length
);
}) })
); );
@ -339,10 +320,7 @@ function readFileAsText(parent, name) {
// Buffer instances are also Uint8Array instances, but they might be a view // Buffer instances are also Uint8Array instances, but they might be a view
// https://nodejs.org/docs/latest/api/buffer.html#buffer_buffers_and_typedarray // https://nodejs.org/docs/latest/api/buffer.html#buffer_buffers_and_typedarray
const toArrayBuffer = nodeBuffer => const toArrayBuffer = nodeBuffer =>
nodeBuffer.buffer.slice( nodeBuffer.buffer.slice(nodeBuffer.byteOffset, nodeBuffer.byteOffset + nodeBuffer.byteLength);
nodeBuffer.byteOffset,
nodeBuffer.byteOffset + nodeBuffer.byteLength
);
function readFileAsArrayBuffer(targetPath) { function readFileAsArrayBuffer(targetPath) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
@ -381,9 +359,7 @@ function _getExportAttachmentFileName(message, index, attachment) {
if (attachment.contentType) { if (attachment.contentType) {
const components = attachment.contentType.split('/'); const components = attachment.contentType.split('/');
name += `.${ name += `.${components.length > 1 ? components[1] : attachment.contentType}`;
components.length > 1 ? components[1] : attachment.contentType
}`;
} }
return name; return name;
@ -413,11 +389,7 @@ async function readEncryptedAttachment(dir, attachment, name, options) {
const isEncrypted = !_.isUndefined(key); const isEncrypted = !_.isUndefined(key);
if (isEncrypted) { if (isEncrypted) {
attachment.data = await crypto.decryptAttachment( attachment.data = await crypto.decryptAttachment(key, attachment.path, data);
key,
attachment.path,
data
);
} else { } else {
attachment.data = data; attachment.data = data;
} }
@ -429,10 +401,7 @@ async function writeQuoteThumbnail(attachment, options) {
} }
const { dir, message, index, key, newKey } = options; const { dir, message, index, key, newKey } = options;
const filename = `${_getAnonymousAttachmentFileName( const filename = `${_getAnonymousAttachmentFileName(message, index)}-quote-thumbnail`;
message,
index
)}-quote-thumbnail`;
const target = path.join(dir, filename); const target = path.join(dir, filename);
await writeEncryptedAttachment(target, attachment.thumbnail.path, { await writeEncryptedAttachment(target, attachment.thumbnail.path, {
@ -485,10 +454,7 @@ async function writeAttachment(attachment, options) {
}); });
if (attachment.thumbnail && _.isString(attachment.thumbnail.path)) { if (attachment.thumbnail && _.isString(attachment.thumbnail.path)) {
const thumbnailName = `${_getAnonymousAttachmentFileName( const thumbnailName = `${_getAnonymousAttachmentFileName(message, index)}-thumbnail`;
message,
index
)}-thumbnail`;
const thumbnailTarget = path.join(dir, thumbnailName); const thumbnailTarget = path.join(dir, thumbnailName);
await writeEncryptedAttachment(thumbnailTarget, attachment.thumbnail.path, { await writeEncryptedAttachment(thumbnailTarget, attachment.thumbnail.path, {
key, key,
@ -499,21 +465,14 @@ async function writeAttachment(attachment, options) {
} }
if (attachment.screenshot && _.isString(attachment.screenshot.path)) { if (attachment.screenshot && _.isString(attachment.screenshot.path)) {
const screenshotName = `${_getAnonymousAttachmentFileName( const screenshotName = `${_getAnonymousAttachmentFileName(message, index)}-screenshot`;
message,
index
)}-screenshot`;
const screenshotTarget = path.join(dir, screenshotName); const screenshotTarget = path.join(dir, screenshotName);
await writeEncryptedAttachment( await writeEncryptedAttachment(screenshotTarget, attachment.screenshot.path, {
screenshotTarget, key,
attachment.screenshot.path, newKey,
{ filename: screenshotName,
key, dir,
newKey, });
filename: screenshotName,
dir,
}
);
} }
} }
@ -686,13 +645,10 @@ async function exportConversation(conversation, options = {}) {
while (!complete) { while (!complete) {
// eslint-disable-next-line no-await-in-loop // eslint-disable-next-line no-await-in-loop
const collection = await window.Signal.Data.getMessagesByConversation( const collection = await window.Signal.Data.getMessagesByConversation(conversation.id, {
conversation.id, limit: CHUNK_SIZE,
{ receivedAt: lastReceivedAt,
limit: CHUNK_SIZE, });
receivedAt: lastReceivedAt,
}
);
const messages = getPlainJS(collection); const messages = getPlainJS(collection);
for (let i = 0, max = messages.length; i < max; i += 1) { for (let i = 0, max = messages.length; i < max; i += 1) {
@ -712,9 +668,7 @@ async function exportConversation(conversation, options = {}) {
const { attachments } = message; const { attachments } = message;
// eliminate attachment data from the JSON, since it will go to disk // 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 // Note: this is for legacy messages only, which stored attachment data in the db
message.attachments = _.map(attachments, attachment => message.attachments = _.map(attachments, attachment => _.omit(attachment, ['data']));
_.omit(attachment, ['data'])
);
// completely drop any attachments in messages cached in error objects // completely drop any attachments in messages cached in error objects
// TODO: move to lodash. Sadly, a number of the method signatures have changed! // 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 => {
@ -901,22 +855,12 @@ async function loadAttachments(dir, getName, options) {
if (attachment.thumbnail && _.isString(attachment.thumbnail.path)) { if (attachment.thumbnail && _.isString(attachment.thumbnail.path)) {
const thumbnailName = `${name}-thumbnail`; const thumbnailName = `${name}-thumbnail`;
await readEncryptedAttachment( await readEncryptedAttachment(dir, attachment.thumbnail, thumbnailName, options);
dir,
attachment.thumbnail,
thumbnailName,
options
);
} }
if (attachment.screenshot && _.isString(attachment.screenshot.path)) { if (attachment.screenshot && _.isString(attachment.screenshot.path)) {
const screenshotName = `${name}-screenshot`; const screenshotName = `${name}-screenshot`;
await readEncryptedAttachment( await readEncryptedAttachment(dir, attachment.screenshot, screenshotName, options);
dir,
attachment.screenshot,
screenshotName,
options
);
} }
}) })
); );
@ -989,10 +933,7 @@ async function saveAllMessages(rawMessages) {
`[REDACTED]${conversationId.slice(-3)}` `[REDACTED]${conversationId.slice(-3)}`
); );
} catch (error) { } catch (error) {
window.log.error( window.log.error('saveAllMessages error', error && error.message ? error.message : error);
'saveAllMessages error',
error && error.message ? error.message : error
);
} }
} }
@ -1015,18 +956,14 @@ async function importConversation(dir, options) {
try { try {
contents = await readFileAsText(dir, 'messages.json'); contents = await readFileAsText(dir, 'messages.json');
} catch (error) { } catch (error) {
window.log.error( window.log.error(`Warning: could not access messages.json in directory: ${dir}`);
`Warning: could not access messages.json in directory: ${dir}`
);
} }
let promiseChain = Promise.resolve(); let promiseChain = Promise.resolve();
const json = JSON.parse(contents); const json = JSON.parse(contents);
if (json.messages && json.messages.length) { if (json.messages && json.messages.length) {
conversationId = `[REDACTED]${(json.messages[0].conversationId || '').slice( conversationId = `[REDACTED]${(json.messages[0].conversationId || '').slice(-3)}`;
-3
)}`;
} }
total = json.messages.length; total = json.messages.length;
@ -1040,9 +977,7 @@ async function importConversation(dir, options) {
const hasAttachments = message.attachments && message.attachments.length; const hasAttachments = message.attachments && message.attachments.length;
const hasQuotedAttachments = const hasQuotedAttachments =
message.quote && message.quote && message.quote.attachments && message.quote.attachments.length > 0;
message.quote.attachments &&
message.quote.attachments.length > 0;
const hasContacts = message.contact && message.contact.length; const hasContacts = message.contact && message.contact.length;
const hasPreviews = message.preview && message.preview.length; const hasPreviews = message.preview && message.preview.length;
@ -1051,8 +986,7 @@ async function importConversation(dir, options) {
const getName = attachmentsDir const getName = attachmentsDir
? _getAnonymousAttachmentFileName ? _getAnonymousAttachmentFileName
: _getExportAttachmentFileName; : _getExportAttachmentFileName;
const parentDir = const parentDir = attachmentsDir || path.join(dir, message.received_at.toString());
attachmentsDir || path.join(dir, message.received_at.toString());
await loadAttachments(parentDir, getName, { await loadAttachments(parentDir, getName, {
message, message,
@ -1229,10 +1163,7 @@ async function exportToDirectory(directory, options) {
window.log.info('done backing up!'); window.log.info('done backing up!');
return directory; return directory;
} catch (error) { } catch (error) {
window.log.error( window.log.error('The backup went wrong!', error && error.stack ? error.stack : error);
'The backup went wrong!',
error && error.stack ? error.stack : error
);
throw error; throw error;
} finally { } finally {
if (stagingDir) { if (stagingDir) {
@ -1255,10 +1186,7 @@ async function importFromDirectory(directory, options) {
options = options || {}; options = options || {};
try { try {
const lookups = await Promise.all([ const lookups = await Promise.all([loadMessagesLookup(), loadConversationLookup()]);
loadMessagesLookup(),
loadConversationLookup(),
]);
const [messageLookup, conversationLookup] = lookups; const [messageLookup, conversationLookup] = lookups;
options = Object.assign({}, options, { options = Object.assign({}, options, {
messageLookup, messageLookup,
@ -1274,9 +1202,7 @@ async function importFromDirectory(directory, options) {
// we're in the world of an encrypted, zipped backup // we're in the world of an encrypted, zipped backup
if (!options.key) { if (!options.key) {
throw new Error( throw new Error('Importing an encrypted backup; decryption key is required!');
'Importing an encrypted backup; decryption key is required!'
);
} }
let stagingDir; let stagingDir;
@ -1315,10 +1241,7 @@ async function importFromDirectory(directory, options) {
window.log.info('Done importing!'); window.log.info('Done importing!');
return result; return result;
} catch (error) { } catch (error) {
window.log.error( window.log.error('The import went wrong!', error && error.stack ? error.stack : error);
'The import went wrong!',
error && error.stack ? error.stack : error
);
throw error; throw error;
} }
} }

@ -129,11 +129,7 @@ async function encryptSymmetric(key, plaintext) {
const cipherKey = await hmacSha256(key, nonce); const cipherKey = await hmacSha256(key, nonce);
const macKey = await hmacSha256(key, cipherKey); const macKey = await hmacSha256(key, cipherKey);
const cipherText = await _encrypt_aes256_CBC_PKCSPadding( const cipherText = await _encrypt_aes256_CBC_PKCSPadding(cipherKey, iv, plaintext);
cipherKey,
iv,
plaintext
);
const mac = _getFirstBytes(await hmacSha256(macKey, cipherText), MAC_LENGTH); const mac = _getFirstBytes(await hmacSha256(macKey, cipherText), MAC_LENGTH);
return concatenateBytes(nonce, cipherText, mac); return concatenateBytes(nonce, cipherText, mac);
@ -143,24 +139,15 @@ async function decryptSymmetric(key, data) {
const iv = getZeroes(IV_LENGTH); const iv = getZeroes(IV_LENGTH);
const nonce = _getFirstBytes(data, NONCE_LENGTH); const nonce = _getFirstBytes(data, NONCE_LENGTH);
const cipherText = _getBytes( const cipherText = _getBytes(data, NONCE_LENGTH, data.byteLength - NONCE_LENGTH - MAC_LENGTH);
data,
NONCE_LENGTH,
data.byteLength - NONCE_LENGTH - MAC_LENGTH
);
const theirMac = _getBytes(data, data.byteLength - MAC_LENGTH, MAC_LENGTH); const theirMac = _getBytes(data, data.byteLength - MAC_LENGTH, MAC_LENGTH);
const cipherKey = await hmacSha256(key, nonce); const cipherKey = await hmacSha256(key, nonce);
const macKey = await hmacSha256(key, cipherKey); const macKey = await hmacSha256(key, cipherKey);
const ourMac = _getFirstBytes( const ourMac = _getFirstBytes(await hmacSha256(macKey, cipherText), MAC_LENGTH);
await hmacSha256(macKey, cipherText),
MAC_LENGTH
);
if (!constantTimeEqual(theirMac, ourMac)) { if (!constantTimeEqual(theirMac, ourMac)) {
throw new Error( throw new Error('decryptSymmetric: Failed to decrypt; MAC verification failed');
'decryptSymmetric: Failed to decrypt; MAC verification failed'
);
} }
return _decrypt_aes256_CBC_PKCSPadding(cipherKey, iv, cipherText); return _decrypt_aes256_CBC_PKCSPadding(cipherKey, iv, cipherText);
@ -189,13 +176,9 @@ async function hmacSha256(key, plaintext) {
}; };
const extractable = false; const extractable = false;
const cryptoKey = await window.crypto.subtle.importKey( const cryptoKey = await window.crypto.subtle.importKey('raw', key, algorithm, extractable, [
'raw', 'sign',
key, ]);
algorithm,
extractable,
['sign']
);
return window.crypto.subtle.sign(algorithm, cryptoKey, plaintext); return window.crypto.subtle.sign(algorithm, cryptoKey, plaintext);
} }
@ -207,13 +190,9 @@ async function _encrypt_aes256_CBC_PKCSPadding(key, iv, plaintext) {
}; };
const extractable = false; const extractable = false;
const cryptoKey = await window.crypto.subtle.importKey( const cryptoKey = await window.crypto.subtle.importKey('raw', key, algorithm, extractable, [
'raw', 'encrypt',
key, ]);
algorithm,
extractable,
['encrypt']
);
return window.crypto.subtle.encrypt(algorithm, cryptoKey, plaintext); return window.crypto.subtle.encrypt(algorithm, cryptoKey, plaintext);
} }
@ -225,13 +204,9 @@ async function _decrypt_aes256_CBC_PKCSPadding(key, iv, plaintext) {
}; };
const extractable = false; const extractable = false;
const cryptoKey = await window.crypto.subtle.importKey( const cryptoKey = await window.crypto.subtle.importKey('raw', key, algorithm, extractable, [
'raw', 'decrypt',
key, ]);
algorithm,
extractable,
['decrypt']
);
return window.crypto.subtle.decrypt(algorithm, cryptoKey, plaintext); return window.crypto.subtle.decrypt(algorithm, cryptoKey, plaintext);
} }
@ -243,19 +218,9 @@ async function encryptAesCtr(key, plaintext, counter) {
length: 128, length: 128,
}; };
const cryptoKey = await crypto.subtle.importKey( const cryptoKey = await crypto.subtle.importKey('raw', key, algorithm, extractable, ['encrypt']);
'raw',
key,
algorithm,
extractable,
['encrypt']
);
const ciphertext = await crypto.subtle.encrypt( const ciphertext = await crypto.subtle.encrypt(algorithm, cryptoKey, plaintext);
algorithm,
cryptoKey,
plaintext
);
return ciphertext; return ciphertext;
} }
@ -268,18 +233,8 @@ async function decryptAesCtr(key, ciphertext, counter) {
length: 128, length: 128,
}; };
const cryptoKey = await crypto.subtle.importKey( const cryptoKey = await crypto.subtle.importKey('raw', key, algorithm, extractable, ['decrypt']);
'raw', const plaintext = await crypto.subtle.decrypt(algorithm, cryptoKey, ciphertext);
key,
algorithm,
extractable,
['decrypt']
);
const plaintext = await crypto.subtle.decrypt(
algorithm,
cryptoKey,
ciphertext
);
return plaintext; return plaintext;
} }
@ -290,13 +245,7 @@ async function _encrypt_aes_gcm(key, iv, plaintext) {
}; };
const extractable = false; const extractable = false;
const cryptoKey = await crypto.subtle.importKey( const cryptoKey = await crypto.subtle.importKey('raw', key, algorithm, extractable, ['encrypt']);
'raw',
key,
algorithm,
extractable,
['encrypt']
);
return crypto.subtle.encrypt(algorithm, cryptoKey, plaintext); return crypto.subtle.encrypt(algorithm, cryptoKey, plaintext);
} }
@ -338,10 +287,7 @@ function getViewOfArrayBuffer(buffer, start, finish) {
} }
function concatenateBytes(...elements) { function concatenateBytes(...elements) {
const length = elements.reduce( const length = elements.reduce((total, element) => total + element.byteLength, 0);
(total, element) => total + element.byteLength,
0
);
const result = new Uint8Array(length); const result = new Uint8Array(length);
let position = 0; let position = 0;

@ -26,8 +26,7 @@ exports.open = (name, version, { onUpgradeNeeded } = {}) => {
reject( reject(
new Error( new Error(
'Database upgrade required:' + 'Database upgrade required:' + ` oldVersion: ${oldVersion}, newVersion: ${newVersion}`
` oldVersion: ${oldVersion}, newVersion: ${newVersion}`
) )
); );
}; };

@ -1,3 +1 @@
export function deferredToPromise<T>( export function deferredToPromise<T>(deferred: JQuery.Deferred<any, any, any>): Promise<T>;
deferred: JQuery.Deferred<any, any, any>
): Promise<T>;

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

@ -151,9 +151,6 @@ function isLinkSneaky(href) {
// We can't use `url.pathname` (and so on) because it automatically encodes strings. // We can't use `url.pathname` (and so on) because it automatically encodes strings.
// For example, it turns `/aquí` into `/aqu%C3%AD`. // For example, it turns `/aquí` into `/aqu%C3%AD`.
const startOfPathAndHash = href.indexOf('/', url.protocol.length + 4); const startOfPathAndHash = href.indexOf('/', url.protocol.length + 4);
const pathAndHash = const pathAndHash = startOfPathAndHash === -1 ? '' : href.substr(startOfPathAndHash);
startOfPathAndHash === -1 ? '' : href.substr(startOfPathAndHash); return [...pathAndHash].some(character => !VALID_URI_CHARACTERS.has(character));
return [...pathAndHash].some(
character => !VALID_URI_CHARACTERS.has(character)
);
} }

@ -1,8 +1,4 @@
import { import { Quote, AttachmentPointer, Preview } from '../../ts/session/messages/outgoing';
Quote,
AttachmentPointer,
Preview,
} from '../../ts/session/messages/outgoing';
interface UploadResponse { interface UploadResponse {
url: string; url: string;

@ -13,12 +13,9 @@ const PUBLICCHAT_CHAN_POLL_EVERY = 20 * 1000; // 20s
const PUBLICCHAT_DELETION_POLL_EVERY = 5 * 1000; // 5s const PUBLICCHAT_DELETION_POLL_EVERY = 5 * 1000; // 5s
const PUBLICCHAT_MOD_POLL_EVERY = 30 * 1000; // 30s const PUBLICCHAT_MOD_POLL_EVERY = 30 * 1000; // 30s
const LOKIFOUNDATION_DEVFILESERVER_PUBKEY = const LOKIFOUNDATION_DEVFILESERVER_PUBKEY = 'BSZiMVxOco/b3sYfaeyiMWv/JnqokxGXkHoclEx8TmZ6';
'BSZiMVxOco/b3sYfaeyiMWv/JnqokxGXkHoclEx8TmZ6'; const LOKIFOUNDATION_FILESERVER_PUBKEY = 'BWJQnVm97sQE3Q1InB4Vuo+U/T1hmwHBv0ipkiv8tzEc';
const LOKIFOUNDATION_FILESERVER_PUBKEY = const LOKIFOUNDATION_APNS_PUBKEY = 'BWQqZYWRl0LlotTcUSRJZPvNi8qyt1YSQH3li4EHQNBJ';
'BWJQnVm97sQE3Q1InB4Vuo+U/T1hmwHBv0ipkiv8tzEc';
const LOKIFOUNDATION_APNS_PUBKEY =
'BWQqZYWRl0LlotTcUSRJZPvNi8qyt1YSQH3li4EHQNBJ';
const urlPubkeyMap = { const urlPubkeyMap = {
'https://file-dev.getsession.org': LOKIFOUNDATION_DEVFILESERVER_PUBKEY, 'https://file-dev.getsession.org': LOKIFOUNDATION_DEVFILESERVER_PUBKEY,
@ -64,27 +61,15 @@ class LokiAppDotNetServerAPI {
// channel getter/factory // channel getter/factory
async findOrCreateChannel(chatAPI, channelId, conversationId) { async findOrCreateChannel(chatAPI, channelId, conversationId) {
let thisChannel = this.channels.find( let thisChannel = this.channels.find(channel => channel.channelId === channelId);
channel => channel.channelId === channelId
);
if (!thisChannel) { if (!thisChannel) {
// make sure we're subscribed // make sure we're subscribed
// eventually we'll need to move to account registration/add server // eventually we'll need to move to account registration/add server
await this.serverRequest(`channels/${channelId}/subscribe`, { await this.serverRequest(`channels/${channelId}/subscribe`, {
method: 'POST', method: 'POST',
}); });
thisChannel = new LokiPublicChannelAPI( thisChannel = new LokiPublicChannelAPI(chatAPI, this, channelId, conversationId);
chatAPI, log.info('LokiPublicChannelAPI started for', channelId, 'on', this.baseServerUrl);
this,
channelId,
conversationId
);
log.info(
'LokiPublicChannelAPI started for',
channelId,
'on',
this.baseServerUrl
);
this.channels.push(thisChannel); this.channels.push(thisChannel);
} }
return thisChannel; return thisChannel;
@ -127,9 +112,7 @@ class LokiAppDotNetServerAPI {
// Hard coded // Hard coded
let pubKeyAB; let pubKeyAB;
if (urlPubkeyMap && urlPubkeyMap[this.baseServerUrl]) { if (urlPubkeyMap && urlPubkeyMap[this.baseServerUrl]) {
pubKeyAB = window.Signal.Crypto.base64ToArrayBuffer( pubKeyAB = window.Signal.Crypto.base64ToArrayBuffer(urlPubkeyMap[this.baseServerUrl]);
urlPubkeyMap[this.baseServerUrl]
);
} }
// do we have their pubkey locally? // do we have their pubkey locally?
@ -142,8 +125,7 @@ class LokiAppDotNetServerAPI {
window.lokiPublicChatAPI.openGroupPubKeys && window.lokiPublicChatAPI.openGroupPubKeys &&
window.lokiPublicChatAPI.openGroupPubKeys[this.baseServerUrl] window.lokiPublicChatAPI.openGroupPubKeys[this.baseServerUrl]
) { ) {
pubKeyAB = pubKeyAB = window.lokiPublicChatAPI.openGroupPubKeys[this.baseServerUrl];
window.lokiPublicChatAPI.openGroupPubKeys[this.baseServerUrl];
} }
} }
// else will fail validation later // else will fail validation later
@ -189,10 +171,7 @@ class LokiAppDotNetServerAPI {
// no big deal if it fails... // no big deal if it fails...
if (res.err || !res.response || !res.response.data) { if (res.err || !res.response || !res.response.data) {
if (res.err) { if (res.err) {
log.error( log.error(`setProfileName Error ${res.err} ${res.statusCode}`, this.baseServerUrl);
`setProfileName Error ${res.err} ${res.statusCode}`,
this.baseServerUrl
);
} }
return []; return [];
} }
@ -243,9 +222,7 @@ class LokiAppDotNetServerAPI {
if (this.token) { if (this.token) {
return this.token; return this.token;
} }
token = await Signal.Data.getPublicServerTokenByServerUrl( token = await Signal.Data.getPublicServerTokenByServerUrl(this.baseServerUrl);
this.baseServerUrl
);
} }
if (!token) { if (!token) {
token = await this.refreshServerToken(); token = await this.refreshServerToken();
@ -360,25 +337,12 @@ class LokiAppDotNetServerAPI {
// not really an error, from a client's pov, network servers can fail... // not really an error, from a client's pov, network servers can fail...
if (e.code === 'ECONNREFUSED') { if (e.code === 'ECONNREFUSED') {
// down // down
log.warn( log.warn('requestToken request can not connect', this.baseServerUrl, e.message);
'requestToken request can not connect',
this.baseServerUrl,
e.message
);
} else if (e.code === 'ECONNRESET') { } else if (e.code === 'ECONNRESET') {
// got disconnected // got disconnected
log.warn( log.warn('requestToken request lost connection', this.baseServerUrl, e.message);
'requestToken request lost connection',
this.baseServerUrl,
e.message
);
} else { } else {
log.error( log.error('requestToken request failed', this.baseServerUrl, e.code, e.message);
'requestToken request failed',
this.baseServerUrl,
e.code,
e.message
);
} }
return null; return null;
} }
@ -448,9 +412,7 @@ class LokiAppDotNetServerAPI {
log.warn('No channelId provided to getModerators!'); log.warn('No channelId provided to getModerators!');
return []; return [];
} }
const res = await this.serverRequest( const res = await this.serverRequest(`loki/v1/channels/${channelId}/moderators`);
`loki/v1/channels/${channelId}/moderators`
);
return (!res.err && res.response && res.response.moderators) || []; return (!res.err && res.response && res.response.moderators) || [];
} }
@ -590,11 +552,7 @@ class LokiAppDotNetServerAPI {
if (res.err || !res.response || !res.response.data) { if (res.err || !res.response || !res.response.data) {
if (res.err) { if (res.err) {
log.error( log.error(`loki_app_dot_net:::getUsers - Error: ${res.err} for ${pubKeys.join(',')}`);
`loki_app_dot_net:::getUsers - Error: ${res.err} for ${pubKeys.join(
','
)}`
);
} }
return []; return [];
} }
@ -639,10 +597,7 @@ class LokiAppDotNetServerAPI {
throw new Error(`Failed to upload avatar to ${this.baseServerUrl}`); throw new Error(`Failed to upload avatar to ${this.baseServerUrl}`);
} }
const url = const url = response.data && response.data.avatar_image && response.data.avatar_image.url;
response.data &&
response.data.avatar_image &&
response.data.avatar_image.url;
if (!url) { if (!url) {
throw new Error(`Failed to upload data: Invalid url.`); throw new Error(`Failed to upload data: Invalid url.`);
@ -720,9 +675,7 @@ class LokiAppDotNetServerAPI {
}); });
if (window.lokiFeatureFlags.useFileOnionRequestsV2) { if (window.lokiFeatureFlags.useFileOnionRequestsV2) {
const buffer = dcodeIO.ByteBuffer.fromBase64( const buffer = dcodeIO.ByteBuffer.fromBase64(res.response).toArrayBuffer();
res.response
).toArrayBuffer();
return buffer; return buffer;
} }
return new Uint8Array(res.response.data).buffer; return new Uint8Array(res.response.data).buffer;
@ -737,9 +690,7 @@ class LokiPublicChannelAPI {
this.channelId = channelId; this.channelId = channelId;
this.baseChannelUrl = `channels/${this.channelId}`; this.baseChannelUrl = `channels/${this.channelId}`;
this.conversationId = conversationId; this.conversationId = conversationId;
this.conversation = window this.conversation = window.getConversationController().getOrThrow(conversationId);
.getConversationController()
.getOrThrow(conversationId);
this.lastMessageServerID = null; this.lastMessageServerID = null;
this.modStatus = false; this.modStatus = false;
this.deleteLastId = 1; this.deleteLastId = 1;
@ -755,9 +706,7 @@ class LokiPublicChannelAPI {
// end properties // end properties
log.info( log.info(`registered LokiPublicChannel ${channelId} on ${this.serverAPI.baseServerUrl}`);
`registered LokiPublicChannel ${channelId} on ${this.serverAPI.baseServerUrl}`
);
// start polling // start polling
this.open(); this.open();
} }
@ -775,12 +724,9 @@ class LokiPublicChannelAPI {
} }
async banUser(pubkey) { async banUser(pubkey) {
const res = await this.serverRequest( const res = await this.serverRequest(`loki/v1/moderation/blacklist/@${pubkey}`, {
`loki/v1/moderation/blacklist/@${pubkey}`, method: 'POST',
{ });
method: 'POST',
}
);
if (res.err || !res.response || !res.response.data) { if (res.err || !res.response || !res.response.data) {
if (res.err) { if (res.err) {
@ -793,9 +739,7 @@ class LokiPublicChannelAPI {
} }
open() { open() {
log.info( log.info(`LokiPublicChannel open ${this.channelId} on ${this.serverAPI.baseServerUrl}`);
`LokiPublicChannel open ${this.channelId} on ${this.serverAPI.baseServerUrl}`
);
if (this.running) { if (this.running) {
log.warn( log.warn(
`LokiPublicChannel already open ${this.channelId} on ${this.serverAPI.baseServerUrl}` `LokiPublicChannel already open ${this.channelId} on ${this.serverAPI.baseServerUrl}`
@ -818,9 +762,7 @@ class LokiPublicChannelAPI {
} }
stop() { stop() {
log.info( log.info(`LokiPublicChannel close ${this.channelId} on ${this.serverAPI.baseServerUrl}`);
`LokiPublicChannel close ${this.channelId} on ${this.serverAPI.baseServerUrl}`
);
if (!this.running) { if (!this.running) {
log.warn( log.warn(
`LokiPublicChannel already open ${this.channelId} on ${this.serverAPI.baseServerUrl}` `LokiPublicChannel already open ${this.channelId} on ${this.serverAPI.baseServerUrl}`
@ -862,11 +804,7 @@ class LokiPublicChannelAPI {
try { try {
await this.pollOnceForModerators(); await this.pollOnceForModerators();
} catch (e) { } catch (e) {
log.warn( log.warn('Error while polling for public chat moderators:', e.code, e.message);
'Error while polling for public chat moderators:',
e.code,
e.message
);
} }
if (this.running) { if (this.running) {
this.timers.moderator = setTimeout(() => { this.timers.moderator = setTimeout(() => {
@ -878,9 +816,7 @@ class LokiPublicChannelAPI {
// get moderator status // get moderator status
async pollOnceForModerators() { async pollOnceForModerators() {
// get moderator status // get moderator status
const res = await this.serverRequest( const res = await this.serverRequest(`loki/v1/channels/${this.channelId}/moderators`);
`loki/v1/channels/${this.channelId}/moderators`
);
const ourNumberDevice = window.libsession.Utils.UserUtils.getOurPubKeyStrFromCache(); const ourNumberDevice = window.libsession.Utils.UserUtils.getOurPubKeyStrFromCache();
// Get the list of moderators if no errors occurred // Get the list of moderators if no errors occurred
@ -911,15 +847,12 @@ class LokiPublicChannelAPI {
log.warn(`public chat channel state unknown, skipping set: ${res.err}`); log.warn(`public chat channel state unknown, skipping set: ${res.err}`);
return false; return false;
} }
let notes = let notes = res.response && res.response.data && res.response.data.annotations;
res.response && res.response.data && res.response.data.annotations;
if (!notes) { if (!notes) {
// ok if nothing is set yet // ok if nothing is set yet
notes = []; notes = [];
} }
let settingNotes = notes.filter( let settingNotes = notes.filter(note => note.type === SETTINGS_CHANNEL_ANNOTATION_TYPE);
note => note.type === SETTINGS_CHANNEL_ANNOTATION_TYPE
);
if (!settingNotes) { if (!settingNotes) {
// default name, description, avatar // default name, description, avatar
settingNotes = [ settingNotes = [
@ -936,10 +869,10 @@ class LokiPublicChannelAPI {
// update settings // update settings
settingNotes[0].value = Object.assign(settingNotes[0].value, settings); settingNotes[0].value = Object.assign(settingNotes[0].value, settings);
// commit settings // commit settings
const updateRes = await this.serverRequest( const updateRes = await this.serverRequest(`loki/v1/${this.baseChannelUrl}`, {
`loki/v1/${this.baseChannelUrl}`, method: 'PUT',
{ method: 'PUT', objBody: { annotations: settingNotes } } objBody: { annotations: settingNotes },
); });
if (updateRes.err || !updateRes.response || !updateRes.response.data) { if (updateRes.err || !updateRes.response || !updateRes.response.data) {
if (updateRes.err) { if (updateRes.err) {
log.error(`setChannelSettings Error ${updateRes.err}`); log.error(`setChannelSettings Error ${updateRes.err}`);
@ -967,17 +900,13 @@ class LokiPublicChannelAPI {
{ method: 'DELETE', params: { ids: serverIds } } { method: 'DELETE', params: { ids: serverIds } }
); );
if (!res.err) { if (!res.err) {
const deletedIds = res.response.data const deletedIds = res.response.data.filter(d => d.is_deleted).map(d => d.id);
.filter(d => d.is_deleted)
.map(d => d.id);
if (deletedIds.length > 0) { if (deletedIds.length > 0) {
log.info(`deleted ${serverIds} on ${this.baseChannelUrl}`); log.info(`deleted ${serverIds} on ${this.baseChannelUrl}`);
} }
const failedIds = res.response.data const failedIds = res.response.data.filter(d => !d.is_deleted).map(d => d.id);
.filter(d => !d.is_deleted)
.map(d => d.id);
if (failedIds.length > 0) { if (failedIds.length > 0) {
log.warn(`failed to delete ${failedIds} on ${this.baseChannelUrl}`); log.warn(`failed to delete ${failedIds} on ${this.baseChannelUrl}`);
@ -985,10 +914,7 @@ class LokiPublicChannelAPI {
// Note: if there is no entry for message, we assume it wasn't found // Note: if there is no entry for message, we assume it wasn't found
// on the server, so it is not treated as explicitly failed // on the server, so it is not treated as explicitly failed
const ignoredIds = _.difference( const ignoredIds = _.difference(serverIds, _.union(failedIds, deletedIds));
serverIds,
_.union(failedIds, deletedIds)
);
if (ignoredIds.length > 0) { if (ignoredIds.length > 0) {
log.warn(`No response for ${ignoredIds} on ${this.baseChannelUrl}`); log.warn(`No response for ${ignoredIds} on ${this.baseChannelUrl}`);
@ -997,9 +923,7 @@ class LokiPublicChannelAPI {
return { deletedIds, ignoredIds }; return { deletedIds, ignoredIds };
} }
if (canThrow) { if (canThrow) {
throw new textsecure.PublicChatError( throw new textsecure.PublicChatError('Failed to delete public chat message');
'Failed to delete public chat message'
);
} }
return { deletedIds: [], ignoredIds: [] }; return { deletedIds: [], ignoredIds: [] };
} }
@ -1015,11 +939,7 @@ class LokiPublicChannelAPI {
try { try {
await this.pollForChannelOnce(); await this.pollForChannelOnce();
} catch (e) { } catch (e) {
log.warn( log.warn('Error while polling for public chat room details', e.code, e.message);
'Error while polling for public chat room details',
e.code,
e.message
);
} }
if (this.running) { if (this.running) {
this.timers.channel = setTimeout(() => { this.timers.channel = setTimeout(() => {
@ -1072,16 +992,11 @@ class LokiPublicChannelAPI {
} else { } else {
// relative URL avatar // relative URL avatar
const avatarAbsUrl = this.serverAPI.baseServerUrl + note.value.avatar; const avatarAbsUrl = this.serverAPI.baseServerUrl + note.value.avatar;
const { const { writeNewAttachmentData, deleteAttachmentData } = window.Signal.Migrations;
writeNewAttachmentData,
deleteAttachmentData,
} = window.Signal.Migrations;
// do we already have this image? no, then // do we already have this image? no, then
// download a copy and save it // download a copy and save it
const imageData = await this.serverAPI.downloadAttachment( const imageData = await this.serverAPI.downloadAttachment(avatarAbsUrl);
avatarAbsUrl
);
const newAttributes = await window.Signal.Types.Conversation.maybeUpdateAvatar( const newAttributes = await window.Signal.Types.Conversation.maybeUpdateAvatar(
this.conversation.attributes, this.conversation.attributes,
@ -1111,11 +1026,7 @@ class LokiPublicChannelAPI {
try { try {
await this.pollOnceForDeletions(); await this.pollOnceForDeletions();
} catch (e) { } catch (e) {
log.warn( log.warn('Error while polling for public chat deletions:', e.code, e.message);
'Error while polling for public chat deletions:',
e.code,
e.message
);
} }
if (this.running) { if (this.running) {
this.timers.delete = setTimeout(() => { this.timers.delete = setTimeout(() => {
@ -1138,18 +1049,10 @@ class LokiPublicChannelAPI {
// grab the next 200 deletions from where we last checked // grab the next 200 deletions from where we last checked
// eslint-disable-next-line no-await-in-loop // eslint-disable-next-line no-await-in-loop
const res = await this.serverRequest( const res = await this.serverRequest(`loki/v1/channel/${this.channelId}/deletes`, { params });
`loki/v1/channel/${this.channelId}/deletes`,
{ params }
);
// if any problems, abort out // if any problems, abort out
if ( if (res.err || !res.response || !res.response.data || !res.response.meta) {
res.err ||
!res.response ||
!res.response.data ||
!res.response.meta
) {
if (res.statusCode === 403) { if (res.statusCode === 403) {
// token is now invalid // token is now invalid
this.serverAPI.getOrRefreshServerToken(true); this.serverAPI.getOrRefreshServerToken(true);
@ -1157,9 +1060,7 @@ class LokiPublicChannelAPI {
if (res.err) { if (res.err) {
log.error(`pollOnceForDeletions Error ${res.err}`); log.error(`pollOnceForDeletions Error ${res.err}`);
} else { } else {
log.error( log.error(`pollOnceForDeletions Error: Received incorrect response ${res.response}`);
`pollOnceForDeletions Error: Received incorrect response ${res.response}`
);
} }
break; break;
} }
@ -1175,20 +1076,11 @@ class LokiPublicChannelAPI {
// update where we last checked // update where we last checked
this.deleteLastId = res.response.meta.max_id; this.deleteLastId = res.response.meta.max_id;
more = more = res.response.meta.more && res.response.data.length >= params.count && this.running;
res.response.meta.more &&
res.response.data.length >= params.count &&
this.running;
} }
} }
static getSigData( static getSigData(sigVer, noteValue, attachmentAnnotations, previewAnnotations, adnMessage) {
sigVer,
noteValue,
attachmentAnnotations,
previewAnnotations,
adnMessage
) {
let sigString = ''; let sigString = '';
sigString += adnMessage.text.trim(); sigString += adnMessage.text.trim();
sigString += noteValue.timestamp; sigString += noteValue.timestamp;
@ -1210,10 +1102,7 @@ class LokiPublicChannelAPI {
} }
async getMessengerData(adnMessage) { async getMessengerData(adnMessage) {
if ( if (!Array.isArray(adnMessage.annotations) || adnMessage.annotations.length === 0) {
!Array.isArray(adnMessage.annotations) ||
adnMessage.annotations.length === 0
) {
return false; return false;
} }
const noteValue = adnMessage.annotations[0].value; const noteValue = adnMessage.annotations[0].value;
@ -1293,8 +1182,7 @@ class LokiPublicChannelAPI {
return { return {
timestamp, timestamp,
serverTimestamp: serverTimestamp: new Date(`${adnMessage.created_at}`).getTime() || timestamp,
new Date(`${adnMessage.created_at}`).getTime() || timestamp,
attachments, attachments,
preview, preview,
quote, quote,
@ -1309,11 +1197,7 @@ class LokiPublicChannelAPI {
try { try {
await this.pollOnceForMessages(); await this.pollOnceForMessages();
} catch (e) { } catch (e) {
log.warn( log.warn('Error while polling for public chat messages:', e.code, e.message);
'Error while polling for public chat messages:',
e.code,
e.message
);
} }
if (this.running) { if (this.running) {
this.timers.message = setTimeout(() => { this.timers.message = setTimeout(() => {
@ -1386,8 +1270,7 @@ class LokiPublicChannelAPI {
// get our profile name // get our profile name
const ourNumberDevice = window.libsession.Utils.UserUtils.getOurPubKeyStrFromCache(); const ourNumberDevice = window.libsession.Utils.UserUtils.getOurPubKeyStrFromCache();
// if no primaryDevicePubKey fall back to ourNumberDevice // if no primaryDevicePubKey fall back to ourNumberDevice
const ourNumberProfile = const ourNumberProfile = window.storage.get('primaryDevicePubKey') || ourNumberDevice;
window.storage.get('primaryDevicePubKey') || ourNumberDevice;
let lastProfileName = false; let lastProfileName = false;
// the signature forces this to be async // the signature forces this to be async
@ -1437,11 +1320,7 @@ class LokiPublicChannelAPI {
// message is one of the object of this.lastMessagesCache // message is one of the object of this.lastMessagesCache
// testedMessage is the adnMessage object // testedMessage is the adnMessage object
const isDuplicate = (message, testedMessage) => const isDuplicate = (message, testedMessage) =>
DataMessage.isDuplicate( DataMessage.isDuplicate(message, testedMessage, testedMessage.user.username);
message,
testedMessage,
testedMessage.user.username
);
const isThisMessageDuplicate = this.lastMessagesCache.some(m => const isThisMessageDuplicate = this.lastMessagesCache.some(m =>
isDuplicate(m, adnMessage) isDuplicate(m, adnMessage)
); );
@ -1503,8 +1382,7 @@ class LokiPublicChannelAPI {
receivedAt, receivedAt,
isPublic: true, isPublic: true,
message: { message: {
body: body: adnMessage.text === timestamp.toString() ? '' : adnMessage.text,
adnMessage.text === timestamp.toString() ? '' : adnMessage.text,
attachments, attachments,
group: { group: {
id: this.conversationId, id: this.conversationId,
@ -1571,9 +1449,7 @@ class LokiPublicChannelAPI {
// if we received one of our own messages // if we received one of our own messages
if (lastProfileName !== false) { if (lastProfileName !== false) {
// get current profileName // get current profileName
const profileConvo = window const profileConvo = window.getConversationController().get(ourNumberProfile);
.getConversationController()
.get(ourNumberProfile);
const profileName = profileConvo.getProfileName(); const profileName = profileConvo.getProfileName();
// check to see if it out of sync // check to see if it out of sync
if (profileName !== lastProfileName) { if (profileName !== lastProfileName) {
@ -1668,12 +1544,8 @@ class LokiPublicChannelAPI {
async sendMessage(data, messageTimeStamp) { async sendMessage(data, messageTimeStamp) {
const { quote, attachments, preview } = data; const { quote, attachments, preview } = data;
const text = data.body || messageTimeStamp.toString(); const text = data.body || messageTimeStamp.toString();
const attachmentAnnotations = attachments.map( const attachmentAnnotations = attachments.map(LokiPublicChannelAPI.getAnnotationFromAttachment);
LokiPublicChannelAPI.getAnnotationFromAttachment const previewAnnotations = preview.map(LokiPublicChannelAPI.getAnnotationFromPreview);
);
const previewAnnotations = preview.map(
LokiPublicChannelAPI.getAnnotationFromPreview
);
const payload = { const payload = {
text, text,
@ -1721,10 +1593,7 @@ class LokiPublicChannelAPI {
previewAnnotations.map(anno => anno.value), previewAnnotations.map(anno => anno.value),
mockAdnMessage mockAdnMessage
); );
const sig = await libsignal.Curve.async.calculateSignature( const sig = await libsignal.Curve.async.calculateSignature(privKey, sigData);
privKey,
sigData
);
payload.annotations[0].value.sig = StringView.arrayBufferToHex(sig); payload.annotations[0].value.sig = StringView.arrayBufferToHex(sig);
payload.annotations[0].value.sigver = sigVer; payload.annotations[0].value.sigver = sigVer;
const res = await this.serverRequest(`${this.baseChannelUrl}/messages`, { const res = await this.serverRequest(`${this.baseChannelUrl}/messages`, {

@ -63,9 +63,7 @@ class LokiFileServerFactoryAPI {
} }
establishHomeConnection(serverUrl) { establishHomeConnection(serverUrl) {
let thisServer = this.servers.find( let thisServer = this.servers.find(server => server._server.baseServerUrl === serverUrl);
server => server._server.baseServerUrl === serverUrl
);
if (!thisServer) { if (!thisServer) {
thisServer = new LokiHomeServerInstance(this.ourKey); thisServer = new LokiHomeServerInstance(this.ourKey);
log.info(`Registering HomeServer ${serverUrl}`); log.info(`Registering HomeServer ${serverUrl}`);
@ -77,9 +75,7 @@ class LokiFileServerFactoryAPI {
} }
async establishConnection(serverUrl) { async establishConnection(serverUrl) {
let thisServer = this.servers.find( let thisServer = this.servers.find(server => server._server.baseServerUrl === serverUrl);
server => server._server.baseServerUrl === serverUrl
);
if (!thisServer) { if (!thisServer) {
thisServer = new LokiFileServerInstance(this.ourKey); thisServer = new LokiFileServerInstance(this.ourKey);
log.info(`Registering FileServer ${serverUrl}`); log.info(`Registering FileServer ${serverUrl}`);

@ -45,9 +45,7 @@ class LokiMessageAPI {
}; };
if (isPublic) { if (isPublic) {
window.log.warn( window.log.warn('this sendMessage() should not be called anymore with an open group message');
'this sendMessage() should not be called anymore with an open group message'
);
return; return;
} }
@ -98,10 +96,7 @@ class LokiMessageAPI {
throw e; throw e;
} }
if (!snode) { if (!snode) {
throw new window.textsecure.EmptySwarmError( throw new window.textsecure.EmptySwarmError(pubKey, 'Ran out of swarm nodes to query');
pubKey,
'Ran out of swarm nodes to query'
);
} else { } else {
log.info( log.info(
`loki_message:::sendMessage - Successfully stored message to ${pubKey} via ${snode.ip}:${snode.port}` `loki_message:::sendMessage - Successfully stored message to ${pubKey} via ${snode.ip}:${snode.port}`

@ -1,6 +1,3 @@
export async function sleepFor(ms: number); export async function sleepFor(ms: number);
export async function abortableIterator( export async function abortableIterator(array: Array<any>, action: (any) => void);
array: Array<any>,
action: (any) => void
);

@ -1,7 +1,4 @@
import { import { LokiAppDotNetServerInterface, LokiPublicChannelAPI } from './loki_app_dot_net_api';
LokiAppDotNetServerInterface,
LokiPublicChannelAPI,
} from './loki_app_dot_net_api';
export interface LokiPublicChatFactoryInterface { export interface LokiPublicChatFactoryInterface {
ourKey: string; ourKey: string;
@ -12,16 +9,11 @@ export interface LokiPublicChatFactoryInterface {
channelId: number, channelId: number,
conversationId: string conversationId: string
): Promise<LokiPublicChannelAPI | null>; ): Promise<LokiPublicChannelAPI | null>;
getListOfMembers(): Promise< getListOfMembers(): Promise<Array<{ authorPhoneNumber: string; authorProfileName?: string }>>;
Array<{ authorPhoneNumber: string; authorProfileName?: string }> setListOfMembers(members: Array<{ authorPhoneNumber: string; authorProfileName?: string }>);
>;
setListOfMembers(
members: Array<{ authorPhoneNumber: string; authorProfileName?: string }>
);
} }
declare class LokiPublicChatFactoryAPI declare class LokiPublicChatFactoryAPI implements LokiPublicChatFactoryInterface {
implements LokiPublicChatFactoryInterface {
constructor(ourKey: string); constructor(ourKey: string);
} }

@ -26,20 +26,14 @@ class LokiPublicChatFactoryAPI extends EventEmitter {
// server getter/factory // server getter/factory
async findOrCreateServer(serverUrl) { async findOrCreateServer(serverUrl) {
let thisServer = this.servers.find( let thisServer = this.servers.find(server => server.baseServerUrl === serverUrl);
server => server.baseServerUrl === serverUrl
);
if (!thisServer) { if (!thisServer) {
log.info(`loki_public_chat::findOrCreateServer - creating ${serverUrl}`); log.info(`loki_public_chat::findOrCreateServer - creating ${serverUrl}`);
const serverIsValid = await OpenGroupUtils.validOpenGroupServer( const serverIsValid = await OpenGroupUtils.validOpenGroupServer(serverUrl);
serverUrl
);
if (!serverIsValid) { if (!serverIsValid) {
// FIXME: add toast? // FIXME: add toast?
log.error( log.error(`loki_public_chat::findOrCreateServer - error: ${serverUrl} is not valid`);
`loki_public_chat::findOrCreateServer - error: ${serverUrl} is not valid`
);
return null; return null;
} }
@ -53,18 +47,14 @@ class LokiPublicChatFactoryAPI extends EventEmitter {
if (this.openGroupPubKeys[serverUrl]) { if (this.openGroupPubKeys[serverUrl]) {
thisServer.getPubKeyForUrl(); thisServer.getPubKeyForUrl();
if (!thisServer.pubKeyHex) { if (!thisServer.pubKeyHex) {
log.warn( log.warn(`loki_public_chat::findOrCreateServer - failed to set public key`);
`loki_public_chat::findOrCreateServer - failed to set public key`
);
} }
} }
} }
const gotToken = await thisServer.getOrRefreshServerToken(); const gotToken = await thisServer.getOrRefreshServerToken();
if (!gotToken) { if (!gotToken) {
log.warn( log.warn(`loki_public_chat::findOrCreateServer - Invalid server ${serverUrl}`);
`loki_public_chat::findOrCreateServer - Invalid server ${serverUrl}`
);
return null; return null;
} }
@ -85,9 +75,7 @@ class LokiPublicChatFactoryAPI extends EventEmitter {
// deallocate resources server uses // deallocate resources server uses
unregisterChannel(serverUrl, channelId) { unregisterChannel(serverUrl, channelId) {
const i = this.servers.findIndex( const i = this.servers.findIndex(server => server.baseServerUrl === serverUrl);
server => server.baseServerUrl === serverUrl
);
if (i === -1) { if (i === -1) {
log.warn(`Tried to unregister from nonexistent server ${serverUrl}`); log.warn(`Tried to unregister from nonexistent server ${serverUrl}`);
return; return;

@ -4,8 +4,7 @@ const LokiAppDotNetAPI = require('./loki_app_dot_net_api');
class LokiPushNotificationServerApi { class LokiPushNotificationServerApi {
constructor() { constructor() {
this.ourKey = this.ourKey = '642a6585919742e5a2d4dc51244964fbcd8bcab2b75612407de58b810740d049';
'642a6585919742e5a2d4dc51244964fbcd8bcab2b75612407de58b810740d049';
this.serverUrl = 'https://live.apns.getsession.org'; this.serverUrl = 'https://live.apns.getsession.org';
this._server = new LokiAppDotNetAPI(this.ourKey, this.serverUrl); this._server = new LokiAppDotNetAPI(this.ourKey, this.serverUrl);

@ -32,9 +32,7 @@ class LokiSnodeAPI {
// Timeouts // Timeouts
const maxTimeoutVal = 2 ** 31 - 1; const maxTimeoutVal = 2 ** 31 - 1;
const timeoutPromise = () => const timeoutPromise = () =>
new Promise((_resolve, reject) => new Promise((_resolve, reject) => setTimeout(() => reject(), timeout || maxTimeoutVal));
setTimeout(() => reject(), timeout || maxTimeoutVal)
);
// Get nodes capable of doing LNS // Get nodes capable of doing LNS
const lnsNodes = await window.SnodePool.getNodesMinVersion( const lnsNodes = await window.SnodePool.getNodesMinVersion(
@ -72,9 +70,7 @@ class LokiSnodeAPI {
if (res && res.result && res.result.status === 'OK') { if (res && res.result && res.result.status === 'OK') {
const hasMapping = res.result.entries && res.result.entries.length > 0; const hasMapping = res.result.entries && res.result.entries.length > 0;
const resValue = hasMapping const resValue = hasMapping ? res.result.entries[0].encrypted_value : null;
? res.result.entries[0].encrypted_value
: null;
confirmedNodes.push(resValue); confirmedNodes.push(resValue);
@ -84,10 +80,7 @@ class LokiSnodeAPI {
return; return;
} }
const [winner, count] = _.maxBy( const [winner, count] = _.maxBy(_.entries(_.countBy(confirmedNodes)), x => x[1]);
_.entries(_.countBy(confirmedNodes)),
x => x[1]
);
if (count >= numRequiredConfirms) { if (count >= numRequiredConfirms) {
ciphertextHex = winner === String(null) ? null : winner; ciphertextHex = winner === String(null) ? null : winner;

@ -26,8 +26,7 @@ exports.getMessageExportLastIndex = connection =>
exports._getItem(connection, MESSAGE_LAST_INDEX_KEY); exports._getItem(connection, MESSAGE_LAST_INDEX_KEY);
exports.setMessageExportLastIndex = (connection, lastIndex) => exports.setMessageExportLastIndex = (connection, lastIndex) =>
exports._setItem(connection, MESSAGE_LAST_INDEX_KEY, lastIndex); exports._setItem(connection, MESSAGE_LAST_INDEX_KEY, lastIndex);
exports.getMessageExportCount = connection => exports.getMessageExportCount = connection => exports._getItem(connection, MESSAGE_COUNT_KEY);
exports._getItem(connection, MESSAGE_COUNT_KEY);
exports.setMessageExportCount = (connection, count) => exports.setMessageExportCount = (connection, count) =>
exports._setItem(connection, MESSAGE_COUNT_KEY, count); exports._setItem(connection, MESSAGE_COUNT_KEY, count);
@ -52,8 +51,7 @@ exports._getItem = (connection, key) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
request.onerror = event => reject(event.target.error); request.onerror = event => reject(event.target.error);
request.onsuccess = event => request.onsuccess = event => resolve(event.target.result ? event.target.result.value : null);
resolve(event.target.result ? event.target.result.value : null);
}); });
}; };

@ -15,42 +15,24 @@ const { Message } = require('../../ts/components/conversation/Message');
// Components // Components
const { EditProfileDialog } = require('../../ts/components/EditProfileDialog'); const { EditProfileDialog } = require('../../ts/components/EditProfileDialog');
const { UserDetailsDialog } = require('../../ts/components/UserDetailsDialog'); const { UserDetailsDialog } = require('../../ts/components/UserDetailsDialog');
const { const { SessionSeedModal } = require('../../ts/components/session/SessionSeedModal');
SessionSeedModal, const { SessionIDResetDialog } = require('../../ts/components/session/SessionIDResetDialog');
} = require('../../ts/components/session/SessionSeedModal'); const { SessionRegistrationView } = require('../../ts/components/session/SessionRegistrationView');
const {
SessionIDResetDialog,
} = require('../../ts/components/session/SessionIDResetDialog');
const {
SessionRegistrationView,
} = require('../../ts/components/session/SessionRegistrationView');
const { const { SessionInboxView } = require('../../ts/components/session/SessionInboxView');
SessionInboxView, const { SessionPasswordModal } = require('../../ts/components/session/SessionPasswordModal');
} = require('../../ts/components/session/SessionInboxView'); const { SessionConfirm } = require('../../ts/components/session/SessionConfirm');
const {
SessionPasswordModal,
} = require('../../ts/components/session/SessionPasswordModal');
const {
SessionConfirm,
} = require('../../ts/components/session/SessionConfirm');
const { const { UpdateGroupNameDialog } = require('../../ts/components/conversation/UpdateGroupNameDialog');
UpdateGroupNameDialog,
} = require('../../ts/components/conversation/UpdateGroupNameDialog');
const { const {
UpdateGroupMembersDialog, UpdateGroupMembersDialog,
} = require('../../ts/components/conversation/UpdateGroupMembersDialog'); } = require('../../ts/components/conversation/UpdateGroupMembersDialog');
const { const { InviteContactsDialog } = require('../../ts/components/conversation/InviteContactsDialog');
InviteContactsDialog,
} = require('../../ts/components/conversation/InviteContactsDialog');
const { const {
AdminLeaveClosedGroupDialog, AdminLeaveClosedGroupDialog,
} = require('../../ts/components/conversation/AdminLeaveClosedGroupDialog'); } = require('../../ts/components/conversation/AdminLeaveClosedGroupDialog');
const { const { AddModeratorsDialog } = require('../../ts/components/conversation/ModeratorsAddDialog');
AddModeratorsDialog,
} = require('../../ts/components/conversation/ModeratorsAddDialog');
const { const {
RemoveModeratorsDialog, RemoveModeratorsDialog,
} = require('../../ts/components/conversation/ModeratorsRemoveDialog'); } = require('../../ts/components/conversation/ModeratorsRemoveDialog');
@ -68,13 +50,7 @@ const SettingsType = require('../../ts/types/Settings');
// Views // Views
const Initialization = require('./views/initialization'); const Initialization = require('./views/initialization');
function initializeMigrations({ function initializeMigrations({ userDataPath, Attachments, Type, VisualType, logger }) {
userDataPath,
Attachments,
Type,
VisualType,
logger,
}) {
if (!Attachments) { if (!Attachments) {
return null; return null;
} }
@ -146,8 +122,7 @@ function initializeMigrations({
logger, logger,
}), }),
writeNewAttachmentData: createWriterForNew(attachmentsPath), writeNewAttachmentData: createWriterForNew(attachmentsPath),
writeAttachment: ({ data, path }) => writeAttachment: ({ data, path }) => createWriterForExisting(attachmentsPath)({ data, path }),
createWriterForExisting(attachmentsPath)({ data, path }),
}; };
} }

@ -4,15 +4,9 @@ const AttachmentTS = require('../../../ts/types/Attachment');
const GoogleChrome = require('../../../ts/util/GoogleChrome'); const GoogleChrome = require('../../../ts/util/GoogleChrome');
const MIME = require('../../../ts/types/MIME'); const MIME = require('../../../ts/types/MIME');
const { toLogFormat } = require('./errors'); const { toLogFormat } = require('./errors');
const { const { arrayBufferToBlob, blobToArrayBuffer, dataURLToBlob } = require('blob-util');
arrayBufferToBlob,
blobToArrayBuffer,
dataURLToBlob,
} = require('blob-util');
const { autoOrientImage } = require('../auto_orient_image'); const { autoOrientImage } = require('../auto_orient_image');
const { const { migrateDataToFileSystem } = require('./attachment/migrate_data_to_file_system');
migrateDataToFileSystem,
} = require('./attachment/migrate_data_to_file_system');
// // Incoming message attachment fields // // Incoming message attachment fields
// { // {
@ -61,10 +55,7 @@ exports.autoOrientJPEG = async attachment => {
return attachment; return attachment;
} }
const dataBlob = await arrayBufferToBlob( const dataBlob = await arrayBufferToBlob(attachment.data, attachment.contentType);
attachment.data,
attachment.contentType
);
const newDataBlob = await dataURLToBlob(await autoOrientImage(dataBlob)); const newDataBlob = await dataURLToBlob(await autoOrientImage(dataBlob));
const newDataArrayBuffer = await blobToArrayBuffer(newDataBlob); const newDataArrayBuffer = await blobToArrayBuffer(newDataBlob);
@ -125,10 +116,7 @@ exports.replaceUnicodeV2 = async attachment => {
return attachment; return attachment;
} }
const fileName = attachment.fileName.replace( const fileName = attachment.fileName.replace(V2_UNWANTED_UNICODE, UNICODE_REPLACEMENT_CHARACTER);
V2_UNWANTED_UNICODE,
UNICODE_REPLACEMENT_CHARACTER
);
return { return {
...attachment, ...attachment,
@ -138,10 +126,7 @@ exports.replaceUnicodeV2 = async attachment => {
exports.removeSchemaVersion = ({ attachment, logger }) => { exports.removeSchemaVersion = ({ attachment, logger }) => {
if (!exports.isValid(attachment)) { if (!exports.isValid(attachment)) {
logger.error( logger.error('Attachment.removeSchemaVersion: Invalid input attachment:', attachment);
'Attachment.removeSchemaVersion: Invalid input attachment:',
attachment
);
return attachment; return attachment;
} }
@ -294,10 +279,7 @@ exports.captureDimensionsAndScreenshot = async (
logger, logger,
}) })
); );
screenshotObjectUrl = makeObjectUrl( screenshotObjectUrl = makeObjectUrl(screenshotBuffer, THUMBNAIL_CONTENT_TYPE);
screenshotBuffer,
THUMBNAIL_CONTENT_TYPE
);
const { width, height } = await getImageDimensions({ const { width, height } = await getImageDimensions({
objectUrl: screenshotObjectUrl, objectUrl: screenshotObjectUrl,
logger, logger,

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

@ -5,10 +5,7 @@ const { SignalService } = require('../../../ts/protobuf');
const DEFAULT_PHONE_TYPE = SignalService.DataMessage.Contact.Phone.Type.HOME; const DEFAULT_PHONE_TYPE = SignalService.DataMessage.Contact.Phone.Type.HOME;
exports.parseAndWriteAvatar = upgradeAttachment => async ( exports.parseAndWriteAvatar = upgradeAttachment => async (contact, context = {}) => {
contact,
context = {}
) => {
const { message, logger } = context; const { message, logger } = context;
const { avatar } = contact; const { avatar } = contact;
@ -31,10 +28,7 @@ exports.parseAndWriteAvatar = upgradeAttachment => async (
messageId: idForLogging(message), messageId: idForLogging(message),
}); });
if (error) { if (error) {
logger.error( logger.error('Contact.parseAndWriteAvatar: contact was malformed.', toLogFormat(error));
'Contact.parseAndWriteAvatar: contact was malformed.',
toLogFormat(error)
);
} }
return parsedContact; return parsedContact;
@ -60,9 +54,7 @@ exports._validate = (contact, options = {}) => {
const { name, number, organization } = contact; const { name, number, organization } = contact;
if ((!name || !name.displayName) && !organization) { if ((!name || !name.displayName) && !organization) {
return new Error( return new Error(`Message ${messageId}: Contact had neither 'displayName' nor 'organization'`);
`Message ${messageId}: Contact had neither 'displayName' nor 'organization'`
);
} }
if (!number || !number.length) { if (!number || !number.length) {

@ -18,14 +18,10 @@ function buildAvatarUpdater({ field }) {
const avatar = conversation[field]; const avatar = conversation[field];
const { writeNewAttachmentData, deleteAttachmentData } = options; const { writeNewAttachmentData, deleteAttachmentData } = options;
if (!isFunction(writeNewAttachmentData)) { if (!isFunction(writeNewAttachmentData)) {
throw new Error( throw new Error('Conversation.buildAvatarUpdater: writeNewAttachmentData must be a function');
'Conversation.buildAvatarUpdater: writeNewAttachmentData must be a function'
);
} }
if (!isFunction(deleteAttachmentData)) { if (!isFunction(deleteAttachmentData)) {
throw new Error( throw new Error('Conversation.buildAvatarUpdater: deleteAttachmentData must be a function');
'Conversation.buildAvatarUpdater: deleteAttachmentData must be a function'
);
} }
const newHash = await computeHash(data); const newHash = await computeHash(data);
@ -70,9 +66,7 @@ async function upgradeToVersion2(conversation, options) {
const { writeNewAttachmentData } = options; const { writeNewAttachmentData } = options;
if (!isFunction(writeNewAttachmentData)) { if (!isFunction(writeNewAttachmentData)) {
throw new Error( throw new Error('Conversation.upgradeToVersion2: writeNewAttachmentData must be a function');
'Conversation.upgradeToVersion2: writeNewAttachmentData must be a function'
);
} }
let { avatar, profileAvatar, profileKey } = conversation; let { avatar, profileAvatar, profileKey } = conversation;
@ -123,9 +117,7 @@ async function deleteExternalFiles(conversation, options = {}) {
const { deleteAttachmentData } = options; const { deleteAttachmentData } = options;
if (!isFunction(deleteAttachmentData)) { if (!isFunction(deleteAttachmentData)) {
throw new Error( throw new Error('Conversation.buildAvatarUpdater: deleteAttachmentData must be a function');
'Conversation.buildAvatarUpdater: deleteAttachmentData must be a function'
);
} }
const { avatar, profileAvatar } = conversation; const { avatar, profileAvatar } = conversation;

@ -60,15 +60,12 @@ exports.isValid = () => true;
// Schema // Schema
exports.initializeSchemaVersion = ({ message, logger }) => { exports.initializeSchemaVersion = ({ message, logger }) => {
const isInitialized = const isInitialized = SchemaVersion.isValid(message.schemaVersion) && message.schemaVersion >= 1;
SchemaVersion.isValid(message.schemaVersion) && message.schemaVersion >= 1;
if (isInitialized) { if (isInitialized) {
return message; return message;
} }
const numAttachments = Array.isArray(message.attachments) const numAttachments = Array.isArray(message.attachments) ? message.attachments.length : 0;
? message.attachments.length
: 0;
const hasAttachments = numAttachments > 0; const hasAttachments = numAttachments > 0;
if (!hasAttachments) { if (!hasAttachments) {
return Object.assign({}, message, { return Object.assign({}, message, {
@ -79,9 +76,7 @@ exports.initializeSchemaVersion = ({ message, logger }) => {
// All attachments should have the same schema version, so we just pick // All attachments should have the same schema version, so we just pick
// the first one: // the first one:
const firstAttachment = message.attachments[0]; const firstAttachment = message.attachments[0];
const inheritedSchemaVersion = SchemaVersion.isValid( const inheritedSchemaVersion = SchemaVersion.isValid(firstAttachment.schemaVersion)
firstAttachment.schemaVersion
)
? firstAttachment.schemaVersion ? firstAttachment.schemaVersion
: INITIAL_SCHEMA_VERSION; : INITIAL_SCHEMA_VERSION;
const messageWithInitialSchema = Object.assign({}, message, { const messageWithInitialSchema = Object.assign({}, message, {
@ -108,17 +103,12 @@ exports._withSchemaVersion = ({ schemaVersion, upgrade }) => {
return async (message, context) => { return async (message, context) => {
if (!context || !isObject(context.logger)) { if (!context || !isObject(context.logger)) {
throw new TypeError( throw new TypeError('_withSchemaVersion: context must have logger object');
'_withSchemaVersion: context must have logger object'
);
} }
const { logger } = context; const { logger } = context;
if (!exports.isValid(message)) { if (!exports.isValid(message)) {
logger.error( logger.error('Message._withSchemaVersion: Invalid input message:', message);
'Message._withSchemaVersion: Invalid input message:',
message
);
return message; return message;
} }
@ -150,10 +140,7 @@ exports._withSchemaVersion = ({ schemaVersion, upgrade }) => {
} }
if (!exports.isValid(upgradedMessage)) { if (!exports.isValid(upgradedMessage)) {
logger.error( logger.error('Message._withSchemaVersion: Invalid upgraded message:', upgradedMessage);
'Message._withSchemaVersion: Invalid upgraded message:',
upgradedMessage
);
return message; return message;
} }
@ -166,11 +153,8 @@ exports._withSchemaVersion = ({ schemaVersion, upgrade }) => {
// (Message, Context) -> // (Message, Context) ->
// Promise Message // Promise Message
exports._mapAttachments = upgradeAttachment => async (message, context) => { exports._mapAttachments = upgradeAttachment => async (message, context) => {
const upgradeWithContext = attachment => const upgradeWithContext = attachment => upgradeAttachment(attachment, context);
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 }); return Object.assign({}, message, { attachments });
}; };
@ -180,21 +164,15 @@ exports._mapAttachments = upgradeAttachment => async (message, context) => {
// Promise Message // Promise Message
exports._mapContact = upgradeContact => async (message, context) => { exports._mapContact = upgradeContact => async (message, context) => {
const contextWithMessage = Object.assign({}, context, { message }); const contextWithMessage = Object.assign({}, context, { message });
const upgradeWithContext = contact => const upgradeWithContext = contact => upgradeContact(contact, contextWithMessage);
upgradeContact(contact, contextWithMessage); const contact = await Promise.all((message.contact || []).map(upgradeWithContext));
const contact = await Promise.all(
(message.contact || []).map(upgradeWithContext)
);
return Object.assign({}, message, { contact }); return Object.assign({}, message, { contact });
}; };
// _mapQuotedAttachments :: (QuotedAttachment -> Promise QuotedAttachment) -> // _mapQuotedAttachments :: (QuotedAttachment -> Promise QuotedAttachment) ->
// (Message, Context) -> // (Message, Context) ->
// Promise Message // Promise Message
exports._mapQuotedAttachments = upgradeAttachment => async ( exports._mapQuotedAttachments = upgradeAttachment => async (message, context) => {
message,
context
) => {
if (!message.quote) { if (!message.quote) {
return message; return message;
} }
@ -216,9 +194,7 @@ exports._mapQuotedAttachments = upgradeAttachment => async (
const quotedAttachments = (message.quote && message.quote.attachments) || []; const quotedAttachments = (message.quote && message.quote.attachments) || [];
const attachments = await Promise.all( const attachments = await Promise.all(quotedAttachments.map(upgradeWithContext));
quotedAttachments.map(upgradeWithContext)
);
return Object.assign({}, message, { return Object.assign({}, message, {
quote: Object.assign({}, message.quote, { quote: Object.assign({}, message.quote, {
attachments, attachments,
@ -229,10 +205,7 @@ exports._mapQuotedAttachments = upgradeAttachment => async (
// _mapPreviewAttachments :: (PreviewAttachment -> Promise PreviewAttachment) -> // _mapPreviewAttachments :: (PreviewAttachment -> Promise PreviewAttachment) ->
// (Message, Context) -> // (Message, Context) ->
// Promise Message // Promise Message
exports._mapPreviewAttachments = upgradeAttachment => async ( exports._mapPreviewAttachments = upgradeAttachment => async (message, context) => {
message,
context
) => {
if (!message.preview) { if (!message.preview) {
return message; return message;
} }
@ -252,9 +225,7 @@ exports._mapPreviewAttachments = upgradeAttachment => async (
}); });
}; };
const preview = await Promise.all( const preview = await Promise.all((message.preview || []).map(upgradeWithContext));
(message.preview || []).map(upgradeWithContext)
);
return Object.assign({}, message, { return Object.assign({}, message, {
preview, preview,
}); });
@ -284,9 +255,7 @@ const toVersion5 = exports._withSchemaVersion({
}); });
const toVersion6 = exports._withSchemaVersion({ const toVersion6 = exports._withSchemaVersion({
schemaVersion: 6, schemaVersion: 6,
upgrade: exports._mapContact( upgrade: exports._mapContact(Contact.parseAndWriteAvatar(Attachment.migrateDataToFileSystem)),
Contact.parseAndWriteAvatar(Attachment.migrateDataToFileSystem)
),
}); });
// IMPORTANT: Weve updated our definition of `initializeAttachmentMetadata`, so // IMPORTANT: Weve updated our definition of `initializeAttachmentMetadata`, so
// we need to run it again on existing items that have previously been incorrectly // we need to run it again on existing items that have previously been incorrectly
@ -435,39 +404,31 @@ exports.processNewAttachment = async (
} }
const rotatedAttachment = await Attachment.autoOrientJPEG(attachment); const rotatedAttachment = await Attachment.autoOrientJPEG(attachment);
const onDiskAttachment = await Attachment.migrateDataToFileSystem( const onDiskAttachment = await Attachment.migrateDataToFileSystem(rotatedAttachment, {
rotatedAttachment, writeNewAttachmentData,
{ writeNewAttachmentData } });
); const finalAttachment = await Attachment.captureDimensionsAndScreenshot(onDiskAttachment, {
const finalAttachment = await Attachment.captureDimensionsAndScreenshot( writeNewAttachmentData,
onDiskAttachment, getAbsoluteAttachmentPath,
{ makeObjectUrl,
writeNewAttachmentData, revokeObjectUrl,
getAbsoluteAttachmentPath, getImageDimensions,
makeObjectUrl, makeImageThumbnail,
revokeObjectUrl, makeVideoScreenshot,
getImageDimensions, logger,
makeImageThumbnail, });
makeVideoScreenshot,
logger,
}
);
return finalAttachment; return finalAttachment;
}; };
exports.createAttachmentLoader = loadAttachmentData => { exports.createAttachmentLoader = loadAttachmentData => {
if (!isFunction(loadAttachmentData)) { if (!isFunction(loadAttachmentData)) {
throw new TypeError( throw new TypeError('createAttachmentLoader: loadAttachmentData is required');
'createAttachmentLoader: loadAttachmentData is required'
);
} }
return async message => return async message =>
Object.assign({}, message, { Object.assign({}, message, {
attachments: await Promise.all( attachments: await Promise.all(message.attachments.map(loadAttachmentData)),
message.attachments.map(loadAttachmentData)
),
}); });
}; };
@ -528,15 +489,11 @@ exports.loadPreviewData = loadAttachmentData => {
exports.deleteAllExternalFiles = ({ deleteAttachmentData, deleteOnDisk }) => { exports.deleteAllExternalFiles = ({ deleteAttachmentData, deleteOnDisk }) => {
if (!isFunction(deleteAttachmentData)) { if (!isFunction(deleteAttachmentData)) {
throw new TypeError( throw new TypeError('deleteAllExternalFiles: deleteAttachmentData must be a function');
'deleteAllExternalFiles: deleteAttachmentData must be a function'
);
} }
if (!isFunction(deleteOnDisk)) { if (!isFunction(deleteOnDisk)) {
throw new TypeError( throw new TypeError('deleteAllExternalFiles: deleteOnDisk must be a function');
'deleteAllExternalFiles: deleteOnDisk must be a function'
);
} }
return async message => { return async message => {
@ -590,10 +547,7 @@ exports.deleteAllExternalFiles = ({ deleteAttachmentData, deleteOnDisk }) => {
// createAttachmentDataWriter :: (RelativePath -> IO Unit) // createAttachmentDataWriter :: (RelativePath -> IO Unit)
// Message -> // Message ->
// IO (Promise Message) // IO (Promise Message)
exports.createAttachmentDataWriter = ({ exports.createAttachmentDataWriter = ({ writeExistingAttachmentData, logger }) => {
writeExistingAttachmentData,
logger,
}) => {
if (!isFunction(writeExistingAttachmentData)) { if (!isFunction(writeExistingAttachmentData)) {
throw new TypeError( throw new TypeError(
'createAttachmentDataWriter: writeExistingAttachmentData must be a function' 'createAttachmentDataWriter: writeExistingAttachmentData must be a function'
@ -633,15 +587,11 @@ exports.createAttachmentDataWriter = ({
(attachments || []).forEach(attachment => { (attachments || []).forEach(attachment => {
if (!Attachment.hasData(attachment)) { if (!Attachment.hasData(attachment)) {
throw new TypeError( throw new TypeError("'attachment.data' is required during message import");
"'attachment.data' is required during message import"
);
} }
if (!isString(attachment.path)) { if (!isString(attachment.path)) {
throw new TypeError( throw new TypeError("'attachment.path' is required during message import");
"'attachment.path' is required during message import"
);
} }
}); });

@ -26,20 +26,12 @@ exports.getImageDimensions = ({ objectUrl, logger }) =>
reject(error); reject(error);
}); });
// TODO image/jpeg is hard coded, but it does not look to cause any issues // TODO image/jpeg is hard coded, but it does not look to cause any issues
DecryptedAttachmentsManager.getDecryptedMediaUrl( DecryptedAttachmentsManager.getDecryptedMediaUrl(objectUrl, 'image/jpg').then(decryptedUrl => {
objectUrl,
'image/jpg'
).then(decryptedUrl => {
image.src = decryptedUrl; image.src = decryptedUrl;
}); });
}); });
exports.makeImageThumbnail = ({ exports.makeImageThumbnail = ({ size, objectUrl, contentType = 'image/png', logger }) =>
size,
objectUrl,
contentType = 'image/png',
logger,
}) =>
new Promise((resolve, reject) => { new Promise((resolve, reject) => {
const image = document.createElement('img'); const image = document.createElement('img');
@ -76,19 +68,12 @@ exports.makeImageThumbnail = ({
reject(error); reject(error);
}); });
DecryptedAttachmentsManager.getDecryptedMediaUrl( DecryptedAttachmentsManager.getDecryptedMediaUrl(objectUrl, contentType).then(decryptedUrl => {
objectUrl,
contentType
).then(decryptedUrl => {
image.src = decryptedUrl; image.src = decryptedUrl;
}); });
}); });
exports.makeVideoScreenshot = ({ exports.makeVideoScreenshot = ({ objectUrl, contentType = 'image/png', logger }) =>
objectUrl,
contentType = 'image/png',
logger,
}) =>
new Promise((resolve, reject) => { new Promise((resolve, reject) => {
const video = document.createElement('video'); const video = document.createElement('video');
@ -96,9 +81,7 @@ exports.makeVideoScreenshot = ({
const canvas = document.createElement('canvas'); const canvas = document.createElement('canvas');
canvas.width = video.videoWidth; canvas.width = video.videoWidth;
canvas.height = video.videoHeight; canvas.height = video.videoHeight;
canvas canvas.getContext('2d').drawImage(video, 0, 0, canvas.width, canvas.height);
.getContext('2d')
.drawImage(video, 0, 0, canvas.width, canvas.height);
const image = dataURLToBlobSync(canvas.toDataURL(contentType)); const image = dataURLToBlobSync(canvas.toDataURL(contentType));
@ -114,10 +97,7 @@ exports.makeVideoScreenshot = ({
reject(error); reject(error);
}); });
DecryptedAttachmentsManager.getDecryptedMediaUrl( DecryptedAttachmentsManager.getDecryptedMediaUrl(objectUrl, contentType).then(decryptedUrl => {
objectUrl,
contentType
).then(decryptedUrl => {
video.src = decryptedUrl; video.src = decryptedUrl;
video.muted = true; video.muted = true;
// for some reason, this is to be started, otherwise the generated thumbnail will be empty // for some reason, this is to be started, otherwise the generated thumbnail will be empty

@ -36,9 +36,7 @@ class WorkerInterface {
if (errorForDisplay) { if (errorForDisplay) {
return reject( return reject(
new Error( new Error(`Error received from worker job ${jobId} (${fnName}): ${errorForDisplay}`)
`Error received from worker job ${jobId} (${fnName}): ${errorForDisplay}`
)
); );
} }
@ -72,18 +70,14 @@ class WorkerInterface {
this._removeJob(id); this._removeJob(id);
const end = Date.now(); const end = Date.now();
if (this._DEBUG) { if (this._DEBUG) {
window.log.info( window.log.info(`Worker job ${id} (${fnName}) succeeded in ${end - start}ms`);
`Worker job ${id} (${fnName}) succeeded in ${end - start}ms`
);
} }
return resolve(value); return resolve(value);
}, },
reject: error => { reject: error => {
this._removeJob(id); this._removeJob(id);
const end = Date.now(); const end = Date.now();
window.log.info( window.log.info(`Worker job ${id} (${fnName}) failed in ${end - start}ms`);
`Worker job ${id} (${fnName}) failed in ${end - start}ms`
);
return reject(error); return reject(error);
}, },
}; };
@ -114,10 +108,7 @@ class WorkerInterface {
}); });
setTimeout( setTimeout(
() => () => reject(new TimedOutError(`Worker job ${jobId} (${fnName}) timed out`)),
reject(
new TimedOutError(`Worker job ${jobId} (${fnName}) timed out`)
),
this.timeout this.timeout
); );
}); });

@ -57,8 +57,7 @@
const { isEnabled } = this; const { isEnabled } = this;
const isAppFocused = isFocused(); const isAppFocused = isFocused();
const isAudioNotificationEnabled = const isAudioNotificationEnabled = storage.get('audio-notification') || false;
storage.get('audio-notification') || false;
const isAudioNotificationSupported = Settings.isAudioNotificationSupported(); const isAudioNotificationSupported = Settings.isAudioNotificationSupported();
// const isNotificationGroupingSupported = Settings.isNotificationGroupingSupported(); // const isNotificationGroupingSupported = Settings.isNotificationGroupingSupported();
const numNotifications = this.length; const numNotifications = this.length;
@ -99,9 +98,7 @@
// e.g. Russian: // e.g. Russian:
// http://docs.translatehouse.org/projects/localization-guide/en/latest/l10n/pluralforms.html // http://docs.translatehouse.org/projects/localization-guide/en/latest/l10n/pluralforms.html
const newMessageCountLabel = `${messagesNotificationCount} ${ const newMessageCountLabel = `${messagesNotificationCount} ${
messagesNotificationCount === 1 messagesNotificationCount === 1 ? i18n('newMessage') : i18n('newMessages')
? i18n('newMessage')
: i18n('newMessages')
}`; }`;
const last = this.last().toJSON(); const last = this.last().toJSON();
@ -141,14 +138,11 @@
iconUrl = last.iconUrl; iconUrl = last.iconUrl;
break; break;
default: default:
window.log.error( window.log.error(`Error: Unknown user notification setting: '${userSetting}'`);
`Error: Unknown user notification setting: '${userSetting}'`
);
break; break;
} }
const shouldHideExpiringMessageBody = const shouldHideExpiringMessageBody = last.isExpiringMessage && Signal.OS.isMacOS();
last.isExpiringMessage && Signal.OS.isMacOS();
if (shouldHideExpiringMessageBody) { if (shouldHideExpiringMessageBody) {
message = i18n('newMessage'); message = i18n('newMessage');
} }
@ -160,8 +154,7 @@
icon: iconUrl, icon: iconUrl,
silent: !status.shouldPlayNotificationSound, silent: !status.shouldPlayNotificationSound,
}); });
this.lastNotification.onclick = () => this.lastNotification.onclick = () => this.trigger('click', last.conversationId, last.id);
this.trigger('click', last.conversationId, last.id);
// We continue to build up more and more messages for our notifications // We continue to build up more and more messages for our notifications
// until the user comes back to our app or closes the app. Then well // until the user comes back to our app or closes the app. Then well

@ -61,14 +61,9 @@
}, },
async onReceipt(receipt) { async onReceipt(receipt) {
try { try {
const messages = await window.Signal.Data.getMessagesBySentAt( const messages = await window.Signal.Data.getMessagesBySentAt(receipt.get('timestamp'));
receipt.get('timestamp')
);
const message = await this.getTargetMessage( const message = await this.getTargetMessage(receipt.get('reader'), messages);
receipt.get('reader'),
messages
);
if (!message) { if (!message) {
window.log.info( window.log.info(
@ -80,9 +75,7 @@
} }
const readBy = message.get('read_by') || []; const readBy = message.get('read_by') || [];
const expirationStartTimestamp = message.get( const expirationStartTimestamp = message.get('expirationStartTimestamp');
'expirationStartTimestamp'
);
readBy.push(receipt.get('reader')); readBy.push(receipt.get('reader'));
message.set({ message.set({
@ -99,9 +92,7 @@
} }
// notify frontend listeners // notify frontend listeners
const conversation = window const conversation = window.getConversationController().get(message.get('conversationId'));
.getConversationController()
.get(message.get('conversationId'));
if (conversation) { if (conversation) {
conversation.updateLastMessage(); conversation.updateLastMessage();
} }

@ -27,20 +27,15 @@
}, },
async onReceipt(receipt) { async onReceipt(receipt) {
try { try {
const messages = await window.Signal.Data.getMessagesBySentAt( const messages = await window.Signal.Data.getMessagesBySentAt(receipt.get('timestamp'));
receipt.get('timestamp')
);
const found = messages.find( const found = messages.find(
item => item => item.isIncoming() && item.get('source') === receipt.get('sender')
item.isIncoming() && item.get('source') === receipt.get('sender')
); );
const notificationForMessage = found const notificationForMessage = found
? Whisper.Notifications.findWhere({ messageId: found.id }) ? Whisper.Notifications.findWhere({ messageId: found.id })
: null; : null;
const removedNotification = Whisper.Notifications.remove( const removedNotification = Whisper.Notifications.remove(notificationForMessage);
notificationForMessage
);
const receiptSender = receipt.get('sender'); const receiptSender = receipt.get('sender');
const receiptTimestamp = receipt.get('timestamp'); const receiptTimestamp = receipt.get('timestamp');
const wasMessageFound = Boolean(found); const wasMessageFound = Boolean(found);
@ -89,10 +84,7 @@
this.remove(receipt); this.remove(receipt);
} catch (error) { } catch (error) {
window.log.error( window.log.error('ReadSyncs.onReceipt error:', error && error.stack ? error.stack : error);
'ReadSyncs.onReceipt error:',
error && error.stack ? error.stack : error
);
} }
}, },
}))(); }))();

@ -46,13 +46,5 @@ function calcPoW(
increment = 1, increment = 1,
startNonce = 0 startNonce = 0
) { ) {
return pow.calcPoW( return pow.calcPoW(timestamp, ttl, pubKey, data, difficulty, increment, startNonce);
timestamp,
ttl,
pubKey,
data,
difficulty,
increment,
startNonce
);
} }

@ -145,8 +145,7 @@
}, },
getThemeObject() { getThemeObject() {
const themeSettings = storage.get('theme-setting') || 'light'; const themeSettings = storage.get('theme-setting') || 'light';
const theme = const theme = themeSettings === 'light' ? window.lightTheme : window.darkTheme;
themeSettings === 'light' ? window.lightTheme : window.darkTheme;
return theme; return theme;
}, },
showUpdateGroupNameDialog(groupConvo) { showUpdateGroupNameDialog(groupConvo) {
@ -165,9 +164,7 @@
}, },
showLeaveGroupDialog(groupConvo) { showLeaveGroupDialog(groupConvo) {
if (!groupConvo.isGroup()) { if (!groupConvo.isGroup()) {
throw new Error( throw new Error('showLeaveGroupDialog() called with a non group convo.');
'showLeaveGroupDialog() called with a non group convo.'
);
} }
const title = i18n('leaveGroup'); const title = i18n('leaveGroup');

@ -67,10 +67,7 @@
.focus() .focus()
.select(); .select();
} catch (error) { } catch (error) {
window.log.error( window.log.error('DebugLogView error:', error && error.stack ? error.stack : error);
'DebugLogView error:',
error && error.stack ? error.stack : error
);
this.$('.loading').removeClass('loading'); this.$('.loading').removeClass('loading');
this.$('.result').text(i18n('debugLogError')); this.$('.result').text(i18n('debugLogError'));
} }

@ -140,10 +140,7 @@
Whisper.Import.reset() Whisper.Import.reset()
) )
.then(() => .then(() =>
Promise.all([ Promise.all([Whisper.Import.start(), window.Signal.Backup.importFromDirectory(directory)])
Whisper.Import.start(),
window.Signal.Backup.importFromDirectory(directory),
])
) )
.then(results => { .then(results => {
const importResult = results[1]; const importResult = results[1];
@ -158,10 +155,7 @@
return this.finishLightImport(directory); return this.finishLightImport(directory);
}) })
.catch(error => { .catch(error => {
window.log.error( window.log.error('Error importing:', error && error.stack ? error.stack : error);
'Error importing:',
error && error.stack ? error.stack : error
);
this.error = error || new Error('Something went wrong!'); this.error = error || new Error('Something went wrong!');
this.state = null; this.state = null;
@ -177,10 +171,7 @@
.getConversationController() .getConversationController()
.load() .load()
.then(() => .then(() =>
Promise.all([ Promise.all([Whisper.Import.saveLocation(directory), Whisper.Import.complete()])
Whisper.Import.saveLocation(directory),
Whisper.Import.complete(),
])
) )
.then(() => { .then(() => {
this.state = State.LIGHT_COMPLETE; this.state = State.LIGHT_COMPLETE;

@ -15,12 +15,7 @@
const convos = window.getConversationController().getConversations(); const convos = window.getConversationController().getConversations();
this.contacts = convos.filter( this.contacts = convos.filter(
d => d => !!d && !d.isBlocked() && d.isPrivate() && !d.isMe() && !!d.get('active_at')
!!d &&
!d.isBlocked() &&
d.isPrivate() &&
!d.isMe() &&
!!d.get('active_at')
); );
if (!convo.isPublic()) { if (!convo.isPublic()) {
const members = convo.get('members') || []; const members = convo.get('members') || [];
@ -93,19 +88,13 @@
return; return;
} }
const allMembers = window.Lodash.concat(existingMembers, newMembers, [ const allMembers = window.Lodash.concat(existingMembers, newMembers, [ourPK]);
ourPK,
]);
const uniqMembers = _.uniq(allMembers, true, d => d); const uniqMembers = _.uniq(allMembers, true, d => d);
const groupId = this.convo.get('id'); const groupId = this.convo.get('id');
const groupName = this.convo.get('name'); const groupName = this.convo.get('name');
window.libsession.ClosedGroup.initiateGroupUpdate( window.libsession.ClosedGroup.initiateGroupUpdate(groupId, groupName, uniqMembers);
groupId,
groupName,
uniqMembers
);
} }
} }
}, },

@ -40,9 +40,7 @@
}, },
update(props) { update(props) {
const updatedProps = this.augmentProps(props); const updatedProps = this.augmentProps(props);
const reactElement = this.JSX const reactElement = this.JSX ? this.JSX : React.createElement(this.Component, updatedProps);
? this.JSX
: React.createElement(this.Component, updatedProps);
ReactDOM.render(reactElement, this.el, () => { ReactDOM.render(reactElement, this.el, () => {
if (this.hasRendered) { if (this.hasRendered) {
return; return;

@ -31,9 +31,7 @@
this.titleText = i18n('updateGroupDialogTitle', this.groupName); this.titleText = i18n('updateGroupDialogTitle', this.groupName);
// I'd much prefer to integrate mods with groupAdmins // I'd much prefer to integrate mods with groupAdmins
// but lets discuss first... // but lets discuss first...
this.isAdmin = groupConvo.isAdmin( this.isAdmin = groupConvo.isAdmin(window.storage.get('primaryDevicePubKey'));
window.storage.get('primaryDevicePubKey')
);
} }
this.$el.focus(); this.$el.focus();
@ -92,9 +90,7 @@
this.titleText = i18n('updateGroupDialogTitle', this.groupName); this.titleText = i18n('updateGroupDialogTitle', this.groupName);
// I'd much prefer to integrate mods with groupAdmins // I'd much prefer to integrate mods with groupAdmins
// but lets discuss first... // but lets discuss first...
this.isAdmin = groupConvo.isAdmin( this.isAdmin = groupConvo.isAdmin(window.storage.get('primaryDevicePubKey'));
window.storage.get('primaryDevicePubKey')
);
// zero out contactList for now // zero out contactList for now
this.contactsAndMembers = []; this.contactsAndMembers = [];
this.existingMembers = []; this.existingMembers = [];
@ -116,11 +112,7 @@
this.contactsAndMembers = convos.filter( this.contactsAndMembers = convos.filter(
d => this.existingMembers.includes(d.id) && d.isPrivate() && !d.isMe() d => this.existingMembers.includes(d.id) && d.isPrivate() && !d.isMe()
); );
this.contactsAndMembers = _.uniq( this.contactsAndMembers = _.uniq(this.contactsAndMembers, true, d => d.id);
this.contactsAndMembers,
true,
d => d.id
);
// at least make sure it's an array // at least make sure it's an array
if (!Array.isArray(this.existingMembers)) { if (!Array.isArray(this.existingMembers)) {
@ -160,24 +152,16 @@
const allMembers = window.Lodash.concat(newMembers, [ourPK]); const allMembers = window.Lodash.concat(newMembers, [ourPK]);
// We need to NOT trigger an group update if the list of member is the same. // We need to NOT trigger an group update if the list of member is the same.
const notPresentInOld = allMembers.filter( const notPresentInOld = allMembers.filter(m => !this.existingMembers.includes(m));
m => !this.existingMembers.includes(m)
);
const membersToRemove = this.existingMembers.filter( const membersToRemove = this.existingMembers.filter(m => !allMembers.includes(m));
m => !allMembers.includes(m)
);
// If any extra devices of removed exist in newMembers, ensure that you filter them // If any extra devices of removed exist in newMembers, ensure that you filter them
const filteredMemberes = allMembers.filter( const filteredMemberes = allMembers.filter(member => !_.includes(membersToRemove, member));
member => !_.includes(membersToRemove, member)
);
const xor = _.xor(membersToRemove, notPresentInOld); const xor = _.xor(membersToRemove, notPresentInOld);
if (xor.length === 0) { if (xor.length === 0) {
window.log.info( window.log.info('skipping group update: no detected changes in group member list');
'skipping group update: no detected changes in group member list'
);
return; return;
} }

@ -8,14 +8,7 @@
Whisper.UserDetailsDialogView = Whisper.View.extend({ Whisper.UserDetailsDialogView = Whisper.View.extend({
className: 'loki-dialog modal', className: 'loki-dialog modal',
initialize({ initialize({ profileName, avatarPath, pubkey, onOk, onStartConversation, theme }) {
profileName,
avatarPath,
pubkey,
onOk,
onStartConversation,
theme,
}) {
this.close = this.close.bind(this); this.close = this.close.bind(this);
this.profileName = profileName; this.profileName = profileName;

@ -1,14 +1,8 @@
export interface CryptoInterface { export interface CryptoInterface {
DHDecrypt: any; DHDecrypt: any;
DHEncrypt: any; DHEncrypt: any;
DecryptAESGCM: ( DecryptAESGCM: (symmetricKey: ArrayBuffer, ivAndCiphertext: ArrayBuffer) => Promise<ArrayBuffer>; // AES-GCM
symmetricKey: ArrayBuffer, deriveSymmetricKey: (pubkey: ArrayBuffer, seckey: ArrayBuffer) => Promise<ArrayBuffer>;
ivAndCiphertext: ArrayBuffer
) => Promise<ArrayBuffer>; // AES-GCM
deriveSymmetricKey: (
pubkey: ArrayBuffer,
seckey: ArrayBuffer
) => Promise<ArrayBuffer>;
EncryptAESGCM: any; // AES-GCM EncryptAESGCM: any; // AES-GCM
_decodeSnodeAddressToPubKey: any; _decodeSnodeAddressToPubKey: any;
decryptToken: any; decryptToken: any;

@ -19,14 +19,8 @@
async function DHEncrypt(symmetricKey, plainText) { async function DHEncrypt(symmetricKey, plainText) {
const iv = libsignal.crypto.getRandomBytes(IV_LENGTH); const iv = libsignal.crypto.getRandomBytes(IV_LENGTH);
const ciphertext = await libsignal.crypto.encrypt( const ciphertext = await libsignal.crypto.encrypt(symmetricKey, plainText, iv);
symmetricKey, const ivAndCiphertext = new Uint8Array(iv.byteLength + ciphertext.byteLength);
plainText,
iv
);
const ivAndCiphertext = new Uint8Array(
iv.byteLength + ciphertext.byteLength
);
ivAndCiphertext.set(new Uint8Array(iv)); ivAndCiphertext.set(new Uint8Array(iv));
ivAndCiphertext.set(new Uint8Array(ciphertext), iv.byteLength); ivAndCiphertext.set(new Uint8Array(ciphertext), iv.byteLength);
return ivAndCiphertext; return ivAndCiphertext;
@ -71,13 +65,9 @@
async function EncryptAESGCM(symmetricKey, plaintext) { async function EncryptAESGCM(symmetricKey, plaintext) {
const nonce = crypto.getRandomValues(new Uint8Array(NONCE_LENGTH)); const nonce = crypto.getRandomValues(new Uint8Array(NONCE_LENGTH));
const key = await crypto.subtle.importKey( const key = await crypto.subtle.importKey('raw', symmetricKey, { name: 'AES-GCM' }, false, [
'raw', 'encrypt',
symmetricKey, ]);
{ name: 'AES-GCM' },
false,
['encrypt']
);
const ciphertext = await crypto.subtle.encrypt( const ciphertext = await crypto.subtle.encrypt(
{ name: 'AES-GCM', iv: nonce, tagLength: 128 }, { name: 'AES-GCM', iv: nonce, tagLength: 128 },
@ -85,9 +75,7 @@
plaintext plaintext
); );
const ivAndCiphertext = new Uint8Array( const ivAndCiphertext = new Uint8Array(NONCE_LENGTH + ciphertext.byteLength);
NONCE_LENGTH + ciphertext.byteLength
);
ivAndCiphertext.set(nonce); ivAndCiphertext.set(nonce);
ivAndCiphertext.set(new Uint8Array(ciphertext), nonce.byteLength); ivAndCiphertext.set(new Uint8Array(ciphertext), nonce.byteLength);
@ -99,19 +87,11 @@
const nonce = ivAndCiphertext.slice(0, NONCE_LENGTH); const nonce = ivAndCiphertext.slice(0, NONCE_LENGTH);
const ciphertext = ivAndCiphertext.slice(NONCE_LENGTH); const ciphertext = ivAndCiphertext.slice(NONCE_LENGTH);
const key = await crypto.subtle.importKey( const key = await crypto.subtle.importKey('raw', symmetricKey, { name: 'AES-GCM' }, false, [
'raw', 'decrypt',
symmetricKey, ]);
{ name: 'AES-GCM' },
false,
['decrypt']
);
return crypto.subtle.decrypt( return crypto.subtle.decrypt({ name: 'AES-GCM', iv: nonce }, key, ciphertext);
{ name: 'AES-GCM', iv: nonce },
key,
ciphertext
);
} }
async function DHDecrypt(symmetricKey, ivAndCiphertext) { async function DHDecrypt(symmetricKey, ivAndCiphertext) {
@ -152,10 +132,7 @@
throw new Error('Failed to get keypair for token decryption'); throw new Error('Failed to get keypair for token decryption');
} }
const { privKey } = keyPair; const { privKey } = keyPair;
const symmetricKey = await libsignal.Curve.async.calculateAgreement( const symmetricKey = await libsignal.Curve.async.calculateAgreement(serverPubKey, privKey);
serverPubKey,
privKey
);
const token = await DHDecrypt(symmetricKey, ivAndCiphertext); const token = await DHDecrypt(symmetricKey, ivAndCiphertext);

@ -62,15 +62,7 @@ const pow = {
}, },
// Return nonce that hashes together with payload lower than the target // Return nonce that hashes together with payload lower than the target
async calcPoW( async calcPoW(timestamp, ttl, pubKey, data, _difficulty = null, increment = 1, startNonce = 0) {
timestamp,
ttl,
pubKey,
data,
_difficulty = null,
increment = 1,
startNonce = 0
) {
const payload = new Uint8Array( const payload = new Uint8Array(
dcodeIO.ByteBuffer.wrap( dcodeIO.ByteBuffer.wrap(
timestamp.toString() + ttl.toString() + pubKey + data, timestamp.toString() + ttl.toString() + pubKey + data,
@ -84,9 +76,7 @@ const pow = {
let nonce = new Uint8Array(NONCE_LEN); let nonce = new Uint8Array(NONCE_LEN);
nonce = pow.incrementNonce(nonce, startNonce); // initial value nonce = pow.incrementNonce(nonce, startNonce); // initial value
let trialValue = new Uint8Array([255, 255, 255, 255, 255, 255, 255, 255]); let trialValue = new Uint8Array([255, 255, 255, 255, 255, 255, 255, 255]);
const initialHash = new Uint8Array( const initialHash = new Uint8Array(await crypto.subtle.digest('SHA-512', payload));
await crypto.subtle.digest('SHA-512', payload)
);
const innerPayload = new Uint8Array(initialHash.length + NONCE_LEN); const innerPayload = new Uint8Array(initialHash.length + NONCE_LEN);
innerPayload.set(initialHash, NONCE_LEN); innerPayload.set(initialHash, NONCE_LEN);
let resultHash; let resultHash;
@ -97,9 +87,10 @@ const pow = {
innerPayload.set(nonce); innerPayload.set(nonce);
// eslint-disable-next-line no-await-in-loop // eslint-disable-next-line no-await-in-loop
resultHash = await crypto.subtle.digest('SHA-512', innerPayload); resultHash = await crypto.subtle.digest('SHA-512', innerPayload);
trialValue = new Uint8Array( trialValue = new Uint8Array(dcodeIO.ByteBuffer.wrap(resultHash, 'hex').toArrayBuffer()).slice(
dcodeIO.ByteBuffer.wrap(resultHash, 'hex').toArrayBuffer() 0,
).slice(0, NONCE_LEN); NONCE_LEN
);
} }
return pow.bufferToBase64(nonce); return pow.bufferToBase64(nonce);
}, },
@ -121,10 +112,7 @@ const pow = {
// totalLen + innerFrac // totalLen + innerFrac
const lenPlusInnerFrac = JSBI.add(totalLen, innerFrac); const lenPlusInnerFrac = JSBI.add(totalLen, innerFrac);
// difficulty * lenPlusInnerFrac // difficulty * lenPlusInnerFrac
const denominator = JSBI.multiply( const denominator = JSBI.multiply(JSBI.BigInt(difficulty), lenPlusInnerFrac);
JSBI.BigInt(difficulty),
lenPlusInnerFrac
);
// 2^64 - 1 // 2^64 - 1
const two64 = JSBI.subtract( const two64 = JSBI.subtract(
JSBI.exponentiate(JSBI.BigInt(2), JSBI.BigInt(64)), // 2^64 JSBI.exponentiate(JSBI.BigInt(2), JSBI.BigInt(64)), // 2^64

@ -5,13 +5,9 @@ let plotlyDiv;
const workers = []; const workers = [];
async function run(messageLength, numWorkers = 1, difficulty = 100, ttl = 72) { async function run(messageLength, numWorkers = 1, difficulty = 100, ttl = 72) {
const timestamp = Math.floor(Date.now() / 1000); const timestamp = Math.floor(Date.now() / 1000);
const pubKey = const pubKey = '05ec8635a07a13743516c7c9b3412f3e8252efb7fcaf67eb1615ffba62bebc6802';
'05ec8635a07a13743516c7c9b3412f3e8252efb7fcaf67eb1615ffba62bebc6802';
const message = randomString(messageLength); const message = randomString(messageLength);
const messageBuffer = dcodeIO.ByteBuffer.wrap( const messageBuffer = dcodeIO.ByteBuffer.wrap(message, 'utf8').toArrayBuffer();
message,
'utf8'
).toArrayBuffer();
const data = dcodeIO.ByteBuffer.wrap(messageBuffer).toString('base64'); const data = dcodeIO.ByteBuffer.wrap(messageBuffer).toString('base64');
const promises = []; const promises = [];
const t0 = performance.now(); const t0 = performance.now();
@ -48,13 +44,7 @@ async function run(messageLength, numWorkers = 1, difficulty = 100, ttl = 72) {
workers.forEach(worker => worker.terminate()); workers.forEach(worker => worker.terminate());
} }
async function runPoW({ async function runPoW({ iteration, difficulty, numWorkers, messageLength = 50, ttl = 72 }) {
iteration,
difficulty,
numWorkers,
messageLength = 50,
ttl = 72,
}) {
const name = `W:${numWorkers} - NT: ${difficulty} - L:${messageLength} - TTL:${ttl}`; const name = `W:${numWorkers} - NT: ${difficulty} - L:${messageLength} - TTL:${ttl}`;
Plotly.addTraces(plotlyDiv, { Plotly.addTraces(plotlyDiv, {
y: [], y: [],
@ -74,8 +64,7 @@ async function runPoW({
function randomString(length) { function randomString(length) {
let text = ''; let text = '';
const possible = const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
for (let i = 0; i < length; i += 1) { for (let i = 0; i < length; i += 1) {
text += possible.charAt(Math.floor(Math.random() * possible.length)); text += possible.charAt(Math.floor(Math.random() * possible.length));
} }
@ -89,21 +78,11 @@ async function startMessageLengthRun() {
const iteration0 = parseFloat(document.getElementById('iteration0').value); const iteration0 = parseFloat(document.getElementById('iteration0').value);
const difficulty0 = parseFloat(document.getElementById('difficulty0').value); const difficulty0 = parseFloat(document.getElementById('difficulty0').value);
const numWorkers0 = parseFloat(document.getElementById('numWorkers0').value); const numWorkers0 = parseFloat(document.getElementById('numWorkers0').value);
const messageLengthStart0 = parseFloat( const messageLengthStart0 = parseFloat(document.getElementById('messageLengthStart0').value);
document.getElementById('messageLengthStart0').value const messageLengthStop0 = parseFloat(document.getElementById('messageLengthStop0').value);
); const messageLengthStep0 = parseFloat(document.getElementById('messageLengthStep0').value);
const messageLengthStop0 = parseFloat(
document.getElementById('messageLengthStop0').value
);
const messageLengthStep0 = parseFloat(
document.getElementById('messageLengthStep0').value
);
const TTL0 = parseFloat(document.getElementById('TTL0').value); const TTL0 = parseFloat(document.getElementById('TTL0').value);
for ( for (let l = messageLengthStart0; l < messageLengthStop0; l += messageLengthStep0) {
let l = messageLengthStart0;
l < messageLengthStop0;
l += messageLengthStep0
) {
// eslint-disable-next-line no-await-in-loop // eslint-disable-next-line no-await-in-loop
await runPoW({ await runPoW({
iteration: iteration0, iteration: iteration0,
@ -117,21 +96,11 @@ async function startMessageLengthRun() {
async function startNumWorkerRun() { async function startNumWorkerRun() {
const iteration1 = parseFloat(document.getElementById('iteration1').value); const iteration1 = parseFloat(document.getElementById('iteration1').value);
const difficulty1 = parseFloat(document.getElementById('difficulty1').value); const difficulty1 = parseFloat(document.getElementById('difficulty1').value);
const numWorkersStart1 = parseFloat( const numWorkersStart1 = parseFloat(document.getElementById('numWorkersStart1').value);
document.getElementById('numWorkersStart1').value const numWorkersEnd1 = parseFloat(document.getElementById('numWorkersEnd1').value);
); const messageLength1 = parseFloat(document.getElementById('messageLength1').value);
const numWorkersEnd1 = parseFloat(
document.getElementById('numWorkersEnd1').value
);
const messageLength1 = parseFloat(
document.getElementById('messageLength1').value
);
const TTL1 = parseFloat(document.getElementById('TTL1').value); const TTL1 = parseFloat(document.getElementById('TTL1').value);
for ( for (let numWorkers = numWorkersStart1; numWorkers <= numWorkersEnd1; numWorkers += 1) {
let numWorkers = numWorkersStart1;
numWorkers <= numWorkersEnd1;
numWorkers += 1
) {
// eslint-disable-next-line no-await-in-loop // eslint-disable-next-line no-await-in-loop
await runPoW({ await runPoW({
iteration: iteration1, iteration: iteration1,
@ -144,19 +113,11 @@ async function startNumWorkerRun() {
} }
async function startDifficultyRun() { async function startDifficultyRun() {
const iteration2 = parseFloat(document.getElementById('iteration2').value); const iteration2 = parseFloat(document.getElementById('iteration2').value);
const messageLength2 = parseFloat( const messageLength2 = parseFloat(document.getElementById('messageLength2').value);
document.getElementById('messageLength2').value
);
const numWorkers2 = parseFloat(document.getElementById('numWorkers2').value); const numWorkers2 = parseFloat(document.getElementById('numWorkers2').value);
const difficultyStart2 = parseFloat( const difficultyStart2 = parseFloat(document.getElementById('difficultyStart2').value);
document.getElementById('difficultyStart2').value const difficultyStop2 = parseFloat(document.getElementById('difficultyStop2').value);
); const difficultyStep2 = parseFloat(document.getElementById('difficultyStep2').value);
const difficultyStop2 = parseFloat(
document.getElementById('difficultyStop2').value
);
const difficultyStep2 = parseFloat(
document.getElementById('difficultyStep2').value
);
const TTL2 = parseFloat(document.getElementById('TTL2').value); const TTL2 = parseFloat(document.getElementById('TTL2').value);
for (let n = difficultyStart2; n < difficultyStop2; n += difficultyStep2) { for (let n = difficultyStart2; n < difficultyStop2; n += difficultyStep2) {
// eslint-disable-next-line no-await-in-loop // eslint-disable-next-line no-await-in-loop
@ -172,9 +133,7 @@ async function startDifficultyRun() {
async function starTTLRun() { async function starTTLRun() {
const iteration3 = parseFloat(document.getElementById('iteration3').value); const iteration3 = parseFloat(document.getElementById('iteration3').value);
const difficulty3 = parseFloat(document.getElementById('difficulty3').value); const difficulty3 = parseFloat(document.getElementById('difficulty3').value);
const messageLength3 = parseFloat( const messageLength3 = parseFloat(document.getElementById('messageLength3').value);
document.getElementById('messageLength3').value
);
const numWorkers3 = parseFloat(document.getElementById('numWorkers3').value); const numWorkers3 = parseFloat(document.getElementById('numWorkers3').value);
const TTLStart3 = parseFloat(document.getElementById('TTLStart3').value); const TTLStart3 = parseFloat(document.getElementById('TTLStart3').value);
const TTLStop3 = parseFloat(document.getElementById('TTLStop3').value); const TTLStop3 = parseFloat(document.getElementById('TTLStop3').value);

@ -1,12 +1,6 @@
/* global assert, JSBI, pow */ /* global assert, JSBI, pow */
const { const { calcTarget, incrementNonce, bufferToBase64, bigIntToUint8Array, greaterThan } = pow;
calcTarget,
incrementNonce,
bufferToBase64,
bigIntToUint8Array,
greaterThan,
} = pow;
describe('Proof of Work', () => { describe('Proof of Work', () => {
describe('#incrementNonce', () => { describe('#incrementNonce', () => {
@ -25,20 +19,14 @@ describe('Proof of Work', () => {
it('should increment a Uint8Array nonce correctly in a loop', () => { it('should increment a Uint8Array nonce correctly in a loop', () => {
let arr = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0]); let arr = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0]);
assert.deepEqual( assert.deepEqual(incrementNonce(arr), new Uint8Array([0, 0, 0, 0, 0, 0, 0, 1]));
incrementNonce(arr),
new Uint8Array([0, 0, 0, 0, 0, 0, 0, 1])
);
arr = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0]); arr = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0]);
for (let i = 0; i <= 255; i += 1) { for (let i = 0; i <= 255; i += 1) {
arr = incrementNonce(arr); arr = incrementNonce(arr);
} }
assert.deepEqual(arr, new Uint8Array([0, 0, 0, 0, 0, 0, 1, 0])); assert.deepEqual(arr, new Uint8Array([0, 0, 0, 0, 0, 0, 1, 0]));
arr = new Uint8Array([255, 255, 255, 255, 255, 255, 255, 255]); arr = new Uint8Array([255, 255, 255, 255, 255, 255, 255, 255]);
assert.deepEqual( assert.deepEqual(incrementNonce(arr), new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0]));
incrementNonce(arr),
new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0])
);
}); });
}); });

@ -43,14 +43,8 @@
const iv = encryptedBin.slice(0, 16); const iv = encryptedBin.slice(0, 16);
const ciphertext = encryptedBin.slice(16, encryptedBin.byteLength - 32); const ciphertext = encryptedBin.slice(16, encryptedBin.byteLength - 32);
const ivAndCiphertext = encryptedBin.slice( const ivAndCiphertext = encryptedBin.slice(0, encryptedBin.byteLength - 32);
0, const mac = encryptedBin.slice(encryptedBin.byteLength - 32, encryptedBin.byteLength);
encryptedBin.byteLength - 32
);
const mac = encryptedBin.slice(
encryptedBin.byteLength - 32,
encryptedBin.byteLength
);
return verifyMAC(ivAndCiphertext, macKey, mac, 32) return verifyMAC(ivAndCiphertext, macKey, mac, 32)
.then(() => { .then(() => {
@ -63,10 +57,7 @@
}, },
encryptAttachment(plaintext, keys, iv) { encryptAttachment(plaintext, keys, iv) {
if ( if (!(plaintext instanceof ArrayBuffer) && !ArrayBuffer.isView(plaintext)) {
!(plaintext instanceof ArrayBuffer) &&
!ArrayBuffer.isView(plaintext)
) {
throw new TypeError( throw new TypeError(
`\`plaintext\` must be an \`ArrayBuffer\` or \`ArrayBufferView\`; got: ${typeof plaintext}` `\`plaintext\` must be an \`ArrayBuffer\` or \`ArrayBufferView\`; got: ${typeof plaintext}`
); );
@ -109,20 +100,11 @@
.importKey('raw', key, { name: 'AES-GCM' }, false, ['encrypt']) .importKey('raw', key, { name: 'AES-GCM' }, false, ['encrypt'])
.then(keyForEncryption => .then(keyForEncryption =>
crypto.subtle crypto.subtle
.encrypt( .encrypt({ name: 'AES-GCM', iv, tagLength: PROFILE_TAG_LENGTH }, keyForEncryption, data)
{ name: 'AES-GCM', iv, tagLength: PROFILE_TAG_LENGTH },
keyForEncryption,
data
)
.then(ciphertext => { .then(ciphertext => {
const ivAndCiphertext = new Uint8Array( const ivAndCiphertext = new Uint8Array(PROFILE_IV_LENGTH + ciphertext.byteLength);
PROFILE_IV_LENGTH + ciphertext.byteLength
);
ivAndCiphertext.set(new Uint8Array(iv)); ivAndCiphertext.set(new Uint8Array(iv));
ivAndCiphertext.set( ivAndCiphertext.set(new Uint8Array(ciphertext), PROFILE_IV_LENGTH);
new Uint8Array(ciphertext),
PROFILE_IV_LENGTH
);
return ivAndCiphertext.buffer; return ivAndCiphertext.buffer;
}) })
); );
@ -166,10 +148,7 @@
return textsecure.crypto.encryptProfile(padded.buffer, key); return textsecure.crypto.encryptProfile(padded.buffer, key);
}, },
decryptProfileName(encryptedProfileName, key) { decryptProfileName(encryptedProfileName, key) {
const data = dcodeIO.ByteBuffer.wrap( const data = dcodeIO.ByteBuffer.wrap(encryptedProfileName, 'base64').toArrayBuffer();
encryptedProfileName,
'base64'
).toArrayBuffer();
return textsecure.crypto.decryptProfile(data, key).then(decrypted => { return textsecure.crypto.decryptProfile(data, key).then(decrypted => {
// unpad // unpad
const padded = new Uint8Array(decrypted); const padded = new Uint8Array(decrypted);

@ -44,8 +44,7 @@ function getStringable(thing) {
window.textsecure.utils = (() => { window.textsecure.utils = (() => {
const self = {}; const self = {};
self.unencodeNumber = number => number.split('.'); self.unencodeNumber = number => number.split('.');
self.isNumberSane = number => self.isNumberSane = number => number[0] === '+' && /^[0-9]+$/.test(number.substring(1));
number[0] === '+' && /^[0-9]+$/.test(number.substring(1));
/** ************************ /** ************************
*** JSON'ing Utilities *** *** JSON'ing Utilities ***

@ -27,11 +27,7 @@ interface CurveSync {
generateKeyPair(): KeyPair; generateKeyPair(): KeyPair;
createKeyPair(privKey: ArrayBuffer): KeyPair; createKeyPair(privKey: ArrayBuffer): KeyPair;
calculateAgreement(pubKey: ArrayBuffer, privKey: ArrayBuffer): ArrayBuffer; calculateAgreement(pubKey: ArrayBuffer, privKey: ArrayBuffer): ArrayBuffer;
verifySignature( verifySignature(pubKey: ArrayBuffer, msg: ArrayBuffer, sig: ArrayBuffer): void;
pubKey: ArrayBuffer,
msg: ArrayBuffer,
sig: ArrayBuffer
): void;
calculateSignature(privKey: ArrayBuffer, message: ArrayBuffer): ArrayBuffer; calculateSignature(privKey: ArrayBuffer, message: ArrayBuffer): ArrayBuffer;
validatePubKeyFormat(pubKey: ArrayBuffer): ArrayBuffer; validatePubKeyFormat(pubKey: ArrayBuffer): ArrayBuffer;
} }
@ -39,19 +35,9 @@ interface CurveSync {
interface CurveAsync { interface CurveAsync {
generateKeyPair(): Promise<KeyPair>; generateKeyPair(): Promise<KeyPair>;
createKeyPair(privKey: ArrayBuffer): Promise<KeyPair>; createKeyPair(privKey: ArrayBuffer): Promise<KeyPair>;
calculateAgreement( calculateAgreement(pubKey: ArrayBuffer, privKey: ArrayBuffer): Promise<ArrayBuffer>;
pubKey: ArrayBuffer, verifySignature(pubKey: ArrayBuffer, msg: ArrayBuffer, sig: ArrayBuffer): Promise<void>;
privKey: ArrayBuffer calculateSignature(privKey: ArrayBuffer, message: ArrayBuffer): Promise<ArrayBuffer>;
): Promise<ArrayBuffer>;
verifySignature(
pubKey: ArrayBuffer,
msg: ArrayBuffer,
sig: ArrayBuffer
): Promise<void>;
calculateSignature(
privKey: ArrayBuffer,
message: ArrayBuffer
): Promise<ArrayBuffer>;
validatePubKeyFormat(pubKey: ArrayBuffer): Promise<ArrayBuffer>; validatePubKeyFormat(pubKey: ArrayBuffer): Promise<ArrayBuffer>;
} }
@ -60,23 +46,10 @@ export interface CurveInterface extends CurveSync {
} }
export interface CryptoInterface { export interface CryptoInterface {
encrypt( encrypt(key: ArrayBuffer, data: ArrayBuffer, iv: ArrayBuffer): Promise<ArrayBuffer>;
key: ArrayBuffer, decrypt(key: ArrayBuffer, data: ArrayBuffer, iv: ArrayBuffer): Promise<ArrayBuffer>;
data: ArrayBuffer,
iv: ArrayBuffer
): Promise<ArrayBuffer>;
decrypt(
key: ArrayBuffer,
data: ArrayBuffer,
iv: ArrayBuffer
): Promise<ArrayBuffer>;
calculateMAC(key: ArrayBuffer, data: ArrayBuffer): Promise<ArrayBuffer>; calculateMAC(key: ArrayBuffer, data: ArrayBuffer): Promise<ArrayBuffer>;
verifyMAC( verifyMAC(data: ArrayBuffer, key: ArrayBuffer, mac: ArrayBuffer, length: number): Promise<void>;
data: ArrayBuffer,
key: ArrayBuffer,
mac: ArrayBuffer,
length: number
): Promise<void>;
getRandomBytes(size: number): ArrayBuffer; getRandomBytes(size: number): ArrayBuffer;
} }

@ -80,12 +80,8 @@ window.textsecure = window.textsecure || {};
textsecure.MessageReceiver = function MessageReceiverWrapper() { textsecure.MessageReceiver = function MessageReceiverWrapper() {
const messageReceiver = new MessageReceiver(); const messageReceiver = new MessageReceiver();
this.addEventListener = messageReceiver.addEventListener.bind( this.addEventListener = messageReceiver.addEventListener.bind(messageReceiver);
messageReceiver this.removeEventListener = messageReceiver.removeEventListener.bind(messageReceiver);
);
this.removeEventListener = messageReceiver.removeEventListener.bind(
messageReceiver
);
this.close = messageReceiver.close.bind(messageReceiver); this.close = messageReceiver.close.bind(messageReceiver);
this.stopProcessing = messageReceiver.stopProcessing.bind(messageReceiver); this.stopProcessing = messageReceiver.stopProcessing.bind(messageReceiver);
@ -97,9 +93,6 @@ textsecure.MessageReceiver.prototype = {
constructor: textsecure.MessageReceiver, constructor: textsecure.MessageReceiver,
}; };
textsecure.MessageReceiver.stringToArrayBuffer = textsecure.MessageReceiver.stringToArrayBuffer = MessageReceiver.stringToArrayBuffer;
MessageReceiver.stringToArrayBuffer; textsecure.MessageReceiver.arrayBufferToString = MessageReceiver.arrayBufferToString;
textsecure.MessageReceiver.arrayBufferToString = textsecure.MessageReceiver.arrayBufferToStringBase64 = MessageReceiver.arrayBufferToStringBase64;
MessageReceiver.arrayBufferToString;
textsecure.MessageReceiver.arrayBufferToStringBase64 =
MessageReceiver.arrayBufferToStringBase64;

@ -10,9 +10,9 @@
{ root: window.PROTO_ROOT, file: filename }, { root: window.PROTO_ROOT, file: filename },
(error, result) => { (error, result) => {
if (error) { if (error) {
const text = `Error loading protos from ${filename} (root: ${ const text = `Error loading protos from ${filename} (root: ${window.PROTO_ROOT}) ${
window.PROTO_ROOT error && error.stack ? error.stack : error
}) ${error && error.stack ? error.stack : error}`; }`;
window.log.error(text); window.log.error(text);
throw error; throw error;
} }

@ -33,8 +33,7 @@
}, },
}; };
window.textsecure.storage.put = (key, value) => window.textsecure.storage.put = (key, value) => textsecure.storage.impl.put(key, value);
textsecure.storage.impl.put(key, value);
window.textsecure.storage.get = (key, defaultValue) => window.textsecure.storage.get = (key, defaultValue) =>
textsecure.storage.impl.get(key, defaultValue); textsecure.storage.impl.get(key, defaultValue);
window.textsecure.storage.remove = key => textsecure.storage.impl.remove(key); window.textsecure.storage.remove = key => textsecure.storage.impl.remove(key);

@ -41,10 +41,7 @@
}, },
setLastProfileUpdateTimestamp(lastUpdateTimestamp) { setLastProfileUpdateTimestamp(lastUpdateTimestamp) {
textsecure.storage.put( textsecure.storage.put('last_profile_update_timestamp', lastUpdateTimestamp);
'last_profile_update_timestamp',
lastUpdateTimestamp
);
}, },
getDeviceId() { getDeviceId() {

@ -38,20 +38,11 @@
let nMod3; let nMod3;
let nMod4; let nMod4;
for ( for (let nUint24 = 0, nOutIdx = 0, nInIdx = 0; nInIdx < nInLen; nInIdx += 1) {
let nUint24 = 0, nOutIdx = 0, nInIdx = 0;
nInIdx < nInLen;
nInIdx += 1
) {
nMod4 = nInIdx & 3; nMod4 = nInIdx & 3;
nUint24 |= nUint24 |= StringView.b64ToUint6(sB64Enc.charCodeAt(nInIdx)) << (18 - 6 * nMod4);
StringView.b64ToUint6(sB64Enc.charCodeAt(nInIdx)) << (18 - 6 * nMod4);
if (nMod4 === 3 || nInLen - nInIdx === 1) { if (nMod4 === 3 || nInLen - nInIdx === 1) {
for ( for (nMod3 = 0; nMod3 < 3 && nOutIdx < nOutLen; nMod3 += 1, nOutIdx += 1) {
nMod3 = 0;
nMod3 < 3 && nOutIdx < nOutLen;
nMod3 += 1, nOutIdx += 1
) {
taBytes[nOutIdx] = (nUint24 >>> ((16 >>> nMod3) & 24)) & 255; taBytes[nOutIdx] = (nUint24 >>> ((16 >>> nMod3) & 24)) & 255;
} }
nUint24 = 0; nUint24 = 0;
@ -77,11 +68,7 @@
bytesToBase64(aBytes) { bytesToBase64(aBytes) {
let nMod3; let nMod3;
let sB64Enc = ''; let sB64Enc = '';
for ( for (let nLen = aBytes.length, nUint24 = 0, nIdx = 0; nIdx < nLen; nIdx += 1) {
let nLen = aBytes.length, nUint24 = 0, nIdx = 0;
nIdx < nLen;
nIdx += 1
) {
nMod3 = nIdx % 3; nMod3 = nIdx % 3;
if (nIdx > 0 && ((nIdx * 4) / 3) % 76 === 0) { if (nIdx > 0 && ((nIdx * 4) / 3) % 76 === 0) {
sB64Enc += '\r\n'; sB64Enc += '\r\n';
@ -102,16 +89,12 @@
arrayBufferToHex(aArrayBuffer) { arrayBufferToHex(aArrayBuffer) {
return Array.prototype.map return Array.prototype.map
.call(new Uint8Array(aArrayBuffer), x => .call(new Uint8Array(aArrayBuffer), x => `00${x.toString(16)}`.slice(-2))
`00${x.toString(16)}`.slice(-2)
)
.join(''); .join('');
}, },
hexToArrayBuffer(aString) { hexToArrayBuffer(aString) {
return new Uint8Array( return new Uint8Array(aString.match(/[\da-f]{2}/gi).map(h => parseInt(h, 16))).buffer;
aString.match(/[\da-f]{2}/gi).map(h => parseInt(h, 16))
).buffer;
}, },
}; };
})(); })();

@ -15,8 +15,7 @@
let complete = false; let complete = false;
let timer = setTimeout(() => { let timer = setTimeout(() => {
if (!complete) { if (!complete) {
const message = `${id || const message = `${id || ''} task did not complete in time. Calling stack: ${
''} task did not complete in time. Calling stack: ${
errorForStack.stack errorForStack.stack
}`; }`;

@ -8,70 +8,48 @@ describe('encrypting and decrypting profile data', () => {
const buffer = dcodeIO.ByteBuffer.wrap(name).toArrayBuffer(); const buffer = dcodeIO.ByteBuffer.wrap(name).toArrayBuffer();
const key = libsignal.crypto.getRandomBytes(32); const key = libsignal.crypto.getRandomBytes(32);
return textsecure.crypto return textsecure.crypto.encryptProfileName(buffer, key).then(encrypted => {
.encryptProfileName(buffer, key) assert(encrypted.byteLength === NAME_PADDED_LENGTH + 16 + 12);
.then(encrypted => { return textsecure.crypto.decryptProfileName(encrypted, key).then(decrypted => {
assert(encrypted.byteLength === NAME_PADDED_LENGTH + 16 + 12); assert.strictEqual(dcodeIO.ByteBuffer.wrap(decrypted).toString('utf8'), 'Alice');
return textsecure.crypto
.decryptProfileName(encrypted, key)
.then(decrypted => {
assert.strictEqual(
dcodeIO.ByteBuffer.wrap(decrypted).toString('utf8'),
'Alice'
);
});
}); });
});
}); });
it('works for empty string', () => { it('works for empty string', () => {
const name = dcodeIO.ByteBuffer.wrap('').toArrayBuffer(); const name = dcodeIO.ByteBuffer.wrap('').toArrayBuffer();
const key = libsignal.crypto.getRandomBytes(32); const key = libsignal.crypto.getRandomBytes(32);
return textsecure.crypto return textsecure.crypto.encryptProfileName(name.buffer, key).then(encrypted => {
.encryptProfileName(name.buffer, key) assert(encrypted.byteLength === NAME_PADDED_LENGTH + 16 + 12);
.then(encrypted => { return textsecure.crypto.decryptProfileName(encrypted, key).then(decrypted => {
assert(encrypted.byteLength === NAME_PADDED_LENGTH + 16 + 12); assert.strictEqual(decrypted.byteLength, 0);
return textsecure.crypto assert.strictEqual(dcodeIO.ByteBuffer.wrap(decrypted).toString('utf8'), '');
.decryptProfileName(encrypted, key)
.then(decrypted => {
assert.strictEqual(decrypted.byteLength, 0);
assert.strictEqual(
dcodeIO.ByteBuffer.wrap(decrypted).toString('utf8'),
''
);
});
}); });
});
}); });
}); });
describe('encrypting and decrypting profile avatars', () => { describe('encrypting and decrypting profile avatars', () => {
it('encrypts and decrypts', () => { it('encrypts and decrypts', () => {
const buffer = dcodeIO.ByteBuffer.wrap( const buffer = dcodeIO.ByteBuffer.wrap('This is an avatar').toArrayBuffer();
'This is an avatar'
).toArrayBuffer();
const key = libsignal.crypto.getRandomBytes(32); const key = libsignal.crypto.getRandomBytes(32);
return textsecure.crypto.encryptProfile(buffer, key).then(encrypted => { return textsecure.crypto.encryptProfile(buffer, key).then(encrypted => {
assert(encrypted.byteLength === buffer.byteLength + 16 + 12); assert(encrypted.byteLength === buffer.byteLength + 16 + 12);
return textsecure.crypto return textsecure.crypto.decryptProfile(encrypted, key).then(decrypted => {
.decryptProfile(encrypted, key) assertEqualArrayBuffers(buffer, decrypted);
.then(decrypted => { });
assertEqualArrayBuffers(buffer, decrypted);
});
}); });
}); });
it('throws when decrypting with the wrong key', () => { it('throws when decrypting with the wrong key', () => {
const buffer = dcodeIO.ByteBuffer.wrap( const buffer = dcodeIO.ByteBuffer.wrap('This is an avatar').toArrayBuffer();
'This is an avatar'
).toArrayBuffer();
const key = libsignal.crypto.getRandomBytes(32); const key = libsignal.crypto.getRandomBytes(32);
const badKey = libsignal.crypto.getRandomBytes(32); const badKey = libsignal.crypto.getRandomBytes(32);
return textsecure.crypto.encryptProfile(buffer, key).then(encrypted => { return textsecure.crypto.encryptProfile(buffer, key).then(encrypted => {
assert(encrypted.byteLength === buffer.byteLength + 16 + 12); assert(encrypted.byteLength === buffer.byteLength + 16 + 12);
return textsecure.crypto return textsecure.crypto.decryptProfile(encrypted, badKey).catch(error => {
.decryptProfile(encrypted, badKey) assert.strictEqual(error.name, 'ProfileDecryptError');
.catch(error => { });
assert.strictEqual(error.name, 'ProfileDecryptError');
});
}); });
}); });
}); });

@ -46,8 +46,7 @@ function getMainWindow() {
// Tray icon and related objects // Tray icon and related objects
let tray = null; let tray = null;
const startInTray = process.argv.some(arg => arg === '--start-in-tray'); const startInTray = process.argv.some(arg => arg === '--start-in-tray');
const usingTrayIcon = const usingTrayIcon = startInTray || process.argv.some(arg => arg === '--use-tray-icon');
startInTray || process.argv.some(arg => arg === '--use-tray-icon');
const config = require('./app/config'); const config = require('./app/config');
@ -56,8 +55,7 @@ const config = require('./app/config');
const userConfig = require('./app/user_config'); const userConfig = require('./app/user_config');
const passwordUtil = require('./ts/util/passwordUtils'); const passwordUtil = require('./ts/util/passwordUtils');
const importMode = const importMode = process.argv.some(arg => arg === '--import') || config.get('import');
process.argv.some(arg => arg === '--import') || config.get('import');
const development = config.environment === 'development'; const development = config.environment === 'development';
const appInstance = config.util.getEnv('NODE_APP_INSTANCE') || 0; const appInstance = config.util.getEnv('NODE_APP_INSTANCE') || 0;
@ -76,10 +74,7 @@ const sql = require('./app/sql');
const sqlChannels = require('./app/sql_channel'); const sqlChannels = require('./app/sql_channel');
const windowState = require('./app/window_state'); const windowState = require('./app/window_state');
const { createTemplate } = require('./app/menu'); const { createTemplate } = require('./app/menu');
const { const { installFileHandler, installWebHandler } = require('./app/protocol_filter');
installFileHandler,
installWebHandler,
} = require('./app/protocol_filter');
const { installPermissionsHandler } = require('./app/permissions'); const { installPermissionsHandler } = require('./app/permissions');
const _sodium = require('libsodium-wrappers'); const _sodium = require('libsodium-wrappers');
@ -218,10 +213,7 @@ function getWindowSize() {
const { minWidth, minHeight, defaultWidth, defaultHeight } = WINDOW_SIZE; const { minWidth, minHeight, defaultWidth, defaultHeight } = WINDOW_SIZE;
// Ensure that the screen can fit within the default size // Ensure that the screen can fit within the default size
const width = Math.min(defaultWidth, Math.max(minWidth, screenSize.width)); const width = Math.min(defaultWidth, Math.max(minWidth, screenSize.width));
const height = Math.min( const height = Math.min(defaultHeight, Math.max(minHeight, screenSize.height));
defaultHeight,
Math.max(minHeight, screenSize.height)
);
return { width, height, minWidth, minHeight }; return { width, height, minWidth, minHeight };
} }
@ -234,15 +226,12 @@ function isVisible(window, bounds) {
const BOUNDS_BUFFER = 100; const BOUNDS_BUFFER = 100;
// requiring BOUNDS_BUFFER pixels on the left or right side // requiring BOUNDS_BUFFER pixels on the left or right side
const rightSideClearOfLeftBound = const rightSideClearOfLeftBound = window.x + window.width >= boundsX + BOUNDS_BUFFER;
window.x + window.width >= boundsX + BOUNDS_BUFFER; const leftSideClearOfRightBound = window.x <= boundsX + boundsWidth - 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 // top can't be offscreen, and must show at least BOUNDS_BUFFER pixels at bottom
const topClearOfUpperBound = window.y >= boundsY; const topClearOfUpperBound = window.y >= boundsY;
const topClearOfLowerBound = const topClearOfLowerBound = window.y <= boundsY + boundsHeight - BOUNDS_BUFFER;
window.y <= boundsY + boundsHeight - BOUNDS_BUFFER;
return ( return (
rightSideClearOfLeftBound && rightSideClearOfLeftBound &&
@ -275,14 +264,7 @@ async function createWindow() {
}, },
icon: path.join(__dirname, 'images', 'session', 'session_icon_64.png'), icon: path.join(__dirname, 'images', 'session', 'session_icon_64.png'),
}, },
_.pick(windowConfig, [ _.pick(windowConfig, ['maximized', 'autoHideMenuBar', 'width', 'height', 'x', 'y'])
'maximized',
'autoHideMenuBar',
'width',
'height',
'x',
'y',
])
); );
if (!_.isNumber(windowOptions.width) || windowOptions.width < minWidth) { if (!_.isNumber(windowOptions.width) || windowOptions.width < minWidth) {
@ -315,10 +297,7 @@ async function createWindow() {
delete windowOptions.fullscreen; delete windowOptions.fullscreen;
} }
logger.info( logger.info('Initializing BrowserWindow config: %s', JSON.stringify(windowOptions));
'Initializing BrowserWindow config: %s',
JSON.stringify(windowOptions)
);
// Create the browser window. // Create the browser window.
mainWindow = new BrowserWindow(windowOptions); mainWindow = new BrowserWindow(windowOptions);
@ -355,10 +334,7 @@ async function createWindow() {
windowConfig.fullscreen = true; windowConfig.fullscreen = true;
} }
logger.info( logger.info('Updating BrowserWindow config: %s', JSON.stringify(windowConfig));
'Updating BrowserWindow config: %s',
JSON.stringify(windowConfig)
);
ephemeralConfig.set('window', windowConfig); ephemeralConfig.set('window', windowConfig);
} }
@ -377,13 +353,9 @@ async function createWindow() {
if (config.environment === 'test') { if (config.environment === 'test') {
mainWindow.loadURL(prepareURL([__dirname, 'test', 'index.html'])); mainWindow.loadURL(prepareURL([__dirname, 'test', 'index.html']));
} else if (config.environment === 'test-lib') { } else if (config.environment === 'test-lib') {
mainWindow.loadURL( mainWindow.loadURL(prepareURL([__dirname, 'libtextsecure', 'test', 'index.html']));
prepareURL([__dirname, 'libtextsecure', 'test', 'index.html'])
);
} else if (config.environment === 'test-loki') { } else if (config.environment === 'test-loki') {
mainWindow.loadURL( mainWindow.loadURL(prepareURL([__dirname, 'libloki', 'test', 'index.html']));
prepareURL([__dirname, 'libloki', 'test', 'index.html'])
);
} else if (config.environment.includes('test-integration')) { } else if (config.environment.includes('test-integration')) {
mainWindow.loadURL(prepareURL([__dirname, 'background_test.html'])); mainWindow.loadURL(prepareURL([__dirname, 'background_test.html']));
} else { } else {
@ -422,10 +394,7 @@ async function createWindow() {
// On Mac, or on other platforms when the tray icon is in use, the window // On Mac, or on other platforms when the tray icon is in use, the window
// should be only hidden, not closed, when the user clicks the close button // should be only hidden, not closed, when the user clicks the close button
if ( if (!windowState.shouldQuit() && (usingTrayIcon || process.platform === 'darwin')) {
!windowState.shouldQuit() &&
(usingTrayIcon || process.platform === 'darwin')
) {
// toggle the visibility of the show/hide tray icon menu entries // toggle the visibility of the show/hide tray icon menu entries
if (tray) { if (tray) {
tray.updateContextMenu(); tray.updateContextMenu();
@ -476,10 +445,7 @@ async function readyForUpdates() {
await updater.start(getMainWindow, userConfig, locale.messages, logger); await updater.start(getMainWindow, userConfig, locale.messages, logger);
} catch (error) { } catch (error) {
const log = logger || console; const log = logger || console;
log.error( log.error('Error starting update checks:', error && error.stack ? error.stack : error);
'Error starting update checks:',
error && error.stack ? error.stack : error
);
} }
} }
ipc.once('ready-for-updates', readyForUpdates); ipc.once('ready-for-updates', readyForUpdates);
@ -555,10 +521,7 @@ function showPasswordWindow() {
// On Mac, or on other platforms when the tray icon is in use, the window // On Mac, or on other platforms when the tray icon is in use, the window
// should be only hidden, not closed, when the user clicks the close button // should be only hidden, not closed, when the user clicks the close button
if ( if (!windowState.shouldQuit() && (usingTrayIcon || process.platform === 'darwin')) {
!windowState.shouldQuit() &&
(usingTrayIcon || process.platform === 'darwin')
) {
// toggle the visibility of the show/hide tray icon menu entries // toggle the visibility of the show/hide tray icon menu entries
if (tray) { if (tray) {
tray.updateContextMenu(); tray.updateContextMenu();
@ -717,9 +680,7 @@ app.on('ready', async () => {
function getDefaultSQLKey() { function getDefaultSQLKey() {
let key = userConfig.get('key'); let key = userConfig.get('key');
if (!key) { if (!key) {
console.log( console.log('key/initialize: Generating new encryption key, since we did not find it on disk');
'key/initialize: Generating new encryption key, since we did not find it on disk'
);
// https://www.zetetic.net/sqlcipher/sqlcipher-api/#key // https://www.zetetic.net/sqlcipher/sqlcipher-api/#key
key = crypto.randomBytes(32).toString('hex'); key = crypto.randomBytes(32).toString('hex');
userConfig.set('key', key); userConfig.set('key', key);
@ -754,9 +715,7 @@ async function showMainWindow(sqlKey, passwordAttempt = false) {
async function cleanupOrphanedAttachments() { async function cleanupOrphanedAttachments() {
const allAttachments = await attachments.getAllAttachments(userDataPath); const allAttachments = await attachments.getAllAttachments(userDataPath);
const orphanedAttachments = await sql.removeKnownAttachments( const orphanedAttachments = await sql.removeKnownAttachments(allAttachments);
allAttachments
);
await attachments.deleteAll({ await attachments.deleteAll({
userDataPath, userDataPath,
attachments: orphanedAttachments, attachments: orphanedAttachments,
@ -823,9 +782,7 @@ async function requestShutdown() {
// yet listening for these events), or if there are a whole lot of stacked-up tasks. // yet listening for these events), or if there are a whole lot of stacked-up tasks.
// Note: two minutes is also our timeout for SQL tasks in data.ts in the browser. // Note: two minutes is also our timeout for SQL tasks in data.ts in the browser.
setTimeout(() => { setTimeout(() => {
console.log( console.log('requestShutdown: Response never received; forcing shutdown.');
'requestShutdown: Response never received; forcing shutdown.'
);
resolve(); resolve();
}, 2 * 60 * 1000); }, 2 * 60 * 1000);
}); });
@ -833,10 +790,7 @@ async function requestShutdown() {
try { try {
await request; await request;
} catch (error) { } catch (error) {
console.log( console.log('requestShutdown error:', error && error.stack ? error.stack : error);
'requestShutdown error:',
error && error.stack ? error.stack : error
);
} }
} }
@ -954,8 +908,7 @@ ipc.on('update-tray-icon', (event, unreadCount) => {
// Password screen related IPC calls // Password screen related IPC calls
ipc.on('password-window-login', async (event, passPhrase) => { ipc.on('password-window-login', async (event, passPhrase) => {
const sendResponse = e => const sendResponse = e => event.sender.send('password-window-login-response', e);
event.sender.send('password-window-login-response', e);
try { try {
const passwordAttempt = true; const passwordAttempt = true;
@ -977,8 +930,7 @@ ipc.on('set-password', async (event, passPhrase, oldPhrase) => {
if (hash && !hashMatches) { if (hash && !hashMatches) {
const incorrectOldPassword = locale.messages.invalidOldPassword.message; const incorrectOldPassword = locale.messages.invalidOldPassword.message;
sendResponse( sendResponse(
incorrectOldPassword || incorrectOldPassword || 'Failed to set password: Old password provided is invalid'
'Failed to set password: Old password provided is invalid'
); );
return; return;
} }

@ -31,9 +31,7 @@ http
// Avoid https://en.wikipedia.org/wiki/Directory_traversal_attack // Avoid https://en.wikipedia.org/wiki/Directory_traversal_attack
// e.g curl --path-as-is http://localhost:9000/../fileInDanger.txt // e.g curl --path-as-is http://localhost:9000/../fileInDanger.txt
// by limiting the path to current directory only // by limiting the path to current directory only
const sanitizePath = path const sanitizePath = path.normalize(parsedUrl.pathname).replace(/^(\.\.[/\\])+/, '');
.normalize(parsedUrl.pathname)
.replace(/^(\.\.[/\\])+/, '');
let pathname = path.join(__dirname, sanitizePath); let pathname = path.join(__dirname, sanitizePath);
fs.exists(pathname, exist => { fs.exists(pathname, exist => {
if (!exist) { if (!exist) {

@ -24,9 +24,7 @@ window.getAppInstance = () => config.appInstance;
const electron = require('electron'); const electron = require('electron');
const ipc = electron.ipcRenderer; const ipc = electron.ipcRenderer;
const { const { SessionPasswordPrompt } = require('./ts/components/session/SessionPasswordPrompt');
SessionPasswordPrompt,
} = require('./ts/components/session/SessionPasswordPrompt');
window.Signal = { window.Signal = {
Components: { Components: {

@ -67,10 +67,7 @@ window.lokiFeatureFlags = {
padOutgoingAttachments: false, padOutgoingAttachments: false,
}; };
if ( if (typeof process.env.NODE_ENV === 'string' && process.env.NODE_ENV.includes('test-integration')) {
typeof process.env.NODE_ENV === 'string' &&
process.env.NODE_ENV.includes('test-integration')
) {
window.electronRequire = require; window.electronRequire = require;
// during test-integration, file server is started on localhost // during test-integration, file server is started on localhost
window.getDefaultFileServer = () => 'http://127.0.0.1:7070'; window.getDefaultFileServer = () => 'http://127.0.0.1:7070';
@ -100,8 +97,7 @@ window.CONSTANTS = new (function() {
this.LNS_MAX_LENGTH = 64; this.LNS_MAX_LENGTH = 64;
// Conforms to naming rules here // Conforms to naming rules here
// https://loki.network/2020/03/25/loki-name-system-the-facts/ // https://loki.network/2020/03/25/loki-name-system-the-facts/
this.LNS_REGEX = `^[a-zA-Z0-9_]([a-zA-Z0-9_-]{0,${this.LNS_MAX_LENGTH - this.LNS_REGEX = `^[a-zA-Z0-9_]([a-zA-Z0-9_-]{0,${this.LNS_MAX_LENGTH - 2}}[a-zA-Z0-9_]){0,1}$`;
2}}[a-zA-Z0-9_]){0,1}$`;
this.MIN_GUARD_COUNT = 2; this.MIN_GUARD_COUNT = 2;
this.DESIRED_GUARD_COUNT = 3; this.DESIRED_GUARD_COUNT = 3;
})(); })();
@ -185,11 +181,9 @@ window.showWindow = () => {
ipc.send('show-window'); ipc.send('show-window');
}; };
window.setAutoHideMenuBar = autoHide => window.setAutoHideMenuBar = autoHide => ipc.send('set-auto-hide-menu-bar', autoHide);
ipc.send('set-auto-hide-menu-bar', autoHide);
window.setMenuBarVisibility = visibility => window.setMenuBarVisibility = visibility => ipc.send('set-menu-bar-visibility', visibility);
ipc.send('set-menu-bar-visibility', visibility);
window.restart = () => { window.restart = () => {
window.log.info('restart'); window.log.info('restart');
@ -213,10 +207,7 @@ window.onUnblockNumber = async number => {
const conversation = window.getConversationController().get(number); const conversation = window.getConversationController().get(number);
await conversation.unblock(); await conversation.unblock();
} catch (e) { } catch (e) {
window.log.info( window.log.info('IPC on unblock: failed to fetch conversation for number: ', number);
'IPC on unblock: failed to fetch conversation for number: ',
number
);
} }
} }
}; };
@ -228,8 +219,7 @@ ipc.on('mediaPermissionsChanged', () => {
window.closeAbout = () => ipc.send('close-about'); window.closeAbout = () => ipc.send('close-about');
window.readyForUpdates = () => ipc.send('ready-for-updates'); window.readyForUpdates = () => ipc.send('ready-for-updates');
window.updateTrayIcon = unreadCount => window.updateTrayIcon = unreadCount => ipc.send('update-tray-icon', unreadCount);
ipc.send('update-tray-icon', unreadCount);
ipc.on('set-up-with-import', () => { ipc.on('set-up-with-import', () => {
Whisper.events.trigger('setupWithImport'); Whisper.events.trigger('setupWithImport');
@ -288,13 +278,11 @@ window.setSettingValue = (settingID, value) => {
}; };
window.getMediaPermissions = () => ipc.sendSync('get-media-permissions'); window.getMediaPermissions = () => ipc.sendSync('get-media-permissions');
window.setMediaPermissions = value => window.setMediaPermissions = value => ipc.send('set-media-permissions', !!value);
ipc.send('set-media-permissions', !!value);
// Auto update setting // Auto update setting
window.getAutoUpdateEnabled = () => ipc.sendSync('get-auto-update-setting'); window.getAutoUpdateEnabled = () => ipc.sendSync('get-auto-update-setting');
window.setAutoUpdateEnabled = value => window.setAutoUpdateEnabled = value => ipc.send('set-auto-update-setting', !!value);
ipc.send('set-auto-update-setting', !!value);
ipc.on('get-ready-for-shutdown', async () => { ipc.on('get-ready-for-shutdown', async () => {
const { shutdown } = window.Events || {}; const { shutdown } = window.Events || {};
@ -308,10 +296,7 @@ ipc.on('get-ready-for-shutdown', async () => {
await shutdown(); await shutdown();
ipc.send('now-ready-for-shutdown'); ipc.send('now-ready-for-shutdown');
} catch (error) { } catch (error) {
ipc.send( ipc.send('now-ready-for-shutdown', error && error.stack ? error.stack : error);
'now-ready-for-shutdown',
error && error.stack ? error.stack : error
);
} }
}); });
@ -411,8 +396,7 @@ window.models = require('./ts/models');
window.Signal = window.Signal || {}; window.Signal = window.Signal || {};
window.Signal.Data = require('./ts/data/data'); window.Signal.Data = require('./ts/data/data');
window.getMessageController = () => window.getMessageController = () => window.libsession.Messages.MessageController.getInstance();
window.libsession.Messages.MessageController.getInstance();
window.getConversationController = () => window.getConversationController = () =>
window.libsession.Conversations.ConversationController.getInstance(); window.libsession.Conversations.ConversationController.getInstance();
@ -422,9 +406,7 @@ window.Signal.Backup = require('./js/modules/backup');
window.Signal.Logs = require('./js/modules/logs'); window.Signal.Logs = require('./js/modules/logs');
window.addEventListener('contextmenu', e => { window.addEventListener('contextmenu', e => {
const editable = e.target.closest( const editable = e.target.closest('textarea, input, [contenteditable="true"]');
'textarea, input, [contenteditable="true"]'
);
const link = e.target.closest('a'); const link = e.target.closest('a');
const selection = Boolean(window.getSelection().toString()); const selection = Boolean(window.getSelection().toString());
if (!editable && !selection && !link) { if (!editable && !selection && !link) {
@ -438,9 +420,7 @@ window.NewSnodeAPI = require('./ts/session/snode_api/serviceNodeAPI');
window.SnodePool = require('./ts/session/snode_api/snodePool'); window.SnodePool = require('./ts/session/snode_api/snodePool');
if (process.env.USE_STUBBED_NETWORK) { if (process.env.USE_STUBBED_NETWORK) {
const { const { SwarmPollingStub } = require('./ts/session/snode_api/swarmPollingStub');
SwarmPollingStub,
} = require('./ts/session/snode_api/swarmPollingStub');
window.SwarmPolling = new SwarmPollingStub(); window.SwarmPolling = new SwarmPollingStub();
} else { } else {
const { SwarmPolling } = require('./ts/session/snode_api/swarmPolling'); const { SwarmPolling } = require('./ts/session/snode_api/swarmPolling');
@ -486,8 +466,6 @@ if (config.environment.includes('test-integration')) {
// Blocking // Blocking
const { const { BlockedNumberController } = require('./ts/util/blockedNumberController');
BlockedNumberController,
} = require('./ts/util/blockedNumberController');
window.BlockedNumberController = BlockedNumberController; window.BlockedNumberController = BlockedNumberController;

@ -356,8 +356,8 @@ $session-compose-margin: 20px;
.session-brand-logo { .session-brand-logo {
height: 180px; height: 180px;
filter: brightness(0) saturate(100%) invert(75%) sepia(84%) saturate(3272%) filter: brightness(0) saturate(100%) invert(75%) sepia(84%) saturate(3272%) hue-rotate(103deg)
hue-rotate(103deg) brightness(106%) contrast(103%); brightness(106%) contrast(103%);
} }
.session-text-logo { .session-text-logo {

@ -4,11 +4,7 @@ $color-loki-dark-gray: #323232;
$color-loki-extra-dark-gray: #101010; $color-loki-extra-dark-gray: #101010;
$color-loki-green: #3bd110; $color-loki-green: #3bd110;
$color-loki-green-dark: #32b10e; $color-loki-green-dark: #32b10e;
$color-loki-green-gradient: linear-gradient( $color-loki-green-gradient: linear-gradient(to right, rgb(120, 190, 32) 0%, rgb(0, 133, 34) 100%);
to right,
rgb(120, 190, 32) 0%,
rgb(0, 133, 34) 100%
);
$color-white: #ffffff; $color-white: #ffffff;
$color-gray-02: #f8f9f9; $color-gray-02: #f8f9f9;

@ -174,9 +174,7 @@ describe('app/logging', () => {
JSON.stringify({ time: '2018-01-04T19:17:02.014Z' }), JSON.stringify({ time: '2018-01-04T19:17:02.014Z' }),
JSON.stringify({ time: '2018-01-04T19:17:03.014Z' }), JSON.stringify({ time: '2018-01-04T19:17:03.014Z' }),
].join('\n'); ].join('\n');
const expected = [ const expected = [JSON.stringify({ time: '2018-01-04T19:17:03.014Z' })].join('\n');
JSON.stringify({ time: '2018-01-04T19:17:03.014Z' }),
].join('\n');
const target = path.join(basePath, 'log.log'); const target = path.join(basePath, 'log.log');
const files = [ const files = [
@ -267,10 +265,7 @@ describe('app/logging', () => {
}); });
}); });
it('returns sorted entries from all files', () => { it('returns sorted entries from all files', () => {
const first = [ const first = [JSON.stringify({ msg: 2, time: '2018-01-04T19:17:05.014Z' }), ''].join('\n');
JSON.stringify({ msg: 2, time: '2018-01-04T19:17:05.014Z' }),
'',
].join('\n');
const second = [ const second = [
JSON.stringify({ msg: 1, time: '2018-01-04T19:17:00.014Z' }), JSON.stringify({ msg: 1, time: '2018-01-04T19:17:00.014Z' }),
JSON.stringify({ msg: 3, time: '2018-01-04T19:18:00.014Z' }), JSON.stringify({ msg: 3, time: '2018-01-04T19:18:00.014Z' }),

@ -61,9 +61,7 @@ describe('SignalMenu', () => {
const { messages } = loadLocale({ appLocale, logger }); const { messages } = loadLocale({ appLocale, logger });
const actual = SignalMenu.createTemplate(options, messages); const actual = SignalMenu.createTemplate(options, messages);
const fixturePath = includeSetup const fixturePath = includeSetup ? fixtures.setup : fixtures.default;
? fixtures.setup
: fixtures.default;
// eslint-disable-next-line global-require, import/no-dynamic-require // eslint-disable-next-line global-require, import/no-dynamic-require
const fixture = require(fixturePath); const fixture = require(fixturePath);
assert.deepEqual(actual, fixture); assert.deepEqual(actual, fixture);

@ -5,10 +5,8 @@ const { _urlToPath } = require('../../app/protocol_filter');
describe('Protocol Filter', () => { describe('Protocol Filter', () => {
describe('_urlToPath', () => { describe('_urlToPath', () => {
it('returns proper file path for unix style file URI with hash', () => { it('returns proper file path for unix style file URI with hash', () => {
const path = const path = 'file:///Users/someone/Development/signal/electron/background.html#first-page';
'file:///Users/someone/Development/signal/electron/background.html#first-page'; const expected = '/Users/someone/Development/signal/electron/background.html';
const expected =
'/Users/someone/Development/signal/electron/background.html';
const actual = _urlToPath(path); const actual = _urlToPath(path);
expect(actual).to.equal(expected); expect(actual).to.equal(expected);
@ -17,8 +15,7 @@ describe('Protocol Filter', () => {
it('returns proper file path for unix style file URI with querystring', () => { it('returns proper file path for unix style file URI with querystring', () => {
const path = const path =
'file:///Users/someone/Development/signal/electron/background.html?name=Signal&locale=en&version=2.4.0'; 'file:///Users/someone/Development/signal/electron/background.html?name=Signal&locale=en&version=2.4.0';
const expected = const expected = '/Users/someone/Development/signal/electron/background.html';
'/Users/someone/Development/signal/electron/background.html';
const actual = _urlToPath(path); const actual = _urlToPath(path);
expect(actual).to.equal(expected); expect(actual).to.equal(expected);
@ -27,8 +24,7 @@ describe('Protocol Filter', () => {
it('returns proper file path for unix style file URI with hash and querystring', () => { it('returns proper file path for unix style file URI with hash and querystring', () => {
const path = const path =
'file:///Users/someone/Development/signal/electron/background.html#somewhere?name=Signal'; 'file:///Users/someone/Development/signal/electron/background.html#somewhere?name=Signal';
const expected = const expected = '/Users/someone/Development/signal/electron/background.html';
'/Users/someone/Development/signal/electron/background.html';
const actual = _urlToPath(path); const actual = _urlToPath(path);
expect(actual).to.equal(expected); expect(actual).to.equal(expected);
@ -45,20 +41,16 @@ describe('Protocol Filter', () => {
}); });
it('translates from URL format to filesystem format', () => { it('translates from URL format to filesystem format', () => {
const path = const path = 'file:///Users/someone/Development%20Files/signal/electron/background.html';
'file:///Users/someone/Development%20Files/signal/electron/background.html'; const expected = '/Users/someone/Development Files/signal/electron/background.html';
const expected =
'/Users/someone/Development Files/signal/electron/background.html';
const actual = _urlToPath(path); const actual = _urlToPath(path);
expect(actual).to.equal(expected); expect(actual).to.equal(expected);
}); });
it('translates from URL format to filesystem format', () => { it('translates from URL format to filesystem format', () => {
const path = const path = 'file:///Users/someone/Development%20Files/signal/electron/background.html';
'file:///Users/someone/Development%20Files/signal/electron/background.html'; const expected = '/Users/someone/Development Files/signal/electron/background.html';
const expected =
'/Users/someone/Development Files/signal/electron/background.html';
const actual = _urlToPath(path); const actual = _urlToPath(path);
expect(actual).to.equal(expected); expect(actual).to.equal(expected);

@ -27,8 +27,7 @@ describe('Backup', () => {
}); });
it('handles a file with a long extension', () => { it('handles a file with a long extension', () => {
const initial = const initial = '0123456789012345678901234567890123456789.01234567890123456789';
'0123456789012345678901234567890123456789.01234567890123456789';
const expected = '012345678901234567890123456789'; const expected = '012345678901234567890123456789';
assert.strictEqual(Signal.Backup._trimFileName(initial), expected); assert.strictEqual(Signal.Backup._trimFileName(initial), expected);
}); });
@ -51,11 +50,7 @@ describe('Backup', () => {
}; };
const expected = 'blah.jpg'; const expected = 'blah.jpg';
const actual = Signal.Backup._getExportAttachmentFileName( const actual = Signal.Backup._getExportAttachmentFileName(message, index, attachment);
message,
index,
attachment
);
assert.strictEqual(actual, expected); assert.strictEqual(actual, expected);
}); });
@ -69,11 +64,7 @@ describe('Backup', () => {
}; };
const expected = '123'; const expected = '123';
const actual = Signal.Backup._getExportAttachmentFileName( const actual = Signal.Backup._getExportAttachmentFileName(message, index, attachment);
message,
index,
attachment
);
assert.strictEqual(actual, expected); assert.strictEqual(actual, expected);
}); });
@ -88,11 +79,7 @@ describe('Backup', () => {
}; };
const expected = '123.jpeg'; const expected = '123.jpeg';
const actual = Signal.Backup._getExportAttachmentFileName( const actual = Signal.Backup._getExportAttachmentFileName(message, index, attachment);
message,
index,
attachment
);
assert.strictEqual(actual, expected); assert.strictEqual(actual, expected);
}); });
@ -107,11 +94,7 @@ describe('Backup', () => {
}; };
const expected = '123.something'; const expected = '123.something';
const actual = Signal.Backup._getExportAttachmentFileName( const actual = Signal.Backup._getExportAttachmentFileName(message, index, attachment);
message,
index,
attachment
);
assert.strictEqual(actual, expected); assert.strictEqual(actual, expected);
}); });
}); });
@ -128,11 +111,7 @@ describe('Backup', () => {
}; };
const expected = 'id-45'; const expected = 'id-45';
const actual = Signal.Backup._getAnonymousAttachmentFileName( const actual = Signal.Backup._getAnonymousAttachmentFileName(message, index, attachment);
message,
index,
attachment
);
assert.strictEqual(actual, expected); assert.strictEqual(actual, expected);
}); });
@ -147,11 +126,7 @@ describe('Backup', () => {
}; };
const expected = 'id-45-1'; const expected = 'id-45-1';
const actual = Signal.Backup._getAnonymousAttachmentFileName( const actual = Signal.Backup._getAnonymousAttachmentFileName(message, index, attachment);
message,
index,
attachment
);
assert.strictEqual(actual, expected); assert.strictEqual(actual, expected);
}); });
}); });
@ -164,10 +139,7 @@ describe('Backup', () => {
id: 'id', id: 'id',
}; };
const expected = '123 (012345678901234567890123456789 id)'; const expected = '123 (012345678901234567890123456789 id)';
assert.strictEqual( assert.strictEqual(Signal.Backup._getConversationDirName(conversation), expected);
Signal.Backup._getConversationDirName(conversation),
expected
);
}); });
it('uses just id if name is not available', () => { it('uses just id if name is not available', () => {
@ -176,10 +148,7 @@ describe('Backup', () => {
id: 'id', id: 'id',
}; };
const expected = '123 (id)'; const expected = '123 (id)';
assert.strictEqual( assert.strictEqual(Signal.Backup._getConversationDirName(conversation), expected);
Signal.Backup._getConversationDirName(conversation),
expected
);
}); });
it('uses inactive for missing active_at', () => { it('uses inactive for missing active_at', () => {
@ -188,10 +157,7 @@ describe('Backup', () => {
id: 'id', id: 'id',
}; };
const expected = 'inactive (name id)'; const expected = 'inactive (name id)';
assert.strictEqual( assert.strictEqual(Signal.Backup._getConversationDirName(conversation), expected);
Signal.Backup._getConversationDirName(conversation),
expected
);
}); });
}); });
@ -203,10 +169,7 @@ describe('Backup', () => {
type: 'private', type: 'private',
}; };
const expected = '123 (id)'; const expected = '123 (id)';
assert.strictEqual( assert.strictEqual(Signal.Backup._getConversationLoggingName(conversation), expected);
Signal.Backup._getConversationLoggingName(conversation),
expected
);
}); });
it('uses just id if name is not available', () => { it('uses just id if name is not available', () => {
@ -216,10 +179,7 @@ describe('Backup', () => {
type: 'group', type: 'group',
}; };
const expected = '123 ([REDACTED_GROUP]pId)'; const expected = '123 ([REDACTED_GROUP]pId)';
assert.strictEqual( assert.strictEqual(Signal.Backup._getConversationLoggingName(conversation), expected);
Signal.Backup._getConversationLoggingName(conversation),
expected
);
}); });
it('uses inactive for missing active_at', () => { it('uses inactive for missing active_at', () => {
@ -228,10 +188,7 @@ describe('Backup', () => {
type: 'private', type: 'private',
}; };
const expected = 'inactive (id)'; const expected = 'inactive (id)';
assert.strictEqual( assert.strictEqual(Signal.Backup._getConversationLoggingName(conversation), expected);
Signal.Backup._getConversationLoggingName(conversation),
expected
);
}); });
}); });
@ -245,17 +202,12 @@ describe('Backup', () => {
// because it always fails due to lstat permission error. // because it always fails due to lstat permission error.
// Don't know how to fix it so this is a temp work around. // Don't know how to fix it so this is a temp work around.
if (isWindows || !isWindows) { if (isWindows || !isWindows) {
console.log( console.log('Skipping exports then imports to produce the same data we started');
'Skipping exports then imports to produce the same data we started'
);
this.skip(); this.skip();
return; return;
} }
const { const { upgradeMessageSchema, loadAttachmentData } = window.Signal.Migrations;
upgradeMessageSchema,
loadAttachmentData,
} = window.Signal.Migrations;
const staticKeyPair = await libsignal.KeyHelper.generateIdentityKeyPair(); const staticKeyPair = await libsignal.KeyHelper.generateIdentityKeyPair();
const attachmentsPattern = path.join(attachmentsPath, '**'); const attachmentsPattern = path.join(attachmentsPath, '**');
@ -278,9 +230,7 @@ describe('Backup', () => {
jpg: getFixture('fixtures/koushik-chowdavarapu-105425-unsplash.jpg'), jpg: getFixture('fixtures/koushik-chowdavarapu-105425-unsplash.jpg'),
mp3: getFixture('fixtures/incompetech-com-Agnus-Dei-X.mp3'), mp3: getFixture('fixtures/incompetech-com-Agnus-Dei-X.mp3'),
txt: getFixture('fixtures/lorem-ipsum.txt'), txt: getFixture('fixtures/lorem-ipsum.txt'),
png: getFixture( png: getFixture('fixtures/freepngs-2cd43b_bed7d1327e88454487397574d87b64dc_mv2.png'),
'fixtures/freepngs-2cd43b_bed7d1327e88454487397574d87b64dc_mv2.png'
),
}; };
async function wrappedLoadAttachment(attachment) { async function wrappedLoadAttachment(attachment) {
@ -308,8 +258,7 @@ describe('Backup', () => {
Object.entries(object) Object.entries(object)
.filter(([, value]) => value === undefined) .filter(([, value]) => value === undefined)
.map(([name]) => name); .map(([name]) => name);
const omitUndefinedKeys = object => const omitUndefinedKeys = object => _.omit(object, getUndefinedKeys(object));
_.omit(object, getUndefinedKeys(object));
// We want to know which paths have two slashes, since that tells us which files // We want to know which paths have two slashes, since that tells us which files
// in the attachment fan-out are files vs. directories. // in the attachment fan-out are files vs. directories.
@ -340,14 +289,11 @@ describe('Backup', () => {
}); });
}; };
const quotedAttachments = const quotedAttachments = (message.quote && message.quote.attachments) || [];
(message.quote && message.quote.attachments) || [];
return Object.assign({}, message, { return Object.assign({}, message, {
quote: Object.assign({}, message.quote, { quote: Object.assign({}, message.quote, {
attachments: await Promise.all( attachments: await Promise.all(quotedAttachments.map(wrappedMapper)),
quotedAttachments.map(wrappedMapper)
),
}), }),
}); });
}; };
@ -369,9 +315,7 @@ describe('Backup', () => {
return contact && contact.avatar && contact.avatar.avatar return contact && contact.avatar && contact.avatar.avatar
? Object.assign({}, contact, { ? Object.assign({}, contact, {
avatar: Object.assign({}, contact.avatar, { avatar: Object.assign({}, contact.avatar, {
avatar: await wrappedLoadAttachment( avatar: await wrappedLoadAttachment(contact.avatar.avatar),
contact.avatar.avatar
),
}), }),
}) })
: contact; : contact;
@ -518,9 +462,7 @@ describe('Backup', () => {
console.log({ conversation }); console.log({ conversation });
await window.Signal.Data.saveConversation(conversation); await window.Signal.Data.saveConversation(conversation);
console.log( console.log('Backup test: Ensure that all attachments were saved to disk');
'Backup test: Ensure that all attachments were saved to disk'
);
const attachmentFiles = removeDirs(glob.sync(attachmentsPattern)); const attachmentFiles = removeDirs(glob.sync(attachmentsPattern));
console.log({ attachmentFiles }); console.log({ attachmentFiles });
assert.strictEqual(ATTACHMENT_COUNT, attachmentFiles.length); assert.strictEqual(ATTACHMENT_COUNT, attachmentFiles.length);
@ -537,9 +479,7 @@ describe('Backup', () => {
const messageZipExists = fse.existsSync(archivePath); const messageZipExists = fse.existsSync(archivePath);
assert.strictEqual(true, messageZipExists); assert.strictEqual(true, messageZipExists);
console.log( console.log('Backup test: Ensure that all attachments made it to backup dir');
'Backup test: Ensure that all attachments made it to backup dir'
);
const backupAttachmentPattern = path.join(backupDir, 'attachments/*'); const backupAttachmentPattern = path.join(backupDir, 'attachments/*');
const backupAttachments = glob.sync(backupAttachmentPattern); const backupAttachments = glob.sync(backupAttachmentPattern);
console.log({ backupAttachments }); console.log({ backupAttachments });
@ -569,10 +509,7 @@ describe('Backup', () => {
]; ];
const conversationFromDB = conversationCollection.at(0).attributes; const conversationFromDB = conversationCollection.at(0).attributes;
console.log({ conversationFromDB, conversation }); console.log({ conversationFromDB, conversation });
assert.deepEqual( assert.deepEqual(_.omit(conversationFromDB, ommited), _.omit(conversation, ommited));
_.omit(conversationFromDB, ommited),
_.omit(conversation, ommited)
);
console.log('Backup test: Check messages'); console.log('Backup test: Check messages');
const allMessages = await window.Signal.Data.getAllMessages(); const allMessages = await window.Signal.Data.getAllMessages();
@ -583,19 +520,13 @@ describe('Backup', () => {
assert.deepEqual(messageFromDB, expectedMessage); assert.deepEqual(messageFromDB, expectedMessage);
console.log('Backup test: ensure that all attachments were imported'); console.log('Backup test: ensure that all attachments were imported');
const recreatedAttachmentFiles = removeDirs( const recreatedAttachmentFiles = removeDirs(glob.sync(attachmentsPattern));
glob.sync(attachmentsPattern)
);
console.log({ recreatedAttachmentFiles }); console.log({ recreatedAttachmentFiles });
assert.strictEqual(ATTACHMENT_COUNT, recreatedAttachmentFiles.length); assert.strictEqual(ATTACHMENT_COUNT, recreatedAttachmentFiles.length);
assert.deepEqual(attachmentFiles, recreatedAttachmentFiles); assert.deepEqual(attachmentFiles, recreatedAttachmentFiles);
console.log( console.log('Backup test: Check that all attachments were successfully imported');
'Backup test: Check that all attachments were successfully imported' const messageWithAttachmentsFromDB = await loadAllFilesFromDisk(messageFromDB);
);
const messageWithAttachmentsFromDB = await loadAllFilesFromDisk(
messageFromDB
);
const expectedMessageWithAttachments = await loadAllFilesFromDisk( const expectedMessageWithAttachments = await loadAllFilesFromDisk(
omitUndefinedKeys(message) omitUndefinedKeys(message)
); );

@ -19,10 +19,7 @@ describe('Crypto', () => {
describe('symmetric encryption', () => { describe('symmetric encryption', () => {
it('roundtrips', async () => { it('roundtrips', async () => {
const message = 'this is my message'; const message = 'this is my message';
const plaintext = dcodeIO.ByteBuffer.wrap( const plaintext = dcodeIO.ByteBuffer.wrap(message, 'binary').toArrayBuffer();
message,
'binary'
).toArrayBuffer();
const key = textsecure.crypto.getRandomBytes(32); const key = textsecure.crypto.getRandomBytes(32);
const encrypted = await Signal.Crypto.encryptSymmetric(key, plaintext); const encrypted = await Signal.Crypto.encryptSymmetric(key, plaintext);
@ -36,10 +33,7 @@ describe('Crypto', () => {
it('roundtrip fails if nonce is modified', async () => { it('roundtrip fails if nonce is modified', async () => {
const message = 'this is my message'; const message = 'this is my message';
const plaintext = dcodeIO.ByteBuffer.wrap( const plaintext = dcodeIO.ByteBuffer.wrap(message, 'binary').toArrayBuffer();
message,
'binary'
).toArrayBuffer();
const key = textsecure.crypto.getRandomBytes(32); const key = textsecure.crypto.getRandomBytes(32);
const encrypted = await Signal.Crypto.encryptSymmetric(key, plaintext); const encrypted = await Signal.Crypto.encryptSymmetric(key, plaintext);
@ -61,10 +55,7 @@ describe('Crypto', () => {
it('roundtrip fails if mac is modified', async () => { it('roundtrip fails if mac is modified', async () => {
const message = 'this is my message'; const message = 'this is my message';
const plaintext = dcodeIO.ByteBuffer.wrap( const plaintext = dcodeIO.ByteBuffer.wrap(message, 'binary').toArrayBuffer();
message,
'binary'
).toArrayBuffer();
const key = textsecure.crypto.getRandomBytes(32); const key = textsecure.crypto.getRandomBytes(32);
const encrypted = await Signal.Crypto.encryptSymmetric(key, plaintext); const encrypted = await Signal.Crypto.encryptSymmetric(key, plaintext);
@ -86,10 +77,7 @@ describe('Crypto', () => {
it('roundtrip fails if encrypted contents are modified', async () => { it('roundtrip fails if encrypted contents are modified', async () => {
const message = 'this is my message'; const message = 'this is my message';
const plaintext = dcodeIO.ByteBuffer.wrap( const plaintext = dcodeIO.ByteBuffer.wrap(message, 'binary').toArrayBuffer();
message,
'binary'
).toArrayBuffer();
const key = textsecure.crypto.getRandomBytes(32); const key = textsecure.crypto.getRandomBytes(32);
const encrypted = await Signal.Crypto.encryptSymmetric(key, plaintext); const encrypted = await Signal.Crypto.encryptSymmetric(key, plaintext);
@ -115,8 +103,7 @@ describe('Crypto', () => {
const staticKeyPair = await libsignal.KeyHelper.generateIdentityKeyPair(); const staticKeyPair = await libsignal.KeyHelper.generateIdentityKeyPair();
const message = 'this is my message'; const message = 'this is my message';
const plaintext = Signal.Crypto.bytesFromString(message); const plaintext = Signal.Crypto.bytesFromString(message);
const path = const path = 'fa/facdf99c22945b1c9393345599a276f4b36ad7ccdc8c2467f5441b742c2d11fa';
'fa/facdf99c22945b1c9393345599a276f4b36ad7ccdc8c2467f5441b742c2d11fa';
const encrypted = await Signal.Crypto.encryptAttachment( const encrypted = await Signal.Crypto.encryptAttachment(
staticKeyPair.pubKey.slice(1), staticKeyPair.pubKey.slice(1),

@ -15,10 +15,7 @@ describe('Fixtures', () => {
await window await window
.getConversationController() .getConversationController()
.getOrCreateAndWait( .getOrCreateAndWait(window.libsession.Utils.UserUtils.getOurPubKeyStrFromCache(), 'private');
window.libsession.Utils.UserUtils.getOurPubKeyStrFromCache(),
'private'
);
}); });
it('renders', async () => { it('renders', async () => {

@ -14,10 +14,7 @@ describe('i18n', () => {
}); });
it('returns message with multiple substitutions', () => { it('returns message with multiple substitutions', () => {
const actual = i18n('theyChangedTheTimer', ['Someone', '5 minutes']); const actual = i18n('theyChangedTheTimer', ['Someone', '5 minutes']);
assert.equal( assert.equal(actual, 'Someone set the disappearing message timer to 5 minutes');
actual,
'Someone set the disappearing message timer to 5 minutes'
);
}); });
}); });

@ -15,8 +15,7 @@ describe('Privacy', () => {
const actual = Privacy.redactSessionID(text); const actual = Privacy.redactSessionID(text);
const expected = const expected =
'This is a log line with a session ID [REDACTED]\n' + 'This is a log line with a session ID [REDACTED]\n' + 'and another one [REDACTED]';
'and another one [REDACTED]';
assert.equal(actual, expected); assert.equal(actual, expected);
}); });
@ -33,8 +32,7 @@ describe('Privacy', () => {
describe('redactGroupIds', () => { describe('redactGroupIds', () => {
it('should redact all group IDs', () => { it('should redact all group IDs', () => {
const text = const text =
'This is a log line with two group IDs: group(123456789)\n' + 'This is a log line with two group IDs: group(123456789)\n' + 'and group(abcdefghij)';
'and group(abcdefghij)';
const actual = Privacy.redactGroupIds(text); const actual = Privacy.redactGroupIds(text);
const expected = const expected =
@ -45,8 +43,7 @@ describe('Privacy', () => {
it('should remove newlines from redacted group IDs', () => { it('should remove newlines from redacted group IDs', () => {
const text = const text =
'This is a log line with two group IDs: group(12345678\n9)\n' + 'This is a log line with two group IDs: group(12345678\n9)\n' + 'and group(abc\ndefghij)';
'and group(abc\ndefghij)';
const actual = Privacy.redactGroupIds(text); const actual = Privacy.redactGroupIds(text);
const expected = const expected =
@ -126,10 +123,8 @@ describe('Privacy', () => {
}); });
it('should redact stack traces with both forward and backslashes', () => { it('should redact stack traces with both forward and backslashes', () => {
const testPath = const testPath = 'C:/Users/Meow/AppData/Local/Programs/loki-messenger-beta';
'C:/Users/Meow/AppData/Local/Programs/loki-messenger-beta'; const modifiedTestPath = 'C:\\Users\\Meow\\AppData\\Local\\Programs\\loki-messenger-beta';
const modifiedTestPath =
'C:\\Users\\Meow\\AppData\\Local\\Programs\\loki-messenger-beta';
const text = const text =
'This is a log line with sensitive information:\n' + 'This is a log line with sensitive information:\n' +
`path1 ${testPath}\\main.js\n` + `path1 ${testPath}\\main.js\n` +
@ -148,8 +143,7 @@ describe('Privacy', () => {
}); });
it('should redact stack traces with escaped backslashes', () => { it('should redact stack traces with escaped backslashes', () => {
const testPath = const testPath = 'C:\\Users\\Meow\\AppData\\Local\\Programs\\loki-messenger-beta';
'C:\\Users\\Meow\\AppData\\Local\\Programs\\loki-messenger-beta';
const modifiedTestPath = const modifiedTestPath =
'C:\\\\Users\\\\Meow\\\\AppData\\\\Local\\\\Programs\\\\loki-messenger-beta'; 'C:\\\\Users\\\\Meow\\\\AppData\\\\Local\\\\Programs\\\\loki-messenger-beta';
const text = const text =

@ -3,9 +3,7 @@ require('mocha-testcheck').install();
const { assert } = require('chai'); const { assert } = require('chai');
const Attachment = require('../../../js/modules/types/attachment'); const Attachment = require('../../../js/modules/types/attachment');
const { const { stringToArrayBuffer } = require('../../../js/modules/string_to_array_buffer');
stringToArrayBuffer,
} = require('../../../js/modules/string_to_array_buffer');
describe('Attachment', () => { describe('Attachment', () => {
describe('replaceUnicodeOrderOverrides', () => { describe('replaceUnicodeOrderOverrides', () => {

@ -14,11 +14,7 @@ describe('Errors', () => {
const formattedError = Errors.toLogFormat(error); const formattedError = Errors.toLogFormat(error);
assert.include(formattedError, 'errors_test.js'); assert.include(formattedError, 'errors_test.js');
assert.include( assert.include(formattedError, APP_ROOT_PATH, 'Formatted stack has app path');
formattedError,
APP_ROOT_PATH,
'Formatted stack has app path'
);
}); });
it('should return error string representation if stack is missing', () => { it('should return error string representation if stack is missing', () => {

@ -3,9 +3,7 @@ const sinon = require('sinon');
const Message = require('../../../js/modules/types/message'); const Message = require('../../../js/modules/types/message');
const { SignalService } = require('../../../ts/protobuf'); const { SignalService } = require('../../../ts/protobuf');
const { const { stringToArrayBuffer } = require('../../../js/modules/string_to_array_buffer');
stringToArrayBuffer,
} = require('../../../js/modules/string_to_array_buffer');
describe('Message', () => { describe('Message', () => {
const logger = { const logger = {
@ -77,10 +75,7 @@ describe('Message', () => {
const writeExistingAttachmentData = attachment => { const writeExistingAttachmentData = attachment => {
assert.equal(attachment.path, 'ab/abcdefghi'); assert.equal(attachment.path, 'ab/abcdefghi');
assert.deepEqual( assert.deepEqual(attachment.data, stringToArrayBuffer('Its easy if you try'));
attachment.data,
stringToArrayBuffer('Its easy if you try')
);
}; };
const actual = await Message.createAttachmentDataWriter({ const actual = await Message.createAttachmentDataWriter({
@ -125,10 +120,7 @@ describe('Message', () => {
const writeExistingAttachmentData = attachment => { const writeExistingAttachmentData = attachment => {
assert.equal(attachment.path, 'ab/abcdefghi'); assert.equal(attachment.path, 'ab/abcdefghi');
assert.deepEqual( assert.deepEqual(attachment.data, stringToArrayBuffer('Its easy if you try'));
attachment.data,
stringToArrayBuffer('Its easy if you try')
);
}; };
const actual = await Message.createAttachmentDataWriter({ const actual = await Message.createAttachmentDataWriter({
@ -176,10 +168,7 @@ describe('Message', () => {
const writeExistingAttachmentData = attachment => { const writeExistingAttachmentData = attachment => {
assert.equal(attachment.path, 'ab/abcdefghi'); assert.equal(attachment.path, 'ab/abcdefghi');
assert.deepEqual( assert.deepEqual(attachment.data, stringToArrayBuffer('Its easy if you try'));
attachment.data,
stringToArrayBuffer('Its easy if you try')
);
}; };
const actual = await Message.createAttachmentDataWriter({ const actual = await Message.createAttachmentDataWriter({
@ -291,9 +280,7 @@ describe('Message', () => {
contact: [], contact: [],
}; };
const expectedAttachmentData = stringToArrayBuffer( const expectedAttachmentData = stringToArrayBuffer('Its easy if you try');
'Its easy if you try'
);
const context = { const context = {
writeNewAttachmentData: async attachmentData => { writeNewAttachmentData: async attachmentData => {
assert.deepEqual(attachmentData, expectedAttachmentData); assert.deepEqual(attachmentData, expectedAttachmentData);
@ -340,13 +327,11 @@ describe('Message', () => {
schemaVersion: 1, schemaVersion: 1,
}; };
const v1 = async message => const v1 = async message => Object.assign({}, message, { hasUpgradedToVersion1: true });
Object.assign({}, message, { hasUpgradedToVersion1: true });
const v2 = async () => { const v2 = async () => {
throw new Error('boom'); throw new Error('boom');
}; };
const v3 = async message => const v3 = async message => Object.assign({}, message, { hasUpgradedToVersion3: true });
Object.assign({}, message, { hasUpgradedToVersion3: true });
const toVersion1 = Message._withSchemaVersion({ const toVersion1 = Message._withSchemaVersion({
schemaVersion: 1, schemaVersion: 1,
@ -363,10 +348,7 @@ describe('Message', () => {
const context = { logger }; const context = { logger };
const upgradeSchema = async message => const upgradeSchema = async message =>
toVersion3( toVersion3(await toVersion2(await toVersion1(message, context), context), context);
await toVersion2(await toVersion1(message, context), context),
context
);
const actual = await upgradeSchema(input); const actual = await upgradeSchema(input);
assert.deepEqual(actual, expected); assert.deepEqual(actual, expected);
@ -421,10 +403,7 @@ describe('Message', () => {
const context = { logger }; const context = { logger };
// NOTE: We upgrade to 3 before 2, i.e. the pipeline should abort: // NOTE: We upgrade to 3 before 2, i.e. the pipeline should abort:
const upgradeSchema = async attachment => const upgradeSchema = async attachment =>
toVersion2( toVersion2(await toVersion3(await toVersion1(attachment, context), context), context);
await toVersion3(await toVersion1(attachment, context), context),
context
);
const actual = await upgradeSchema(input); const actual = await upgradeSchema(input);
assert.deepEqual(actual, expected); assert.deepEqual(actual, expected);
@ -436,8 +415,7 @@ describe('Message', () => {
it('should require a version number', () => { it('should require a version number', () => {
const toVersionX = () => {}; const toVersionX = () => {};
assert.throws( assert.throws(
() => () => Message._withSchemaVersion({ schemaVersion: toVersionX, upgrade: 2 }),
Message._withSchemaVersion({ schemaVersion: toVersionX, upgrade: 2 }),
'_withSchemaVersion: schemaVersion is invalid' '_withSchemaVersion: schemaVersion is invalid'
); );
}); });
@ -450,8 +428,7 @@ describe('Message', () => {
}); });
it('should skip upgrading if message has already been upgraded', async () => { it('should skip upgrading if message has already been upgraded', async () => {
const upgrade = async message => const upgrade = async message => Object.assign({}, message, { foo: true });
Object.assign({}, message, { foo: true });
const upgradeWithVersion = Message._withSchemaVersion({ const upgradeWithVersion = Message._withSchemaVersion({
schemaVersion: 3, schemaVersion: 3,
upgrade, upgrade,
@ -512,9 +489,7 @@ describe('Message', () => {
describe('_mapQuotedAttachments', () => { describe('_mapQuotedAttachments', () => {
it('handles message with no quote', async () => { it('handles message with no quote', async () => {
const upgradeAttachment = sinon const upgradeAttachment = sinon.stub().throws(new Error("Shouldn't be called"));
.stub()
.throws(new Error("Shouldn't be called"));
const upgradeVersion = Message._mapQuotedAttachments(upgradeAttachment); const upgradeVersion = Message._mapQuotedAttachments(upgradeAttachment);
const message = { const message = {
@ -525,9 +500,7 @@ describe('Message', () => {
}); });
it('handles quote with no attachments', async () => { it('handles quote with no attachments', async () => {
const upgradeAttachment = sinon const upgradeAttachment = sinon.stub().throws(new Error("Shouldn't be called"));
.stub()
.throws(new Error("Shouldn't be called"));
const upgradeVersion = Message._mapQuotedAttachments(upgradeAttachment); const upgradeVersion = Message._mapQuotedAttachments(upgradeAttachment);
const message = { const message = {
@ -548,9 +521,7 @@ describe('Message', () => {
}); });
it('handles zero attachments', async () => { it('handles zero attachments', async () => {
const upgradeAttachment = sinon const upgradeAttachment = sinon.stub().throws(new Error("Shouldn't be called"));
.stub()
.throws(new Error("Shouldn't be called"));
const upgradeVersion = Message._mapQuotedAttachments(upgradeAttachment); const upgradeVersion = Message._mapQuotedAttachments(upgradeAttachment);
const message = { const message = {
@ -565,9 +536,7 @@ describe('Message', () => {
}); });
it('handles attachments with no thumbnail', async () => { it('handles attachments with no thumbnail', async () => {
const upgradeAttachment = sinon const upgradeAttachment = sinon.stub().throws(new Error("Shouldn't be called"));
.stub()
.throws(new Error("Shouldn't be called"));
const upgradeVersion = Message._mapQuotedAttachments(upgradeAttachment); const upgradeVersion = Message._mapQuotedAttachments(upgradeAttachment);
const message = { const message = {
@ -587,9 +556,7 @@ describe('Message', () => {
}); });
it('does not eliminate thumbnails with missing data field', async () => { it('does not eliminate thumbnails with missing data field', async () => {
const upgradeAttachment = sinon const upgradeAttachment = sinon.stub().returns({ fileName: 'processed!' });
.stub()
.returns({ fileName: 'processed!' });
const upgradeVersion = Message._mapQuotedAttachments(upgradeAttachment); const upgradeVersion = Message._mapQuotedAttachments(upgradeAttachment);
const message = { const message = {
@ -665,9 +632,7 @@ describe('Message', () => {
describe('_mapContact', () => { describe('_mapContact', () => {
it('handles message with no contact field', async () => { it('handles message with no contact field', async () => {
const upgradeContact = sinon const upgradeContact = sinon.stub().throws(new Error("Shouldn't be called"));
.stub()
.throws(new Error("Shouldn't be called"));
const upgradeVersion = Message._mapContact(upgradeContact); const upgradeVersion = Message._mapContact(upgradeContact);
const message = { const message = {

@ -6,10 +6,7 @@ import { default as glob } from 'glob';
import fse from 'fs-extra'; import fse from 'fs-extra';
import toArrayBuffer from 'to-arraybuffer'; import toArrayBuffer from 'to-arraybuffer';
import { isArrayBuffer, isString, map } from 'lodash'; import { isArrayBuffer, isString, map } from 'lodash';
import { import { decryptAttachmentBuffer, encryptAttachmentBuffer } from '../../ts/types/Attachment';
decryptAttachmentBuffer,
encryptAttachmentBuffer,
} from '../../ts/types/Attachment';
const PATH = 'attachments.noindex'; const PATH = 'attachments.noindex';
@ -111,9 +108,7 @@ export const createWriterForExisting = (root: any) => {
} }
await fse.ensureFile(normalized); await fse.ensureFile(normalized);
const { encryptedBufferWithHeader } = await encryptAttachmentBuffer( const { encryptedBufferWithHeader } = await encryptAttachmentBuffer(arrayBuffer);
arrayBuffer
);
const buffer = Buffer.from(encryptedBufferWithHeader.buffer); const buffer = Buffer.from(encryptedBufferWithHeader.buffer);
await fse.writeFile(normalized, buffer); await fse.writeFile(normalized, buffer);
@ -175,9 +170,7 @@ export const getRelativePath = (name: any) => {
}; };
// createAbsolutePathGetter :: RootPath -> RelativePath -> AbsolutePath // createAbsolutePathGetter :: RootPath -> RelativePath -> AbsolutePath
export const createAbsolutePathGetter = (rootPath: string) => ( export const createAbsolutePathGetter = (rootPath: string) => (relativePath: string) => {
relativePath: string
) => {
const absolutePath = path.join(rootPath, relativePath); const absolutePath = path.join(rootPath, relativePath);
const normalized = path.normalize(absolutePath); const normalized = path.normalize(absolutePath);
if (!normalized.startsWith(rootPath)) { if (!normalized.startsWith(rootPath)) {

@ -47,13 +47,7 @@ const NoImage = (props: {
const { memberAvatars, size } = props; const { memberAvatars, size } = props;
// if no image but we have conversations set for the group, renders group members avatars // if no image but we have conversations set for the group, renders group members avatars
if (memberAvatars) { if (memberAvatars) {
return ( return <ClosedGroupAvatar size={size} memberAvatars={memberAvatars} i18n={window.i18n} />;
<ClosedGroupAvatar
size={size}
memberAvatars={memberAvatars}
i18n={window.i18n}
/>
);
} }
return <Identicon {...props} />; return <Identicon {...props} />;
@ -87,10 +81,7 @@ export const Avatar = (props: Props) => {
const { urlToLoad } = useEncryptedFileFetch(avatarPath || '', ''); const { urlToLoad } = useEncryptedFileFetch(avatarPath || '', '');
const handleImageError = () => { const handleImageError = () => {
window.log.warn( window.log.warn('Avatar: Image failed to load; failing over to placeholder', urlToLoad);
'Avatar: Image failed to load; failing over to placeholder',
urlToLoad
);
setImageBroken(true); setImageBroken(true);
}; };

@ -11,10 +11,7 @@ type Props = {
const sha512FromPubkey = async (pubkey: string): Promise<string> => { const sha512FromPubkey = async (pubkey: string): Promise<string> => {
// tslint:disable-next-line: await-promise // tslint:disable-next-line: await-promise
const buf = await crypto.subtle.digest( const buf = await crypto.subtle.digest('SHA-512', new TextEncoder().encode(pubkey));
'SHA-512',
new TextEncoder().encode(pubkey)
);
// tslint:disable: prefer-template restrict-plus-operands // tslint:disable: prefer-template restrict-plus-operands
return Array.prototype.map return Array.prototype.map

@ -24,9 +24,7 @@ export class ClosedGroupAvatar extends React.PureComponent<Props> {
case AvatarSize.HUGE: case AvatarSize.HUGE:
return AvatarSize.XL; return AvatarSize.XL;
default: default:
throw new Error( throw new Error(`Invalid size request for closed group avatar: ${size}`);
`Invalid size request for closed group avatar: ${size}`
);
} }
} }

@ -6,11 +6,7 @@ import * as GoogleChrome from '../util/GoogleChrome';
import { AttachmentType } from '../types/Attachment'; import { AttachmentType } from '../types/Attachment';
import { SessionInput } from './session/SessionInput'; import { SessionInput } from './session/SessionInput';
import { import { SessionButton, SessionButtonColor, SessionButtonType } from './session/SessionButton';
SessionButton,
SessionButtonColor,
SessionButtonType,
} from './session/SessionButton';
import { darkTheme, lightTheme } from '../state/ducks/SessionTheme'; import { darkTheme, lightTheme } from '../state/ducks/SessionTheme';
interface Props { interface Props {
@ -87,14 +83,8 @@ export class CaptionEditor extends React.Component<Props, State> {
return ( return (
<div role="dialog" className="module-caption-editor"> <div role="dialog" className="module-caption-editor">
<div <div role="button" onClick={onClose} className="module-caption-editor__close-button" />
role="button" <div className="module-caption-editor__media-container">{this.renderObject()}</div>
onClick={onClose}
className="module-caption-editor__close-button"
/>
<div className="module-caption-editor__media-container">
{this.renderObject()}
</div>
<div className="module-caption-editor__bottom-bar"> <div className="module-caption-editor__bottom-bar">
<div className="module-caption-editor__input-container"> <div className="module-caption-editor__input-container">
<SessionInput <SessionInput

@ -23,12 +23,7 @@ export class ContactListItem extends React.Component<Props> {
const userName = name || profileName || phoneNumber; const userName = name || profileName || phoneNumber;
return ( return (
<Avatar <Avatar avatarPath={avatarPath} name={userName} size={AvatarSize.S} pubkey={phoneNumber} />
avatarPath={avatarPath}
name={userName}
size={AvatarSize.S}
pubkey={phoneNumber}
/>
); );
} }
@ -42,11 +37,7 @@ export class ContactListItem extends React.Component<Props> {
!isMe && profileName && !name ? ( !isMe && profileName && !name ? (
<span className="module-contact-list-item__text__profile-name"> <span className="module-contact-list-item__text__profile-name">
~ ~
<Emojify <Emojify text={profileName} i18n={i18n} key={`emojify-list-item-${phoneNumber}`} />
text={profileName}
i18n={i18n}
key={`emojify-list-item-${phoneNumber}`}
/>
</span> </span>
) : null; ) : null;

@ -49,10 +49,7 @@ type PropsHousekeeping = {
type Props = ConversationListItemProps & PropsHousekeeping; type Props = ConversationListItemProps & PropsHousekeeping;
const Portal = ({ children }: { children: any }) => { const Portal = ({ children }: { children: any }) => {
return createPortal( return createPortal(children, document.querySelector('.inbox.index') as Element);
children,
document.querySelector('.inbox.index') as Element
);
}; };
class ConversationListItem extends React.PureComponent<Props> { class ConversationListItem extends React.PureComponent<Props> {
@ -61,13 +58,7 @@ class ConversationListItem extends React.PureComponent<Props> {
} }
public renderAvatar() { public renderAvatar() {
const { const { avatarPath, name, phoneNumber, profileName, memberAvatars } = this.props;
avatarPath,
name,
phoneNumber,
profileName,
memberAvatars,
} = this.props;
const userName = name || profileName || phoneNumber; const userName = name || profileName || phoneNumber;
@ -91,11 +82,7 @@ class ConversationListItem extends React.PureComponent<Props> {
let unreadCountDiv = null; let unreadCountDiv = null;
if (unreadCount > 0) { if (unreadCount > 0) {
atSymbol = mentionedUs ? <p className="at-symbol">@</p> : null; atSymbol = mentionedUs ? <p className="at-symbol">@</p> : null;
unreadCountDiv = ( unreadCountDiv = <p className="module-conversation-list-item__unread-count">{unreadCount}</p>;
<p className="module-conversation-list-item__unread-count">
{unreadCount}
</p>
);
} }
return ( return (
@ -103,9 +90,7 @@ class ConversationListItem extends React.PureComponent<Props> {
<div <div
className={classNames( className={classNames(
'module-conversation-list-item__header__name', 'module-conversation-list-item__header__name',
unreadCount > 0 unreadCount > 0 ? 'module-conversation-list-item__header__name--with-unread' : null
? 'module-conversation-list-item__header__name--with-unread'
: null
)} )}
> >
{this.renderUser()} {this.renderUser()}
@ -116,9 +101,7 @@ class ConversationListItem extends React.PureComponent<Props> {
<div <div
className={classNames( className={classNames(
'module-conversation-list-item__header__date', 'module-conversation-list-item__header__date',
unreadCount > 0 unreadCount > 0 ? 'module-conversation-list-item__header__date--has-unread' : null
? 'module-conversation-list-item__header__date--has-unread'
: null
)} )}
> >
{ {
@ -152,9 +135,7 @@ class ConversationListItem extends React.PureComponent<Props> {
<div <div
className={classNames( className={classNames(
'module-conversation-list-item__message__text', 'module-conversation-list-item__message__text',
unreadCount > 0 unreadCount > 0 ? 'module-conversation-list-item__message__text--has-unread' : null
? 'module-conversation-list-item__message__text--has-unread'
: null
)} )}
> >
{isTyping ? ( {isTyping ? (
@ -212,12 +193,8 @@ class ConversationListItem extends React.PureComponent<Props> {
style={style} style={style}
className={classNames( className={classNames(
'module-conversation-list-item', 'module-conversation-list-item',
unreadCount > 0 unreadCount > 0 ? 'module-conversation-list-item--has-unread' : null,
? 'module-conversation-list-item--has-unread' unreadCount > 0 && mentionedUs ? 'module-conversation-list-item--mentioned-us' : null,
: null,
unreadCount > 0 && mentionedUs
? 'module-conversation-list-item--mentioned-us'
: null,
isSelected ? 'module-conversation-list-item--is-selected' : null, isSelected ? 'module-conversation-list-item--is-selected' : null,
isBlocked ? 'module-conversation-list-item--is-blocked' : null isBlocked ? 'module-conversation-list-item--is-blocked' : null
)} )}

@ -4,17 +4,9 @@ import { QRCode } from 'react-qr-svg';
import { Avatar, AvatarSize } from './Avatar'; import { Avatar, AvatarSize } from './Avatar';
import { import { SessionButton, SessionButtonColor, SessionButtonType } from './session/SessionButton';
SessionButton,
SessionButtonColor, import { SessionIconButton, SessionIconSize, SessionIconType } from './session/icon';
SessionButtonType,
} from './session/SessionButton';
import {
SessionIconButton,
SessionIconSize,
SessionIconType,
} from './session/icon';
import { SessionModal } from './session/SessionModal'; import { SessionModal } from './session/SessionModal';
import { PillDivider } from './session/PillDivider'; import { PillDivider } from './session/PillDivider';
import { ToastUtils, UserUtils } from '../session/utils'; import { ToastUtils, UserUtils } from '../session/utils';
@ -104,14 +96,7 @@ export class EditProfileDialog extends React.Component<Props, State> {
<div className="session-id-section"> <div className="session-id-section">
<PillDivider text={window.i18n('yourSessionID')} /> <PillDivider text={window.i18n('yourSessionID')} />
<p <p className={classNames('text-selectable', 'session-id-section-display')}>{sessionID}</p>
className={classNames(
'text-selectable',
'session-id-section-display'
)}
>
{sessionID}
</p>
<div className="spacer-lg" /> <div className="spacer-lg" />
<SessionSpinner loading={this.state.loading} /> <SessionSpinner loading={this.state.loading} />
@ -149,11 +134,7 @@ export class EditProfileDialog extends React.Component<Props, State> {
<div className="avatar-center"> <div className="avatar-center">
<div className="avatar-center-inner"> <div className="avatar-center-inner">
{this.renderAvatar()} {this.renderAvatar()}
<div <div className="image-upload-section" role="button" onClick={this.fireInputEvent} />
className="image-upload-section"
role="button"
onClick={this.fireInputEvent}
/>
<input <input
type="file" type="file"
ref={this.inputEl} ref={this.inputEl}
@ -237,12 +218,7 @@ export class EditProfileDialog extends React.Component<Props, State> {
return ( return (
<div className="qr-image"> <div className="qr-image">
<QRCode <QRCode value={sessionID} bgColor={bgColor} fgColor={fgColor} level="L" />
value={sessionID}
bgColor={bgColor}
fgColor={fgColor}
level="L"
/>
</div> </div>
); );
} }
@ -261,14 +237,7 @@ export class EditProfileDialog extends React.Component<Props, State> {
const { pubkey } = this.props; const { pubkey } = this.props;
const userName = profileName || pubkey; const userName = profileName || pubkey;
return ( return <Avatar avatarPath={avatar} name={userName} size={AvatarSize.XL} pubkey={pubkey} />;
<Avatar
avatarPath={avatar}
name={userName}
size={AvatarSize.XL}
pubkey={pubkey}
/>
);
} }
private onNameEdited(event: any) { private onNameEdited(event: any) {

@ -22,9 +22,7 @@ export class Intl extends React.Component<Props> {
if (!components || !components.length || components.length <= index) { if (!components || !components.length || components.length <= index) {
// tslint:disable-next-line no-console // tslint:disable-next-line no-console
console.log( console.log(`Error: Intl missing provided components for id ${id}, index ${index}`);
`Error: Intl missing provided components for id ${id}, index ${index}`
);
return; return;
} }

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

Loading…
Cancel
Save