Clean logs on start - and eslint/mocha with code coverage (#1945)
* Clean logs on startup; install server-side testing/linting * Add eslint config, make all of app/ conform to its demands * Add Node.js testing and linting to CI * Lock project to Node.js 7.9.0, used by Electron 1.7.10 * New eslint error: trailing commas in function argumensts Node 7.9.0 doesn't like trailing commas, but Electron does * Move electron to devDependency, tell eslint it's built-in
This commit is contained in:
parent
6464d0a5fa
commit
64fe9dbfb2
21 changed files with 1782 additions and 316 deletions
17
.eslintignore
Normal file
17
.eslintignore
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
build/**
|
||||||
|
components/**
|
||||||
|
dist/**
|
||||||
|
libtextsecure/**
|
||||||
|
coverage/**
|
||||||
|
|
||||||
|
# these aren't ready yet, pulling files in one-by-one
|
||||||
|
js/**
|
||||||
|
test/**
|
||||||
|
/*.js
|
||||||
|
!main.js
|
||||||
|
!prepare_build.js
|
||||||
|
|
||||||
|
# all of these files will be new
|
||||||
|
!test/server/**/*.js
|
||||||
|
|
||||||
|
# all of app/ is included
|
41
.eslintrc.js
Normal file
41
.eslintrc.js
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
// For reference: https://github.com/airbnb/javascript
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
settings: {
|
||||||
|
'import/core-modules': [
|
||||||
|
'electron'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
extends: [
|
||||||
|
'airbnb-base',
|
||||||
|
],
|
||||||
|
|
||||||
|
rules: {
|
||||||
|
'comma-dangle': ['error', {
|
||||||
|
arrays: 'always-multiline',
|
||||||
|
objects: 'always-multiline',
|
||||||
|
imports: 'always-multiline',
|
||||||
|
exports: 'always-multiline',
|
||||||
|
functions: 'never',
|
||||||
|
}],
|
||||||
|
|
||||||
|
// putting params on their own line helps stay within line length limit
|
||||||
|
'function-paren-newline': ['error', 'consistent'],
|
||||||
|
|
||||||
|
// 90 characters allows three+ side-by-side screens on a standard-size monitor
|
||||||
|
'max-len': ['error', {
|
||||||
|
code: 90,
|
||||||
|
ignoreUrls: true,
|
||||||
|
}],
|
||||||
|
|
||||||
|
// it helps readability to put public API at top,
|
||||||
|
'no-use-before-define': 'off',
|
||||||
|
|
||||||
|
// useful for unused or internal fields
|
||||||
|
'no-underscore-dangle': 'off',
|
||||||
|
|
||||||
|
// though we have a logger, we still remap console to log to disk
|
||||||
|
'no-console': 'off',
|
||||||
|
}
|
||||||
|
};
|
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -1,5 +1,6 @@
|
||||||
node_modules
|
node_modules
|
||||||
.sass-cache
|
.sass-cache
|
||||||
|
coverage/*
|
||||||
build/curve25519_compiled.js
|
build/curve25519_compiled.js
|
||||||
build/icons/*
|
build/icons/*
|
||||||
stylesheets/*.css.map
|
stylesheets/*.css.map
|
||||||
|
@ -11,3 +12,5 @@ config/local-*.json
|
||||||
*.provisionprofile
|
*.provisionprofile
|
||||||
release/
|
release/
|
||||||
/dev-app-update.yml
|
/dev-app-update.yml
|
||||||
|
.nyc_output/
|
||||||
|
*.sublime*
|
||||||
|
|
1
.nvmrc
Normal file
1
.nvmrc
Normal file
|
@ -0,0 +1 @@
|
||||||
|
7.9.0
|
|
@ -1,6 +1,6 @@
|
||||||
language: node_js
|
language: node_js
|
||||||
node_js:
|
node_js:
|
||||||
- 'node'
|
- '7.9.0'
|
||||||
os:
|
os:
|
||||||
- linux
|
- linux
|
||||||
dist: trusty
|
dist: trusty
|
||||||
|
@ -9,6 +9,8 @@ install:
|
||||||
script:
|
script:
|
||||||
- yarn run generate
|
- yarn run generate
|
||||||
- yarn prepare-build
|
- yarn prepare-build
|
||||||
|
- yarn eslint
|
||||||
|
- yarn test-server
|
||||||
- ./node_modules/.bin/build --em.environment=$SIGNAL_ENV --config.mac.bundleVersion='$TRAVIS_BUILD_NUMBER' --publish=never
|
- ./node_modules/.bin/build --em.environment=$SIGNAL_ENV --config.mac.bundleVersion='$TRAVIS_BUILD_NUMBER' --publish=never
|
||||||
- ./travis.sh
|
- ./travis.sh
|
||||||
env:
|
env:
|
||||||
|
|
|
@ -39,3 +39,6 @@ Gruntfile.js
|
||||||
# misc
|
# misc
|
||||||
*.gz
|
*.gz
|
||||||
*.md
|
*.md
|
||||||
|
|
||||||
|
# asset directories
|
||||||
|
!nyc/node_modules/istanbul-reports/lib/html/assets
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
const autoUpdater = require('electron-updater').autoUpdater
|
const { autoUpdater } = require('electron-updater');
|
||||||
const { dialog } = require('electron');
|
const { dialog } = require('electron');
|
||||||
|
|
||||||
const config = require('./config');
|
const config = require('./config');
|
||||||
|
@ -18,7 +18,7 @@ function checkForUpdates() {
|
||||||
autoUpdater.checkForUpdates();
|
autoUpdater.checkForUpdates();
|
||||||
}
|
}
|
||||||
|
|
||||||
var showingDialog = false;
|
let showingDialog = false;
|
||||||
function showUpdateDialog(mainWindow, messages) {
|
function showUpdateDialog(mainWindow, messages) {
|
||||||
if (showingDialog) {
|
if (showingDialog) {
|
||||||
return;
|
return;
|
||||||
|
@ -29,21 +29,21 @@ function showUpdateDialog(mainWindow, messages) {
|
||||||
type: 'info',
|
type: 'info',
|
||||||
buttons: [
|
buttons: [
|
||||||
messages.autoUpdateRestartButtonLabel.message,
|
messages.autoUpdateRestartButtonLabel.message,
|
||||||
messages.autoUpdateLaterButtonLabel.message
|
messages.autoUpdateLaterButtonLabel.message,
|
||||||
],
|
],
|
||||||
title: messages.autoUpdateNewVersionTitle.message,
|
title: messages.autoUpdateNewVersionTitle.message,
|
||||||
message: messages.autoUpdateNewVersionMessage.message,
|
message: messages.autoUpdateNewVersionMessage.message,
|
||||||
detail: messages.autoUpdateNewVersionInstructions.message,
|
detail: messages.autoUpdateNewVersionInstructions.message,
|
||||||
defaultId: LATER_BUTTON,
|
defaultId: LATER_BUTTON,
|
||||||
cancelId: RESTART_BUTTON,
|
cancelId: RESTART_BUTTON,
|
||||||
}
|
};
|
||||||
|
|
||||||
dialog.showMessageBox(mainWindow, options, function(response) {
|
dialog.showMessageBox(mainWindow, options, (response) => {
|
||||||
if (response == RESTART_BUTTON) {
|
if (response === RESTART_BUTTON) {
|
||||||
// We delay these update calls because they don't seem to work in this
|
// We delay these update calls because they don't seem to work in this
|
||||||
// callback - but only if the message box has a parent window.
|
// callback - but only if the message box has a parent window.
|
||||||
// Fixes this bug: https://github.com/WhisperSystems/Signal-Desktop/issues/1864
|
// Fixes this bug: https://github.com/WhisperSystems/Signal-Desktop/issues/1864
|
||||||
setTimeout(function() {
|
setTimeout(() => {
|
||||||
windowState.markShouldQuit();
|
windowState.markShouldQuit();
|
||||||
autoUpdater.quitAndInstall();
|
autoUpdater.quitAndInstall();
|
||||||
}, 200);
|
}, 200);
|
||||||
|
@ -54,7 +54,7 @@ function showUpdateDialog(mainWindow, messages) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function onError(error) {
|
function onError(error) {
|
||||||
console.log("Got an error while updating: ", error.stack);
|
console.log('Got an error while updating: ', error.stack);
|
||||||
}
|
}
|
||||||
|
|
||||||
function initialize(getMainWindow, messages) {
|
function initialize(getMainWindow, messages) {
|
||||||
|
@ -66,7 +66,7 @@ function initialize(getMainWindow, messages) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
autoUpdater.addListener('update-downloaded', function() {
|
autoUpdater.addListener('update-downloaded', () => {
|
||||||
showUpdateDialog(getMainWindow(), messages);
|
showUpdateDialog(getMainWindow(), messages);
|
||||||
});
|
});
|
||||||
autoUpdater.addListener('error', onError);
|
autoUpdater.addListener('error', onError);
|
||||||
|
@ -77,5 +77,5 @@ function initialize(getMainWindow, messages) {
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
initialize
|
initialize,
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
|
||||||
|
const config = require('config');
|
||||||
|
|
||||||
const packageJson = require('../package.json');
|
const packageJson = require('../package.json');
|
||||||
|
|
||||||
|
|
||||||
const environment = packageJson.environment || process.env.NODE_ENV || 'development';
|
const environment = packageJson.environment || process.env.NODE_ENV || 'development';
|
||||||
|
config.environment = environment;
|
||||||
|
|
||||||
// Set environment vars to configure node-config before requiring it
|
// Set environment vars to configure node-config before requiring it
|
||||||
process.env.NODE_ENV = environment;
|
process.env.NODE_ENV = environment;
|
||||||
|
@ -19,8 +22,6 @@ if (environment === 'production') {
|
||||||
process.env.SUPPRESS_NO_CONFIG_WARNING = '';
|
process.env.SUPPRESS_NO_CONFIG_WARNING = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
const config = require('config');
|
|
||||||
config.environment = environment;
|
|
||||||
|
|
||||||
// Log resulting env vars in use by config
|
// Log resulting env vars in use by config
|
||||||
[
|
[
|
||||||
|
@ -30,9 +31,9 @@ config.environment = environment;
|
||||||
'ALLOW_CONFIG_MUTATIONS',
|
'ALLOW_CONFIG_MUTATIONS',
|
||||||
'HOSTNAME',
|
'HOSTNAME',
|
||||||
'NODE_APP_INSTANCE',
|
'NODE_APP_INSTANCE',
|
||||||
'SUPPRESS_NO_CONFIG_WARNING'
|
'SUPPRESS_NO_CONFIG_WARNING',
|
||||||
].forEach(function(s) {
|
].forEach((s) => {
|
||||||
console.log(s + ' ' + config.util.getEnv(s));
|
console.log(`${s} ${config.util.getEnv(s)}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
module.exports = config;
|
module.exports = config;
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const app = require('electron').app;
|
const { app } = require('electron');
|
||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
|
|
||||||
const logger = require('./logging').getLogger();
|
const logging = require('./logging');
|
||||||
|
|
||||||
function normalizeLocaleName(locale) {
|
function normalizeLocaleName(locale) {
|
||||||
if (/^en-/.test(locale)) {
|
if (/^en-/.test(locale)) {
|
||||||
|
@ -28,7 +28,8 @@ function getLocaleMessages(locale) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function load() {
|
function load() {
|
||||||
let english = getLocaleMessages('en');
|
const logger = logging.getLogger();
|
||||||
|
const english = getLocaleMessages('en');
|
||||||
let appLocale = app.getLocale();
|
let appLocale = app.getLocale();
|
||||||
|
|
||||||
if (process.env.NODE_ENV === 'test') {
|
if (process.env.NODE_ENV === 'test') {
|
||||||
|
@ -49,7 +50,7 @@ function load() {
|
||||||
// We start with english, then overwrite that with anything present in locale
|
// We start with english, then overwrite that with anything present in locale
|
||||||
messages = _.merge(english, messages);
|
messages = _.merge(english, messages);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error('Problem loading messages for locale ' + localeName + ' ' + e.stack);
|
logger.error(`Problem loading messages for locale ${localeName} ${e.stack}`);
|
||||||
logger.error('Falling back to en locale');
|
logger.error('Falling back to en locale');
|
||||||
|
|
||||||
localeName = 'en';
|
localeName = 'en';
|
||||||
|
@ -58,10 +59,10 @@ function load() {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: localeName,
|
name: localeName,
|
||||||
messages
|
messages,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
load: load
|
load,
|
||||||
};
|
};
|
||||||
|
|
202
app/logging.js
202
app/logging.js
|
@ -1,22 +1,31 @@
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
|
|
||||||
const electron = require('electron')
|
const electron = require('electron');
|
||||||
const bunyan = require('bunyan');
|
const bunyan = require('bunyan');
|
||||||
const mkdirp = require('mkdirp');
|
const mkdirp = require('mkdirp');
|
||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
|
const readFirstLine = require('firstline');
|
||||||
|
const readLastLines = require('read-last-lines').read;
|
||||||
|
|
||||||
|
const {
|
||||||
const app = electron.app;
|
app,
|
||||||
const ipc = electron.ipcMain;
|
ipcMain: ipc,
|
||||||
|
} = electron;
|
||||||
const LEVELS = ['fatal', 'error', 'warn', 'info', 'debug', 'trace'];
|
const LEVELS = ['fatal', 'error', 'warn', 'info', 'debug', 'trace'];
|
||||||
|
|
||||||
let logger;
|
let logger;
|
||||||
|
|
||||||
|
|
||||||
function dropFirst(args) {
|
module.exports = {
|
||||||
return Array.prototype.slice.call(args, 1);
|
initialize,
|
||||||
}
|
getLogger,
|
||||||
|
// for tests only:
|
||||||
|
isLineAfterDate,
|
||||||
|
eliminateOutOfDateFiles,
|
||||||
|
eliminateOldEntries,
|
||||||
|
fetchLog,
|
||||||
|
fetch,
|
||||||
|
};
|
||||||
|
|
||||||
function initialize() {
|
function initialize() {
|
||||||
if (logger) {
|
if (logger) {
|
||||||
|
@ -27,38 +36,114 @@ function initialize() {
|
||||||
const logPath = path.join(basePath, 'logs');
|
const logPath = path.join(basePath, 'logs');
|
||||||
mkdirp.sync(logPath);
|
mkdirp.sync(logPath);
|
||||||
|
|
||||||
const logFile = path.join(logPath, 'log.log');
|
return cleanupLogs(logPath).then(() => {
|
||||||
|
const logFile = path.join(logPath, 'log.log');
|
||||||
|
|
||||||
logger = bunyan.createLogger({
|
logger = bunyan.createLogger({
|
||||||
name: 'log',
|
name: 'log',
|
||||||
streams: [{
|
streams: [{
|
||||||
level: 'debug',
|
level: 'debug',
|
||||||
stream: process.stdout
|
stream: process.stdout,
|
||||||
}, {
|
}, {
|
||||||
type: 'rotating-file',
|
type: 'rotating-file',
|
||||||
path: logFile,
|
path: logFile,
|
||||||
period: '1d',
|
period: '1d',
|
||||||
count: 3
|
count: 3,
|
||||||
}]
|
}],
|
||||||
});
|
});
|
||||||
|
|
||||||
LEVELS.forEach(function(level) {
|
LEVELS.forEach((level) => {
|
||||||
ipc.on('log-' + level, function() {
|
ipc.on(`log-${level}`, (first, ...rest) => {
|
||||||
// first parameter is the event, rest are provided arguments
|
logger[level](...rest);
|
||||||
var args = dropFirst(arguments);
|
});
|
||||||
logger[level].apply(logger, args);
|
});
|
||||||
|
|
||||||
|
ipc.on('fetch-log', (event) => {
|
||||||
|
fetch(logPath).then((data) => {
|
||||||
|
event.sender.send('fetched-log', data);
|
||||||
|
}, (error) => {
|
||||||
|
logger.error(`Problem loading log from disk: ${error.stack}`);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
ipc.on('fetch-log', function(event) {
|
function cleanupLogs(logPath) {
|
||||||
fetch(logPath).then(function(data) {
|
const now = new Date();
|
||||||
event.sender.send('fetched-log', data);
|
const earliestDate = new Date(Date.UTC(
|
||||||
}, function(error) {
|
now.getUTCFullYear(),
|
||||||
logger.error('Problem loading log from disk: ' + error.stack);
|
now.getUTCMonth(),
|
||||||
});
|
now.getUTCDate() - 3
|
||||||
|
));
|
||||||
|
|
||||||
|
return eliminateOutOfDateFiles(logPath, earliestDate).then((remaining) => {
|
||||||
|
const files = _.filter(remaining, file => !file.start && file.end);
|
||||||
|
|
||||||
|
if (!files.length) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return eliminateOldEntries(files, earliestDate);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isLineAfterDate(line, date) {
|
||||||
|
if (!line) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data = JSON.parse(line);
|
||||||
|
return (new Date(data.time)).getTime() > date.getTime();
|
||||||
|
} catch (e) {
|
||||||
|
console.log('error parsing log line', e.stack, line);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function eliminateOutOfDateFiles(logPath, date) {
|
||||||
|
const files = fs.readdirSync(logPath);
|
||||||
|
const paths = files.map(file => path.join(logPath, file));
|
||||||
|
|
||||||
|
return Promise.all(_.map(
|
||||||
|
paths,
|
||||||
|
target => Promise.all([
|
||||||
|
readFirstLine(target),
|
||||||
|
readLastLines(target, 2),
|
||||||
|
]).then((results) => {
|
||||||
|
const start = results[0];
|
||||||
|
const end = results[1].split('\n');
|
||||||
|
|
||||||
|
const file = {
|
||||||
|
path: target,
|
||||||
|
start: isLineAfterDate(start, date),
|
||||||
|
end: isLineAfterDate(end[end.length - 1], date)
|
||||||
|
|| isLineAfterDate(end[end.length - 2], date),
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!file.start && !file.end) {
|
||||||
|
fs.unlinkSync(file.path);
|
||||||
|
}
|
||||||
|
|
||||||
|
return file;
|
||||||
|
})
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
function eliminateOldEntries(files, date) {
|
||||||
|
const earliest = date.getTime();
|
||||||
|
|
||||||
|
return Promise.all(_.map(
|
||||||
|
files,
|
||||||
|
file => fetchLog(file.path).then((lines) => {
|
||||||
|
const recent = _.filter(lines, line => (new Date(line.time)).getTime() >= earliest);
|
||||||
|
const text = _.map(recent, line => JSON.stringify(line)).join('\n');
|
||||||
|
|
||||||
|
return fs.writeFileSync(file.path, `${text}\n`);
|
||||||
|
})
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
function getLogger() {
|
function getLogger() {
|
||||||
if (!logger) {
|
if (!logger) {
|
||||||
throw new Error('Logger hasn\'t been initialized yet!');
|
throw new Error('Logger hasn\'t been initialized yet!');
|
||||||
|
@ -68,18 +153,19 @@ function getLogger() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function fetchLog(logFile) {
|
function fetchLog(logFile) {
|
||||||
return new Promise(function(resolve, reject) {
|
return new Promise((resolve, reject) => {
|
||||||
fs.readFile(logFile, { encoding: 'utf8' }, function(err, text) {
|
fs.readFile(logFile, { encoding: 'utf8' }, (err, text) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
return reject(err);
|
return reject(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
const lines = _.compact(text.split('\n'));
|
const lines = _.compact(text.split('\n'));
|
||||||
const data = _.compact(lines.map(function(line) {
|
const data = _.compact(lines.map((line) => {
|
||||||
try {
|
try {
|
||||||
return _.pick(JSON.parse(line), ['level', 'time', 'msg']);
|
return _.pick(JSON.parse(line), ['level', 'time', 'msg']);
|
||||||
|
} catch (e) {
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
catch (e) {}
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return resolve(data);
|
return resolve(data);
|
||||||
|
@ -89,19 +175,17 @@ function fetchLog(logFile) {
|
||||||
|
|
||||||
function fetch(logPath) {
|
function fetch(logPath) {
|
||||||
const files = fs.readdirSync(logPath);
|
const files = fs.readdirSync(logPath);
|
||||||
const paths = files.map(function(file) {
|
const paths = files.map(file => path.join(logPath, file));
|
||||||
return path.join(logPath, file)
|
|
||||||
});
|
|
||||||
|
|
||||||
// creating a manual log entry for the final log result
|
// creating a manual log entry for the final log result
|
||||||
var now = new Date();
|
const now = new Date();
|
||||||
const fileListEntry = {
|
const fileListEntry = {
|
||||||
level: 30, // INFO
|
level: 30, // INFO
|
||||||
time: now.toJSON(),
|
time: now.toJSON(),
|
||||||
msg: 'Loaded this list of log files from logPath: ' + files.join(', '),
|
msg: `Loaded this list of log files from logPath: ${files.join(', ')}`,
|
||||||
};
|
};
|
||||||
|
|
||||||
return Promise.all(paths.map(fetchLog)).then(function(results) {
|
return Promise.all(paths.map(fetchLog)).then((results) => {
|
||||||
const data = _.flatten(results);
|
const data = _.flatten(results);
|
||||||
|
|
||||||
data.push(fileListEntry);
|
data.push(fileListEntry);
|
||||||
|
@ -111,18 +195,14 @@ function fetch(logPath) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function logAtLevel() {
|
function logAtLevel(level, ...args) {
|
||||||
const level = arguments[0];
|
|
||||||
const args = Array.prototype.slice.call(arguments, 1);
|
|
||||||
|
|
||||||
if (logger) {
|
if (logger) {
|
||||||
// To avoid [Object object] in our log since console.log handles non-strings smoothly
|
// To avoid [Object object] in our log since console.log handles non-strings smoothly
|
||||||
const str = args.map(function(item) {
|
const str = args.map((item) => {
|
||||||
if (typeof item !== 'string') {
|
if (typeof item !== 'string') {
|
||||||
try {
|
try {
|
||||||
return JSON.stringify(item);
|
return JSON.stringify(item);
|
||||||
}
|
} catch (e) {
|
||||||
catch (e) {
|
|
||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -131,20 +211,16 @@ function logAtLevel() {
|
||||||
});
|
});
|
||||||
logger[level](str.join(' '));
|
logger[level](str.join(' '));
|
||||||
} else {
|
} else {
|
||||||
console._log.apply(console, consoleArgs);
|
console._log(...args);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This blows up using mocha --watch, so we ensure it is run just once
|
||||||
console._log = console.log;
|
if (!console._log) {
|
||||||
console.log = _.partial(logAtLevel, 'info');
|
console._log = console.log;
|
||||||
console._error = console.error;
|
console.log = _.partial(logAtLevel, 'info');
|
||||||
console.error = _.partial(logAtLevel, 'error');
|
console._error = console.error;
|
||||||
console._warn = console.warn;
|
console.error = _.partial(logAtLevel, 'error');
|
||||||
console.warn = _.partial(logAtLevel, 'warn');
|
console._warn = console.warn;
|
||||||
|
console.warn = _.partial(logAtLevel, 'warn');
|
||||||
|
}
|
||||||
module.exports = {
|
|
||||||
initialize,
|
|
||||||
getLogger,
|
|
||||||
};
|
|
||||||
|
|
43
app/menu.js
43
app/menu.js
|
@ -1,18 +1,20 @@
|
||||||
function createTemplate(options, messages) {
|
function createTemplate(options, messages) {
|
||||||
const showDebugLog = options.showDebugLog;
|
const {
|
||||||
const showAbout = options.showAbout;
|
showDebugLog,
|
||||||
const openReleaseNotes = options.openReleaseNotes;
|
showAbout,
|
||||||
const openNewBugForm = options.openNewBugForm;
|
openReleaseNotes,
|
||||||
const openSupportPage = options.openSupportPage;
|
openNewBugForm,
|
||||||
const openForums = options.openForums;
|
openSupportPage,
|
||||||
|
openForums,
|
||||||
|
} = options;
|
||||||
|
|
||||||
let template = [{
|
const template = [{
|
||||||
label: messages.mainMenuFile.message,
|
label: messages.mainMenuFile.message,
|
||||||
submenu: [
|
submenu: [
|
||||||
{
|
{
|
||||||
role: 'quit',
|
role: 'quit',
|
||||||
},
|
},
|
||||||
]
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: messages.mainMenuEdit.message,
|
label: messages.mainMenuEdit.message,
|
||||||
|
@ -43,8 +45,8 @@ function createTemplate(options, messages) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
role: 'selectall',
|
role: 'selectall',
|
||||||
}
|
},
|
||||||
]
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: messages.mainMenuView.message,
|
label: messages.mainMenuView.message,
|
||||||
|
@ -77,7 +79,7 @@ function createTemplate(options, messages) {
|
||||||
{
|
{
|
||||||
role: 'toggledevtools',
|
role: 'toggledevtools',
|
||||||
},
|
},
|
||||||
]
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: messages.mainMenuWindow.message,
|
label: messages.mainMenuWindow.message,
|
||||||
|
@ -86,7 +88,7 @@ function createTemplate(options, messages) {
|
||||||
{
|
{
|
||||||
role: 'minimize',
|
role: 'minimize',
|
||||||
},
|
},
|
||||||
]
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: messages.mainMenuHelp.message,
|
label: messages.mainMenuHelp.message,
|
||||||
|
@ -118,7 +120,7 @@ function createTemplate(options, messages) {
|
||||||
label: messages.aboutSignalDesktop.message,
|
label: messages.aboutSignalDesktop.message,
|
||||||
click: showAbout,
|
click: showAbout,
|
||||||
},
|
},
|
||||||
]
|
],
|
||||||
}];
|
}];
|
||||||
|
|
||||||
if (process.platform === 'darwin') {
|
if (process.platform === 'darwin') {
|
||||||
|
@ -129,8 +131,10 @@ function createTemplate(options, messages) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateForMac(template, messages, options) {
|
function updateForMac(template, messages, options) {
|
||||||
const showWindow = options.showWindow;
|
const {
|
||||||
const showAbout = options.showAbout;
|
showWindow,
|
||||||
|
showAbout,
|
||||||
|
} = options;
|
||||||
|
|
||||||
// Remove About item and separator from Help menu, since it's on the first menu
|
// Remove About item and separator from Help menu, since it's on the first menu
|
||||||
template[4].submenu.pop();
|
template[4].submenu.pop();
|
||||||
|
@ -162,13 +166,13 @@ function updateForMac(template, messages, options) {
|
||||||
{
|
{
|
||||||
role: 'quit',
|
role: 'quit',
|
||||||
},
|
},
|
||||||
]
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add to Edit menu
|
// Add to Edit menu
|
||||||
template[1].submenu.push(
|
template[1].submenu.push(
|
||||||
{
|
{
|
||||||
type: 'separator'
|
type: 'separator',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: messages.speech.message,
|
label: messages.speech.message,
|
||||||
|
@ -179,11 +183,12 @@ function updateForMac(template, messages, options) {
|
||||||
{
|
{
|
||||||
role: 'stopspeaking',
|
role: 'stopspeaking',
|
||||||
},
|
},
|
||||||
]
|
],
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
// Add to Window menu
|
// Replace Window menu
|
||||||
|
// eslint-disable-next-line no-param-reassign
|
||||||
template[3].submenu = [
|
template[3].submenu = [
|
||||||
{
|
{
|
||||||
accelerator: 'CmdOrCtrl+W',
|
accelerator: 'CmdOrCtrl+W',
|
||||||
|
|
|
@ -1,23 +1,24 @@
|
||||||
const electron = require('electron')
|
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
|
||||||
const app = electron.app;
|
const {
|
||||||
const Menu = electron.Menu;
|
app,
|
||||||
const Tray = electron.Tray;
|
Menu,
|
||||||
|
Tray,
|
||||||
|
} = require('electron');
|
||||||
|
|
||||||
let trayContextMenu = null;
|
let trayContextMenu = null;
|
||||||
let tray = null;
|
let tray = null;
|
||||||
|
|
||||||
function createTrayIcon(getMainWindow, messages) {
|
function createTrayIcon(getMainWindow, messages) {
|
||||||
|
|
||||||
// A smaller icon is needed on macOS
|
// A smaller icon is needed on macOS
|
||||||
tray = new Tray(
|
tray = new Tray(
|
||||||
process.platform == "darwin" ?
|
process.platform === 'darwin' ?
|
||||||
path.join(__dirname, '..', 'images', 'icon_16.png') :
|
path.join(__dirname, '..', 'images', 'icon_16.png') :
|
||||||
path.join(__dirname, '..', 'images', 'icon_256.png'));
|
path.join(__dirname, '..', 'images', 'icon_256.png')
|
||||||
|
);
|
||||||
|
|
||||||
tray.toggleWindowVisibility = function () {
|
tray.toggleWindowVisibility = () => {
|
||||||
var mainWindow = getMainWindow();
|
const mainWindow = getMainWindow();
|
||||||
if (mainWindow) {
|
if (mainWindow) {
|
||||||
if (mainWindow.isVisible()) {
|
if (mainWindow.isVisible()) {
|
||||||
mainWindow.hide();
|
mainWindow.hide();
|
||||||
|
@ -33,31 +34,28 @@ function createTrayIcon(getMainWindow, messages) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tray.updateContextMenu();
|
tray.updateContextMenu();
|
||||||
}
|
};
|
||||||
|
|
||||||
tray.updateContextMenu = function () {
|
tray.updateContextMenu = () => {
|
||||||
|
const mainWindow = getMainWindow();
|
||||||
var mainWindow = getMainWindow();
|
|
||||||
|
|
||||||
// NOTE: we want to have the show/hide entry available in the tray icon
|
// NOTE: we want to have the show/hide entry available in the tray icon
|
||||||
// context menu, since the 'click' event may not work on all platforms.
|
// context menu, since the 'click' event may not work on all platforms.
|
||||||
// For details please refer to:
|
// For details please refer to:
|
||||||
// https://github.com/electron/electron/blob/master/docs/api/tray.md.
|
// https://github.com/electron/electron/blob/master/docs/api/tray.md.
|
||||||
trayContextMenu = Menu.buildFromTemplate([
|
trayContextMenu = Menu.buildFromTemplate([{
|
||||||
{
|
id: 'toggleWindowVisibility',
|
||||||
id: 'toggleWindowVisibility',
|
label: messages[mainWindow.isVisible() ? 'hide' : 'show'].message,
|
||||||
label: messages[mainWindow.isVisible() ? 'hide' : 'show'].message,
|
click: tray.toggleWindowVisibility,
|
||||||
click: tray.toggleWindowVisibility
|
},
|
||||||
},
|
{
|
||||||
{
|
id: 'quit',
|
||||||
id: 'quit',
|
label: messages.quit.message,
|
||||||
label: messages.quit.message,
|
click: app.quit.bind(app),
|
||||||
click: app.quit.bind(app)
|
}]);
|
||||||
}
|
|
||||||
]);
|
|
||||||
|
|
||||||
tray.setContextMenu(trayContextMenu);
|
tray.setContextMenu(trayContextMenu);
|
||||||
}
|
};
|
||||||
|
|
||||||
tray.on('click', tray.toggleWindowVisibility);
|
tray.on('click', tray.toggleWindowVisibility);
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
|
||||||
const app = require('electron').app;
|
const { app } = require('electron');
|
||||||
const ElectronConfig = require('electron-config');
|
const ElectronConfig = require('electron-config');
|
||||||
|
|
||||||
const config = require('./config');
|
const config = require('./config');
|
||||||
|
@ -10,13 +10,13 @@ const config = require('./config');
|
||||||
if (config.has('storageProfile')) {
|
if (config.has('storageProfile')) {
|
||||||
const userData = path.join(
|
const userData = path.join(
|
||||||
app.getPath('appData'),
|
app.getPath('appData'),
|
||||||
'Signal-' + config.get('storageProfile')
|
`Signal-${config.get('storageProfile')}`
|
||||||
);
|
);
|
||||||
|
|
||||||
app.setPath('userData', userData);
|
app.setPath('userData', userData);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('userData: ' + app.getPath('userData'));
|
console.log(`userData: ${app.getPath('userData')}`);
|
||||||
|
|
||||||
// this needs to be below our update to the appData path
|
// this needs to be below our update to the appData path
|
||||||
const userConfig = new ElectronConfig();
|
const userConfig = new ElectronConfig();
|
||||||
|
|
|
@ -10,5 +10,5 @@ function shouldQuit() {
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
shouldQuit,
|
shouldQuit,
|
||||||
markShouldQuit
|
markShouldQuit,
|
||||||
};
|
};
|
||||||
|
|
|
@ -8,10 +8,12 @@ cache:
|
||||||
install:
|
install:
|
||||||
- systeminfo | findstr /C:"OS"
|
- systeminfo | findstr /C:"OS"
|
||||||
- set PATH=C:\Ruby23-x64\bin;%PATH%
|
- set PATH=C:\Ruby23-x64\bin;%PATH%
|
||||||
- ps: Install-Product node 6 x64
|
- ps: Install-Product node 7.9.0 x64
|
||||||
- yarn install
|
- yarn install
|
||||||
|
|
||||||
build_script:
|
build_script:
|
||||||
|
- yarn eslint
|
||||||
|
- yarn test-server
|
||||||
- yarn run icon-gen
|
- yarn run icon-gen
|
||||||
- node build\grunt.js
|
- node build\grunt.js
|
||||||
- type package.json | findstr /v certificateSubjectName > temp.json
|
- type package.json | findstr /v certificateSubjectName > temp.json
|
||||||
|
|
184
main.js
184
main.js
|
@ -6,19 +6,25 @@ const _ = require('lodash');
|
||||||
const electron = require('electron');
|
const electron = require('electron');
|
||||||
const semver = require('semver');
|
const semver = require('semver');
|
||||||
|
|
||||||
const BrowserWindow = electron.BrowserWindow;
|
const {
|
||||||
const app = electron.app;
|
BrowserWindow,
|
||||||
const ipc = electron.ipcMain;
|
app,
|
||||||
const Menu = electron.Menu;
|
Menu,
|
||||||
const shell = electron.shell;
|
shell,
|
||||||
|
ipcMain: ipc,
|
||||||
|
} = electron;
|
||||||
|
|
||||||
const packageJson = require('./package.json');
|
const packageJson = require('./package.json');
|
||||||
|
|
||||||
|
const createTrayIcon = require('./app/tray_icon');
|
||||||
|
const createTemplate = require('./app/menu.js');
|
||||||
|
const logging = require('./app/logging');
|
||||||
const autoUpdate = require('./app/auto_update');
|
const autoUpdate = require('./app/auto_update');
|
||||||
const windowState = require('./app/window_state');
|
const windowState = require('./app/window_state');
|
||||||
|
|
||||||
|
|
||||||
const aumid = 'org.whispersystems.' + packageJson.name;
|
const aumid = `org.whispersystems.${packageJson.name}`;
|
||||||
console.log('setting AUMID to ' + aumid);
|
console.log(`setting AUMID to ${aumid}`);
|
||||||
app.setAppUserModelId(aumid);
|
app.setAppUserModelId(aumid);
|
||||||
|
|
||||||
// Keep a global reference of the window object, if you don't, the window will
|
// Keep a global reference of the window object, if you don't, the window will
|
||||||
|
@ -34,7 +40,7 @@ let tray = null;
|
||||||
const startInTray = process.argv.find(arg => arg === '--start-in-tray');
|
const startInTray = process.argv.find(arg => arg === '--start-in-tray');
|
||||||
const usingTrayIcon = startInTray || process.argv.find(arg => arg === '--use-tray-icon');
|
const usingTrayIcon = startInTray || process.argv.find(arg => arg === '--use-tray-icon');
|
||||||
|
|
||||||
const config = require("./app/config");
|
const config = require('./app/config');
|
||||||
|
|
||||||
// Very important to put before the single instance check, since it is based on the
|
// Very important to put before the single instance check, since it is based on the
|
||||||
// userData directory.
|
// userData directory.
|
||||||
|
@ -63,7 +69,7 @@ function showWindow() {
|
||||||
|
|
||||||
if (!process.mas) {
|
if (!process.mas) {
|
||||||
console.log('making app single instance');
|
console.log('making app single instance');
|
||||||
var shouldQuit = app.makeSingleInstance(function(commandLine, workingDirectory) {
|
const shouldQuit = app.makeSingleInstance(() => {
|
||||||
// Someone tried to run a second instance, we should focus our window
|
// Someone tried to run a second instance, we should focus our window
|
||||||
if (mainWindow) {
|
if (mainWindow) {
|
||||||
if (mainWindow.isMinimized()) {
|
if (mainWindow.isMinimized()) {
|
||||||
|
@ -78,19 +84,14 @@ if (!process.mas) {
|
||||||
if (shouldQuit) {
|
if (shouldQuit) {
|
||||||
console.log('quitting; we are the second instance');
|
console.log('quitting; we are the second instance');
|
||||||
app.quit();
|
app.quit();
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const logging = require('./app/logging');
|
|
||||||
|
|
||||||
// This must be after we set up appPath in user_config.js, so we know where logs go
|
|
||||||
logging.initialize();
|
|
||||||
const logger = logging.getLogger();
|
|
||||||
|
|
||||||
let windowConfig = userConfig.get('window');
|
let windowConfig = userConfig.get('window');
|
||||||
const loadLocale = require('./app/locale').load;
|
const loadLocale = require('./app/locale').load;
|
||||||
|
|
||||||
|
// Both of these will be set after app fires the 'ready' event
|
||||||
|
let logger;
|
||||||
let locale;
|
let locale;
|
||||||
|
|
||||||
const WINDOWS_8 = '8.0.0';
|
const WINDOWS_8 = '8.0.0';
|
||||||
|
@ -118,20 +119,20 @@ function prepareURL(pathSegments) {
|
||||||
appInstance: process.env.NODE_APP_INSTANCE,
|
appInstance: process.env.NODE_APP_INSTANCE,
|
||||||
polyfillNotifications: polyfillNotifications ? true : undefined, // for stringify()
|
polyfillNotifications: polyfillNotifications ? true : undefined, // for stringify()
|
||||||
proxyUrl: process.env.HTTPS_PROXY || process.env.https_proxy,
|
proxyUrl: process.env.HTTPS_PROXY || process.env.https_proxy,
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleUrl(event, target) {
|
function handleUrl(event, target) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
const protocol = url.parse(target).protocol;
|
const { protocol } = url.parse(target);
|
||||||
if (protocol === 'http:' || protocol === 'https:') {
|
if (protocol === 'http:' || protocol === 'https:') {
|
||||||
shell.openExternal(target);
|
shell.openExternal(target);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function captureClicks(window) {
|
function captureClicks(window) {
|
||||||
window.webContents.on('will-navigate', handleUrl)
|
window.webContents.on('will-navigate', handleUrl);
|
||||||
window.webContents.on('new-window', handleUrl);
|
window.webContents.on('new-window', handleUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -150,11 +151,11 @@ function isVisible(window, bounds) {
|
||||||
|
|
||||||
// requiring BOUNDS_BUFFER pixels on the left or right side
|
// requiring BOUNDS_BUFFER pixels on the left or right side
|
||||||
const rightSideClearOfLeftBound = (window.x + window.width >= boundsX + BOUNDS_BUFFER);
|
const rightSideClearOfLeftBound = (window.x + window.width >= boundsX + BOUNDS_BUFFER);
|
||||||
const leftSideClearOfRightBound = (window.x <= boundsX + boundsWidth - 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
|
// top can't be offscreen, and must show at least BOUNDS_BUFFER pixels at bottom
|
||||||
const topClearOfUpperBound = window.y >= boundsY;
|
const topClearOfUpperBound = window.y >= boundsY;
|
||||||
const topClearOfLowerBound = (window.y <= boundsY + boundsHeight - BOUNDS_BUFFER);
|
const topClearOfLowerBound = (window.y <= (boundsY + boundsHeight) - BOUNDS_BUFFER);
|
||||||
|
|
||||||
return rightSideClearOfLeftBound
|
return rightSideClearOfLeftBound
|
||||||
&& leftSideClearOfRightBound
|
&& leftSideClearOfRightBound
|
||||||
|
@ -162,8 +163,8 @@ function isVisible(window, bounds) {
|
||||||
&& topClearOfLowerBound;
|
&& topClearOfLowerBound;
|
||||||
}
|
}
|
||||||
|
|
||||||
function createWindow () {
|
function createWindow() {
|
||||||
const screen = electron.screen;
|
const { screen } = electron;
|
||||||
const windowOptions = Object.assign({
|
const windowOptions = Object.assign({
|
||||||
show: !startInTray, // allow to start minimised in tray
|
show: !startInTray, // allow to start minimised in tray
|
||||||
width: DEFAULT_WIDTH,
|
width: DEFAULT_WIDTH,
|
||||||
|
@ -173,8 +174,8 @@ function createWindow () {
|
||||||
autoHideMenuBar: false,
|
autoHideMenuBar: false,
|
||||||
webPreferences: {
|
webPreferences: {
|
||||||
nodeIntegration: false,
|
nodeIntegration: false,
|
||||||
//sandbox: true,
|
// sandbox: true,
|
||||||
preload: path.join(__dirname, 'preload.js')
|
preload: path.join(__dirname, 'preload.js'),
|
||||||
},
|
},
|
||||||
icon: path.join(__dirname, 'images', 'icon_256.png'),
|
icon: path.join(__dirname, 'images', 'icon_256.png'),
|
||||||
}, _.pick(windowConfig, ['maximized', 'autoHideMenuBar', 'width', 'height', 'x', 'y']));
|
}, _.pick(windowConfig, ['maximized', 'autoHideMenuBar', 'width', 'height', 'x', 'y']));
|
||||||
|
@ -192,7 +193,7 @@ function createWindow () {
|
||||||
delete windowOptions.autoHideMenuBar;
|
delete windowOptions.autoHideMenuBar;
|
||||||
}
|
}
|
||||||
|
|
||||||
const visibleOnAnyScreen = _.some(screen.getAllDisplays(), function(display) {
|
const visibleOnAnyScreen = _.some(screen.getAllDisplays(), (display) => {
|
||||||
if (!_.isNumber(windowOptions.x) || !_.isNumber(windowOptions.y)) {
|
if (!_.isNumber(windowOptions.x) || !_.isNumber(windowOptions.y)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -225,7 +226,7 @@ function createWindow () {
|
||||||
width: size[0],
|
width: size[0],
|
||||||
height: size[1],
|
height: size[1],
|
||||||
x: position[0],
|
x: position[0],
|
||||||
y: position[1]
|
y: position[1],
|
||||||
};
|
};
|
||||||
|
|
||||||
if (mainWindow.isFullScreen()) {
|
if (mainWindow.isFullScreen()) {
|
||||||
|
@ -243,12 +244,13 @@ function createWindow () {
|
||||||
mainWindow.on('move', debouncedCaptureStats);
|
mainWindow.on('move', debouncedCaptureStats);
|
||||||
mainWindow.on('close', captureAndSaveWindowStats);
|
mainWindow.on('close', captureAndSaveWindowStats);
|
||||||
|
|
||||||
mainWindow.on('focus', function() {
|
mainWindow.on('focus', () => {
|
||||||
mainWindow.flashFrame(false);
|
mainWindow.flashFrame(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Ingested in preload.js via a sendSync call
|
// Ingested in preload.js via a sendSync call
|
||||||
ipc.on('locale-data', function(event, arg) {
|
ipc.on('locale-data', (event) => {
|
||||||
|
// eslint-disable-next-line no-param-reassign
|
||||||
event.returnValue = locale.messages;
|
event.returnValue = locale.messages;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -262,23 +264,21 @@ function createWindow () {
|
||||||
|
|
||||||
if (config.get('openDevTools')) {
|
if (config.get('openDevTools')) {
|
||||||
// Open the DevTools.
|
// Open the DevTools.
|
||||||
mainWindow.webContents.openDevTools()
|
mainWindow.webContents.openDevTools();
|
||||||
}
|
}
|
||||||
|
|
||||||
captureClicks(mainWindow);
|
captureClicks(mainWindow);
|
||||||
|
|
||||||
mainWindow.webContents.on('will-navigate', function(e) {
|
mainWindow.webContents.on('will-navigate', (e) => {
|
||||||
logger.info('will-navigate');
|
logger.info('will-navigate');
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Emitted when the window is about to be closed.
|
// Emitted when the window is about to be closed.
|
||||||
mainWindow.on('close', function (e) {
|
mainWindow.on('close', (e) => {
|
||||||
|
|
||||||
// If the application is terminating, just do the default
|
// If the application is terminating, just do the default
|
||||||
if (windowState.shouldQuit()
|
if (windowState.shouldQuit()
|
||||||
|| config.environment === 'test' || config.environment === 'test-lib') {
|
|| config.environment === 'test' || config.environment === 'test-lib') {
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -296,26 +296,26 @@ function createWindow () {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Emitted when the window is closed.
|
// Emitted when the window is closed.
|
||||||
mainWindow.on('closed', function () {
|
mainWindow.on('closed', () => {
|
||||||
// Dereference the window object, usually you would store windows
|
// Dereference the window object, usually you would store windows
|
||||||
// in an array if your app supports multi windows, this is the time
|
// in an array if your app supports multi windows, this is the time
|
||||||
// when you should delete the corresponding element.
|
// when you should delete the corresponding element.
|
||||||
mainWindow = null
|
mainWindow = null;
|
||||||
});
|
});
|
||||||
|
|
||||||
ipc.on('show-window', function() {
|
ipc.on('show-window', () => {
|
||||||
showWindow();
|
showWindow();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function showDebugLog() {
|
function showDebugLog() {
|
||||||
if (mainWindow) {
|
if (mainWindow) {
|
||||||
mainWindow.webContents.send('debug-log')
|
mainWindow.webContents.send('debug-log');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function openReleaseNotes() {
|
function openReleaseNotes() {
|
||||||
shell.openExternal('https://github.com/WhisperSystems/Signal-Desktop/releases/tag/v' + app.getVersion());
|
shell.openExternal(`https://github.com/WhisperSystems/Signal-Desktop/releases/tag/v${app.getVersion()}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
function openNewBugForm() {
|
function openNewBugForm() {
|
||||||
|
@ -348,7 +348,7 @@ function showAbout() {
|
||||||
show: false,
|
show: false,
|
||||||
webPreferences: {
|
webPreferences: {
|
||||||
nodeIntegration: false,
|
nodeIntegration: false,
|
||||||
preload: path.join(__dirname, 'preload.js')
|
preload: path.join(__dirname, 'preload.js'),
|
||||||
},
|
},
|
||||||
parent: mainWindow,
|
parent: mainWindow,
|
||||||
};
|
};
|
||||||
|
@ -359,11 +359,11 @@ function showAbout() {
|
||||||
|
|
||||||
aboutWindow.loadURL(prepareURL([__dirname, 'about.html']));
|
aboutWindow.loadURL(prepareURL([__dirname, 'about.html']));
|
||||||
|
|
||||||
aboutWindow.on('closed', function () {
|
aboutWindow.on('closed', () => {
|
||||||
aboutWindow = null;
|
aboutWindow = null;
|
||||||
});
|
});
|
||||||
|
|
||||||
aboutWindow.once('ready-to-show', function() {
|
aboutWindow.once('ready-to-show', () => {
|
||||||
aboutWindow.show();
|
aboutWindow.show();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -372,53 +372,64 @@ function showAbout() {
|
||||||
// initialization and is ready to create browser windows.
|
// initialization and is ready to create browser windows.
|
||||||
// Some APIs can only be used after this event occurs.
|
// Some APIs can only be used after this event occurs.
|
||||||
let ready = false;
|
let ready = false;
|
||||||
app.on('ready', function() {
|
app.on('ready', () => {
|
||||||
logger.info('app ready');
|
let loggingSetupError;
|
||||||
ready = true;
|
logging.initialize().catch((error) => {
|
||||||
|
loggingSetupError = error;
|
||||||
|
}).then(() => {
|
||||||
|
logger = logging.getLogger();
|
||||||
|
logger.info('app ready');
|
||||||
|
|
||||||
if (!locale) {
|
if (loggingSetupError) {
|
||||||
locale = loadLocale();
|
logger.error('Problem setting up logging', loggingSetupError.stack);
|
||||||
}
|
}
|
||||||
|
|
||||||
autoUpdate.initialize(getMainWindow, locale.messages);
|
if (!locale) {
|
||||||
|
locale = loadLocale();
|
||||||
|
}
|
||||||
|
|
||||||
createWindow();
|
ready = true;
|
||||||
|
|
||||||
if (usingTrayIcon) {
|
autoUpdate.initialize(getMainWindow, locale.messages);
|
||||||
const createTrayIcon = require("./app/tray_icon");
|
|
||||||
tray = createTrayIcon(getMainWindow, locale.messages);
|
|
||||||
}
|
|
||||||
|
|
||||||
const options = {
|
createWindow();
|
||||||
showDebugLog,
|
|
||||||
showWindow,
|
|
||||||
showAbout,
|
|
||||||
openReleaseNotes,
|
|
||||||
openNewBugForm,
|
|
||||||
openSupportPage,
|
|
||||||
openForums,
|
|
||||||
};
|
|
||||||
const createTemplate = require('./app/menu.js');
|
|
||||||
const template = createTemplate(options, locale.messages);
|
|
||||||
|
|
||||||
const menu = Menu.buildFromTemplate(template);
|
if (usingTrayIcon) {
|
||||||
Menu.setApplicationMenu(menu);
|
tray = createTrayIcon(getMainWindow, locale.messages);
|
||||||
})
|
}
|
||||||
|
|
||||||
app.on('before-quit', function() {
|
const options = {
|
||||||
|
showDebugLog,
|
||||||
|
showWindow,
|
||||||
|
showAbout,
|
||||||
|
openReleaseNotes,
|
||||||
|
openNewBugForm,
|
||||||
|
openSupportPage,
|
||||||
|
openForums,
|
||||||
|
};
|
||||||
|
const template = createTemplate(options, locale.messages);
|
||||||
|
|
||||||
|
const menu = Menu.buildFromTemplate(template);
|
||||||
|
Menu.setApplicationMenu(menu);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
app.on('before-quit', () => {
|
||||||
windowState.markShouldQuit();
|
windowState.markShouldQuit();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Quit when all windows are closed.
|
// Quit when all windows are closed.
|
||||||
app.on('window-all-closed', function () {
|
app.on('window-all-closed', () => {
|
||||||
// On OS X it is common for applications and their menu bar
|
// On OS X it is common for applications and their menu bar
|
||||||
// to stay active until the user quits explicitly with Cmd + Q
|
// to stay active until the user quits explicitly with Cmd + Q
|
||||||
if (process.platform !== 'darwin' || config.environment === 'test' || config.environment === 'test-lib') {
|
if (process.platform !== 'darwin'
|
||||||
app.quit()
|
|| config.environment === 'test'
|
||||||
|
|| config.environment === 'test-lib') {
|
||||||
|
app.quit();
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
app.on('activate', function () {
|
app.on('activate', () => {
|
||||||
if (!ready) {
|
if (!ready) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -430,46 +441,43 @@ app.on('activate', function () {
|
||||||
} else {
|
} else {
|
||||||
createWindow();
|
createWindow();
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
// In this file you can include the rest of your app's specific main process
|
ipc.on('set-badge-count', (event, count) => {
|
||||||
// code. You can also put them in separate files and require them here.
|
|
||||||
|
|
||||||
ipc.on('set-badge-count', function(event, count) {
|
|
||||||
app.setBadgeCount(count);
|
app.setBadgeCount(count);
|
||||||
});
|
});
|
||||||
|
|
||||||
ipc.on('draw-attention', function(event, count) {
|
ipc.on('draw-attention', () => {
|
||||||
if (process.platform === 'darwin') {
|
if (process.platform === 'darwin') {
|
||||||
app.dock.bounce();
|
app.dock.bounce();
|
||||||
} else if (process.platform == 'win32') {
|
} else if (process.platform === 'win32') {
|
||||||
mainWindow.flashFrame(true);
|
mainWindow.flashFrame(true);
|
||||||
setTimeout(function() {
|
setTimeout(() => {
|
||||||
mainWindow.flashFrame(false);
|
mainWindow.flashFrame(false);
|
||||||
}, 1000);
|
}, 1000);
|
||||||
} else if (process.platform == 'linux') {
|
} else if (process.platform === 'linux') {
|
||||||
mainWindow.flashFrame(true);
|
mainWindow.flashFrame(true);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
ipc.on('restart', function(event) {
|
ipc.on('restart', () => {
|
||||||
app.relaunch();
|
app.relaunch();
|
||||||
app.quit();
|
app.quit();
|
||||||
});
|
});
|
||||||
|
|
||||||
ipc.on("set-auto-hide-menu-bar", function(event, autoHide) {
|
ipc.on('set-auto-hide-menu-bar', (event, autoHide) => {
|
||||||
if (mainWindow) {
|
if (mainWindow) {
|
||||||
mainWindow.setAutoHideMenuBar(autoHide);
|
mainWindow.setAutoHideMenuBar(autoHide);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
ipc.on("set-menu-bar-visibility", function(event, visibility) {
|
ipc.on('set-menu-bar-visibility', (event, visibility) => {
|
||||||
if (mainWindow) {
|
if (mainWindow) {
|
||||||
mainWindow.setMenuBarVisibility(visibility);
|
mainWindow.setMenuBarVisibility(visibility);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
ipc.on("close-about", function() {
|
ipc.on('close-about', () => {
|
||||||
if (aboutWindow) {
|
if (aboutWindow) {
|
||||||
aboutWindow.close();
|
aboutWindow.close();
|
||||||
}
|
}
|
||||||
|
|
99
package.json
99
package.json
|
@ -10,26 +10,6 @@
|
||||||
"email": "support@whispersystems.org"
|
"email": "support@whispersystems.org"
|
||||||
},
|
},
|
||||||
"main": "main.js",
|
"main": "main.js",
|
||||||
"devDependencies": {
|
|
||||||
"asar": "^0.14.0",
|
|
||||||
"bower": "^1.8.2",
|
|
||||||
"electron": "1.7.10",
|
|
||||||
"electron-builder": "^19.49.2",
|
|
||||||
"electron-icon-maker": "^0.0.4",
|
|
||||||
"electron-publisher-s3": "^19.49.0",
|
|
||||||
"grunt": "^1.0.1",
|
|
||||||
"grunt-cli": "^1.2.0",
|
|
||||||
"grunt-contrib-concat": "^1.0.1",
|
|
||||||
"grunt-contrib-copy": "^1.0.0",
|
|
||||||
"grunt-contrib-jshint": "^1.1.0",
|
|
||||||
"grunt-contrib-watch": "^1.0.0",
|
|
||||||
"grunt-exec": "^3.0.0",
|
|
||||||
"grunt-gitinfo": "^0.1.7",
|
|
||||||
"grunt-jscs": "^3.0.1",
|
|
||||||
"grunt-sass": "^2.0.0",
|
|
||||||
"node-sass-import-once": "^1.2.0",
|
|
||||||
"spectron": "^3.7.2"
|
|
||||||
},
|
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"postinstall": "electron-builder install-app-deps && rimraf node_modules/dtrace-provider",
|
"postinstall": "electron-builder install-app-deps && rimraf node_modules/dtrace-provider",
|
||||||
"test": "grunt test",
|
"test": "grunt test",
|
||||||
|
@ -53,7 +33,62 @@
|
||||||
"release-mac": "npm run build-release -- -m --prepackaged release/mac/Signal*.app --publish=always",
|
"release-mac": "npm run build-release -- -m --prepackaged release/mac/Signal*.app --publish=always",
|
||||||
"release-win": "npm run build-release -- -w --prepackaged release/windows --publish=always",
|
"release-win": "npm run build-release -- -w --prepackaged release/windows --publish=always",
|
||||||
"release-lin": "npm run build-release -- -l --prepackaged release/linux && NAME=$npm_package_name VERSION=$npm_package_version ./aptly.sh",
|
"release-lin": "npm run build-release -- -l --prepackaged release/linux && NAME=$npm_package_name VERSION=$npm_package_version ./aptly.sh",
|
||||||
"release": "npm run release-mac && npm run release-win && npm run release-lin"
|
"release": "npm run release-mac && npm run release-win && npm run release-lin",
|
||||||
|
"test-server": "mocha --recursive test/server",
|
||||||
|
"test-server-coverage": "nyc --reporter=lcov --reporter=text mocha --recursive test/server",
|
||||||
|
"eslint": "eslint .",
|
||||||
|
"open-coverage": "open coverage/lcov-report/index.html"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"bunyan": "^1.8.12",
|
||||||
|
"config": "^1.28.1",
|
||||||
|
"electron-config": "^1.0.0",
|
||||||
|
"electron-editor-context-menu": "^1.1.1",
|
||||||
|
"electron-updater": "^2.17.6",
|
||||||
|
"emoji-datasource": "4.0.0",
|
||||||
|
"emoji-datasource-apple": "4.0.0",
|
||||||
|
"emoji-js": "^3.4.0",
|
||||||
|
"emoji-panel": "https://github.com/scottnonnenberg/emoji-panel.git#v0.5.5",
|
||||||
|
"firstline": "^1.2.1",
|
||||||
|
"google-libphonenumber": "^3.0.7",
|
||||||
|
"lodash": "^4.17.4",
|
||||||
|
"mkdirp": "^0.5.1",
|
||||||
|
"node-fetch": "https://github.com/scottnonnenberg/node-fetch.git#3e5f51e08c647ee5f20c43b15cf2d352d61c36b4",
|
||||||
|
"node-notifier": "^5.1.2",
|
||||||
|
"os-locale": "^2.1.0",
|
||||||
|
"proxy-agent": "^2.1.0",
|
||||||
|
"read-last-lines": "^1.3.0",
|
||||||
|
"rimraf": "^2.6.2",
|
||||||
|
"semver": "^5.4.1",
|
||||||
|
"spellchecker": "^3.4.4",
|
||||||
|
"websocket": "^1.0.25"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"asar": "^0.14.0",
|
||||||
|
"bower": "^1.8.2",
|
||||||
|
"chai": "^4.1.2",
|
||||||
|
"electron": "1.7.10",
|
||||||
|
"electron-builder": "^19.49.2",
|
||||||
|
"electron-icon-maker": "^0.0.4",
|
||||||
|
"electron-publisher-s3": "^19.49.0",
|
||||||
|
"eslint": "^4.14.0",
|
||||||
|
"eslint-config-airbnb-base": "^12.1.0",
|
||||||
|
"eslint-plugin-import": "^2.8.0",
|
||||||
|
"grunt": "^1.0.1",
|
||||||
|
"grunt-cli": "^1.2.0",
|
||||||
|
"grunt-contrib-concat": "^1.0.1",
|
||||||
|
"grunt-contrib-copy": "^1.0.0",
|
||||||
|
"grunt-contrib-jshint": "^1.1.0",
|
||||||
|
"grunt-contrib-watch": "^1.0.0",
|
||||||
|
"grunt-exec": "^3.0.0",
|
||||||
|
"grunt-gitinfo": "^0.1.7",
|
||||||
|
"grunt-jscs": "^3.0.1",
|
||||||
|
"grunt-sass": "^2.0.0",
|
||||||
|
"mocha": "^4.1.0",
|
||||||
|
"node-sass-import-once": "^1.2.0",
|
||||||
|
"nyc": "^11.4.1",
|
||||||
|
"spectron": "^3.7.2",
|
||||||
|
"tmp": "^0.0.33"
|
||||||
},
|
},
|
||||||
"build": {
|
"build": {
|
||||||
"appId": "org.whispersystems.signal-desktop",
|
"appId": "org.whispersystems.signal-desktop",
|
||||||
|
@ -169,27 +204,5 @@
|
||||||
"node_modules/spellchecker/build/Release/*.node",
|
"node_modules/spellchecker/build/Release/*.node",
|
||||||
"node_modules/websocket/build/Release/*.node"
|
"node_modules/websocket/build/Release/*.node"
|
||||||
]
|
]
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"bunyan": "^1.8.12",
|
|
||||||
"config": "^1.28.1",
|
|
||||||
"electron-config": "^1.0.0",
|
|
||||||
"electron-editor-context-menu": "^1.1.1",
|
|
||||||
"electron-updater": "^2.17.6",
|
|
||||||
"emoji-datasource": "4.0.0",
|
|
||||||
"emoji-datasource-apple": "4.0.0",
|
|
||||||
"emoji-js": "^3.4.0",
|
|
||||||
"emoji-panel": "https://github.com/scottnonnenberg/emoji-panel.git#v0.5.5",
|
|
||||||
"google-libphonenumber": "^3.0.7",
|
|
||||||
"lodash": "^4.17.4",
|
|
||||||
"mkdirp": "^0.5.1",
|
|
||||||
"node-fetch": "https://github.com/scottnonnenberg/node-fetch.git#3e5f51e08c647ee5f20c43b15cf2d352d61c36b4",
|
|
||||||
"node-notifier": "^5.1.2",
|
|
||||||
"os-locale": "^2.1.0",
|
|
||||||
"proxy-agent": "^2.1.0",
|
|
||||||
"rimraf": "^2.6.2",
|
|
||||||
"semver": "^5.4.1",
|
|
||||||
"spellchecker": "^3.4.4",
|
|
||||||
"websocket": "^1.0.25"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,9 @@ const fs = require('fs');
|
||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
|
|
||||||
const packageJson = require('./package.json');
|
const packageJson = require('./package.json');
|
||||||
const version = packageJson.version;
|
|
||||||
|
|
||||||
|
const { version } = packageJson;
|
||||||
const beta = /beta/;
|
const beta = /beta/;
|
||||||
|
|
||||||
// You might be wondering why this file is necessary. It comes down to our desire to allow
|
// You might be wondering why this file is necessary. It comes down to our desire to allow
|
||||||
|
@ -12,7 +14,7 @@ const beta = /beta/;
|
||||||
// adding the ${channel} macro to these values, but Electron-Builder didn't like that.
|
// adding the ${channel} macro to these values, but Electron-Builder didn't like that.
|
||||||
|
|
||||||
if (!beta.test(version)) {
|
if (!beta.test(version)) {
|
||||||
return;
|
process.exit();
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('prepare_build: updating package.json for beta build');
|
console.log('prepare_build: updating package.json for beta build');
|
||||||
|
@ -36,13 +38,12 @@ const PRODUCTION_STARTUP_WM_CLASS = 'Signal';
|
||||||
const BETA_STARTUP_WM_CLASS = 'Signal Beta';
|
const BETA_STARTUP_WM_CLASS = 'Signal Beta';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// -------
|
// -------
|
||||||
|
|
||||||
function checkValue(object, objectPath, expected) {
|
function checkValue(object, objectPath, expected) {
|
||||||
const actual = _.get(object, objectPath)
|
const actual = _.get(object, objectPath);
|
||||||
if (actual !== expected) {
|
if (actual !== expected) {
|
||||||
throw new Error(objectPath + ' was ' + actual + '; expected ' + expected);
|
throw new Error(`${objectPath} was ${actual}; expected ${expected}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
17
test/.eslintrc.js
Normal file
17
test/.eslintrc.js
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
// For reference: https://github.com/airbnb/javascript
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
env: {
|
||||||
|
mocha: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
rules: {
|
||||||
|
// We still get the value of this rule, it just allows for dev deps
|
||||||
|
'import/no-extraneous-dependencies': ['error', {
|
||||||
|
devDependencies: true
|
||||||
|
}],
|
||||||
|
|
||||||
|
// We want to keep each test structured the same, even if its contents are tiny
|
||||||
|
'arrow-body-style': 'off',
|
||||||
|
}
|
||||||
|
};
|
271
test/server/app/logging_test.js
Normal file
271
test/server/app/logging_test.js
Normal file
|
@ -0,0 +1,271 @@
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
const tmp = require('tmp');
|
||||||
|
const { expect } = require('chai');
|
||||||
|
|
||||||
|
const {
|
||||||
|
eliminateOutOfDateFiles,
|
||||||
|
eliminateOldEntries,
|
||||||
|
isLineAfterDate,
|
||||||
|
fetchLog,
|
||||||
|
fetch,
|
||||||
|
} = require('../../../app/logging');
|
||||||
|
|
||||||
|
describe('app/logging', () => {
|
||||||
|
let basePath;
|
||||||
|
let tmpDir;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
tmpDir = tmp.dirSync({
|
||||||
|
unsafeCleanup: true,
|
||||||
|
});
|
||||||
|
basePath = tmpDir.name;
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach((done) => {
|
||||||
|
// we need the unsafe option to recursively remove the directory
|
||||||
|
tmpDir.removeCallback(done);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#isLineAfterDate', () => {
|
||||||
|
it('returns false if falsy', () => {
|
||||||
|
const actual = isLineAfterDate('', new Date());
|
||||||
|
expect(actual).to.equal(false);
|
||||||
|
});
|
||||||
|
it('returns false if invalid JSON', () => {
|
||||||
|
const actual = isLineAfterDate('{{}', new Date());
|
||||||
|
expect(actual).to.equal(false);
|
||||||
|
});
|
||||||
|
it('returns false if date is invalid', () => {
|
||||||
|
const line = JSON.stringify({ time: '2018-01-04T19:17:05.014Z' });
|
||||||
|
const actual = isLineAfterDate(line, new Date('try6'));
|
||||||
|
expect(actual).to.equal(false);
|
||||||
|
});
|
||||||
|
it('returns false if log time is invalid', () => {
|
||||||
|
const line = JSON.stringify({ time: 'try7' });
|
||||||
|
const date = new Date('2018-01-04T19:17:00.000Z');
|
||||||
|
const actual = isLineAfterDate(line, date);
|
||||||
|
expect(actual).to.equal(false);
|
||||||
|
});
|
||||||
|
it('returns false if date before provided date', () => {
|
||||||
|
const line = JSON.stringify({ time: '2018-01-04T19:17:00.000Z' });
|
||||||
|
const date = new Date('2018-01-04T19:17:05.014Z');
|
||||||
|
const actual = isLineAfterDate(line, date);
|
||||||
|
expect(actual).to.equal(false);
|
||||||
|
});
|
||||||
|
it('returns true if date is after provided date', () => {
|
||||||
|
const line = JSON.stringify({ time: '2018-01-04T19:17:05.014Z' });
|
||||||
|
const date = new Date('2018-01-04T19:17:00.000Z');
|
||||||
|
const actual = isLineAfterDate(line, date);
|
||||||
|
expect(actual).to.equal(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#eliminateOutOfDateFiles', () => {
|
||||||
|
it('deletes an empty file', () => {
|
||||||
|
const date = new Date();
|
||||||
|
const log = '\n';
|
||||||
|
const target = path.join(basePath, 'log.log');
|
||||||
|
fs.writeFileSync(target, log);
|
||||||
|
|
||||||
|
return eliminateOutOfDateFiles(basePath, date).then(() => {
|
||||||
|
expect(fs.existsSync(target)).to.equal(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('deletes a file with invalid JSON lines', () => {
|
||||||
|
const date = new Date();
|
||||||
|
const log = '{{}\n';
|
||||||
|
const target = path.join(basePath, 'log.log');
|
||||||
|
fs.writeFileSync(target, log);
|
||||||
|
|
||||||
|
return eliminateOutOfDateFiles(basePath, date).then(() => {
|
||||||
|
expect(fs.existsSync(target)).to.equal(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('deletes a file with all dates before provided date', () => {
|
||||||
|
const date = new Date('2018-01-04T19:17:05.014Z');
|
||||||
|
const contents = [
|
||||||
|
JSON.stringify({ time: '2018-01-04T19:17:00.014Z' }),
|
||||||
|
JSON.stringify({ time: '2018-01-04T19:17:01.014Z' }),
|
||||||
|
JSON.stringify({ time: '2018-01-04T19:17:02.014Z' }),
|
||||||
|
JSON.stringify({ time: '2018-01-04T19:17:03.014Z' }),
|
||||||
|
].join('\n');
|
||||||
|
const target = path.join(basePath, 'log.log');
|
||||||
|
fs.writeFileSync(target, contents);
|
||||||
|
|
||||||
|
return eliminateOutOfDateFiles(basePath, date).then(() => {
|
||||||
|
expect(fs.existsSync(target)).to.equal(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('keeps a file with first line date before provided date', () => {
|
||||||
|
const date = new Date('2018-01-04T19:16:00.000Z');
|
||||||
|
const contents = [
|
||||||
|
JSON.stringify({ time: '2018-01-04T19:17:00.014Z' }),
|
||||||
|
JSON.stringify({ time: '2018-01-04T19:17:01.014Z' }),
|
||||||
|
JSON.stringify({ time: '2018-01-04T19:17:02.014Z' }),
|
||||||
|
JSON.stringify({ time: '2018-01-04T19:17:03.014Z' }),
|
||||||
|
].join('\n');
|
||||||
|
const target = path.join(basePath, 'log.log');
|
||||||
|
fs.writeFileSync(target, contents);
|
||||||
|
|
||||||
|
return eliminateOutOfDateFiles(basePath, date).then(() => {
|
||||||
|
expect(fs.existsSync(target)).to.equal(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('keeps a file with last line date before provided date', () => {
|
||||||
|
const date = new Date('2018-01-04T19:17:01.000Z');
|
||||||
|
const contents = [
|
||||||
|
JSON.stringify({ time: '2018-01-04T19:17:00.014Z' }),
|
||||||
|
JSON.stringify({ time: '2018-01-04T19:17:01.014Z' }),
|
||||||
|
JSON.stringify({ time: '2018-01-04T19:17:02.014Z' }),
|
||||||
|
JSON.stringify({ time: '2018-01-04T19:17:03.014Z' }),
|
||||||
|
].join('\n');
|
||||||
|
const target = path.join(basePath, 'log.log');
|
||||||
|
fs.writeFileSync(target, contents);
|
||||||
|
|
||||||
|
return eliminateOutOfDateFiles(basePath, date).then(() => {
|
||||||
|
expect(fs.existsSync(target)).to.equal(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#eliminateOldEntries', () => {
|
||||||
|
it('eliminates all non-parsing entries', () => {
|
||||||
|
const date = new Date('2018-01-04T19:17:01.000Z');
|
||||||
|
const contents = [
|
||||||
|
'random line',
|
||||||
|
JSON.stringify({ time: '2018-01-04T19:17:01.014Z' }),
|
||||||
|
JSON.stringify({ time: '2018-01-04T19:17:02.014Z' }),
|
||||||
|
JSON.stringify({ time: '2018-01-04T19:17:03.014Z' }),
|
||||||
|
].join('\n');
|
||||||
|
const expected = [
|
||||||
|
JSON.stringify({ time: '2018-01-04T19:17:01.014Z' }),
|
||||||
|
JSON.stringify({ time: '2018-01-04T19:17:02.014Z' }),
|
||||||
|
JSON.stringify({ time: '2018-01-04T19:17:03.014Z' }),
|
||||||
|
].join('\n');
|
||||||
|
|
||||||
|
const target = path.join(basePath, 'log.log');
|
||||||
|
const files = [{
|
||||||
|
path: target,
|
||||||
|
}];
|
||||||
|
|
||||||
|
fs.writeFileSync(target, contents);
|
||||||
|
|
||||||
|
return eliminateOldEntries(files, date).then(() => {
|
||||||
|
expect(fs.readFileSync(target, 'utf8')).to.equal(`${expected}\n`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('preserves all lines if before target date', () => {
|
||||||
|
const date = new Date('2018-01-04T19:17:03.000Z');
|
||||||
|
const contents = [
|
||||||
|
'random line',
|
||||||
|
JSON.stringify({ time: '2018-01-04T19:17:01.014Z' }),
|
||||||
|
JSON.stringify({ time: '2018-01-04T19:17:02.014Z' }),
|
||||||
|
JSON.stringify({ time: '2018-01-04T19:17:03.014Z' }),
|
||||||
|
].join('\n');
|
||||||
|
const expected = [
|
||||||
|
JSON.stringify({ time: '2018-01-04T19:17:03.014Z' }),
|
||||||
|
].join('\n');
|
||||||
|
|
||||||
|
const target = path.join(basePath, 'log.log');
|
||||||
|
const files = [{
|
||||||
|
path: target,
|
||||||
|
}];
|
||||||
|
|
||||||
|
fs.writeFileSync(target, contents);
|
||||||
|
|
||||||
|
return eliminateOldEntries(files, date).then(() => {
|
||||||
|
expect(fs.readFileSync(target, 'utf8')).to.equal(`${expected}\n`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#fetchLog', () => {
|
||||||
|
it('returns error if file does not exist', () => {
|
||||||
|
const target = 'random_file';
|
||||||
|
return fetchLog(target).then(() => {
|
||||||
|
throw new Error('Expected an error!');
|
||||||
|
}, (error) => {
|
||||||
|
expect(error).to.have.property('message').that.match(/random_file/);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('returns empty array if file has no valid JSON lines', () => {
|
||||||
|
const contents = 'line 1\nline2\n';
|
||||||
|
const expected = [];
|
||||||
|
const target = path.join(basePath, 'test.log');
|
||||||
|
|
||||||
|
fs.writeFileSync(target, contents);
|
||||||
|
|
||||||
|
return fetchLog(target).then((result) => {
|
||||||
|
expect(result).to.deep.equal(expected);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('returns just three fields in each returned line', () => {
|
||||||
|
const contents = [
|
||||||
|
JSON.stringify({
|
||||||
|
one: 1,
|
||||||
|
two: 2,
|
||||||
|
level: 1,
|
||||||
|
time: 2,
|
||||||
|
msg: 3,
|
||||||
|
}),
|
||||||
|
JSON.stringify({
|
||||||
|
one: 1,
|
||||||
|
two: 2,
|
||||||
|
level: 2,
|
||||||
|
time: 3,
|
||||||
|
msg: 4,
|
||||||
|
}),
|
||||||
|
'',
|
||||||
|
].join('\n');
|
||||||
|
const expected = [{
|
||||||
|
level: 1,
|
||||||
|
time: 2,
|
||||||
|
msg: 3,
|
||||||
|
}, {
|
||||||
|
level: 2,
|
||||||
|
time: 3,
|
||||||
|
msg: 4,
|
||||||
|
}];
|
||||||
|
|
||||||
|
const target = path.join(basePath, 'test.log');
|
||||||
|
|
||||||
|
fs.writeFileSync(target, contents);
|
||||||
|
|
||||||
|
return fetchLog(target).then((result) => {
|
||||||
|
expect(result).to.deep.equal(expected);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#fetch', () => {
|
||||||
|
it('returns single entry if no files', () => {
|
||||||
|
return fetch(basePath).then((results) => {
|
||||||
|
expect(results).to.have.length(1);
|
||||||
|
expect(results[0].msg).to.match(/Loaded this list/);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('returns sorted entries from all files', () => {
|
||||||
|
const first = [
|
||||||
|
JSON.stringify({ msg: 2, time: '2018-01-04T19:17:05.014Z' }),
|
||||||
|
'',
|
||||||
|
].join('\n');
|
||||||
|
const second = [
|
||||||
|
JSON.stringify({ msg: 1, time: '2018-01-04T19:17:00.014Z' }),
|
||||||
|
JSON.stringify({ msg: 3, time: '2018-01-04T19:18:00.014Z' }),
|
||||||
|
'',
|
||||||
|
].join('\n');
|
||||||
|
|
||||||
|
fs.writeFileSync(path.join(basePath, 'first.log'), first);
|
||||||
|
fs.writeFileSync(path.join(basePath, 'second.log'), second);
|
||||||
|
|
||||||
|
return fetch(basePath).then((results) => {
|
||||||
|
expect(results).to.have.length(4);
|
||||||
|
expect(results[0].msg).to.equal(1);
|
||||||
|
expect(results[1].msg).to.equal(2);
|
||||||
|
expect(results[2].msg).to.equal(3);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
Loading…
Add table
Add a link
Reference in a new issue