electron is modified

This commit is contained in:
병준 박 2019-05-18 03:41:15 +09:00
parent 0e0ca76947
commit 6e75194ee8
26 changed files with 3317 additions and 109 deletions

1
.gitignore vendored
View File

@ -2,6 +2,7 @@
# compiled output
/dist
/build
/tmp
/out-tsc
# Only exists if Bazel was run

17
config/app-info.js Normal file
View 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
View File

@ -0,0 +1,3 @@
export function getReleaseChannel() {
return process.env.NODE_ENV || 'development';
}

36
config/helpers.js Normal file
View 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
View 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')
})
)
]
});

View 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

File diff suppressed because it is too large Load Diff

View File

@ -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
View File

@ -0,0 +1 @@
export class AppWindow {}

View 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
View 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
View 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
View 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;
}
}

View File

@ -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
View 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
View 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);
}
}

View 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);
}

View File

@ -5,6 +5,7 @@
"outDir": "./dist",
"sourceMap": true,
"declaration": false,
"downlevelIteration": true,
"module": "commonjs",
"moduleResolution": "node",
"emitDecoratorMetadata": true,