You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			506 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			JavaScript
		
	
			
		
		
	
	
			506 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			JavaScript
		
	
| var path = require('path');
 | |
| var packageJson = require('./package.json');
 | |
| 
 | |
| module.exports = function(grunt) {
 | |
|   'use strict';
 | |
| 
 | |
|   var bower = grunt.file.readJSON('bower.json');
 | |
|   var components = [];
 | |
|   for (var i in bower.concat.app) {
 | |
|     components.push('components/' + bower.concat.app[i] + '/**/*.js');
 | |
|   }
 | |
|   components.push('components/' + 'webaudiorecorder/lib/WebAudioRecorder.js');
 | |
| 
 | |
|   var libtextsecurecomponents = [];
 | |
|   for (i in bower.concat.libtextsecure) {
 | |
|     libtextsecurecomponents.push('components/' + bower.concat.libtextsecure[i] + '/**/*.js');
 | |
|   }
 | |
| 
 | |
|   var importOnce = require("node-sass-import-once");
 | |
|   grunt.loadNpmTasks("grunt-sass");
 | |
| 
 | |
|   grunt.initConfig({
 | |
|     pkg: grunt.file.readJSON('package.json'),
 | |
|     concat: {
 | |
|       components: {
 | |
|         src: components,
 | |
|         dest: 'js/components.js',
 | |
|       },
 | |
|       libtextsecurecomponents: {
 | |
|         src: libtextsecurecomponents,
 | |
|         dest: 'libtextsecure/components.js',
 | |
|       },
 | |
|       test: {
 | |
|         src: [
 | |
|           'components/mocha/mocha.js',
 | |
|           'components/chai/chai.js',
 | |
|           'test/_test.js'
 | |
|         ],
 | |
|         dest: 'test/test.js',
 | |
|       },
 | |
|       //TODO: Move errors back down?
 | |
|       libtextsecure: {
 | |
|         options: {
 | |
|           banner: ";(function() {\n",
 | |
|           footer: "})();\n",
 | |
|         },
 | |
|         src: [
 | |
|           'libtextsecure/errors.js',
 | |
|           'libtextsecure/libsignal-protocol.js',
 | |
|           'libtextsecure/protocol_wrapper.js',
 | |
| 
 | |
|           'libtextsecure/crypto.js',
 | |
|           'libtextsecure/storage.js',
 | |
|           'libtextsecure/storage/user.js',
 | |
|           'libtextsecure/storage/groups.js',
 | |
|           'libtextsecure/storage/unprocessed.js',
 | |
|           'libtextsecure/protobufs.js',
 | |
|           'libtextsecure/helpers.js',
 | |
|           'libtextsecure/stringview.js',
 | |
|           'libtextsecure/event_target.js',
 | |
|           'libtextsecure/api.js',
 | |
|           'libtextsecure/account_manager.js',
 | |
|           'libtextsecure/websocket-resources.js',
 | |
|           'libtextsecure/message_receiver.js',
 | |
|           'libtextsecure/outgoing_message.js',
 | |
|           'libtextsecure/sendmessage.js',
 | |
|           'libtextsecure/sync_request.js',
 | |
|           'libtextsecure/contacts_parser.js',
 | |
|           'libtextsecure/ProvisioningCipher.js',
 | |
|           'libtextsecure/task_with_timeout.js',
 | |
|         ],
 | |
|         dest: 'js/libtextsecure.js',
 | |
|       },
 | |
|       libtextsecuretest: {
 | |
|         src: [
 | |
|           'components/jquery/dist/jquery.js',
 | |
|           'components/mock-socket/dist/mock-socket.js',
 | |
|           'components/mocha/mocha.js',
 | |
|           'components/chai/chai.js',
 | |
|           'libtextsecure/test/_test.js'
 | |
|         ],
 | |
|         dest: 'libtextsecure/test/test.js',
 | |
|       }
 | |
|     },
 | |
|     sass: {
 | |
|       options: {
 | |
|         sourceMap: true,
 | |
|         importer: importOnce
 | |
|       },
 | |
|       dev: {
 | |
|         files: {
 | |
|           "stylesheets/manifest.css": "stylesheets/manifest.scss"
 | |
|         }
 | |
|       }
 | |
|     },
 | |
|     jshint: {
 | |
|       files: [
 | |
|         'Gruntfile.js',
 | |
|         'js/**/*.js',
 | |
|         '!js/background.js',
 | |
|         '!js/backup.js',
 | |
|         '!js/components.js',
 | |
|         '!js/database.js',
 | |
|         '!js/jquery.js',
 | |
|         '!js/libsignal-protocol-worker.js',
 | |
|         '!js/libtextsecure.js',
 | |
|         '!js/logging.js',
 | |
|         '!js/modules/**/*.js',
 | |
|         '!js/Mp3LameEncoder.min.js',
 | |
|         '!js/signal_protocol_store.js',
 | |
|         '!js/views/conversation_search_view.js',
 | |
|         '!js/views/conversation_view.js',
 | |
|         '!js/views/debug_log_view.js',
 | |
|         '!js/views/file_input_view.js',
 | |
|         '!js/views/message_view.js',
 | |
|         '!js/models/conversations.js',
 | |
|         '!js/models/messages.js',
 | |
|         '!js/WebAudioRecorderMp3.js',
 | |
|         '!libtextsecure/message_receiver.js',
 | |
|         '_locales/**/*'
 | |
|       ],
 | |
|       options: { jshintrc: '.jshintrc' },
 | |
|     },
 | |
|     dist: {
 | |
|       src: [
 | |
|         'background.html',
 | |
|         'index.html',
 | |
|         'options.html',
 | |
|         '_locales/**',
 | |
|         'protos/*',
 | |
|         'js/**',
 | |
|         'stylesheets/*.css',
 | |
|         '!js/register.js'
 | |
|       ],
 | |
|       res: [
 | |
|         'images/**/*',
 | |
|         'fonts/*',
 | |
|       ]
 | |
|     },
 | |
|     copy: {
 | |
|       deps: {
 | |
|         files: [{
 | |
|           src: 'components/mp3lameencoder/lib/Mp3LameEncoder.js',
 | |
|           dest: 'js/Mp3LameEncoder.min.js'
 | |
|         }, {
 | |
|           src: 'components/webaudiorecorder/lib/WebAudioRecorderMp3.js',
 | |
|           dest: 'js/WebAudioRecorderMp3.js'
 | |
|         }, {
 | |
|           src: 'components/jquery/dist/jquery.js',
 | |
|           dest: 'js/jquery.js'
 | |
|         }],
 | |
|       },
 | |
|       res: {
 | |
|         files: [{ expand: true, dest: 'dist/', src: ['<%= dist.res %>'] }],
 | |
|       },
 | |
|       src: {
 | |
|         files: [{ expand: true, dest: 'dist/', src: ['<%= dist.src %>'] }],
 | |
|       }
 | |
|     },
 | |
|     jscs: {
 | |
|       all: {
 | |
|         src: [
 | |
|         'Gruntfile',
 | |
|         'js/**/*.js',
 | |
|         '!js/components.js',
 | |
|         '!js/libsignal-protocol-worker.js',
 | |
|         '!js/libtextsecure.js',
 | |
|         '!js/modules/**/*.js',
 | |
|         '!js/models/conversations.js',
 | |
|         '!js/models/messages.js',
 | |
|         '!js/views/conversation_search_view.js',
 | |
|         '!js/views/conversation_view.js',
 | |
|         '!js/views/debug_log_view.js',
 | |
|         '!js/views/file_input_view.js',
 | |
|         '!js/views/message_view.js',
 | |
|         '!js/Mp3LameEncoder.min.js',
 | |
|         '!js/WebAudioRecorderMp3.js',
 | |
|         'test/**/*.js',
 | |
|         '!test/blanket_mocha.js',
 | |
|         '!test/modules/**/*.js',
 | |
|         '!test/test.js',
 | |
|         ]
 | |
|       }
 | |
|     },
 | |
|     watch: {
 | |
|       sass: {
 | |
|         files: ['./stylesheets/*.scss'],
 | |
|         tasks: ['sass']
 | |
|       },
 | |
|       libtextsecure: {
 | |
|         files: ['./libtextsecure/*.js', './libtextsecure/storage/*.js'],
 | |
|         tasks: ['concat:libtextsecure']
 | |
|       },
 | |
|       dist: {
 | |
|         files: ['<%= dist.src %>', '<%= dist.res %>'],
 | |
|         tasks: ['copy_dist']
 | |
|       },
 | |
|       scripts: {
 | |
|         files: ['<%= jshint.files %>'],
 | |
|         tasks: ['jshint']
 | |
|       },
 | |
|       style: {
 | |
|         files: ['<%= jscs.all.src %>'],
 | |
|         tasks: ['jscs']
 | |
|       },
 | |
|       transpile: {
 | |
|         files: ['./ts/**/*.ts'],
 | |
|         tasks: ['exec:transpile']
 | |
|       }
 | |
|     },
 | |
|     exec: {
 | |
|       'tx-pull': {
 | |
|         cmd: 'tx pull'
 | |
|       },
 | |
|       'transpile': {
 | |
|         cmd: 'npm run transpile',
 | |
|       }
 | |
|     },
 | |
|     'test-release': {
 | |
|       osx: {
 | |
|         archive: 'mac/' + packageJson.productName + '.app/Contents/Resources/app.asar',
 | |
|         appUpdateYML: 'mac/' + packageJson.productName + '.app/Contents/Resources/app-update.yml',
 | |
|         exe: 'mac/' + packageJson.productName + '.app/Contents/MacOS/' + packageJson.productName
 | |
|       },
 | |
|       mas: {
 | |
|         archive: 'mas/Signal.app/Contents/Resources/app.asar',
 | |
|         appUpdateYML: 'mac/Signal.app/Contents/Resources/app-update.yml',
 | |
|         exe: 'mas/' + packageJson.productName + '.app/Contents/MacOS/' + packageJson.productName
 | |
|       },
 | |
|       linux: {
 | |
|         archive: 'linux-unpacked/resources/app.asar',
 | |
|         exe: 'linux-unpacked/' + packageJson.name
 | |
|       },
 | |
|       win: {
 | |
|         archive: 'win-unpacked/resources/app.asar',
 | |
|         appUpdateYML: 'win-unpacked/resources/app-update.yml',
 | |
|         exe: 'win-unpacked/' + packageJson.productName + '.exe'
 | |
|       }
 | |
|     },
 | |
|     gitinfo: {} // to be populated by grunt gitinfo
 | |
|   });
 | |
| 
 | |
|   Object.keys(grunt.config.get('pkg').devDependencies).forEach(function(key) {
 | |
|     if (/^grunt(?!(-cli)?$)/.test(key)) {  // ignore grunt and grunt-cli
 | |
|       grunt.loadNpmTasks(key);
 | |
|     }
 | |
|   });
 | |
| 
 | |
|   // Transifex does not understand placeholders, so this task patches all non-en
 | |
|   // locales with missing placeholders
 | |
|   grunt.registerTask('locale-patch', function(){
 | |
|     var en = grunt.file.readJSON('_locales/en/messages.json');
 | |
|     grunt.file.recurse('_locales', function(abspath, rootdir, subdir, filename){
 | |
|       if (subdir === 'en' || filename !== 'messages.json'){
 | |
|         return;
 | |
|       }
 | |
|       var messages = grunt.file.readJSON(abspath);
 | |
| 
 | |
|       for (var key in messages){
 | |
|         if (en[key] !== undefined && messages[key] !== undefined){
 | |
|           if (en[key].placeholders !== undefined && messages[key].placeholders === undefined){
 | |
|             messages[key].placeholders = en[key].placeholders;
 | |
|           }
 | |
|         }
 | |
|       }
 | |
| 
 | |
|       grunt.file.write(abspath, JSON.stringify(messages, null, 4) + '\n');
 | |
|     });
 | |
|   });
 | |
| 
 | |
|   grunt.registerTask('getExpireTime', function() {
 | |
|       grunt.task.requires('gitinfo');
 | |
|       var gitinfo = grunt.config.get('gitinfo');
 | |
|       var commited = gitinfo.local.branch.current.lastCommitTime;
 | |
|       var time = Date.parse(commited) + 1000 * 60 * 60 * 24 * 90;
 | |
|       grunt.file.write('config/local-production.json',
 | |
|         JSON.stringify({ buildExpiration: time }) + '\n');
 | |
|   });
 | |
| 
 | |
|   grunt.registerTask('clean-release', function() {
 | |
|     require('rimraf').sync('release');
 | |
|     require('mkdirp').sync('release');
 | |
|   });
 | |
| 
 | |
|   grunt.registerTask('fetch-release', function() {
 | |
|     grunt.task.requires('gitinfo');
 | |
|     require('mkdirp').sync('release');
 | |
|     var fs = require('fs');
 | |
|     var done = this.async();
 | |
|     var gitinfo = grunt.config.get('gitinfo');
 | |
|     var https = require('https');
 | |
| 
 | |
|     var urlBase = "https://s3-us-west-1.amazonaws.com/signal-desktop-builds";
 | |
|     var keyBase = 'signalapp/Signal-Desktop';
 | |
|     var sha = gitinfo.local.branch.current.SHA;
 | |
|     var files = [{
 | |
|       zip: packageJson.name + '-' + packageJson.version + '.zip',
 | |
|       extractedTo: 'linux'
 | |
|     }];
 | |
| 
 | |
|     var extract = require('extract-zip');
 | |
|     var download = function(url, dest, extractedTo, cb) {
 | |
|         var file = fs.createWriteStream(dest);
 | |
|         var request = https.get(url, function(response) {
 | |
|           if (response.statusCode !== 200) {
 | |
|             cb(response.statusCode);
 | |
|           } else {
 | |
|             response.pipe(file);
 | |
|             file.on('finish', function() {
 | |
|               file.close(function() {
 | |
|                 extract(dest, {dir: path.join(__dirname, 'release', extractedTo)}, cb);
 | |
|               });
 | |
|             });
 | |
|           }
 | |
|         }).on('error', function(err) { // Handle errors
 | |
|           fs.unlink(dest); // Delete the file async. (But we don't check the result)
 | |
|           if (cb) cb(err.message);
 | |
|         });
 | |
|     };
 | |
| 
 | |
|     Promise.all(files.map(function(item) {
 | |
|       var key = [ keyBase, sha, 'dist', item.zip].join('/');
 | |
|       var url = [urlBase, key].join('/');
 | |
|       var dest = 'release/' + item.zip;
 | |
|       return new Promise(function(resolve) {
 | |
|         console.log(url);
 | |
|         download(url, dest, item.extractedTo, function(err) {
 | |
|           if (err) {
 | |
|             console.log('failed', dest, err);
 | |
|             resolve(err);
 | |
|           } else {
 | |
|             console.log('done', dest);
 | |
|             resolve();
 | |
|           }
 | |
|         });
 | |
|       });
 | |
|     })).then(function(results) {
 | |
|       results.forEach(function(error) {
 | |
|         if (error) {
 | |
|           grunt.fail.warn('Failed to fetch some release artifacts');
 | |
|         }
 | |
|       });
 | |
|       done();
 | |
|     });
 | |
|   });
 | |
| 
 | |
|   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
 | |
|       }
 | |
|     });
 | |
| 
 | |
|     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('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) {
 | |
|       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() {
 | |
|       var dir = grunt.option('dir') || 'dist';
 | |
|       var environment = grunt.option('env') || 'production';
 | |
|       var asar = require('asar');
 | |
|       var config = this.data;
 | |
|       var archive = [dir, config.archive].join('/');
 | |
|       var files = [
 | |
|         'config/default.json',
 | |
|         'config/' + environment + '.json',
 | |
|         'config/local-' + environment + '.json'
 | |
|       ];
 | |
| 
 | |
|       console.log(this.target, archive);
 | |
|       var releaseFiles = files.concat(config.files || []);
 | |
|       releaseFiles.forEach(function(fileName) {
 | |
|         console.log(fileName);
 | |
|         try {
 | |
|           asar.statFile(archive, fileName);
 | |
|           return true;
 | |
|         } catch (e) {
 | |
|           console.log(e);
 | |
|           throw new Error("Missing file " + fileName);
 | |
|         }
 | |
|       });
 | |
| 
 | |
|       if (config.appUpdateYML) {
 | |
|         var appUpdateYML = [dir, config.appUpdateYML].join('/');
 | |
|         if (require('fs').existsSync(appUpdateYML)) {
 | |
|           console.log("auto update ok");
 | |
|         } else {
 | |
|           throw new Error("Missing auto update config " + appUpdateYML);
 | |
|         }
 | |
|       }
 | |
| 
 | |
|       var done = this.async();
 | |
|       // A simple test to verify a visible window is opened with a title
 | |
|       var Application = require('spectron').Application;
 | |
|       var assert = require('assert');
 | |
| 
 | |
|       var app = new Application({
 | |
|         path: [dir, config.exe].join('/')
 | |
|       });
 | |
| 
 | |
|       app.start().then(function () {
 | |
|         return app.client.getWindowCount();
 | |
|       }).then(function (count) {
 | |
|         assert.equal(count, 1);
 | |
|         console.log('window opened');
 | |
|       }).then(function () {
 | |
|         // Get the window's title
 | |
|         return app.client.getTitle();
 | |
|       }).then(function (title) {
 | |
|         // Verify the window's title
 | |
|         assert.equal(title, packageJson.productName);
 | |
|         console.log('title ok');
 | |
|       }).then(function () {
 | |
|         assert(app.chromeDriver.logLines.indexOf('NODE_ENV ' + environment) > -1);
 | |
|         console.log('environment ok');
 | |
|       }).then(function () {
 | |
|         // Successfully completed test
 | |
|         return app.stop();
 | |
|       }, function (error) {
 | |
|         // Test failed!
 | |
|         return app.stop().then(function() {
 | |
|           grunt.fail.fatal('Test failed: ' + error.message + ' ' + error.stack);
 | |
|         });
 | |
|       }).then(done);
 | |
|   });
 | |
| 
 | |
|   grunt.registerTask('tx', ['exec:tx-pull', 'locale-patch']);
 | |
|   grunt.registerTask('dev', ['default', 'watch']);
 | |
|   grunt.registerTask('lint', ['jshint', 'jscs']);
 | |
|   grunt.registerTask('test', ['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']);
 | |
|   grunt.registerTask(
 | |
|     'default',
 | |
|     ['concat', 'copy:deps', 'sass', 'date', 'exec:transpile']
 | |
|   );
 | |
| };
 |