Electron-401 (fixes crash reporter) (#334)

- fix crash reporter
- fix typo
- Add support to generate crash report
- Add logic to store globally and return
- PR review fixes
- Add logic to validate file mime type
This commit is contained in:
Kiran Niranjan 2018-04-11 13:13:59 +00:00 committed by Vishwas Shashidhar
parent 2de86c8003
commit 3be2986c7e
12 changed files with 276 additions and 80 deletions

View File

@ -7,6 +7,7 @@ const fs = require('fs');
const log = require('../log.js');
const logLevels = require('../enums/logLevels.js');
const buildNumber = require('../../package.json').buildNumber;
const { initCrashReporterMain, initCrashReporterRenderer } = require('../crashReporter.js');
let aboutWindow;
@ -80,9 +81,27 @@ function openAboutWindow(windowName) {
});
aboutWindow.webContents.on('did-finish-load', () => {
// initialize crash reporter
initCrashReporterMain({ process: 'about app window' });
initCrashReporterRenderer(aboutWindow, { process: 'render | about app window' });
aboutWindow.webContents.send('buildNumber', buildNumber || '0');
});
aboutWindow.webContents.on('crashed', function () {
const options = {
type: 'error',
title: 'Renderer Process Crashed',
message: 'Oops! Looks like we have had a crash.',
buttons: ['Close']
};
electron.dialog.showMessageBox(options, function () {
if (aboutWindow && !aboutWindow.isDestroyed()) {
aboutWindow.close();
}
});
});
aboutWindow.on('close', () => {
destroyWindow();
});

View File

@ -1,5 +1,5 @@
'use strict';
const { remote, ipcRenderer } = require('electron');
const { remote, ipcRenderer, crashReporter } = require('electron');
renderDom();
@ -24,4 +24,10 @@ ipcRenderer.on('buildNumber', (event, buildNumber) => {
if (versionText) {
versionText.innerHTML = version ? `Version ${version} (${version}.${buildNumber})` : 'N/A';
}
});
ipcRenderer.on('register-crash-reporter', (event, arg) => {
if (arg && typeof arg === 'object') {
crashReporter.start(arg);
}
});

View File

@ -8,6 +8,7 @@ const fs = require('fs');
const log = require('../log.js');
const logLevels = require('../enums/logLevels.js');
const { isMac } = require('../utils/misc');
const { initCrashReporterMain, initCrashReporterRenderer } = require('../crashReporter.js');
let basicAuthWindow;
@ -95,10 +96,26 @@ function openBasicAuthWindow(windowName, hostname, isValidCredentials, clearSett
});
basicAuthWindow.webContents.on('did-finish-load', () => {
// initialize crash reporter
initCrashReporterMain({ process: 'basic auth window' });
initCrashReporterRenderer(basicAuthWindow, { process: 'render | basic auth window' });
basicAuthWindow.webContents.send('hostname', hostname);
basicAuthWindow.webContents.send('isValidCredentials', isValidCredentials);
});
basicAuthWindow.webContents.on('crashed', function () {
const options = {
type: 'error',
title: 'Renderer Process Crashed',
message: 'Oops! Looks like we have had a crash.',
buttons: ['Close']
};
electron.dialog.showMessageBox(options, function () {
closeAuthWindow(true);
});
});
basicAuthWindow.on('close', () => {
destroyWindow();
});

View File

@ -1,6 +1,5 @@
'use strict';
const electron = require('electron');
const ipc = electron.ipcRenderer;
const { ipcRenderer, crashReporter } = require('electron');
renderDom();
@ -26,7 +25,7 @@ function loadContent() {
if (cancel) {
cancel.addEventListener('click', () => {
ipc.send('close-basic-auth');
ipcRenderer.send('close-basic-auth');
});
}
}
@ -39,14 +38,14 @@ function submitForm() {
let password = document.getElementById('password').value;
if (username && password) {
ipc.send('login', { username, password });
ipcRenderer.send('login', { username, password });
}
}
/**
* Updates the hosts name
*/
ipc.on('hostname', (event, host) => {
ipcRenderer.on('hostname', (event, host) => {
let hostname = document.getElementById('hostname');
if (hostname){
@ -57,10 +56,16 @@ ipc.on('hostname', (event, host) => {
/**
* Triggered if user credentials are invalid
*/
ipc.on('isValidCredentials', (event, isValidCredentials) => {
ipcRenderer.on('isValidCredentials', (event, isValidCredentials) => {
let credentialsError = document.getElementById('credentialsError');
if (credentialsError){
credentialsError.style.display = isValidCredentials ? 'none' : 'block'
}
});
ipcRenderer.on('register-crash-reporter', (event, arg) => {
if (arg && typeof arg === 'object') {
crashReporter.start(arg);
}
});

85
js/crashReporter.js Normal file
View File

@ -0,0 +1,85 @@
const { crashReporter } = require('electron');
const { getMultipleConfigField } = require('./config.js');
const log = require('./log.js');
const logLevels = require('./enums/logLevels.js');
const configFields = ['url', 'crashReporter'];
let crashReporterData;
/**
* Method that returns all the required field for crash reporter
*
* @param extras {object}
* @return {Promise<any>}
*/
function getCrashReporterConfig(extras) {
return new Promise((resolve, reject) => {
if (crashReporterData && crashReporterData.companyName) {
crashReporterData.extra = Object.assign(crashReporterData.extra, extras);
resolve(crashReporterData);
return;
}
getMultipleConfigField(configFields)
.then((data) => {
if (!data && !data.crashReporter && !data.crashReporter.companyName) {
reject('Company name cannot be empty');
return;
}
crashReporterData = {
companyName: data.crashReporter.companyName,
submitURL: data.crashReporter.submitURL,
uploadToServer: data.crashReporter.uploadToServer,
extra: Object.assign(
{podUrl: data.url},
extras
)
};
resolve(crashReporterData);
})
.catch((err) => log.send(
logLevels.ERROR,
'Unable to initialize crash reporter failed to read config file. Error is -> ' + err
));
})
}
function initCrashReporterMain(extras) {
getCrashReporterConfig(extras).then((mainCrashReporterData) => {
try {
crashReporter.start(mainCrashReporterData);
} catch (err) {
log.send(logLevels.ERROR, 'Failed to start crash reporter main process. Error is -> ' + err);
}
}).catch((err) => log.send(
logLevels.ERROR,
'Unable to initialize crash reporter for main process. Error is -> ' + err
));
}
/**
* Method to initialize crash reporter for renderer process
*
* @param browserWindow {Electron.BrowserWindow}
* @param extras {Object}
*/
function initCrashReporterRenderer(browserWindow, extras) {
if (browserWindow && browserWindow.webContents && !browserWindow.isDestroyed()) {
getCrashReporterConfig(extras).then((rendererCrashReporterData) => {
browserWindow.webContents.send('register-crash-reporter', rendererCrashReporterData);
}).catch((err) => log.send(
logLevels.ERROR,
'Unable to initialize crash reporter for renderer process. Error is -> ' + err
));
}
}
module.exports = {
initCrashReporterMain,
initCrashReporterRenderer,
};

View File

@ -8,6 +8,7 @@ const fs = require('fs');
const log = require('../log.js');
const logLevels = require('../enums/logLevels.js');
const { isMac, isWindowsOS } = require('./../utils/misc.js');
const { initCrashReporterMain, initCrashReporterRenderer } = require('../crashReporter.js');
let screenPickerWindow;
let preloadWindow;
@ -91,9 +92,27 @@ function openScreenPickerWindow(eventSender, sources, id) {
});
screenPickerWindow.webContents.on('did-finish-load', () => {
// initialize crash reporter
initCrashReporterMain({ process: 'desktop capture window' });
initCrashReporterRenderer(screenPickerWindow, { process: 'render | desktop capture window' });
screenPickerWindow.webContents.send('desktop-capturer-sources', sources, isWindowsOS);
});
screenPickerWindow.webContents.on('crashed', function () {
const options = {
type: 'error',
title: 'Renderer Process Crashed',
message: 'Oops! Looks like we have had a crash.',
buttons: ['Close']
};
electron.dialog.showMessageBox(options, function () {
if (screenPickerWindow && !screenPickerWindow.isDestroyed()) {
screenPickerWindow.close();
}
});
});
screenPickerWindow.on('close', () => {
destroyWindow();
});

View File

@ -1,5 +1,5 @@
'use strict';
const { ipcRenderer } = require('electron');
const { ipcRenderer, crashReporter } = require('electron');
const screenRegExp = new RegExp(/^Screen \d+$/gmi);
@ -265,4 +265,10 @@ function updateSelectedSource(index) {
function closeScreenPickerWindow() {
document.removeEventListener('keyUp', handleKeyUpPress.bind(this), true);
ipcRenderer.send('close-screen-picker');
}
}
ipcRenderer.on('register-crash-reporter', (event, arg) => {
if (arg && typeof arg === 'object') {
crashReporter.start(arg);
}
});

View File

@ -129,7 +129,8 @@ const template = [{
{
label: isMac ? 'Show Logs in Finder' : 'Show Logs in Explorer',
click() {
const FILE_EXTENSIONS = [ '.log' ];
const MAC_LOGS_PATH = '/Library/Logs/Symphony/';
const WINDOWS_LOGS_PATH = '\\AppData\\Roaming\\Symphony\\';
@ -146,7 +147,7 @@ const template = [{
let destination = electron.app.getPath('downloads') + destPath + timestamp + '.zip';
archiveHandler.generateArchiveForDirectory(source, destination)
archiveHandler.generateArchiveForDirectory(source, destination, FILE_EXTENSIONS)
.then(() => {
electron.shell.showItemInFolder(destination);
})
@ -157,10 +158,29 @@ const template = [{
}
},
{
label: 'Open Crashes Directory',
label: isMac ? 'Show crash dump in Finder' : 'Show crash dump in Explorer',
click() {
const crashesDirectory = electron.crashReporter.getCrashesDirectory() + '/completed';
electron.shell.showItemInFolder(crashesDirectory);
const FILE_EXTENSIONS = isMac ? [ '.dmp' ] : [ '.dmp', '.txt' ];
const crashesDirectory = electron.crashReporter.getCrashesDirectory();
let source = isMac ? crashesDirectory + '/completed' : crashesDirectory;
if (!fs.existsSync(source) || fs.readdirSync(source).length === 0) {
electron.dialog.showErrorBox('Failed!', 'No crashes available to share');
return;
}
let destPath = isMac ? '/crashes_symphony_' : '\\crashes_symphony_';
let timestamp = new Date().getTime();
let destination = electron.app.getPath('downloads') + destPath + timestamp + '.zip';
archiveHandler.generateArchiveForDirectory(source, destination, FILE_EXTENSIONS)
.then(() => {
electron.shell.showItemInFolder(destination);
})
.catch((err) => {
electron.dialog.showErrorBox('Failed!', 'Unable to generate crash report due to -> ' + err);
});
}
}
]

View File

@ -130,19 +130,6 @@ function createAPI() {
*/
ScreenSnippet: remote.require('./screenSnippet/index.js').ScreenSnippet,
/**
* Provides API to crash the renderer process that calls this function
* Is only used for demos.
*/
crashRendererProcess: function () {
// For practical purposes, we don't allow
// this method to work in non-dev environments
if (!process.env.ELECTRON_DEV) {
return;
}
process.crash();
},
/**
* Provides api for client side searching
* using the SymphonySearchEngine library
@ -394,9 +381,13 @@ function createAPI() {
});
/**
* an event triggered by the main process to start crash reporter
* in the render thread
*/
local.ipcRenderer.on('register-crash-reporter', (event, arg) => {
if (arg) {
crashReporter.start({companyName: arg.companyName, submitURL: arg.submitURL, uploadToServer: arg.uploadToServer, extra: {'process': arg.process, podUrl: arg.podUrl}});
if (arg && typeof arg === 'object') {
crashReporter.start(arg);
}
});

View File

@ -3,11 +3,25 @@
const fs = require('fs');
const path = require('path');
const archiver = require('archiver');
const log = require('../log.js');
const logLevels = require('../enums/logLevels.js');
const mmm = require('mmmagic');
const Magic = mmm.Magic;
const magic = new Magic(mmm.MAGIC_MIME_TYPE);
function generateArchiveForDirectory(source, destination) {
/**
* Archives files in the source directory
* that matches the given file extension
*
* @param source {String} source path
* @param destination {String} destination path
* @param fileExtensions {Array} array of file ext
* @return {Promise<any>}
*/
function generateArchiveForDirectory(source, destination, fileExtensions) {
return new Promise((resolve, reject) => {
let output = fs.createWriteStream(destination);
let archive = archiver('zip', {zlib: {level: 9}});
@ -20,20 +34,55 @@ function generateArchiveForDirectory(source, destination) {
});
archive.pipe(output);
let files = fs.readdirSync(source);
files.forEach((file) => {
if (path.extname(file) === '.log') {
archive.file(source + '/' + file, { name: 'logs/' + file });
}
});
archive.finalize();
let filtered = files.filter((file) => fileExtensions.indexOf(path.extname(file)) !== -1);
mapMimeType(filtered, source)
.then((mappedData) => {
if (mappedData.length > 0) {
mappedData.map((data) => {
switch (data.mimeType) {
case 'text/plain':
if (path.extname(data.file) === '.txt') {
archive.file(source + '/' + data.file, { name: 'crashes/' + data.file });
} else {
archive.file(source + '/' + data.file, { name: 'logs/' + data.file });
}
break;
case 'application/x-dmp':
archive.file(source + '/' + data.file, { name: 'crashes/' + data.file });
break;
default:
break;
}
});
}
archive.finalize();
})
.catch((err) => {
log.send(logLevels.ERROR, 'Failed to find mime type. Error is -> ' + err);
});
});
}
function mapMimeType(files, source) {
return Promise.all(files.map((file) => {
return new Promise((resolve, reject) => {
return magic.detectFile(source + '/' + file, (err, result) => {
if (err) {
return reject(err);
}
return resolve({file: file, mimeType: result});
});
});
}))
.then((data) => data)
.catch((err) => log.send(logLevels.ERROR, 'Failed to find mime type. Error is -> ' + err));
}
module.exports = {
generateArchiveForDirectory: generateArchiveForDirectory
};

View File

@ -4,7 +4,6 @@ const fs = require('fs');
const electron = require('electron');
const app = electron.app;
const globalShortcut = electron.globalShortcut;
const crashReporter = electron.crashReporter;
const BrowserWindow = electron.BrowserWindow;
const path = require('path');
const nodeURL = require('url');
@ -24,6 +23,7 @@ const { getConfigField, updateConfigField, getGlobalConfigField } = require('./c
const { isMac, isNodeEnv, isWindows10, isWindowsOS } = require('./utils/misc');
const { deleteIndexFolder } = require('./search/search.js');
const { isWhitelisted } = require('./utils/whitelistHandler');
const { initCrashReporterMain, initCrashReporterRenderer } = require('./crashReporter.js');
// show dialog when certificate errors occur
require('./dialogs/showCertError.js');
@ -188,6 +188,10 @@ function doCreateMainWindow(initialUrl, initialBounds, isCustomTitleBar) {
// content can be cached and will still finish load but
// we might not have network connectivity, so warn the user.
mainWindow.webContents.on('did-finish-load', function () {
// Initialize crash reporter
initCrashReporterMain({ process: 'main window' });
initCrashReporterRenderer(mainWindow, { process: 'render | main window' });
url = mainWindow.webContents.getURL();
if (isCustomTitleBarEnabled) {
mainWindow.webContents.insertCSS(fs.readFileSync(path.join(__dirname, '/windowsTitleBar/style.css'), 'utf8').toString());
@ -318,23 +322,6 @@ function doCreateMainWindow(initialUrl, initialBounds, isCustomTitleBar) {
});
});
getConfigField('url')
.then(initializeCrashReporter)
.catch(app.quit);
function initializeCrashReporter(podUrl) {
getConfigField('crashReporter')
.then((crashReporterConfig) => {
log.send(logLevels.INFO, 'Initializing crash reporter on the main window!');
crashReporter.start({ companyName: crashReporterConfig.companyName, submitURL: crashReporterConfig.submitURL, uploadToServer: crashReporterConfig.uploadToServer, extra: { 'process': 'renderer / main window', podUrl: podUrl } });
log.send(logLevels.INFO, 'initialized crash reporter on the main window!');
mainWindow.webContents.send('register-crash-reporter', { companyName: crashReporterConfig.companyName, submitURL: crashReporterConfig.submitURL, uploadToServer: crashReporterConfig.uploadToServer, process: 'preload script / main window renderer' });
})
.catch((err) => {
log.send(logLevels.ERROR, 'Unable to initialize crash reporter in the main window. Error is -> ' + err);
});
}
// open external links in default browser - a tag with href='_blank' or window.open
mainWindow.webContents.on('new-window', handleNewWindow);
@ -420,19 +407,8 @@ function doCreateMainWindow(initialUrl, initialBounds, isCustomTitleBar) {
browserWin.setMenu(null);
}
getConfigField('url')
.then((podUrl) => {
getConfigField('crashReporter')
.then((crashReporterConfig) => {
crashReporter.start({ companyName: crashReporterConfig.companyName, submitURL: crashReporterConfig.submitURL, uploadToServer: crashReporterConfig.uploadToServer, extra: { 'process': 'renderer / child window', podUrl: podUrl } });
log.send(logLevels.INFO, 'initialized crash reporter on a child window!');
browserWin.webContents.send('register-crash-reporter', { companyName: crashReporterConfig.companyName, submitURL: crashReporterConfig.submitURL, uploadToServer: crashReporterConfig.uploadToServer, process: 'preload script / child window renderer' });
})
.catch((err) => {
log.send(logLevels.ERROR, 'Unable to initialize crash reporter in the child window. Error is -> ' + err);
});
})
.catch(app.quit);
initCrashReporterMain({ process: 'pop-out window' });
initCrashReporterRenderer(browserWin, { process: 'render | pop-out window' });
browserWin.winName = frameName;
browserWin.setAlwaysOnTop(alwaysOnTop);
@ -452,7 +428,7 @@ function doCreateMainWindow(initialUrl, initialBounds, isCustomTitleBar) {
browserWin.webContents.removeListener('crashed', handleChildWindowCrashEvent);
});
let handleChildWindowCrashEvent = () => {
let handleChildWindowCrashEvent = (e) => {
const options = {
type: 'error',
title: 'Renderer Process Crashed',
@ -460,14 +436,16 @@ function doCreateMainWindow(initialUrl, initialBounds, isCustomTitleBar) {
buttons: ['Reload', 'Close']
};
electron.dialog.showMessageBox(options, function (index) {
if (index === 0) {
browserWin.reload();
}
else {
browserWin.close();
}
});
let childBrowserWindow = BrowserWindow.fromWebContents(e.sender);
if (childBrowserWindow && !childBrowserWindow.isDestroyed()) {
electron.dialog.showMessageBox(childBrowserWindow, options, function (index) {
if (index === 0) {
childBrowserWindow.reload();
} else {
childBrowserWindow.close();
}
});
}
};
browserWin.webContents.on('crashed', handleChildWindowCrashEvent);

View File

@ -122,6 +122,7 @@
"lodash.isequal": "4.5.0",
"lodash.omit": "4.5.0",
"lodash.pick": "4.4.0",
"mmmagic": "0.5.0",
"parse-domain": "2.0.0",
"ref": "1.3.5",
"shell-path": "2.1.0",