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() {
 | 
			
		||||
      var environment = grunt.option('env') || 'test';
 | 
			
		||||
      var done = this.async();
 | 
			
		||||
      var failure;
 | 
			
		||||
 | 
			
		||||
      var Application = require('spectron').Application;
 | 
			
		||||
      var electronBinary = process.platform === 'win32' ? 'electron.cmd' : 'electron';
 | 
			
		||||
      var app = new Application({
 | 
			
		||||
        path: path.join(__dirname, 'node_modules', '.bin', electronBinary),
 | 
			
		||||
        args: [path.join(__dirname, 'main.js')],
 | 
			
		||||
        env: {
 | 
			
		||||
          NODE_ENV: environment
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      function getMochaResults() {
 | 
			
		||||
        return window.mochaResults;
 | 
			
		||||
  function runTests(environment, cb) {
 | 
			
		||||
    var failure;
 | 
			
		||||
    var Application = require('spectron').Application;
 | 
			
		||||
    var electronBinary = process.platform === 'win32' ? 'electron.cmd' : 'electron';
 | 
			
		||||
    var app = new Application({
 | 
			
		||||
      path: path.join(__dirname, 'node_modules', '.bin', electronBinary),
 | 
			
		||||
      args: [path.join(__dirname, 'main.js')],
 | 
			
		||||
      env: {
 | 
			
		||||
        NODE_ENV: environment
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
      app.start().then(function() {
 | 
			
		||||
        return app.client.waitUntil(function() {
 | 
			
		||||
          return app.client.execute(getMochaResults).then(function(data) {
 | 
			
		||||
            return Boolean(data.value);
 | 
			
		||||
          });
 | 
			
		||||
        }, 10000, 'Expected to find window.mochaResults set!');
 | 
			
		||||
      }).then(function() {
 | 
			
		||||
        return app.client.execute(getMochaResults);
 | 
			
		||||
      }).then(function(data) {
 | 
			
		||||
        var results = data.value;
 | 
			
		||||
        if (results.failures > 0) {
 | 
			
		||||
          console.error(results.reports);
 | 
			
		||||
          failure = function() {
 | 
			
		||||
            grunt.fail.fatal('Found ' + results.failures + ' failing unit tests.');
 | 
			
		||||
          };
 | 
			
		||||
          return app.client.log('browser');
 | 
			
		||||
        } 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) {
 | 
			
		||||
    function getMochaResults() {
 | 
			
		||||
      return window.mochaResults;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    app.start().then(function() {
 | 
			
		||||
      return app.client.waitUntil(function() {
 | 
			
		||||
        return app.client.execute(getMochaResults).then(function(data) {
 | 
			
		||||
          return Boolean(data.value);
 | 
			
		||||
        });
 | 
			
		||||
      }, 10000, 'Expected to find window.mochaResults set!');
 | 
			
		||||
    }).then(function() {
 | 
			
		||||
      return app.client.execute(getMochaResults);
 | 
			
		||||
    }).then(function(data) {
 | 
			
		||||
      var results = data.value;
 | 
			
		||||
      if (results.failures > 0) {
 | 
			
		||||
        console.error(results.reports);
 | 
			
		||||
        failure = function() {
 | 
			
		||||
          grunt.fail.fatal('Something went wrong: ' + error.message + ' ' + error.stack);
 | 
			
		||||
          grunt.fail.fatal('Found ' + results.failures + ' failing unit tests.');
 | 
			
		||||
        };
 | 
			
		||||
      }).then(function () {
 | 
			
		||||
        // We need to use the failure variable and this early stop to clean up before
 | 
			
		||||
        // shutting down. Grunt's fail methods are the only way to set the return value,
 | 
			
		||||
        // but they shut the process down immediately!
 | 
			
		||||
        return app.stop();
 | 
			
		||||
      }).then(function() {
 | 
			
		||||
        if (failure) {
 | 
			
		||||
          failure();
 | 
			
		||||
        }
 | 
			
		||||
        done();
 | 
			
		||||
      }).catch(function (error) {
 | 
			
		||||
        console.error('Second-level error:', error.message, error.stack);
 | 
			
		||||
        if (failure) {
 | 
			
		||||
          failure();
 | 
			
		||||
        }
 | 
			
		||||
        done();
 | 
			
		||||
      });
 | 
			
		||||
        return app.client.log('browser');
 | 
			
		||||
      } 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() {
 | 
			
		||||
        grunt.fail.fatal('Something went wrong: ' + error.message + ' ' + error.stack);
 | 
			
		||||
      };
 | 
			
		||||
    }).then(function () {
 | 
			
		||||
      // We need to use the failure variable and this early stop to clean up before
 | 
			
		||||
      // shutting down. Grunt's fail methods are the only way to set the return value,
 | 
			
		||||
      // but they shut the process down immediately!
 | 
			
		||||
      return app.stop();
 | 
			
		||||
    }).then(function() {
 | 
			
		||||
      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() {
 | 
			
		||||
| 
						 | 
				
			
			@ -473,7 +484,7 @@ module.exports = function(grunt) {
 | 
			
		|||
 | 
			
		||||
  grunt.registerTask('tx', ['exec:tx-pull', 'locale-patch']);
 | 
			
		||||
  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('date', ['gitinfo', 'getExpireTime']);
 | 
			
		||||
  grunt.registerTask('prep-release', ['gitinfo', 'clean-release', 'fetch-release']);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -93,8 +93,19 @@ function fetch(logPath) {
 | 
			
		|||
    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) {
 | 
			
		||||
    const data = _.flatten(results);
 | 
			
		||||
 | 
			
		||||
    data.push(fileListEntry);
 | 
			
		||||
 | 
			
		||||
    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() {
 | 
			
		||||
        var currentVersion = window.config.version;
 | 
			
		||||
        var lastVersion = storage.get('version');
 | 
			
		||||
        var newVersion = !lastVersion || currentVersion !== lastVersion;
 | 
			
		||||
        storage.put('version', currentVersion);
 | 
			
		||||
 | 
			
		||||
        if (!lastVersion || currentVersion !== lastVersion) {
 | 
			
		||||
        if (newVersion) {
 | 
			
		||||
            console.log('New version detected:', currentVersion);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -99,7 +100,7 @@
 | 
			
		|||
        console.log('listening for registration events');
 | 
			
		||||
        Whisper.events.on('registration_done', function() {
 | 
			
		||||
            console.log('handling registration event');
 | 
			
		||||
            Whisper.RotateSignedPreKeyListener.init(Whisper.events);
 | 
			
		||||
            Whisper.RotateSignedPreKeyListener.init(Whisper.events, newVersion);
 | 
			
		||||
            connect(true);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -112,7 +113,7 @@
 | 
			
		|||
            console.log('Import was interrupted, showing import error screen');
 | 
			
		||||
            appView.openImporter();
 | 
			
		||||
        } else if (Whisper.Registration.everDone()) {
 | 
			
		||||
            Whisper.RotateSignedPreKeyListener.init(Whisper.events);
 | 
			
		||||
            Whisper.RotateSignedPreKeyListener.init(Whisper.events, newVersion);
 | 
			
		||||
            connect();
 | 
			
		||||
            appView.openInbox({
 | 
			
		||||
                initialLoadComplete: initialLoadComplete
 | 
			
		||||
| 
						 | 
				
			
			@ -377,6 +378,14 @@
 | 
			
		|||
        return ConversationController.getOrCreateAndWait(id, 'private')
 | 
			
		||||
            .then(function(conversation) {
 | 
			
		||||
                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) {
 | 
			
		||||
                      conversation.set({profileKey: details.profileKey});
 | 
			
		||||
                    }
 | 
			
		||||
| 
						 | 
				
			
			@ -384,7 +393,7 @@
 | 
			
		|||
                        name: details.name,
 | 
			
		||||
                        avatar: details.avatar,
 | 
			
		||||
                        color: details.color,
 | 
			
		||||
                        active_at: conversation.get('active_at') || Date.now(),
 | 
			
		||||
                        active_at: activeAt,
 | 
			
		||||
                    }).then(resolve, reject);
 | 
			
		||||
                }).then(function() {
 | 
			
		||||
                    if (details.verified) {
 | 
			
		||||
| 
						 | 
				
			
			@ -421,7 +430,13 @@
 | 
			
		|||
                type: 'group',
 | 
			
		||||
            };
 | 
			
		||||
            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 {
 | 
			
		||||
                updates.left = true;
 | 
			
		||||
            }
 | 
			
		||||
| 
						 | 
				
			
			@ -553,8 +568,7 @@
 | 
			
		|||
 | 
			
		||||
    function onError(ev) {
 | 
			
		||||
        var error = ev.error;
 | 
			
		||||
        console.log(error);
 | 
			
		||||
        console.log(error.stack);
 | 
			
		||||
        console.log('background onError:', error && error.stack ? error.stack : error);
 | 
			
		||||
 | 
			
		||||
        if (error.name === 'HTTPError' && (error.code == 401 || error.code == 403)) {
 | 
			
		||||
            Whisper.Registration.remove();
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -38003,26 +38003,35 @@ var TextSecureServer = (function() {
 | 
			
		|||
        rotateSignedPreKey: function() {
 | 
			
		||||
            return this.queueTask(function() {
 | 
			
		||||
                var signedKeyId = textsecure.storage.get('signedKeyId', 1);
 | 
			
		||||
 | 
			
		||||
                if (typeof signedKeyId != 'number') {
 | 
			
		||||
                    throw new Error('Invalid signedKeyId');
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                var store = textsecure.storage.protocol;
 | 
			
		||||
                var server = this.server;
 | 
			
		||||
                var cleanSignedPreKeys = this.cleanSignedPreKeys;
 | 
			
		||||
 | 
			
		||||
                return store.getIdentityKeyPair().then(function(identityKey) {
 | 
			
		||||
                    return libsignal.KeyHelper.generateSignedPreKey(identityKey, signedKeyId);
 | 
			
		||||
                }).then(function(res) {
 | 
			
		||||
                    return server.setSignedPreKey({
 | 
			
		||||
                        keyId     : res.keyId,
 | 
			
		||||
                        publicKey : res.keyPair.pubKey,
 | 
			
		||||
                        signature : res.signature
 | 
			
		||||
                    console.log('Saving new signed prekey', res.keyId);
 | 
			
		||||
                    return Promise.all([
 | 
			
		||||
                        textsecure.storage.put('signedKeyId', signedKeyId + 1),
 | 
			
		||||
                        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() {
 | 
			
		||||
                        textsecure.storage.put('signedKeyId', signedKeyId + 1);
 | 
			
		||||
                        textsecure.storage.remove('signedKeyRotationRejected');
 | 
			
		||||
                        return store.storeSignedPreKey(res.keyId, res.keyPair).then(function() {
 | 
			
		||||
                            return cleanSignedPreKeys();
 | 
			
		||||
                        });
 | 
			
		||||
                        return cleanSignedPreKeys();
 | 
			
		||||
                    });
 | 
			
		||||
                }).catch(function(e) {
 | 
			
		||||
                    console.log(
 | 
			
		||||
| 
						 | 
				
			
			@ -38034,9 +38043,9 @@ var TextSecureServer = (function() {
 | 
			
		|||
                        var rejections = 1 + textsecure.storage.get('signedKeyRotationRejected', 0);
 | 
			
		||||
                        textsecure.storage.put('signedKeyRotationRejected', rejections);
 | 
			
		||||
                        console.log('Signed key rotation rejected count:', rejections);
 | 
			
		||||
                    } else {
 | 
			
		||||
                        throw e;
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    throw e;
 | 
			
		||||
                });
 | 
			
		||||
            }.bind(this));
 | 
			
		||||
        },
 | 
			
		||||
| 
						 | 
				
			
			@ -38045,35 +38054,72 @@ var TextSecureServer = (function() {
 | 
			
		|||
            return this.pending = this.pending.then(taskWithTimeout, taskWithTimeout);
 | 
			
		||||
        },
 | 
			
		||||
        cleanSignedPreKeys: function() {
 | 
			
		||||
            var nextSignedKeyId = textsecure.storage.get('signedKeyId');
 | 
			
		||||
            if (typeof nextSignedKeyId != 'number') {
 | 
			
		||||
                return Promise.resolve();
 | 
			
		||||
            }
 | 
			
		||||
            var activeSignedPreKeyId = nextSignedKeyId - 1;
 | 
			
		||||
 | 
			
		||||
            var MINIMUM_KEYS = 3;
 | 
			
		||||
            var store = textsecure.storage.protocol;
 | 
			
		||||
            return store.loadSignedPreKeys().then(function(allRecords) {
 | 
			
		||||
                var oldRecords = allRecords.filter(function(record) {
 | 
			
		||||
                    return record.keyId !== activeSignedPreKeyId;
 | 
			
		||||
                });
 | 
			
		||||
                oldRecords.sort(function(a, b) {
 | 
			
		||||
            return store.loadSignedPreKeys().then(function(allKeys) {
 | 
			
		||||
                allKeys.sort(function(a, b) {
 | 
			
		||||
                    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);
 | 
			
		||||
                console.log("Old signed prekey record count: " + oldRecords.length);
 | 
			
		||||
                var recent = allKeys[0] ? allKeys[0].keyId : 'none';
 | 
			
		||||
                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) {
 | 
			
		||||
                    if ( oldRecord.keyId > activeSignedPreKeyId - 3 ) {
 | 
			
		||||
                        // keep at least the last 3 signed keys
 | 
			
		||||
                var confirmedCount = confirmed.length;
 | 
			
		||||
 | 
			
		||||
                // Keep MINIMUM_KEYS confirmed keys, then drop if older than a week
 | 
			
		||||
                confirmed = confirmed.forEach(function(key, index) {
 | 
			
		||||
                    if (index < MINIMUM_KEYS) {
 | 
			
		||||
                        return;
 | 
			
		||||
                    }
 | 
			
		||||
                    var created_at = oldRecord.created_at || 0;
 | 
			
		||||
                    var archiveDuration = Date.now() - created_at;
 | 
			
		||||
                    if (archiveDuration > ARCHIVE_AGE) {
 | 
			
		||||
                        console.log("Removing signed prekey record:",
 | 
			
		||||
                          oldRecord.keyId, "with timestamp:", created_at);
 | 
			
		||||
                        store.removeSignedPreKey(oldRecord.keyId);
 | 
			
		||||
                    var created_at = key.created_at || 0;
 | 
			
		||||
                    var age = Date.now() - created_at;
 | 
			
		||||
                    if (age > ARCHIVE_AGE) {
 | 
			
		||||
                        console.log(
 | 
			
		||||
                            'Removing confirmed signed prekey:',
 | 
			
		||||
                            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');
 | 
			
		||||
            return this.removeFromCache(envelope);
 | 
			
		||||
        } else if (syncMessage.read && syncMessage.read.length) {
 | 
			
		||||
            console.log('read messages',
 | 
			
		||||
                    'from', envelope.source + '.' + envelope.sourceDevice);
 | 
			
		||||
            console.log('read messages from', this.getEnvelopeId(envelope));
 | 
			
		||||
            return this.handleRead(envelope, syncMessage.read);
 | 
			
		||||
        } else if (syncMessage.verified) {
 | 
			
		||||
            return this.handleVerified(envelope, syncMessage.verified);
 | 
			
		||||
| 
						 | 
				
			
			@ -39166,6 +39211,7 @@ MessageReceiver.prototype.extend({
 | 
			
		|||
        }.bind(this));
 | 
			
		||||
    },
 | 
			
		||||
    handleBlocked: function(envelope, blocked) {
 | 
			
		||||
        console.log('Setting these numbers as blocked:', blocked.numbers);
 | 
			
		||||
        textsecure.storage.put('blocked', blocked.numbers);
 | 
			
		||||
    },
 | 
			
		||||
    isBlocked: function(number) {
 | 
			
		||||
| 
						 | 
				
			
			@ -39908,10 +39954,11 @@ MessageSender.prototype = {
 | 
			
		|||
        var proto = textsecure.protobuf.DataMessage.decode(encodedMessage);
 | 
			
		||||
        return new Promise(function(resolve, reject) {
 | 
			
		||||
            this.sendMessageProto(timestamp, numbers, proto, function(res) {
 | 
			
		||||
                if (res.errors.length > 0)
 | 
			
		||||
                if (res.errors.length > 0) {
 | 
			
		||||
                    reject(res);
 | 
			
		||||
                else
 | 
			
		||||
                } else {
 | 
			
		||||
                    resolve(res);
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
        }.bind(this));
 | 
			
		||||
    },
 | 
			
		||||
| 
						 | 
				
			
			@ -39963,7 +40010,9 @@ MessageSender.prototype = {
 | 
			
		|||
        syncMessage.sent = sentMessage;
 | 
			
		||||
        var contentMessage = new textsecure.protobuf.Content();
 | 
			
		||||
        contentMessage.syncMessage = syncMessage;
 | 
			
		||||
        return this.sendIndividualProto(myNumber, contentMessage, Date.now());
 | 
			
		||||
 | 
			
		||||
        var silent = true;
 | 
			
		||||
        return this.sendIndividualProto(myNumber, contentMessage, Date.now(), silent);
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    getProfile: function(number) {
 | 
			
		||||
| 
						 | 
				
			
			@ -39984,7 +40033,8 @@ MessageSender.prototype = {
 | 
			
		|||
            var contentMessage = new textsecure.protobuf.Content();
 | 
			
		||||
            contentMessage.syncMessage = syncMessage;
 | 
			
		||||
 | 
			
		||||
            return this.sendIndividualProto(myNumber, contentMessage, Date.now());
 | 
			
		||||
            var silent = true;
 | 
			
		||||
            return this.sendIndividualProto(myNumber, contentMessage, Date.now(), silent);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return Promise.resolve();
 | 
			
		||||
| 
						 | 
				
			
			@ -40000,7 +40050,8 @@ MessageSender.prototype = {
 | 
			
		|||
            var contentMessage = new textsecure.protobuf.Content();
 | 
			
		||||
            contentMessage.syncMessage = syncMessage;
 | 
			
		||||
 | 
			
		||||
            return this.sendIndividualProto(myNumber, contentMessage, Date.now());
 | 
			
		||||
            var silent = true;
 | 
			
		||||
            return this.sendIndividualProto(myNumber, contentMessage, Date.now(), silent);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return Promise.resolve();
 | 
			
		||||
| 
						 | 
				
			
			@ -40017,7 +40068,8 @@ MessageSender.prototype = {
 | 
			
		|||
            var contentMessage = new textsecure.protobuf.Content();
 | 
			
		||||
            contentMessage.syncMessage = syncMessage;
 | 
			
		||||
 | 
			
		||||
            return this.sendIndividualProto(myNumber, contentMessage, Date.now());
 | 
			
		||||
            var silent = true;
 | 
			
		||||
            return this.sendIndividualProto(myNumber, contentMessage, Date.now(), silent);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return Promise.resolve();
 | 
			
		||||
| 
						 | 
				
			
			@ -40030,7 +40082,8 @@ MessageSender.prototype = {
 | 
			
		|||
        var contentMessage = new textsecure.protobuf.Content();
 | 
			
		||||
        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) {
 | 
			
		||||
        var myNumber = textsecure.storage.user.getNumber();
 | 
			
		||||
| 
						 | 
				
			
			@ -40047,7 +40100,8 @@ MessageSender.prototype = {
 | 
			
		|||
            var contentMessage = new textsecure.protobuf.Content();
 | 
			
		||||
            contentMessage.syncMessage = syncMessage;
 | 
			
		||||
 | 
			
		||||
            return this.sendIndividualProto(myNumber, contentMessage, Date.now());
 | 
			
		||||
            var silent = true;
 | 
			
		||||
            return this.sendIndividualProto(myNumber, contentMessage, Date.now(), silent);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return Promise.resolve();
 | 
			
		||||
| 
						 | 
				
			
			@ -40055,38 +40109,44 @@ MessageSender.prototype = {
 | 
			
		|||
    syncVerification: function(destination, state, identityKey) {
 | 
			
		||||
        var myNumber = textsecure.storage.user.getNumber();
 | 
			
		||||
        var myDevice = textsecure.storage.user.getDeviceId();
 | 
			
		||||
        if (myDevice != 1) {
 | 
			
		||||
            // First send a null message to mask the sync message.
 | 
			
		||||
            var nullMessage = new textsecure.protobuf.NullMessage();
 | 
			
		||||
        var now = Date.now();
 | 
			
		||||
 | 
			
		||||
            // 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;
 | 
			
		||||
 | 
			
		||||
            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));
 | 
			
		||||
        if (myDevice === 1) {
 | 
			
		||||
            return Promise.resolve();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        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) {
 | 
			
		||||
| 
						 | 
				
			
			@ -40098,14 +40158,17 @@ MessageSender.prototype = {
 | 
			
		|||
        }
 | 
			
		||||
 | 
			
		||||
        return new Promise(function(resolve, reject) {
 | 
			
		||||
            this.sendMessageProto(timestamp, numbers, proto, function(res) {
 | 
			
		||||
            var silent = true;
 | 
			
		||||
            var callback = function(res) {
 | 
			
		||||
                res.dataMessage = proto.toArrayBuffer();
 | 
			
		||||
                if (res.errors.length > 0) {
 | 
			
		||||
                    reject(res);
 | 
			
		||||
                } else {
 | 
			
		||||
                    resolve(res);
 | 
			
		||||
                }
 | 
			
		||||
            }.bind(this));
 | 
			
		||||
            }.bind(this);
 | 
			
		||||
 | 
			
		||||
            this.sendMessageProto(timestamp, numbers, proto, callback, silent);
 | 
			
		||||
        }.bind(this));
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -166,6 +166,10 @@ window.log = {
 | 
			
		|||
};
 | 
			
		||||
 | 
			
		||||
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.forEach(function(e) {
 | 
			
		||||
                console.log(e);
 | 
			
		||||
                console.log(e.reason, e.stack);
 | 
			
		||||
                console.log(
 | 
			
		||||
                    'Message.saveErrors:',
 | 
			
		||||
                    e && e.reason ? e.reason : null,
 | 
			
		||||
                    e && e.stack ? e.stack : e
 | 
			
		||||
                );
 | 
			
		||||
            });
 | 
			
		||||
            errors = errors.map(function(e) {
 | 
			
		||||
                if (e.constructor === Error ||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -65,13 +65,12 @@
 | 
			
		|||
            }
 | 
			
		||||
            initComplete = true;
 | 
			
		||||
 | 
			
		||||
            if (Whisper.Registration.isDone()) {
 | 
			
		||||
            if (newVersion) {
 | 
			
		||||
                runWhenOnline();
 | 
			
		||||
            } else {
 | 
			
		||||
                setTimeoutForNextRun();
 | 
			
		||||
            }
 | 
			
		||||
            events.on('registration_done', function() {
 | 
			
		||||
                scheduleNextRotation();
 | 
			
		||||
                setTimeoutForNextRun();
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            events.on('timetravel', function() {
 | 
			
		||||
                if (Whisper.Registration.isDone()) {
 | 
			
		||||
                    setTimeoutForNextRun();
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -186,11 +186,15 @@
 | 
			
		|||
            var prekey = new PreKey({id: keyId});
 | 
			
		||||
            return new Promise(function(resolve) {
 | 
			
		||||
                prekey.fetch().then(function() {
 | 
			
		||||
                    console.log('Successfully fetched prekey:', keyId);
 | 
			
		||||
                    resolve({
 | 
			
		||||
                        pubKey: prekey.attributes.publicKey,
 | 
			
		||||
                        privKey: prekey.attributes.privateKey
 | 
			
		||||
                        pubKey: prekey.get('publicKey'),
 | 
			
		||||
                        privKey: prekey.get('privateKey'),
 | 
			
		||||
                    });
 | 
			
		||||
                }).fail(resolve);
 | 
			
		||||
                }, function() {
 | 
			
		||||
                    console.log('Failed to load prekey:', keyId);
 | 
			
		||||
                    resolve();
 | 
			
		||||
                });
 | 
			
		||||
            });
 | 
			
		||||
        },
 | 
			
		||||
        storePreKey: function(keyId, keyPair) {
 | 
			
		||||
| 
						 | 
				
			
			@ -211,7 +215,16 @@
 | 
			
		|||
            this.trigger('removePreKey');
 | 
			
		||||
 | 
			
		||||
            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();
 | 
			
		||||
                });
 | 
			
		||||
            });
 | 
			
		||||
| 
						 | 
				
			
			@ -222,21 +235,23 @@
 | 
			
		|||
            var prekey = new SignedPreKey({id: keyId});
 | 
			
		||||
            return new Promise(function(resolve) {
 | 
			
		||||
                prekey.fetch().then(function() {
 | 
			
		||||
                    console.log('Successfully loaded prekey:', prekey.get('id'));
 | 
			
		||||
                    resolve({
 | 
			
		||||
                        pubKey     : prekey.get('publicKey'),
 | 
			
		||||
                        privKey    : prekey.get('privateKey'),
 | 
			
		||||
                        created_at : prekey.get('created_at'),
 | 
			
		||||
                        keyId      : prekey.get('id')
 | 
			
		||||
                        keyId      : prekey.get('id'),
 | 
			
		||||
                        confirmed  : prekey.get('confirmed'),
 | 
			
		||||
                    });
 | 
			
		||||
                }).fail(function() {
 | 
			
		||||
                    console.log("Failed to load signed prekey:", keyId);
 | 
			
		||||
                    console.log('Failed to load signed prekey:', keyId);
 | 
			
		||||
                    resolve();
 | 
			
		||||
                });
 | 
			
		||||
            });
 | 
			
		||||
        },
 | 
			
		||||
        loadSignedPreKeys: function() {
 | 
			
		||||
            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();
 | 
			
		||||
            return new Promise(function(resolve) {
 | 
			
		||||
| 
						 | 
				
			
			@ -246,18 +261,20 @@
 | 
			
		|||
                            pubKey     : prekey.get('publicKey'),
 | 
			
		||||
                            privKey    : prekey.get('privateKey'),
 | 
			
		||||
                            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({
 | 
			
		||||
                id         : keyId,
 | 
			
		||||
                publicKey  : keyPair.pubKey,
 | 
			
		||||
                privateKey : keyPair.privKey,
 | 
			
		||||
                created_at : Date.now()
 | 
			
		||||
                created_at : Date.now(),
 | 
			
		||||
                confirmed  : Boolean(confirmed),
 | 
			
		||||
            });
 | 
			
		||||
            return new Promise(function(resolve) {
 | 
			
		||||
                prekey.save().always(function() {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -118,26 +118,35 @@
 | 
			
		|||
        rotateSignedPreKey: function() {
 | 
			
		||||
            return this.queueTask(function() {
 | 
			
		||||
                var signedKeyId = textsecure.storage.get('signedKeyId', 1);
 | 
			
		||||
 | 
			
		||||
                if (typeof signedKeyId != 'number') {
 | 
			
		||||
                    throw new Error('Invalid signedKeyId');
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                var store = textsecure.storage.protocol;
 | 
			
		||||
                var server = this.server;
 | 
			
		||||
                var cleanSignedPreKeys = this.cleanSignedPreKeys;
 | 
			
		||||
 | 
			
		||||
                return store.getIdentityKeyPair().then(function(identityKey) {
 | 
			
		||||
                    return libsignal.KeyHelper.generateSignedPreKey(identityKey, signedKeyId);
 | 
			
		||||
                }).then(function(res) {
 | 
			
		||||
                    return server.setSignedPreKey({
 | 
			
		||||
                        keyId     : res.keyId,
 | 
			
		||||
                        publicKey : res.keyPair.pubKey,
 | 
			
		||||
                        signature : res.signature
 | 
			
		||||
                    console.log('Saving new signed prekey', res.keyId);
 | 
			
		||||
                    return Promise.all([
 | 
			
		||||
                        textsecure.storage.put('signedKeyId', signedKeyId + 1),
 | 
			
		||||
                        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() {
 | 
			
		||||
                        textsecure.storage.put('signedKeyId', signedKeyId + 1);
 | 
			
		||||
                        textsecure.storage.remove('signedKeyRotationRejected');
 | 
			
		||||
                        return store.storeSignedPreKey(res.keyId, res.keyPair).then(function() {
 | 
			
		||||
                            return cleanSignedPreKeys();
 | 
			
		||||
                        });
 | 
			
		||||
                        return cleanSignedPreKeys();
 | 
			
		||||
                    });
 | 
			
		||||
                }).catch(function(e) {
 | 
			
		||||
                    console.log(
 | 
			
		||||
| 
						 | 
				
			
			@ -149,9 +158,9 @@
 | 
			
		|||
                        var rejections = 1 + textsecure.storage.get('signedKeyRotationRejected', 0);
 | 
			
		||||
                        textsecure.storage.put('signedKeyRotationRejected', rejections);
 | 
			
		||||
                        console.log('Signed key rotation rejected count:', rejections);
 | 
			
		||||
                    } else {
 | 
			
		||||
                        throw e;
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    throw e;
 | 
			
		||||
                });
 | 
			
		||||
            }.bind(this));
 | 
			
		||||
        },
 | 
			
		||||
| 
						 | 
				
			
			@ -160,35 +169,72 @@
 | 
			
		|||
            return this.pending = this.pending.then(taskWithTimeout, taskWithTimeout);
 | 
			
		||||
        },
 | 
			
		||||
        cleanSignedPreKeys: function() {
 | 
			
		||||
            var nextSignedKeyId = textsecure.storage.get('signedKeyId');
 | 
			
		||||
            if (typeof nextSignedKeyId != 'number') {
 | 
			
		||||
                return Promise.resolve();
 | 
			
		||||
            }
 | 
			
		||||
            var activeSignedPreKeyId = nextSignedKeyId - 1;
 | 
			
		||||
 | 
			
		||||
            var MINIMUM_KEYS = 3;
 | 
			
		||||
            var store = textsecure.storage.protocol;
 | 
			
		||||
            return store.loadSignedPreKeys().then(function(allRecords) {
 | 
			
		||||
                var oldRecords = allRecords.filter(function(record) {
 | 
			
		||||
                    return record.keyId !== activeSignedPreKeyId;
 | 
			
		||||
                });
 | 
			
		||||
                oldRecords.sort(function(a, b) {
 | 
			
		||||
            return store.loadSignedPreKeys().then(function(allKeys) {
 | 
			
		||||
                allKeys.sort(function(a, b) {
 | 
			
		||||
                    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);
 | 
			
		||||
                console.log("Old signed prekey record count: " + oldRecords.length);
 | 
			
		||||
                var recent = allKeys[0] ? allKeys[0].keyId : 'none';
 | 
			
		||||
                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) {
 | 
			
		||||
                    if ( oldRecord.keyId > activeSignedPreKeyId - 3 ) {
 | 
			
		||||
                        // keep at least the last 3 signed keys
 | 
			
		||||
                var confirmedCount = confirmed.length;
 | 
			
		||||
 | 
			
		||||
                // Keep MINIMUM_KEYS confirmed keys, then drop if older than a week
 | 
			
		||||
                confirmed = confirmed.forEach(function(key, index) {
 | 
			
		||||
                    if (index < MINIMUM_KEYS) {
 | 
			
		||||
                        return;
 | 
			
		||||
                    }
 | 
			
		||||
                    var created_at = oldRecord.created_at || 0;
 | 
			
		||||
                    var archiveDuration = Date.now() - created_at;
 | 
			
		||||
                    if (archiveDuration > ARCHIVE_AGE) {
 | 
			
		||||
                        console.log("Removing signed prekey record:",
 | 
			
		||||
                          oldRecord.keyId, "with timestamp:", created_at);
 | 
			
		||||
                        store.removeSignedPreKey(oldRecord.keyId);
 | 
			
		||||
                    var created_at = key.created_at || 0;
 | 
			
		||||
                    var age = Date.now() - created_at;
 | 
			
		||||
                    if (age > ARCHIVE_AGE) {
 | 
			
		||||
                        console.log(
 | 
			
		||||
                            'Removing confirmed signed prekey:',
 | 
			
		||||
                            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');
 | 
			
		||||
            return this.removeFromCache(envelope);
 | 
			
		||||
        } else if (syncMessage.read && syncMessage.read.length) {
 | 
			
		||||
            console.log('read messages',
 | 
			
		||||
                    'from', envelope.source + '.' + envelope.sourceDevice);
 | 
			
		||||
            console.log('read messages from', this.getEnvelopeId(envelope));
 | 
			
		||||
            return this.handleRead(envelope, syncMessage.read);
 | 
			
		||||
        } else if (syncMessage.verified) {
 | 
			
		||||
            return this.handleVerified(envelope, syncMessage.verified);
 | 
			
		||||
| 
						 | 
				
			
			@ -730,6 +729,7 @@ MessageReceiver.prototype.extend({
 | 
			
		|||
        }.bind(this));
 | 
			
		||||
    },
 | 
			
		||||
    handleBlocked: function(envelope, blocked) {
 | 
			
		||||
        console.log('Setting these numbers as blocked:', blocked.numbers);
 | 
			
		||||
        textsecure.storage.put('blocked', blocked.numbers);
 | 
			
		||||
    },
 | 
			
		||||
    isBlocked: function(number) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -265,10 +265,11 @@ MessageSender.prototype = {
 | 
			
		|||
        var proto = textsecure.protobuf.DataMessage.decode(encodedMessage);
 | 
			
		||||
        return new Promise(function(resolve, reject) {
 | 
			
		||||
            this.sendMessageProto(timestamp, numbers, proto, function(res) {
 | 
			
		||||
                if (res.errors.length > 0)
 | 
			
		||||
                if (res.errors.length > 0) {
 | 
			
		||||
                    reject(res);
 | 
			
		||||
                else
 | 
			
		||||
                } else {
 | 
			
		||||
                    resolve(res);
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
        }.bind(this));
 | 
			
		||||
    },
 | 
			
		||||
| 
						 | 
				
			
			@ -320,7 +321,9 @@ MessageSender.prototype = {
 | 
			
		|||
        syncMessage.sent = sentMessage;
 | 
			
		||||
        var contentMessage = new textsecure.protobuf.Content();
 | 
			
		||||
        contentMessage.syncMessage = syncMessage;
 | 
			
		||||
        return this.sendIndividualProto(myNumber, contentMessage, Date.now());
 | 
			
		||||
 | 
			
		||||
        var silent = true;
 | 
			
		||||
        return this.sendIndividualProto(myNumber, contentMessage, Date.now(), silent);
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    getProfile: function(number) {
 | 
			
		||||
| 
						 | 
				
			
			@ -341,7 +344,8 @@ MessageSender.prototype = {
 | 
			
		|||
            var contentMessage = new textsecure.protobuf.Content();
 | 
			
		||||
            contentMessage.syncMessage = syncMessage;
 | 
			
		||||
 | 
			
		||||
            return this.sendIndividualProto(myNumber, contentMessage, Date.now());
 | 
			
		||||
            var silent = true;
 | 
			
		||||
            return this.sendIndividualProto(myNumber, contentMessage, Date.now(), silent);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return Promise.resolve();
 | 
			
		||||
| 
						 | 
				
			
			@ -357,7 +361,8 @@ MessageSender.prototype = {
 | 
			
		|||
            var contentMessage = new textsecure.protobuf.Content();
 | 
			
		||||
            contentMessage.syncMessage = syncMessage;
 | 
			
		||||
 | 
			
		||||
            return this.sendIndividualProto(myNumber, contentMessage, Date.now());
 | 
			
		||||
            var silent = true;
 | 
			
		||||
            return this.sendIndividualProto(myNumber, contentMessage, Date.now(), silent);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return Promise.resolve();
 | 
			
		||||
| 
						 | 
				
			
			@ -374,7 +379,8 @@ MessageSender.prototype = {
 | 
			
		|||
            var contentMessage = new textsecure.protobuf.Content();
 | 
			
		||||
            contentMessage.syncMessage = syncMessage;
 | 
			
		||||
 | 
			
		||||
            return this.sendIndividualProto(myNumber, contentMessage, Date.now());
 | 
			
		||||
            var silent = true;
 | 
			
		||||
            return this.sendIndividualProto(myNumber, contentMessage, Date.now(), silent);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return Promise.resolve();
 | 
			
		||||
| 
						 | 
				
			
			@ -387,7 +393,8 @@ MessageSender.prototype = {
 | 
			
		|||
        var contentMessage = new textsecure.protobuf.Content();
 | 
			
		||||
        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) {
 | 
			
		||||
        var myNumber = textsecure.storage.user.getNumber();
 | 
			
		||||
| 
						 | 
				
			
			@ -404,7 +411,8 @@ MessageSender.prototype = {
 | 
			
		|||
            var contentMessage = new textsecure.protobuf.Content();
 | 
			
		||||
            contentMessage.syncMessage = syncMessage;
 | 
			
		||||
 | 
			
		||||
            return this.sendIndividualProto(myNumber, contentMessage, Date.now());
 | 
			
		||||
            var silent = true;
 | 
			
		||||
            return this.sendIndividualProto(myNumber, contentMessage, Date.now(), silent);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return Promise.resolve();
 | 
			
		||||
| 
						 | 
				
			
			@ -412,38 +420,44 @@ MessageSender.prototype = {
 | 
			
		|||
    syncVerification: function(destination, state, identityKey) {
 | 
			
		||||
        var myNumber = textsecure.storage.user.getNumber();
 | 
			
		||||
        var myDevice = textsecure.storage.user.getDeviceId();
 | 
			
		||||
        if (myDevice != 1) {
 | 
			
		||||
            // First send a null message to mask the sync message.
 | 
			
		||||
            var nullMessage = new textsecure.protobuf.NullMessage();
 | 
			
		||||
        var now = Date.now();
 | 
			
		||||
 | 
			
		||||
            // 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;
 | 
			
		||||
 | 
			
		||||
            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));
 | 
			
		||||
        if (myDevice === 1) {
 | 
			
		||||
            return Promise.resolve();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        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) {
 | 
			
		||||
| 
						 | 
				
			
			@ -455,14 +469,17 @@ MessageSender.prototype = {
 | 
			
		|||
        }
 | 
			
		||||
 | 
			
		||||
        return new Promise(function(resolve, reject) {
 | 
			
		||||
            this.sendMessageProto(timestamp, numbers, proto, function(res) {
 | 
			
		||||
            var silent = true;
 | 
			
		||||
            var callback = function(res) {
 | 
			
		||||
                res.dataMessage = proto.toArrayBuffer();
 | 
			
		||||
                if (res.errors.length > 0) {
 | 
			
		||||
                    reject(res);
 | 
			
		||||
                } else {
 | 
			
		||||
                    resolve(res);
 | 
			
		||||
                }
 | 
			
		||||
            }.bind(this));
 | 
			
		||||
            }.bind(this);
 | 
			
		||||
 | 
			
		||||
            this.sendMessageProto(timestamp, numbers, proto, callback, silent);
 | 
			
		||||
        }.bind(this));
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,5 +1,6 @@
 | 
			
		|||
mocha.setup("bdd");
 | 
			
		||||
window.assert = chai.assert;
 | 
			
		||||
window.PROTO_ROOT = '../../protos';
 | 
			
		||||
 | 
			
		||||
(function() {
 | 
			
		||||
  var OriginalReporter = mocha._reporter;
 | 
			
		||||
| 
						 | 
				
			
			@ -52,3 +53,5 @@ function hexToArrayBuffer(str) {
 | 
			
		|||
    array[i] = parseInt(str.substr(i*2, 2), 16);
 | 
			
		||||
  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>
 | 
			
		||||
 | 
			
		||||
<head>
 | 
			
		||||
  <meta charset='utf-8'>
 | 
			
		||||
  <title>libTextSecure test runner</title>
 | 
			
		||||
  <link rel="stylesheet" href="../../components/mocha/mocha.css" />
 | 
			
		||||
</head>
 | 
			
		||||
| 
						 | 
				
			
			@ -12,7 +13,6 @@
 | 
			
		|||
  </div>
 | 
			
		||||
 | 
			
		||||
  <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="../components.js"></script>
 | 
			
		||||
| 
						 | 
				
			
			@ -23,12 +23,12 @@
 | 
			
		|||
  <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="../event_target.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="../stringview.js" data-cover></script>
 | 
			
		||||
  <script type="text/javascript" src="../api.js"></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="../contacts_parser.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="websocket-resources_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>
 | 
			
		||||
</html>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1 +0,0 @@
 | 
			
		|||
../../protos/
 | 
			
		||||
| 
						 | 
				
			
			@ -22054,6 +22054,7 @@ Library.prototype.test = function(obj, type) {
 | 
			
		|||
});
 | 
			
		||||
mocha.setup("bdd");
 | 
			
		||||
window.assert = chai.assert;
 | 
			
		||||
window.PROTO_ROOT = '../../protos';
 | 
			
		||||
 | 
			
		||||
(function() {
 | 
			
		||||
  var OriginalReporter = mocha._reporter;
 | 
			
		||||
| 
						 | 
				
			
			@ -22106,3 +22107,5 @@ function hexToArrayBuffer(str) {
 | 
			
		|||
    array[i] = parseInt(str.substr(i*2, 2), 16);
 | 
			
		||||
  return ret;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
window.MockSocket.prototype.addEventListener = function() {};
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -18,7 +18,8 @@
 | 
			
		|||
                        assert.strictEqual(message.response.status, 200);
 | 
			
		||||
                        assert.strictEqual(message.response.id.toString(), request_id);
 | 
			
		||||
                        done();
 | 
			
		||||
                    }
 | 
			
		||||
                    },
 | 
			
		||||
                    addEventListener: function() {},
 | 
			
		||||
                };
 | 
			
		||||
 | 
			
		||||
                // actual test
 | 
			
		||||
| 
						 | 
				
			
			@ -58,7 +59,8 @@
 | 
			
		|||
                        assert.strictEqual(message.request.path, '/some/path');
 | 
			
		||||
                        assertEqualArrayBuffers(message.request.body.toArrayBuffer(), new Uint8Array([1,2,3]).buffer);
 | 
			
		||||
                        request_id = message.request.id;
 | 
			
		||||
                    }
 | 
			
		||||
                    },
 | 
			
		||||
                    addEventListener: function() {},
 | 
			
		||||
                };
 | 
			
		||||
 | 
			
		||||
                // actual test
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										80
									
								
								main.js
									
										
									
									
									
								
							
							
						
						
									
										80
									
								
								main.js
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -110,13 +110,41 @@ function captureClicks(window) {
 | 
			
		|||
  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 () {
 | 
			
		||||
  const screen = electron.screen;
 | 
			
		||||
  const windowOptions = Object.assign({
 | 
			
		||||
    show: !startInTray, // allow to start minimised in tray
 | 
			
		||||
    width: 800,
 | 
			
		||||
    height: 610,
 | 
			
		||||
    minWidth: 700,
 | 
			
		||||
    minHeight: 360,
 | 
			
		||||
    width: DEFAULT_WIDTH,
 | 
			
		||||
    height: DEFAULT_HEIGHT,
 | 
			
		||||
    minWidth: MIN_WIDTH,
 | 
			
		||||
    minHeight: MIN_HEIGHT,
 | 
			
		||||
    autoHideMenuBar: false,
 | 
			
		||||
    webPreferences: {
 | 
			
		||||
      nodeIntegration: false,
 | 
			
		||||
| 
						 | 
				
			
			@ -124,7 +152,33 @@ function createWindow () {
 | 
			
		|||
      preload: path.join(__dirname, 'preload.js')
 | 
			
		||||
    },
 | 
			
		||||
    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) {
 | 
			
		||||
    delete windowOptions.fullscreen;
 | 
			
		||||
| 
						 | 
				
			
			@ -175,6 +229,8 @@ 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 {
 | 
			
		||||
    mainWindow.loadURL(prepareURL([__dirname, 'background.html']));
 | 
			
		||||
  }
 | 
			
		||||
| 
						 | 
				
			
			@ -195,7 +251,9 @@ function createWindow () {
 | 
			
		|||
  mainWindow.on('close', function (e) {
 | 
			
		||||
 | 
			
		||||
    // 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;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -349,7 +407,7 @@ app.on('before-quit', function() {
 | 
			
		|||
app.on('window-all-closed', function () {
 | 
			
		||||
  // On OS X it is common for applications and their menu bar
 | 
			
		||||
  // to stay active until the user quits explicitly with Cmd + Q
 | 
			
		||||
  if (process.platform !== 'darwin' || config.environment === 'test') {
 | 
			
		||||
  if (process.platform !== 'darwin' || config.environment === 'test' || config.environment === 'test-lib') {
 | 
			
		||||
    app.quit()
 | 
			
		||||
  }
 | 
			
		||||
})
 | 
			
		||||
| 
						 | 
				
			
			@ -394,11 +452,15 @@ ipc.on('restart', function(event) {
 | 
			
		|||
});
 | 
			
		||||
 | 
			
		||||
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) {
 | 
			
		||||
  mainWindow.setMenuBarVisibility(visibility);
 | 
			
		||||
  if (mainWindow) {
 | 
			
		||||
    mainWindow.setMenuBarVisibility(visibility);
 | 
			
		||||
  }
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
ipc.on("close-about", function() {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue