summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorOskar Nyberg <oskar@mullvad.net>2021-11-03 19:38:17 +0100
committerOskar Nyberg <oskar@mullvad.net>2022-03-14 13:58:44 +0100
commitd5f21653bec09d342112d66c5d20eca38e16b49e (patch)
tree9d6b8baacd70c2f84909e45fc7ea9976e9314e4d
parent46fa74ad03658ad0afa9dc984565b7f10106af94 (diff)
downloadmullvadvpn-d5f21653bec09d342112d66c5d20eca38e16b49e.tar.xz
mullvadvpn-d5f21653bec09d342112d66c5d20eca38e16b49e.zip
Add rpc and ipc calls for listing and removing devices
-rw-r--r--gui/src/main/daemon-rpc.ts54
-rw-r--r--gui/src/main/index.ts44
-rw-r--r--gui/src/renderer/app.tsx32
-rw-r--r--gui/src/renderer/redux/account/actions.ts14
-rw-r--r--gui/src/shared/daemon-rpc-types.ts17
-rw-r--r--gui/src/shared/ipc-schema.ts10
6 files changed, 126 insertions, 45 deletions
diff --git a/gui/src/main/daemon-rpc.ts b/gui/src/main/daemon-rpc.ts
index eab5559ad3..f17505bd80 100644
--- a/gui/src/main/daemon-rpc.ts
+++ b/gui/src/main/daemon-rpc.ts
@@ -46,7 +46,9 @@ import {
VoucherResponse,
TunnelProtocol,
IDnsOptions,
- IDeviceConfig,
+ DeviceConfig,
+ IDevice,
+ IDeviceRemoval,
} from '../shared/daemon-rpc-types';
import log from '../shared/logging';
@@ -484,6 +486,37 @@ export class DaemonRpc {
await this.callEmpty(this.client.checkVolumes);
}
+ public async getDevice(): Promise<DeviceConfig> {
+ try {
+ const response = await this.callEmpty<grpcTypes.DeviceConfig>(this.client.getDevice);
+ return convertFromDeviceConfig(response);
+ } catch (e) {
+ const error = e as grpc.ServiceError;
+ if (error.code === grpc.status.NOT_FOUND) {
+ return undefined;
+ } else {
+ throw error;
+ }
+ }
+ }
+
+ public async listDevices(accountToken: AccountToken): Promise<Array<IDevice>> {
+ const response = await this.callString<grpcTypes.DeviceList>(
+ this.client.listDevices,
+ accountToken,
+ );
+
+ return response.toObject().devicesList;
+ }
+
+ public async removeDevice(deviceRemoval: IDeviceRemoval): Promise<void> {
+ const grpcDeviceRemoval = new grpcTypes.DeviceRemoval();
+ grpcDeviceRemoval.setAccountToken(deviceRemoval.accountToken);
+ grpcDeviceRemoval.setDeviceId(deviceRemoval.deviceId);
+
+ await this.call<grpcTypes.DeviceRemoval, Empty>(this.client.removeDevice, grpcDeviceRemoval);
+ }
+
private subscriptionId(): number {
const current = this.nextSubscriptionId;
this.nextSubscriptionId += 1;
@@ -1110,7 +1143,7 @@ function convertFromDaemonEvent(data: grpcTypes.DaemonEvent): DaemonEvent {
const deviceConfig = data.getDevice();
if (deviceConfig !== undefined) {
- return { deviceConfig: convertFromDeviceConfig(deviceConfig) };
+ return { deviceConfig: convertFromDeviceEvent(deviceConfig) };
}
return {
@@ -1313,12 +1346,17 @@ function convertToTransportProtocol(protocol: RelayProtocol): grpcTypes.Transpor
}
}
-function convertFromDeviceConfig(deviceEvent: grpcTypes.DeviceEvent): IDeviceConfig {
- const deviceConfig = deviceEvent.getDevice();
- return {
- accountToken: deviceConfig?.getAccountToken(),
- device: deviceConfig?.getDevice()?.toObject(),
- };
+function convertFromDeviceEvent(deviceEvent: grpcTypes.DeviceEvent): DeviceConfig {
+ return convertFromDeviceConfig(deviceEvent.getDevice());
+}
+
+function convertFromDeviceConfig(deviceConfig?: grpcTypes.DeviceConfig): DeviceConfig {
+ return (
+ deviceConfig && {
+ accountToken: deviceConfig.getAccountToken(),
+ device: deviceConfig.getDevice()?.toObject(),
+ }
+ );
}
function ensureExists<T>(value: T | undefined, errorMessage: string): T {
diff --git a/gui/src/main/index.ts b/gui/src/main/index.ts
index 07afda535c..f8a9b2497f 100644
--- a/gui/src/main/index.ts
+++ b/gui/src/main/index.ts
@@ -27,7 +27,8 @@ import {
DaemonEvent,
IAccountData,
IAppVersionInfo,
- IDeviceConfig,
+ DeviceConfig,
+ IDeviceRemoval,
IDnsOptions,
IRelayList,
ISettings,
@@ -197,7 +198,7 @@ class ApplicationMain {
},
},
};
- private deviceConfig: IDeviceConfig = { accountToken: undefined, device: undefined };
+ private deviceConfig: DeviceConfig = undefined;
private guiSettings = new GuiSettings();
private tunnelStateExpectation?: Expectation;
@@ -640,6 +641,16 @@ class ApplicationMain {
return this.handleBootstrapError(error);
}
+ // fetch device
+ try {
+ this.setDeviceConfig(await this.daemonRpc.getDevice());
+ } catch (e) {
+ const error = e as Error;
+ log.error(`Failed to fetch device: ${error.message}`);
+
+ return this.handleBootstrapError(error);
+ }
+
// fetch settings
try {
this.setSettings(await this.daemonRpc.getSettings());
@@ -697,7 +708,7 @@ class ApplicationMain {
}
// show window when account is not set
- if (!this.deviceConfig.accountToken) {
+ if (!this.deviceConfig) {
this.windowController?.show();
}
};
@@ -1095,14 +1106,21 @@ class ApplicationMain {
}
}
- private setDeviceConfig(deviceConfig: IDeviceConfig) {
+ private setDeviceConfig(deviceConfig: DeviceConfig) {
const oldDeviceConfig = this.deviceConfig;
this.deviceConfig = deviceConfig;
// make sure to invalidate the account data cache when account tokens change
- this.updateAccountDataOnAccountChange(oldDeviceConfig.accountToken, deviceConfig.accountToken);
+ this.updateAccountDataOnAccountChange(
+ oldDeviceConfig?.accountToken,
+ deviceConfig?.accountToken,
+ );
void this.updateAccountHistory();
+
+ if (this.windowController) {
+ IpcMainEventChannel.account.notifyDevice(this.windowController.webContents, deviceConfig);
+ }
}
private trayIconType(tunnelState: TunnelState, blockWhenDisconnected: boolean): TrayIconType {
@@ -1276,7 +1294,7 @@ class ApplicationMain {
IpcMainEventChannel.account.handleLogout(() => this.logout());
IpcMainEventChannel.account.handleGetWwwAuthToken(() => this.daemonRpc.getWwwAuthToken());
IpcMainEventChannel.account.handleSubmitVoucher(async (voucherCode: string) => {
- const currentAccountToken = this.deviceConfig.accountToken;
+ const currentAccountToken = this.deviceConfig?.accountToken;
const response = await this.daemonRpc.submitVoucher(voucherCode);
if (currentAccountToken) {
@@ -1287,6 +1305,13 @@ class ApplicationMain {
});
IpcMainEventChannel.account.handleUpdateData(() => this.updateAccountData());
+ IpcMainEventChannel.account.handleListDevices((accountToken: AccountToken) => {
+ return this.daemonRpc.listDevices(accountToken);
+ });
+ IpcMainEventChannel.account.handleRemoveDevice((deviceRemoval: IDeviceRemoval) => {
+ return this.daemonRpc.removeDevice(deviceRemoval);
+ });
+
IpcMainEventChannel.accountHistory.handleClear(async () => {
await this.daemonRpc.clearAccountHistory();
void this.updateAccountHistory();
@@ -1467,10 +1492,7 @@ class ApplicationMain {
private async autoConnect() {
if (process.env.NODE_ENV === 'development') {
log.info('Skip autoconnect in development');
- } else if (
- this.deviceConfig.accountToken &&
- (!this.accountData || !hasExpired(this.accountData.expiry))
- ) {
+ } else if (this.deviceConfig && (!this.accountData || !hasExpired(this.accountData.expiry))) {
if (this.guiSettings.autoConnect) {
try {
log.info('Autoconnect the tunnel');
@@ -1530,7 +1552,7 @@ class ApplicationMain {
}
private updateAccountData() {
- if (this.connectedToDaemon && this.deviceConfig.accountToken) {
+ if (this.connectedToDaemon && this.deviceConfig) {
this.accountDataCache.fetch(this.deviceConfig.accountToken);
}
}
diff --git a/gui/src/renderer/app.tsx b/gui/src/renderer/app.tsx
index ff46b76f1a..595ded57c5 100644
--- a/gui/src/renderer/app.tsx
+++ b/gui/src/renderer/app.tsx
@@ -31,7 +31,9 @@ import {
BridgeState,
IAccountData,
IAppVersionInfo,
- IDeviceConfig,
+ IDevice,
+ DeviceConfig,
+ IDeviceRemoval,
IDnsOptions,
ILocation,
ISettings,
@@ -93,7 +95,7 @@ export default class AppRenderer {
private relayListPair!: IRelayListPair;
private tunnelState!: TunnelState;
private settings!: ISettings;
- private deviceConfig!: IDeviceConfig;
+ private deviceConfig: DeviceConfig;
private guiSettings!: IGuiSettingsState;
private loginState: 'none' | 'logging in' | 'creating account' = 'none';
private loginScheduler = new Scheduler();
@@ -122,10 +124,9 @@ export default class AppRenderer {
this.setAccountExpiry(newAccountData?.expiry);
});
- IpcRendererEventChannel.account.listenDevice((deviceConfig: IDeviceConfig) => {
+ IpcRendererEventChannel.account.listenDevice((deviceConfig: DeviceConfig) => {
const oldDeviceConfig = this.deviceConfig;
- this.deviceConfig = deviceConfig;
- this.handleAccountChange(deviceConfig, oldDeviceConfig.accountToken);
+ this.handleAccountChange(deviceConfig, oldDeviceConfig?.accountToken);
});
IpcRendererEventChannel.accountHistory.listen((newAccountHistory?: AccountToken) => {
@@ -232,7 +233,7 @@ export default class AppRenderer {
const navigationBase = this.getNavigationBase(
initialState.isConnected,
- initialState.deviceConfig.accountToken,
+ initialState.deviceConfig?.accountToken,
);
this.history = new History(navigationBase);
}
@@ -307,6 +308,14 @@ export default class AppRenderer {
IpcRendererEventChannel.account.updateData();
}
+ public listDevices(accountToken: AccountToken): Promise<Array<IDevice>> {
+ return IpcRendererEventChannel.account.listDevices(accountToken);
+ }
+
+ public removeDevice(deviceRemoval: IDeviceRemoval): Promise<void> {
+ return IpcRendererEventChannel.account.removeDevice(deviceRemoval);
+ }
+
public async connectTunnel(): Promise<void> {
return IpcRendererEventChannel.tunnel.connect();
}
@@ -615,7 +624,7 @@ export default class AppRenderer {
const pathname = this.history.location.pathname;
const nextPath = this.getNavigationBase(
this.connectedToDaemon,
- this.deviceConfig.accountToken,
+ this.deviceConfig?.accountToken,
);
// First level contains the possible next locations and the second level contains the possible
@@ -737,10 +746,11 @@ export default class AppRenderer {
}
}
- private handleAccountChange(newDeviceConfig: IDeviceConfig, oldAccount?: string) {
+ private handleAccountChange(newDeviceConfig: DeviceConfig, oldAccount?: string) {
const reduxAccount = this.reduxActions.account;
- const newAccount = newDeviceConfig.accountToken;
+ this.deviceConfig = newDeviceConfig;
+ const newAccount = newDeviceConfig?.accountToken;
if (oldAccount && !newAccount) {
this.loginScheduler.cancel();
@@ -748,8 +758,8 @@ export default class AppRenderer {
this.resetNavigation();
} else if (
- newDeviceConfig.accountToken !== undefined &&
- newDeviceConfig.device !== undefined &&
+ newDeviceConfig?.accountToken !== undefined &&
+ newDeviceConfig?.device !== undefined &&
oldAccount !== newAccount
) {
switch (this.loginState) {
diff --git a/gui/src/renderer/redux/account/actions.ts b/gui/src/renderer/redux/account/actions.ts
index ebc63e322c..2eb9f21a17 100644
--- a/gui/src/renderer/redux/account/actions.ts
+++ b/gui/src/renderer/redux/account/actions.ts
@@ -1,4 +1,4 @@
-import { AccountToken, IDeviceConfig } from '../../../shared/daemon-rpc-types';
+import { AccountToken, DeviceConfig } from '../../../shared/daemon-rpc-types';
interface IStartLoginAction {
type: 'START_LOGIN';
@@ -8,7 +8,7 @@ interface IStartLoginAction {
interface ILoggedInAction {
type: 'LOGGED_IN';
accountToken: AccountToken;
- deviceName: string;
+ deviceName?: string;
}
interface ILoginFailedAction {
@@ -36,7 +36,7 @@ interface ICreateAccountFailed {
interface IAccountCreated {
type: 'ACCOUNT_CREATED';
accountToken: AccountToken;
- deviceName: string;
+ deviceName?: string;
expiry: string;
}
@@ -80,11 +80,11 @@ function startLogin(accountToken: AccountToken): IStartLoginAction {
};
}
-function loggedIn(deviceConfig: Required<IDeviceConfig>): ILoggedInAction {
+function loggedIn(deviceConfig: NonNullable<DeviceConfig>): ILoggedInAction {
return {
type: 'LOGGED_IN',
accountToken: deviceConfig.accountToken,
- deviceName: deviceConfig.device.name,
+ deviceName: deviceConfig.device?.name,
};
}
@@ -120,11 +120,11 @@ function createAccountFailed(error: Error): ICreateAccountFailed {
};
}
-function accountCreated(deviceConfig: Required<IDeviceConfig>, expiry: string): IAccountCreated {
+function accountCreated(deviceConfig: NonNullable<DeviceConfig>, expiry: string): IAccountCreated {
return {
type: 'ACCOUNT_CREATED',
accountToken: deviceConfig.accountToken,
- deviceName: deviceConfig.device.name,
+ deviceName: deviceConfig.device?.name,
expiry,
};
}
diff --git a/gui/src/shared/daemon-rpc-types.ts b/gui/src/shared/daemon-rpc-types.ts
index 3b0f7774d3..61a068c08e 100644
--- a/gui/src/shared/daemon-rpc-types.ts
+++ b/gui/src/shared/daemon-rpc-types.ts
@@ -105,7 +105,7 @@ export type DaemonEvent =
| { settings: ISettings }
| { relayList: IRelayList }
| { appVersionInfo: IAppVersionInfo }
- | { deviceConfig: IDeviceConfig };
+ | { deviceConfig: DeviceConfig };
export interface ITunnelStateRelayInfo {
endpoint: ITunnelEndpoint;
@@ -321,16 +321,23 @@ export interface IAppVersionInfo {
suggestedIsBeta?: boolean;
}
-export interface IDeviceConfig {
- accountToken?: AccountToken;
- device?: IDevice;
-}
+export type DeviceConfig =
+ | undefined
+ | {
+ accountToken: AccountToken;
+ device?: IDevice;
+ };
export interface IDevice {
id: string;
name: string;
}
+export interface IDeviceRemoval {
+ accountToken: string;
+ deviceId: string;
+}
+
export interface ISettings {
allowLan: boolean;
autoConnect: boolean;
diff --git a/gui/src/shared/ipc-schema.ts b/gui/src/shared/ipc-schema.ts
index 6b3082cdd7..6ea154bf89 100644
--- a/gui/src/shared/ipc-schema.ts
+++ b/gui/src/shared/ipc-schema.ts
@@ -6,7 +6,9 @@ import {
BridgeState,
IAccountData,
IAppVersionInfo,
- IDeviceConfig,
+ IDevice,
+ DeviceConfig,
+ IDeviceRemoval,
IDnsOptions,
ILocation,
IRelayList,
@@ -51,7 +53,7 @@ export interface IAppStateSnapshot {
accountHistory?: AccountToken;
tunnelState: TunnelState;
settings: ISettings;
- deviceConfig: IDeviceConfig;
+ deviceConfig: DeviceConfig;
relayListPair: IRelayListPair;
currentVersion: ICurrentAppVersionInfo;
upgradeVersion: IAppVersionInfo;
@@ -165,13 +167,15 @@ export const ipcSchema = {
},
account: {
'': notifyRenderer<IAccountData | undefined>(),
- device: notifyRenderer<IDeviceConfig>(),
+ device: notifyRenderer<DeviceConfig>(),
create: invoke<void, string>(),
login: invoke<AccountToken, void>(),
logout: invoke<void, void>(),
getWwwAuthToken: invoke<void, string>(),
submitVoucher: invoke<string, VoucherResponse>(),
updateData: send<void>(),
+ listDevices: invoke<AccountToken, Array<IDevice>>(),
+ removeDevice: invoke<IDeviceRemoval, void>(),
},
accountHistory: {
'': notifyRenderer<AccountToken | undefined>(),