electron is modified
This commit is contained in:
parent
0e0ca76947
commit
6e75194ee8
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -2,6 +2,7 @@
|
|||
|
||||
# compiled output
|
||||
/dist
|
||||
/build
|
||||
/tmp
|
||||
/out-tsc
|
||||
# Only exists if Bazel was run
|
||||
|
|
17
config/app-info.js
Normal file
17
config/app-info.js
Normal file
|
@ -0,0 +1,17 @@
|
|||
const fse = require('fs-extra');
|
||||
const path = require('path');
|
||||
|
||||
const EVENT = process.env.npm_lifecycle_event || '';
|
||||
const DEV = EVENT.includes('dev');
|
||||
|
||||
function getReplacements() {
|
||||
return {
|
||||
__DARWIN__: process.platform === 'darwin',
|
||||
__WIN32__: process.platform === 'win32',
|
||||
__LINUX__: process.platform === 'linux',
|
||||
__DEV__: DEV,
|
||||
'process.env.TEST_ENV': JSON.stringify(process.env.TEST_ENV)
|
||||
};
|
||||
}
|
||||
|
||||
exports.getReplacements = getReplacements;
|
3
config/dist-info.ts
Normal file
3
config/dist-info.ts
Normal file
|
@ -0,0 +1,3 @@
|
|||
export function getReleaseChannel() {
|
||||
return process.env.NODE_ENV || 'development';
|
||||
}
|
36
config/helpers.js
Normal file
36
config/helpers.js
Normal file
|
@ -0,0 +1,36 @@
|
|||
const path = require('path');
|
||||
const fse = require('fs-extra');
|
||||
|
||||
// Helper functions
|
||||
const _root = path.resolve(__dirname, '..');
|
||||
|
||||
function checkNodeImport(context, request, cb) {
|
||||
if (!path.isAbsolute(request) && request.charAt(0) !== '.') {
|
||||
cb(null, 'commonjs ' + request);
|
||||
return;
|
||||
}
|
||||
cb();
|
||||
}
|
||||
|
||||
function includeClientPackages(packages) {
|
||||
return function(context, request, cb) {
|
||||
if (packages && packages.indexOf(request) !== -1) {
|
||||
return cb();
|
||||
}
|
||||
return checkNodeImport(context, request, cb);
|
||||
};
|
||||
}
|
||||
|
||||
function hasProcessFlag(flag) {
|
||||
return process.argv.join('').indexOf(flag) > -1;
|
||||
}
|
||||
|
||||
function root(args) {
|
||||
args = Array.prototype.slice.call(arguments, 0);
|
||||
return path.join.apply(path, [_root].concat(args));
|
||||
}
|
||||
|
||||
exports.checkNodeImport;
|
||||
exports.includeClientPackages = includeClientPackages;
|
||||
exports.hasProcessFlag = hasProcessFlag;
|
||||
exports.root = root;
|
88
config/webpack.common.ts
Normal file
88
config/webpack.common.ts
Normal file
|
@ -0,0 +1,88 @@
|
|||
import * as path from 'path';
|
||||
import CleanWebpackPlugin from 'clean-webpack-plugin';
|
||||
import * as webpack from 'webpack';
|
||||
import * as merge from 'webpack-merge';
|
||||
import { getReleaseChannel } from './dist-info';
|
||||
import { getReplacements } from './app-info';
|
||||
|
||||
const channel = getReleaseChannel();
|
||||
|
||||
export const externals = ['7zip'];
|
||||
if (channel === 'development') {
|
||||
externals.push('devtron');
|
||||
}
|
||||
|
||||
const outputDir = 'dist';
|
||||
const tsConfigBase = '../tsconfig.json';
|
||||
|
||||
const atlConfig = {
|
||||
configFileName: tsConfigBase
|
||||
};
|
||||
|
||||
export const replacements = getReplacements();
|
||||
|
||||
const commonConfig: webpack.Configuration = {
|
||||
optimization: {
|
||||
noEmitOnErrors: true
|
||||
},
|
||||
externals: externals,
|
||||
output: {
|
||||
filename: '[name].js',
|
||||
path: path.resolve(__dirname, '..', outputDir),
|
||||
libraryTarget: 'commonjs2'
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.tsx?$/,
|
||||
include: path.resolve(__dirname, '../src'),
|
||||
use: [
|
||||
{
|
||||
loader: 'awesome-typescript-loader?' + JSON.stringify(atlConfig),
|
||||
options: {
|
||||
useCache: true
|
||||
}
|
||||
}
|
||||
],
|
||||
exclude: /node_modules/
|
||||
},
|
||||
{
|
||||
test: /\.json$/,
|
||||
loader: 'json-loader'
|
||||
},
|
||||
{
|
||||
test: /\.node$/,
|
||||
loader: 'awesome-node-loader',
|
||||
options: {
|
||||
name: '[name].[ext]'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
plugins: [
|
||||
new CleanWebpackPlugin({ verbose: false }),
|
||||
// This saves us a bunch of bytes by pruning locales (which we don't use)
|
||||
// from moment.
|
||||
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/)
|
||||
],
|
||||
resolve: {
|
||||
extensions: ['.js', '.ts', '.tsx'],
|
||||
modules: [path.resolve(__dirname, '../node_modules/')]
|
||||
},
|
||||
node: {
|
||||
__dirname: false,
|
||||
__filename: false
|
||||
}
|
||||
};
|
||||
|
||||
export const main = merge({}, commonConfig, {
|
||||
entry: { main: path.resolve(__dirname, '../src/main') },
|
||||
target: 'electron-main',
|
||||
plugins: [
|
||||
new webpack.DefinePlugin(
|
||||
Object.assign({}, replacements, {
|
||||
__PROCESS_KIND__: JSON.stringify('main')
|
||||
})
|
||||
)
|
||||
]
|
||||
});
|
13
config/webpack.development.ts
Normal file
13
config/webpack.development.ts
Normal file
|
@ -0,0 +1,13 @@
|
|||
import * as common from './webpack.common';
|
||||
|
||||
import * as webpack from 'webpack';
|
||||
import * as merge from 'webpack-merge';
|
||||
|
||||
const config: webpack.Configuration = {
|
||||
mode: 'development',
|
||||
devtool: 'source-map'
|
||||
};
|
||||
|
||||
const mainConfig = merge({}, common.main, config);
|
||||
|
||||
export = [mainConfig];
|
2783
package-lock.json
generated
2783
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
29
package.json
29
package.json
|
@ -9,30 +9,53 @@
|
|||
"author": "",
|
||||
"license": "ISC",
|
||||
"scripts": {
|
||||
"electron": "tsc --project ./ && electron . --serve"
|
||||
"electron": "tsc --project ./ && electron . --serve",
|
||||
"build:dev": "cross-env NODE_ENV=development parallel-webpack --config config/webpack.development.ts --progress --profile && electron . --serve"
|
||||
},
|
||||
"dependencies": {
|
||||
"electron-devtools-installer": "^2.2.4",
|
||||
"electron-log": "^3.0.5",
|
||||
"electron-window-state": "^5.0.3",
|
||||
"event-kit": "^2.5.3",
|
||||
"file-uri-to-path": "^1.0.0",
|
||||
"file-url": "^3.0.0",
|
||||
"fs-extra": "^7.0.1",
|
||||
"grpc": "^1.20.3",
|
||||
"rxjs": "^6.5.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@odds-crawler/proto": "^0.0.6",
|
||||
"@types/clean-webpack-plugin": "^0.1.3",
|
||||
"@types/event-kit": "^2.4.0",
|
||||
"@types/google-protobuf": "^3.2.7",
|
||||
"@types/node": "^8.9.4",
|
||||
"@types/source-map-support": "^0.5.0",
|
||||
"awesome-node-loader": "^1.1.1",
|
||||
"awesome-typescript-loader": "^5.2.1",
|
||||
"clean-webpack-plugin": "^2.0.2",
|
||||
"core-js": "^3.0.1",
|
||||
"cross-env": "^5.2.0",
|
||||
"devtron": "^1.4.0",
|
||||
"electron": "^5.0.0",
|
||||
"electron-builder": "^20.40.2",
|
||||
"electron-connect": "^0.6.3",
|
||||
"electron-debug": "^3.0.0",
|
||||
"electron-log": "^3.0.5",
|
||||
"electron-window-state": "^5.0.3",
|
||||
"google-protobuf": "^3.8.0-rc.1",
|
||||
"grpc": "^1.20.3",
|
||||
"parallel-webpack": "^2.3.0",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"reflection": "^0.0.1",
|
||||
"source-map-support": "^0.5.12",
|
||||
"ts-node": "^8.1.0",
|
||||
"tslib": "^1.9.3",
|
||||
"tslint": "^5.16.0",
|
||||
"typescript": "^3.4.5"
|
||||
"typescript": "^3.4.5",
|
||||
"tsyringe": "^3.2.0",
|
||||
"webpack": "^4.31.0",
|
||||
"webpack-cli": "^3.3.2",
|
||||
"webpack-merge": "^4.2.1",
|
||||
"webpack-node-externals": "^1.7.2"
|
||||
},
|
||||
"main": "dist/main.js"
|
||||
}
|
||||
|
|
1
src/app/app-window.ts
Normal file
1
src/app/app-window.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export class AppWindow {}
|
5
src/crash/crash-window.ts
Normal file
5
src/crash/crash-window.ts
Normal file
|
@ -0,0 +1,5 @@
|
|||
import { CrashType } from './type';
|
||||
|
||||
export class CrashWindow {
|
||||
public constructor(type: CrashType, error: Error) {}
|
||||
}
|
55
src/crash/crash.ts
Normal file
55
src/crash/crash.ts
Normal file
|
@ -0,0 +1,55 @@
|
|||
import * as log from 'electron-log';
|
||||
import { CrashWindow } from './crash-window';
|
||||
|
||||
let hasReportedUncaughtException = false;
|
||||
|
||||
/** Show the uncaught exception UI. */
|
||||
export function showUncaughtException(isLaunchError: boolean, error: Error) {
|
||||
log.error(error);
|
||||
|
||||
if (hasReportedUncaughtException) {
|
||||
return;
|
||||
}
|
||||
|
||||
hasReportedUncaughtException = true;
|
||||
|
||||
// setCrashMenu();
|
||||
|
||||
const crashWindow = new CrashWindow(
|
||||
isLaunchError ? 'launch' : 'generic',
|
||||
error
|
||||
);
|
||||
|
||||
// crashWindow.onDidLoad(() => {
|
||||
// crashWindow.show();
|
||||
// });
|
||||
|
||||
// crashWindow.onFailedToLoad(() => {
|
||||
// dialog.showMessageBox(
|
||||
// {
|
||||
// type: 'error',
|
||||
// title: __DARWIN__ ? `Unrecoverable Error` : 'Unrecoverable error',
|
||||
// message:
|
||||
// `GitHub Desktop has encountered an unrecoverable error and will need to restart.\n\n` +
|
||||
// `This has been reported to the team, but if you encounter this repeatedly please report ` +
|
||||
// `this issue to the GitHub Desktop issue tracker.\n\n${error.stack ||
|
||||
// error.message}`
|
||||
// },
|
||||
// response => {
|
||||
// if (!__DEV__) {
|
||||
// app.relaunch();
|
||||
// }
|
||||
// app.quit();
|
||||
// }
|
||||
// );
|
||||
// });
|
||||
|
||||
// crashWindow.onClose(() => {
|
||||
// if (!__DEV__) {
|
||||
// app.relaunch();
|
||||
// }
|
||||
// app.quit();
|
||||
// });
|
||||
|
||||
// crashWindow.load();
|
||||
}
|
15
src/crash/type.ts
Normal file
15
src/crash/type.ts
Normal file
|
@ -0,0 +1,15 @@
|
|||
export type CrashType = 'launch' | 'generic';
|
||||
|
||||
export interface ICrashDetails {
|
||||
/**
|
||||
* Whether this error was thrown before we were able to launch
|
||||
* the main renderer process or not. See the documentation for
|
||||
* the ErrorType type for more details.
|
||||
*/
|
||||
readonly location: CrashType;
|
||||
|
||||
/**
|
||||
* The error that caused us to spawn the crash process.
|
||||
*/
|
||||
readonly error: Error;
|
||||
}
|
42
src/global.d.ts
vendored
Normal file
42
src/global.d.ts
vendored
Normal file
|
@ -0,0 +1,42 @@
|
|||
/* eslint-disable @typescript-eslint/interface-name-prefix */
|
||||
/** Is the app running in dev mode? */
|
||||
declare const __DEV__: boolean;
|
||||
|
||||
/** Is the app being built to run on Darwin? */
|
||||
declare const __DARWIN__: boolean;
|
||||
|
||||
/** Is the app being built to run on Win32? */
|
||||
declare const __WIN32__: boolean;
|
||||
|
||||
/** Is the app being built to run on Linux? */
|
||||
declare const __LINUX__: boolean;
|
||||
|
||||
/**
|
||||
* The currently executing process kind, this is specific to desktop
|
||||
* and identifies the processes that we have.
|
||||
*/
|
||||
declare const __PROCESS_KIND__: 'main' | 'renderer' | 'crash';
|
||||
|
||||
declare namespace Electron {
|
||||
interface MenuItem {
|
||||
readonly accelerator?: Electron.Accelerator;
|
||||
readonly submenu?: Electron.Menu;
|
||||
readonly role?: string;
|
||||
readonly type: 'normal' | 'separator' | 'submenu' | 'checkbox' | 'radio';
|
||||
}
|
||||
|
||||
interface RequestOptions {
|
||||
readonly method: string;
|
||||
readonly url: string;
|
||||
readonly headers: any;
|
||||
}
|
||||
|
||||
type AppleActionOnDoubleClickPref = 'Maximize' | 'Minimize' | 'None';
|
||||
|
||||
interface SystemPreferences {
|
||||
getUserDefault(
|
||||
key: 'AppleActionOnDoubleClick',
|
||||
type: 'string'
|
||||
): AppleActionOnDoubleClickPref;
|
||||
}
|
||||
}
|
227
src/main.ts
227
src/main.ts
|
@ -1,76 +1,211 @@
|
|||
import { app, BrowserWindow, ipcMain } from 'electron';
|
||||
import { app, ipcMain, IpcMessageEvent, BrowserWindow } from 'electron';
|
||||
import * as path from 'path';
|
||||
import * as url from 'url';
|
||||
import * as fs from 'fs';
|
||||
import * as grpc from 'grpc';
|
||||
import * as fse from 'fs-extra';
|
||||
import * as log from 'electron-log';
|
||||
|
||||
import * as CDPServiceProto from '@odds-crawler/proto/cdp/cdp.service_pb';
|
||||
import * as CDPServiceGRpcProto from '@odds-crawler/proto/cdp/cdp.service_grpc_pb';
|
||||
import { now } from './util/performance';
|
||||
import { showUncaughtException } from './crash/crash';
|
||||
import { withSourceMappedStack } from './util/source-map-support';
|
||||
import { reportError } from './util/report';
|
||||
|
||||
const launchStartTime = now();
|
||||
let launchReadyTime: number | null = null;
|
||||
|
||||
let preventQuit = false;
|
||||
|
||||
const appRoot = path.join(__dirname, `../../dist/odds-crawler-frontend-app`);
|
||||
process.env['ELECTRON_DISABLE_SECURITY_WARNINGS'] = 'true';
|
||||
|
||||
const appDir = path.join(__dirname, `../../dist/odds-crawler-frontend-app`);
|
||||
let mainWindow: BrowserWindow | null = null;
|
||||
|
||||
let mainWindow: BrowserWindow;
|
||||
function handleUncaughtException(error: Error) {
|
||||
preventQuit = true;
|
||||
|
||||
app.on('ready', createWindow);
|
||||
const isLaunchError = mainWindow === null;
|
||||
|
||||
app.on('activate', () => {
|
||||
if (null === mainWindow) {
|
||||
createWindow();
|
||||
if (mainWindow) {
|
||||
mainWindow.destroy();
|
||||
mainWindow = null;
|
||||
}
|
||||
|
||||
showUncaughtException(isLaunchError, error);
|
||||
}
|
||||
process.on('uncaughtException', (error: Error) => {
|
||||
error = withSourceMappedStack(error);
|
||||
|
||||
reportError(error);
|
||||
handleUncaughtException(error);
|
||||
});
|
||||
|
||||
let isDuplicateInstance = false;
|
||||
const gotSingleInstanceLock = app.requestSingleInstanceLock();
|
||||
isDuplicateInstance = !gotSingleInstanceLock;
|
||||
|
||||
app.on('second-instance', (event, args, workingDirectory) => {
|
||||
// Someone tried to run a second instance, we should focus our window.
|
||||
if (mainWindow) {
|
||||
if (mainWindow.isMinimized()) {
|
||||
mainWindow.restore();
|
||||
}
|
||||
|
||||
if (!mainWindow.isVisible()) {
|
||||
mainWindow.show();
|
||||
}
|
||||
|
||||
mainWindow.focus();
|
||||
}
|
||||
});
|
||||
|
||||
app.on('window-all-closed', () => {
|
||||
// On OS X it is common for applications and their menu bar
|
||||
// to stay active until the user quits explicitly with Cmd + Q
|
||||
if ('darwin' !== process.platform) {
|
||||
app.quit();
|
||||
if (isDuplicateInstance) {
|
||||
app.quit();
|
||||
}
|
||||
|
||||
app.on('ready', () => {
|
||||
if (isDuplicateInstance) {
|
||||
return;
|
||||
}
|
||||
|
||||
launchReadyTime = now() - launchStartTime;
|
||||
|
||||
createWindow();
|
||||
|
||||
ipcMain.on(
|
||||
'uncaught-exception',
|
||||
(event: Electron.IpcMessageEvent, error: Error) => {
|
||||
handleUncaughtException(error);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
let windowStateKeeper: any | null = null;
|
||||
|
||||
function createWindow() {
|
||||
mainWindow = new BrowserWindow({
|
||||
width: 800,
|
||||
height: 600,
|
||||
webPreferences: { nodeIntegration: true }
|
||||
const minWidth = 960;
|
||||
const minHeight = 660;
|
||||
|
||||
if (!windowStateKeeper) {
|
||||
// `electron-window-state` requires Electron's `screen` module, which can
|
||||
// only be required after the app has emitted `ready`. So require it
|
||||
// lazily.
|
||||
windowStateKeeper = require('electron-window-state');
|
||||
}
|
||||
|
||||
const savedWindowState = windowStateKeeper({
|
||||
defaultWidth: minWidth,
|
||||
defaultHeight: minHeight
|
||||
});
|
||||
|
||||
const windowOptions: Electron.BrowserWindowConstructorOptions = {
|
||||
x: savedWindowState.x,
|
||||
y: savedWindowState.y,
|
||||
width: savedWindowState.width,
|
||||
height: savedWindowState.height,
|
||||
minWidth: minWidth,
|
||||
minHeight: minHeight,
|
||||
show: false,
|
||||
// This fixes subpixel aliasing on Windows
|
||||
// See https://github.com/atom/atom/commit/683bef5b9d133cb194b476938c77cc07fd05b972
|
||||
backgroundColor: '#fff',
|
||||
webPreferences: {
|
||||
// Disable auxclick event
|
||||
// See https://developers.google.com/web/updates/2016/10/auxclick
|
||||
disableBlinkFeatures: 'Auxclick',
|
||||
// Enable, among other things, the ResizeObserver
|
||||
experimentalFeatures: true,
|
||||
nodeIntegration: true
|
||||
},
|
||||
acceptFirstMouse: true
|
||||
};
|
||||
|
||||
if (__DARWIN__) {
|
||||
windowOptions.titleBarStyle = 'hidden';
|
||||
} else if (__WIN32__) {
|
||||
windowOptions.frame = false;
|
||||
} else if (__LINUX__) {
|
||||
windowOptions.icon = path.join(__dirname, 'static', 'icon-logo.png');
|
||||
}
|
||||
|
||||
mainWindow = new BrowserWindow(windowOptions);
|
||||
savedWindowState.manage(mainWindow);
|
||||
|
||||
let quitting = false;
|
||||
app.on('before-quit', () => {
|
||||
quitting = true;
|
||||
});
|
||||
|
||||
ipcMain.on('will-quit', (event: Electron.IpcMessageEvent) => {
|
||||
quitting = true;
|
||||
event.returnValue = true;
|
||||
});
|
||||
|
||||
let startLoad = 0;
|
||||
// We only listen for the first of the loading events to avoid a bug in
|
||||
// Electron/Chromium where they can sometimes fire more than once. See
|
||||
// See
|
||||
// https://github.com/desktop/desktop/pull/513#issuecomment-253028277. This
|
||||
// shouldn't really matter as in production builds loading _should_ only
|
||||
// happen once.
|
||||
mainWindow.webContents.once('did-start-loading', () => {
|
||||
// this._rendererReadyTime = null;
|
||||
// this._loadTime = null;
|
||||
|
||||
startLoad = now();
|
||||
});
|
||||
|
||||
mainWindow.webContents.once('did-finish-load', () => {
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
mainWindow.webContents.openDevTools();
|
||||
}
|
||||
|
||||
// this._loadTime = now() - startLoad;
|
||||
|
||||
// this.maybeEmitDidLoad();
|
||||
});
|
||||
|
||||
mainWindow.webContents.on('did-finish-load', () => {
|
||||
mainWindow.webContents.setVisualZoomLevelLimits(1, 1);
|
||||
});
|
||||
|
||||
mainWindow.webContents.on('did-fail-load', () => {
|
||||
mainWindow.webContents.openDevTools();
|
||||
mainWindow.show();
|
||||
});
|
||||
|
||||
// TODO: This should be scoped by the window.
|
||||
ipcMain.once(
|
||||
'renderer-ready',
|
||||
(event: Electron.IpcMessageEvent, readyTime: number) => {
|
||||
// this._rendererReadyTime = readyTime;
|
||||
// this.maybeEmitDidLoad();
|
||||
}
|
||||
);
|
||||
|
||||
mainWindow.loadURL(
|
||||
url.format({
|
||||
pathname: path.join(appDir, `/index.html`),
|
||||
pathname: path.join(appRoot, 'index.html'),
|
||||
protocol: 'file:',
|
||||
slashes: true
|
||||
})
|
||||
);
|
||||
|
||||
mainWindow.webContents.openDevTools();
|
||||
|
||||
mainWindow.on('closed', () => {
|
||||
mainWindow = null;
|
||||
mainWindow.once('ready-to-show', () => {
|
||||
mainWindow.show();
|
||||
});
|
||||
|
||||
let cdpServiceClient = new CDPServiceGRpcProto.CDPServiceClient(
|
||||
'localhost:50051',
|
||||
grpc.credentials.createInsecure()
|
||||
);
|
||||
|
||||
// let req: CDPServiceProto.NavigateRequest;
|
||||
// let res: CDPServiceProto.NavigateReply;
|
||||
|
||||
// req = new CDPServiceProto.NavigateRequest();
|
||||
// req.setUrl('https://www.google.com');
|
||||
|
||||
// cdpServiceClient.navigate(req, (err, res) => {
|
||||
// if (err) {
|
||||
// console.log(err);
|
||||
// return;
|
||||
// }
|
||||
// console.log(res);
|
||||
// });
|
||||
mainWindow.on('closed', function() {
|
||||
mainWindow = null;
|
||||
});
|
||||
}
|
||||
|
||||
ipcMain.on('getFiles', (event, arg) => {
|
||||
const files = fs.readdirSync(__dirname);
|
||||
mainWindow.webContents.send('getFilesResponse', files);
|
||||
app.on('window-all-closed', function() {
|
||||
if (process.platform !== 'darwin') {
|
||||
app.quit();
|
||||
}
|
||||
});
|
||||
|
||||
app.on('activate', function() {
|
||||
if (mainWindow === null) {
|
||||
createWindow();
|
||||
}
|
||||
});
|
||||
|
|
4
src/util/performance.ts
Normal file
4
src/util/performance.ts
Normal file
|
@ -0,0 +1,4 @@
|
|||
export function now(): number {
|
||||
const time = process.hrtime();
|
||||
return time[0] * 1000 + time[1] / 1000000;
|
||||
}
|
71
src/util/report.ts
Normal file
71
src/util/report.ts
Normal file
|
@ -0,0 +1,71 @@
|
|||
import { app, net } from 'electron';
|
||||
import * as log from 'electron-log';
|
||||
|
||||
const ErrorEndpoint = 'https://central.github.com/api/desktop/exception';
|
||||
|
||||
export async function reportError(
|
||||
error: Error,
|
||||
extra?: { [key: string]: string }
|
||||
) {
|
||||
if (__DEV__) {
|
||||
return;
|
||||
}
|
||||
|
||||
const data = new Map<string, string>();
|
||||
|
||||
data.set('name', error.name);
|
||||
data.set('message', error.message);
|
||||
|
||||
if (error.stack) {
|
||||
data.set('stack', error.stack);
|
||||
}
|
||||
|
||||
data.set('platform', process.platform);
|
||||
data.set('version', app.getVersion());
|
||||
|
||||
if (extra) {
|
||||
for (const key of Object.keys(extra)) {
|
||||
data.set(key, extra[key]);
|
||||
}
|
||||
}
|
||||
|
||||
const requestOptions: Electron.RequestOptions = {
|
||||
method: 'POST',
|
||||
url: ErrorEndpoint,
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded'
|
||||
}
|
||||
};
|
||||
|
||||
const body = [...data.entries()]
|
||||
.map(
|
||||
([key, value]) =>
|
||||
`${encodeURIComponent(key)}=${encodeURIComponent(value)}`
|
||||
)
|
||||
.join('&');
|
||||
|
||||
try {
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
const request = net.request(requestOptions);
|
||||
|
||||
request.on('response', response => {
|
||||
if (response.statusCode === 200) {
|
||||
resolve();
|
||||
} else {
|
||||
reject(
|
||||
`Got ${response.statusCode} - ${
|
||||
response.statusMessage
|
||||
} from central`
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
request.on('error', reject);
|
||||
|
||||
request.end(body);
|
||||
});
|
||||
log.info('Error report submitted');
|
||||
} catch (e) {
|
||||
log.error('Failed submitting error report', error);
|
||||
}
|
||||
}
|
35
src/util/source-map-support.ts
Normal file
35
src/util/source-map-support.ts
Normal file
|
@ -0,0 +1,35 @@
|
|||
const stackFrameMap = new WeakMap<Error, ReadonlyArray<any>>();
|
||||
|
||||
let prepareStackTraceWithSourceMap: (
|
||||
error: Error,
|
||||
frames: ReadonlyArray<any>
|
||||
) => string;
|
||||
|
||||
export function withSourceMappedStack(error: Error): Error {
|
||||
return {
|
||||
name: error.name,
|
||||
message: error.message,
|
||||
stack: sourceMappedStackTrace(error)
|
||||
};
|
||||
}
|
||||
|
||||
function sourceMappedStackTrace(error: Error): string | undefined {
|
||||
let frames = stackFrameMap.get(error);
|
||||
|
||||
if (!frames) {
|
||||
// At this point there's no guarantee that anyone has actually retrieved the
|
||||
// stack on this error which means that our custom prepareStackTrace handler
|
||||
// hasn't run and as a result of that we don't have the native frames stored
|
||||
// in our weak map. In order to get around that we'll eagerly access the
|
||||
// stack, forcing our handler to run which should ensure that the native
|
||||
// frames are stored in our weak map.
|
||||
(error.stack || '').toString();
|
||||
frames = stackFrameMap.get(error);
|
||||
}
|
||||
|
||||
if (!frames) {
|
||||
return error.stack;
|
||||
}
|
||||
|
||||
return prepareStackTraceWithSourceMap(error, frames);
|
||||
}
|
|
@ -5,6 +5,7 @@
|
|||
"outDir": "./dist",
|
||||
"sourceMap": true,
|
||||
"declaration": false,
|
||||
"downlevelIteration": true,
|
||||
"module": "commonjs",
|
||||
"moduleResolution": "node",
|
||||
"emitDecoratorMetadata": true,
|
||||
|
|
Loading…
Reference in New Issue
Block a user