Skip to content
This repository was archived by the owner on Sep 3, 2024. It is now read-only.

Dev: Electron migration #515

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions .eslintrc.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{
{
"extends": [
"airbnb",
"airbnb-typescript"
@@ -17,6 +17,7 @@
"no-bitwise": 0,
"no-await-in-loop": 0,
"no-use-before-define": 0,
"no-param-reassign": 0
"no-param-reassign": 0,
"react/function-component-definition": 0
}
}
}
4 changes: 2 additions & 2 deletions .yarnrc
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
runtime "electron"
target "13.6.9"
target "19.0.15"
target_arch "x64"
disturl "https://atom.io/download/atom-shell"
disturl "https://electronjs.org/headers"
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -44,7 +44,7 @@
"colors": "^1.4.0",
"cross-env": "^7.0.2",
"css-loader": "^5.2.6",
"electron": "13.6.9",
"electron": "19.0.15",
"eslint": "^8.15.0",
"eslint-config-airbnb": "^19.0.4",
"eslint-config-airbnb-typescript": "^17.0.0",
@@ -92,6 +92,7 @@
"pvtsutils": "^1.3.2",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-router-dom": "^6.3.0",
"reflect-metadata": "^0.1.13",
"request": "^2.88.2",
"semver": "^6.3.0",
6 changes: 2 additions & 4 deletions scripts/webpack.renderer.config.js
Original file line number Diff line number Diff line change
@@ -5,10 +5,8 @@ const baseConfig = require('./webpack.base.config');
module.exports = merge.smart(baseConfig, {
target: 'electron-renderer',
entry: {
message: path.join(__dirname, '../src/renderer/containers/message/index.tsx'),
'key-pin': path.join(__dirname, '../src/renderer/containers/key_pin/index.tsx'),
'p11-pin': path.join(__dirname, '../src/renderer/containers/p11_pin/index.tsx'),
preferences: path.join(__dirname, '../src/renderer/containers/preferences/index.tsx'),
index: path.join(__dirname, '../src/renderer/index.tsx'),
preload: path.join(__dirname, '../src/preload/index.ts'),
},
optimization: {
splitChunks: {
2 changes: 1 addition & 1 deletion src/main/application.ts
Original file line number Diff line number Diff line change
@@ -73,7 +73,7 @@ export class Application {
app.dock.hide();
}

app.allowRendererProcessReuse = true;
// app.allowRendererProcessReuse = true;

/**
* Don't quit when all windows are closed.
1 change: 1 addition & 0 deletions src/main/constants/files.ts
Original file line number Diff line number Diff line change
@@ -21,6 +21,7 @@ export const SRC_DIR = path.join(APP_DIR, 'src');
export const RESOURCES_DIR = path.join(SRC_DIR, 'resources');
export const STATIC_DIR = path.join(SRC_DIR, 'static');
export const HTML_PATH = path.join(STATIC_DIR, 'index.html');
export const PRELOAD_PATH = path.join(APP_DIR, 'out/preload.js');
export const ICON_DIR = path.join(STATIC_DIR, 'icons');

export const APP_LOG_FILE = path.join(APP_USER_DIR, 'fortify.log');
7 changes: 7 additions & 0 deletions src/main/ipc_messages.ts
Original file line number Diff line number Diff line change
@@ -138,6 +138,13 @@ const initEvents = () => {
.on('ipc-update-check', () => {
autoUpdater.checkForUpdates();
})
.on('window-close', (event) => {
const window = BrowserWindow.fromWebContents(event.sender);

if (window) {
window.close();
}
})
.on('error', (event: IpcMainEvent) => {
logger.error('ipc-messages', 'Event error', {
event: event.toString(),
14 changes: 7 additions & 7 deletions src/main/windows/browser_window.ts
Original file line number Diff line number Diff line change
@@ -59,9 +59,8 @@ export class BrowserWindow {
});

this.window.loadFile(constants.HTML_PATH, {
query: {
app: options.app,
},
hash: `/${options.app}`,
query: options.params || {},
});

this.window.lang = l10n.lang;
@@ -81,7 +80,7 @@ export class BrowserWindow {
});

// Show page only after `lfinish-load` event and prevent show index page
if (this.window.app !== 'index') {
if (options.app !== 'index') {
this.window.webContents.once('did-finish-load', () => {
this.window.show();
});
@@ -121,13 +120,14 @@ export class BrowserWindow {
show: false,
...this.getWindowSize(),
webPreferences: {
nodeIntegration: true,
// nodeIntegration: true,
// Prevent open DevTools on production
devTools: constants.isDevelopment,
enableRemoteModule: true,
// enableRemoteModule: true,
// https://github.com/PeculiarVentures/fortify/issues/453
backgroundThrottling: false,
contextIsolation: false,
// contextIsolation: false,
preload: constants.PRELOAD_PATH,
},
};
}
49 changes: 49 additions & 0 deletions src/preload/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { contextBridge, ipcRenderer } from 'electron';
import { version } from '../../package.json';
import type {
IElectronAPI,
ThemeChangeListenerType,
TelemetryStatusChangeListenerType,
LoggingStatusChangeListenerType,
IdentitiesChangeListenerType,
UpdateAvailableListenerType,
UpdateNotAvailableListenerType,
UpdateErrorListenerType,
UpdateCheckingListenerType,
WindowParamsChangeListenerType,
LanguageChangeListenerType,
} from '../renderer/renderer';

contextBridge.exposeInMainWorld('electronAPI', {
version,
// Language.
getLanguage: () => ipcRenderer.sendSync('ipc-language-get'),
updateLanguage: (lang: string) => ipcRenderer.send('ipc-language-set', lang),
onLanguageChange: (listener: LanguageChangeListenerType) => ipcRenderer.on('ipc-language-changed', listener),
// Logging.
openLoggingFile: () => ipcRenderer.send('ipc-logging-open'),
toggleLoggingStatus: () => ipcRenderer.send('ipc-logging-status-change'),
getLoggingStatus: () => ipcRenderer.sendSync('ipc-logging-status-get'),
onLoggingStatusChange: (listener: LoggingStatusChangeListenerType) => ipcRenderer.on('ipc-logging-status-changed', listener),
// Telementry.
getTelemetryStatus: () => ipcRenderer.sendSync('ipc-telemetry-status-get'),
toggleTelemetryStatus: () => ipcRenderer.send('ipc-telemetry-status-change'),
onTelemetryStatusChange: (listener: TelemetryStatusChangeListenerType) => ipcRenderer.on('ipc-telemetry-status-changed', listener),
// Theme.
getTheme: () => ipcRenderer.sendSync('ipc-theme-get'),
updateTheme: (theme: ThemeType) => ipcRenderer.send('ipc-theme-set', theme),
onThemeChange: (listener: ThemeChangeListenerType) => ipcRenderer.on('ipc-theme-changed', listener),
// Identities.
removeIdentities: (origin: string) => ipcRenderer.send('ipc-2key-remove', origin),
getIdentities: () => ipcRenderer.sendSync('ipc-2key-list-get'),
onIdentitiesChange: (listener: IdentitiesChangeListenerType) => ipcRenderer.on('ipc-2key-changed', listener),
// Updates.s
checkUpdates: () => ipcRenderer.send('ipc-update-check'),
onUpdateAvailable: (listener: UpdateAvailableListenerType) => ipcRenderer.on('ipc-update-available', listener),
onUpdateNotAvailable: (listener: UpdateNotAvailableListenerType) => ipcRenderer.on('ipc-update-not-available', listener),
onUpdateError: (listener: UpdateErrorListenerType) => ipcRenderer.on('ipc-update-error', listener),
onUpdateChecking: (listener: UpdateCheckingListenerType) => ipcRenderer.on('ipc-checking-for-update', listener),
// Window params.
onWindowParamsChange: (listener: WindowParamsChangeListenerType) => ipcRenderer.on('window-params-changed', listener),
closeWindow: () => ipcRenderer.sendSync('window-close'),
} as IElectronAPI);
35 changes: 35 additions & 0 deletions src/renderer/app.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import * as React from 'react';
import {
HashRouter,
Routes,
Route,
} from 'react-router-dom';
import {
Preferences,
Message,
KeyPin,
P11Pin,
} from './containers';

export const App: React.FC = () => (
<HashRouter>
<Routes>
<Route
path="/preferences"
element={<Preferences />}
/>
<Route
path="/message"
element={<Message />}
/>
<Route
path="/key-pin"
element={<KeyPin />}
/>
<Route
path="/p11-pin"
element={<P11Pin />}
/>
</Routes>
</HashRouter>
);
7 changes: 3 additions & 4 deletions src/renderer/components/intl/intl_provider.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,20 @@
import * as React from 'react';
import { ipcRenderer } from 'electron';
import { IntlContext, IIntlContext } from './intl_context';
import { printf } from '../../../main/utils';
import { printf } from '../../../main/utils/printf';

interface IIntlProviderProps {
children: React.ReactNode;
}

export default class IntlProvider extends React.Component<IIntlProviderProps, IIntlContext> {
UNSAFE_componentWillMount() {
ipcRenderer.on('ipc-language-changed', this.onLanguageListener);
window.electronAPI.onLanguageChange(this.onLanguageListener);

this.onLanguageListener();
}

onLanguageListener = () => {
const l10n = ipcRenderer.sendSync('ipc-language-get');
const l10n = window.electronAPI.getLanguage();

this.setState({
lang: l10n.lang,
9 changes: 3 additions & 6 deletions src/renderer/components/window_provider.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,15 @@
import { remote } from 'electron';
import * as React from 'react';
import * as winston from 'winston';
import { IntlProvider } from './intl';
import DocumentTitle from './document_title';

winston.add(new winston.transports.Console());

export default abstract class WindowProvider<P, S> extends React.Component<P, S> {
public params: Record<string, any>;

constructor(props: P) {
super(props);

this.params = (remote.getCurrentWindow() as any).params || {};
const searchParams = new URLSearchParams(window.location.search);
this.params = Object.fromEntries(searchParams);
}

// eslint-disable-next-line class-methods-use-this
@@ -23,7 +20,7 @@ export default abstract class WindowProvider<P, S> extends React.Component<P, S>
close = (...args: any[]) => {
this.onClose(...args);

remote.getCurrentWindow().close();
window.electronAPI.closeWindow();
};

abstract renderChildrens(): JSX.Element;
4 changes: 4 additions & 0 deletions src/renderer/containers/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export * from './key_pin';
export * from './message';
export * from './p11_pin';
export * from './preferences';
8 changes: 1 addition & 7 deletions src/renderer/containers/key_pin/index.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import WindowProvider from '../../components/window_provider';
import Container from './container';

class Root extends WindowProvider<{}, {}> {
export class KeyPin extends WindowProvider<{}, {}> {
onReject = () => {
this.params.accept = false;
this.close();
@@ -25,8 +24,3 @@ class Root extends WindowProvider<{}, {}> {
);
}
}

ReactDOM.render(
<Root />,
document.getElementById('root'),
);
8 changes: 1 addition & 7 deletions src/renderer/containers/message/index.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import WindowProvider from '../../components/window_provider';
import Container from './container';

class Root extends WindowProvider<{}, {}> {
export class Message extends WindowProvider<{}, {}> {
onApprove = (showAgain?: boolean) => {
this.params.result = 1;
this.close(showAgain);
@@ -30,8 +29,3 @@ class Root extends WindowProvider<{}, {}> {
);
}
}

ReactDOM.render(
<Root />,
document.getElementById('root'),
);
8 changes: 1 addition & 7 deletions src/renderer/containers/p11_pin/index.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import WindowProvider from '../../components/window_provider';
import Container from './container';

class Root extends WindowProvider<{}, {}> {
export class P11Pin extends WindowProvider<{}, {}> {
onApprove = (password: string) => {
this.params.pin = password;
this.close();
@@ -24,8 +23,3 @@ class Root extends WindowProvider<{}, {}> {
);
}
}

ReactDOM.render(
<Root />,
document.getElementById('root'),
);
66 changes: 25 additions & 41 deletions src/renderer/containers/preferences/index.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@
/* eslint-disable class-methods-use-this */
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { ipcRenderer, IpcRendererEvent } from 'electron';
import * as path from 'path';
import * as fs from 'fs';
import type { IpcRendererEvent } from 'electron';
import WindowProvider from '../../components/window_provider';
import Container from './container';

const PACKAGE_PATH = path.join(__dirname, '../../package.json');

interface IRootState {
interface IPreferencesState {
activeTab: TabType;
keys: {
list: IKey[];
@@ -23,7 +19,7 @@ interface IRootState {
};
}

class Root extends WindowProvider<{}, IRootState> {
export class Preferences extends WindowProvider<{}, IPreferencesState> {
version: string;

constructor(props: {}) {
@@ -43,65 +39,58 @@ class Root extends WindowProvider<{}, IRootState> {
},
};

this.version = Root.getVersion();
this.version = window.electronAPI.version;
}

componentWillMount() {
/**
* Keys section.
*/
this.keysListGet();
ipcRenderer.on('ipc-2key-changed', this.keysListGet);
window.electronAPI.onIdentitiesChange(this.keysListGet);

/**
* Logging section.
*/
this.loggingGet();
ipcRenderer.on('ipc-logging-status-changed', this.onLoggingChangedListener);
window.electronAPI.onLoggingStatusChange(this.onLoggingChangedListener);

/**
* Telemetry section.
*/
this.telemetryGet();
ipcRenderer.on('ipc-telemetry-status-changed', this.onTelemetryChangedListener);
window.electronAPI.onTelemetryStatusChange(this.onTelemetryChangedListener);

/**
* Theme section.
*/
this.themeGet();
ipcRenderer.on('ipc-theme-changed', this.onThemeChangedListener);
window.electronAPI.onThemeChange(this.onThemeChangedListener);

/**
* Update section.
*/
ipcRenderer.send('ipc-update-check');
ipcRenderer.on('ipc-checking-for-update', this.onUpdateChekingListener);
ipcRenderer.on('ipc-update-available', this.onUpdateAvailableListener);
ipcRenderer.on('ipc-update-not-available', this.onUpdateNotAvailableListener);
ipcRenderer.on('ipc-update-error', this.onUpdateErrorListener);
window.electronAPI.checkUpdates();
window.electronAPI.onUpdateChecking(this.onUpdateChekingListener);
window.electronAPI.onUpdateAvailable(this.onUpdateAvailableListener);
window.electronAPI.onUpdateNotAvailable(this.onUpdateNotAvailableListener);
window.electronAPI.onUpdateError(this.onUpdateErrorListener);

/**
* Window section.
*/
ipcRenderer.on('window-params-changed', this.onWindowParamsChangeListener);
}

static getVersion() {
const json = fs.readFileSync(PACKAGE_PATH, { encoding: 'utf8' });
const data = JSON.parse(json);

return data.version;
window.electronAPI.onWindowParamsChange(this.onWindowParamsChangeListener);
}

/**
* Keys section.
*/
private handleKeyRemove = (origin: string) => {
ipcRenderer.send('ipc-2key-remove', origin);
window.electronAPI.removeIdentities(origin);
};

private keysListGet = () => {
const list = ipcRenderer.sendSync('ipc-2key-list-get');
const list = window.electronAPI.getIdentities();

this.setState({
keys: {
@@ -115,19 +104,19 @@ class Root extends WindowProvider<{}, IRootState> {
* Logging section.
*/
private loggingGet() {
const status = ipcRenderer.sendSync('ipc-logging-status-get');
const status = window.electronAPI.getLoggingStatus();

this.setState({
logging: status,
});
}

private handleLoggingStatusChange = () => {
ipcRenderer.send('ipc-logging-status-change');
window.electronAPI.toggleLoggingStatus();
};

private handleLoggingOpen = () => {
ipcRenderer.send('ipc-logging-open');
window.electronAPI.openLoggingFile();
};

private onLoggingChangedListener = (_: IpcRendererEvent, status: boolean) => {
@@ -140,14 +129,14 @@ class Root extends WindowProvider<{}, IRootState> {
* Language section.
*/
private handleLanguageChange = (lang: string) => {
ipcRenderer.send('ipc-language-set', lang);
window.electronAPI.updateLanguage(lang);
};

/**
* Telemetry section.
*/
private telemetryGet() {
const status = ipcRenderer.sendSync('ipc-telemetry-status-get');
const status = window.electronAPI.getTelemetryStatus();

this.setState({
telemetry: status,
@@ -161,14 +150,14 @@ class Root extends WindowProvider<{}, IRootState> {
};

private handleTelemetryStatusChange = () => {
ipcRenderer.send('ipc-telemetry-status-change');
window.electronAPI.toggleTelemetryStatus();
};

/**
* Theme section.
*/
private themeGet() {
const theme = ipcRenderer.sendSync('ipc-theme-get');
const theme = window.electronAPI.getTheme();

this.setState({
theme,
@@ -182,7 +171,7 @@ class Root extends WindowProvider<{}, IRootState> {
};

private handleThemeChange = (theme: ThemeType) => {
ipcRenderer.send('ipc-theme-set', theme);
window.electronAPI.updateTheme(theme);
};

/**
@@ -284,8 +273,3 @@ class Root extends WindowProvider<{}, IRootState> {
);
}
}

ReactDOM.render(
<Root />,
document.getElementById('root'),
);
4 changes: 3 additions & 1 deletion src/renderer/containers/preferences/updates.tsx
Original file line number Diff line number Diff line change
@@ -5,11 +5,13 @@ import {
CircularProgress,
Box,
} from 'lib-react-components';
import { GITHUB_REPO_LINK, DOWNLOAD_LINK } from '../../../main/constants';
import { IntlContext } from '../../components/intl';

const s = require('./styles/updates.sass');

const GITHUB_REPO_LINK = '';
const DOWNLOAD_LINK = '';

interface IUpdatesProps {
name: any;
update: {
8 changes: 8 additions & 0 deletions src/renderer/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { App } from './app';

ReactDOM.render(
<App />,
document.getElementById('root'),
);
60 changes: 60 additions & 0 deletions src/renderer/renderer.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import type { IpcRendererEvent } from 'electron';
import { ISO_LANGS } from '../renderer/conts';

export type LanguageChangeListenerType = (event: IpcRendererEvent) => void;
export type ThemeChangeListenerType = (event: IpcRendererEvent, theme: ThemeType) => void;
export type TelemetryStatusChangeListenerType = (event: IpcRendererEvent, status: boolean) => void;
export type LoggingStatusChangeListenerType = (event: IpcRendererEvent, status: boolean) => void;
export type IdentitiesChangeListenerType = (event: IpcRendererEvent) => void;
export type UpdateAvailableListenerType = (event: IpcRendererEvent, info: UpdateInfoType) => void;
export type UpdateNotAvailableListenerType = (event: IpcRendererEvent) => void;
export type UpdateErrorListenerType = (event: IpcRendererEvent) => void;
export type UpdateCheckingListenerType = (event: IpcRendererEvent) => void;
export type WindowParamsChangeListenerType = (
event: IpcRendererEvent,
params: Record<string, any>,
) => void;

export interface IElectronAPI {
version: string;
// Language.
getLanguage: () => {
lang: string;
data: Assoc<string>;
list: (keyof typeof ISO_LANGS)[];
};
updateLanguage: (lang: string) => void;
onLanguageChange: (listener: LanguageChangeListenerType) => void;
// Logging.
openLoggingFile: () => void;
toggleLoggingStatus: () => void;
getLoggingStatus: () => boolean;
onLoggingStatusChange: (listener: LoggingStatusChangeListenerType) => void;
// Telementry.
toggleTelemetryStatus: () => void;
getTelemetryStatus: () => boolean;
onTelemetryStatusChange: (listener: TelemetryStatusChangeListenerType) => void;
// Theme.
getTheme: () => ThemeType;
updateTheme: (theme: ThemeType) => void;
onThemeChange: (listener: ThemeChangeListenerType) => void;
// Identities.
removeIdentities: (origin: string) => void;
getIdentities: () => IKey[];
onIdentitiesChange: (listener: IdentitiesChangeListenerType) => void;
// Updates.
checkUpdates: () => void;
onUpdateAvailable: (listener: UpdateAvailableListenerType) => void;
onUpdateNotAvailable: (listener: UpdateNotAvailableListenerType) => void;
onUpdateError: (listener: UpdateErrorListenerType) => void;
onUpdateChecking: (listener: UpdateCheckingListenerType) => void;
// Window params.
onWindowParamsChange: (listener: WindowParamsChangeListenerType) => void;
closeWindow: () => void;
}

declare global {
interface Window {
electronAPI: IElectronAPI;
}
}
15 changes: 2 additions & 13 deletions src/static/index.html
Original file line number Diff line number Diff line change
@@ -155,18 +155,7 @@
</div>
</div>

<script src="../../out/vendors.js"></script>
<script>
const searchParams = new URLSearchParams(location.search);
const currentWindow = searchParams.get('app');

if (!currentWindow) {
throw new Error("app parameter is empty");
}

const $script = document.createElement("script");
$script.setAttribute('src', `../../out/${currentWindow}.js`);
document.body.appendChild($script);
</script>
<script defer src="../../out/vendors.js"></script>
<script defer src="../../out/index.js"></script>
</body>
</html>
19 changes: 10 additions & 9 deletions tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
{
"compilerOptions": {
"target": "es2019",
"module": "commonjs",
"target": "es2019",
"module": "commonjs",
"lib": [
"es2019",
"dom"
],
"jsx": "react",
"strict": true,
"moduleResolution": "node",
],
"jsx": "react",
"strict": true,
"moduleResolution": "node",
"allowSyntheticDefaultImports": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"skipLibCheck": true
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"skipLibCheck": true,
"resolveJsonModule": true
}
}
51 changes: 40 additions & 11 deletions yarn.lock
Original file line number Diff line number Diff line change
@@ -34,6 +34,13 @@
dependencies:
regenerator-runtime "^0.13.4"

"@babel/runtime@^7.7.6":
version "7.18.9"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.18.9.tgz#b4fcfce55db3d2e5e080d2490f608a3b9f407f4a"
integrity sha512-lkqXDcvlFT5rvEjiu6+QYO+1GXrEHRo2LOtS7E4GtX5ESIZOgepqsZBVIj6Pv+a6zqsya9VCgiK1KAK4BvJDAw==
dependencies:
regenerator-runtime "^0.13.4"

"@colors/colors@1.5.0":
version "1.5.0"
resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.5.0.tgz#bb504579c1cae923e6576a4f5da43d25f97bdbd9"
@@ -60,7 +67,7 @@
resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz#1d572bfbbe14b7704e0ba0f39b74815b84870d70"
integrity sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==

"@electron/get@^1.0.1":
"@electron/get@^1.14.1":
version "1.14.1"
resolved "https://registry.yarnpkg.com/@electron/get/-/get-1.14.1.tgz#16ba75f02dffb74c23965e72d617adc721d27f40"
integrity sha512-BrZYyL/6m0ZXz/lDxy/nlVhQz+WF+iPS6qXolEU8atw7h6v1aYkjwJZ63m+bJMBTxDE66X+r2tPS4a/8C82sZw==
@@ -471,10 +478,10 @@
resolved "https://registry.yarnpkg.com/@types/node/-/node-12.20.52.tgz#2fd2dc6bfa185601b15457398d4ba1ef27f81251"
integrity sha512-cfkwWw72849SNYp3Zx0IcIs25vABmFh73xicxhCkTcvtZQeIez15PpwQN8fY3RD7gv1Wrxlc9MEtfMORZDEsGw==

"@types/node@^14.6.2":
version "14.18.18"
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.18.18.tgz#5c9503030df484ccffcbb935ea9a9e1d6fad1a20"
integrity sha512-B9EoJFjhqcQ9OmQrNorItO+OwEOORNn3S31WuiHvZY/dm9ajkB7AKD/8toessEtHHNL+58jofbq7hMMY9v4yig==
"@types/node@^16.11.26":
version "16.11.57"
resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.57.tgz#786f74cef16acf2c5eb11795b6c3f7ae93596662"
integrity sha512-diBb5AE2V8h9Fs9zEDtBwSeLvIACng/aAkdZ3ujMV+cGuIQ9Nc/V+wQqurk9HJp8ni5roBxQHW21z/ZYbGDivg==

"@types/node@^16.9.2":
version "16.11.39"
@@ -2078,13 +2085,13 @@ ecc-jsbn@~0.1.1:
jsbn "~0.1.0"
safer-buffer "^2.1.0"

electron@13.6.9:
version "13.6.9"
resolved "https://registry.yarnpkg.com/electron/-/electron-13.6.9.tgz#7bd83cc1662ceaaa09dcd132a7b507cec888b028"
integrity sha512-Es/sBy85NIuqsO9MW41PUCpwIkeinlTQ7g0ainfnmRAM2rmog3GBxVCaoV5dzEjwTF7TKG1Yr/E7Z3qHmlfWAg==
electron@19.0.15:
version "19.0.15"
resolved "https://registry.yarnpkg.com/electron/-/electron-19.0.15.tgz#a410b43f56a628c9b38f88e896ec68daedbc3cb1"
integrity sha512-VB9cT/6VLg1GOSmEST3mGLN2VCrQMv75dcnEsHjXq6DjltStR/miiXKqujOTVFNzT4fxTRcDDGX5iFF51H+Jhw==
dependencies:
"@electron/get" "^1.0.1"
"@types/node" "^14.6.2"
"@electron/get" "^1.14.1"
"@types/node" "^16.11.26"
extract-zip "^1.0.3"

elliptic@^6.5.3:
@@ -3161,6 +3168,13 @@ he@1.2.0:
resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==

history@^5.2.0:
version "5.3.0"
resolved "https://registry.yarnpkg.com/history/-/history-5.3.0.tgz#1548abaa245ba47992f063a0783db91ef201c73b"
integrity sha512-ZqaKwjjrAYUYfLG+htGaIIZ4nioX2L70ZUMIFysS3xvBsSG4x/n1V6TXV3N8ZYNuFGlDirFg32T7B6WOUPDYcQ==
dependencies:
"@babel/runtime" "^7.7.6"

hmac-drbg@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1"
@@ -5061,6 +5075,21 @@ react-popper@^1.0.2:
typed-styles "^0.0.7"
warning "^4.0.2"

react-router-dom@^6.3.0:
version "6.3.0"
resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.3.0.tgz#a0216da813454e521905b5fa55e0e5176123f43d"
integrity sha512-uaJj7LKytRxZNQV8+RbzJWnJ8K2nPsOOEuX7aQstlMZKQT0164C+X2w6bnkqU3sjtLvpd5ojrezAyfZ1+0sStw==
dependencies:
history "^5.2.0"
react-router "6.3.0"

react-router@6.3.0:
version "6.3.0"
resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.3.0.tgz#3970cc64b4cb4eae0c1ea5203a80334fdd175557"
integrity sha512-7Wh1DzVQ+tlFjkeo+ujvjSqSJmkt1+8JO+T5xklPlgrh70y7ogx75ODRW0ThWhY7S+6yEDks8TYrtQe/aoboBQ==
dependencies:
history "^5.2.0"

react@^16.13.1:
version "16.14.0"
resolved "https://registry.yarnpkg.com/react/-/react-16.14.0.tgz#94d776ddd0aaa37da3eda8fc5b6b18a4c9a3114d"