Merge pull request #1576 from Bilb/open-group-v2

Open group v2
This commit is contained in:
Audric Ackermann 2021-05-04 14:01:02 +10:00 committed by GitHub
commit 4fffb8c93e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
384 changed files with 8079 additions and 10060 deletions

View file

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

View file

@ -5,10 +5,7 @@ on:
push:
branches:
- master
- development
- clearnet
- github-actions
- react-refactor
jobs:
build:
@ -30,15 +27,24 @@ jobs:
- name: Install node
uses: actions/setup-node@v1
with:
node-version: 10.13.0
node-version: 10.19.0
- name: Chocolatey Install Action
if: runner.os == 'Windows'
uses: crazy-max/ghaction-chocolatey@v1.4.2
with:
args: install python2 visualcpp-build-tools -y
- name: Add msbuild to PATH
uses: microsoft/setup-msbuild@v1.0.2
if: runner.os == 'Windows'
- name: Setup node for windows
if: runner.os == 'Windows'
run: |
npm install --global --production windows-build-tools@4.0.0
npm install --global node-gyp@latest
npm config set python python2.7
npm config set msvs_version 2015
npm config set msvs_version 2017
- name: Install yarn
run: npm install yarn --no-save

View file

@ -4,10 +4,7 @@ name: Session Test
on:
pull_request:
branches:
- development
- clearnet
- github-actions
- react-refactor
jobs:
build:
@ -28,38 +25,41 @@ jobs:
- name: Pull git submodules
run: git submodule update --init
# file server dependencies are not needed for now
# - name: Install file server dependency
# run: |
# cd session-file-server
# yarn install;
# cd -
- name: Install node
uses: actions/setup-node@v1
with:
node-version: 10.13.0
node-version: 10.19.0
- name: Chocolatey Install Action
if: runner.os == 'Windows'
uses: crazy-max/ghaction-chocolatey@v1.4.2
with:
args: install python2 visualcpp-build-tools -y
#Not having this will break the windows build because the PATH won't be set by msbuild.
- name: Add msbuild to PATH
uses: microsoft/setup-msbuild@v1.0.2
if: runner.os == 'Windows'
- name: Setup node for windows
if: runner.os == 'Windows'
run: |
npm install --global --production windows-build-tools@4.0.0
npm install --global node-gyp@latest
npm config set python python2.7
npm config set msvs_version 2015
npm config set msvs_version 2017
- uses: actions/cache@v2
id: yarn-cache
if: runner.os != 'Windows'
with:
path: '**/node_modules'
key: ${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }}
- name: Install Dependencies #skipped if step before set variable to true
- name: Install Dependencies #skipped if step before set variable to true
if: |
steps.yarn-cache.outputs.cache-hit != 'true'
run: yarn install --frozen-lockfile --network-timeout 600000
- name: Generate and concat files
run: yarn generate

View file

@ -26,15 +26,24 @@ jobs:
- name: Install node
uses: actions/setup-node@v1
with:
node-version: 10.13.0
node-version: 10.19.0
- name: Chocolatey Install Action
if: runner.os == 'Windows'
uses: crazy-max/ghaction-chocolatey@v1.4.2
with:
args: install python2 visualcpp-build-tools -y
- name: Add msbuild to PATH
uses: microsoft/setup-msbuild@v1.0.2
if: runner.os == 'Windows'
- name: Setup node for windows
if: runner.os == 'Windows'
run: |
npm install --global --production windows-build-tools@4.0.0
npm install --global node-gyp@latest
npm config set python python2.7
npm config set msvs_version 2015
npm config set msvs_version 2017
- name: Install yarn
run: npm install yarn --no-save

2
.nvmrc
View file

@ -1 +1 @@
10.13.0
10.19.0

View file

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

View file

@ -47,7 +47,7 @@ base64 -i certificate.p12 -o encoded.txt
### Node version
You will need node `10.13.0`.
You will need node `10.19.0`.
This can be done by using [nvm](https://github.com/nvm-sh/nvm) and running `nvm use` or you can install it manually.
### Prerequisites

View file

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

View file

@ -1683,6 +1683,22 @@
"description": "Shown in the settings page as the heading for the blocked user settings",
"androidKey": "preferences_app_protection__blocked_contacts"
},
"unbanUser": {
"message": "Unban User",
"description": "Unban user from open group by public key."
},
"unbanUserConfirm": {
"message": "Are you sure you want to unban user?",
"description": "Message shown when confirming user unban."
},
"userUnbanned": {
"message": "User unbanned successfully",
"description": "Toast on succesful user unban."
},
"userUnbanFailed": {
"message": "Unban failed!",
"description": "Toast on unsuccesful user unban."
},
"banUser": {
"message": "Ban User",
"description": "Ban user from open group by public key."
@ -2223,5 +2239,8 @@
},
"errorHappenedWhileRemovingModeratorDesc": {
"message": "An error happened while removing this user from the moderator list."
},
"orJoinOneOfThese": {
"message": "Or join one of these..."
}
}

View file

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

View file

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

View file

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

View file

@ -13,13 +13,7 @@ function normalizeLocaleName(locale) {
function getLocaleMessages(locale) {
const onDiskLocale = locale.replace('-', '_');
const targetFile = path.join(
__dirname,
'..',
'_locales',
onDiskLocale,
'messages.json'
);
const targetFile = path.join(__dirname, '..', '_locales', onDiskLocale, 'messages.json');
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
messages = _.merge(english, messages);
} catch (e) {
logger.error(
`Problem loading messages for locale ${localeName} ${e.stack}`
);
logger.error(`Problem loading messages for locale ${localeName} ${e.stack}`);
logger.error('Falling back to en locale');
localeName = 'en';

View file

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

View file

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

View file

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

View file

@ -7,15 +7,7 @@ const { redactAll } = require('../js/modules/privacy');
const { remove: removeUserConfig } = require('./user_config');
const pify = require('pify');
const {
map,
isString,
fromPairs,
forEach,
last,
isEmpty,
isObject,
} = require('lodash');
const { map, isString, fromPairs, forEach, last, isEmpty, isObject } = require('lodash');
// To get long stack traces
// https://github.com/mapbox/node-sqlite3/wiki/API#sqlite3verbose
@ -57,7 +49,8 @@ module.exports = {
updateConversation,
removeConversation,
getAllConversations,
getAllPublicConversations,
getAllOpenGroupV1Conversations,
getAllOpenGroupV2Conversations,
getPubkeysInPublicConversation,
getAllConversationIds,
getAllGroupsInvolvingId,
@ -79,6 +72,7 @@ module.exports = {
getUnreadByConversation,
getUnreadCountByConversation,
getMessageBySender,
getMessageBySenderAndServerId,
getMessageIdsFromServerIds,
getMessageById,
getAllMessages,
@ -119,6 +113,13 @@ module.exports = {
addClosedGroupEncryptionKeyPair,
isKeyPairAlreadySaved,
removeAllClosedGroupEncryptionKeyPairs,
// open group v2
getV2OpenGroupRoom,
saveV2OpenGroupRoom,
getAllV2OpenGroupRooms,
getV2OpenGroupRoomByRoomId,
removeV2OpenGroupRoom,
};
function objectToJSON(data) {
@ -759,8 +760,11 @@ const LOKI_SCHEMA_VERSIONS = [
updateToLokiSchemaVersion9,
updateToLokiSchemaVersion10,
updateToLokiSchemaVersion11,
updateToLokiSchemaVersion12,
];
const SERVERS_TOKEN_TABLE = 'servers';
async function updateToLokiSchemaVersion1(currentVersion, instance) {
if (currentVersion >= 1) {
return;
@ -773,7 +777,7 @@ async function updateToLokiSchemaVersion1(currentVersion, instance) {
ADD COLUMN serverId INTEGER;`
);
await instance.run(
`CREATE TABLE servers(
`CREATE TABLE ${SERVERS_TOKEN_TABLE}(
serverUrl STRING PRIMARY KEY ASC,
token TEXT
);`
@ -1061,6 +1065,35 @@ async function updateToLokiSchemaVersion11(currentVersion, instance) {
console.log('updateToLokiSchemaVersion11: success!');
}
const OPEN_GROUP_ROOMS_V2_TABLE = 'openGroupRoomsV2';
async function updateToLokiSchemaVersion12(currentVersion, instance) {
if (currentVersion >= 12) {
return;
}
console.log('updateToLokiSchemaVersion12: starting...');
await instance.run('BEGIN TRANSACTION;');
await instance.run(
`CREATE TABLE ${OPEN_GROUP_ROOMS_V2_TABLE} (
serverUrl TEXT NOT NULL,
roomId TEXT NOT NULL,
conversationId TEXT,
json TEXT,
PRIMARY KEY (serverUrl, roomId)
);`
);
await instance.run(
`INSERT INTO loki_schema (
version
) values (
12
);`
);
await instance.run('COMMIT TRANSACTION;');
console.log('updateToLokiSchemaVersion12: success!');
}
async function updateLokiSchema(instance) {
const result = await instance.get(
"SELECT name FROM sqlite_master WHERE type = 'table' AND name='loki_schema';"
@ -1074,11 +1107,7 @@ async function updateLokiSchema(instance) {
`Current loki schema version: ${lokiSchemaVersion};`,
`Most recent schema version: ${LOKI_SCHEMA_VERSIONS.length};`
);
for (
let index = 0, max = LOKI_SCHEMA_VERSIONS.length;
index < max;
index += 1
) {
for (let index = 0, max = LOKI_SCHEMA_VERSIONS.length; index < max; index += 1) {
const runSchemaUpdate = LOKI_SCHEMA_VERSIONS[index];
// Yes, we really want to do this asynchronously, in order
@ -1088,9 +1117,7 @@ async function updateLokiSchema(instance) {
}
async function getLokiSchemaVersion(instance) {
const result = await instance.get(
'SELECT MAX(version) as version FROM loki_schema;'
);
const result = await instance.get('SELECT MAX(version) as version FROM loki_schema;');
if (!result || !result.version) {
return 0;
}
@ -1180,10 +1207,7 @@ async function initialize({ configDir, key, messages, passwordAttempt }) {
}
console.log('Database startup error:', error.stack);
const buttonIndex = dialog.showMessageBox({
buttons: [
messages.copyErrorAndQuit.message,
messages.clearAllData.message,
],
buttons: [messages.copyErrorAndQuit.message, messages.clearAllData.message],
defaultId: 0,
detail: redactAll(error.stack),
message: messages.databaseError.message,
@ -1192,9 +1216,7 @@ async function initialize({ configDir, key, messages, passwordAttempt }) {
});
if (buttonIndex === 0) {
clipboard.writeText(
`Database startup error:\n\n${redactAll(error.stack)}`
);
clipboard.writeText(`Database startup error:\n\n${redactAll(error.stack)}`);
} else {
await close();
await removeDB();
@ -1350,12 +1372,9 @@ async function createOrUpdate(table, data, instance) {
}
async function getById(table, id, instance) {
const row = await (db || instance).get(
`SELECT * FROM ${table} WHERE id = $id;`,
{
$id: id,
}
);
const row = await (db || instance).get(`SELECT * FROM ${table} WHERE id = $id;`, {
$id: id,
});
if (!row) {
return null;
@ -1375,10 +1394,7 @@ async function removeById(table, id) {
}
// Our node interface doesn't seem to allow you to replace one single ? with an array
await db.run(
`DELETE FROM ${table} WHERE id IN ( ${id.map(() => '?').join(', ')} );`,
id
);
await db.run(`DELETE FROM ${table} WHERE id IN ( ${id.map(() => '?').join(', ')} );`, id);
}
async function removeAllFromTable(table) {
@ -1388,12 +1404,9 @@ async function removeAllFromTable(table) {
// Conversations
async function getSwarmNodesForPubkey(pubkey) {
const row = await db.get(
`SELECT * FROM ${NODES_FOR_PUBKEY_TABLE} WHERE pubkey = $pubkey;`,
{
$pubkey: pubkey,
}
);
const row = await db.get(`SELECT * FROM ${NODES_FOR_PUBKEY_TABLE} WHERE pubkey = $pubkey;`, {
$pubkey: pubkey,
});
if (!row) {
return [];
@ -1424,9 +1437,7 @@ async function getConversationCount() {
const row = await db.get(`SELECT count(*) from ${CONVERSATIONS_TABLE};`);
if (!row) {
throw new Error(
`getConversationCount: Unable to get count of ${CONVERSATIONS_TABLE}`
);
throw new Error(`getConversationCount: Unable to get count of ${CONVERSATIONS_TABLE}`);
}
return row['count(*)'];
@ -1524,23 +1535,22 @@ async function removeConversation(id) {
// Our node interface doesn't seem to allow you to replace one single ? with an array
await db.run(
`DELETE FROM ${CONVERSATIONS_TABLE} WHERE id IN ( ${id
.map(() => '?')
.join(', ')} );`,
`DELETE FROM ${CONVERSATIONS_TABLE} WHERE id IN ( ${id.map(() => '?').join(', ')} );`,
id
);
}
// open groups v1 only
async function savePublicServerToken(data) {
const { serverUrl, token } = data;
await db.run(
`INSERT OR REPLACE INTO servers (
serverUrl,
token
) values (
$serverUrl,
$token
)`,
`INSERT OR REPLACE INTO ${SERVERS_TOKEN_TABLE} (
serverUrl,
token
) values (
$serverUrl,
$token
)`,
{
$serverUrl: serverUrl,
$token: token,
@ -1548,13 +1558,11 @@ async function savePublicServerToken(data) {
);
}
// open groups v1 only
async function getPublicServerTokenByServerUrl(serverUrl) {
const row = await db.get(
'SELECT * FROM servers WHERE serverUrl = $serverUrl;',
{
$serverUrl: serverUrl,
}
);
const row = await db.get(`SELECT * FROM ${SERVERS_TOKEN_TABLE} WHERE serverUrl = $serverUrl;`, {
$serverUrl: serverUrl,
});
if (!row) {
return null;
@ -1564,12 +1572,9 @@ async function getPublicServerTokenByServerUrl(serverUrl) {
}
async function getConversationById(id) {
const row = await db.get(
`SELECT * FROM ${CONVERSATIONS_TABLE} WHERE id = $id;`,
{
$id: id,
}
);
const row = await db.get(`SELECT * FROM ${CONVERSATIONS_TABLE} WHERE id = $id;`, {
$id: id,
});
if (!row) {
return null;
@ -1579,24 +1584,34 @@ async function getConversationById(id) {
}
async function getAllConversations() {
const rows = await db.all(
`SELECT json FROM ${CONVERSATIONS_TABLE} ORDER BY id ASC;`
);
const rows = await db.all(`SELECT json FROM ${CONVERSATIONS_TABLE} ORDER BY id ASC;`);
return map(rows, row => jsonToObject(row.json));
}
async function getAllConversationIds() {
const rows = await db.all(
`SELECT id FROM ${CONVERSATIONS_TABLE} ORDER BY id ASC;`
);
const rows = await db.all(`SELECT id FROM ${CONVERSATIONS_TABLE} ORDER BY id ASC;`);
return map(rows, row => row.id);
}
async function getAllPublicConversations() {
async function getAllOpenGroupV1Conversations() {
const rows = await db.all(
`SELECT json FROM ${CONVERSATIONS_TABLE} WHERE
type = 'group' AND
id LIKE 'publicChat:%'
id LIKE 'publicChat:1@%'
ORDER BY id ASC;`
);
return map(rows, row => jsonToObject(row.json));
}
async function getAllOpenGroupV2Conversations() {
// first _ matches all opengroupv1,
// second _ force a second char to be there, so it can only be opengroupv2 convos
const rows = await db.all(
`SELECT json FROM ${CONVERSATIONS_TABLE} WHERE
type = 'group' AND
id LIKE 'publicChat:__%@%'
ORDER BY id ASC;`
);
@ -1674,11 +1689,7 @@ async function searchMessages(query, { limit } = {}) {
}));
}
async function searchMessagesInConversation(
query,
conversationId,
{ limit } = {}
) {
async function searchMessagesInConversation(query, conversationId, { limit } = {}) {
const rows = await db.all(
`SELECT
messages.json,
@ -1707,9 +1718,7 @@ async function getMessageCount() {
const row = await db.get(`SELECT count(*) from ${MESSAGES_TABLE};`);
if (!row) {
throw new Error(
`getMessageCount: Unable to get count of ${MESSAGES_TABLE}`
);
throw new Error(`getMessageCount: Unable to get count of ${MESSAGES_TABLE}`);
}
return row['count(*)'];
@ -1921,9 +1930,7 @@ async function removeMessage(id) {
// Our node interface doesn't seem to allow you to replace one single ? with an array
await db.run(
`DELETE FROM ${MESSAGES_TABLE} WHERE id IN ( ${id
.map(() => '?')
.join(', ')} );`,
`DELETE FROM ${MESSAGES_TABLE} WHERE id IN ( ${id.map(() => '?').join(', ')} );`,
id
);
}
@ -1934,9 +1941,7 @@ async function getMessageIdsFromServerIds(serverIds, conversationId) {
}
// Sanitize the input as we're going to use it directly in the query
const validIds = serverIds
.map(id => Number(id))
.filter(n => !Number.isNaN(n));
const validIds = serverIds.map(id => Number(id)).filter(n => !Number.isNaN(n));
/*
Sqlite3 doesn't have a good way to have `IN` query with another query.
@ -1968,21 +1973,17 @@ async function getMessageById(id) {
}
async function getAllMessages() {
const rows = await db.all(
`SELECT json FROM ${MESSAGES_TABLE} ORDER BY id ASC;`
);
const rows = await db.all(`SELECT json FROM ${MESSAGES_TABLE} ORDER BY id ASC;`);
return map(rows, row => jsonToObject(row.json));
}
async function getAllMessageIds() {
const rows = await db.all(
`SELECT id FROM ${MESSAGES_TABLE} ORDER BY id ASC;`
);
const rows = await db.all(`SELECT id FROM ${MESSAGES_TABLE} ORDER BY id ASC;`);
return map(rows, row => row.id);
}
// eslint-disable-next-line camelcase
async function getMessageBySender({ source, sourceDevice, sent_at }) {
async function getMessageBySender({ source, sourceDevice, sentAt }) {
const rows = await db.all(
`SELECT json FROM ${MESSAGES_TABLE} WHERE
source = $source AND
@ -1991,7 +1992,21 @@ async function getMessageBySender({ source, sourceDevice, sent_at }) {
{
$source: source,
$sourceDevice: sourceDevice,
$sent_at: sent_at,
$sent_at: sentAt,
}
);
return map(rows, row => jsonToObject(row.json));
}
async function getMessageBySenderAndServerId({ source, serverId }) {
const rows = await db.all(
`SELECT json FROM ${MESSAGES_TABLE} WHERE
source = $source AND
serverId = $serverId;`,
{
$source: source,
$serverId: serverId,
}
);
@ -2074,13 +2089,10 @@ async function getMessagesBySentAt(sentAt) {
}
async function getLastHashBySnode(convoId, snode) {
const row = await db.get(
'SELECT * FROM lastHashes WHERE snode = $snode AND id = $id;',
{
$snode: snode,
$id: convoId,
}
);
const row = await db.get('SELECT * FROM lastHashes WHERE snode = $snode AND id = $id;', {
$snode: snode,
$id: convoId,
});
if (!row) {
return null;
@ -2091,9 +2103,7 @@ async function getLastHashBySnode(convoId, snode) {
async function getSeenMessagesByHashList(hashes) {
const rows = await db.all(
`SELECT * FROM seenMessages WHERE hash IN ( ${hashes
.map(() => '?')
.join(', ')} );`,
`SELECT * FROM seenMessages WHERE hash IN ( ${hashes.map(() => '?').join(', ')} );`,
hashes
);
@ -2183,13 +2193,7 @@ async function updateUnprocessedAttempts(id, attempts) {
});
}
async function updateUnprocessedWithData(id, data = {}) {
const {
source,
sourceDevice,
serverTimestamp,
decrypted,
senderIdentity,
} = data;
const { source, sourceDevice, serverTimestamp, decrypted, senderIdentity } = data;
await db.run(
`UPDATE unprocessed SET
@ -2229,9 +2233,7 @@ async function getUnprocessedCount() {
}
async function getAllUnprocessed() {
const rows = await db.all(
'SELECT * FROM unprocessed ORDER BY timestamp ASC;'
);
const rows = await db.all('SELECT * FROM unprocessed ORDER BY timestamp ASC;');
return rows;
}
@ -2247,10 +2249,7 @@ async function removeUnprocessed(id) {
}
// Our node interface doesn't seem to allow you to replace one single ? with an array
await db.run(
`DELETE FROM unprocessed WHERE id IN ( ${id.map(() => '?').join(', ')} );`,
id
);
await db.run(`DELETE FROM unprocessed WHERE id IN ( ${id.map(() => '?').join(', ')} );`, id);
}
async function removeAllUnprocessed() {
@ -2277,9 +2276,7 @@ async function getNextAttachmentDownloadJobs(limit, options = {}) {
async function saveAttachmentDownloadJob(job) {
const { id, pending, timestamp } = job;
if (!id) {
throw new Error(
'saveAttachmentDownloadJob: Provided job did not have a truthy id'
);
throw new Error('saveAttachmentDownloadJob: Provided job did not have a truthy id');
}
await db.run(
@ -2303,18 +2300,13 @@ async function saveAttachmentDownloadJob(job) {
);
}
async function setAttachmentDownloadJobPending(id, pending) {
await db.run(
'UPDATE attachment_downloads SET pending = $pending WHERE id = $id;',
{
$id: id,
$pending: pending,
}
);
await db.run('UPDATE attachment_downloads SET pending = $pending WHERE id = $id;', {
$id: id,
$pending: pending,
});
}
async function resetAttachmentDownloadPending() {
await db.run(
'UPDATE attachment_downloads SET pending = 0 WHERE pending != 0;'
);
await db.run('UPDATE attachment_downloads SET pending = 0 WHERE pending != 0;');
}
async function removeAttachmentDownloadJob(id) {
return removeById(ATTACHMENT_DOWNLOADS_TABLE, id);
@ -2338,7 +2330,7 @@ async function removeAll() {
db.run('DELETE FROM unprocessed;'),
db.run('DELETE FROM contactPreKeys;'),
db.run('DELETE FROM contactSignedPreKeys;'),
db.run('DELETE FROM servers;'),
db.run(`DELETE FROM ${SERVERS_TOKEN_TABLE};`),
db.run('DELETE FROM lastHashes;'),
db.run(`DELETE FROM ${SENDER_KEYS_TABLE};`),
db.run(`DELETE FROM ${NODES_FOR_PUBKEY_TABLE};`),
@ -2359,10 +2351,7 @@ async function removeAllConversations() {
await removeAllFromTable(CONVERSATIONS_TABLE);
}
async function getMessagesWithVisualMediaAttachments(
conversationId,
{ limit }
) {
async function getMessagesWithVisualMediaAttachments(conversationId, { limit }) {
const rows = await db.all(
`SELECT json FROM ${MESSAGES_TABLE} WHERE
conversationId = $conversationId AND
@ -2466,9 +2455,7 @@ async function removeKnownAttachments(allAttachments) {
const chunkSize = 50;
const total = await getMessageCount();
console.log(
`removeKnownAttachments: About to iterate through ${total} messages`
);
console.log(`removeKnownAttachments: About to iterate through ${total} messages`);
let count = 0;
let complete = false;
@ -2585,28 +2572,19 @@ async function removePrefixFromGroupConversations(instance) {
);
// We have another conversation with the same future name.
// We decided to keep only the conversation with the higher number of messages
const countMessagesOld = await getMessagesCountByConversation(
instance,
oldId,
{ limit: Number.MAX_VALUE }
);
const countMessagesNew = await getMessagesCountByConversation(
instance,
newId,
{ limit: Number.MAX_VALUE }
);
const countMessagesOld = await getMessagesCountByConversation(instance, oldId, {
limit: Number.MAX_VALUE,
});
const countMessagesNew = await getMessagesCountByConversation(instance, newId, {
limit: Number.MAX_VALUE,
});
console.log(
`countMessagesOld: ${countMessagesOld}, countMessagesNew: ${countMessagesNew}`
);
console.log(`countMessagesOld: ${countMessagesOld}, countMessagesNew: ${countMessagesNew}`);
const deleteId = countMessagesOld > countMessagesNew ? newId : oldId;
await instance.run(
`DELETE FROM ${CONVERSATIONS_TABLE} WHERE id = $id;`,
{
$id: deleteId,
}
);
await instance.run(`DELETE FROM ${CONVERSATIONS_TABLE} WHERE id = $id;`, {
$id: deleteId,
});
}
const morphedObject = {
@ -2662,8 +2640,7 @@ function remove05PrefixFromStringIfNeeded(str) {
async function updateExistingClosedGroupToClosedGroup(instance) {
// the migration is called only once, so all current groups not being open groups are v1 closed group.
const allClosedGroupV1 =
(await getAllClosedGroupConversations(instance)) || [];
const allClosedGroupV1 = (await getAllClosedGroupConversations(instance)) || [];
await Promise.all(
allClosedGroupV1.map(async groupV1 => {
@ -2710,9 +2687,7 @@ async function getAllEncryptionKeyPairsForGroup(groupPublicKey) {
}
async function getAllEncryptionKeyPairsForGroupRaw(groupPublicKey) {
const pubkeyAsString = groupPublicKey.key
? groupPublicKey.key
: groupPublicKey;
const pubkeyAsString = groupPublicKey.key ? groupPublicKey.key : groupPublicKey;
const rows = await db.all(
`SELECT * FROM ${CLOSED_GROUP_V2_KEY_PAIRS_TABLE} WHERE groupPublicKey = $groupPublicKey ORDER BY timestamp ASC;`,
{
@ -2731,11 +2706,7 @@ async function getLatestClosedGroupEncryptionKeyPair(groupPublicKey) {
return rows[rows.length - 1];
}
async function addClosedGroupEncryptionKeyPair(
groupPublicKey,
keypair,
instance
) {
async function addClosedGroupEncryptionKeyPair(groupPublicKey, keypair, instance) {
const timestamp = Date.now();
await (db || instance).run(
@ -2762,9 +2733,7 @@ async function isKeyPairAlreadySaved(
) {
const allKeyPairs = await getAllEncryptionKeyPairsForGroup(groupPublicKey);
return (allKeyPairs || []).some(
k =>
newKeyPairInHex.publicHex === k.publicHex &&
newKeyPairInHex.privateHex === k.privateHex
k => newKeyPairInHex.publicHex === k.publicHex && newKeyPairInHex.privateHex === k.privateHex
);
}
@ -2776,3 +2745,72 @@ async function removeAllClosedGroupEncryptionKeyPairs(groupPublicKey) {
}
);
}
/**
* Related to Opengroup V2
*/
async function getAllV2OpenGroupRooms() {
const rows = await db.all(`SELECT json FROM ${OPEN_GROUP_ROOMS_V2_TABLE};`);
return map(rows, row => jsonToObject(row.json));
}
async function getV2OpenGroupRoom(conversationId) {
const row = await db.get(
`SELECT * FROM ${OPEN_GROUP_ROOMS_V2_TABLE} WHERE conversationId = $conversationId;`,
{
$conversationId: conversationId,
}
);
if (!row) {
return null;
}
return jsonToObject(row.json);
}
async function getV2OpenGroupRoomByRoomId(serverUrl, roomId) {
const row = await db.get(
`SELECT * FROM ${OPEN_GROUP_ROOMS_V2_TABLE} WHERE serverUrl = $serverUrl AND roomId = $roomId;`,
{
$serverUrl: serverUrl,
$roomId: roomId,
}
);
if (!row) {
return null;
}
return jsonToObject(row.json);
}
async function saveV2OpenGroupRoom(opengroupsv2Room) {
const { serverUrl, roomId, conversationId } = opengroupsv2Room;
await db.run(
`INSERT OR REPLACE INTO ${OPEN_GROUP_ROOMS_V2_TABLE} (
serverUrl,
roomId,
conversationId,
json
) values (
$serverUrl,
$roomId,
$conversationId,
$json
)`,
{
$serverUrl: serverUrl,
$roomId: roomId,
$conversationId: conversationId,
$json: objectToJSON(opengroupsv2Room),
}
);
}
async function removeV2OpenGroupRoom(conversationId) {
await db.run(`DELETE FROM ${OPEN_GROUP_ROOMS_V2_TABLE} WHERE conversationId = $conversationId`, {
$conversationId: conversationId,
});
}

View file

@ -24,18 +24,14 @@ function initialize() {
try {
const fn = sql[callName];
if (!fn) {
throw new Error(
`sql channel: ${callName} is not an available function`
);
throw new Error(`sql channel: ${callName} is not an available function`);
}
const result = await fn(...args);
event.sender.send(`${SQL_CHANNEL_KEY}-done`, jobId, null, result);
} catch (error) {
const errorForDisplay = error && error.stack ? error.stack : error;
console.log(
`sql channel error with call ${callName}: ${errorForDisplay}`
);
console.log(`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
if (!process.env.NODE_ENV.includes('test-integration')) {
event.sender.send(`${SQL_CHANNEL_KEY}-done`, jobId, errorForDisplay);

View file

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

View file

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

View file

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

View file

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

View file

@ -3,7 +3,6 @@
"localUrl": "localhost.loki",
"cdnUrl": "random.snode",
"contentProxyUrl": "",
"defaultPoWDifficulty": "1",
"seedNodeList": [
{
"ip_url": "http://116.203.53.213:4433/",
@ -22,7 +21,6 @@
"openDevTools": false,
"buildExpiration": 0,
"commitHash": "",
"certificateAuthority": "-----BEGIN CERTIFICATE-----\nMIID7zCCAtegAwIBAgIJAIm6LatK5PNiMA0GCSqGSIb3DQEBBQUAMIGNMQswCQYD\nVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5j\naXNjbzEdMBsGA1UECgwUT3BlbiBXaGlzcGVyIFN5c3RlbXMxHTAbBgNVBAsMFE9w\nZW4gV2hpc3BlciBTeXN0ZW1zMRMwEQYDVQQDDApUZXh0U2VjdXJlMB4XDTEzMDMy\nNTIyMTgzNVoXDTIzMDMyMzIyMTgzNVowgY0xCzAJBgNVBAYTAlVTMRMwEQYDVQQI\nDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1TYW4gRnJhbmNpc2NvMR0wGwYDVQQKDBRP\ncGVuIFdoaXNwZXIgU3lzdGVtczEdMBsGA1UECwwUT3BlbiBXaGlzcGVyIFN5c3Rl\nbXMxEzARBgNVBAMMClRleHRTZWN1cmUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw\nggEKAoIBAQDBSWBpOCBDF0i4q2d4jAXkSXUGpbeWugVPQCjaL6qD9QDOxeW1afvf\nPo863i6Crq1KDxHpB36EwzVcjwLkFTIMeo7t9s1FQolAt3mErV2U0vie6Ves+yj6\ngrSfxwIDAcdsKmI0a1SQCZlr3Q1tcHAkAKFRxYNawADyps5B+Zmqcgf653TXS5/0\nIPPQLocLn8GWLwOYNnYfBvILKDMItmZTtEbucdigxEA9mfIvvHADEbteLtVgwBm9\nR5vVvtwrD6CCxI3pgH7EH7kMP0Od93wLisvn1yhHY7FuYlrkYqdkMvWUrKoASVw4\njb69vaeJCUdU+HCoXOSP1PQcL6WenNCHAgMBAAGjUDBOMB0GA1UdDgQWBBQBixjx\nP/s5GURuhYa+lGUypzI8kDAfBgNVHSMEGDAWgBQBixjxP/s5GURuhYa+lGUypzI8\nkDAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQB+Hr4hC56m0LvJAu1R\nK6NuPDbTMEN7/jMojFHxH4P3XPFfupjR+bkDq0pPOU6JjIxnrD1XD/EVmTTaTVY5\niOheyv7UzJOefb2pLOc9qsuvI4fnaESh9bhzln+LXxtCrRPGhkxA1IMIo3J/s2WF\n/KVYZyciu6b4ubJ91XPAuBNZwImug7/srWvbpk0hq6A6z140WTVSKtJG7EP41kJe\n/oF4usY5J7LPkxK3LWzMJnb5EIJDmRvyH8pyRwWg6Qm6qiGFaI4nL8QU4La1x2en\n4DGXRaLMPRwjELNgQPodR38zoCMuA8gHZfZYYoZ7D7Q1wNUiVHcxuFrEeBaYJbLE\nrwLV\n-----END CERTIFICATE-----\n",
"import": false,
"serverTrustRoot": "BbqY1DzohE4NUZoVF+L18oUPrK3kILllLEJh2UnPSsEx",
"defaultPublicChatServer": "https://chat.getsession.org",

View file

@ -5,6 +5,5 @@
"ip_url": "http://144.76.164.202:38157/"
}
],
"openDevTools": true,
"defaultPublicChatServer": "https://chat-dev.lokinet.org/"
"openDevTools": true
}

View file

@ -6,6 +6,5 @@
}
],
"openDevTools": true,
"defaultPublicChatServer": "https://team-chat.lokinet.org/",
"defaultFileServer": "https://file-dev.getsession.org"
}

View file

@ -106,7 +106,7 @@
if (specialConvInited) {
return;
}
const publicConversations = await window.Signal.Data.getAllPublicConversations();
const publicConversations = await window.Signal.Data.getAllOpenGroupV1Conversations();
publicConversations.forEach(conversation => {
// weird but create the object and does everything we need
conversation.getPublicSendData();
@ -119,9 +119,9 @@
return;
}
const ourKey = libsession.Utils.UserUtils.getOurPubKeyStrFromCache();
window.lokiMessageAPI = new window.LokiMessageAPI();
// singleton to relay events to libtextsecure/message_receiver
window.lokiPublicChatAPI = new window.LokiPublicChatAPI(ourKey);
// singleton to interface the File server
// If already exists we registered as a secondary device
if (!window.lokiFileServerAPI) {
@ -160,17 +160,9 @@
// Update zoom
window.updateZoomFactor();
const currentPoWDifficulty = storage.get('PoWDifficulty', null);
if (!currentPoWDifficulty) {
storage.put('PoWDifficulty', window.getDefaultPoWDifficulty());
}
// Ensure accounts created prior to 1.0.0-beta8 do have their
// 'primaryDevicePubKey' defined.
if (
Whisper.Registration.isDone() &&
!storage.get('primaryDevicePubKey', null)
) {
if (Whisper.Registration.isDone() && !storage.get('primaryDevicePubKey', null)) {
storage.put(
'primaryDevicePubKey',
window.libsession.Utils.UserUtils.getOurPubKeyStrFromCache()
@ -206,7 +198,7 @@
shutdown: async () => {
// Stop background processing
window.Signal.AttachmentDownloads.stop();
window.libsession.Utils.AttachmentDownloads.stop();
// Stop processing incoming messages
if (messageReceiver) {
@ -225,9 +217,7 @@
await storage.put('version', currentVersion);
if (newVersion) {
window.log.info(
`New version detected: ${currentVersion}; previous: ${lastVersion}`
);
window.log.info(`New version detected: ${currentVersion}; previous: ${lastVersion}`);
await window.Signal.Data.cleanupOrphanedAttachments();
@ -264,31 +254,26 @@
}
});
Whisper.events.on(
'deleteLocalPublicMessages',
async ({ messageServerIds, conversationId }) => {
if (!Array.isArray(messageServerIds)) {
return;
}
const messageIds = await window.Signal.Data.getMessageIdsFromServerIds(
messageServerIds,
conversationId
);
if (messageIds.length === 0) {
return;
}
const conversation = window
.getConversationController()
.get(conversationId);
messageIds.forEach(id => {
if (conversation) {
conversation.removeMessage(id);
}
window.Signal.Data.removeMessage(id);
});
Whisper.events.on('deleteLocalPublicMessages', async ({ messageServerIds, conversationId }) => {
if (!Array.isArray(messageServerIds)) {
return;
}
);
const messageIds = await window.Signal.Data.getMessageIdsFromServerIds(
messageServerIds,
conversationId
);
if (messageIds.length === 0) {
return;
}
const conversation = window.getConversationController().get(conversationId);
messageIds.forEach(id => {
if (conversation) {
conversation.removeMessage(id);
}
window.Signal.Data.removeMessage(id);
});
});
function manageExpiringData() {
window.Signal.Data.cleanSeenMessages();
@ -302,9 +287,7 @@
window.log.info('Cleanup: starting...');
const results = await Promise.all([
window.Signal.Data.getOutgoingWithoutExpiresAt(),
]);
const results = await Promise.all([window.Signal.Data.getOutgoingWithoutExpiresAt()]);
// Combine the models
const messagesForCleanup = results.reduce(
@ -312,29 +295,20 @@
[]
);
window.log.info(
`Cleanup: Found ${messagesForCleanup.length} messages for cleanup`
);
window.log.info(`Cleanup: Found ${messagesForCleanup.length} messages for cleanup`);
await Promise.all(
messagesForCleanup.map(async message => {
const delivered = message.get('delivered');
const sentAt = message.get('sent_at');
const expirationStartTimestamp = message.get(
'expirationStartTimestamp'
);
const expirationStartTimestamp = message.get('expirationStartTimestamp');
if (message.hasErrors()) {
return;
}
if (delivered) {
window.log.info(
`Cleanup: Starting timer for delivered message ${sentAt}`
);
message.set(
'expirationStartTimestamp',
expirationStartTimestamp || sentAt
);
window.log.info(`Cleanup: Starting timer for delivered message ${sentAt}`);
message.set('expirationStartTimestamp', expirationStartTimestamp || sentAt);
await message.setToExpire();
return;
}
@ -484,13 +458,11 @@
profileKey
);
const avatarPointer = await libsession.Utils.AttachmentUtils.uploadAvatar(
{
...dataResized,
data: encryptedData,
size: encryptedData.byteLength,
}
);
const avatarPointer = await libsession.Utils.AttachmentUtils.uploadAvatarV1({
...dataResized,
data: encryptedData,
size: encryptedData.byteLength,
});
({ url } = avatarPointer);
@ -511,12 +483,8 @@
avatar: newAvatarPath,
});
await conversation.commit();
window.libsession.Utils.UserUtils.setLastProfileUpdateTimestamp(
Date.now()
);
await window.libsession.Utils.SyncUtils.forceSyncConfigurationNowIfNeeded(
true
);
window.libsession.Utils.UserUtils.setLastProfileUpdateTimestamp(Date.now());
await window.libsession.Utils.SyncUtils.forceSyncConfigurationNowIfNeeded(true);
} catch (error) {
window.log.error(
'showEditProfileDialog Error ensuring that image is properly sized:',
@ -530,12 +498,8 @@
});
// might be good to not trigger a sync if the name did not change
await conversation.commit();
window.libsession.Utils.UserUtils.setLastProfileUpdateTimestamp(
Date.now()
);
await window.libsession.Utils.SyncUtils.forceSyncConfigurationNowIfNeeded(
true
);
window.libsession.Utils.UserUtils.setLastProfileUpdateTimestamp(Date.now());
await window.libsession.Utils.SyncUtils.forceSyncConfigurationNowIfNeeded(true);
}
// inform all your registered public servers
@ -550,9 +514,7 @@
.getConversationController()
.getConversations()
.filter(convo => convo.isPublic())
.forEach(convo =>
convo.trigger('ourAvatarChanged', { url, profileKey })
);
.forEach(convo => convo.trigger('ourAvatarChanged', { url, profileKey }));
}
},
});
@ -639,48 +601,7 @@
}
});
Whisper.events.on(
'publicChatInvitationAccepted',
async (serverAddress, channelId) => {
// To some degree this has been copy-pasted
// form connection_to_server_dialog_view.js:
const rawServerUrl = serverAddress
.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 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('leaveClosedGroup', async groupConvo => {
if (appView) {
appView.showLeaveGroupDialog(groupConvo);
}
@ -689,9 +610,7 @@
Whisper.Notifications.on('click', (id, messageId) => {
window.showWindow();
if (id) {
window.inboxStore.dispatch(
window.actionsCreators.openConversationExternal(id, messageId)
);
window.inboxStore.dispatch(window.actionsCreators.openConversationExternal(id, messageId));
} else {
appView.openInbox({
initialLoadComplete,
@ -789,7 +708,7 @@
if (messageReceiver) {
await messageReceiver.close();
}
window.Signal.AttachmentDownloads.stop();
window.libsession.Utils.AttachmentDownloads.stop();
}
let connectCount = 0;
@ -801,9 +720,7 @@
window.addEventListener('offline', onOffline);
}
if (connectCount === 0 && !navigator.onLine) {
window.log.warn(
'Starting up offline; will connect when we have network access'
);
window.log.warn('Starting up offline; will connect when we have network access');
window.addEventListener('online', onOnline);
onEmpty(); // this ensures that the loading screen is dismissed
return;
@ -840,19 +757,13 @@
initAPIs();
await initSpecialConversations();
messageReceiver = new textsecure.MessageReceiver();
messageReceiver.addEventListener(
'message',
window.DataMessageReceiver.handleMessageEvent
);
messageReceiver.addEventListener(
'sent',
window.DataMessageReceiver.handleMessageEvent
);
messageReceiver.addEventListener('message', window.DataMessageReceiver.handleMessageEvent);
messageReceiver.addEventListener('sent', window.DataMessageReceiver.handleMessageEvent);
messageReceiver.addEventListener('reconnect', onReconnect);
messageReceiver.addEventListener('configuration', onConfiguration);
// messageReceiver.addEventListener('typing', onTyping);
window.Signal.AttachmentDownloads.start({
window.libsession.Utils.AttachmentDownloads.start({
logger: window.log,
});

View file

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

View file

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

View file

@ -21,9 +21,7 @@
window.libsession.Utils.UserUtils.getOurPubKeyStrFromCache();
} catch (e) {
// give it a minute
log.warn(
'Could not check to see if newer version is available cause our pubkey is not set'
);
log.warn('Could not check to see if newer version is available cause our pubkey is not set');
nextWaitSeconds = 60;
setTimeout(async () => {
await checkForUpgrades();
@ -81,9 +79,7 @@
if (expiredVersion !== null) {
return res(expiredVersion);
}
log.info(
`Delaying sending checks for ${nextWaitSeconds}s, no version yet`
);
log.info(`Delaying sending checks for ${nextWaitSeconds}s, no version yet`);
setTimeout(waitForVersion, nextWaitSeconds * 1000);
return true;
}
@ -107,44 +103,4 @@
// yes we know
cb(expiredVersion);
};
const getServerTime = async () => {
let timestamp = NaN;
try {
const res = await window.tokenlessFileServerAdnAPI.serverRequest(
'loki/v1/time'
);
if (res.ok) {
timestamp = res.response;
}
} catch (e) {
return timestamp;
}
return Number(timestamp);
};
const getTimeDifferential = async () => {
// Get time differential between server and client in seconds
const serverTime = await getServerTime();
const clientTime = Math.ceil(Date.now() / 1000);
if (Number.isNaN(serverTime)) {
log.error('expire:::getTimeDifferential - serverTime is not valid');
return 0;
}
return serverTime - clientTime;
};
// require for PoW to work
window.setClockParams = async () => {
// Set server-client time difference
const maxTimeDifferential = 30 + 15; // + 15 for onion requests
const timeDifferential = await getTimeDifferential();
log.info('expire:::setClockParams - Clock difference', timeDifferential);
window.clientClockSynced = Math.abs(timeDifferential) < maxTimeDifferential;
return window.clientClockSynced;
};
})();

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,8 +1,4 @@
import {
Quote,
AttachmentPointer,
Preview,
} from '../../ts/session/messages/outgoing';
import { Quote, AttachmentPointer, Preview } from '../../ts/session/messages/outgoing';
interface UploadResponse {
url: string;
@ -21,6 +17,7 @@ export interface LokiAppDotNetServerInterface {
putAttachment(data: ArrayBuffer): Promise<UploadResponse>;
putAvatar(data: ArrayBuffer): Promise<UploadResponse>;
downloadAttachment(url: String): Promise<ArrayBuffer>;
serverRequest(endpoint: string): Promise<any>;
}
export interface LokiPublicChannelAPI {

View file

@ -1,12 +1,11 @@
/* global log, textsecure, libloki, Signal, Whisper,
clearTimeout, getMessageController, libsignal, StringView, window, _,
dcodeIO, Buffer, process */
const insecureNodeFetch = require('node-fetch');
const { URL, URLSearchParams } = require('url');
const { URL } = require('url');
const FormData = require('form-data');
const https = require('https');
const path = require('path');
const DataMessage = require('../../ts/receiver/dataMessage');
const OnionSend = require('../../ts/session/onions/onionSend');
// Can't be less than 1200 if we have unauth'd requests
const PUBLICCHAT_MSG_POLL_EVERY = 1.5 * 1000; // 1.5s
@ -14,20 +13,9 @@ const PUBLICCHAT_CHAN_POLL_EVERY = 20 * 1000; // 20s
const PUBLICCHAT_DELETION_POLL_EVERY = 5 * 1000; // 5s
const PUBLICCHAT_MOD_POLL_EVERY = 30 * 1000; // 30s
// FIXME: replace with something on urlPubkeyMap...
const FILESERVER_HOSTS = [
'file-dev.lokinet.org',
'file.lokinet.org',
'file-dev.getsession.org',
'file.getsession.org',
];
const LOKIFOUNDATION_DEVFILESERVER_PUBKEY =
'BSZiMVxOco/b3sYfaeyiMWv/JnqokxGXkHoclEx8TmZ6';
const LOKIFOUNDATION_FILESERVER_PUBKEY =
'BWJQnVm97sQE3Q1InB4Vuo+U/T1hmwHBv0ipkiv8tzEc';
const LOKIFOUNDATION_APNS_PUBKEY =
'BWQqZYWRl0LlotTcUSRJZPvNi8qyt1YSQH3li4EHQNBJ';
const LOKIFOUNDATION_DEVFILESERVER_PUBKEY = 'BSZiMVxOco/b3sYfaeyiMWv/JnqokxGXkHoclEx8TmZ6';
const LOKIFOUNDATION_FILESERVER_PUBKEY = 'BWJQnVm97sQE3Q1InB4Vuo+U/T1hmwHBv0ipkiv8tzEc';
const LOKIFOUNDATION_APNS_PUBKEY = 'BWQqZYWRl0LlotTcUSRJZPvNi8qyt1YSQH3li4EHQNBJ';
const urlPubkeyMap = {
'https://file-dev.getsession.org': LOKIFOUNDATION_DEVFILESERVER_PUBKEY,
@ -45,314 +33,6 @@ const MESSAGE_ATTACHMENT_TYPE = 'net.app.core.oembed';
const LOKI_ATTACHMENT_TYPE = 'attachment';
const LOKI_PREVIEW_TYPE = 'preview';
const snodeHttpsAgent = new https.Agent({
rejectUnauthorized: false,
});
const MAX_SEND_ONION_RETRIES = 3;
const sendViaOnion = async (srvPubKey, url, fetchOptions, options = {}) => {
if (!srvPubKey) {
log.error(
'loki_app_dot_net:::sendViaOnion - called without a server public key'
);
return {};
}
// set retry count
if (options.retry === undefined) {
// eslint-disable-next-line no-param-reassign
options.retry = 0;
// eslint-disable-next-line no-param-reassign
options.requestNumber = window.OnionPaths.getInstance().assignOnionRequestNumber();
}
const payloadObj = {
method: fetchOptions.method || 'GET',
body: fetchOptions.body || '',
// safety issue with file server, just safer to have this
headers: fetchOptions.headers || {},
// no initial /
endpoint: url.pathname.replace(/^\//, ''),
};
if (url.search) {
payloadObj.endpoint += url.search;
}
// from https://github.com/sindresorhus/is-stream/blob/master/index.js
if (
payloadObj.body &&
typeof payloadObj.body === 'object' &&
typeof payloadObj.body.pipe === 'function'
) {
const fData = payloadObj.body.getBuffer();
const fHeaders = payloadObj.body.getHeaders();
// update headers for boundary
payloadObj.headers = { ...payloadObj.headers, ...fHeaders };
// update body with base64 chunk
payloadObj.body = {
fileUpload: fData.toString('base64'),
};
}
let pathNodes = [];
try {
pathNodes = await window.OnionPaths.getInstance().getOnionPath();
} catch (e) {
log.error(
`loki_app_dot_net:::sendViaOnion #${options.requestNumber} - getOnionPath Error ${e.code} ${e.message}`
);
}
if (!pathNodes || !pathNodes.length) {
log.warn(
`loki_app_dot_net:::sendViaOnion #${options.requestNumber} - failing, no path available`
);
// should we retry?
return {};
}
// do the request
let result;
try {
result = await window.NewSnodeAPI.sendOnionRequestLsrpcDest(
0,
pathNodes,
srvPubKey,
url.host,
payloadObj,
options.requestNumber
);
if (typeof result === 'number') {
window.log.error(
'sendOnionRequestLsrpcDest() returned a number indicating an error: ',
result
);
}
} catch (e) {
log.error(
'loki_app_dot_net:::sendViaOnion - lokiRpcUtils error',
e.code,
e.message
);
return {};
}
// handle error/retries
if (!result.status) {
log.error(
`loki_app_dot_net:::sendViaOnion #${options.requestNumber} - Retry #${options.retry} Couldnt handle onion request, retrying`,
payloadObj
);
if (options.retry && options.retry >= MAX_SEND_ONION_RETRIES) {
log.error(
`sendViaOnion too many retries: ${options.retry}. Stopping retries.`
);
return {};
}
return sendViaOnion(srvPubKey, url, fetchOptions, {
...options,
retry: options.retry + 1,
counter: options.requestNumber,
});
}
if (options.noJson) {
return {
result,
txtResponse: result.body,
response: result.body,
};
}
// get the return variables we need
let response = {};
let txtResponse = '';
let { body } = result;
if (typeof body === 'string') {
// adn does uses this path
// log.info(`loki_app_dot_net:::sendViaOnion - got text response ${url.toString()}`);
txtResponse = result.body;
try {
body = JSON.parse(result.body);
} catch (e) {
log.error(
`loki_app_dot_net:::sendViaOnion #${options.requestNumber} - Can't decode JSON body`,
typeof result.body,
result.body
);
}
} else {
// FIXME why is
// https://chat-dev.lokinet.org/loki/v1/channel/1/deletes?count=200&since_id=
// difference in response than all the other calls....
// log.info(
// `loki_app_dot_net:::sendViaOnion #${
// options.requestNumber
// } - got object response ${url.toString()}`
// );
}
// result.status has the http response code
if (!txtResponse) {
txtResponse = JSON.stringify(body);
}
response = body;
response.headers = result.headers;
return { result, txtResponse, response };
};
const serverRequest = async (endpoint, options = {}) => {
const {
params = {},
method,
rawBody,
objBody,
token,
srvPubKey,
forceFreshToken = false,
} = options;
const url = new URL(endpoint);
if (!_.isEmpty(params)) {
url.search = new URLSearchParams(params);
}
const fetchOptions = {};
const headers = {};
try {
if (token) {
headers.Authorization = `Bearer ${token}`;
}
if (method) {
fetchOptions.method = method;
}
if (objBody) {
headers['Content-Type'] = 'application/json';
fetchOptions.body = JSON.stringify(objBody);
} else if (rawBody) {
fetchOptions.body = rawBody;
}
fetchOptions.headers = headers;
// domain ends in .loki
if (url.host.match(/\.loki$/i)) {
fetchOptions.agent = snodeHttpsAgent;
}
} catch (e) {
log.error(
'loki_app_dot_net:::serverRequest - set up error:',
e.code,
e.message
);
return {
err: e,
ok: false,
};
}
let response;
let result;
let txtResponse;
let mode = 'insecureNodeFetch';
try {
const host = url.host.toLowerCase();
// log.info('host', host, FILESERVER_HOSTS);
if (
window.lokiFeatureFlags.useFileOnionRequests &&
FILESERVER_HOSTS.includes(host)
) {
mode = 'sendViaOnion';
({ response, txtResponse, result } = await sendViaOnion(
srvPubKey,
url,
fetchOptions,
options
));
} else if (window.lokiFeatureFlags.useFileOnionRequests) {
if (!srvPubKey) {
throw new Error(
'useFileOnionRequests=true but we do not have a server pubkey set.'
);
}
mode = 'sendViaOnionOG';
({ response, txtResponse, result } = await sendViaOnion(
srvPubKey,
url,
fetchOptions,
options
));
} else {
// we end up here only if window.lokiFeatureFlags.useFileOnionRequests is false
log.info(`insecureNodeFetch => plaintext for ${url}`);
result = await insecureNodeFetch(url, fetchOptions);
txtResponse = await result.text();
// cloudflare timeouts (504s) will be html...
response = options.noJson ? txtResponse : JSON.parse(txtResponse);
// result.status will always be 200
// emulate the correct http code if available
if (response && response.meta && response.meta.code) {
result.status = response.meta.code;
}
}
} catch (e) {
if (txtResponse) {
log.error(
`loki_app_dot_net:::serverRequest - ${mode} error`,
e.code,
e.message,
`json: ${txtResponse}`,
'attempting connection to',
url.toString()
);
} else {
log.error(
`loki_app_dot_net:::serverRequest - ${mode} error`,
e.code,
e.message,
'attempting connection to',
url.toString()
);
}
return {
err: e,
ok: false,
};
}
if (!result) {
return {
err: 'noResult',
response,
ok: false,
};
}
// if it's a response style with a meta
if (result.status !== 200) {
if (!forceFreshToken && (!response.meta || response.meta.code === 401)) {
// retry with forcing a fresh token
return serverRequest(endpoint, {
...options,
forceFreshToken: true,
});
}
return {
err: 'statusCode',
statusCode: result.status,
response,
ok: false,
};
}
return {
statusCode: result.status,
response,
ok: result.status >= 200 && result.status <= 299,
};
};
// the core ADN class that handles all communication with a specific server
class LokiAppDotNetServerAPI {
constructor(ourKey, url) {
@ -381,27 +61,15 @@ class LokiAppDotNetServerAPI {
// channel getter/factory
async findOrCreateChannel(chatAPI, channelId, conversationId) {
let thisChannel = this.channels.find(
channel => channel.channelId === channelId
);
let thisChannel = this.channels.find(channel => channel.channelId === channelId);
if (!thisChannel) {
// make sure we're subscribed
// eventually we'll need to move to account registration/add server
await this.serverRequest(`channels/${channelId}/subscribe`, {
method: 'POST',
});
thisChannel = new LokiPublicChannelAPI(
chatAPI,
this,
channelId,
conversationId
);
log.info(
'LokiPublicChannelAPI started for',
channelId,
'on',
this.baseServerUrl
);
thisChannel = new LokiPublicChannelAPI(chatAPI, this, channelId, conversationId);
log.info('LokiPublicChannelAPI started for', channelId, 'on', this.baseServerUrl);
this.channels.push(thisChannel);
}
return thisChannel;
@ -444,9 +112,7 @@ class LokiAppDotNetServerAPI {
// Hard coded
let pubKeyAB;
if (urlPubkeyMap && urlPubkeyMap[this.baseServerUrl]) {
pubKeyAB = window.Signal.Crypto.base64ToArrayBuffer(
urlPubkeyMap[this.baseServerUrl]
);
pubKeyAB = window.Signal.Crypto.base64ToArrayBuffer(urlPubkeyMap[this.baseServerUrl]);
}
// do we have their pubkey locally?
@ -459,8 +125,7 @@ class LokiAppDotNetServerAPI {
window.lokiPublicChatAPI.openGroupPubKeys &&
window.lokiPublicChatAPI.openGroupPubKeys[this.baseServerUrl]
) {
pubKeyAB =
window.lokiPublicChatAPI.openGroupPubKeys[this.baseServerUrl];
pubKeyAB = window.lokiPublicChatAPI.openGroupPubKeys[this.baseServerUrl];
}
}
// else will fail validation later
@ -506,10 +171,7 @@ class LokiAppDotNetServerAPI {
// no big deal if it fails...
if (res.err || !res.response || !res.response.data) {
if (res.err) {
log.error(
`setProfileName Error ${res.err} ${res.statusCode}`,
this.baseServerUrl
);
log.error(`setProfileName Error ${res.err} ${res.statusCode}`, this.baseServerUrl);
}
return [];
}
@ -560,9 +222,7 @@ class LokiAppDotNetServerAPI {
if (this.token) {
return this.token;
}
token = await Signal.Data.getPublicServerTokenByServerUrl(
this.baseServerUrl
);
token = await Signal.Data.getPublicServerTokenByServerUrl(this.baseServerUrl);
}
if (!token) {
token = await this.refreshServerToken();
@ -677,25 +337,12 @@ class LokiAppDotNetServerAPI {
// not really an error, from a client's pov, network servers can fail...
if (e.code === 'ECONNREFUSED') {
// down
log.warn(
'requestToken request can not connect',
this.baseServerUrl,
e.message
);
log.warn('requestToken request can not connect', this.baseServerUrl, e.message);
} else if (e.code === 'ECONNRESET') {
// got disconnected
log.warn(
'requestToken request lost connection',
this.baseServerUrl,
e.message
);
log.warn('requestToken request lost connection', this.baseServerUrl, e.message);
} else {
log.error(
'requestToken request failed',
this.baseServerUrl,
e.code,
e.message
);
log.error('requestToken request failed', this.baseServerUrl, e.code, e.message);
}
return null;
}
@ -731,10 +378,10 @@ class LokiAppDotNetServerAPI {
if (options.forceFreshToken) {
await this.getOrRefreshServerToken(true);
}
return serverRequest(`${this.baseServerUrl}/${endpoint}`, {
return OnionSend.serverRequest(`${this.baseServerUrl}/${endpoint}`, {
...options,
token: this.token,
srvPubKey: this.pubKey,
srvPubKey: this.pubKeyHex,
});
}
@ -765,9 +412,7 @@ class LokiAppDotNetServerAPI {
log.warn('No channelId provided to getModerators!');
return [];
}
const res = await this.serverRequest(
`loki/v1/channels/${channelId}/moderators`
);
const res = await this.serverRequest(`loki/v1/channels/${channelId}/moderators`);
return (!res.err && res.response && res.response.moderators) || [];
}
@ -907,11 +552,7 @@ class LokiAppDotNetServerAPI {
if (res.err || !res.response || !res.response.data) {
if (res.err) {
log.error(
`loki_app_dot_net:::getUsers - Error: ${res.err} for ${pubKeys.join(
','
)}`
);
log.error(`loki_app_dot_net:::getUsers - Error: ${res.err} for ${pubKeys.join(',')}`);
}
return [];
}
@ -956,10 +597,7 @@ class LokiAppDotNetServerAPI {
throw new Error(`Failed to upload avatar to ${this.baseServerUrl}`);
}
const url =
response.data &&
response.data.avatar_image &&
response.data.avatar_image.url;
const url = response.data && response.data.avatar_image && response.data.avatar_image.url;
if (!url) {
throw new Error(`Failed to upload data: Invalid url.`);
@ -1037,9 +675,7 @@ class LokiAppDotNetServerAPI {
});
if (window.lokiFeatureFlags.useFileOnionRequestsV2) {
const buffer = dcodeIO.ByteBuffer.fromBase64(
res.response
).toArrayBuffer();
const buffer = dcodeIO.ByteBuffer.fromBase64(res.response).toArrayBuffer();
return buffer;
}
return new Uint8Array(res.response.data).buffer;
@ -1054,9 +690,7 @@ class LokiPublicChannelAPI {
this.channelId = channelId;
this.baseChannelUrl = `channels/${this.channelId}`;
this.conversationId = conversationId;
this.conversation = window
.getConversationController()
.getOrThrow(conversationId);
this.conversation = window.getConversationController().getOrThrow(conversationId);
this.lastMessageServerID = null;
this.modStatus = false;
this.deleteLastId = 1;
@ -1072,9 +706,7 @@ class LokiPublicChannelAPI {
// end properties
log.info(
`registered LokiPublicChannel ${channelId} on ${this.serverAPI.baseServerUrl}`
);
log.info(`registered LokiPublicChannel ${channelId} on ${this.serverAPI.baseServerUrl}`);
// start polling
this.open();
}
@ -1092,12 +724,9 @@ class LokiPublicChannelAPI {
}
async banUser(pubkey) {
const res = await this.serverRequest(
`loki/v1/moderation/blacklist/@${pubkey}`,
{
method: 'POST',
}
);
const res = await this.serverRequest(`loki/v1/moderation/blacklist/@${pubkey}`, {
method: 'POST',
});
if (res.err || !res.response || !res.response.data) {
if (res.err) {
@ -1110,9 +739,7 @@ class LokiPublicChannelAPI {
}
open() {
log.info(
`LokiPublicChannel open ${this.channelId} on ${this.serverAPI.baseServerUrl}`
);
log.info(`LokiPublicChannel open ${this.channelId} on ${this.serverAPI.baseServerUrl}`);
if (this.running) {
log.warn(
`LokiPublicChannel already open ${this.channelId} on ${this.serverAPI.baseServerUrl}`
@ -1135,9 +762,7 @@ class LokiPublicChannelAPI {
}
stop() {
log.info(
`LokiPublicChannel close ${this.channelId} on ${this.serverAPI.baseServerUrl}`
);
log.info(`LokiPublicChannel close ${this.channelId} on ${this.serverAPI.baseServerUrl}`);
if (!this.running) {
log.warn(
`LokiPublicChannel already open ${this.channelId} on ${this.serverAPI.baseServerUrl}`
@ -1179,11 +804,7 @@ class LokiPublicChannelAPI {
try {
await this.pollOnceForModerators();
} catch (e) {
log.warn(
'Error while polling for public chat moderators:',
e.code,
e.message
);
log.warn('Error while polling for public chat moderators:', e.code, e.message);
}
if (this.running) {
this.timers.moderator = setTimeout(() => {
@ -1195,9 +816,7 @@ class LokiPublicChannelAPI {
// get moderator status
async pollOnceForModerators() {
// get moderator status
const res = await this.serverRequest(
`loki/v1/channels/${this.channelId}/moderators`
);
const res = await this.serverRequest(`loki/v1/channels/${this.channelId}/moderators`);
const ourNumberDevice = window.libsession.Utils.UserUtils.getOurPubKeyStrFromCache();
// Get the list of moderators if no errors occurred
@ -1228,15 +847,12 @@ class LokiPublicChannelAPI {
log.warn(`public chat channel state unknown, skipping set: ${res.err}`);
return false;
}
let notes =
res.response && res.response.data && res.response.data.annotations;
let notes = res.response && res.response.data && res.response.data.annotations;
if (!notes) {
// ok if nothing is set yet
notes = [];
}
let settingNotes = notes.filter(
note => note.type === SETTINGS_CHANNEL_ANNOTATION_TYPE
);
let settingNotes = notes.filter(note => note.type === SETTINGS_CHANNEL_ANNOTATION_TYPE);
if (!settingNotes) {
// default name, description, avatar
settingNotes = [
@ -1253,10 +869,10 @@ class LokiPublicChannelAPI {
// update settings
settingNotes[0].value = Object.assign(settingNotes[0].value, settings);
// commit settings
const updateRes = await this.serverRequest(
`loki/v1/${this.baseChannelUrl}`,
{ method: 'PUT', objBody: { annotations: settingNotes } }
);
const updateRes = await this.serverRequest(`loki/v1/${this.baseChannelUrl}`, {
method: 'PUT',
objBody: { annotations: settingNotes },
});
if (updateRes.err || !updateRes.response || !updateRes.response.data) {
if (updateRes.err) {
log.error(`setChannelSettings Error ${updateRes.err}`);
@ -1284,17 +900,13 @@ class LokiPublicChannelAPI {
{ method: 'DELETE', params: { ids: serverIds } }
);
if (!res.err) {
const deletedIds = res.response.data
.filter(d => d.is_deleted)
.map(d => d.id);
const deletedIds = res.response.data.filter(d => d.is_deleted).map(d => d.id);
if (deletedIds.length > 0) {
log.info(`deleted ${serverIds} on ${this.baseChannelUrl}`);
}
const failedIds = res.response.data
.filter(d => !d.is_deleted)
.map(d => d.id);
const failedIds = res.response.data.filter(d => !d.is_deleted).map(d => d.id);
if (failedIds.length > 0) {
log.warn(`failed to delete ${failedIds} on ${this.baseChannelUrl}`);
@ -1302,10 +914,7 @@ class LokiPublicChannelAPI {
// 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
const ignoredIds = _.difference(
serverIds,
_.union(failedIds, deletedIds)
);
const ignoredIds = _.difference(serverIds, _.union(failedIds, deletedIds));
if (ignoredIds.length > 0) {
log.warn(`No response for ${ignoredIds} on ${this.baseChannelUrl}`);
@ -1314,9 +923,7 @@ class LokiPublicChannelAPI {
return { deletedIds, ignoredIds };
}
if (canThrow) {
throw new textsecure.PublicChatError(
'Failed to delete public chat message'
);
throw new textsecure.PublicChatError('Failed to delete public chat message');
}
return { deletedIds: [], ignoredIds: [] };
}
@ -1332,11 +939,7 @@ class LokiPublicChannelAPI {
try {
await this.pollForChannelOnce();
} catch (e) {
log.warn(
'Error while polling for public chat room details',
e.code,
e.message
);
log.warn('Error while polling for public chat room details', e.code, e.message);
}
if (this.running) {
this.timers.channel = setTimeout(() => {
@ -1389,16 +992,11 @@ class LokiPublicChannelAPI {
} else {
// relative URL avatar
const avatarAbsUrl = this.serverAPI.baseServerUrl + note.value.avatar;
const {
writeNewAttachmentData,
deleteAttachmentData,
} = window.Signal.Migrations;
const { writeNewAttachmentData, deleteAttachmentData } = window.Signal.Migrations;
// do we already have this image? no, then
// download a copy and save it
const imageData = await this.serverAPI.downloadAttachment(
avatarAbsUrl
);
const imageData = await this.serverAPI.downloadAttachment(avatarAbsUrl);
const newAttributes = await window.Signal.Types.Conversation.maybeUpdateAvatar(
this.conversation.attributes,
@ -1428,11 +1026,7 @@ class LokiPublicChannelAPI {
try {
await this.pollOnceForDeletions();
} catch (e) {
log.warn(
'Error while polling for public chat deletions:',
e.code,
e.message
);
log.warn('Error while polling for public chat deletions:', e.code, e.message);
}
if (this.running) {
this.timers.delete = setTimeout(() => {
@ -1455,18 +1049,10 @@ class LokiPublicChannelAPI {
// grab the next 200 deletions from where we last checked
// eslint-disable-next-line no-await-in-loop
const res = await this.serverRequest(
`loki/v1/channel/${this.channelId}/deletes`,
{ params }
);
const res = await this.serverRequest(`loki/v1/channel/${this.channelId}/deletes`, { params });
// if any problems, abort out
if (
res.err ||
!res.response ||
!res.response.data ||
!res.response.meta
) {
if (res.err || !res.response || !res.response.data || !res.response.meta) {
if (res.statusCode === 403) {
// token is now invalid
this.serverAPI.getOrRefreshServerToken(true);
@ -1474,9 +1060,7 @@ class LokiPublicChannelAPI {
if (res.err) {
log.error(`pollOnceForDeletions Error ${res.err}`);
} else {
log.error(
`pollOnceForDeletions Error: Received incorrect response ${res.response}`
);
log.error(`pollOnceForDeletions Error: Received incorrect response ${res.response}`);
}
break;
}
@ -1492,20 +1076,11 @@ class LokiPublicChannelAPI {
// update where we last checked
this.deleteLastId = res.response.meta.max_id;
more =
res.response.meta.more &&
res.response.data.length >= params.count &&
this.running;
more = res.response.meta.more && res.response.data.length >= params.count && this.running;
}
}
static getSigData(
sigVer,
noteValue,
attachmentAnnotations,
previewAnnotations,
adnMessage
) {
static getSigData(sigVer, noteValue, attachmentAnnotations, previewAnnotations, adnMessage) {
let sigString = '';
sigString += adnMessage.text.trim();
sigString += noteValue.timestamp;
@ -1527,10 +1102,7 @@ class LokiPublicChannelAPI {
}
async getMessengerData(adnMessage) {
if (
!Array.isArray(adnMessage.annotations) ||
adnMessage.annotations.length === 0
) {
if (!Array.isArray(adnMessage.annotations) || adnMessage.annotations.length === 0) {
return false;
}
const noteValue = adnMessage.annotations[0].value;
@ -1610,8 +1182,7 @@ class LokiPublicChannelAPI {
return {
timestamp,
serverTimestamp:
new Date(`${adnMessage.created_at}`).getTime() || timestamp,
serverTimestamp: new Date(`${adnMessage.created_at}`).getTime() || timestamp,
attachments,
preview,
quote,
@ -1626,11 +1197,7 @@ class LokiPublicChannelAPI {
try {
await this.pollOnceForMessages();
} catch (e) {
log.warn(
'Error while polling for public chat messages:',
e.code,
e.message
);
log.warn('Error while polling for public chat messages:', e.code, e.message);
}
if (this.running) {
this.timers.message = setTimeout(() => {
@ -1703,8 +1270,7 @@ class LokiPublicChannelAPI {
// get our profile name
const ourNumberDevice = window.libsession.Utils.UserUtils.getOurPubKeyStrFromCache();
// if no primaryDevicePubKey fall back to ourNumberDevice
const ourNumberProfile =
window.storage.get('primaryDevicePubKey') || ourNumberDevice;
const ourNumberProfile = window.storage.get('primaryDevicePubKey') || ourNumberDevice;
let lastProfileName = false;
// the signature forces this to be async
@ -1754,11 +1320,7 @@ class LokiPublicChannelAPI {
// message is one of the object of this.lastMessagesCache
// testedMessage is the adnMessage object
const isDuplicate = (message, testedMessage) =>
DataMessage.isDuplicate(
message,
testedMessage,
testedMessage.user.username
);
DataMessage.isDuplicate(message, testedMessage, testedMessage.user.username);
const isThisMessageDuplicate = this.lastMessagesCache.some(m =>
isDuplicate(m, adnMessage)
);
@ -1820,8 +1382,7 @@ class LokiPublicChannelAPI {
receivedAt,
isPublic: true,
message: {
body:
adnMessage.text === timestamp.toString() ? '' : adnMessage.text,
body: adnMessage.text === timestamp.toString() ? '' : adnMessage.text,
attachments,
group: {
id: this.conversationId,
@ -1888,9 +1449,7 @@ class LokiPublicChannelAPI {
// if we received one of our own messages
if (lastProfileName !== false) {
// get current profileName
const profileConvo = window
.getConversationController()
.get(ourNumberProfile);
const profileConvo = window.getConversationController().get(ourNumberProfile);
const profileName = profileConvo.getProfileName();
// check to see if it out of sync
if (profileName !== lastProfileName) {
@ -1985,12 +1544,8 @@ class LokiPublicChannelAPI {
async sendMessage(data, messageTimeStamp) {
const { quote, attachments, preview } = data;
const text = data.body || messageTimeStamp.toString();
const attachmentAnnotations = attachments.map(
LokiPublicChannelAPI.getAnnotationFromAttachment
);
const previewAnnotations = preview.map(
LokiPublicChannelAPI.getAnnotationFromPreview
);
const attachmentAnnotations = attachments.map(LokiPublicChannelAPI.getAnnotationFromAttachment);
const previewAnnotations = preview.map(LokiPublicChannelAPI.getAnnotationFromPreview);
const payload = {
text,
@ -2038,10 +1593,7 @@ class LokiPublicChannelAPI {
previewAnnotations.map(anno => anno.value),
mockAdnMessage
);
const sig = await libsignal.Curve.async.calculateSignature(
privKey,
sigData
);
const sig = await libsignal.Curve.async.calculateSignature(privKey, sigData);
payload.annotations[0].value.sig = StringView.arrayBufferToHex(sig);
payload.annotations[0].value.sigver = sigVer;
const res = await this.serverRequest(`${this.baseChannelUrl}/messages`, {
@ -2069,8 +1621,7 @@ class LokiPublicChannelAPI {
}
}
LokiAppDotNetServerAPI.serverRequest = serverRequest;
LokiAppDotNetServerAPI.sendViaOnion = sendViaOnion;
LokiAppDotNetServerAPI.serverRequest = OnionSend.serverRequest;
// These files are expected to be in commonjs so we can't use es6 syntax :(
// If we move these to TS then we should be able to use es6

View file

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

View file

@ -1,14 +0,0 @@
export interface LokiMessageInterface {
sendMessage(
pubKey: string,
data: Uint8Array,
messageTimeStamp: number,
ttl: number
): Promise<void>;
}
declare class LokiMessageAPI implements LokiMessageInterface {
constructor(ourKey: string);
}
export default LokiMessageAPI;

View file

@ -1,115 +0,0 @@
/* eslint-disable no-await-in-loop */
/* eslint-disable no-loop-func */
/* global log, dcodeIO, window, callWorker, textsecure */
const _ = require('lodash');
const primitives = require('./loki_primitives');
const DEFAULT_CONNECTIONS = 3;
const calcNonce = (messageEventData, pubKey, data64, timestamp, ttl) => {
const difficulty = window.storage.get('PoWDifficulty', null);
// Nonce is returned as a base64 string to include in header
window.Whisper.events.trigger('calculatingPoW', messageEventData);
return callWorker('calcPoW', timestamp, ttl, pubKey, data64, difficulty);
};
async function _openSendConnection(snode, params) {
// TODO: Revert back to using snode address instead of IP
const successfulSend = await window.NewSnodeAPI.storeOnNode(snode, params);
if (successfulSend) {
return snode;
}
// should we mark snode as bad if it can't store our message?
return false;
}
class LokiMessageAPI {
/**
* Refactor note: We should really clean this up ... it's very messy
*
* We need to split it into 2 sends:
* - Snodes
* - Open Groups
*
* Mikunj:
* Temporarily i've made it so `MessageSender` handles open group sends and calls this function for regular sends.
*/
async sendMessage(pubKey, data, messageTimeStamp, ttl, options = {}) {
const { isPublic = false, numConnections = DEFAULT_CONNECTIONS } = options;
// Data required to identify a message in a conversation
const messageEventData = {
pubKey,
timestamp: messageTimeStamp,
};
if (isPublic) {
window.log.warn(
'this sendMessage() should not be called anymore with an open group message'
);
return;
}
const data64 = dcodeIO.ByteBuffer.wrap(data).toString('base64');
const timestamp = Date.now();
const nonce = await calcNonce(
messageEventData,
window.getStoragePubKey(pubKey),
data64,
timestamp,
ttl
);
// Using timestamp as a unique identifier
const swarm = await window.SnodePool.getSnodesFor(pubKey);
// send parameters
const params = {
pubKey,
ttl: ttl.toString(),
nonce,
timestamp: timestamp.toString(),
data: data64,
};
const usedNodes = _.slice(swarm, 0, numConnections);
const promises = usedNodes.map(snode => _openSendConnection(snode, params));
let snode;
try {
// eslint-disable-next-line more/no-then
snode = await primitives.firstTrue(promises);
} catch (e) {
const snodeStr = snode ? `${snode.ip}:${snode.port}` : 'null';
log.warn(
`loki_message:::sendMessage - ${e.code} ${e.message} to ${pubKey} via snode:${snodeStr}`
);
if (e instanceof textsecure.WrongDifficultyError) {
// Force nonce recalculation
// NOTE: Currently if there are snodes with conflicting difficulties we
// will send the message twice (or more). Won't affect client side but snodes
// could store the same message multiple times because they will have different
// timestamps (and therefore nonces)
await this.sendMessage(pubKey, data, messageTimeStamp, ttl, options);
return;
}
throw e;
}
if (!snode) {
throw new window.textsecure.EmptySwarmError(
pubKey,
'Ran out of swarm nodes to query'
);
} else {
log.info(
`loki_message:::sendMessage - Successfully stored message to ${pubKey} via ${snode.ip}:${snode.port}`
);
}
}
}
// These files are expected to be in commonjs so we can't use es6 syntax :(
// If we move these to TS then we should be able to use es6
module.exports = LokiMessageAPI;

View file

@ -1,11 +0,0 @@
export async function sleepFor(ms: number);
export async function allowOnlyOneAtATime(
name: any,
process: any,
timeout?: any
);
export async function abortableIterator(
array: Array<any>,
action: (any) => void
);

View file

@ -1,120 +0,0 @@
/* global clearTimeout, log */
// was timeoutDelay
const sleepFor = ms => new Promise(resolve => setTimeout(resolve, ms));
// Taken from https://stackoverflow.com/questions/51160260/clean-way-to-wait-for-first-true-returned-by-promise
// The promise returned by this function will resolve true when the first promise
// in ps resolves true *or* it will resolve false when all of ps resolve false
const firstTrue = ps => {
const newPs = ps.map(
p =>
new Promise(
// eslint-disable-next-line more/no-then
(resolve, reject) => p.then(v => v && resolve(v), reject)
)
);
// eslint-disable-next-line more/no-then
newPs.push(Promise.all(ps).then(() => false));
return Promise.race(newPs);
};
// one action resolves all
const snodeGlobalLocks = {};
async function allowOnlyOneAtATime(name, process, timeout) {
// if currently not in progress
if (snodeGlobalLocks[name] === undefined) {
// set lock
snodeGlobalLocks[name] = new Promise(async (resolve, reject) => {
// set up timeout feature
let timeoutTimer = null;
if (timeout) {
timeoutTimer = setTimeout(() => {
log.warn(
`loki_primitives:::allowOnlyOneAtATime - TIMEDOUT after ${timeout}s`
);
delete snodeGlobalLocks[name]; // clear lock
reject();
}, timeout);
}
// do actual work
let innerRetVal;
try {
innerRetVal = await process();
} catch (e) {
if (typeof e === 'string') {
log.error(`loki_primitives:::allowOnlyOneAtATime - error ${e}`);
} else {
log.error(
`loki_primitives:::allowOnlyOneAtATime - error ${e.code} ${e.message}`
);
}
// clear timeout timer
if (timeout) {
if (timeoutTimer !== null) {
clearTimeout(timeoutTimer);
timeoutTimer = null;
}
}
delete snodeGlobalLocks[name]; // clear lock
throw e;
}
// clear timeout timer
if (timeout) {
if (timeoutTimer !== null) {
clearTimeout(timeoutTimer);
timeoutTimer = null;
}
}
delete snodeGlobalLocks[name]; // clear lock
// release the kraken
resolve(innerRetVal);
});
}
return snodeGlobalLocks[name];
}
function abortableIterator(array, iterator) {
let abortIteration = false;
// for the control promise
let controlResolveFunctor;
const stopPolling = new Promise(res => {
// store resolve functor
controlResolveFunctor = res;
});
// eslint-disable-next-line more/no-then
stopPolling.then(() => {
abortIteration = true;
});
const destructableList = [...array];
const accum = [];
return {
start: async serially => {
let item = destructableList.pop();
while (item && !abortIteration) {
if (serially) {
// eslint-disable-next-line no-await-in-loop
accum.push(await iterator(item));
} else {
accum.push(iterator(item));
}
item = destructableList.pop();
}
return accum;
},
stop: () => {
controlResolveFunctor();
},
};
}
module.exports = {
sleepFor,
allowOnlyOneAtATime,
abortableIterator,
firstTrue,
};

View file

@ -1,26 +1,19 @@
import {
LokiAppDotNetServerInterface,
LokiPublicChannelAPI,
} from './loki_app_dot_net_api';
import { LokiAppDotNetServerInterface, LokiPublicChannelAPI } from './loki_app_dot_net_api';
export interface LokiPublicChatFactoryInterface {
ourKey: string;
openGroupPubKeys: { [key: string]: string };
findOrCreateServer(url: string): Promise<LokiAppDotNetServerInterface | null>;
findOrCreateChannel(
url: string,
channelId: number,
conversationId: string
): Promise<LokiPublicChannelAPI | null>;
getListOfMembers(): Promise<
Array<{ authorPhoneNumber: string; authorProfileName?: string }>
>;
setListOfMembers(
members: Array<{ authorPhoneNumber: string; authorProfileName?: string }>
);
getListOfMembers(): Promise<Array<{ authorPhoneNumber: string; authorProfileName?: string }>>;
setListOfMembers(members: Array<{ authorPhoneNumber: string; authorProfileName?: string }>);
}
declare class LokiPublicChatFactoryAPI
implements LokiPublicChatFactoryInterface {
declare class LokiPublicChatFactoryAPI implements LokiPublicChatFactoryInterface {
constructor(ourKey: string);
}

View file

@ -1,95 +1,7 @@
/* global log, window, process, URL, dcodeIO */
/* global log, process */
const EventEmitter = require('events');
const LokiAppDotNetAPI = require('./loki_app_dot_net_api');
const insecureNodeFetch = require('node-fetch');
/**
* Tries to establish a connection with the specified open group url.
*
* This will try to do an onion routing call if the `useFileOnionRequests` feature flag is set,
* or call directly insecureNodeFetch if it's not.
*
* Returns
* * true if useFileOnionRequests is false and no exception where thrown by insecureNodeFetch
* * true if useFileOnionRequests is true and we established a connection to the server with onion routing
* * false otherwise
*
*/
const validOpenGroupServer = async serverUrl => {
// test to make sure it's online (and maybe has a valid SSL cert)
try {
const url = new URL(serverUrl);
if (!window.lokiFeatureFlags.useFileOnionRequests) {
// we are not running with onion request
// this is an insecure insecureNodeFetch. It will expose the user ip to the serverUrl (not onion routed)
log.info(`insecureNodeFetch => plaintext for ${url.toString()}`);
// we probably have to check the response here
await insecureNodeFetch(serverUrl);
return true;
}
// This MUST be an onion routing call, no nodeFetch calls below here.
/**
* this is safe (as long as node's in your trust model)
*
* First, we need to fetch the open group public key of this open group.
* The fileserver have all the open groups public keys.
* We need the open group public key because for onion routing we will need to encode
* our request with it.
* We can just ask the file-server to get the one for the open group we are trying to add.
*/
const result = await window.tokenlessFileServerAdnAPI.serverRequest(
`loki/v1/getOpenGroupKey/${url.hostname}`
);
if (result.response.meta.code === 200) {
// we got the public key of the server we are trying to add.
// decode it.
const obj = JSON.parse(result.response.data);
const pubKey = dcodeIO.ByteBuffer.wrap(
obj.data,
'base64'
).toArrayBuffer();
// verify we can make an onion routed call to that open group with the decoded public key
// get around the FILESERVER_HOSTS filter by not using serverRequest
const res = await LokiAppDotNetAPI.sendViaOnion(
pubKey,
url,
{ method: 'GET' },
{ noJson: true }
);
if (res.result && res.result.status === 200) {
log.info(
`loki_public_chat::validOpenGroupServer - onion routing enabled on ${url.toString()}`
);
// save pubkey for use...
window.lokiPublicChatAPI.openGroupPubKeys[serverUrl] = pubKey;
return true;
}
// return here, just so we are sure adding some code below won't do a nodeFetch fallback
return false;
} else if (result.response.meta.code !== 404) {
// unknown error code
log.warn(
'loki_public_chat::validOpenGroupServer - unknown error code',
result.response.meta
);
}
return false;
} catch (e) {
log.warn(
`loki_public_chat::validOpenGroupServer - failing to create ${serverUrl}`,
e.code,
e.message
);
// bail out if not valid enough
}
return false;
};
const OpenGroupUtils = require('../../ts/opengroup/utils/OpenGroupUtils');
class LokiPublicChatFactoryAPI extends EventEmitter {
constructor(ourKey) {
@ -99,7 +11,6 @@ class LokiPublicChatFactoryAPI extends EventEmitter {
this.allMembers = [];
this.openGroupPubKeys = {};
// Multidevice states
this.primaryUserProfileName = {};
}
// MessageReceiver.connect calls this
@ -115,18 +26,14 @@ class LokiPublicChatFactoryAPI extends EventEmitter {
// server getter/factory
async findOrCreateServer(serverUrl) {
let thisServer = this.servers.find(
server => server.baseServerUrl === serverUrl
);
let thisServer = this.servers.find(server => server.baseServerUrl === serverUrl);
if (!thisServer) {
log.info(`loki_public_chat::findOrCreateServer - creating ${serverUrl}`);
const serverIsValid = await validOpenGroupServer(serverUrl);
const serverIsValid = await OpenGroupUtils.validOpenGroupServer(serverUrl);
if (!serverIsValid) {
// FIXME: add toast?
log.error(
`loki_public_chat::findOrCreateServer - error: ${serverUrl} is not valid`
);
log.error(`loki_public_chat::findOrCreateServer - error: ${serverUrl} is not valid`);
return null;
}
@ -140,25 +47,16 @@ class LokiPublicChatFactoryAPI extends EventEmitter {
if (this.openGroupPubKeys[serverUrl]) {
thisServer.getPubKeyForUrl();
if (!thisServer.pubKeyHex) {
log.warn(
`loki_public_chat::findOrCreateServer - failed to set public key`
);
log.warn(`loki_public_chat::findOrCreateServer - failed to set public key`);
}
}
}
const gotToken = await thisServer.getOrRefreshServerToken();
if (!gotToken) {
log.warn(
`loki_public_chat::findOrCreateServer - Invalid server ${serverUrl}`
);
log.warn(`loki_public_chat::findOrCreateServer - Invalid server ${serverUrl}`);
return null;
}
// if (window.isDev) {
// log.info(
// `loki_public_chat::findOrCreateServer - set token ${thisServer.token} for ${serverUrl}`
// );
// }
this.servers.push(thisServer);
}
@ -177,9 +75,7 @@ class LokiPublicChatFactoryAPI extends EventEmitter {
// deallocate resources server uses
unregisterChannel(serverUrl, channelId) {
const i = this.servers.findIndex(
server => server.baseServerUrl === serverUrl
);
const i = this.servers.findIndex(server => server.baseServerUrl === serverUrl);
if (i === -1) {
log.warn(`Tried to unregister from nonexistent server ${serverUrl}`);
return;

View file

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

View file

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

View file

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

View file

@ -9,48 +9,29 @@ const OS = require('../../ts/OS');
const Settings = require('./settings');
const Util = require('../../ts/util');
const LinkPreviews = require('./link_previews');
const AttachmentDownloads = require('./attachment_downloads');
const { Message } = require('../../ts/components/conversation/Message');
// Components
const { EditProfileDialog } = require('../../ts/components/EditProfileDialog');
const { UserDetailsDialog } = require('../../ts/components/UserDetailsDialog');
const {
SessionSeedModal,
} = require('../../ts/components/session/SessionSeedModal');
const {
SessionIDResetDialog,
} = require('../../ts/components/session/SessionIDResetDialog');
const {
SessionRegistrationView,
} = require('../../ts/components/session/SessionRegistrationView');
const { SessionSeedModal } = require('../../ts/components/session/SessionSeedModal');
const { SessionIDResetDialog } = require('../../ts/components/session/SessionIDResetDialog');
const { SessionRegistrationView } = require('../../ts/components/session/SessionRegistrationView');
const {
SessionInboxView,
} = require('../../ts/components/session/SessionInboxView');
const {
SessionPasswordModal,
} = require('../../ts/components/session/SessionPasswordModal');
const {
SessionConfirm,
} = require('../../ts/components/session/SessionConfirm');
const { SessionInboxView } = require('../../ts/components/session/SessionInboxView');
const { SessionPasswordModal } = require('../../ts/components/session/SessionPasswordModal');
const { SessionConfirm } = require('../../ts/components/session/SessionConfirm');
const {
UpdateGroupNameDialog,
} = require('../../ts/components/conversation/UpdateGroupNameDialog');
const { UpdateGroupNameDialog } = require('../../ts/components/conversation/UpdateGroupNameDialog');
const {
UpdateGroupMembersDialog,
} = require('../../ts/components/conversation/UpdateGroupMembersDialog');
const {
InviteContactsDialog,
} = require('../../ts/components/conversation/InviteContactsDialog');
const { InviteContactsDialog } = require('../../ts/components/conversation/InviteContactsDialog');
const {
AdminLeaveClosedGroupDialog,
} = require('../../ts/components/conversation/AdminLeaveClosedGroupDialog');
const {
AddModeratorsDialog,
} = require('../../ts/components/conversation/ModeratorsAddDialog');
const { AddModeratorsDialog } = require('../../ts/components/conversation/ModeratorsAddDialog');
const {
RemoveModeratorsDialog,
} = require('../../ts/components/conversation/ModeratorsRemoveDialog');
@ -68,13 +49,7 @@ const SettingsType = require('../../ts/types/Settings');
// Views
const Initialization = require('./views/initialization');
function initializeMigrations({
userDataPath,
Attachments,
Type,
VisualType,
logger,
}) {
function initializeMigrations({ userDataPath, Attachments, Type, VisualType, logger }) {
if (!Attachments) {
return null;
}
@ -146,8 +121,7 @@ function initializeMigrations({
logger,
}),
writeNewAttachmentData: createWriterForNew(attachmentsPath),
writeAttachment: ({ data, path }) =>
createWriterForExisting(attachmentsPath)({ data, path }),
writeAttachment: ({ data, path }) => createWriterForExisting(attachmentsPath)({ data, path }),
};
}
@ -198,7 +172,6 @@ exports.setup = (options = {}) => {
};
return {
AttachmentDownloads,
Components,
Crypto,
Data,

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,9 +1,8 @@
/* global dcodeIO, pow */
/* global dcodeIO */
/* eslint-disable strict */
const functions = {
arrayBufferToStringBase64,
calcPoW,
};
onmessage = async e => {
@ -37,22 +36,3 @@ function prepareErrorForPostMessage(error) {
function arrayBufferToStringBase64(arrayBuffer) {
return dcodeIO.ByteBuffer.wrap(arrayBuffer).toString('base64');
}
function calcPoW(
timestamp,
ttl,
pubKey,
data,
difficulty = undefined,
increment = 1,
startNonce = 0
) {
return pow.calcPoW(
timestamp,
ttl,
pubKey,
data,
difficulty,
increment,
startNonce
);
}

View file

@ -37,7 +37,7 @@
this.remove();
},
submit() {
this.convo.leaveGroup();
this.convo.leaveClosedGroup();
},
});
})();

View file

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

View file

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

View file

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

View file

@ -1,4 +1,4 @@
/* global Whisper, _ */
/* global Whisper */
// eslint-disable-next-line func-names
(function() {
@ -10,17 +10,11 @@
className: 'loki-dialog modal',
initialize(convo) {
this.close = this.close.bind(this);
this.submit = this.submit.bind(this);
this.theme = convo.theme;
const convos = window.getConversationController().getConversations();
this.contacts = convos.filter(
d =>
!!d &&
!d.isBlocked() &&
d.isPrivate() &&
!d.isMe() &&
!!d.get('active_at')
d => !!d && !d.isBlocked() && d.isPrivate() && !d.isMe() && !!d.get('active_at')
);
if (!convo.isPublic()) {
const members = convo.get('members') || [];
@ -42,10 +36,10 @@
Component: window.Signal.Components.InviteContactsDialog,
props: {
contactList: this.contacts,
onSubmit: this.submit,
onClose: this.close,
chatName: this.chatName,
theme: this.theme,
convo: this.convo,
},
});
@ -55,59 +49,5 @@
close() {
this.remove();
},
submit(pubkeys) {
// public group chats
if (this.isPublic) {
const serverInfos = {
address: this.chatServer,
name: this.chatName,
channelId: this.channelId,
};
pubkeys.forEach(async pubkeyStr => {
const convo = await window
.getConversationController()
.getOrCreateAndWait(pubkeyStr, 'private');
if (convo) {
convo.sendMessage('', null, null, null, serverInfos);
}
});
} else {
// private group chats
const ourPK = window.libsession.Utils.UserUtils.getOurPubKeyStrFromCache();
let existingMembers = this.convo.get('members') || [];
// at least make sure it's an array
if (!Array.isArray(existingMembers)) {
existingMembers = [];
}
existingMembers = existingMembers.filter(d => !!d);
const newMembers = pubkeys.filter(d => !existingMembers.includes(d));
if (newMembers.length > 0) {
// Do not trigger an update if there is too many members
if (
newMembers.length + existingMembers.length >
window.CONSTANTS.CLOSED_GROUP_SIZE_LIMIT
) {
window.libsession.Utils.ToastUtils.pushTooManyMembers();
return;
}
const allMembers = window.Lodash.concat(existingMembers, newMembers, [
ourPK,
]);
const uniqMembers = _.uniq(allMembers, true, d => d);
const groupId = this.convo.get('id');
const groupName = this.convo.get('name');
window.libsession.ClosedGroup.initiateGroupUpdate(
groupId,
groupName,
uniqMembers
);
}
}
},
});
})();

View file

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

View file

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

View file

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

16
libloki/crypto.d.ts vendored
View file

@ -1,17 +1,15 @@
declare enum PairingTypeEnum {
REQUEST = 1,
GRANT,
}
export interface CryptoInterface {
DHDecrypt: any;
DHEncrypt: any;
DecryptGCM: any; // AES-GCM
EncryptGCM: any; // AES-GCM
PairingType: PairingTypeEnum;
DecryptAESGCM: (symmetricKey: ArrayBuffer, ivAndCiphertext: ArrayBuffer) => Promise<ArrayBuffer>; // AES-GCM
deriveSymmetricKey: (pubkey: ArrayBuffer, seckey: ArrayBuffer) => Promise<ArrayBuffer>;
EncryptAESGCM: any; // AES-GCM
_decodeSnodeAddressToPubKey: any;
decryptToken: any;
encryptForPubkey: any;
encryptForPubkey: (
publicKey: string,
data: Uint8Array
) => Promise<{ ciphertext: Uint8Array; symmetricKey: ArrayBuffer; ephemeralKey: ArrayBuffer }>;
generateEphemeralKeyPair: any;
sha512: any;
}

View file

@ -19,23 +19,17 @@
async function DHEncrypt(symmetricKey, plainText) {
const iv = libsignal.crypto.getRandomBytes(IV_LENGTH);
const ciphertext = await libsignal.crypto.encrypt(
symmetricKey,
plainText,
iv
);
const ivAndCiphertext = new Uint8Array(
iv.byteLength + ciphertext.byteLength
);
const ciphertext = await libsignal.crypto.encrypt(symmetricKey, plainText, iv);
const ivAndCiphertext = new Uint8Array(iv.byteLength + ciphertext.byteLength);
ivAndCiphertext.set(new Uint8Array(iv));
ivAndCiphertext.set(new Uint8Array(ciphertext), iv.byteLength);
return ivAndCiphertext;
}
async function deriveSymmetricKey(pubkey, seckey) {
async function deriveSymmetricKey(x25519PublicKey, x25519PrivateKey) {
const ephemeralSecret = await libsignal.Curve.async.calculateAgreement(
pubkey,
seckey
x25519PublicKey,
x25519PrivateKey
);
const salt = window.Signal.Crypto.bytesFromString('LOKI');
@ -56,28 +50,22 @@
return symmetricKey;
}
// encryptForPubkey: string, payloadBytes: Uint8Array
async function encryptForPubkey(pubkeyX25519, payloadBytes) {
const ephemeral = await libloki.crypto.generateEphemeralKeyPair();
const snPubkey = StringView.hexToArrayBuffer(pubkeyX25519);
const symmetricKey = await deriveSymmetricKey(snPubkey, ephemeral.privKey);
const ciphertext = await EncryptGCM(symmetricKey, payloadBytes);
const ciphertext = await EncryptAESGCM(symmetricKey, payloadBytes);
return { ciphertext, symmetricKey, ephemeralKey: ephemeral.pubKey };
}
async function EncryptGCM(symmetricKey, plaintext) {
async function EncryptAESGCM(symmetricKey, plaintext) {
const nonce = crypto.getRandomValues(new Uint8Array(NONCE_LENGTH));
const key = await crypto.subtle.importKey(
'raw',
symmetricKey,
{ name: 'AES-GCM' },
false,
['encrypt']
);
const key = await crypto.subtle.importKey('raw', symmetricKey, { name: 'AES-GCM' }, false, [
'encrypt',
]);
const ciphertext = await crypto.subtle.encrypt(
{ name: 'AES-GCM', iv: nonce, tagLength: 128 },
@ -85,9 +73,7 @@
plaintext
);
const ivAndCiphertext = new Uint8Array(
NONCE_LENGTH + ciphertext.byteLength
);
const ivAndCiphertext = new Uint8Array(NONCE_LENGTH + ciphertext.byteLength);
ivAndCiphertext.set(nonce);
ivAndCiphertext.set(new Uint8Array(ciphertext), nonce.byteLength);
@ -95,23 +81,14 @@
return ivAndCiphertext;
}
async function DecryptGCM(symmetricKey, ivAndCiphertext) {
async function DecryptAESGCM(symmetricKey, ivAndCiphertext) {
const nonce = ivAndCiphertext.slice(0, NONCE_LENGTH);
const ciphertext = ivAndCiphertext.slice(NONCE_LENGTH);
const key = await crypto.subtle.importKey('raw', symmetricKey, { name: 'AES-GCM' }, false, [
'decrypt',
]);
const key = await crypto.subtle.importKey(
'raw',
symmetricKey,
{ name: 'AES-GCM' },
false,
['decrypt']
);
return crypto.subtle.decrypt(
{ name: 'AES-GCM', iv: nonce },
key,
ciphertext
);
return crypto.subtle.decrypt({ name: 'AES-GCM', iv: nonce }, key, ciphertext);
}
async function DHDecrypt(symmetricKey, ivAndCiphertext) {
@ -152,10 +129,7 @@
throw new Error('Failed to get keypair for token decryption');
}
const { privKey } = keyPair;
const symmetricKey = await libsignal.Curve.async.calculateAgreement(
serverPubKey,
privKey
);
const symmetricKey = await libsignal.Curve.async.calculateAgreement(serverPubKey, privKey);
const token = await DHDecrypt(symmetricKey, ivAndCiphertext);
@ -165,18 +139,13 @@
const sha512 = data => crypto.subtle.digest('SHA-512', data);
const PairingType = Object.freeze({
REQUEST: 1,
GRANT: 2,
});
window.libloki.crypto = {
DHEncrypt,
EncryptGCM, // AES-GCM
EncryptAESGCM, // AES-GCM
DHDecrypt,
DecryptGCM, // AES-GCM
DecryptAESGCM, // AES-GCM
decryptToken,
PairingType,
deriveSymmetricKey,
generateEphemeralKeyPair,
encryptForPubkey,
_decodeSnodeAddressToPubKey: decodeSnodeAddressToPubKey,

View file

@ -1,137 +0,0 @@
/* global dcodeIO, crypto, JSBI */
const NONCE_LEN = 8;
// Modify this value for difficulty scaling
const FALLBACK_DIFFICULTY = 10;
const pow = {
// Increment Uint8Array nonce by '_increment' with carrying
incrementNonce(nonce, _increment = 1) {
let idx = NONCE_LEN - 1;
const newNonce = new Uint8Array(nonce);
let increment = _increment;
do {
const sum = newNonce[idx] + increment;
newNonce[idx] = sum % 256;
increment = Math.floor(sum / 256);
idx -= 1;
} while (increment > 0 && idx >= 0);
return newNonce;
},
// Convert a Uint8Array to a base64 string
bufferToBase64(buf) {
function mapFn(ch) {
return String.fromCharCode(ch);
}
const binaryString = Array.prototype.map.call(buf, mapFn).join('');
return dcodeIO.ByteBuffer.btoa(binaryString);
},
// Convert BigInteger to Uint8Array of length NONCE_LEN
bigIntToUint8Array(bigInt) {
const arr = new Uint8Array(NONCE_LEN);
let n;
for (let idx = NONCE_LEN - 1; idx >= 0; idx -= 1) {
n = NONCE_LEN - (idx + 1);
// 256 ** n is the value of one bit in arr[idx], modulus to carry over
// (bigInt / 256**n) % 256;
const denominator = JSBI.exponentiate(JSBI.BigInt('256'), JSBI.BigInt(n));
const fraction = JSBI.divide(bigInt, denominator);
const uint8Val = JSBI.remainder(fraction, JSBI.BigInt(256));
arr[idx] = JSBI.toNumber(uint8Val);
}
return arr;
},
// Compare two Uint8Arrays, return true if arr1 is > arr2
greaterThan(arr1, arr2) {
// Early exit if lengths are not equal. Should never happen
if (arr1.length !== arr2.length) {
return false;
}
for (let i = 0, len = arr1.length; i < len; i += 1) {
if (arr1[i] > arr2[i]) {
return true;
}
if (arr1[i] < arr2[i]) {
return false;
}
}
return false;
},
// Return nonce that hashes together with payload lower than the target
async calcPoW(
timestamp,
ttl,
pubKey,
data,
_difficulty = null,
increment = 1,
startNonce = 0
) {
const payload = new Uint8Array(
dcodeIO.ByteBuffer.wrap(
timestamp.toString() + ttl.toString() + pubKey + data,
'binary'
).toArrayBuffer()
);
const difficulty = _difficulty || FALLBACK_DIFFICULTY;
const target = pow.calcTarget(ttl, payload.length, difficulty);
let nonce = new Uint8Array(NONCE_LEN);
nonce = pow.incrementNonce(nonce, startNonce); // initial value
let trialValue = new Uint8Array([255, 255, 255, 255, 255, 255, 255, 255]);
const initialHash = new Uint8Array(
await crypto.subtle.digest('SHA-512', payload)
);
const innerPayload = new Uint8Array(initialHash.length + NONCE_LEN);
innerPayload.set(initialHash, NONCE_LEN);
let resultHash;
let nextNonce = nonce;
while (pow.greaterThan(trialValue, target)) {
nonce = nextNonce;
nextNonce = pow.incrementNonce(nonce, increment);
innerPayload.set(nonce);
// eslint-disable-next-line no-await-in-loop
resultHash = await crypto.subtle.digest('SHA-512', innerPayload);
trialValue = new Uint8Array(
dcodeIO.ByteBuffer.wrap(resultHash, 'hex').toArrayBuffer()
).slice(0, NONCE_LEN);
}
return pow.bufferToBase64(nonce);
},
calcTarget(ttl, payloadLen, difficulty = FALLBACK_DIFFICULTY) {
// payloadLength + NONCE_LEN
const totalLen = JSBI.add(JSBI.BigInt(payloadLen), JSBI.BigInt(NONCE_LEN));
// ttl converted to seconds
const ttlSeconds = JSBI.divide(JSBI.BigInt(ttl), JSBI.BigInt(1000));
// ttl * totalLen
const ttlMult = JSBI.multiply(ttlSeconds, JSBI.BigInt(totalLen));
// 2^16 - 1
const two16 = JSBI.subtract(
JSBI.exponentiate(JSBI.BigInt(2), JSBI.BigInt(16)), // 2^16
JSBI.BigInt(1)
);
// ttlMult / two16
const innerFrac = JSBI.divide(ttlMult, two16);
// totalLen + innerFrac
const lenPlusInnerFrac = JSBI.add(totalLen, innerFrac);
// difficulty * lenPlusInnerFrac
const denominator = JSBI.multiply(
JSBI.BigInt(difficulty),
lenPlusInnerFrac
);
// 2^64 - 1
const two64 = JSBI.subtract(
JSBI.exponentiate(JSBI.BigInt(2), JSBI.BigInt(64)), // 2^64
JSBI.BigInt(1)
);
// two64 / denominator
const targetNum = JSBI.divide(two64, denominator);
return pow.bigIntToUint8Array(targetNum);
},
};

View file

@ -1,19 +0,0 @@
/* global window */
// eslint-disable-next-line func-names
(function() {
window.libloki = window.libloki || {};
function getGuardNodes() {
return window.Signal.Data.getGuardNodes();
}
function updateGuardNodes(nodes) {
return window.Signal.Data.updateGuardNodes(nodes);
}
window.libloki.storage = {
getGuardNodes,
updateGuardNodes,
};
})();

View file

@ -1,18 +0,0 @@
{
"env": {
"browser": true,
"node": false,
"mocha": true
},
"parserOptions": {
"sourceType": "script"
},
"rules": {
"strict": "off",
"more/no-then": "off"
},
"globals": {
"assert": true,
"clearDatabase": true
}
}

View file

@ -1,57 +0,0 @@
/* global window, mocha, chai, assert, Whisper */
mocha
.setup('bdd')
.fullTrace()
.timeout(10000);
window.assert = chai.assert;
window.PROTO_ROOT = '../../protos';
const OriginalReporter = mocha._reporter;
const SauceReporter = function Constructor(runner) {
const failedTests = [];
runner.on('end', () => {
window.mochaResults = runner.stats;
window.mochaResults.reports = failedTests;
});
runner.on('fail', (test, err) => {
const flattenTitles = item => {
const titles = [];
while (item.parent.title) {
titles.push(item.parent.title);
// eslint-disable-next-line no-param-reassign
item = item.parent;
}
return titles.reverse();
};
failedTests.push({
name: test.title,
result: false,
message: err.message,
stack: err.stack,
titles: flattenTitles(test),
});
});
// eslint-disable-next-line no-new
new OriginalReporter(runner);
};
SauceReporter.prototype = OriginalReporter.prototype;
mocha.reporter(SauceReporter);
// Override the database id.
window.Whisper = window.Whisper || {};
window.Whisper.Database = window.Whisper.Database || {};
Whisper.Database.id = 'test';
/*
* global helpers for tests
*/
window.clearDatabase = async () => {
await window.Signal.Data.removeAll();
};

View file

@ -1,43 +0,0 @@
<html>
<head>
<meta charset='utf-8'>
<title>libloki test runner</title>
<link rel="stylesheet" href="../../node_modules/mocha/mocha.css" />
</head>
<body>
<div id="mocha">
</div>
<div id="tests">
</div>
<script type="text/javascript" src="test.js"></script>
<script type="text/javascript" src="components.js"></script>
<script type="text/javascript" src="../../libloki/proof-of-work.js"></script>
<script type="text/javascript" src="../../libtextsecure/helpers.js" data-cover></script>
<script type="text/javascript" src="../../libtextsecure/storage.js" data-cover></script>
<script type="text/javascript" src="../../libtextsecure/libsignal-protocol.js"></script>
<script type="text/javascript" src="../../libtextsecure/protobufs.js" data-cover></script>
<script type="text/javascript" src="../../libtextsecure/stringview.js" data-cover></script>
<script type="text/javascript" src="../crypto.js" data-cover></script>
<script type="text/javascript" src="../service_nodes.js" data-cover></script>
<script type="text/javascript" src="../storage.js" data-cover></script>
<script type="text/javascript" src="proof-of-work_test.js"></script>
<script type="text/javascript" src="messages.js"></script>
<!-- Comment out to turn off code coverage. Useful for getting real callstacks. -->
<!-- NOTE: blanket doesn't support modern syntax and will choke until we find a replacement. :0( -->
<!-- <script type="text/javascript" src="blanket_mocha.js"></script> -->
<!-- Uncomment to start tests without code coverage enabled -->
<script type="text/javascript">
mocha.run();
</script>
</body>
</html>

View file

@ -1,222 +0,0 @@
/* global dcodeIO, Plotly */
let jobId = 0;
let currentTrace = 0;
let plotlyDiv;
const workers = [];
async function run(messageLength, numWorkers = 1, difficulty = 100, ttl = 72) {
const timestamp = Math.floor(Date.now() / 1000);
const pubKey =
'05ec8635a07a13743516c7c9b3412f3e8252efb7fcaf67eb1615ffba62bebc6802';
const message = randomString(messageLength);
const messageBuffer = dcodeIO.ByteBuffer.wrap(
message,
'utf8'
).toArrayBuffer();
const data = dcodeIO.ByteBuffer.wrap(messageBuffer).toString('base64');
const promises = [];
const t0 = performance.now();
for (let w = 0; w < numWorkers; w += 1) {
const worker = new Worker('../../js/util_worker.js');
workers.push(worker);
jobId += 1;
const increment = numWorkers;
const index = w;
worker.postMessage([
jobId,
'calcPoW',
timestamp,
ttl * 60 * 60 * 1000,
pubKey,
data,
false,
difficulty,
increment,
index,
]);
const p = new Promise(resolve => {
worker.onmessage = nonce => {
resolve(nonce);
};
});
promises.push(p);
}
await Promise.race(promises);
const t1 = performance.now();
const duration = (t1 - t0) / 1000;
addPoint(duration);
// clean up
workers.forEach(worker => worker.terminate());
}
async function runPoW({
iteration,
difficulty,
numWorkers,
messageLength = 50,
ttl = 72,
}) {
const name = `W:${numWorkers} - NT: ${difficulty} - L:${messageLength} - TTL:${ttl}`;
Plotly.addTraces(plotlyDiv, {
y: [],
type: 'box',
boxpoints: 'all',
name,
});
for (let i = 0; i < iteration; i += 1) {
// eslint-disable-next-line no-await-in-loop
await run(messageLength, numWorkers, difficulty, ttl);
}
currentTrace += 1;
// eslint-disable-next-line
console.log(`done for ${name}`);
}
function randomString(length) {
let text = '';
const possible =
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
for (let i = 0; i < length; i += 1) {
text += possible.charAt(Math.floor(Math.random() * possible.length));
}
return text;
}
function addPoint(duration) {
Plotly.extendTraces(plotlyDiv, { y: [[duration]] }, [currentTrace]);
}
async function startMessageLengthRun() {
const iteration0 = parseFloat(document.getElementById('iteration0').value);
const difficulty0 = parseFloat(document.getElementById('difficulty0').value);
const numWorkers0 = parseFloat(document.getElementById('numWorkers0').value);
const messageLengthStart0 = parseFloat(
document.getElementById('messageLengthStart0').value
);
const messageLengthStop0 = parseFloat(
document.getElementById('messageLengthStop0').value
);
const messageLengthStep0 = parseFloat(
document.getElementById('messageLengthStep0').value
);
const TTL0 = parseFloat(document.getElementById('TTL0').value);
for (
let l = messageLengthStart0;
l < messageLengthStop0;
l += messageLengthStep0
) {
// eslint-disable-next-line no-await-in-loop
await runPoW({
iteration: iteration0,
difficulty: difficulty0,
numWorkers: numWorkers0,
messageLength: l,
ttl: TTL0,
});
}
}
async function startNumWorkerRun() {
const iteration1 = parseFloat(document.getElementById('iteration1').value);
const difficulty1 = parseFloat(document.getElementById('difficulty1').value);
const numWorkersStart1 = parseFloat(
document.getElementById('numWorkersStart1').value
);
const numWorkersEnd1 = parseFloat(
document.getElementById('numWorkersEnd1').value
);
const messageLength1 = parseFloat(
document.getElementById('messageLength1').value
);
const TTL1 = parseFloat(document.getElementById('TTL1').value);
for (
let numWorkers = numWorkersStart1;
numWorkers <= numWorkersEnd1;
numWorkers += 1
) {
// eslint-disable-next-line no-await-in-loop
await runPoW({
iteration: iteration1,
difficulty: difficulty1,
numWorkers,
messageLength: messageLength1,
ttl: TTL1,
});
}
}
async function startDifficultyRun() {
const iteration2 = parseFloat(document.getElementById('iteration2').value);
const messageLength2 = parseFloat(
document.getElementById('messageLength2').value
);
const numWorkers2 = parseFloat(document.getElementById('numWorkers2').value);
const difficultyStart2 = parseFloat(
document.getElementById('difficultyStart2').value
);
const difficultyStop2 = parseFloat(
document.getElementById('difficultyStop2').value
);
const difficultyStep2 = parseFloat(
document.getElementById('difficultyStep2').value
);
const TTL2 = parseFloat(document.getElementById('TTL2').value);
for (let n = difficultyStart2; n < difficultyStop2; n += difficultyStep2) {
// eslint-disable-next-line no-await-in-loop
await runPoW({
iteration: iteration2,
difficulty: n,
numWorkers: numWorkers2,
messageLength: messageLength2,
ttl: TTL2,
});
}
}
async function starTTLRun() {
const iteration3 = parseFloat(document.getElementById('iteration3').value);
const difficulty3 = parseFloat(document.getElementById('difficulty3').value);
const messageLength3 = parseFloat(
document.getElementById('messageLength3').value
);
const numWorkers3 = parseFloat(document.getElementById('numWorkers3').value);
const TTLStart3 = parseFloat(document.getElementById('TTLStart3').value);
const TTLStop3 = parseFloat(document.getElementById('TTLStop3').value);
const TTLStep3 = parseFloat(document.getElementById('TTLStep3').value);
for (let ttl = TTLStart3; ttl < TTLStop3; ttl += TTLStep3) {
// eslint-disable-next-line no-await-in-loop
await runPoW({
iteration: iteration3,
difficulty: difficulty3,
numWorkers: numWorkers3,
messageLength: messageLength3,
ttl,
});
}
}
// eslint-disable-next-line no-unused-vars
async function start(index) {
const data = [];
const layout = {};
const options = {
responsive: true,
};
plotlyDiv = `plotly${index}`;
currentTrace = 0;
window.chart = Plotly.newPlot(plotlyDiv, data, layout, options);
workers.forEach(worker => worker.terminate());
switch (index) {
case 0:
await startMessageLengthRun();
break;
case 1:
await startNumWorkerRun();
break;
case 2:
await startDifficultyRun();
break;
case 3:
await starTTLRun();
break;
default:
break;
}
}

View file

@ -1,31 +0,0 @@
// For reference: https://github.com/airbnb/javascript
module.exports = {
env: {
node: true,
mocha: true,
browser: true,
},
globals: {
check: true,
gen: true,
},
parserOptions: {
sourceType: 'module',
},
rules: {
// We still get the value of this rule, it just allows for dev deps
'import/no-extraneous-dependencies': [
'error',
{
devDependencies: true,
},
],
// We want to keep each test structured the same, even if its contents are tiny
'arrow-body-style': 'off',
},
};

View file

@ -1,111 +0,0 @@
/* global assert, JSBI, pow */
const {
calcTarget,
incrementNonce,
bufferToBase64,
bigIntToUint8Array,
greaterThan,
} = pow;
describe('Proof of Work', () => {
describe('#incrementNonce', () => {
it('should increment a Uint8Array nonce correctly', () => {
const arr1Before = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0]);
const arr1After = incrementNonce(arr1Before);
assert.strictEqual(arr1After[0], 0);
assert.strictEqual(arr1After[1], 0);
assert.strictEqual(arr1After[2], 0);
assert.strictEqual(arr1After[3], 0);
assert.strictEqual(arr1After[4], 0);
assert.strictEqual(arr1After[5], 0);
assert.strictEqual(arr1After[6], 0);
assert.strictEqual(arr1After[7], 1);
});
it('should increment a Uint8Array nonce correctly in a loop', () => {
let arr = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0]);
assert.deepEqual(
incrementNonce(arr),
new Uint8Array([0, 0, 0, 0, 0, 0, 0, 1])
);
arr = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0]);
for (let i = 0; i <= 255; i += 1) {
arr = incrementNonce(arr);
}
assert.deepEqual(arr, new Uint8Array([0, 0, 0, 0, 0, 0, 1, 0]));
arr = new Uint8Array([255, 255, 255, 255, 255, 255, 255, 255]);
assert.deepEqual(
incrementNonce(arr),
new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0])
);
});
});
describe('#calcTarget', () => {
it('should calculate a correct difficulty target', () => {
// These values will need to be updated if we adjust the difficulty settings
let payloadLen = 625;
const ttl = 86400000;
let expectedTarget = new Uint8Array([0, 4, 119, 164, 35, 224, 222, 64]);
let actualTarget = calcTarget(ttl, payloadLen, 10);
assert.deepEqual(actualTarget, expectedTarget);
payloadLen = 6597;
expectedTarget = new Uint8Array([0, 0, 109, 145, 174, 146, 124, 3]);
actualTarget = calcTarget(ttl, payloadLen, 10);
assert.deepEqual(actualTarget, expectedTarget);
});
});
describe('#greaterThan', () => {
it('should correclty compare two Uint8Arrays', () => {
let arr1 = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 1]);
let arr2 = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 1]);
assert.isFalse(greaterThan(arr1, arr2));
arr1 = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 2]);
arr2 = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 1]);
assert.isTrue(greaterThan(arr1, arr2));
arr1 = new Uint8Array([255, 255, 255, 255, 255, 255, 255, 255, 255, 255]);
arr2 = new Uint8Array([255, 255, 255, 255, 255, 255, 255, 255, 255, 254]);
assert.isTrue(greaterThan(arr1, arr2));
arr1 = new Uint8Array([254, 255, 255, 255, 255, 255, 255, 255, 255, 255]);
arr2 = new Uint8Array([255, 255, 255, 255, 255, 255, 255, 255, 255, 255]);
assert.isFalse(greaterThan(arr1, arr2));
arr1 = new Uint8Array([0]);
arr2 = new Uint8Array([0, 0]);
assert.isFalse(greaterThan(arr1, arr2));
});
});
describe('#bufferToBase64', () => {
it('should correclty convert a Uint8Array to a base64 string', () => {
let arr = new Uint8Array([1, 2, 3]);
let expected = 'AQID';
assert.strictEqual(bufferToBase64(arr), expected);
arr = new Uint8Array([123, 25, 3, 121, 45, 87, 24, 111]);
expected = 'exkDeS1XGG8=';
assert.strictEqual(bufferToBase64(arr), expected);
arr = new Uint8Array([]);
expected = '';
assert.strictEqual(bufferToBase64(arr), expected);
});
});
describe('#bigIntToUint8Array', () => {
it('should correclty convert a BigInteger to a Uint8Array', () => {
let bigInt = JSBI.BigInt(Number.MAX_SAFE_INTEGER);
let expected = new Uint8Array([0, 31, 255, 255, 255, 255, 255, 255]);
assert.deepEqual(bigIntToUint8Array(bigInt), expected);
bigInt = JSBI.BigInt('0');
expected = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0]);
assert.deepEqual(bigIntToUint8Array(bigInt), expected);
bigInt = JSBI.BigInt('255');
expected = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 255]);
assert.deepEqual(bigIntToUint8Array(bigInt), expected);
bigInt = JSBI.BigInt('256');
expected = new Uint8Array([0, 0, 0, 0, 0, 0, 1, 0]);
assert.deepEqual(bigIntToUint8Array(bigInt), expected);
});
});
});

View file

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

View file

@ -114,19 +114,6 @@
}
}
function WrongDifficultyError(newDifficulty) {
this.name = 'WrongDifficultyError';
this.newDifficulty = newDifficulty;
Error.call(this, this.name);
// Maintains proper stack trace, where our error was thrown (only available on V8)
// via https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error
if (Error.captureStackTrace) {
Error.captureStackTrace(this);
}
}
function TimestampError(message) {
this.name = 'TimeStampError';
@ -156,7 +143,6 @@
window.textsecure.HTTPError = HTTPError;
window.textsecure.NotFoundError = NotFoundError;
window.textsecure.WrongSwarmError = WrongSwarmError;
window.textsecure.WrongDifficultyError = WrongDifficultyError;
window.textsecure.TimestampError = TimestampError;
window.textsecure.PublicChatError = PublicChatError;
})();

View file

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

View file

@ -11,7 +11,6 @@ export interface LibTextsecure {
HTTPError: any;
NotFoundError: any;
WrongSwarmError: any;
WrongDifficultyError: any;
TimestampError: any;
PublicChatError: any;
createTaskWithTimeout(task: any, id: any, options?: any): Promise<any>;

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

95
main.js
View file

@ -46,8 +46,7 @@ function getMainWindow() {
// Tray icon and related objects
let tray = null;
const startInTray = process.argv.some(arg => arg === '--start-in-tray');
const usingTrayIcon =
startInTray || process.argv.some(arg => arg === '--use-tray-icon');
const usingTrayIcon = startInTray || process.argv.some(arg => arg === '--use-tray-icon');
const config = require('./app/config');
@ -56,8 +55,7 @@ const config = require('./app/config');
const userConfig = require('./app/user_config');
const passwordUtil = require('./ts/util/passwordUtils');
const importMode =
process.argv.some(arg => arg === '--import') || config.get('import');
const importMode = process.argv.some(arg => arg === '--import') || config.get('import');
const development = config.environment === 'development';
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 windowState = require('./app/window_state');
const { createTemplate } = require('./app/menu');
const {
installFileHandler,
installWebHandler,
} = require('./app/protocol_filter');
const { installFileHandler, installWebHandler } = require('./app/protocol_filter');
const { installPermissionsHandler } = require('./app/permissions');
const _sodium = require('libsodium-wrappers');
@ -174,10 +169,8 @@ function prepareURL(pathSegments, moreKeys) {
serverUrl: config.get('serverUrl'),
localUrl: config.get('localUrl'),
cdnUrl: config.get('cdnUrl'),
defaultPoWDifficulty: config.get('defaultPoWDifficulty'),
// one day explain why we need to do this - neuroscr
seedNodeList: JSON.stringify(config.get('seedNodeList')),
certificateAuthority: config.get('certificateAuthority'),
environment: config.environment,
node_version: process.versions.node,
hostname: os.hostname(),
@ -219,10 +212,7 @@ function getWindowSize() {
const { minWidth, minHeight, defaultWidth, defaultHeight } = WINDOW_SIZE;
// Ensure that the screen can fit within the default size
const width = Math.min(defaultWidth, Math.max(minWidth, screenSize.width));
const height = Math.min(
defaultHeight,
Math.max(minHeight, screenSize.height)
);
const height = Math.min(defaultHeight, Math.max(minHeight, screenSize.height));
return { width, height, minWidth, minHeight };
}
@ -235,15 +225,12 @@ function isVisible(window, bounds) {
const BOUNDS_BUFFER = 100;
// requiring BOUNDS_BUFFER pixels on the left or right side
const rightSideClearOfLeftBound =
window.x + window.width >= boundsX + BOUNDS_BUFFER;
const leftSideClearOfRightBound =
window.x <= boundsX + boundsWidth - BOUNDS_BUFFER;
const rightSideClearOfLeftBound = window.x + window.width >= boundsX + BOUNDS_BUFFER;
const leftSideClearOfRightBound = window.x <= boundsX + boundsWidth - BOUNDS_BUFFER;
// top can't be offscreen, and must show at least BOUNDS_BUFFER pixels at bottom
const topClearOfUpperBound = window.y >= boundsY;
const topClearOfLowerBound =
window.y <= boundsY + boundsHeight - BOUNDS_BUFFER;
const topClearOfLowerBound = window.y <= boundsY + boundsHeight - BOUNDS_BUFFER;
return (
rightSideClearOfLeftBound &&
@ -276,14 +263,7 @@ async function createWindow() {
},
icon: path.join(__dirname, 'images', 'session', 'session_icon_64.png'),
},
_.pick(windowConfig, [
'maximized',
'autoHideMenuBar',
'width',
'height',
'x',
'y',
])
_.pick(windowConfig, ['maximized', 'autoHideMenuBar', 'width', 'height', 'x', 'y'])
);
if (!_.isNumber(windowOptions.width) || windowOptions.width < minWidth) {
@ -316,10 +296,7 @@ async function createWindow() {
delete windowOptions.fullscreen;
}
logger.info(
'Initializing BrowserWindow config: %s',
JSON.stringify(windowOptions)
);
logger.info('Initializing BrowserWindow config: %s', JSON.stringify(windowOptions));
// Create the browser window.
mainWindow = new BrowserWindow(windowOptions);
@ -356,10 +333,7 @@ async function createWindow() {
windowConfig.fullscreen = true;
}
logger.info(
'Updating BrowserWindow config: %s',
JSON.stringify(windowConfig)
);
logger.info('Updating BrowserWindow config: %s', JSON.stringify(windowConfig));
ephemeralConfig.set('window', windowConfig);
}
@ -378,13 +352,7 @@ async function createWindow() {
if (config.environment === 'test') {
mainWindow.loadURL(prepareURL([__dirname, 'test', 'index.html']));
} else if (config.environment === 'test-lib') {
mainWindow.loadURL(
prepareURL([__dirname, 'libtextsecure', 'test', 'index.html'])
);
} else if (config.environment === 'test-loki') {
mainWindow.loadURL(
prepareURL([__dirname, 'libloki', 'test', 'index.html'])
);
mainWindow.loadURL(prepareURL([__dirname, 'libtextsecure', 'test', 'index.html']));
} else if (config.environment.includes('test-integration')) {
mainWindow.loadURL(prepareURL([__dirname, 'background_test.html']));
} else {
@ -410,7 +378,6 @@ async function createWindow() {
if (
config.environment === 'test' ||
config.environment === 'test-lib' ||
config.environment === 'test-loki' ||
config.environment.includes('test-integration') ||
(mainWindow.readyForShutdown && windowState.shouldQuit())
) {
@ -423,10 +390,7 @@ async function createWindow() {
// 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
if (
!windowState.shouldQuit() &&
(usingTrayIcon || process.platform === 'darwin')
) {
if (!windowState.shouldQuit() && (usingTrayIcon || process.platform === 'darwin')) {
// toggle the visibility of the show/hide tray icon menu entries
if (tray) {
tray.updateContextMenu();
@ -477,10 +441,7 @@ async function readyForUpdates() {
await updater.start(getMainWindow, userConfig, locale.messages, logger);
} catch (error) {
const log = logger || console;
log.error(
'Error starting update checks:',
error && error.stack ? error.stack : error
);
log.error('Error starting update checks:', error && error.stack ? error.stack : error);
}
}
ipc.once('ready-for-updates', readyForUpdates);
@ -556,10 +517,7 @@ function showPasswordWindow() {
// 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
if (
!windowState.shouldQuit() &&
(usingTrayIcon || process.platform === 'darwin')
) {
if (!windowState.shouldQuit() && (usingTrayIcon || process.platform === 'darwin')) {
// toggle the visibility of the show/hide tray icon menu entries
if (tray) {
tray.updateContextMenu();
@ -677,7 +635,6 @@ app.on('ready', async () => {
if (
process.env.NODE_ENV !== 'test' &&
process.env.NODE_ENV !== 'test-lib' &&
process.env.NODE_ENV !== 'test-loki' &&
!process.env.NODE_ENV.includes('test-integration')
) {
installFileHandler({
@ -718,9 +675,7 @@ app.on('ready', async () => {
function getDefaultSQLKey() {
let key = userConfig.get('key');
if (!key) {
console.log(
'key/initialize: Generating new encryption key, since we did not find it on disk'
);
console.log('key/initialize: Generating new encryption key, since we did not find it on disk');
// https://www.zetetic.net/sqlcipher/sqlcipher-api/#key
key = crypto.randomBytes(32).toString('hex');
userConfig.set('key', key);
@ -755,9 +710,7 @@ async function showMainWindow(sqlKey, passwordAttempt = false) {
async function cleanupOrphanedAttachments() {
const allAttachments = await attachments.getAllAttachments(userDataPath);
const orphanedAttachments = await sql.removeKnownAttachments(
allAttachments
);
const orphanedAttachments = await sql.removeKnownAttachments(allAttachments);
await attachments.deleteAll({
userDataPath,
attachments: orphanedAttachments,
@ -824,9 +777,7 @@ async function requestShutdown() {
// 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.
setTimeout(() => {
console.log(
'requestShutdown: Response never received; forcing shutdown.'
);
console.log('requestShutdown: Response never received; forcing shutdown.');
resolve();
}, 2 * 60 * 1000);
});
@ -834,10 +785,7 @@ async function requestShutdown() {
try {
await request;
} catch (error) {
console.log(
'requestShutdown error:',
error && error.stack ? error.stack : error
);
console.log('requestShutdown error:', error && error.stack ? error.stack : error);
}
}
@ -858,7 +806,6 @@ app.on('window-all-closed', () => {
process.platform !== 'darwin' ||
config.environment === 'test' ||
config.environment === 'test-lib' ||
config.environment === 'test-loki' ||
config.environment.includes('test-integration')
) {
app.quit();
@ -955,8 +902,7 @@ ipc.on('update-tray-icon', (event, unreadCount) => {
// Password screen related IPC calls
ipc.on('password-window-login', async (event, passPhrase) => {
const sendResponse = e =>
event.sender.send('password-window-login-response', e);
const sendResponse = e => event.sender.send('password-window-login-response', e);
try {
const passwordAttempt = true;
@ -978,8 +924,7 @@ ipc.on('set-password', async (event, passPhrase, oldPhrase) => {
if (hash && !hashMatches) {
const incorrectOldPassword = locale.messages.invalidOldPassword.message;
sendResponse(
incorrectOldPassword ||
'Failed to set password: Old password provided is invalid'
incorrectOldPassword || 'Failed to set password: Old password provided is invalid'
);
return;
}

View file

@ -1,66 +0,0 @@
<html>
<head>
<meta charset='utf-8'>
<title>pow calculations</title>
<style>
input[type=number] {
width: 50px;
}
</style>
<script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
</head>
<body>
<h2>Influence of message length</h2>
<label for="iteration0">Iterations:</label><input name="iteration0" id ="iteration0" type="number" value="20"/>
<label for="nonceTrials0">Nonce Trials:</label><input name="nonceTrials0" id ="nonceTrials0" type="number" value="100"/>
<label for="numWorkers0">Number of workers:</label><input name="numWorkers0" id ="numWorkers0" type="number" value="1"/>
<label for="TTL0">TTL:</label><input name="TTL0" id ="TTL0" type="number" value="72"/>
<label for="messageLengthStart0">Message length start:</label><input name="messageLengthStart0" id ="messageLengthStart0" type="number" value="50"/>
<label for="messageLengthStop0">Message length stop:</label><input name="messageLengthStop0" id ="messageLengthStop0" type="number" value="1000"/>
<label for="messageLengthStep0">Message length step:</label><input name="messageLengthStep0" id ="messageLengthStep0" type="number" value="100"/>
<br>
<button onclick="start(0)">Run</button>
<div id="plotly0"></div>
<h2>Influence of workers</h2>
<label for="iteration1">Iterations:</label><input name="iteration1" id ="iteration1" type="number" value="20"/>
<label for="nonceTrials1">Nonce Trials:</label><input name="nonceTrials1" id ="nonceTrials1" type="number" value="100"/>
<label for="TTL1">TTL:</label><input name="TTL1" id ="TTL1" type="number" value="72"/>
<label for="numWorkersStart1">Number of workers start:</label><input name="numWorkersStart1" id ="numWorkersStart1" type="number" value="1"/>
<label for="numWorkersEnd1">Number of workers end:</label><input name="numWorkersEnd1" id ="numWorkersEnd1" type="number" value="4"/>
<label for="messageLength1">Message length stop:</label><input name="messageLength1" id ="messageLength1" type="number" value="100"/>
<br>
<button onclick="start(1)">Run</button>
<div id="plotly1"></div>
<h2>Influence of NonceTrials</h2>
<label for="iteration2">Iterations:</label><input name="iteration2" id ="iteration2" type="number" value="20"/>
<label for="messageLength2">Message length:</label><input name="messageLength2" id ="messageLength2" type="number" value="100"/>
<label for="numWorkers2">Number of workers:</label><input name="numWorkers2" id ="numWorkers2" type="number" value="1"/>
<label for="TTL2">TTL:</label><input name="TTL2" id ="TTL2" type="number" value="72"/>
<label for="nonceTrialsStart2">Nonce Trials start:</label><input name="nonceTrialsStart2" id ="nonceTrialsStart2" type="number" value="10"/>
<label for="nonceTrialsStop2">Nonce Trials stop:</label><input name="nonceTrialsStop2" id ="nonceTrialsStop2" type="number" value="100"/>
<label for="nonceTrialsStep2">Nonce Trials step:</label><input name="nonceTrialsStep2" id ="nonceTrialsStep2" type="number" value="10"/>
<br>
<button onclick="start(2)">Run</button>
<div id="plotly2"></div>
<h2>Influence of TTL</h2>
<label for="iteration3">Iterations:</label><input name="iteration3" id ="iteration3" type="number" value="20"/>
<label for="nonceTrials3">Nonce Trials:</label><input name="nonceTrials3" id ="nonceTrials3" type="number" value="100"/>
<label for="messageLength3">Message length:</label><input name="messageLength3" id ="messageLength3" type="number" value="100"/>
<label for="numWorkers3">Number of workers:</label><input name="numWorkers3" id ="numWorkers3" type="number" value="1"/>
<label for="TTLStart3">TTL start:</label><input name="TTLStart3" id ="TTLStart3" type="number" value="12"/>
<label for="TTLStop3">TTL stop:</label><input name="TTLStop3" id ="TTLStop3" type="number" value="96"/>
<label for="TTLStep3">TTL step:</label><input name="TTLStep3" id ="TTLStep3" type="number" value="12"/>
<br>
<button onclick="start(3)">Run</button>
<div id="plotly3"></div>
<script type="text/javascript" src="libloki/test/components.js"></script>
<script type="text/javascript" src="libloki/proof-of-work.js"></script>
<script type="text/javascript" src="libloki/test/metrics.js"></script>
</body>
</html>

View file

@ -1,67 +0,0 @@
const http = require('http');
const url = require('url');
const fs = require('fs');
const path = require('path');
// you can pass the parameter in the command line. e.g. node static_server.js 3000
const port = process.argv[3] || 9000;
const hostname = process.argv[2] || 'localhost';
// maps file extention to MIME types
const mimeType = {
'.ico': 'image/x-icon',
'.html': 'text/html',
'.js': 'text/javascript',
'.json': 'application/json',
'.css': 'text/css',
'.png': 'image/png',
'.jpg': 'image/jpeg',
'.wav': 'audio/wav',
'.mp3': 'audio/mpeg',
'.svg': 'image/svg+xml',
'.pdf': 'application/pdf',
'.doc': 'application/msword',
'.eot': 'appliaction/vnd.ms-fontobject',
'.ttf': 'aplication/font-sfnt',
};
http
.createServer((req, res) => {
// console.log(`${req.method} ${req.url}`);
// parse URL
const parsedUrl = url.parse(req.url);
// extract URL path
// Avoid https://en.wikipedia.org/wiki/Directory_traversal_attack
// e.g curl --path-as-is http://localhost:9000/../fileInDanger.txt
// by limiting the path to current directory only
const sanitizePath = path
.normalize(parsedUrl.pathname)
.replace(/^(\.\.[/\\])+/, '');
let pathname = path.join(__dirname, sanitizePath);
fs.exists(pathname, exist => {
if (!exist) {
// if the file is not found, return 404
res.statusCode = 404;
res.end(`File ${pathname} not found!`);
return;
}
// if is a directory, then look for index.html
if (fs.statSync(pathname).isDirectory()) {
pathname += '/index.html';
}
// read file from file system
fs.readFile(pathname, (err, data) => {
if (err) {
res.statusCode = 500;
res.end(`Error getting the file: ${err}.`);
} else {
// based on the URL path, extract the file extention. e.g. .js, .doc, ...
const { ext } = path.parse(pathname);
// if the file is found, set Content-type and send data
res.setHeader('Content-type', mimeType[ext] || 'text/plain');
res.end(data);
}
});
});
})
.listen(parseInt(port, 10), hostname);
// eslint-disable-next-line
console.log(`metrics running on http://${hostname}:${port}/metrics.html`);

View file

@ -2,7 +2,7 @@
"name": "session-desktop",
"productName": "Session",
"description": "Private messaging from your desktop",
"version": "1.5.4",
"version": "1.5.5",
"license": "GPL-3.0",
"author": {
"name": "Loki Project",
@ -21,6 +21,7 @@
"start-prod": "cross-env NODE_ENV=production NODE_APP_INSTANCE=devprod$MULTI electron .",
"start-swarm-test": "cross-env NODE_ENV=swarm-testing NODE_APP_INSTANCE=$MULTI electron .",
"grunt": "grunt",
"grunt:dev": "yarn clean-transpile; yarn grunt dev --force",
"icon-gen": "electron-icon-maker --input=images/session/session_icon_1024.png --output=./build",
"generate": "yarn icon-gen && yarn grunt --force",
"build-release": "cross-env SIGNAL_ENV=production electron-builder --config.extraMetadata.environment=production --publish=never --config.directories.output=release",
@ -31,29 +32,21 @@
"test": "yarn test-node && yarn test-electron",
"test-view": "NODE_ENV=test yarn run start",
"test-lib-view": "NODE_ENV=test-lib yarn run start",
"test-loki-view": "NODE_ENV=test-loki yarn run start",
"test-electron": "yarn grunt test",
"test-integration": "ELECTRON_DISABLE_SANDBOX=1 mocha --exit --full-trace --timeout 10000 ts/test/session/integration/integration_itest.js",
"test-node": "mocha --recursive --exit --timeout 10000 test/app test/modules \"./ts/test/**/*_test.js\" libloki/test/node ",
"eslint": "eslint --cache .",
"eslint-fix": "eslint --fix .",
"eslint-full": "eslint .",
"lint": "yarn format && yarn lint-files",
"test-node": "mocha --recursive --exit --timeout 10000 test/app test/modules \"./ts/test/**/*_test.js\" ",
"eslint-full": "eslint . --cache",
"lint-full": "yarn format-full && yarn lint-files-full",
"dev-lint": "yarn format && yarn lint-files",
"lint-files": "yarn eslint && yarn tslint",
"lint-files-full": "yarn eslint-full && yarn tslint",
"lint-deps": "node ts/util/lint/linter.js",
"tslint": "tslint --format stylish --project .",
"format": "prettier --list-different --write `git ls-files --modified *.{css,js,json,scss,ts,tsx}` `git ls-files --modified ./**/*.{css,js,json,scss,ts,tsx}`",
"format-full": "prettier --list-different --write \"*.{css,js,json,scss,ts,tsx}\" \"./**/*.{css,js,json,scss,ts,tsx}\"",
"transpile": "tsc --incremental",
"clean-transpile": "rimraf ts/**/*.js && rimraf ts/*.js",
"pow-metrics": "node metrics_app.js localhost 9000",
"ready": "yarn clean-transpile && yarn grunt && yarn lint-full && yarn test-node && yarn test-electron && yarn lint-deps"
"transpile:watch": "tsc -w",
"clean-transpile": "rimraf ts/**/*.js && rimraf ts/*.js && rimraf tsconfig.tsbuildinfo;",
"ready": "yarn clean-transpile; yarn grunt && yarn lint-full && yarn test"
},
"dependencies": {
"@journeyapps/sqlcipher": "https://github.com/scottnonnenberg-signal/node-sqlcipher.git#b10f232fac62ba7f8775c9e086bb5558fe7d948b",
"@journeyapps/sqlcipher": "https://github.com/Bilb/node-sqlcipher.git#98039b72e197171b69358b900bc179ffc22e1f32",
"@reduxjs/toolkit": "^1.4.0",
"@sindresorhus/is": "0.8.0",
"abort-controller": "3.0.0",
@ -219,7 +212,7 @@
"webpack": "4.4.1"
},
"engines": {
"node": "^10.13.0"
"node": "^10.19.0"
},
"build": {
"appId": "com.loki-project.messenger-desktop",

View file

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

View file

@ -15,7 +15,6 @@ const { clipboard } = electron;
window.PROTO_ROOT = 'protos';
const appConfig = require('./app/config');
const config = require('url').parse(window.location.toString(), true).query;
let title = config.name;
@ -36,7 +35,6 @@ window.displayNameRegex = /[^\u0041-\u005A\u0061-\u007A\u00AA\u00B5\u00BA\u00C0-
window.semver = semver;
window.platform = process.platform;
window.getDefaultPoWDifficulty = () => config.defaultPoWDifficulty;
window.getTitle = () => title;
window.getEnvironment = () => config.environment;
window.isDev = () => config.environment === 'development';
@ -50,25 +48,22 @@ window.getHostName = () => config.hostname;
window.getServerTrustRoot = () => config.serverTrustRoot;
window.JobQueue = JobQueue;
window.isBehindProxy = () => Boolean(config.proxyUrl);
window.getStoragePubKey = key =>
window.isDev() ? key.substring(0, key.length - 2) : key;
window.getStoragePubKey = key => (window.isDev() ? key.substring(0, key.length - 2) : key);
window.getDefaultFileServer = () => config.defaultFileServer;
window.initialisedAPI = false;
window.lokiFeatureFlags = {
useOnionRequests: true,
useOnionRequestsV2: true,
useFileOnionRequests: true,
useFileOnionRequestsV2: true, // more compact encoding of files in response
onionRequestHops: 3,
useRequestEncryptionKeyPair: false,
padOutgoingAttachments: false,
padOutgoingAttachments: true,
};
if (
typeof process.env.NODE_ENV === 'string' &&
process.env.NODE_ENV.includes('test-integration')
) {
if (typeof process.env.NODE_ENV === 'string' && process.env.NODE_ENV.includes('test-integration')) {
window.electronRequire = require;
// during test-integration, file server is started on localhost
window.getDefaultFileServer = () => 'http://127.0.0.1:7070';
@ -89,23 +84,16 @@ window.isBeforeVersion = (toCheck, baseVersion) => {
// eslint-disable-next-line func-names
window.CONSTANTS = new (function() {
this.MAX_GROUP_NAME_LENGTH = 64;
this.DEFAULT_PUBLIC_CHAT_URL = appConfig.get('defaultPublicChatServer');
this.MAX_LINKED_DEVICES = 1;
this.MAX_CONNECTION_DURATION = 5000;
this.CLOSED_GROUP_SIZE_LIMIT = 100;
// Number of seconds to turn on notifications after reconnect/start of app
this.NOTIFICATION_ENABLE_TIMEOUT_SECONDS = 10;
this.SESSION_ID_LENGTH = 66;
// Loki Name System (LNS)
this.LNS_DEFAULT_LOOKUP_TIMEOUT = 6000;
// Minimum nodes version for LNS lookup
this.LNS_CAPABLE_NODES_VERSION = '2.0.3';
this.LNS_MAX_LENGTH = 64;
// Conforms to naming rules here
// 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 -
2}}[a-zA-Z0-9_]){0,1}$`;
this.LNS_REGEX = `^[a-zA-Z0-9_]([a-zA-Z0-9_-]{0,${this.LNS_MAX_LENGTH - 2}}[a-zA-Z0-9_]){0,1}$`;
this.MIN_GUARD_COUNT = 2;
this.DESIRED_GUARD_COUNT = 3;
})();
@ -189,11 +177,9 @@ window.showWindow = () => {
ipc.send('show-window');
};
window.setAutoHideMenuBar = autoHide =>
ipc.send('set-auto-hide-menu-bar', autoHide);
window.setAutoHideMenuBar = autoHide => ipc.send('set-auto-hide-menu-bar', autoHide);
window.setMenuBarVisibility = visibility =>
ipc.send('set-menu-bar-visibility', visibility);
window.setMenuBarVisibility = visibility => ipc.send('set-menu-bar-visibility', visibility);
window.restart = () => {
window.log.info('restart');
@ -217,10 +203,7 @@ window.onUnblockNumber = async number => {
const conversation = window.getConversationController().get(number);
await conversation.unblock();
} catch (e) {
window.log.info(
'IPC on unblock: failed to fetch conversation for number: ',
number
);
window.log.info('IPC on unblock: failed to fetch conversation for number: ', number);
}
}
};
@ -232,8 +215,7 @@ ipc.on('mediaPermissionsChanged', () => {
window.closeAbout = () => ipc.send('close-about');
window.readyForUpdates = () => ipc.send('ready-for-updates');
window.updateTrayIcon = unreadCount =>
ipc.send('update-tray-icon', unreadCount);
window.updateTrayIcon = unreadCount => ipc.send('update-tray-icon', unreadCount);
ipc.on('set-up-with-import', () => {
Whisper.events.trigger('setupWithImport');
@ -292,13 +274,11 @@ window.setSettingValue = (settingID, value) => {
};
window.getMediaPermissions = () => ipc.sendSync('get-media-permissions');
window.setMediaPermissions = value =>
ipc.send('set-media-permissions', !!value);
window.setMediaPermissions = value => ipc.send('set-media-permissions', !!value);
// Auto update setting
window.getAutoUpdateEnabled = () => ipc.sendSync('get-auto-update-setting');
window.setAutoUpdateEnabled = value =>
ipc.send('set-auto-update-setting', !!value);
window.setAutoUpdateEnabled = value => ipc.send('set-auto-update-setting', !!value);
ipc.on('get-ready-for-shutdown', async () => {
const { shutdown } = window.Events || {};
@ -312,10 +292,7 @@ ipc.on('get-ready-for-shutdown', async () => {
await shutdown();
ipc.send('now-ready-for-shutdown');
} catch (error) {
ipc.send(
'now-ready-for-shutdown',
error && error.stack ? error.stack : error
);
ipc.send('now-ready-for-shutdown', error && error.stack ? error.stack : error);
}
});
@ -342,14 +319,9 @@ window.Signal = Signal.setup({
});
if (process.env.USE_STUBBED_NETWORK) {
const StubMessageAPI = require('./ts/test/session/integration/stubs/stub_message_api');
window.LokiMessageAPI = StubMessageAPI;
const StubAppDotNetAPI = require('./ts/test/session/integration/stubs/stub_app_dot_net_api');
window.LokiAppDotNetServerAPI = StubAppDotNetAPI;
} else {
window.LokiMessageAPI = require('./js/modules/loki_message_api');
window.LokiAppDotNetServerAPI = require('./js/modules/loki_app_dot_net_api');
}
window.LokiPublicChatAPI = require('./js/modules/loki_public_chat_api');
@ -415,8 +387,7 @@ window.models = require('./ts/models');
window.Signal = window.Signal || {};
window.Signal.Data = require('./ts/data/data');
window.getMessageController = () =>
window.libsession.Messages.MessageController.getInstance();
window.getMessageController = () => window.libsession.Messages.MessageController.getInstance();
window.getConversationController = () =>
window.libsession.Conversations.ConversationController.getInstance();
@ -426,9 +397,7 @@ window.Signal.Backup = require('./js/modules/backup');
window.Signal.Logs = require('./js/modules/logs');
window.addEventListener('contextmenu', e => {
const editable = e.target.closest(
'textarea, input, [contenteditable="true"]'
);
const editable = e.target.closest('textarea, input, [contenteditable="true"]');
const link = e.target.closest('a');
const selection = Boolean(window.getSelection().toString());
if (!editable && !selection && !link) {
@ -442,9 +411,7 @@ window.NewSnodeAPI = require('./ts/session/snode_api/serviceNodeAPI');
window.SnodePool = require('./ts/session/snode_api/snodePool');
if (process.env.USE_STUBBED_NETWORK) {
const {
SwarmPollingStub,
} = require('./ts/session/snode_api/swarmPollingStub');
const { SwarmPollingStub } = require('./ts/session/snode_api/swarmPollingStub');
window.SwarmPolling = new SwarmPollingStub();
} else {
const { SwarmPolling } = require('./ts/session/snode_api/swarmPolling');
@ -480,7 +447,6 @@ if (config.environment.includes('test-integration')) {
window.lokiFeatureFlags = {
useOnionRequests: false,
useFileOnionRequests: false,
useOnionRequestsV2: false,
useRequestEncryptionKeyPair: false,
};
/* eslint-disable global-require, import/no-extraneous-dependencies */
@ -490,8 +456,6 @@ if (config.environment.includes('test-integration')) {
// Blocking
const {
BlockedNumberController,
} = require('./ts/util/blockedNumberController');
const { BlockedNumberController } = require('./ts/util/blockedNumberController');
window.BlockedNumberController = BlockedNumberController;

View file

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

View file

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

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