feat: electron app (#1586)

This commit is contained in:
Peng Xiao
2023-03-16 22:58:21 +08:00
committed by GitHub
parent 6ae06d5609
commit 88f662e6f6
42 changed files with 6597 additions and 284 deletions
+57
View File
@@ -0,0 +1,57 @@
import './security-restrictions';
import { app } from 'electron';
import { restoreOrCreateWindow } from './main-window';
import { registerProtocol } from './protocol';
/**
* Prevent multiple instances
*/
const isSingleInstance = app.requestSingleInstanceLock();
if (!isSingleInstance) {
app.quit();
process.exit(0);
}
app.on('second-instance', restoreOrCreateWindow);
/**
* Disable Hardware Acceleration for more power-save
*/
app.disableHardwareAcceleration();
/**
* Shout down background process if all windows was closed
*/
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit();
}
});
/**
* @see https://www.electronjs.org/docs/v14-x-y/api/app#event-activate-macos Event: 'activate'
*/
app.on('activate', restoreOrCreateWindow);
/**
* Create app window when background process will be ready
*/
app
.whenReady()
.then(registerProtocol)
.then(restoreOrCreateWindow)
.catch(e => console.error('Failed create window:', e));
/**
* Check new app version in production mode only
*/
// FIXME: add me back later
// if (import.meta.env.PROD) {
// app
// .whenReady()
// .then(() => import('electron-updater'))
// .then(({ autoUpdater }) => autoUpdater.checkForUpdatesAndNotify())
// .catch(e => console.error('Failed check updates:', e));
// }
@@ -0,0 +1,78 @@
import { BrowserWindow } from 'electron';
import electronWindowState from 'electron-window-state';
import { join } from 'path';
const IS_DEV = process.env.NODE_ENV === 'development';
async function createWindow() {
const mainWindowState = electronWindowState({
defaultWidth: 1000,
defaultHeight: 800,
});
const browserWindow = new BrowserWindow({
x: mainWindowState.x,
y: mainWindowState.y,
width: mainWindowState.width,
height: mainWindowState.height,
show: false, // Use 'ready-to-show' event to show window
webPreferences: {
contextIsolation: true,
sandbox: false,
webviewTag: false, // The webview tag is not recommended. Consider alternatives like iframe or Electron's BrowserView. https://www.electronjs.org/docs/latest/api/webview-tag#warning
spellcheck: false, // FIXME: enable?
preload: join(__dirname, '../../preload/dist/index.js'),
},
});
mainWindowState.manage(browserWindow);
/**
* If you install `show: true` then it can cause issues when trying to close the window.
* Use `show: false` and listener events `ready-to-show` to fix these issues.
*
* @see https://github.com/electron/electron/issues/25012
*/
browserWindow.on('ready-to-show', () => {
browserWindow.show();
if (IS_DEV) {
browserWindow.webContents.openDevTools();
}
});
browserWindow.on('close', e => {
e.preventDefault();
browserWindow.destroy();
// TODO: gracefully close the app, for example, ask user to save unsaved changes
});
/**
* URL for main window.
*/
const pageUrl =
IS_DEV && process.env.DEV_SERVER_URL !== undefined
? process.env.DEV_SERVER_URL
: 'file://./index.html'; // see protocol.ts
await browserWindow.loadURL(pageUrl);
return browserWindow;
}
/**
* Restore existing BrowserWindow or Create new BrowserWindow
*/
export async function restoreOrCreateWindow() {
let window = BrowserWindow.getAllWindows().find(w => !w.isDestroyed());
if (window === undefined) {
window = await createWindow();
}
if (window.isMinimized()) {
window.restore();
}
window.focus();
}
+16
View File
@@ -0,0 +1,16 @@
import { protocol } from 'electron';
import { join } from 'path';
export function registerProtocol() {
protocol.interceptFileProtocol('file', (request, callback) => {
const url = request.url.replace(/^file:\/\//, '');
if (url.startsWith('./')) {
const realpath = join(
__dirname,
'../../../resources/web-static',
decodeURIComponent(url)
);
callback(realpath);
}
});
}
@@ -0,0 +1,35 @@
import { app, shell } from 'electron';
app.on('web-contents-created', (_, contents) => {
/**
* Block navigation to origins not on the allowlist.
*
* Navigation is a common attack vector. If an attacker can convince the app to navigate away
* from its current page, they can possibly force the app to open web sites on the Internet.
*
* @see https://www.electronjs.org/docs/latest/tutorial/security#13-disable-or-limit-navigation
*/
contents.on('will-navigate', (event, url) => {
// Prevent navigation
event.preventDefault();
shell.openExternal(url).catch(console.error);
});
/**
* Hyperlinks to allowed sites open in the default browser.
*
* The creation of new `webContents` is a common attack vector. Attackers attempt to convince the app to create new windows,
* frames, or other renderer processes with more privileges than they had before; or with pages opened that they couldn't open before.
* You should deny any unexpected window creation.
*
* @see https://www.electronjs.org/docs/latest/tutorial/security#14-disable-or-limit-creation-of-new-windows
* @see https://www.electronjs.org/docs/latest/tutorial/security#15-do-not-use-openexternal-with-untrusted-content
*/
contents.setWindowOpenHandler(({ url }) => {
// Open default browser
shell.openExternal(url).catch(console.error);
// Prevent creating new window in application
return { action: 'deny' };
});
});