mirror of
https://github.com/oxen-io/session-desktop.git
synced 2023-12-14 02:12:57 +01:00
commit
4fffb8c93e
384 changed files with 8079 additions and 10060 deletions
|
@ -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',
|
||||
|
|
18
.github/workflows/build-binaries.yml
vendored
18
.github/workflows/build-binaries.yml
vendored
|
@ -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
|
||||
|
|
30
.github/workflows/pull-request.yml
vendored
30
.github/workflows/pull-request.yml
vendored
|
@ -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
|
||||
|
||||
|
|
15
.github/workflows/release.yml
vendored
15
.github/workflows/release.yml
vendored
|
@ -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
2
.nvmrc
|
@ -1 +1 @@
|
|||
10.13.0
|
||||
10.19.0
|
||||
|
|
|
@ -3,4 +3,5 @@ module.exports = {
|
|||
trailingComma: 'es5',
|
||||
bracketSpacing: true,
|
||||
arrowParens: 'avoid',
|
||||
printWidth: 100,
|
||||
};
|
||||
|
|
|
@ -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
|
||||
|
|
249
Gruntfile.js
249
Gruntfile.js
|
@ -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',
|
||||
|
|
|
@ -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..."
|
||||
}
|
||||
}
|
||||
|
|
6
app/base_config.d.ts
vendored
6
app/base_config.d.ts
vendored
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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`);
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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 })
|
||||
|
|
386
app/sql.js
386
app/sql.js
|
@ -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,
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
{
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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([
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -5,6 +5,5 @@
|
|||
"ip_url": "http://144.76.164.202:38157/"
|
||||
}
|
||||
],
|
||||
"openDevTools": true,
|
||||
"defaultPublicChatServer": "https://chat-dev.lokinet.org/"
|
||||
"openDevTools": true
|
||||
}
|
||||
|
|
|
@ -6,6 +6,5 @@
|
|||
}
|
||||
],
|
||||
"openDevTools": true,
|
||||
"defaultPublicChatServer": "https://team-chat.lokinet.org/",
|
||||
"defaultFileServer": "https://file-dev.getsession.org"
|
||||
}
|
||||
|
|
181
js/background.js
181
js/background.js
|
@ -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,
|
||||
});
|
||||
|
||||
|
|
|
@ -64,11 +64,7 @@
|
|||
};
|
||||
|
||||
request.onerror = () => {
|
||||
Whisper.Database.handleDOMException(
|
||||
'clearStores request error',
|
||||
request.error,
|
||||
reject
|
||||
);
|
||||
Whisper.Database.handleDOMException('clearStores request error', request.error, reject);
|
||||
};
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
48
js/expire.js
48
js/expire.js
|
@ -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;
|
||||
};
|
||||
})();
|
||||
|
|
|
@ -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({
|
||||
|
|
|
@ -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) => {
|
||||
|
|
|
@ -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);
|
||||
},
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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}`)
|
||||
);
|
||||
};
|
||||
|
||||
|
|
4
js/modules/deferred_to_promise.d.ts
vendored
4
js/modules/deferred_to_promise.d.ts
vendored
|
@ -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>;
|
||||
|
|
|
@ -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 '';
|
||||
}
|
||||
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
7
js/modules/loki_app_dot_net_api.d.ts
vendored
7
js/modules/loki_app_dot_net_api.d.ts
vendored
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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}`);
|
||||
|
|
14
js/modules/loki_message_api.d.ts
vendored
14
js/modules/loki_message_api.d.ts
vendored
|
@ -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;
|
|
@ -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;
|
11
js/modules/loki_primitives.d.ts
vendored
11
js/modules/loki_primitives.d.ts
vendored
|
@ -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
|
||||
);
|
|
@ -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,
|
||||
};
|
17
js/modules/loki_public_chat_api.d.ts
vendored
17
js/modules/loki_public_chat_api.d.ts
vendored
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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: We’ve 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");
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
);
|
||||
});
|
||||
|
|
|
@ -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 we’ll
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
},
|
||||
}))();
|
||||
|
|
|
@ -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
|
||||
);
|
||||
}
|
||||
|
|
|
@ -37,7 +37,7 @@
|
|||
this.remove();
|
||||
},
|
||||
submit() {
|
||||
this.convo.leaveGroup();
|
||||
this.convo.leaveClosedGroup();
|
||||
},
|
||||
});
|
||||
})();
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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'));
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
})();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
16
libloki/crypto.d.ts
vendored
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
},
|
||||
};
|
|
@ -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,
|
||||
};
|
||||
})();
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
};
|
|
@ -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>
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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',
|
||||
},
|
||||
};
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
})();
|
||||
|
|
|
@ -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 ***
|
||||
|
|
1
libtextsecure/index.d.ts
vendored
1
libtextsecure/index.d.ts
vendored
|
@ -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>;
|
||||
|
|
41
libtextsecure/libsignal-protocol.d.ts
vendored
41
libtextsecure/libsignal-protocol.d.ts
vendored
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -41,10 +41,7 @@
|
|||
},
|
||||
|
||||
setLastProfileUpdateTimestamp(lastUpdateTimestamp) {
|
||||
textsecure.storage.put(
|
||||
'last_profile_update_timestamp',
|
||||
lastUpdateTimestamp
|
||||
);
|
||||
textsecure.storage.put('last_profile_update_timestamp', lastUpdateTimestamp);
|
||||
},
|
||||
|
||||
getDeviceId() {
|
||||
|
|
|
@ -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;
|
||||
},
|
||||
};
|
||||
})();
|
||||
|
|
|
@ -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
|
||||
}`;
|
||||
|
||||
|
|
|
@ -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
95
main.js
|
@ -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;
|
||||
}
|
||||
|
|
66
metrics.html
66
metrics.html
|
@ -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>
|
|
@ -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`);
|
25
package.json
25
package.json
|
@ -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",
|
||||
|
|
|
@ -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: {
|
||||
|
|
70
preload.js
70
preload.js
|
@ -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;
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
Loading…
Reference in a new issue