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.
		
		
		
		
		
			
		
			
				
	
	
		
			390 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			JavaScript
		
	
			
		
		
	
	
			390 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			JavaScript
		
	
| const path = require('path');
 | |
| const packageJson = require('./package.json');
 | |
| const importOnce = require('node-sass-import-once');
 | |
| const rimraf = require('rimraf');
 | |
| const mkdirp = require('mkdirp');
 | |
| const spectron = require('spectron');
 | |
| const asar = require('asar');
 | |
| const fs = require('fs');
 | |
| const assert = require('assert');
 | |
| const sass = require('node-sass');
 | |
| 
 | |
| /* eslint-disable more/no-then, no-console  */
 | |
| 
 | |
| module.exports = grunt => {
 | |
|   const bower = grunt.file.readJSON('bower.json');
 | |
|   const components = [];
 | |
|   // eslint-disable-next-line guard-for-in, no-restricted-syntax
 | |
|   for (const i in bower.concat.app) {
 | |
|     components.push(bower.concat.app[i]);
 | |
|   }
 | |
| 
 | |
|   const libtextsecurecomponents = [];
 | |
|   // eslint-disable-next-line guard-for-in, no-restricted-syntax
 | |
|   for (const i in bower.concat.libtextsecure) {
 | |
|     libtextsecurecomponents.push(bower.concat.libtextsecure[i]);
 | |
|   }
 | |
| 
 | |
|   const utilWorkerComponents = [
 | |
|     'node_modules/bytebuffer/dist/bytebuffer.js',
 | |
|     'js/curve/curve25519_compiled.js',
 | |
|     'js/curve/curve25519_wrapper.js',
 | |
|     'node_modules/libsodium/dist/modules/libsodium.js',
 | |
|     'node_modules/libsodium-wrappers/dist/modules/libsodium-wrappers.js',
 | |
| 
 | |
|     'libtextsecure/libsignal-protocol.js',
 | |
|     'js/util_worker_tasks.js',
 | |
|   ];
 | |
| 
 | |
|   grunt.loadNpmTasks('grunt-sass');
 | |
| 
 | |
|   grunt.initConfig({
 | |
|     pkg: grunt.file.readJSON('package.json'),
 | |
|     concat: {
 | |
|       components: {
 | |
|         src: components,
 | |
|         dest: 'js/components.js',
 | |
|       },
 | |
|       util_worker: {
 | |
|         src: utilWorkerComponents,
 | |
|         dest: 'js/util_worker.js',
 | |
|       },
 | |
|       libtextsecurecomponents: {
 | |
|         src: libtextsecurecomponents,
 | |
|         dest: 'libtextsecure/components.js',
 | |
|       },
 | |
|       test: {
 | |
|         src: ['node_modules/mocha/mocha.js', 'node_modules/chai/chai.js', 'test/_test.js'],
 | |
|         dest: 'test/test.js',
 | |
|       },
 | |
|       libtextsecure: {
 | |
|         options: {
 | |
|           banner: ';(function() {\n',
 | |
|           footer: '})();\n',
 | |
|         },
 | |
|         src: [
 | |
|           'libtextsecure/errors.js',
 | |
|           'libtextsecure/libsignal-protocol.js',
 | |
|           'libtextsecure/crypto.js',
 | |
|           'libtextsecure/storage.js',
 | |
|           'libtextsecure/storage/user.js',
 | |
|           'libtextsecure/storage/groups.js',
 | |
|           'libtextsecure/protobufs.js',
 | |
|           'libtextsecure/helpers.js',
 | |
|           'libtextsecure/stringview.js',
 | |
|           'libtextsecure/event_target.js',
 | |
|           'libtextsecure/http-resources.js',
 | |
|           'libtextsecure/message_receiver.js',
 | |
|         ],
 | |
|         dest: 'js/libtextsecure.js',
 | |
|       },
 | |
|       libtextsecuretest: {
 | |
|         src: [
 | |
|           'node_modules/jquery/dist/jquery.js',
 | |
|           'node_modules/mocha/mocha.js',
 | |
|           'node_modules/chai/chai.js',
 | |
|           'libtextsecure/test/_test.js',
 | |
|         ],
 | |
|         dest: 'libtextsecure/test/test.js',
 | |
|       },
 | |
|     },
 | |
|     sass: {
 | |
|       options: {
 | |
|         implementation: sass,
 | |
|         sourceMap: true,
 | |
|         importer: importOnce,
 | |
|       },
 | |
|       dev: {
 | |
|         files: {
 | |
|           'stylesheets/manifest.css': 'stylesheets/manifest.scss',
 | |
|         },
 | |
|       },
 | |
|     },
 | |
|     watch: {
 | |
|       libtextsecure: {
 | |
|         files: ['./libtextsecure/*.js', './libtextsecure/storage/*.js'],
 | |
|         tasks: ['concat:libtextsecure'],
 | |
|       },
 | |
|       utilworker: {
 | |
|         files: utilWorkerComponents,
 | |
|         tasks: ['concat:util_worker'],
 | |
|       },
 | |
|       protobuf: {
 | |
|         files: ['./protos/SignalService.proto'],
 | |
|         tasks: ['exec:build-protobuf'],
 | |
|       },
 | |
|       sass: {
 | |
|         files: ['./stylesheets/*.scss'],
 | |
|         tasks: ['sass'],
 | |
|       },
 | |
|       transpile: {
 | |
|         files: ['./ts/**/*.ts', './ts/**/*.tsx', './ts/**/**/*.tsx', './test/ts/**.ts'],
 | |
|         tasks: ['exec:transpile'],
 | |
|       },
 | |
|     },
 | |
|     exec: {
 | |
|       transpile: {
 | |
|         cmd: 'yarn transpile',
 | |
|       },
 | |
|       'build-protobuf': {
 | |
|         cmd: 'yarn build-protobuf',
 | |
|       },
 | |
|     },
 | |
|     '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(key => {
 | |
|     if (/^grunt(?!(-cli)?$)/.test(key)) {
 | |
|       // ignore grunt and grunt-cli
 | |
|       grunt.loadNpmTasks(key);
 | |
|     }
 | |
|   });
 | |
| 
 | |
|   function updateLocalConfig(update) {
 | |
|     const environment = process.env.SIGNAL_ENV || 'development';
 | |
|     const configPath = `config/local-${environment}.json`;
 | |
|     let localConfig;
 | |
|     try {
 | |
|       localConfig = grunt.file.readJSON(configPath);
 | |
|     } catch (e) {
 | |
|       //
 | |
|     }
 | |
|     localConfig = {
 | |
|       ...localConfig,
 | |
|       ...update,
 | |
|     };
 | |
|     grunt.file.write(configPath, `${JSON.stringify(localConfig)}\n`);
 | |
|   }
 | |
| 
 | |
|   grunt.registerTask('getExpireTime', () => {
 | |
|     grunt.task.requires('gitinfo');
 | |
|     const gitinfo = grunt.config.get('gitinfo');
 | |
|     const committed = gitinfo.local.branch.current.lastCommitTime;
 | |
|     const time = Date.parse(committed) + 1000 * 60 * 60 * 24 * 90;
 | |
|     updateLocalConfig({ buildExpiration: time });
 | |
|   });
 | |
| 
 | |
|   grunt.registerTask('getCommitHash', () => {
 | |
|     grunt.task.requires('gitinfo');
 | |
|     const gitinfo = grunt.config.get('gitinfo');
 | |
|     const hash = gitinfo.local.branch.current.SHA;
 | |
|     updateLocalConfig({ commitHash: hash });
 | |
|   });
 | |
| 
 | |
|   grunt.registerTask('clean-release', () => {
 | |
|     rimraf.sync('release');
 | |
|     mkdirp.sync('release');
 | |
|   });
 | |
| 
 | |
|   function runTests(environment, cb) {
 | |
|     let failure;
 | |
|     const { Application } = spectron;
 | |
|     const electronBinary = process.platform === 'win32' ? 'electron.cmd' : 'electron';
 | |
|     const app = new Application({
 | |
|       path: path.join(__dirname, 'node_modules', '.bin', electronBinary),
 | |
|       args: [path.join(__dirname, 'main.js')],
 | |
|       env: {
 | |
|         NODE_ENV: environment,
 | |
|       },
 | |
|       requireName: 'unused',
 | |
|       chromeDriverArgs: [
 | |
|         `remote-debugging-port=${Math.floor(Math.random() * (9999 - 9000) + 9000)}`,
 | |
|       ],
 | |
|     });
 | |
| 
 | |
|     function getMochaResults() {
 | |
|       // eslint-disable-next-line no-undef
 | |
|       return window.mochaResults;
 | |
|     }
 | |
| 
 | |
|     app
 | |
|       .start()
 | |
|       .then(() =>
 | |
|         app.client.waitUntil(
 | |
|           () => app.client.execute(getMochaResults).then(data => Boolean(data.value)),
 | |
|           25000,
 | |
|           'Expected to find window.mochaResults set!'
 | |
|         )
 | |
|       )
 | |
|       .then(() => app.client.execute(getMochaResults))
 | |
|       .then(data => {
 | |
|         const results = data.value;
 | |
|         if (results.failures > 0) {
 | |
|           console.error(results.reports);
 | |
|           failure = () => grunt.fail.fatal(`Found ${results.failures} failing unit tests.`);
 | |
|           return app.client.log('browser');
 | |
|         }
 | |
|         grunt.log.ok(`${results.passes} tests passed.`);
 | |
|         return null;
 | |
|       })
 | |
|       .then(logs => {
 | |
|         if (logs) {
 | |
|           console.error();
 | |
|           console.error('Because tests failed, printing browser logs:');
 | |
|           console.error(logs);
 | |
|         }
 | |
|       })
 | |
|       .catch(error => {
 | |
|         failure = () => grunt.fail.fatal(`Something went wrong: ${error.message} ${error.stack}`);
 | |
|       })
 | |
|       .then(() => {
 | |
|         // We need to use the failure variable and this early stop to clean up before
 | |
|         // shutting down. Grunt's fail methods are the only way to set the return value,
 | |
|         // but they shut the process down immediately!
 | |
|         if (failure) {
 | |
|           console.log();
 | |
|           console.log('Main process logs:');
 | |
|           return app.client.getMainProcessLogs().then(logs => {
 | |
|             logs.forEach(log => {
 | |
|               console.log(log);
 | |
|             });
 | |
|             try {
 | |
|               return app.stop();
 | |
|             } catch (err) {
 | |
|               return Promise.resolve();
 | |
|             }
 | |
|           });
 | |
|         }
 | |
|         try {
 | |
|           return app.stop();
 | |
|         } catch (err) {
 | |
|           return Promise.resolve();
 | |
|         }
 | |
|       })
 | |
|       .then(() => {
 | |
|         if (failure) {
 | |
|           failure();
 | |
|         }
 | |
|         cb();
 | |
|       })
 | |
|       .catch(error => {
 | |
|         console.error('Second-level error:', error.message, error.stack);
 | |
|         if (failure) {
 | |
|           failure();
 | |
|         }
 | |
|         cb();
 | |
|       });
 | |
|   }
 | |
| 
 | |
|   grunt.registerTask('unit-tests', 'Run unit tests w/Electron', function thisNeeded() {
 | |
|     const environment = grunt.option('env') || 'test';
 | |
|     const done = this.async();
 | |
| 
 | |
|     runTests(environment, done);
 | |
|   });
 | |
| 
 | |
|   grunt.registerMultiTask('test-release', 'Test packaged releases', function thisNeeded() {
 | |
|     const dir = grunt.option('dir') || 'release';
 | |
|     const environment = grunt.option('env') || 'production';
 | |
|     const config = this.data;
 | |
|     const archive = [dir, config.archive].join('/');
 | |
|     const files = [
 | |
|       'config/default.json',
 | |
|       `config/${environment}.json`,
 | |
|       `config/local-${environment}.json`,
 | |
|     ];
 | |
| 
 | |
|     console.log(this.target, archive);
 | |
|     const releaseFiles = files.concat(config.files || []);
 | |
|     releaseFiles.forEach(fileName => {
 | |
|       console.log(fileName);
 | |
|       try {
 | |
|         asar.statFile(archive, fileName);
 | |
|         return true;
 | |
|       } catch (e) {
 | |
|         console.log(e);
 | |
|         throw new Error(`Missing file ${fileName}`);
 | |
|       }
 | |
|     });
 | |
| 
 | |
|     if (config.appUpdateYML) {
 | |
|       const appUpdateYML = [dir, config.appUpdateYML].join('/');
 | |
|       if (fs.existsSync(appUpdateYML)) {
 | |
|         console.log('auto update ok');
 | |
|       } else {
 | |
|         throw new Error(`Missing auto update config ${appUpdateYML}`);
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     const done = this.async();
 | |
|     // A simple test to verify a visible window is opened with a title
 | |
|     const { Application } = spectron;
 | |
| 
 | |
|     const app = new Application({
 | |
|       path: [dir, config.exe].join('/'),
 | |
|       requireName: 'unused',
 | |
|       chromeDriverArgs: [
 | |
|         `remote-debugging-port=${Math.floor(Math.random() * (9999 - 9000) + 9000)}`,
 | |
|       ],
 | |
|     });
 | |
| 
 | |
|     app
 | |
|       .start()
 | |
|       .then(() => app.client.getWindowCount())
 | |
|       .then(count => {
 | |
|         assert.equal(count, 1);
 | |
|         console.log('window opened');
 | |
|       })
 | |
|       .then(() =>
 | |
|         // Get the window's title
 | |
|         app.client.getTitle()
 | |
|       )
 | |
|       .then(title => {
 | |
|         // TODO: restore once fixed on win
 | |
|         if (this.target !== 'win') {
 | |
|           // Verify the window's title
 | |
|           assert.equal(title, packageJson.productName);
 | |
|           console.log('title ok');
 | |
|         }
 | |
|       })
 | |
|       .then(() => {
 | |
|         assert(app.chromeDriver.logLines.indexOf(`NODE_ENV ${environment}`) > -1);
 | |
|         console.log('environment ok');
 | |
|       })
 | |
|       .then(
 | |
|         () =>
 | |
|           // Successfully completed test
 | |
|           app.stop(),
 | |
|         error =>
 | |
|           // Test failed!
 | |
|           app.stop().then(() => {
 | |
|             grunt.fail.fatal(`Test failed: ${error.message} ${error.stack}`);
 | |
|           })
 | |
|       )
 | |
|       .then(done);
 | |
|   });
 | |
| 
 | |
|   grunt.registerTask('dev', ['default', 'watch']);
 | |
|   grunt.registerTask('test', ['unit-tests']);
 | |
|   grunt.registerTask('date', ['gitinfo', 'getExpireTime']);
 | |
|   grunt.registerTask('default', [
 | |
|     'exec:build-protobuf',
 | |
|     'exec:transpile',
 | |
|     'concat',
 | |
|     'sass',
 | |
|     'date',
 | |
|     'getCommitHash',
 | |
|   ]);
 | |
| };
 |