summaryrefslogtreecommitdiffhomepage
path: root/gui/src
diff options
context:
space:
mode:
Diffstat (limited to 'gui/src')
-rw-r--r--gui/src/renderer/app.tsx20
-rw-r--r--gui/src/renderer/lib/history.ts112
-rw-r--r--gui/src/renderer/redux/store.ts2
3 files changed, 123 insertions, 11 deletions
diff --git a/gui/src/renderer/app.tsx b/gui/src/renderer/app.tsx
index b7c9b1a187..4d7b9e9931 100644
--- a/gui/src/renderer/app.tsx
+++ b/gui/src/renderer/app.tsx
@@ -5,7 +5,6 @@ import {
} from 'connected-react-router';
import { ipcRenderer, shell, webFrame } from 'electron';
import log from 'electron-log';
-import { createMemoryHistory } from 'history';
import * as React from 'react';
import { Provider } from 'react-redux';
import { bindActionCreators } from 'redux';
@@ -29,6 +28,7 @@ import { IpcRendererEventChannel, IRelayListPair } from '../shared/ipc-event-cha
import ISplitTunnelingApplication from '../shared/linux-split-tunneling-application';
import { getRendererLogFile, setupLogging } from '../shared/logging';
import consumePromise from '../shared/promise';
+import History from './lib/history';
import {
AccountToken,
@@ -76,8 +76,8 @@ const SUPPORTED_LOCALE_LIST = [
];
export default class AppRenderer {
- private memoryHistory = createMemoryHistory();
- private reduxStore = configureStore(this.memoryHistory);
+ private history = new History('/');
+ private reduxStore = configureStore(this.history);
private reduxActions = {
account: bindActionCreators(accountActions, this.reduxStore.dispatch),
connection: bindActionCreators(connectionActions, this.reduxStore.dispatch),
@@ -229,7 +229,7 @@ export default class AppRenderer {
return (
<AppContext.Provider value={{ app: this }}>
<Provider store={this.reduxStore}>
- <ConnectedRouter history={this.memoryHistory}>
+ <ConnectedRouter history={this.history}>
<ErrorBoundary>
<AppRoutes />
</ErrorBoundary>
@@ -450,7 +450,7 @@ export default class AppRenderer {
private redirectToConnect() {
// Redirect the user after some time to allow for the 'Logged in' screen to be visible
- this.loginTimer = global.setTimeout(() => this.memoryHistory.replace('/connect'), 1000);
+ this.loginTimer = global.setTimeout(() => this.history.resetWith('/connect'), 1000);
}
private loadTranslations(locale: string) {
@@ -530,12 +530,12 @@ export default class AppRenderer {
this.connectedToDaemon = true;
if (this.settings.accountToken) {
- this.memoryHistory.replace('/connect');
+ this.history.resetWith('/connect');
// try to autoconnect the tunnel
await this.autoConnect();
} else {
- this.memoryHistory.replace('/login');
+ this.history.resetWith('/login');
// show window when account is not set
ipcRenderer.send('show-window');
@@ -548,7 +548,7 @@ export default class AppRenderer {
this.connectedToDaemon = false;
if (error && wasConnected) {
- this.memoryHistory.replace('/');
+ this.history.resetWith('/');
}
}
@@ -660,12 +660,12 @@ export default class AppRenderer {
clearTimeout(this.loginTimer);
}
reduxAccount.loggedOut();
- this.memoryHistory.replace('/login');
+ this.history.resetWith('/login');
} else if (newAccount && oldAccount !== newAccount && !this.doingLogin) {
reduxAccount.updateAccountToken(newAccount);
reduxAccount.loggedIn();
if (!oldAccount) {
- this.memoryHistory.replace('/connect');
+ this.history.resetWith('/connect');
}
}
diff --git a/gui/src/renderer/lib/history.ts b/gui/src/renderer/lib/history.ts
new file mode 100644
index 0000000000..b726e72bbb
--- /dev/null
+++ b/gui/src/renderer/lib/history.ts
@@ -0,0 +1,112 @@
+import { Location, Action, LocationListener, LocationDescriptor } from 'history';
+
+// It currently isn't possible to implement this correctly with support for a generic state. State
+// can be added as a generic type (<S = unknown>) after this issue has been resolved:
+// https://github.com/DefinitelyTyped/DefinitelyTyped/issues/49060
+type S = unknown;
+export default class History {
+ private listeners: LocationListener<S>[] = [];
+ private entries: Location<S>[];
+ private index = 0;
+ private lastAction: Action = 'POP';
+
+ public constructor(location: string | Location<S>, state?: S) {
+ this.entries = [this.createLocation(location, state)];
+ }
+
+ public get location(): Location<S> {
+ return this.entries[this.index];
+ }
+
+ public get length(): number {
+ return this.entries.length;
+ }
+
+ public get action(): Action {
+ return this.lastAction;
+ }
+
+ public push = (nextLocation: LocationDescriptor<S>, nextState?: S) => {
+ const location = this.createLocation(nextLocation, nextState);
+ this.lastAction = 'PUSH';
+ this.index += 1;
+ this.entries.splice(this.index, this.entries.length - this.index, location);
+ this.notify();
+ };
+
+ public replace = (nextLocation: LocationDescriptor<S>, nextState?: S) => {
+ this.entries[this.index] = this.createLocation(nextLocation, nextState);
+ this.lastAction = 'REPLACE';
+ this.notify();
+ };
+
+ public go = (n: number) => {
+ if (this.canGo(n)) {
+ this.index += n;
+ this.lastAction = 'POP';
+ this.notify();
+ }
+ };
+
+ public goBack = () => this.go(-1);
+ public goForward = () => this.go(1);
+
+ public reset = () => {
+ this.lastAction = 'POP';
+ this.index = 0;
+ this.notify();
+ };
+
+ public resetWith = (nextLocation: LocationDescriptor<S>, nextState?: S) => {
+ this.entries = [this.createLocation(nextLocation, nextState)];
+ this.lastAction = 'REPLACE';
+ this.index = 0;
+ this.notify();
+ };
+
+ public canGo(n: number) {
+ const nextIndex = this.index + n;
+ return nextIndex >= 0 && nextIndex < this.entries.length;
+ }
+
+ public listen(callback: LocationListener<S>) {
+ this.listeners.push(callback);
+ return () => (this.listeners = this.listeners.filter((listener) => listener !== callback));
+ }
+
+ public block(): () => void {
+ throw Error('Not implemented');
+ }
+
+ public createHref(): string {
+ throw Error('Not implemented');
+ }
+
+ private notify() {
+ this.listeners.forEach((listener) => listener(this.location, this.action));
+ }
+
+ private createLocation(location: LocationDescriptor<S>, state?: S): Location<S> {
+ if (typeof location === 'object') {
+ return {
+ pathname: location.pathname ?? this.location.pathname,
+ search: location.search ?? '',
+ hash: location.hash ?? '',
+ state: location.state,
+ key: location.key ?? this.getRandomKey(),
+ };
+ } else {
+ return {
+ pathname: location,
+ search: '',
+ hash: '',
+ state,
+ key: this.getRandomKey(),
+ };
+ }
+ }
+
+ private getRandomKey() {
+ return Math.random().toString(36).substr(8);
+ }
+}
diff --git a/gui/src/renderer/redux/store.ts b/gui/src/renderer/redux/store.ts
index bedaddf1bd..43ebc312ef 100644
--- a/gui/src/renderer/redux/store.ts
+++ b/gui/src/renderer/redux/store.ts
@@ -14,7 +14,7 @@ import userInterfaceReducer, { IUserInterfaceReduxState } from './userinterface/
import versionActions, { VersionAction } from './version/actions';
import versionReducer, { IVersionReduxState } from './version/reducers';
-import { History } from 'history';
+import History from '../lib/history';
export interface IReduxState {
account: IAccountReduxState;