Merge branch 'master' into development
Bringing beta up to date with production v1.0.40
This commit is contained in:
commit
f013eed9d1
19 changed files with 675 additions and 251 deletions
135
Gruntfile.js
135
Gruntfile.js
|
@ -335,71 +335,82 @@ module.exports = function(grunt) {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
grunt.registerTask('unit-tests', 'Run unit tests inside Electron', function() {
|
function runTests(environment, cb) {
|
||||||
var environment = grunt.option('env') || 'test';
|
var failure;
|
||||||
var done = this.async();
|
var Application = require('spectron').Application;
|
||||||
var failure;
|
var electronBinary = process.platform === 'win32' ? 'electron.cmd' : 'electron';
|
||||||
|
var app = new Application({
|
||||||
var Application = require('spectron').Application;
|
path: path.join(__dirname, 'node_modules', '.bin', electronBinary),
|
||||||
var electronBinary = process.platform === 'win32' ? 'electron.cmd' : 'electron';
|
args: [path.join(__dirname, 'main.js')],
|
||||||
var app = new Application({
|
env: {
|
||||||
path: path.join(__dirname, 'node_modules', '.bin', electronBinary),
|
NODE_ENV: environment
|
||||||
args: [path.join(__dirname, 'main.js')],
|
|
||||||
env: {
|
|
||||||
NODE_ENV: environment
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
function getMochaResults() {
|
|
||||||
return window.mochaResults;
|
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
app.start().then(function() {
|
function getMochaResults() {
|
||||||
return app.client.waitUntil(function() {
|
return window.mochaResults;
|
||||||
return app.client.execute(getMochaResults).then(function(data) {
|
}
|
||||||
return Boolean(data.value);
|
|
||||||
});
|
app.start().then(function() {
|
||||||
}, 10000, 'Expected to find window.mochaResults set!');
|
return app.client.waitUntil(function() {
|
||||||
}).then(function() {
|
return app.client.execute(getMochaResults).then(function(data) {
|
||||||
return app.client.execute(getMochaResults);
|
return Boolean(data.value);
|
||||||
}).then(function(data) {
|
});
|
||||||
var results = data.value;
|
}, 10000, 'Expected to find window.mochaResults set!');
|
||||||
if (results.failures > 0) {
|
}).then(function() {
|
||||||
console.error(results.reports);
|
return app.client.execute(getMochaResults);
|
||||||
failure = function() {
|
}).then(function(data) {
|
||||||
grunt.fail.fatal('Found ' + results.failures + ' failing unit tests.');
|
var results = data.value;
|
||||||
};
|
if (results.failures > 0) {
|
||||||
return app.client.log('browser');
|
console.error(results.reports);
|
||||||
} else {
|
|
||||||
grunt.log.ok(results.passes + ' tests passed.');
|
|
||||||
}
|
|
||||||
}).then(function(logs) {
|
|
||||||
if (logs) {
|
|
||||||
console.error();
|
|
||||||
console.error('Because tests failed, printing browser logs:');
|
|
||||||
console.error(logs);
|
|
||||||
}
|
|
||||||
}).catch(function (error) {
|
|
||||||
failure = function() {
|
failure = function() {
|
||||||
grunt.fail.fatal('Something went wrong: ' + error.message + ' ' + error.stack);
|
grunt.fail.fatal('Found ' + results.failures + ' failing unit tests.');
|
||||||
};
|
};
|
||||||
}).then(function () {
|
return app.client.log('browser');
|
||||||
// We need to use the failure variable and this early stop to clean up before
|
} else {
|
||||||
// shutting down. Grunt's fail methods are the only way to set the return value,
|
grunt.log.ok(results.passes + ' tests passed.');
|
||||||
// but they shut the process down immediately!
|
}
|
||||||
return app.stop();
|
}).then(function(logs) {
|
||||||
}).then(function() {
|
if (logs) {
|
||||||
if (failure) {
|
console.error();
|
||||||
failure();
|
console.error('Because tests failed, printing browser logs:');
|
||||||
}
|
console.error(logs);
|
||||||
done();
|
}
|
||||||
}).catch(function (error) {
|
}).catch(function (error) {
|
||||||
console.error('Second-level error:', error.message, error.stack);
|
failure = function() {
|
||||||
if (failure) {
|
grunt.fail.fatal('Something went wrong: ' + error.message + ' ' + error.stack);
|
||||||
failure();
|
};
|
||||||
}
|
}).then(function () {
|
||||||
done();
|
// We need to use the failure variable and this early stop to clean up before
|
||||||
});
|
// shutting down. Grunt's fail methods are the only way to set the return value,
|
||||||
|
// but they shut the process down immediately!
|
||||||
|
return app.stop();
|
||||||
|
}).then(function() {
|
||||||
|
if (failure) {
|
||||||
|
failure();
|
||||||
|
}
|
||||||
|
cb();
|
||||||
|
}).catch(function (error) {
|
||||||
|
console.error('Second-level error:', error.message, error.stack);
|
||||||
|
if (failure) {
|
||||||
|
failure();
|
||||||
|
}
|
||||||
|
cb();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
grunt.registerTask('unit-tests', 'Run unit tests w/Electron', function() {
|
||||||
|
var environment = grunt.option('env') || 'test';
|
||||||
|
var done = this.async();
|
||||||
|
|
||||||
|
runTests(environment, done);
|
||||||
|
});
|
||||||
|
|
||||||
|
grunt.registerTask('lib-unit-tests', 'Run libtextsecure unit tests w/Electron', function() {
|
||||||
|
var environment = grunt.option('env') || 'test-lib';
|
||||||
|
var done = this.async();
|
||||||
|
|
||||||
|
runTests(environment, done);
|
||||||
});
|
});
|
||||||
|
|
||||||
grunt.registerMultiTask('test-release', 'Test packaged releases', function() {
|
grunt.registerMultiTask('test-release', 'Test packaged releases', function() {
|
||||||
|
@ -473,7 +484,7 @@ module.exports = function(grunt) {
|
||||||
|
|
||||||
grunt.registerTask('tx', ['exec:tx-pull', 'locale-patch']);
|
grunt.registerTask('tx', ['exec:tx-pull', 'locale-patch']);
|
||||||
grunt.registerTask('dev', ['default', 'watch']);
|
grunt.registerTask('dev', ['default', 'watch']);
|
||||||
grunt.registerTask('test', ['jshint', 'jscs', 'unit-tests']);
|
grunt.registerTask('test', ['jshint', 'jscs', 'unit-tests', 'lib-unit-tests']);
|
||||||
grunt.registerTask('copy_dist', ['gitinfo', 'copy:res', 'copy:src']);
|
grunt.registerTask('copy_dist', ['gitinfo', 'copy:res', 'copy:src']);
|
||||||
grunt.registerTask('date', ['gitinfo', 'getExpireTime']);
|
grunt.registerTask('date', ['gitinfo', 'getExpireTime']);
|
||||||
grunt.registerTask('prep-release', ['gitinfo', 'clean-release', 'fetch-release']);
|
grunt.registerTask('prep-release', ['gitinfo', 'clean-release', 'fetch-release']);
|
||||||
|
|
|
@ -93,8 +93,19 @@ function fetch(logPath) {
|
||||||
return path.join(logPath, file)
|
return path.join(logPath, file)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// creating a manual log entry for the final log result
|
||||||
|
var now = new Date();
|
||||||
|
const fileListEntry = {
|
||||||
|
level: 30, // INFO
|
||||||
|
time: now.toJSON(),
|
||||||
|
msg: 'Loaded this list of log files from logPath: ' + files.join(', '),
|
||||||
|
};
|
||||||
|
|
||||||
return Promise.all(paths.map(fetchLog)).then(function(results) {
|
return Promise.all(paths.map(fetchLog)).then(function(results) {
|
||||||
const data = _.flatten(results);
|
const data = _.flatten(results);
|
||||||
|
|
||||||
|
data.push(fileListEntry);
|
||||||
|
|
||||||
return _.sortBy(data, 'time');
|
return _.sortBy(data, 'time');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
5
config/test-lib.json
Normal file
5
config/test-lib.json
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"storageProfile": "test",
|
||||||
|
"disableAutoUpdate": true,
|
||||||
|
"openDevTools": false
|
||||||
|
}
|
|
@ -88,9 +88,10 @@
|
||||||
function start() {
|
function start() {
|
||||||
var currentVersion = window.config.version;
|
var currentVersion = window.config.version;
|
||||||
var lastVersion = storage.get('version');
|
var lastVersion = storage.get('version');
|
||||||
|
var newVersion = !lastVersion || currentVersion !== lastVersion;
|
||||||
storage.put('version', currentVersion);
|
storage.put('version', currentVersion);
|
||||||
|
|
||||||
if (!lastVersion || currentVersion !== lastVersion) {
|
if (newVersion) {
|
||||||
console.log('New version detected:', currentVersion);
|
console.log('New version detected:', currentVersion);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -99,7 +100,7 @@
|
||||||
console.log('listening for registration events');
|
console.log('listening for registration events');
|
||||||
Whisper.events.on('registration_done', function() {
|
Whisper.events.on('registration_done', function() {
|
||||||
console.log('handling registration event');
|
console.log('handling registration event');
|
||||||
Whisper.RotateSignedPreKeyListener.init(Whisper.events);
|
Whisper.RotateSignedPreKeyListener.init(Whisper.events, newVersion);
|
||||||
connect(true);
|
connect(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -112,7 +113,7 @@
|
||||||
console.log('Import was interrupted, showing import error screen');
|
console.log('Import was interrupted, showing import error screen');
|
||||||
appView.openImporter();
|
appView.openImporter();
|
||||||
} else if (Whisper.Registration.everDone()) {
|
} else if (Whisper.Registration.everDone()) {
|
||||||
Whisper.RotateSignedPreKeyListener.init(Whisper.events);
|
Whisper.RotateSignedPreKeyListener.init(Whisper.events, newVersion);
|
||||||
connect();
|
connect();
|
||||||
appView.openInbox({
|
appView.openInbox({
|
||||||
initialLoadComplete: initialLoadComplete
|
initialLoadComplete: initialLoadComplete
|
||||||
|
@ -377,6 +378,14 @@
|
||||||
return ConversationController.getOrCreateAndWait(id, 'private')
|
return ConversationController.getOrCreateAndWait(id, 'private')
|
||||||
.then(function(conversation) {
|
.then(function(conversation) {
|
||||||
return new Promise(function(resolve, reject) {
|
return new Promise(function(resolve, reject) {
|
||||||
|
var activeAt = conversation.get('active_at');
|
||||||
|
|
||||||
|
// The idea is to make any new contact show up in the left pane. If
|
||||||
|
// activeAt is null, then this contact has been purposefully hidden.
|
||||||
|
if (activeAt !== null) {
|
||||||
|
activeAt = activeAt || Date.now();
|
||||||
|
}
|
||||||
|
|
||||||
if (details.profileKey) {
|
if (details.profileKey) {
|
||||||
conversation.set({profileKey: details.profileKey});
|
conversation.set({profileKey: details.profileKey});
|
||||||
}
|
}
|
||||||
|
@ -384,7 +393,7 @@
|
||||||
name: details.name,
|
name: details.name,
|
||||||
avatar: details.avatar,
|
avatar: details.avatar,
|
||||||
color: details.color,
|
color: details.color,
|
||||||
active_at: conversation.get('active_at') || Date.now(),
|
active_at: activeAt,
|
||||||
}).then(resolve, reject);
|
}).then(resolve, reject);
|
||||||
}).then(function() {
|
}).then(function() {
|
||||||
if (details.verified) {
|
if (details.verified) {
|
||||||
|
@ -421,7 +430,13 @@
|
||||||
type: 'group',
|
type: 'group',
|
||||||
};
|
};
|
||||||
if (details.active) {
|
if (details.active) {
|
||||||
updates.active_at = Date.now();
|
var activeAt = conversation.get('active_at');
|
||||||
|
|
||||||
|
// The idea is to make any new group show up in the left pane. If
|
||||||
|
// activeAt is null, then this group has been purposefully hidden.
|
||||||
|
if (activeAt !== null) {
|
||||||
|
updates.active_at = activeAt || Date.now();
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
updates.left = true;
|
updates.left = true;
|
||||||
}
|
}
|
||||||
|
@ -553,8 +568,7 @@
|
||||||
|
|
||||||
function onError(ev) {
|
function onError(ev) {
|
||||||
var error = ev.error;
|
var error = ev.error;
|
||||||
console.log(error);
|
console.log('background onError:', error && error.stack ? error.stack : error);
|
||||||
console.log(error.stack);
|
|
||||||
|
|
||||||
if (error.name === 'HTTPError' && (error.code == 401 || error.code == 403)) {
|
if (error.name === 'HTTPError' && (error.code == 401 || error.code == 403)) {
|
||||||
Whisper.Registration.remove();
|
Whisper.Registration.remove();
|
||||||
|
|
|
@ -38003,26 +38003,35 @@ var TextSecureServer = (function() {
|
||||||
rotateSignedPreKey: function() {
|
rotateSignedPreKey: function() {
|
||||||
return this.queueTask(function() {
|
return this.queueTask(function() {
|
||||||
var signedKeyId = textsecure.storage.get('signedKeyId', 1);
|
var signedKeyId = textsecure.storage.get('signedKeyId', 1);
|
||||||
|
|
||||||
if (typeof signedKeyId != 'number') {
|
if (typeof signedKeyId != 'number') {
|
||||||
throw new Error('Invalid signedKeyId');
|
throw new Error('Invalid signedKeyId');
|
||||||
}
|
}
|
||||||
|
|
||||||
var store = textsecure.storage.protocol;
|
var store = textsecure.storage.protocol;
|
||||||
var server = this.server;
|
var server = this.server;
|
||||||
var cleanSignedPreKeys = this.cleanSignedPreKeys;
|
var cleanSignedPreKeys = this.cleanSignedPreKeys;
|
||||||
|
|
||||||
return store.getIdentityKeyPair().then(function(identityKey) {
|
return store.getIdentityKeyPair().then(function(identityKey) {
|
||||||
return libsignal.KeyHelper.generateSignedPreKey(identityKey, signedKeyId);
|
return libsignal.KeyHelper.generateSignedPreKey(identityKey, signedKeyId);
|
||||||
}).then(function(res) {
|
}).then(function(res) {
|
||||||
return server.setSignedPreKey({
|
console.log('Saving new signed prekey', res.keyId);
|
||||||
keyId : res.keyId,
|
return Promise.all([
|
||||||
publicKey : res.keyPair.pubKey,
|
textsecure.storage.put('signedKeyId', signedKeyId + 1),
|
||||||
signature : res.signature
|
store.storeSignedPreKey(res.keyId, res.keyPair),
|
||||||
|
server.setSignedPreKey({
|
||||||
|
keyId : res.keyId,
|
||||||
|
publicKey : res.keyPair.pubKey,
|
||||||
|
signature : res.signature
|
||||||
|
}),
|
||||||
|
]).then(function() {
|
||||||
|
var confirmed = true;
|
||||||
|
console.log('Confirming new signed prekey', res.keyId);
|
||||||
|
return Promise.all([
|
||||||
|
textsecure.storage.remove('signedKeyRotationRejected'),
|
||||||
|
store.storeSignedPreKey(res.keyId, res.keyPair, confirmed),
|
||||||
|
]);
|
||||||
}).then(function() {
|
}).then(function() {
|
||||||
textsecure.storage.put('signedKeyId', signedKeyId + 1);
|
return cleanSignedPreKeys();
|
||||||
textsecure.storage.remove('signedKeyRotationRejected');
|
|
||||||
return store.storeSignedPreKey(res.keyId, res.keyPair).then(function() {
|
|
||||||
return cleanSignedPreKeys();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}).catch(function(e) {
|
}).catch(function(e) {
|
||||||
console.log(
|
console.log(
|
||||||
|
@ -38034,9 +38043,9 @@ var TextSecureServer = (function() {
|
||||||
var rejections = 1 + textsecure.storage.get('signedKeyRotationRejected', 0);
|
var rejections = 1 + textsecure.storage.get('signedKeyRotationRejected', 0);
|
||||||
textsecure.storage.put('signedKeyRotationRejected', rejections);
|
textsecure.storage.put('signedKeyRotationRejected', rejections);
|
||||||
console.log('Signed key rotation rejected count:', rejections);
|
console.log('Signed key rotation rejected count:', rejections);
|
||||||
|
} else {
|
||||||
|
throw e;
|
||||||
}
|
}
|
||||||
|
|
||||||
throw e;
|
|
||||||
});
|
});
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
},
|
},
|
||||||
|
@ -38045,35 +38054,72 @@ var TextSecureServer = (function() {
|
||||||
return this.pending = this.pending.then(taskWithTimeout, taskWithTimeout);
|
return this.pending = this.pending.then(taskWithTimeout, taskWithTimeout);
|
||||||
},
|
},
|
||||||
cleanSignedPreKeys: function() {
|
cleanSignedPreKeys: function() {
|
||||||
var nextSignedKeyId = textsecure.storage.get('signedKeyId');
|
var MINIMUM_KEYS = 3;
|
||||||
if (typeof nextSignedKeyId != 'number') {
|
|
||||||
return Promise.resolve();
|
|
||||||
}
|
|
||||||
var activeSignedPreKeyId = nextSignedKeyId - 1;
|
|
||||||
|
|
||||||
var store = textsecure.storage.protocol;
|
var store = textsecure.storage.protocol;
|
||||||
return store.loadSignedPreKeys().then(function(allRecords) {
|
return store.loadSignedPreKeys().then(function(allKeys) {
|
||||||
var oldRecords = allRecords.filter(function(record) {
|
allKeys.sort(function(a, b) {
|
||||||
return record.keyId !== activeSignedPreKeyId;
|
|
||||||
});
|
|
||||||
oldRecords.sort(function(a, b) {
|
|
||||||
return (a.created_at || 0) - (b.created_at || 0);
|
return (a.created_at || 0) - (b.created_at || 0);
|
||||||
});
|
});
|
||||||
|
allKeys.reverse(); // we want the most recent first
|
||||||
|
var confirmed = allKeys.filter(function(key) {
|
||||||
|
return key.confirmed;
|
||||||
|
});
|
||||||
|
var unconfirmed = allKeys.filter(function(key) {
|
||||||
|
return !key.confirmed;
|
||||||
|
});
|
||||||
|
|
||||||
console.log("Active signed prekey: " + activeSignedPreKeyId);
|
var recent = allKeys[0] ? allKeys[0].keyId : 'none';
|
||||||
console.log("Old signed prekey record count: " + oldRecords.length);
|
var recentConfirmed = confirmed[0] ? confirmed[0].keyId : 'none';
|
||||||
|
console.log('Most recent signed key: ' + recent);
|
||||||
|
console.log('Most recent confirmed signed key: ' + recentConfirmed);
|
||||||
|
console.log(
|
||||||
|
'Total signed key count:',
|
||||||
|
allKeys.length,
|
||||||
|
'-',
|
||||||
|
confirmed.length,
|
||||||
|
'confirmed'
|
||||||
|
);
|
||||||
|
|
||||||
oldRecords.forEach(function(oldRecord) {
|
var confirmedCount = confirmed.length;
|
||||||
if ( oldRecord.keyId > activeSignedPreKeyId - 3 ) {
|
|
||||||
// keep at least the last 3 signed keys
|
// Keep MINIMUM_KEYS confirmed keys, then drop if older than a week
|
||||||
|
confirmed = confirmed.forEach(function(key, index) {
|
||||||
|
if (index < MINIMUM_KEYS) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var created_at = oldRecord.created_at || 0;
|
var created_at = key.created_at || 0;
|
||||||
var archiveDuration = Date.now() - created_at;
|
var age = Date.now() - created_at;
|
||||||
if (archiveDuration > ARCHIVE_AGE) {
|
if (age > ARCHIVE_AGE) {
|
||||||
console.log("Removing signed prekey record:",
|
console.log(
|
||||||
oldRecord.keyId, "with timestamp:", created_at);
|
'Removing confirmed signed prekey:',
|
||||||
store.removeSignedPreKey(oldRecord.keyId);
|
key.keyId,
|
||||||
|
'with timestamp:',
|
||||||
|
created_at
|
||||||
|
);
|
||||||
|
store.removeSignedPreKey(key.keyId);
|
||||||
|
confirmedCount--;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var stillNeeded = MINIMUM_KEYS - confirmedCount;
|
||||||
|
|
||||||
|
// If we still don't have enough total keys, we keep as many unconfirmed
|
||||||
|
// keys as necessary. If not necessary, and over a week old, we drop.
|
||||||
|
unconfirmed.forEach(function(key, index) {
|
||||||
|
if (index < stillNeeded) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var created_at = key.created_at || 0;
|
||||||
|
var age = Date.now() - created_at;
|
||||||
|
if (age > ARCHIVE_AGE) {
|
||||||
|
console.log(
|
||||||
|
'Removing unconfirmed signed prekey:',
|
||||||
|
key.keyId,
|
||||||
|
'with timestamp:',
|
||||||
|
created_at
|
||||||
|
);
|
||||||
|
store.removeSignedPreKey(key.keyId);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -39054,8 +39100,7 @@ MessageReceiver.prototype.extend({
|
||||||
console.log('Got SyncMessage Request');
|
console.log('Got SyncMessage Request');
|
||||||
return this.removeFromCache(envelope);
|
return this.removeFromCache(envelope);
|
||||||
} else if (syncMessage.read && syncMessage.read.length) {
|
} else if (syncMessage.read && syncMessage.read.length) {
|
||||||
console.log('read messages',
|
console.log('read messages from', this.getEnvelopeId(envelope));
|
||||||
'from', envelope.source + '.' + envelope.sourceDevice);
|
|
||||||
return this.handleRead(envelope, syncMessage.read);
|
return this.handleRead(envelope, syncMessage.read);
|
||||||
} else if (syncMessage.verified) {
|
} else if (syncMessage.verified) {
|
||||||
return this.handleVerified(envelope, syncMessage.verified);
|
return this.handleVerified(envelope, syncMessage.verified);
|
||||||
|
@ -39166,6 +39211,7 @@ MessageReceiver.prototype.extend({
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
},
|
},
|
||||||
handleBlocked: function(envelope, blocked) {
|
handleBlocked: function(envelope, blocked) {
|
||||||
|
console.log('Setting these numbers as blocked:', blocked.numbers);
|
||||||
textsecure.storage.put('blocked', blocked.numbers);
|
textsecure.storage.put('blocked', blocked.numbers);
|
||||||
},
|
},
|
||||||
isBlocked: function(number) {
|
isBlocked: function(number) {
|
||||||
|
@ -39908,10 +39954,11 @@ MessageSender.prototype = {
|
||||||
var proto = textsecure.protobuf.DataMessage.decode(encodedMessage);
|
var proto = textsecure.protobuf.DataMessage.decode(encodedMessage);
|
||||||
return new Promise(function(resolve, reject) {
|
return new Promise(function(resolve, reject) {
|
||||||
this.sendMessageProto(timestamp, numbers, proto, function(res) {
|
this.sendMessageProto(timestamp, numbers, proto, function(res) {
|
||||||
if (res.errors.length > 0)
|
if (res.errors.length > 0) {
|
||||||
reject(res);
|
reject(res);
|
||||||
else
|
} else {
|
||||||
resolve(res);
|
resolve(res);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
},
|
},
|
||||||
|
@ -39963,7 +40010,9 @@ MessageSender.prototype = {
|
||||||
syncMessage.sent = sentMessage;
|
syncMessage.sent = sentMessage;
|
||||||
var contentMessage = new textsecure.protobuf.Content();
|
var contentMessage = new textsecure.protobuf.Content();
|
||||||
contentMessage.syncMessage = syncMessage;
|
contentMessage.syncMessage = syncMessage;
|
||||||
return this.sendIndividualProto(myNumber, contentMessage, Date.now());
|
|
||||||
|
var silent = true;
|
||||||
|
return this.sendIndividualProto(myNumber, contentMessage, Date.now(), silent);
|
||||||
},
|
},
|
||||||
|
|
||||||
getProfile: function(number) {
|
getProfile: function(number) {
|
||||||
|
@ -39984,7 +40033,8 @@ MessageSender.prototype = {
|
||||||
var contentMessage = new textsecure.protobuf.Content();
|
var contentMessage = new textsecure.protobuf.Content();
|
||||||
contentMessage.syncMessage = syncMessage;
|
contentMessage.syncMessage = syncMessage;
|
||||||
|
|
||||||
return this.sendIndividualProto(myNumber, contentMessage, Date.now());
|
var silent = true;
|
||||||
|
return this.sendIndividualProto(myNumber, contentMessage, Date.now(), silent);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
|
@ -40000,7 +40050,8 @@ MessageSender.prototype = {
|
||||||
var contentMessage = new textsecure.protobuf.Content();
|
var contentMessage = new textsecure.protobuf.Content();
|
||||||
contentMessage.syncMessage = syncMessage;
|
contentMessage.syncMessage = syncMessage;
|
||||||
|
|
||||||
return this.sendIndividualProto(myNumber, contentMessage, Date.now());
|
var silent = true;
|
||||||
|
return this.sendIndividualProto(myNumber, contentMessage, Date.now(), silent);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
|
@ -40017,7 +40068,8 @@ MessageSender.prototype = {
|
||||||
var contentMessage = new textsecure.protobuf.Content();
|
var contentMessage = new textsecure.protobuf.Content();
|
||||||
contentMessage.syncMessage = syncMessage;
|
contentMessage.syncMessage = syncMessage;
|
||||||
|
|
||||||
return this.sendIndividualProto(myNumber, contentMessage, Date.now());
|
var silent = true;
|
||||||
|
return this.sendIndividualProto(myNumber, contentMessage, Date.now(), silent);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
|
@ -40030,7 +40082,8 @@ MessageSender.prototype = {
|
||||||
var contentMessage = new textsecure.protobuf.Content();
|
var contentMessage = new textsecure.protobuf.Content();
|
||||||
contentMessage.receiptMessage = receiptMessage;
|
contentMessage.receiptMessage = receiptMessage;
|
||||||
|
|
||||||
return this.sendIndividualProto(sender, contentMessage, Date.now(), true /*silent*/);
|
var silent = true;
|
||||||
|
return this.sendIndividualProto(sender, contentMessage, Date.now(), silent);
|
||||||
},
|
},
|
||||||
syncReadMessages: function(reads) {
|
syncReadMessages: function(reads) {
|
||||||
var myNumber = textsecure.storage.user.getNumber();
|
var myNumber = textsecure.storage.user.getNumber();
|
||||||
|
@ -40047,7 +40100,8 @@ MessageSender.prototype = {
|
||||||
var contentMessage = new textsecure.protobuf.Content();
|
var contentMessage = new textsecure.protobuf.Content();
|
||||||
contentMessage.syncMessage = syncMessage;
|
contentMessage.syncMessage = syncMessage;
|
||||||
|
|
||||||
return this.sendIndividualProto(myNumber, contentMessage, Date.now());
|
var silent = true;
|
||||||
|
return this.sendIndividualProto(myNumber, contentMessage, Date.now(), silent);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
|
@ -40055,38 +40109,44 @@ MessageSender.prototype = {
|
||||||
syncVerification: function(destination, state, identityKey) {
|
syncVerification: function(destination, state, identityKey) {
|
||||||
var myNumber = textsecure.storage.user.getNumber();
|
var myNumber = textsecure.storage.user.getNumber();
|
||||||
var myDevice = textsecure.storage.user.getDeviceId();
|
var myDevice = textsecure.storage.user.getDeviceId();
|
||||||
if (myDevice != 1) {
|
var now = Date.now();
|
||||||
// First send a null message to mask the sync message.
|
|
||||||
var nullMessage = new textsecure.protobuf.NullMessage();
|
|
||||||
|
|
||||||
// Generate a random int from 1 and 512
|
if (myDevice === 1) {
|
||||||
var buffer = libsignal.crypto.getRandomBytes(1);
|
return Promise.resolve();
|
||||||
var paddingLength = (new Uint8Array(buffer)[0] & 0x1ff) + 1;
|
|
||||||
|
|
||||||
// Generate a random padding buffer of the chosen size
|
|
||||||
nullMessage.padding = libsignal.crypto.getRandomBytes(paddingLength);
|
|
||||||
|
|
||||||
var contentMessage = new textsecure.protobuf.Content();
|
|
||||||
contentMessage.nullMessage = nullMessage;
|
|
||||||
|
|
||||||
return this.sendIndividualProto(destination, contentMessage, Date.now()).then(function() {
|
|
||||||
var verified = new textsecure.protobuf.Verified();
|
|
||||||
verified.state = state;
|
|
||||||
verified.destination = destination;
|
|
||||||
verified.identityKey = identityKey;
|
|
||||||
verified.nullMessage = nullMessage.padding;
|
|
||||||
|
|
||||||
var syncMessage = this.createSyncMessage();
|
|
||||||
syncMessage.verified = verified;
|
|
||||||
|
|
||||||
var contentMessage = new textsecure.protobuf.Content();
|
|
||||||
contentMessage.syncMessage = syncMessage;
|
|
||||||
|
|
||||||
return this.sendIndividualProto(myNumber, contentMessage, Date.now());
|
|
||||||
}.bind(this));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise.resolve();
|
// First send a null message to mask the sync message.
|
||||||
|
var nullMessage = new textsecure.protobuf.NullMessage();
|
||||||
|
|
||||||
|
// Generate a random int from 1 and 512
|
||||||
|
var buffer = libsignal.crypto.getRandomBytes(1);
|
||||||
|
var paddingLength = (new Uint8Array(buffer)[0] & 0x1ff) + 1;
|
||||||
|
|
||||||
|
// Generate a random padding buffer of the chosen size
|
||||||
|
nullMessage.padding = libsignal.crypto.getRandomBytes(paddingLength);
|
||||||
|
|
||||||
|
var contentMessage = new textsecure.protobuf.Content();
|
||||||
|
contentMessage.nullMessage = nullMessage;
|
||||||
|
|
||||||
|
// We want the NullMessage to look like a normal outgoing message; not silent
|
||||||
|
const promise = this.sendIndividualProto(destination, contentMessage, now);
|
||||||
|
|
||||||
|
return promise.then(function() {
|
||||||
|
var verified = new textsecure.protobuf.Verified();
|
||||||
|
verified.state = state;
|
||||||
|
verified.destination = destination;
|
||||||
|
verified.identityKey = identityKey;
|
||||||
|
verified.nullMessage = nullMessage.padding;
|
||||||
|
|
||||||
|
var syncMessage = this.createSyncMessage();
|
||||||
|
syncMessage.verified = verified;
|
||||||
|
|
||||||
|
var contentMessage = new textsecure.protobuf.Content();
|
||||||
|
contentMessage.syncMessage = syncMessage;
|
||||||
|
|
||||||
|
var silent = true;
|
||||||
|
return this.sendIndividualProto(myNumber, contentMessage, now, silent);
|
||||||
|
}.bind(this));
|
||||||
},
|
},
|
||||||
|
|
||||||
sendGroupProto: function(numbers, proto, timestamp) {
|
sendGroupProto: function(numbers, proto, timestamp) {
|
||||||
|
@ -40098,14 +40158,17 @@ MessageSender.prototype = {
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Promise(function(resolve, reject) {
|
return new Promise(function(resolve, reject) {
|
||||||
this.sendMessageProto(timestamp, numbers, proto, function(res) {
|
var silent = true;
|
||||||
|
var callback = function(res) {
|
||||||
res.dataMessage = proto.toArrayBuffer();
|
res.dataMessage = proto.toArrayBuffer();
|
||||||
if (res.errors.length > 0) {
|
if (res.errors.length > 0) {
|
||||||
reject(res);
|
reject(res);
|
||||||
} else {
|
} else {
|
||||||
resolve(res);
|
resolve(res);
|
||||||
}
|
}
|
||||||
}.bind(this));
|
}.bind(this);
|
||||||
|
|
||||||
|
this.sendMessageProto(timestamp, numbers, proto, callback, silent);
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -166,6 +166,10 @@ window.log = {
|
||||||
};
|
};
|
||||||
|
|
||||||
window.onerror = function(message, script, line, col, error) {
|
window.onerror = function(message, script, line, col, error) {
|
||||||
window.log.error(error.stack);
|
const errorInfo = error && error.stack ? error.stack : JSON.stringify(error);
|
||||||
|
window.log.error('Top-level unhandled error: ' + errorInfo);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
window.addEventListener('unhandledrejection', function(rejectionEvent) {
|
||||||
|
window.log.error('Top-level unhandled promise rejection: ' + rejectionEvent.reason);
|
||||||
|
});
|
||||||
|
|
|
@ -314,8 +314,11 @@
|
||||||
errors = [errors];
|
errors = [errors];
|
||||||
}
|
}
|
||||||
errors.forEach(function(e) {
|
errors.forEach(function(e) {
|
||||||
console.log(e);
|
console.log(
|
||||||
console.log(e.reason, e.stack);
|
'Message.saveErrors:',
|
||||||
|
e && e.reason ? e.reason : null,
|
||||||
|
e && e.stack ? e.stack : e
|
||||||
|
);
|
||||||
});
|
});
|
||||||
errors = errors.map(function(e) {
|
errors = errors.map(function(e) {
|
||||||
if (e.constructor === Error ||
|
if (e.constructor === Error ||
|
||||||
|
|
|
@ -65,13 +65,12 @@
|
||||||
}
|
}
|
||||||
initComplete = true;
|
initComplete = true;
|
||||||
|
|
||||||
if (Whisper.Registration.isDone()) {
|
if (newVersion) {
|
||||||
|
runWhenOnline();
|
||||||
|
} else {
|
||||||
setTimeoutForNextRun();
|
setTimeoutForNextRun();
|
||||||
}
|
}
|
||||||
events.on('registration_done', function() {
|
|
||||||
scheduleNextRotation();
|
|
||||||
setTimeoutForNextRun();
|
|
||||||
});
|
|
||||||
events.on('timetravel', function() {
|
events.on('timetravel', function() {
|
||||||
if (Whisper.Registration.isDone()) {
|
if (Whisper.Registration.isDone()) {
|
||||||
setTimeoutForNextRun();
|
setTimeoutForNextRun();
|
||||||
|
|
|
@ -186,11 +186,15 @@
|
||||||
var prekey = new PreKey({id: keyId});
|
var prekey = new PreKey({id: keyId});
|
||||||
return new Promise(function(resolve) {
|
return new Promise(function(resolve) {
|
||||||
prekey.fetch().then(function() {
|
prekey.fetch().then(function() {
|
||||||
|
console.log('Successfully fetched prekey:', keyId);
|
||||||
resolve({
|
resolve({
|
||||||
pubKey: prekey.attributes.publicKey,
|
pubKey: prekey.get('publicKey'),
|
||||||
privKey: prekey.attributes.privateKey
|
privKey: prekey.get('privateKey'),
|
||||||
});
|
});
|
||||||
}).fail(resolve);
|
}, function() {
|
||||||
|
console.log('Failed to load prekey:', keyId);
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
storePreKey: function(keyId, keyPair) {
|
storePreKey: function(keyId, keyPair) {
|
||||||
|
@ -211,7 +215,16 @@
|
||||||
this.trigger('removePreKey');
|
this.trigger('removePreKey');
|
||||||
|
|
||||||
return new Promise(function(resolve) {
|
return new Promise(function(resolve) {
|
||||||
prekey.destroy().then(function() {
|
var deferred = prekey.destroy();
|
||||||
|
if (!deferred) {
|
||||||
|
return resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
return deferred.then(resolve, function(error) {
|
||||||
|
console.log(
|
||||||
|
'removePreKey error:',
|
||||||
|
error && error.stack ? error.stack : error
|
||||||
|
);
|
||||||
resolve();
|
resolve();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -222,21 +235,23 @@
|
||||||
var prekey = new SignedPreKey({id: keyId});
|
var prekey = new SignedPreKey({id: keyId});
|
||||||
return new Promise(function(resolve) {
|
return new Promise(function(resolve) {
|
||||||
prekey.fetch().then(function() {
|
prekey.fetch().then(function() {
|
||||||
|
console.log('Successfully loaded prekey:', prekey.get('id'));
|
||||||
resolve({
|
resolve({
|
||||||
pubKey : prekey.get('publicKey'),
|
pubKey : prekey.get('publicKey'),
|
||||||
privKey : prekey.get('privateKey'),
|
privKey : prekey.get('privateKey'),
|
||||||
created_at : prekey.get('created_at'),
|
created_at : prekey.get('created_at'),
|
||||||
keyId : prekey.get('id')
|
keyId : prekey.get('id'),
|
||||||
|
confirmed : prekey.get('confirmed'),
|
||||||
});
|
});
|
||||||
}).fail(function() {
|
}).fail(function() {
|
||||||
console.log("Failed to load signed prekey:", keyId);
|
console.log('Failed to load signed prekey:', keyId);
|
||||||
resolve();
|
resolve();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
loadSignedPreKeys: function() {
|
loadSignedPreKeys: function() {
|
||||||
if (arguments.length > 0) {
|
if (arguments.length > 0) {
|
||||||
return Promise.reject(new Error("loadSignedPreKeys takes no arguments"));
|
return Promise.reject(new Error('loadSignedPreKeys takes no arguments'));
|
||||||
}
|
}
|
||||||
var signedPreKeys = new SignedPreKeyCollection();
|
var signedPreKeys = new SignedPreKeyCollection();
|
||||||
return new Promise(function(resolve) {
|
return new Promise(function(resolve) {
|
||||||
|
@ -246,18 +261,20 @@
|
||||||
pubKey : prekey.get('publicKey'),
|
pubKey : prekey.get('publicKey'),
|
||||||
privKey : prekey.get('privateKey'),
|
privKey : prekey.get('privateKey'),
|
||||||
created_at : prekey.get('created_at'),
|
created_at : prekey.get('created_at'),
|
||||||
keyId : prekey.get('id')
|
keyId : prekey.get('id'),
|
||||||
|
confirmed : prekey.get('confirmed'),
|
||||||
};
|
};
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
storeSignedPreKey: function(keyId, keyPair) {
|
storeSignedPreKey: function(keyId, keyPair, confirmed) {
|
||||||
var prekey = new SignedPreKey({
|
var prekey = new SignedPreKey({
|
||||||
id : keyId,
|
id : keyId,
|
||||||
publicKey : keyPair.pubKey,
|
publicKey : keyPair.pubKey,
|
||||||
privateKey : keyPair.privKey,
|
privateKey : keyPair.privKey,
|
||||||
created_at : Date.now()
|
created_at : Date.now(),
|
||||||
|
confirmed : Boolean(confirmed),
|
||||||
});
|
});
|
||||||
return new Promise(function(resolve) {
|
return new Promise(function(resolve) {
|
||||||
prekey.save().always(function() {
|
prekey.save().always(function() {
|
||||||
|
|
|
@ -118,26 +118,35 @@
|
||||||
rotateSignedPreKey: function() {
|
rotateSignedPreKey: function() {
|
||||||
return this.queueTask(function() {
|
return this.queueTask(function() {
|
||||||
var signedKeyId = textsecure.storage.get('signedKeyId', 1);
|
var signedKeyId = textsecure.storage.get('signedKeyId', 1);
|
||||||
|
|
||||||
if (typeof signedKeyId != 'number') {
|
if (typeof signedKeyId != 'number') {
|
||||||
throw new Error('Invalid signedKeyId');
|
throw new Error('Invalid signedKeyId');
|
||||||
}
|
}
|
||||||
|
|
||||||
var store = textsecure.storage.protocol;
|
var store = textsecure.storage.protocol;
|
||||||
var server = this.server;
|
var server = this.server;
|
||||||
var cleanSignedPreKeys = this.cleanSignedPreKeys;
|
var cleanSignedPreKeys = this.cleanSignedPreKeys;
|
||||||
|
|
||||||
return store.getIdentityKeyPair().then(function(identityKey) {
|
return store.getIdentityKeyPair().then(function(identityKey) {
|
||||||
return libsignal.KeyHelper.generateSignedPreKey(identityKey, signedKeyId);
|
return libsignal.KeyHelper.generateSignedPreKey(identityKey, signedKeyId);
|
||||||
}).then(function(res) {
|
}).then(function(res) {
|
||||||
return server.setSignedPreKey({
|
console.log('Saving new signed prekey', res.keyId);
|
||||||
keyId : res.keyId,
|
return Promise.all([
|
||||||
publicKey : res.keyPair.pubKey,
|
textsecure.storage.put('signedKeyId', signedKeyId + 1),
|
||||||
signature : res.signature
|
store.storeSignedPreKey(res.keyId, res.keyPair),
|
||||||
|
server.setSignedPreKey({
|
||||||
|
keyId : res.keyId,
|
||||||
|
publicKey : res.keyPair.pubKey,
|
||||||
|
signature : res.signature
|
||||||
|
}),
|
||||||
|
]).then(function() {
|
||||||
|
var confirmed = true;
|
||||||
|
console.log('Confirming new signed prekey', res.keyId);
|
||||||
|
return Promise.all([
|
||||||
|
textsecure.storage.remove('signedKeyRotationRejected'),
|
||||||
|
store.storeSignedPreKey(res.keyId, res.keyPair, confirmed),
|
||||||
|
]);
|
||||||
}).then(function() {
|
}).then(function() {
|
||||||
textsecure.storage.put('signedKeyId', signedKeyId + 1);
|
return cleanSignedPreKeys();
|
||||||
textsecure.storage.remove('signedKeyRotationRejected');
|
|
||||||
return store.storeSignedPreKey(res.keyId, res.keyPair).then(function() {
|
|
||||||
return cleanSignedPreKeys();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}).catch(function(e) {
|
}).catch(function(e) {
|
||||||
console.log(
|
console.log(
|
||||||
|
@ -149,9 +158,9 @@
|
||||||
var rejections = 1 + textsecure.storage.get('signedKeyRotationRejected', 0);
|
var rejections = 1 + textsecure.storage.get('signedKeyRotationRejected', 0);
|
||||||
textsecure.storage.put('signedKeyRotationRejected', rejections);
|
textsecure.storage.put('signedKeyRotationRejected', rejections);
|
||||||
console.log('Signed key rotation rejected count:', rejections);
|
console.log('Signed key rotation rejected count:', rejections);
|
||||||
|
} else {
|
||||||
|
throw e;
|
||||||
}
|
}
|
||||||
|
|
||||||
throw e;
|
|
||||||
});
|
});
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
},
|
},
|
||||||
|
@ -160,35 +169,72 @@
|
||||||
return this.pending = this.pending.then(taskWithTimeout, taskWithTimeout);
|
return this.pending = this.pending.then(taskWithTimeout, taskWithTimeout);
|
||||||
},
|
},
|
||||||
cleanSignedPreKeys: function() {
|
cleanSignedPreKeys: function() {
|
||||||
var nextSignedKeyId = textsecure.storage.get('signedKeyId');
|
var MINIMUM_KEYS = 3;
|
||||||
if (typeof nextSignedKeyId != 'number') {
|
|
||||||
return Promise.resolve();
|
|
||||||
}
|
|
||||||
var activeSignedPreKeyId = nextSignedKeyId - 1;
|
|
||||||
|
|
||||||
var store = textsecure.storage.protocol;
|
var store = textsecure.storage.protocol;
|
||||||
return store.loadSignedPreKeys().then(function(allRecords) {
|
return store.loadSignedPreKeys().then(function(allKeys) {
|
||||||
var oldRecords = allRecords.filter(function(record) {
|
allKeys.sort(function(a, b) {
|
||||||
return record.keyId !== activeSignedPreKeyId;
|
|
||||||
});
|
|
||||||
oldRecords.sort(function(a, b) {
|
|
||||||
return (a.created_at || 0) - (b.created_at || 0);
|
return (a.created_at || 0) - (b.created_at || 0);
|
||||||
});
|
});
|
||||||
|
allKeys.reverse(); // we want the most recent first
|
||||||
|
var confirmed = allKeys.filter(function(key) {
|
||||||
|
return key.confirmed;
|
||||||
|
});
|
||||||
|
var unconfirmed = allKeys.filter(function(key) {
|
||||||
|
return !key.confirmed;
|
||||||
|
});
|
||||||
|
|
||||||
console.log("Active signed prekey: " + activeSignedPreKeyId);
|
var recent = allKeys[0] ? allKeys[0].keyId : 'none';
|
||||||
console.log("Old signed prekey record count: " + oldRecords.length);
|
var recentConfirmed = confirmed[0] ? confirmed[0].keyId : 'none';
|
||||||
|
console.log('Most recent signed key: ' + recent);
|
||||||
|
console.log('Most recent confirmed signed key: ' + recentConfirmed);
|
||||||
|
console.log(
|
||||||
|
'Total signed key count:',
|
||||||
|
allKeys.length,
|
||||||
|
'-',
|
||||||
|
confirmed.length,
|
||||||
|
'confirmed'
|
||||||
|
);
|
||||||
|
|
||||||
oldRecords.forEach(function(oldRecord) {
|
var confirmedCount = confirmed.length;
|
||||||
if ( oldRecord.keyId > activeSignedPreKeyId - 3 ) {
|
|
||||||
// keep at least the last 3 signed keys
|
// Keep MINIMUM_KEYS confirmed keys, then drop if older than a week
|
||||||
|
confirmed = confirmed.forEach(function(key, index) {
|
||||||
|
if (index < MINIMUM_KEYS) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var created_at = oldRecord.created_at || 0;
|
var created_at = key.created_at || 0;
|
||||||
var archiveDuration = Date.now() - created_at;
|
var age = Date.now() - created_at;
|
||||||
if (archiveDuration > ARCHIVE_AGE) {
|
if (age > ARCHIVE_AGE) {
|
||||||
console.log("Removing signed prekey record:",
|
console.log(
|
||||||
oldRecord.keyId, "with timestamp:", created_at);
|
'Removing confirmed signed prekey:',
|
||||||
store.removeSignedPreKey(oldRecord.keyId);
|
key.keyId,
|
||||||
|
'with timestamp:',
|
||||||
|
created_at
|
||||||
|
);
|
||||||
|
store.removeSignedPreKey(key.keyId);
|
||||||
|
confirmedCount--;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var stillNeeded = MINIMUM_KEYS - confirmedCount;
|
||||||
|
|
||||||
|
// If we still don't have enough total keys, we keep as many unconfirmed
|
||||||
|
// keys as necessary. If not necessary, and over a week old, we drop.
|
||||||
|
unconfirmed.forEach(function(key, index) {
|
||||||
|
if (index < stillNeeded) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var created_at = key.created_at || 0;
|
||||||
|
var age = Date.now() - created_at;
|
||||||
|
if (age > ARCHIVE_AGE) {
|
||||||
|
console.log(
|
||||||
|
'Removing unconfirmed signed prekey:',
|
||||||
|
key.keyId,
|
||||||
|
'with timestamp:',
|
||||||
|
created_at
|
||||||
|
);
|
||||||
|
store.removeSignedPreKey(key.keyId);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -618,8 +618,7 @@ MessageReceiver.prototype.extend({
|
||||||
console.log('Got SyncMessage Request');
|
console.log('Got SyncMessage Request');
|
||||||
return this.removeFromCache(envelope);
|
return this.removeFromCache(envelope);
|
||||||
} else if (syncMessage.read && syncMessage.read.length) {
|
} else if (syncMessage.read && syncMessage.read.length) {
|
||||||
console.log('read messages',
|
console.log('read messages from', this.getEnvelopeId(envelope));
|
||||||
'from', envelope.source + '.' + envelope.sourceDevice);
|
|
||||||
return this.handleRead(envelope, syncMessage.read);
|
return this.handleRead(envelope, syncMessage.read);
|
||||||
} else if (syncMessage.verified) {
|
} else if (syncMessage.verified) {
|
||||||
return this.handleVerified(envelope, syncMessage.verified);
|
return this.handleVerified(envelope, syncMessage.verified);
|
||||||
|
@ -730,6 +729,7 @@ MessageReceiver.prototype.extend({
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
},
|
},
|
||||||
handleBlocked: function(envelope, blocked) {
|
handleBlocked: function(envelope, blocked) {
|
||||||
|
console.log('Setting these numbers as blocked:', blocked.numbers);
|
||||||
textsecure.storage.put('blocked', blocked.numbers);
|
textsecure.storage.put('blocked', blocked.numbers);
|
||||||
},
|
},
|
||||||
isBlocked: function(number) {
|
isBlocked: function(number) {
|
||||||
|
|
|
@ -265,10 +265,11 @@ MessageSender.prototype = {
|
||||||
var proto = textsecure.protobuf.DataMessage.decode(encodedMessage);
|
var proto = textsecure.protobuf.DataMessage.decode(encodedMessage);
|
||||||
return new Promise(function(resolve, reject) {
|
return new Promise(function(resolve, reject) {
|
||||||
this.sendMessageProto(timestamp, numbers, proto, function(res) {
|
this.sendMessageProto(timestamp, numbers, proto, function(res) {
|
||||||
if (res.errors.length > 0)
|
if (res.errors.length > 0) {
|
||||||
reject(res);
|
reject(res);
|
||||||
else
|
} else {
|
||||||
resolve(res);
|
resolve(res);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
},
|
},
|
||||||
|
@ -320,7 +321,9 @@ MessageSender.prototype = {
|
||||||
syncMessage.sent = sentMessage;
|
syncMessage.sent = sentMessage;
|
||||||
var contentMessage = new textsecure.protobuf.Content();
|
var contentMessage = new textsecure.protobuf.Content();
|
||||||
contentMessage.syncMessage = syncMessage;
|
contentMessage.syncMessage = syncMessage;
|
||||||
return this.sendIndividualProto(myNumber, contentMessage, Date.now());
|
|
||||||
|
var silent = true;
|
||||||
|
return this.sendIndividualProto(myNumber, contentMessage, Date.now(), silent);
|
||||||
},
|
},
|
||||||
|
|
||||||
getProfile: function(number) {
|
getProfile: function(number) {
|
||||||
|
@ -341,7 +344,8 @@ MessageSender.prototype = {
|
||||||
var contentMessage = new textsecure.protobuf.Content();
|
var contentMessage = new textsecure.protobuf.Content();
|
||||||
contentMessage.syncMessage = syncMessage;
|
contentMessage.syncMessage = syncMessage;
|
||||||
|
|
||||||
return this.sendIndividualProto(myNumber, contentMessage, Date.now());
|
var silent = true;
|
||||||
|
return this.sendIndividualProto(myNumber, contentMessage, Date.now(), silent);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
|
@ -357,7 +361,8 @@ MessageSender.prototype = {
|
||||||
var contentMessage = new textsecure.protobuf.Content();
|
var contentMessage = new textsecure.protobuf.Content();
|
||||||
contentMessage.syncMessage = syncMessage;
|
contentMessage.syncMessage = syncMessage;
|
||||||
|
|
||||||
return this.sendIndividualProto(myNumber, contentMessage, Date.now());
|
var silent = true;
|
||||||
|
return this.sendIndividualProto(myNumber, contentMessage, Date.now(), silent);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
|
@ -374,7 +379,8 @@ MessageSender.prototype = {
|
||||||
var contentMessage = new textsecure.protobuf.Content();
|
var contentMessage = new textsecure.protobuf.Content();
|
||||||
contentMessage.syncMessage = syncMessage;
|
contentMessage.syncMessage = syncMessage;
|
||||||
|
|
||||||
return this.sendIndividualProto(myNumber, contentMessage, Date.now());
|
var silent = true;
|
||||||
|
return this.sendIndividualProto(myNumber, contentMessage, Date.now(), silent);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
|
@ -387,7 +393,8 @@ MessageSender.prototype = {
|
||||||
var contentMessage = new textsecure.protobuf.Content();
|
var contentMessage = new textsecure.protobuf.Content();
|
||||||
contentMessage.receiptMessage = receiptMessage;
|
contentMessage.receiptMessage = receiptMessage;
|
||||||
|
|
||||||
return this.sendIndividualProto(sender, contentMessage, Date.now(), true /*silent*/);
|
var silent = true;
|
||||||
|
return this.sendIndividualProto(sender, contentMessage, Date.now(), silent);
|
||||||
},
|
},
|
||||||
syncReadMessages: function(reads) {
|
syncReadMessages: function(reads) {
|
||||||
var myNumber = textsecure.storage.user.getNumber();
|
var myNumber = textsecure.storage.user.getNumber();
|
||||||
|
@ -404,7 +411,8 @@ MessageSender.prototype = {
|
||||||
var contentMessage = new textsecure.protobuf.Content();
|
var contentMessage = new textsecure.protobuf.Content();
|
||||||
contentMessage.syncMessage = syncMessage;
|
contentMessage.syncMessage = syncMessage;
|
||||||
|
|
||||||
return this.sendIndividualProto(myNumber, contentMessage, Date.now());
|
var silent = true;
|
||||||
|
return this.sendIndividualProto(myNumber, contentMessage, Date.now(), silent);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
|
@ -412,38 +420,44 @@ MessageSender.prototype = {
|
||||||
syncVerification: function(destination, state, identityKey) {
|
syncVerification: function(destination, state, identityKey) {
|
||||||
var myNumber = textsecure.storage.user.getNumber();
|
var myNumber = textsecure.storage.user.getNumber();
|
||||||
var myDevice = textsecure.storage.user.getDeviceId();
|
var myDevice = textsecure.storage.user.getDeviceId();
|
||||||
if (myDevice != 1) {
|
var now = Date.now();
|
||||||
// First send a null message to mask the sync message.
|
|
||||||
var nullMessage = new textsecure.protobuf.NullMessage();
|
|
||||||
|
|
||||||
// Generate a random int from 1 and 512
|
if (myDevice === 1) {
|
||||||
var buffer = libsignal.crypto.getRandomBytes(1);
|
return Promise.resolve();
|
||||||
var paddingLength = (new Uint8Array(buffer)[0] & 0x1ff) + 1;
|
|
||||||
|
|
||||||
// Generate a random padding buffer of the chosen size
|
|
||||||
nullMessage.padding = libsignal.crypto.getRandomBytes(paddingLength);
|
|
||||||
|
|
||||||
var contentMessage = new textsecure.protobuf.Content();
|
|
||||||
contentMessage.nullMessage = nullMessage;
|
|
||||||
|
|
||||||
return this.sendIndividualProto(destination, contentMessage, Date.now()).then(function() {
|
|
||||||
var verified = new textsecure.protobuf.Verified();
|
|
||||||
verified.state = state;
|
|
||||||
verified.destination = destination;
|
|
||||||
verified.identityKey = identityKey;
|
|
||||||
verified.nullMessage = nullMessage.padding;
|
|
||||||
|
|
||||||
var syncMessage = this.createSyncMessage();
|
|
||||||
syncMessage.verified = verified;
|
|
||||||
|
|
||||||
var contentMessage = new textsecure.protobuf.Content();
|
|
||||||
contentMessage.syncMessage = syncMessage;
|
|
||||||
|
|
||||||
return this.sendIndividualProto(myNumber, contentMessage, Date.now());
|
|
||||||
}.bind(this));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise.resolve();
|
// First send a null message to mask the sync message.
|
||||||
|
var nullMessage = new textsecure.protobuf.NullMessage();
|
||||||
|
|
||||||
|
// Generate a random int from 1 and 512
|
||||||
|
var buffer = libsignal.crypto.getRandomBytes(1);
|
||||||
|
var paddingLength = (new Uint8Array(buffer)[0] & 0x1ff) + 1;
|
||||||
|
|
||||||
|
// Generate a random padding buffer of the chosen size
|
||||||
|
nullMessage.padding = libsignal.crypto.getRandomBytes(paddingLength);
|
||||||
|
|
||||||
|
var contentMessage = new textsecure.protobuf.Content();
|
||||||
|
contentMessage.nullMessage = nullMessage;
|
||||||
|
|
||||||
|
// We want the NullMessage to look like a normal outgoing message; not silent
|
||||||
|
const promise = this.sendIndividualProto(destination, contentMessage, now);
|
||||||
|
|
||||||
|
return promise.then(function() {
|
||||||
|
var verified = new textsecure.protobuf.Verified();
|
||||||
|
verified.state = state;
|
||||||
|
verified.destination = destination;
|
||||||
|
verified.identityKey = identityKey;
|
||||||
|
verified.nullMessage = nullMessage.padding;
|
||||||
|
|
||||||
|
var syncMessage = this.createSyncMessage();
|
||||||
|
syncMessage.verified = verified;
|
||||||
|
|
||||||
|
var contentMessage = new textsecure.protobuf.Content();
|
||||||
|
contentMessage.syncMessage = syncMessage;
|
||||||
|
|
||||||
|
var silent = true;
|
||||||
|
return this.sendIndividualProto(myNumber, contentMessage, now, silent);
|
||||||
|
}.bind(this));
|
||||||
},
|
},
|
||||||
|
|
||||||
sendGroupProto: function(numbers, proto, timestamp) {
|
sendGroupProto: function(numbers, proto, timestamp) {
|
||||||
|
@ -455,14 +469,17 @@ MessageSender.prototype = {
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Promise(function(resolve, reject) {
|
return new Promise(function(resolve, reject) {
|
||||||
this.sendMessageProto(timestamp, numbers, proto, function(res) {
|
var silent = true;
|
||||||
|
var callback = function(res) {
|
||||||
res.dataMessage = proto.toArrayBuffer();
|
res.dataMessage = proto.toArrayBuffer();
|
||||||
if (res.errors.length > 0) {
|
if (res.errors.length > 0) {
|
||||||
reject(res);
|
reject(res);
|
||||||
} else {
|
} else {
|
||||||
resolve(res);
|
resolve(res);
|
||||||
}
|
}
|
||||||
}.bind(this));
|
}.bind(this);
|
||||||
|
|
||||||
|
this.sendMessageProto(timestamp, numbers, proto, callback, silent);
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
mocha.setup("bdd");
|
mocha.setup("bdd");
|
||||||
window.assert = chai.assert;
|
window.assert = chai.assert;
|
||||||
|
window.PROTO_ROOT = '../../protos';
|
||||||
|
|
||||||
(function() {
|
(function() {
|
||||||
var OriginalReporter = mocha._reporter;
|
var OriginalReporter = mocha._reporter;
|
||||||
|
@ -52,3 +53,5 @@ function hexToArrayBuffer(str) {
|
||||||
array[i] = parseInt(str.substr(i*2, 2), 16);
|
array[i] = parseInt(str.substr(i*2, 2), 16);
|
||||||
return ret;
|
return ret;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
window.MockSocket.prototype.addEventListener = function() {};
|
||||||
|
|
156
libtextsecure/test/account_manager_test.js
Normal file
156
libtextsecure/test/account_manager_test.js
Normal file
|
@ -0,0 +1,156 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
describe("AccountManager", function() {
|
||||||
|
let accountManager;
|
||||||
|
let originalServer;
|
||||||
|
|
||||||
|
before(function() {
|
||||||
|
originalServer = window.TextSecureServer;
|
||||||
|
window.TextSecureServer = function() {};
|
||||||
|
});
|
||||||
|
after(function() {
|
||||||
|
window.TextSecureServer = originalServer;
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(function() {
|
||||||
|
accountManager = new window.textsecure.AccountManager();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#cleanSignedPreKeys', function() {
|
||||||
|
let originalProtocolStorage;
|
||||||
|
let signedPreKeys;
|
||||||
|
const DAY = 1000 * 60 * 60 * 24;
|
||||||
|
|
||||||
|
beforeEach(function() {
|
||||||
|
originalProtocolStorage = window.textsecure.storage.protocol;
|
||||||
|
window.textsecure.storage.protocol = {
|
||||||
|
loadSignedPreKeys: function() {
|
||||||
|
return Promise.resolve(signedPreKeys);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
afterEach(function() {
|
||||||
|
window.textsecure.storage.protocol = originalProtocolStorage;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('keeps three confirmed keys even if over a week old', function() {
|
||||||
|
const now = Date.now();
|
||||||
|
signedPreKeys = [{
|
||||||
|
keyId: 1,
|
||||||
|
created_at: now - DAY * 21,
|
||||||
|
confirmed: true,
|
||||||
|
}, {
|
||||||
|
keyId: 2,
|
||||||
|
created_at: now - DAY * 14,
|
||||||
|
confirmed: true,
|
||||||
|
}, {
|
||||||
|
keyId: 3,
|
||||||
|
created_at: now - DAY * 18,
|
||||||
|
confirmed: true,
|
||||||
|
}];
|
||||||
|
|
||||||
|
// should be no calls to store.removeSignedPreKey, would cause crash
|
||||||
|
return accountManager.cleanSignedPreKeys();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('eliminates confirmed keys over a week old, if more than three', function() {
|
||||||
|
const now = Date.now();
|
||||||
|
signedPreKeys = [{
|
||||||
|
keyId: 1,
|
||||||
|
created_at: now - DAY * 21,
|
||||||
|
confirmed: true,
|
||||||
|
}, {
|
||||||
|
keyId: 2,
|
||||||
|
created_at: now - DAY * 14,
|
||||||
|
confirmed: true,
|
||||||
|
}, {
|
||||||
|
keyId: 3,
|
||||||
|
created_at: now - DAY * 4,
|
||||||
|
confirmed: true,
|
||||||
|
}, {
|
||||||
|
keyId: 4,
|
||||||
|
created_at: now - DAY * 18,
|
||||||
|
confirmed: true,
|
||||||
|
}, {
|
||||||
|
keyId: 5,
|
||||||
|
created_at: now - DAY,
|
||||||
|
confirmed: true,
|
||||||
|
}];
|
||||||
|
|
||||||
|
let count = 0;
|
||||||
|
window.textsecure.storage.protocol.removeSignedPreKey = function(keyId) {
|
||||||
|
if (keyId !== 1 && keyId !== 4) {
|
||||||
|
throw new Error('Wrong keys were eliminated! ' + keyId);
|
||||||
|
}
|
||||||
|
|
||||||
|
count++;
|
||||||
|
};
|
||||||
|
|
||||||
|
return accountManager.cleanSignedPreKeys().then(function() {
|
||||||
|
assert.strictEqual(count, 2);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('keeps at least three unconfirmed keys if no confirmed', function() {
|
||||||
|
const now = Date.now();
|
||||||
|
signedPreKeys = [{
|
||||||
|
keyId: 1,
|
||||||
|
created_at: now - DAY * 14,
|
||||||
|
}, {
|
||||||
|
keyId: 2,
|
||||||
|
created_at: now - DAY * 21,
|
||||||
|
}, {
|
||||||
|
keyId: 3,
|
||||||
|
created_at: now - DAY * 18,
|
||||||
|
}, {
|
||||||
|
keyId: 4,
|
||||||
|
created_at: now - DAY
|
||||||
|
}];
|
||||||
|
|
||||||
|
let count = 0;
|
||||||
|
window.textsecure.storage.protocol.removeSignedPreKey = function(keyId) {
|
||||||
|
if (keyId !== 2) {
|
||||||
|
throw new Error('Wrong keys were eliminated! ' + keyId);
|
||||||
|
}
|
||||||
|
|
||||||
|
count++;
|
||||||
|
};
|
||||||
|
|
||||||
|
return accountManager.cleanSignedPreKeys().then(function() {
|
||||||
|
assert.strictEqual(count, 1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('if some confirmed keys, keeps unconfirmed to addd up to three total', function() {
|
||||||
|
const now = Date.now();
|
||||||
|
signedPreKeys = [{
|
||||||
|
keyId: 1,
|
||||||
|
created_at: now - DAY * 21,
|
||||||
|
confirmed: true,
|
||||||
|
}, {
|
||||||
|
keyId: 2,
|
||||||
|
created_at: now - DAY * 14,
|
||||||
|
confirmed: true,
|
||||||
|
}, {
|
||||||
|
keyId: 3,
|
||||||
|
created_at: now - DAY * 12,
|
||||||
|
}, {
|
||||||
|
keyId: 4,
|
||||||
|
created_at: now - DAY * 8,
|
||||||
|
}];
|
||||||
|
|
||||||
|
let count = 0;
|
||||||
|
window.textsecure.storage.protocol.removeSignedPreKey = function(keyId) {
|
||||||
|
if (keyId !== 3) {
|
||||||
|
throw new Error('Wrong keys were eliminated! ' + keyId);
|
||||||
|
}
|
||||||
|
|
||||||
|
count++;
|
||||||
|
};
|
||||||
|
|
||||||
|
return accountManager.cleanSignedPreKeys().then(function() {
|
||||||
|
assert.strictEqual(count, 1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -1,6 +1,7 @@
|
||||||
<html>
|
<html>
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
|
<meta charset='utf-8'>
|
||||||
<title>libTextSecure test runner</title>
|
<title>libTextSecure test runner</title>
|
||||||
<link rel="stylesheet" href="../../components/mocha/mocha.css" />
|
<link rel="stylesheet" href="../../components/mocha/mocha.css" />
|
||||||
</head>
|
</head>
|
||||||
|
@ -12,7 +13,6 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script type="text/javascript" src="test.js"></script>
|
<script type="text/javascript" src="test.js"></script>
|
||||||
<script type="text/javascript" src="blanket_mocha.js"></script>
|
|
||||||
<script type="text/javascript" src="in_memory_signal_protocol_store.js"></script>
|
<script type="text/javascript" src="in_memory_signal_protocol_store.js"></script>
|
||||||
|
|
||||||
<script type="text/javascript" src="../components.js"></script>
|
<script type="text/javascript" src="../components.js"></script>
|
||||||
|
@ -23,12 +23,12 @@
|
||||||
<script type="text/javascript" src="../storage.js" data-cover></script>
|
<script type="text/javascript" src="../storage.js" data-cover></script>
|
||||||
<script type="text/javascript" src="../protocol_wrapper.js" data-cover></script>
|
<script type="text/javascript" src="../protocol_wrapper.js" data-cover></script>
|
||||||
|
|
||||||
|
<script type="text/javascript" src="../event_target.js" data-cover></script>
|
||||||
<script type="text/javascript" src="../websocket-resources.js" data-cover></script>
|
<script type="text/javascript" src="../websocket-resources.js" data-cover></script>
|
||||||
<script type="text/javascript" src="../helpers.js" data-cover></script>
|
<script type="text/javascript" src="../helpers.js" data-cover></script>
|
||||||
<script type="text/javascript" src="../stringview.js" data-cover></script>
|
<script type="text/javascript" src="../stringview.js" data-cover></script>
|
||||||
<script type="text/javascript" src="../api.js"></script>
|
<script type="text/javascript" src="../api.js"></script>
|
||||||
<script type="text/javascript" src="../sendmessage.js" data-cover></script>
|
<script type="text/javascript" src="../sendmessage.js" data-cover></script>
|
||||||
<script type="text/javascript" src="../event_target.js" data-cover></script>
|
|
||||||
<script type="text/javascript" src="../account_manager.js" data-cover></script>
|
<script type="text/javascript" src="../account_manager.js" data-cover></script>
|
||||||
<script type="text/javascript" src="../contacts_parser.js" data-cover></script>
|
<script type="text/javascript" src="../contacts_parser.js" data-cover></script>
|
||||||
<script type="text/javascript" src="../task_with_timeout.js" data-cover></script>
|
<script type="text/javascript" src="../task_with_timeout.js" data-cover></script>
|
||||||
|
@ -42,5 +42,14 @@
|
||||||
<script type="text/javascript" src="generate_keys_test.js"></script>
|
<script type="text/javascript" src="generate_keys_test.js"></script>
|
||||||
<script type="text/javascript" src="websocket-resources_test.js"></script>
|
<script type="text/javascript" src="websocket-resources_test.js"></script>
|
||||||
<script type="text/javascript" src="task_with_timeout_test.js"></script>
|
<script type="text/javascript" src="task_with_timeout_test.js"></script>
|
||||||
|
<script type="text/javascript" src="account_manager_test.js"></script>
|
||||||
|
|
||||||
|
<!-- Comment out to turn off code coverage. Useful for getting real callstacks. -->
|
||||||
|
<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>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
../../protos/
|
|
|
@ -22054,6 +22054,7 @@ Library.prototype.test = function(obj, type) {
|
||||||
});
|
});
|
||||||
mocha.setup("bdd");
|
mocha.setup("bdd");
|
||||||
window.assert = chai.assert;
|
window.assert = chai.assert;
|
||||||
|
window.PROTO_ROOT = '../../protos';
|
||||||
|
|
||||||
(function() {
|
(function() {
|
||||||
var OriginalReporter = mocha._reporter;
|
var OriginalReporter = mocha._reporter;
|
||||||
|
@ -22106,3 +22107,5 @@ function hexToArrayBuffer(str) {
|
||||||
array[i] = parseInt(str.substr(i*2, 2), 16);
|
array[i] = parseInt(str.substr(i*2, 2), 16);
|
||||||
return ret;
|
return ret;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
window.MockSocket.prototype.addEventListener = function() {};
|
||||||
|
|
|
@ -18,7 +18,8 @@
|
||||||
assert.strictEqual(message.response.status, 200);
|
assert.strictEqual(message.response.status, 200);
|
||||||
assert.strictEqual(message.response.id.toString(), request_id);
|
assert.strictEqual(message.response.id.toString(), request_id);
|
||||||
done();
|
done();
|
||||||
}
|
},
|
||||||
|
addEventListener: function() {},
|
||||||
};
|
};
|
||||||
|
|
||||||
// actual test
|
// actual test
|
||||||
|
@ -58,7 +59,8 @@
|
||||||
assert.strictEqual(message.request.path, '/some/path');
|
assert.strictEqual(message.request.path, '/some/path');
|
||||||
assertEqualArrayBuffers(message.request.body.toArrayBuffer(), new Uint8Array([1,2,3]).buffer);
|
assertEqualArrayBuffers(message.request.body.toArrayBuffer(), new Uint8Array([1,2,3]).buffer);
|
||||||
request_id = message.request.id;
|
request_id = message.request.id;
|
||||||
}
|
},
|
||||||
|
addEventListener: function() {},
|
||||||
};
|
};
|
||||||
|
|
||||||
// actual test
|
// actual test
|
||||||
|
|
80
main.js
80
main.js
|
@ -110,13 +110,41 @@ function captureClicks(window) {
|
||||||
window.webContents.on('new-window', handleUrl);
|
window.webContents.on('new-window', handleUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const DEFAULT_WIDTH = 800;
|
||||||
|
const DEFAULT_HEIGHT = 610;
|
||||||
|
const MIN_WIDTH = 700;
|
||||||
|
const MIN_HEIGHT = 360;
|
||||||
|
const BOUNDS_BUFFER = 100;
|
||||||
|
|
||||||
|
function isVisible(window, bounds) {
|
||||||
|
const boundsX = _.get(bounds, 'x') || 0;
|
||||||
|
const boundsY = _.get(bounds, 'y') || 0;
|
||||||
|
const boundsWidth = _.get(bounds, 'width') || DEFAULT_WIDTH;
|
||||||
|
const boundsHeight = _.get(bounds, 'height') || DEFAULT_HEIGHT;
|
||||||
|
|
||||||
|
// requiring BOUNDS_BUFFER pixels on the left or right side
|
||||||
|
const rightSideClearOfLeftBound = (window.x + window.width >= boundsX + BOUNDS_BUFFER);
|
||||||
|
const leftSideClearOfRightBound = (window.x <= boundsX + boundsWidth - BOUNDS_BUFFER);
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
|
||||||
|
return rightSideClearOfLeftBound
|
||||||
|
&& leftSideClearOfRightBound
|
||||||
|
&& topClearOfUpperBound
|
||||||
|
&& topClearOfLowerBound;
|
||||||
|
}
|
||||||
|
|
||||||
function createWindow () {
|
function createWindow () {
|
||||||
|
const screen = electron.screen;
|
||||||
const windowOptions = Object.assign({
|
const windowOptions = Object.assign({
|
||||||
show: !startInTray, // allow to start minimised in tray
|
show: !startInTray, // allow to start minimised in tray
|
||||||
width: 800,
|
width: DEFAULT_WIDTH,
|
||||||
height: 610,
|
height: DEFAULT_HEIGHT,
|
||||||
minWidth: 700,
|
minWidth: MIN_WIDTH,
|
||||||
minHeight: 360,
|
minHeight: MIN_HEIGHT,
|
||||||
autoHideMenuBar: false,
|
autoHideMenuBar: false,
|
||||||
webPreferences: {
|
webPreferences: {
|
||||||
nodeIntegration: false,
|
nodeIntegration: false,
|
||||||
|
@ -124,7 +152,33 @@ function createWindow () {
|
||||||
preload: path.join(__dirname, 'preload.js')
|
preload: path.join(__dirname, 'preload.js')
|
||||||
},
|
},
|
||||||
icon: path.join(__dirname, 'images', 'icon_256.png'),
|
icon: path.join(__dirname, 'images', 'icon_256.png'),
|
||||||
}, windowConfig);
|
}, _.pick(windowConfig, ['maximized', 'autoHideMenuBar', 'width', 'height', 'x', 'y']));
|
||||||
|
|
||||||
|
if (!_.isNumber(windowOptions.width) || windowOptions.width < MIN_WIDTH) {
|
||||||
|
windowOptions.width = DEFAULT_WIDTH;
|
||||||
|
}
|
||||||
|
if (!_.isNumber(windowOptions.height) || windowOptions.height < MIN_HEIGHT) {
|
||||||
|
windowOptions.height = DEFAULT_HEIGHT;
|
||||||
|
}
|
||||||
|
if (!_.isBoolean(windowOptions.maximized)) {
|
||||||
|
delete windowOptions.maximized;
|
||||||
|
}
|
||||||
|
if (!_.isBoolean(windowOptions.autoHideMenuBar)) {
|
||||||
|
delete windowOptions.autoHideMenuBar;
|
||||||
|
}
|
||||||
|
|
||||||
|
const visibleOnAnyScreen = _.some(screen.getAllDisplays(), function(display) {
|
||||||
|
if (!_.isNumber(windowOptions.x) || !_.isNumber(windowOptions.y)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return isVisible(windowOptions, _.get(display, 'bounds'));
|
||||||
|
});
|
||||||
|
if (!visibleOnAnyScreen) {
|
||||||
|
console.log('Location reset needed');
|
||||||
|
delete windowOptions.x;
|
||||||
|
delete windowOptions.y;
|
||||||
|
}
|
||||||
|
|
||||||
if (windowOptions.fullscreen === false) {
|
if (windowOptions.fullscreen === false) {
|
||||||
delete windowOptions.fullscreen;
|
delete windowOptions.fullscreen;
|
||||||
|
@ -175,6 +229,8 @@ function createWindow () {
|
||||||
|
|
||||||
if (config.environment === 'test') {
|
if (config.environment === 'test') {
|
||||||
mainWindow.loadURL(prepareURL([__dirname, 'test', 'index.html']));
|
mainWindow.loadURL(prepareURL([__dirname, 'test', 'index.html']));
|
||||||
|
} else if (config.environment === 'test-lib') {
|
||||||
|
mainWindow.loadURL(prepareURL([__dirname, 'libtextsecure', 'test', 'index.html']));
|
||||||
} else {
|
} else {
|
||||||
mainWindow.loadURL(prepareURL([__dirname, 'background.html']));
|
mainWindow.loadURL(prepareURL([__dirname, 'background.html']));
|
||||||
}
|
}
|
||||||
|
@ -195,7 +251,9 @@ function createWindow () {
|
||||||
mainWindow.on('close', function (e) {
|
mainWindow.on('close', function (e) {
|
||||||
|
|
||||||
// If the application is terminating, just do the default
|
// If the application is terminating, just do the default
|
||||||
if (windowState.shouldQuit() || config.environment === 'test') {
|
if (windowState.shouldQuit()
|
||||||
|
|| config.environment === 'test' || config.environment === 'test-lib') {
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -349,7 +407,7 @@ app.on('before-quit', function() {
|
||||||
app.on('window-all-closed', function () {
|
app.on('window-all-closed', function () {
|
||||||
// On OS X it is common for applications and their menu bar
|
// On OS X it is common for applications and their menu bar
|
||||||
// to stay active until the user quits explicitly with Cmd + Q
|
// to stay active until the user quits explicitly with Cmd + Q
|
||||||
if (process.platform !== 'darwin' || config.environment === 'test') {
|
if (process.platform !== 'darwin' || config.environment === 'test' || config.environment === 'test-lib') {
|
||||||
app.quit()
|
app.quit()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -394,11 +452,15 @@ ipc.on('restart', function(event) {
|
||||||
});
|
});
|
||||||
|
|
||||||
ipc.on("set-auto-hide-menu-bar", function(event, autoHide) {
|
ipc.on("set-auto-hide-menu-bar", function(event, autoHide) {
|
||||||
mainWindow.setAutoHideMenuBar(autoHide);
|
if (mainWindow) {
|
||||||
|
mainWindow.setAutoHideMenuBar(autoHide);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
ipc.on("set-menu-bar-visibility", function(event, visibility) {
|
ipc.on("set-menu-bar-visibility", function(event, visibility) {
|
||||||
mainWindow.setMenuBarVisibility(visibility);
|
if (mainWindow) {
|
||||||
|
mainWindow.setMenuBarVisibility(visibility);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
ipc.on("close-about", function() {
|
ipc.on("close-about", function() {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue