Merge branch 'master' of github.com:symphonyoss/SymphonyElectron

This commit is contained in:
Vishwas Shashidhar 2021-02-01 14:54:38 +05:30
commit 8ab674443c
7 changed files with 165 additions and 25 deletions

View File

@ -153,6 +153,7 @@ export const Menu = {
export const crashReporter = {
start: jest.fn(),
getLastCrashReport: jest.fn(),
};
const getCurrentWindow = jest.fn(() => {

View File

@ -1,9 +1,16 @@
export interface IAnalyticsData {
element: AnalyticsElements;
action_type: MenuActionTypes | ScreenSnippetActionTypes;
action_type?: MenuActionTypes | ScreenSnippetActionTypes;
action_result?: AnalyticsActions;
}
export interface ICrashData extends IAnalyticsData {
process: SDACrashProcess;
crashCause: string;
windowName: string;
miniDump?: string;
}
export enum MenuActionTypes {
AUTO_LAUNCH_ON_START_UP = 'auto_launch_on_start_up',
ALWAYS_ON_TOP = 'always_on_top',
@ -30,6 +37,13 @@ export enum AnalyticsActions {
export enum AnalyticsElements {
MENU = 'Menu',
SCREEN_CAPTURE_ANNOTATE = 'screen_capture_annotate',
SDA_CRASH = 'sda_crash',
}
export enum SDACrashProcess {
MAIN = 'main',
RENDERER = 'renderer',
GPU = 'gpu',
}
const MAX_EVENT_QUEUE_LENGTH = 50;
@ -51,9 +65,12 @@ class Analytics {
return;
}
if (this.analyticsEventQueue && this.analyticsEventQueue.length > 0) {
this.analyticsEventQueue.forEach((events) => {
this.analyticsEventQueue.forEach((eventData) => {
if (this.preloadWindow && !this.preloadWindow.isDestroyed()) {
this.preloadWindow.send(analyticsCallback, events);
if (eventData.element === AnalyticsElements.SDA_CRASH) {
eventData = eventData as ICrashData;
}
this.preloadWindow.send(analyticsCallback, eventData);
}
});
this.resetAnalytics();
@ -66,12 +83,15 @@ class Analytics {
* @param eventData {IAnalyticsData}
*/
public track(eventData: IAnalyticsData): void {
if (eventData.element === AnalyticsElements.SDA_CRASH) {
eventData = eventData as ICrashData;
}
if (this.preloadWindow && !this.preloadWindow.isDestroyed()) {
this.preloadWindow.send(analyticsCallback, eventData);
return;
}
this.analyticsEventQueue.push(eventData);
// don't store more than 50 msgs. keep most recent log msgs.
// don't store more than specified limit. keep most recent events.
if (this.analyticsEventQueue.length > MAX_EVENT_QUEUE_LENGTH) {
this.analyticsEventQueue.shift();
}

View File

@ -1,4 +1,4 @@
import { BrowserWindow, WebContents } from 'electron';
import { BrowserWindow, crashReporter, WebContents } from 'electron';
import { parse as parseQuerystring } from 'querystring';
import { format, parse, Url } from 'url';
@ -8,6 +8,7 @@ import { logger } from '../common/logger';
import { getGuid } from '../common/utils';
import { whitelistHandler } from '../common/whitelist-handler';
import { config } from './config-handler';
import crashHandler from './crash-handler';
import {
handlePermissionRequests,
monitorWindowActions,
@ -262,6 +263,14 @@ export const handleChildWindow = (webContents: WebContents): void => {
removeWindowEventListener(browserWin);
});
crashReporter.start({
submitURL: '',
uploadToServer: false,
ignoreSystemCrashHandler: false,
});
crashHandler.handleRendererCrash(browserWin);
if (browserWin.webContents) {
// validate link and create a child window or open in browser
handleChildWindow(browserWin.webContents);

110
src/app/crash-handler.ts Normal file
View File

@ -0,0 +1,110 @@
import { app, crashReporter, Details, dialog } from 'electron';
import { i18n } from '../common/i18n';
import { logger } from '../common/logger';
import {
analytics,
AnalyticsElements,
ICrashData,
SDACrashProcess,
} from './analytics-handler';
import { ICustomBrowserWindow } from './window-handler';
import { windowExists } from './window-utils';
class CrashHandler {
/**
* Shows a message to the user to take further action
* @param browserWindow Browser Window to show the dialog on
* @private
*/
private static async showMessageToUser(browserWindow: ICustomBrowserWindow) {
if (!browserWindow || !windowExists(browserWindow)) {
return;
}
const { response } = await dialog.showMessageBox({
type: 'error',
title: i18n.t('Renderer Process Crashed')(),
message: i18n.t(
'Oops! Looks like we have had a crash. Please reload or close this window.',
)(),
buttons: ['Reload', 'Close'],
});
response === 0 ? browserWindow.reload() : browserWindow.close();
}
/**
* Handles a GPU crash event
* @private
*/
private static handleGpuCrash() {
app.on('gpu-process-crashed', (_event: Event, _killed: boolean) => {
logger.info(`crash-handler: GPU process crashed.`);
const eventData: ICrashData = {
element: AnalyticsElements.SDA_CRASH,
process: SDACrashProcess.GPU,
windowName: 'main',
crashCause: _killed ? 'killed' : 'crashed',
};
analytics.track(eventData);
});
}
/**
* Handles a main process crash event
* @private
*/
private static handleMainProcessCrash() {
const lastCrash = crashReporter.getLastCrashReport();
if (!lastCrash) {
logger.info(`crash-handler: No crashes found for main process`);
return;
}
const eventData: ICrashData = {
element: AnalyticsElements.SDA_CRASH,
process: SDACrashProcess.MAIN,
windowName: 'main',
crashCause: lastCrash.id,
};
analytics.track(eventData);
}
constructor() {
CrashHandler.handleMainProcessCrash();
CrashHandler.handleGpuCrash();
}
/**
* Handles a crash event for a browser window
* @param browserWindow Browser Window on which the crash should be handled
*/
public handleRendererCrash(browserWindow: ICustomBrowserWindow) {
browserWindow.webContents.on(
'render-process-gone',
async (_event: Event, details: Details) => {
logger.info(`crash-handler: Renderer process for ${browserWindow.winName} crashed.
Reason is ${details.reason}`);
const eventData: ICrashData = {
element: AnalyticsElements.SDA_CRASH,
process: SDACrashProcess.RENDERER,
windowName: browserWindow.winName,
crashCause: details.reason,
};
switch (details.reason) {
case 'abnormal-exit':
case 'crashed':
case 'integrity-failure':
case 'launch-failed':
case 'oom':
await CrashHandler.showMessageToUser(browserWindow);
analytics.track(eventData);
break;
default:
break;
}
},
);
}
}
const crashHandler = new CrashHandler();
export default crashHandler;

View File

@ -1,4 +1,4 @@
import { app } from 'electron';
import { app, crashReporter } from 'electron';
import * as path from 'path';
import { isDevEnv, isNodeEnv } from '../common/env';
@ -39,6 +39,14 @@ if (userDataPath) {
logger.info(`init: Fetch user data path`, app.getPath('userData'));
logger.info(`Crashes directory: ${app.getPath('crashDumps')}`);
crashReporter.start({
submitURL: '',
uploadToServer: false,
ignoreSystemCrashHandler: false,
});
logger.info(`Crash Reporter started`);
// Log app statistics
appStats.logStats();

View File

@ -1,5 +1,5 @@
import * as archiver from 'archiver';
import { app, BrowserWindow, crashReporter, dialog, shell } from 'electron';
import { app, BrowserWindow, dialog, shell } from 'electron';
import * as fs from 'fs';
import * as path from 'path';
@ -184,7 +184,7 @@ export const exportLogs = (): void => {
*/
export const exportCrashDumps = (): void => {
const FILE_EXTENSIONS = isMac ? ['.dmp'] : ['.dmp', '.txt'];
const crashesDirectory = (crashReporter as any).getCrashesDirectory();
const crashesDirectory = app.getPath('crashDumps');
const source = isMac ? crashesDirectory + '/completed' : crashesDirectory;
const focusedWindow = BrowserWindow.getFocusedWindow();

View File

@ -40,6 +40,7 @@ import {
IConfig,
IGlobalConfig,
} from './config-handler';
import crashHandler from './crash-handler';
import { SpellChecker } from './spell-check-handler';
import { checkIfBuildExpired } from './ttl-handler';
import { versionHandler } from './version-handler';
@ -232,23 +233,6 @@ export class WindowHandler {
app.getLocale()) as LocaleType;
i18n.setLocale(locale);
try {
const extra = {
podUrl: this.userConfig.url
? this.userConfig.url
: this.globalConfig.url,
process: 'main',
};
const defaultOpts = {
uploadToServer: false,
companyName: 'Symphony',
submitURL: '',
};
crashReporter.start({ ...defaultOpts, extra });
} catch (e) {
throw new Error('failed to init crash report');
}
this.listenForLoad();
}
@ -625,6 +609,14 @@ export class WindowHandler {
this.destroyAllWindows();
});
crashReporter.start({
submitURL: '',
uploadToServer: false,
ignoreSystemCrashHandler: false,
});
crashHandler.handleRendererCrash(this.mainWindow);
// Reloads the Symphony
ipcMain.on('reload-symphony', () => {
this.reloadSymphony();