summaryrefslogtreecommitdiffhomepage
path: root/gui/src/shared
diff options
context:
space:
mode:
authorOskar <oskar@mullvad.net>2024-11-05 07:57:08 +0100
committerOskar <oskar@mullvad.net>2024-11-14 16:43:18 +0100
commit84f14d79c4f0dde73337820ec94ba8ff928a3797 (patch)
treece468658e5ba7b0a74950c7ad1b09b3a4d00520b /gui/src/shared
parente3ce0eb5cd0610dbff6ec98cb8cb388415c74bf6 (diff)
downloadmullvadvpn-84f14d79c4f0dde73337820ec94ba8ff928a3797.tar.xz
mullvadvpn-84f14d79c4f0dde73337820ec94ba8ff928a3797.zip
Move gui directory to desktop/packages/mullvad-vpn
Diffstat (limited to 'gui/src/shared')
-rw-r--r--gui/src/shared/account-expiry.ts28
-rw-r--r--gui/src/shared/application-types.ts60
-rw-r--r--gui/src/shared/connect-helper.ts34
-rw-r--r--gui/src/shared/daemon-rpc-types.ts626
-rw-r--r--gui/src/shared/date-helper.ts125
-rw-r--r--gui/src/shared/gettext.ts32
-rw-r--r--gui/src/shared/gui-settings-state.ts37
-rw-r--r--gui/src/shared/ipc-helpers.ts199
-rw-r--r--gui/src/shared/ipc-schema.ts257
-rw-r--r--gui/src/shared/ipc-types.ts30
-rw-r--r--gui/src/shared/localization-contexts.ts40
-rw-r--r--gui/src/shared/logging-types.ts17
-rw-r--r--gui/src/shared/logging.ts108
-rw-r--r--gui/src/shared/notifications/account-expired.ts42
-rw-r--r--gui/src/shared/notifications/block-when-disconnected.ts66
-rw-r--r--gui/src/shared/notifications/close-to-account-expiry.ts72
-rw-r--r--gui/src/shared/notifications/connected.ts42
-rw-r--r--gui/src/shared/notifications/connecting.ts60
-rw-r--r--gui/src/shared/notifications/daemon-disconnected.ts22
-rw-r--r--gui/src/shared/notifications/disconnected.ts28
-rw-r--r--gui/src/shared/notifications/error.ts338
-rw-r--r--gui/src/shared/notifications/inconsistent-version.ts39
-rw-r--r--gui/src/shared/notifications/new-device.ts32
-rw-r--r--gui/src/shared/notifications/notification.ts86
-rw-r--r--gui/src/shared/notifications/reconnecting.ts35
-rw-r--r--gui/src/shared/notifications/unsupported-version.ts62
-rw-r--r--gui/src/shared/notifications/update-available.ts96
-rw-r--r--gui/src/shared/scheduler.ts37
-rw-r--r--gui/src/shared/string-helpers.ts11
-rw-r--r--gui/src/shared/utils.ts5
-rw-r--r--gui/src/shared/version.ts22
31 files changed, 0 insertions, 2688 deletions
diff --git a/gui/src/shared/account-expiry.ts b/gui/src/shared/account-expiry.ts
deleted file mode 100644
index 1b40848220..0000000000
--- a/gui/src/shared/account-expiry.ts
+++ /dev/null
@@ -1,28 +0,0 @@
-import {
- dateByAddingComponent,
- DateComponent,
- DateType,
- FormatDateOptions,
- formatRelativeDate,
-} from './date-helper';
-
-export function hasExpired(expiry: DateType): boolean {
- return new Date(expiry).getTime() < Date.now();
-}
-
-export function closeToExpiry(expiry: DateType, days = 3): boolean {
- return (
- !hasExpired(expiry) &&
- new Date(expiry) <= dateByAddingComponent(new Date(), DateComponent.day, days)
- );
-}
-
-export function formatDate(date: DateType, locale: string): string {
- return new Intl.DateTimeFormat(locale, { dateStyle: 'medium', timeStyle: 'short' }).format(
- new Date(date),
- );
-}
-
-export function formatRemainingTime(expiry: DateType, options?: FormatDateOptions): string {
- return formatRelativeDate(new Date(), expiry, options);
-}
diff --git a/gui/src/shared/application-types.ts b/gui/src/shared/application-types.ts
deleted file mode 100644
index 526d994d7b..0000000000
--- a/gui/src/shared/application-types.ts
+++ /dev/null
@@ -1,60 +0,0 @@
-type Warning = 'launches-in-existing-process' | 'launches-elsewhere';
-
-export interface IApplication {
- absolutepath: string;
- name: string;
- icon?: string;
-}
-
-export interface ISplitTunnelingApplication extends IApplication {
- deletable: boolean;
-}
-
-export interface ILinuxApplication extends IApplication {
- exec: string;
- type: string;
- terminal?: string;
- noDisplay?: string;
- hidden?: string;
- onlyShowIn?: string[];
- notShowIn?: string[];
- tryExec?: string;
-}
-
-export interface ILinuxSplitTunnelingApplication extends ILinuxApplication {
- warning?: Warning;
-}
-
-export interface ISplitTunnelingAppListRetriever {
- /**
- * Returns a list of all applications known to the app.
- * @param updateCaches Specifies if the application list should be fetched again and merged into the existing cache.
- */
- getApplications(
- updateCaches?: boolean,
- ): Promise<{ fromCache: boolean; applications: ISplitTunnelingApplication[] }>;
-
- /**
- * Returns an object containing information about whether or not it was fetched from the cache,
- * and a list of ISplitTunnelingApplication corresponding to the provided paths.
- */
- getMetadataForApplications(
- applicationPaths: string[],
- ): Promise<{ fromCache: boolean; applications: ISplitTunnelingApplication[] }>;
-
- /**
- * Resolves the actual executable path when an app is provided. On Windows this resolves links and
- * on macOS this finds the executable when an application bundle is provided.
- */
- resolveExecutablePath(providedPath: string): Promise<string>;
-
- /**
- * Adds an application to the internal cache.
- */
- addApplicationPathToCache(applicationPath: string): Promise<void>;
-
- /**
- * Removes an application from the internal cache.
- */
- removeApplicationFromCache(application: ISplitTunnelingApplication): void;
-}
diff --git a/gui/src/shared/connect-helper.ts b/gui/src/shared/connect-helper.ts
deleted file mode 100644
index 26128513ed..0000000000
--- a/gui/src/shared/connect-helper.ts
+++ /dev/null
@@ -1,34 +0,0 @@
-import { TunnelState } from './daemon-rpc-types';
-
-export function connectEnabled(
- connectedToDaemon: boolean,
- loggedIn: boolean,
- tunnelState: TunnelState['state'],
-) {
- return (
- connectedToDaemon &&
- loggedIn &&
- (tunnelState === 'disconnected' || tunnelState === 'disconnecting' || tunnelState === 'error')
- );
-}
-
-export function reconnectEnabled(
- connectedToDaemon: boolean,
- loggedIn: boolean,
- tunnelState: TunnelState['state'],
-) {
- return (
- connectedToDaemon &&
- loggedIn &&
- (tunnelState === 'connected' || tunnelState === 'connecting' || tunnelState === 'error')
- );
-}
-
-// Disconnecting while logged out is allowed since it's possible to "connect" and end up in the
-// blocked state with the CLI.
-export function disconnectEnabled(connectedToDaemon: boolean, tunnelState: TunnelState['state']) {
- return (
- connectedToDaemon &&
- (tunnelState === 'connected' || tunnelState === 'connecting' || tunnelState === 'error')
- );
-}
diff --git a/gui/src/shared/daemon-rpc-types.ts b/gui/src/shared/daemon-rpc-types.ts
deleted file mode 100644
index 1522d43b39..0000000000
--- a/gui/src/shared/daemon-rpc-types.ts
+++ /dev/null
@@ -1,626 +0,0 @@
-export interface IAccountData {
- expiry: string;
-}
-
-export type AccountDataError = {
- type: 'error';
- error: 'invalid-account' | 'too-many-devices' | 'list-devices' | 'communication';
-};
-
-export type AccountDataResponse = ({ type: 'success' } & IAccountData) | AccountDataError;
-
-export type AccountNumber = string;
-export type Ip = string;
-export interface ILocation {
- ipv4?: string;
- ipv6?: string;
- country: string;
- city?: string;
- latitude: number;
- longitude: number;
- mullvadExitIp: boolean;
- hostname?: string;
- bridgeHostname?: string;
- entryHostname?: string;
- provider?: string;
-}
-
-export enum FirewallPolicyErrorType {
- generic,
- locked,
-}
-
-export type FirewallPolicyError =
- | { type: FirewallPolicyErrorType.generic }
- | {
- type: FirewallPolicyErrorType.locked;
- name: string;
- pid: number;
- };
-
-export enum ErrorStateCause {
- authFailed,
- ipv6Unavailable,
- setFirewallPolicyError,
- setDnsError,
- startTunnelError,
- createTunnelDeviceError,
- tunnelParameterError,
- isOffline,
- splitTunnelError,
- needFullDiskPermissions,
-}
-
-export enum AuthFailedError {
- unknown,
- invalidAccount,
- expiredAccount,
- tooManyConnections,
-}
-
-export enum TunnelParameterError {
- noMatchingRelay,
- noMatchingBridgeRelay,
- noWireguardKey,
- customTunnelHostResolutionError,
-}
-
-export type ErrorStateDetails =
- | {
- cause:
- | ErrorStateCause.ipv6Unavailable
- | ErrorStateCause.setDnsError
- | ErrorStateCause.startTunnelError
- | ErrorStateCause.isOffline
- | ErrorStateCause.splitTunnelError
- | ErrorStateCause.needFullDiskPermissions;
- blockingError?: FirewallPolicyError;
- }
- | {
- cause: ErrorStateCause.authFailed;
- blockingError?: FirewallPolicyError;
- authFailedError: AuthFailedError;
- }
- | {
- cause: ErrorStateCause.createTunnelDeviceError;
- blockingError?: FirewallPolicyError;
- osError?: number;
- }
- | {
- cause: ErrorStateCause.tunnelParameterError;
- blockingError?: FirewallPolicyError;
- parameterError: TunnelParameterError;
- }
- | {
- cause: ErrorStateCause.setFirewallPolicyError;
- blockingError?: FirewallPolicyError;
- policyError: FirewallPolicyError;
- };
-
-export type AfterDisconnect = 'nothing' | 'block' | 'reconnect';
-
-export type TunnelType = 'any' | 'wireguard' | 'openvpn';
-export function tunnelTypeToString(tunnel: TunnelType): string {
- switch (tunnel) {
- case 'wireguard':
- return 'WireGuard';
- case 'openvpn':
- return 'OpenVPN';
- case 'any':
- return '';
- }
-}
-
-export type RelayProtocol = 'tcp' | 'udp';
-export type EndpointObfuscationType = 'udp2tcp' | 'shadowsocks';
-
-export type Constraint<T> = 'any' | { only: T };
-export type LiftedConstraint<T> = 'any' | T;
-
-export function liftConstraint<T>(constraint: Constraint<T>): LiftedConstraint<T> {
- return constraint === 'any' ? constraint : constraint.only;
-}
-export function wrapConstraint<T>(
- constraint: LiftedConstraint<T> | undefined | null,
-): Constraint<T> {
- if (constraint) {
- return constraint === 'any' ? 'any' : { only: constraint };
- }
- return 'any';
-}
-
-export type ProxyType = 'shadowsocks' | 'custom';
-
-export enum Ownership {
- any,
- mullvadOwned,
- rented,
-}
-
-export interface ITunnelEndpoint {
- address: string;
- protocol: RelayProtocol;
- tunnelType: TunnelType;
- quantumResistant: boolean;
- proxy?: IProxyEndpoint;
- obfuscationEndpoint?: IObfuscationEndpoint;
- entryEndpoint?: IEndpoint;
- daita: boolean;
-}
-
-export interface IEndpoint {
- address: string;
- transportProtocol: RelayProtocol;
-}
-
-export interface IObfuscationEndpoint {
- address: string;
- port: number;
- protocol: RelayProtocol;
- obfuscationType: EndpointObfuscationType;
-}
-
-export interface IProxyEndpoint {
- address: string;
- protocol: RelayProtocol;
- proxyType: ProxyType;
-}
-
-export type DaemonEvent =
- | { tunnelState: TunnelState }
- | { settings: ISettings }
- | { relayList: IRelayListWithEndpointData }
- | { appVersionInfo: IAppVersionInfo }
- | { device: DeviceEvent }
- | { deviceRemoval: Array<IDevice> }
- | { accessMethodSetting: AccessMethodSetting };
-
-export interface ITunnelStateRelayInfo {
- endpoint: ITunnelEndpoint;
- location?: ILocation;
-}
-
-// The order of the variants match the priority order and can be sorted on.
-export enum FeatureIndicator {
- daita,
- quantumResistance,
- multihop,
- bridgeMode,
- splitTunneling,
- lockdownMode,
- udp2tcp,
- shadowsocks,
- lanSharing,
- dnsContentBlockers,
- customDns,
- serverIpOverride,
- customMtu,
- customMssFix,
-}
-
-export type DisconnectedState = { state: 'disconnected'; location?: Partial<ILocation> };
-export type ConnectingState = {
- state: 'connecting';
- details?: ITunnelStateRelayInfo;
- featureIndicators?: Array<FeatureIndicator>;
-};
-export type ConnectedState = {
- state: 'connected';
- details: ITunnelStateRelayInfo;
- featureIndicators?: Array<FeatureIndicator>;
-};
-export type DisconnectingState = {
- state: 'disconnecting';
- details: AfterDisconnect;
- location?: Partial<ILocation>;
-};
-export type ErrorState = { state: 'error'; details: ErrorStateDetails };
-
-export type TunnelState =
- | DisconnectedState
- | ConnectingState
- | ConnectedState
- | DisconnectingState
- | ErrorState;
-
-export interface RelayLocationCountry extends Partial<RelayLocationCustomList> {
- country: string;
-}
-
-export interface RelayLocationCity extends RelayLocationCountry {
- city: string;
-}
-
-export interface RelayLocationRelay extends RelayLocationCity {
- hostname: string;
-}
-
-export interface RelayLocationCustomList {
- customList: string;
-}
-
-export type RelayLocationGeographical =
- | RelayLocationRelay
- | RelayLocationCountry
- | RelayLocationCity;
-
-export type RelayLocation = RelayLocationGeographical | RelayLocationCustomList;
-
-export interface IOpenVpnConstraints {
- port: Constraint<number>;
- protocol: Constraint<RelayProtocol>;
-}
-
-export interface IWireguardConstraints {
- port: Constraint<number>;
- ipVersion: Constraint<IpVersion>;
- useMultihop: boolean;
- entryLocation: Constraint<RelayLocation>;
-}
-
-export type TunnelProtocol = 'wireguard' | 'openvpn';
-
-export type IpVersion = 'ipv4' | 'ipv6';
-
-export interface IRelaySettingsNormal<OpenVpn, Wireguard> {
- location: Constraint<RelayLocation>;
- tunnelProtocol: Constraint<TunnelProtocol>;
- providers: string[];
- ownership: Ownership;
- openvpnConstraints: OpenVpn;
- wireguardConstraints: Wireguard;
-}
-
-export type ConnectionConfig =
- | {
- openvpn: {
- endpoint: {
- ip: string;
- port: number;
- protocol: RelayProtocol;
- };
- username: string;
- };
- }
- | {
- wireguard: {
- tunnel: {
- privateKey: string;
- addresses: string[];
- };
- peer: {
- publicKey: string;
- addresses: string[];
- endpoint: string;
- };
- ipv4Gateway: string;
- ipv6Gateway?: string;
- };
- };
-
-// types describing the structure of RelaySettings
-export interface IRelaySettingsCustom {
- host: string;
- config: ConnectionConfig;
-}
-export type RelaySettings =
- | {
- normal: IRelaySettingsNormal<IOpenVpnConstraints, IWireguardConstraints>;
- }
- | {
- customTunnelEndpoint: IRelaySettingsCustom;
- };
-
-export interface IRelayListWithEndpointData {
- relayList: IRelayList;
- wireguardEndpointData: IWireguardEndpointData;
-}
-
-export interface IRelayList {
- countries: IRelayListCountry[];
-}
-
-export interface IWireguardEndpointData {
- portRanges: [number, number][];
- udp2tcpPorts: number[];
-}
-
-export interface IRelayListCountry {
- name: string;
- code: string;
- cities: IRelayListCity[];
-}
-
-export interface IRelayListCity {
- name: string;
- code: string;
- latitude: number;
- longitude: number;
- relays: IRelayListHostname[];
-}
-
-export interface IRelayListHostname {
- hostname: string;
- provider: string;
- ipv4AddrIn: string;
- includeInCountry: boolean;
- active: boolean;
- weight: number;
- owned: boolean;
- endpointType: RelayEndpointType;
- daita: boolean;
-}
-
-export type RelayEndpointType = 'wireguard' | 'openvpn' | 'bridge';
-
-export interface ITunnelOptions {
- openvpn: {
- mssfix?: number;
- };
- wireguard: {
- mtu?: number;
- quantumResistant?: boolean;
- daita?: IDaitaSettings;
- };
- generic: {
- enableIpv6: boolean;
- };
- dns: IDnsOptions;
-}
-
-export interface IDnsOptions {
- state: 'custom' | 'default';
- customOptions: {
- addresses: string[];
- };
- defaultOptions: {
- blockAds: boolean;
- blockTrackers: boolean;
- blockMalware: boolean;
- blockAdultContent: boolean;
- blockGambling: boolean;
- blockSocialMedia: boolean;
- };
-}
-
-export interface IAppVersionInfo {
- supported: boolean;
- suggestedUpgrade?: string;
- suggestedIsBeta?: boolean;
-}
-
-export interface IAccountAndDevice {
- accountNumber: AccountNumber;
- device?: IDevice;
-}
-
-export type LoggedInDeviceState = { type: 'logged in'; accountAndDevice: IAccountAndDevice };
-export type LoggedOutDeviceState = { type: 'logged out' | 'revoked' };
-
-export type DeviceState = LoggedInDeviceState | LoggedOutDeviceState;
-
-export type DeviceEvent =
- | { type: 'logged in' | 'updated' | 'rotated_key'; deviceState: LoggedInDeviceState }
- | { type: 'logged out' | 'revoked'; deviceState: LoggedOutDeviceState };
-
-export interface IDevice {
- id: string;
- name: string;
- created: Date;
-}
-
-export interface IDeviceRemoval {
- accountNumber: string;
- deviceId: string;
-}
-
-export type CustomLists = Array<ICustomList>;
-
-export interface ICustomList {
- id: string;
- name: string;
- locations: Array<RelayLocationGeographical>;
-}
-
-export type CustomListError = { type: 'name already exists' };
-
-export interface ISettings {
- allowLan: boolean;
- autoConnect: boolean;
- blockWhenDisconnected: boolean;
- showBetaReleases: boolean;
- relaySettings: RelaySettings;
- tunnelOptions: ITunnelOptions;
- bridgeSettings: BridgeSettings;
- bridgeState: BridgeState;
- splitTunnel: SplitTunnelSettings;
- obfuscationSettings: ObfuscationSettings;
- customLists: CustomLists;
- apiAccessMethods: ApiAccessMethodSettings;
- relayOverrides: Array<RelayOverride>;
-}
-
-export type BridgeState = 'auto' | 'on' | 'off';
-
-export type SplitTunnelSettings = {
- enableExclusions: boolean;
- appsList: string[];
-};
-
-export type Udp2TcpObfuscationSettings = {
- port: Constraint<number>;
-};
-
-export type ShadowsocksSettings = {
- port: Constraint<number>;
-};
-
-export enum ObfuscationType {
- auto,
- off,
- udp2tcp,
- shadowsocks,
-}
-
-export type ObfuscationSettings = {
- selectedObfuscation: ObfuscationType;
- udp2tcpSettings: Udp2TcpObfuscationSettings;
- shadowsocksSettings: ShadowsocksSettings;
-};
-
-export interface IBridgeConstraints {
- location: Constraint<RelayLocation>;
- providers: string[];
- ownership: Ownership;
-}
-
-export type BridgeType = 'normal' | 'custom';
-
-export interface BridgeSettings {
- type: BridgeType;
- normal: IBridgeConstraints;
- custom?: CustomProxy;
-}
-
-export interface ISocketAddress {
- host: string;
- port: number;
-}
-
-export type VoucherResponse =
- | { type: 'success'; newExpiry: string; secondsAdded: number }
- | { type: 'invalid' | 'already_used' | 'error' };
-
-export interface SocksAuth {
- username: string;
- password: string;
-}
-
-export type Socks5LocalCustomProxy = {
- type: 'socks5-local';
- remoteIp: string;
- remotePort: number;
- remoteTransportProtocol: RelayProtocol;
- localPort: number;
-};
-
-export type Socks5RemoteCustomProxy = {
- type: 'socks5-remote';
- ip: string;
- port: number;
- authentication?: SocksAuth;
-};
-
-export type ShadowsocksCustomProxy = {
- type: 'shadowsocks';
- ip: string;
- port: number;
- password: string;
- cipher: string;
-};
-
-export type CustomProxy = Socks5LocalCustomProxy | Socks5RemoteCustomProxy | ShadowsocksCustomProxy;
-export type NamedCustomProxy = CustomProxy & { name: string };
-
-export type DirectMethod = { type: 'direct' };
-export type BridgesMethod = { type: 'bridges' };
-export type EncryptedDnsProxy = { type: 'encrypted-dns-proxy' };
-export type AccessMethod = DirectMethod | BridgesMethod | EncryptedDnsProxy | CustomProxy;
-
-export type NamedAccessMethod<T extends AccessMethod> = T & { name: string };
-
-export type NewAccessMethodSetting<T extends AccessMethod = AccessMethod> = NamedAccessMethod<T> & {
- enabled: boolean;
-};
-
-export type AccessMethodSetting<T extends AccessMethod = AccessMethod> =
- NewAccessMethodSetting<T> & {
- id: string;
- };
-
-export type ApiAccessMethodSettings = {
- direct: AccessMethodSetting<DirectMethod>;
- mullvadBridges: AccessMethodSetting<BridgesMethod>;
- encryptedDnsProxy: AccessMethodSetting<EncryptedDnsProxy>;
- custom: Array<AccessMethodSetting<CustomProxy>>;
-};
-
-export interface RelayOverride {
- hostname: string;
- ipv4AddrIn?: string;
- ipv6AddrIn?: string;
-}
-
-export interface IDaitaSettings {
- enabled: boolean;
- directOnly: boolean;
-}
-
-export function parseSocketAddress(socketAddrStr: string): ISocketAddress {
- const re = new RegExp(/(.+):(\d+)$/);
- const matches = socketAddrStr.match(re);
-
- if (!matches || matches.length < 3) {
- throw new Error(`Failed to parse socket address from address string '${socketAddrStr}'`);
- }
- const socketAddress: ISocketAddress = {
- host: matches[1],
- port: Number(matches[2]),
- };
- return socketAddress;
-}
-
-export function compareRelayLocationCount(lhs: RelayLocation, rhs: RelayLocation): boolean {
- if (
- ('count' in lhs || 'count' in rhs) &&
- !('count' in lhs && 'count' in rhs && lhs.count === rhs.count)
- ) {
- return false;
- }
-
- return compareRelayLocation(lhs, rhs);
-}
-
-export function compareRelayLocation(lhs: RelayLocation, rhs: RelayLocation): boolean {
- if (
- ('customList' in lhs || 'customList' in rhs) &&
- !('customList' in lhs && 'customList' in rhs && lhs.customList === rhs.customList)
- ) {
- return false;
- }
-
- return compareRelayLocationGeographical(lhs, rhs);
-}
-
-export function compareRelayLocationGeographical(lhs: RelayLocation, rhs: RelayLocation): boolean {
- if (
- ('country' in lhs || 'country' in rhs) &&
- !('country' in lhs && 'country' in rhs && lhs.country === rhs.country)
- ) {
- return false;
- }
-
- if (
- ('city' in lhs || 'city' in rhs) &&
- !('city' in lhs && 'city' in rhs && lhs.city === rhs.city)
- ) {
- return false;
- }
-
- if (
- ('hostname' in lhs || 'hostname' in rhs) &&
- !('hostname' in lhs && 'hostname' in rhs && lhs.hostname === rhs.hostname)
- ) {
- return false;
- }
-
- return true;
-}
-
-export function compareRelayLocationLoose(lhs?: RelayLocation, rhs?: RelayLocation) {
- if (lhs && rhs) {
- return compareRelayLocation(lhs, rhs);
- } else {
- return lhs === rhs;
- }
-}
diff --git a/gui/src/shared/date-helper.ts b/gui/src/shared/date-helper.ts
deleted file mode 100644
index be83473cfa..0000000000
--- a/gui/src/shared/date-helper.ts
+++ /dev/null
@@ -1,125 +0,0 @@
-import { sprintf } from 'sprintf-js';
-
-import { messages } from './gettext';
-import { capitalize } from './string-helpers';
-
-export type DateType = Date | string | number;
-
-export enum DateComponent {
- day,
- hour,
- minute,
-}
-
-export function dateByAddingComponent(date: DateType, component: DateComponent, value: number) {
- const modifiedDate = new Date(date);
- switch (component) {
- case DateComponent.day:
- modifiedDate.setDate(modifiedDate.getDate() + value);
- break;
- case DateComponent.hour:
- modifiedDate.setHours(modifiedDate.getHours() + value);
- break;
- case DateComponent.minute:
- modifiedDate.setMinutes(modifiedDate.getMinutes() + value);
- break;
- }
-
- return modifiedDate;
-}
-
-export class DateDiff {
- private readonly fromDate: Date;
- private readonly toDate: Date;
-
- public constructor(fromDate: DateType, toDate: DateType) {
- this.fromDate = new Date(fromDate);
- this.toDate = new Date(toDate);
- }
-
- get milliseconds(): number {
- return this.toDate.getTime() - this.fromDate.getTime();
- }
-
- get seconds(): number {
- return this.floor(this.milliseconds / 1000);
- }
-
- get minutes(): number {
- return this.floor(this.seconds / 60);
- }
-
- get hours(): number {
- return this.floor(this.minutes / 60);
- }
-
- get days(): number {
- return this.floor(this.hours / 24);
- }
-
- get months(): number {
- const months = new Date(Math.abs(this.milliseconds)).getUTCMonth();
- const monthsWithSign = this.milliseconds >= 0 ? months : -months;
- return this.years * 12 + monthsWithSign;
- }
-
- get years(): number {
- const years = new Date(Math.abs(this.milliseconds)).getUTCFullYear() - 1970;
- return this.milliseconds >= 0 ? years : -years;
- }
-
- private floor(n: number): number {
- return n >= 0 ? Math.floor(n) : Math.ceil(n);
- }
-}
-
-export interface FormatDateOptions {
- suffix?: boolean;
- displayMonths?: boolean;
- capitalize?: boolean;
-}
-
-// If withSuffix is true then "left" will be added at the end of the remaining time.
-// If noMonths is true then the following applies:
-// If a user has more than 2 years (730 days) left of time it should be displayed in whole years
-// rounded down If a user has less than 2 years left (e.g. 729 days) then this should be displayed
-// in days.
-export function formatRelativeDate(
- fromDate: DateType,
- toDate: DateType,
- options?: FormatDateOptions,
-): string {
- const diff = new DateDiff(fromDate, toDate);
- const years = Math.abs(diff.years);
- const months = Math.abs(diff.months);
- const days = Math.abs(diff.days);
-
- if (isNaN(years) || isNaN(months) || isNaN(days)) {
- return '';
- }
-
- let result = '';
- if (!options?.suffix) {
- if (options?.displayMonths ? years > 0 : days >= 730) {
- result = sprintf(messages.ngettext('1 year', '%d years', years), years);
- } else if (options?.displayMonths && months >= 3) {
- result = sprintf(messages.ngettext('1 month', '%d months', months), months);
- } else if (days > 0) {
- result = sprintf(messages.ngettext('1 day', '%d days', days), days);
- } else {
- result = messages.gettext('less than a day');
- }
- } else if (diff.milliseconds > 0) {
- if (options?.displayMonths ? years > 0 : days >= 730) {
- result = sprintf(messages.ngettext('1 year left', '%d years left', years), years);
- } else if (options?.displayMonths && months >= 3) {
- result = sprintf(messages.ngettext('1 month left', '%d months left', months), months);
- } else if (days > 0) {
- result = sprintf(messages.ngettext('1 day left', '%d days left', days), days);
- } else {
- result = messages.gettext('less than a day left');
- }
- }
-
- return options?.capitalize ? capitalize(result) : result;
-}
diff --git a/gui/src/shared/gettext.ts b/gui/src/shared/gettext.ts
deleted file mode 100644
index 90391107e0..0000000000
--- a/gui/src/shared/gettext.ts
+++ /dev/null
@@ -1,32 +0,0 @@
-import Gettext from 'node-gettext';
-
-import { LocalizationContexts } from './localization-contexts';
-import log from './logging';
-
-const SOURCE_LANGUAGE = 'en';
-
-function setErrorHandler(catalogue: Gettext) {
- catalogue.on('error', (error) => {
- log.debug(`Gettext error: ${error.message}`);
- });
-}
-
-const gettextOptions = { sourceLocale: SOURCE_LANGUAGE };
-
-declare class GettextWithAppContexts extends Gettext {
- pgettext(msgctxt: LocalizationContexts, msgid: string): string;
- npgettext(
- msgctxt: LocalizationContexts,
- msgid: string,
- msgidPlural: string,
- count: number,
- ): string;
-}
-
-export const messages = new Gettext(gettextOptions) as GettextWithAppContexts;
-messages.setTextDomain('messages');
-setErrorHandler(messages);
-
-export const relayLocations = new Gettext(gettextOptions);
-relayLocations.setTextDomain('relay-locations');
-setErrorHandler(relayLocations);
diff --git a/gui/src/shared/gui-settings-state.ts b/gui/src/shared/gui-settings-state.ts
deleted file mode 100644
index 68e958324a..0000000000
--- a/gui/src/shared/gui-settings-state.ts
+++ /dev/null
@@ -1,37 +0,0 @@
-// This is a special value which is when contained within IGuiSettingsState.preferredLocale
-// indicates that app should use the active operating system locale to determine the UI language.
-export const SYSTEM_PREFERRED_LOCALE_KEY = 'system';
-
-export interface IGuiSettingsState {
- // A user interface locale.
- // Use 'system' to opt-in for active locale set in the operating system
- // (see SYSTEM_PREFERRED_LOCALE_KEY)
- preferredLocale: string;
-
- // Enable or disable system notifications on tunnel state etc.
- enableSystemNotifications: boolean;
-
- // Tells the app to activate auto-connect feature in the mullvad-daemon, but only if the app is
- // set to auto-start with the system.
- autoConnect: boolean;
-
- // Tells the app to use monochromatic set of icons for tray.
- monochromaticIcon: boolean;
-
- // Tells the app to hide the main window on start.
- startMinimized: boolean;
-
- // Tells the app whether or not it should act as a window or a context menu.
- unpinnedWindow: boolean;
-
- // Contains a list of filepaths to applications added to the list of applications, in the split
- // tunneling view, by the user.
- browsedForSplitTunnelingApplications: Array<string>;
-
- // The last version that the changelog dialog was shown for. This is used to only show the
- // changelog after upgrade.
- changelogDisplayedForVersion: string;
-
- // Tells the app whether or not to show the map in the main view.
- animateMap: boolean;
-}
diff --git a/gui/src/shared/ipc-helpers.ts b/gui/src/shared/ipc-helpers.ts
deleted file mode 100644
index c49b7df9b8..0000000000
--- a/gui/src/shared/ipc-helpers.ts
+++ /dev/null
@@ -1,199 +0,0 @@
-import { IpcMain as EIpcMain, IpcRenderer as EIpcRenderer, WebContents } from 'electron';
-
-import log from './logging';
-import { capitalize } from './string-helpers';
-
-type Handler<T, R> = (callback: (arg: T) => R) => void;
-type Sender<T, R> = (arg: T) => R;
-type Notifier<T> = ((arg: T) => void) | undefined;
-type Listener<T> = (callback: (arg: T) => void) => () => void;
-
-interface MainToRenderer<T> {
- direction: 'main-to-renderer';
- send: (event: string, webContents: WebContents) => Notifier<T>;
- receive: (event: string, ipcRenderer: EIpcRenderer) => Listener<T>;
-}
-
-interface RendererToMain<T, R> {
- direction: 'renderer-to-main';
- send: (event: string, ipcRenderer: EIpcRenderer) => Sender<T, R>;
- receive: (event: string, ipcMain: EIpcMain) => Handler<T, R>;
-}
-
-// eslint-disable-next-line @typescript-eslint/no-explicit-any
-type AnyIpcCall = MainToRenderer<any> | RendererToMain<any, any>;
-
-export type Schema = Record<string, Record<string, AnyIpcCall>>;
-
-// Renames all IPC calls, e.g. `callName` to either `notifyCallName` or `handleCallName` depending
-// on direction.
-type IpcMainKey<N extends string, I extends AnyIpcCall> = I['direction'] extends 'main-to-renderer'
- ? `notify${Capitalize<N>}`
- : `handle${Capitalize<N>}`;
-
-// Selects either the send or receive function depending on direction.
-type IpcMainFn<I extends AnyIpcCall> = I['direction'] extends 'main-to-renderer'
- ? ReturnType<I['send']>
- : ReturnType<I['receive']>;
-
-// Renames all receiving IPC calls, e.g. `callName` to `listenCallName`.
-type IpcRendererKey<
- N extends string,
- I extends AnyIpcCall,
-> = I['direction'] extends 'main-to-renderer' ? `listen${Capitalize<N>}` : N;
-
-// Selects either the send or receive function depending on direction.
-type IpcRendererFn<I extends AnyIpcCall> = I['direction'] extends 'main-to-renderer'
- ? ReturnType<I['receive']>
- : ReturnType<I['send']>;
-
-// Transforms the provided schema to the correct type for the main event channel.
-export type IpcMain<S extends Schema> = {
- [G in keyof S]: {
- [K in keyof S[G] as IpcMainKey<string & K, S[G][K]>]: IpcMainFn<S[G][K]>;
- };
-};
-
-// Transforms the provided schema to the correct type for the renderer event channel.
-export type IpcRenderer<S extends Schema> = {
- [G in keyof S]: {
- [K in keyof S[G] as IpcRendererKey<string & K, S[G][K]>]: IpcRendererFn<S[G][K]>;
- };
-};
-
-// Preforms the transformation of the main event channel in accordance with the above types.
-export function createIpcMain<S extends Schema>(
- schema: S,
- ipcMain: EIpcMain,
- webContents: WebContents | undefined,
-): IpcMain<S> {
- return createIpc(schema, (event, key, spec) => {
- const capitalizedKey = capitalize(key);
- const newKey =
- spec.direction === 'main-to-renderer' ? `notify${capitalizedKey}` : `handle${capitalizedKey}`;
-
- let newValue;
- if (spec.direction === 'main-to-renderer') {
- newValue = webContents ? spec.send(event, webContents) : undefined;
- } else {
- newValue = spec.receive(event, ipcMain);
- }
-
- return [newKey, newValue];
- });
-}
-
-// Preforms the transformation of the renderer event channel in accordance with the above types.
-export function createIpcRenderer<S extends Schema>(
- schema: S,
- ipcRenderer: EIpcRenderer,
-): IpcRenderer<S> {
- return createIpc(schema, (event, key, spec) => {
- const newKey = spec.direction === 'main-to-renderer' ? `listen${capitalize(key)}` : key;
- const newValue =
- spec.direction === 'main-to-renderer'
- ? spec.receive(event, ipcRenderer)
- : spec.send(event, ipcRenderer);
-
- return [newKey, newValue];
- });
-}
-
-function createIpc<S extends Schema, T, R extends IpcMain<S> | IpcRenderer<S>>(
- ipc: S,
- fn: (event: string, key: string, spec: AnyIpcCall) => [newKey: string, newValue: T],
-): R {
- return Object.fromEntries(
- Object.entries(ipc).map(([groupKey, group]) => {
- const newGroup = Object.fromEntries(
- Object.entries(group).map(([key, spec]) => fn(`${groupKey}-${key}`, key, spec)),
- );
- return [groupKey, newGroup];
- }),
- ) as R;
-}
-
-// Sends a request from the renderer process to the main process without any possibility to respond.
-export function send<T>(): RendererToMain<T, void> {
- return {
- direction: 'renderer-to-main',
- send: (event, ipcRenderer) => (newValue: T) => ipcRenderer.send(event, newValue),
- receive: (event, ipcMain) => (handlerFn: (value: T) => void) => {
- ipcMain.on(event, (_event, newValue: T) => {
- handlerFn(newValue);
- });
- },
- };
-}
-
-// Sends a synchronous request from the renderer process to the main process.
-export function invokeSync<T, R>(): RendererToMain<T, R> {
- return {
- direction: 'renderer-to-main',
- send: (event, ipcRenderer) => (newValue: T) => ipcRenderer.sendSync(event, newValue),
- receive: (event, ipcMain) => (handlerFn: (value: T) => R) => {
- ipcMain.on(event, (ipcEvent, newValue: T) => {
- ipcEvent.returnValue = handlerFn(newValue);
- });
- },
- };
-}
-
-// Sends an asynchronous request from the renderer process to the main process.
-export function invoke<T, R>(): RendererToMain<T, Promise<R>> {
- return {
- direction: 'renderer-to-main',
- send: invokeImpl,
- receive: handle,
- };
-}
-
-// Sends a request from the main process to the renderer process without any possibility to respond.
-export function notifyRenderer<T>(): MainToRenderer<T> {
- return {
- direction: 'main-to-renderer',
- send: notifyRendererImpl,
- receive: (event, ipcRenderer) => (fn: (value: T) => void) => {
- const listener = (_event: unknown, newState: T) => fn(newState);
- ipcRenderer.on(event, listener);
- return () => ipcRenderer.off(event, listener);
- },
- };
-}
-
-function notifyRendererImpl<T>(event: string, webContents: WebContents): Notifier<T> {
- return (value) => {
- if (webContents === undefined || webContents.isDestroyed() || webContents.isCrashed()) {
- log.error(`sender(${event}): webContents is already destroyed!`);
- } else {
- webContents.send(event, value);
- }
- };
-}
-
-type RequestResult<T> = { type: 'success'; value: T } | { type: 'error'; message: string };
-
-function invokeImpl<T, R>(event: string, ipcRenderer: EIpcRenderer): Sender<T, Promise<R>> {
- return async (arg: T): Promise<R> => {
- const result: RequestResult<R> = await ipcRenderer.invoke(event, arg);
- switch (result.type) {
- case 'error':
- throw new Error(result.message);
- case 'success':
- return result.value;
- }
- };
-}
-
-function handle<T, R>(event: string, ipcMain: EIpcMain): Handler<T, Promise<R>> {
- return (fn: (arg: T) => Promise<R>) => {
- ipcMain.handle(event, async (_ipcEvent, arg: T) => {
- try {
- return { type: 'success', value: await fn(arg) };
- } catch (e) {
- const error = e as Error;
- return { type: 'error', message: error.message || '' };
- }
- });
- };
-}
diff --git a/gui/src/shared/ipc-schema.ts b/gui/src/shared/ipc-schema.ts
deleted file mode 100644
index a2282e2849..0000000000
--- a/gui/src/shared/ipc-schema.ts
+++ /dev/null
@@ -1,257 +0,0 @@
-import { GetTextTranslations } from 'gettext-parser';
-
-import { ILinuxSplitTunnelingApplication, ISplitTunnelingApplication } from './application-types';
-import {
- AccessMethodSetting,
- AccountDataError,
- AccountNumber,
- BridgeSettings,
- BridgeState,
- CustomListError,
- CustomProxy,
- DeviceEvent,
- DeviceState,
- IAccountData,
- IAppVersionInfo,
- ICustomList,
- IDevice,
- IDeviceRemoval,
- IDnsOptions,
- IRelayListWithEndpointData,
- ISettings,
- NewAccessMethodSetting,
- ObfuscationSettings,
- RelaySettings,
- TunnelState,
- VoucherResponse,
-} from './daemon-rpc-types';
-import { IGuiSettingsState } from './gui-settings-state';
-import { LogLevel } from './logging-types';
-
-interface ILogEntry {
- level: LogLevel;
- message: string;
-}
-import { MapData } from '../renderer/lib/3dmap';
-import { invoke, invokeSync, notifyRenderer, send } from './ipc-helpers';
-import {
- IChangelog,
- ICurrentAppVersionInfo,
- IHistoryObject,
- IWindowShapeParameters,
-} from './ipc-types';
-
-export interface ITranslations {
- locale: string;
- messages?: GetTextTranslations;
- relayLocations?: GetTextTranslations;
-}
-
-export type LaunchApplicationResult = { success: true } | { error: string };
-
-export enum MacOsScrollbarVisibility {
- always,
- whenScrolling,
- automatic,
-}
-
-export interface IAppStateSnapshot {
- isConnected: boolean;
- autoStart: boolean;
- accountData?: IAccountData;
- accountHistory?: AccountNumber;
- tunnelState: TunnelState;
- settings: ISettings;
- isPerformingPostUpgrade: boolean;
- daemonAllowed?: boolean;
- deviceState?: DeviceState;
- relayList?: IRelayListWithEndpointData;
- currentVersion: ICurrentAppVersionInfo;
- upgradeVersion: IAppVersionInfo;
- guiSettings: IGuiSettingsState;
- translations: ITranslations;
- splitTunnelingApplications?: ISplitTunnelingApplication[];
- macOsScrollbarVisibility?: MacOsScrollbarVisibility;
- changelog: IChangelog;
- forceShowChanges: boolean;
- navigationHistory?: IHistoryObject;
- currentApiAccessMethod?: AccessMethodSetting;
- isMacOs13OrNewer: boolean;
-}
-
-// The different types of requests are:
-// * send<ArgumentType>(), which is used for one-way communication from the renderer process to the
-// main process. The main channel will have a property named 'handle<PropertyName>' and the
-// renderer will have a property named the same as the one specified.
-// * invoke<ArgumentType, ReturnType>(), which is used for two-way communication from the renderer
-// process to the main process. The naming is the same as `send<A>()`.
-// * invokeSync<ArgumentType, ReturnType>(), same as `invoke<A, R>()` but synchronous.
-// * notifyRenderer<ArgumentType>(), which is used for one-way communication from the main process
-// to the renderer process. The renderer ipc channel will have a property named
-// `listen<PropertyName>` and the main channel will have a property named `notify<PropertyName>`.
-//
-// Example:
-// const ipc = {
-// groupOfCalls: {
-// first: send<boolean>(),
-// second: request<boolean, number>(),
-// third: requestSync<boolean, number>(),
-// fourth: notifyRenderer<boolean>(),
-// },
-// };
-//
-// createIpcMain(ipc)
-// => {
-// groupOfCalls: {
-// handleFirst: (fn: (arg: boolean) => void) => void,
-// handleSecond: (fn: (arg: boolean) => Promise<number>) => void,
-// handleThird: (fn: (arg: boolean) => number) => void,
-// notifyFourth: (arg: boolean) => void,
-// },
-//
-// createIpcRenderer(ipc)
-// => {
-// groupOfCalls: {
-// first: (arg: boolean) => void,
-// second: (arg: boolean) => Promise<number>,
-// third: (arg: boolean) => number,
-// listenFourth: (fn: (arg: boolean) => void) => void,
-// },
-// }
-export const ipcSchema = {
- state: {
- get: invokeSync<void, IAppStateSnapshot>(),
- },
- map: {
- getData: invoke<void, MapData>(),
- },
- window: {
- shape: notifyRenderer<IWindowShapeParameters>(),
- focus: notifyRenderer<boolean>(),
- macOsScrollbarVisibility: notifyRenderer<MacOsScrollbarVisibility>(),
- scaleFactorChange: notifyRenderer<void>(),
- },
- navigation: {
- reset: notifyRenderer<void>(),
- setHistory: send<IHistoryObject>(),
- },
- daemon: {
- isPerformingPostUpgrade: notifyRenderer<boolean>(),
- daemonAllowed: notifyRenderer<boolean>(),
- connected: notifyRenderer<void>(),
- disconnected: notifyRenderer<void>(),
- },
- relays: {
- '': notifyRenderer<IRelayListWithEndpointData>(),
- },
- customLists: {
- createCustomList: invoke<string, void | CustomListError>(),
- deleteCustomList: invoke<string, void>(),
- updateCustomList: invoke<ICustomList, void | CustomListError>(),
- },
- currentVersion: {
- '': notifyRenderer<ICurrentAppVersionInfo>(),
- displayedChangelog: send<void>(),
- },
- upgradeVersion: {
- '': notifyRenderer<IAppVersionInfo>(),
- },
- app: {
- quit: send<void>(),
- openUrl: invoke<string, void>(),
- showOpenDialog: invoke<Electron.OpenDialogOptions, Electron.OpenDialogReturnValue>(),
- showLaunchDaemonSettings: invoke<void, void>(),
- showFullDiskAccessSettings: invoke<void, void>(),
- getPathBaseName: invoke<string, string>(),
- },
- tunnel: {
- '': notifyRenderer<TunnelState>(),
- connect: invoke<void, void>(),
- disconnect: invoke<void, void>(),
- reconnect: invoke<void, void>(),
- },
- settings: {
- '': notifyRenderer<ISettings>(),
- importFile: invoke<string, void>(),
- importText: invoke<string, void>(),
- apiAccessMethodSettingChange: notifyRenderer<AccessMethodSetting>(),
- setAllowLan: invoke<boolean, void>(),
- setShowBetaReleases: invoke<boolean, void>(),
- setEnableIpv6: invoke<boolean, void>(),
- setBlockWhenDisconnected: invoke<boolean, void>(),
- setBridgeState: invoke<BridgeState, void>(),
- setOpenVpnMssfix: invoke<number | undefined, void>(),
- setWireguardMtu: invoke<number | undefined, void>(),
- setWireguardQuantumResistant: invoke<boolean | undefined, void>(),
- setRelaySettings: invoke<RelaySettings, void>(),
- updateBridgeSettings: invoke<BridgeSettings, void>(),
- setDnsOptions: invoke<IDnsOptions, void>(),
- setObfuscationSettings: invoke<ObfuscationSettings, void>(),
- addApiAccessMethod: invoke<NewAccessMethodSetting, string>(),
- updateApiAccessMethod: invoke<AccessMethodSetting, void>(),
- removeApiAccessMethod: invoke<string, void>(),
- setApiAccessMethod: invoke<string, void>(),
- testApiAccessMethodById: invoke<string, boolean>(),
- testCustomApiAccessMethod: invoke<CustomProxy, boolean>(),
- clearAllRelayOverrides: invoke<void, void>(),
- setEnableDaita: invoke<boolean, void>(),
- setDaitaDirectOnly: invoke<boolean, void>(),
- },
- guiSettings: {
- '': notifyRenderer<IGuiSettingsState>(),
- setEnableSystemNotifications: send<boolean>(),
- setAutoConnect: send<boolean>(),
- setStartMinimized: send<boolean>(),
- setMonochromaticIcon: send<boolean>(),
- setPreferredLocale: invoke<string, ITranslations>(),
- setUnpinnedWindow: send<boolean>(),
- setAnimateMap: send<boolean>(),
- },
- account: {
- '': notifyRenderer<IAccountData | undefined>(),
- device: notifyRenderer<DeviceEvent>(),
- devices: notifyRenderer<Array<IDevice>>(),
- create: invoke<void, string>(),
- login: invoke<AccountNumber, AccountDataError | undefined>(),
- logout: invoke<void, void>(),
- getWwwAuthToken: invoke<void, string>(),
- submitVoucher: invoke<string, VoucherResponse>(),
- updateData: send<void>(),
- listDevices: invoke<AccountNumber, Array<IDevice>>(),
- removeDevice: invoke<IDeviceRemoval, void>(),
- },
- accountHistory: {
- '': notifyRenderer<AccountNumber | undefined>(),
- clear: invoke<void, void>(),
- },
- autoStart: {
- '': notifyRenderer<boolean>(),
- set: invoke<boolean, void>(),
- },
- problemReport: {
- collectLogs: invoke<string | undefined, string>(),
- sendReport: invoke<{ email: string; message: string; savedReportId: string }, void>(),
- viewLog: invoke<string, string>(),
- },
- logging: {
- log: send<ILogEntry>(),
- },
- linuxSplitTunneling: {
- getApplications: invoke<void, ILinuxSplitTunnelingApplication[]>(),
- launchApplication: invoke<ILinuxSplitTunnelingApplication | string, LaunchApplicationResult>(),
- },
- macOsSplitTunneling: {
- needFullDiskPermissions: invoke<void, boolean>(),
- },
- splitTunneling: {
- '': notifyRenderer<ISplitTunnelingApplication[]>(),
- setState: invoke<boolean, void>(),
- getApplications: invoke<
- boolean,
- { fromCache: boolean; applications: ISplitTunnelingApplication[] }
- >(),
- addApplication: invoke<ISplitTunnelingApplication | string, void>(),
- removeApplication: invoke<ISplitTunnelingApplication, void>(),
- forgetManuallyAddedApplication: invoke<ISplitTunnelingApplication, void>(),
- },
-};
diff --git a/gui/src/shared/ipc-types.ts b/gui/src/shared/ipc-types.ts
deleted file mode 100644
index c186c9ac83..0000000000
--- a/gui/src/shared/ipc-types.ts
+++ /dev/null
@@ -1,30 +0,0 @@
-import { Action, Location } from 'history';
-
-import { ITransitionSpecification } from '../renderer/lib/history';
-
-export interface ICurrentAppVersionInfo {
- gui: string;
- daemon?: string;
- isConsistent: boolean;
- isBeta: boolean;
-}
-
-export interface IWindowShapeParameters {
- arrowPosition?: number;
-}
-
-export type IChangelog = Array<string>;
-
-export interface LocationState {
- scrollPosition: [number, number];
- expandedSections: Record<string, boolean>;
- transition: ITransitionSpecification;
-}
-
-export interface IHistoryObject {
- entries: Location<LocationState>[];
- index: number;
- lastAction: Action;
-}
-
-export type ScrollPositions = Record<string, [number, number]>;
diff --git a/gui/src/shared/localization-contexts.ts b/gui/src/shared/localization-contexts.ts
deleted file mode 100644
index f30212025c..0000000000
--- a/gui/src/shared/localization-contexts.ts
+++ /dev/null
@@ -1,40 +0,0 @@
-export type LocalizationContexts =
- | 'changelog'
- | 'accessibility'
- | 'login-view'
- | 'device-management'
- | 'auth-failure'
- | 'launch-view'
- | 'error-boundary-view'
- | 'connect-container'
- | 'connect-view'
- | 'tunnel-control'
- | 'connection-info'
- | 'notifications'
- | 'in-app-notifications'
- | 'account-expiry'
- | 'select-location-view'
- | 'select-location-nav'
- | 'custom-bridge'
- | 'filter-view'
- | 'filter-nav'
- | 'settings-view'
- | 'navigation-bar'
- | 'account-view'
- | 'redeem-voucher-view'
- | 'redeem-voucher-alert'
- | 'user-interface-settings-view'
- | 'vpn-settings-view'
- | 'wireguard-settings-view'
- | 'wireguard-settings-nav'
- | 'openvpn-settings-view'
- | 'openvpn-settings-nav'
- | 'split-tunneling-view'
- | 'split-tunneling-nav'
- | 'api-access-methods-view'
- | 'settings-import'
- | 'support-view'
- | 'select-language-nav'
- | 'tray-icon-context-menu'
- | 'tray-icon-tooltip'
- | 'troubleshoot';
diff --git a/gui/src/shared/logging-types.ts b/gui/src/shared/logging-types.ts
deleted file mode 100644
index 8b4ff9e306..0000000000
--- a/gui/src/shared/logging-types.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-export enum LogLevel {
- error,
- warning,
- info,
- verbose,
- debug,
-}
-
-export interface ILogOutput {
- level: LogLevel;
- write(level: LogLevel, message: string): void | Promise<void>;
- dispose?(): void;
-}
-
-export interface ILogInput {
- on(handler: (level: LogLevel, message: string) => void): void;
-}
diff --git a/gui/src/shared/logging.ts b/gui/src/shared/logging.ts
deleted file mode 100644
index c685289bf8..0000000000
--- a/gui/src/shared/logging.ts
+++ /dev/null
@@ -1,108 +0,0 @@
-import { ILogInput, ILogOutput, LogLevel } from './logging-types';
-
-export class Logger {
- private outputs: ILogOutput[] = [];
-
- public addOutput(output: ILogOutput) {
- this.outputs.push(output);
- }
-
- public addInput(input: ILogInput) {
- input.on((level: LogLevel, message: string) => this.outputMessage(level, message));
- }
-
- public log(level: LogLevel, ...data: unknown[]) {
- const time = this.getDateString();
- const stringifiedData = data.map(this.stringifyData).join(' ');
- const message = `[${time}][${LogLevel[level]}] ${stringifiedData}`;
-
- this.outputMessage(level, message);
- }
-
- public error = (...data: unknown[]) => this.log(LogLevel.error, ...data);
- public warn = (...data: unknown[]) => this.log(LogLevel.warning, ...data);
- public info = (...data: unknown[]) => this.log(LogLevel.info, ...data);
- public verbose = (...data: unknown[]) => this.log(LogLevel.verbose, ...data);
- public debug = (...data: unknown[]) => this.log(LogLevel.debug, ...data);
-
- public disposeDisposableOutputs() {
- // Keep the outputs that aren't disposable to continue to forward log messages to them.
- this.outputs = this.outputs.filter((output) => {
- output.dispose?.();
- return output.dispose === undefined;
- });
- }
-
- private getDateString(): string {
- const date = new Date();
- const year = date.getFullYear();
- const month = Number(date.getMonth() + 1)
- .toString()
- .padStart(2, '0');
- const day = Number(date.getDate()).toString().padStart(2, '0');
- const hour = Number(date.getHours()).toString().padStart(2, '0');
- const minute = Number(date.getMinutes()).toString().padStart(2, '0');
- const second = Number(date.getSeconds()).toString().padStart(2, '0');
- const millisecond = Number(date.getMilliseconds()).toString().padStart(3, '0');
- return `${year}-${month}-${day} ${hour}:${minute}:${second}.${millisecond}`;
- }
-
- private stringifyData(data: unknown): string {
- return typeof data === 'string' ? data : JSON.stringify(data);
- }
-
- private outputMessage(level: LogLevel, message: string) {
- this.outputs
- .filter((output) => level <= output.level)
- .forEach(async (output) => {
- try {
- await output.write(level, message);
- } catch (e) {
- const error = e as Error;
- console.error(
- `${output.constructor.name}.write: ${error.message}. Original message: ${message}`,
- );
- }
- });
- }
-}
-
-export class ConsoleOutput implements ILogOutput {
- private disabled = false;
-
- constructor(public level: LogLevel) {}
-
- public write(level: LogLevel, message: string) {
- if (this.disabled) {
- return;
- }
-
- try {
- switch (level) {
- case LogLevel.error:
- console.error(message);
- break;
- case LogLevel.warning:
- console.warn(message);
- break;
- case LogLevel.info:
- console.info(message);
- break;
- case LogLevel.verbose:
- console.log(message);
- break;
- case LogLevel.debug:
- console.log(message);
- break;
- }
- } catch (error) {
- this.disabled = true;
-
- const message = error instanceof Object && 'message' in error ? error.message : '';
- logger.error('Disabling console output due to:', message, error);
- }
- }
-}
-
-const logger = new Logger();
-export default logger;
diff --git a/gui/src/shared/notifications/account-expired.ts b/gui/src/shared/notifications/account-expired.ts
deleted file mode 100644
index a7af4f2c8b..0000000000
--- a/gui/src/shared/notifications/account-expired.ts
+++ /dev/null
@@ -1,42 +0,0 @@
-import { links } from '../../config.json';
-import { hasExpired } from '../account-expiry';
-import { TunnelState } from '../daemon-rpc-types';
-import { messages } from '../gettext';
-import {
- SystemNotification,
- SystemNotificationCategory,
- SystemNotificationProvider,
- SystemNotificationSeverityType,
-} from './notification';
-
-interface AccountExpiredNotificaitonContext {
- accountExpiry: string;
- tunnelState: TunnelState;
-}
-
-export class AccountExpiredNotificationProvider implements SystemNotificationProvider {
- public constructor(private context: AccountExpiredNotificaitonContext) {}
-
- public mayDisplay() {
- // Only show when disconnected since the error state handles this if the connection is closed
- // due to account expiry.
- return (
- this.context.tunnelState.state === 'disconnected' && hasExpired(this.context.accountExpiry)
- );
- }
-
- public getSystemNotification(): SystemNotification {
- return {
- message: messages.pgettext('notifications', 'Account is out of time'),
- category: SystemNotificationCategory.expiry,
- severity: SystemNotificationSeverityType.high,
- presentOnce: { value: true, name: this.constructor.name },
- action: {
- type: 'open-url',
- url: links.purchase,
- withAuth: true,
- text: messages.pgettext('notifications', 'Buy more'),
- },
- };
- }
-}
diff --git a/gui/src/shared/notifications/block-when-disconnected.ts b/gui/src/shared/notifications/block-when-disconnected.ts
deleted file mode 100644
index 2f3df718b6..0000000000
--- a/gui/src/shared/notifications/block-when-disconnected.ts
+++ /dev/null
@@ -1,66 +0,0 @@
-import { sprintf } from 'sprintf-js';
-
-import { strings } from '../../config.json';
-import { messages } from '../../shared/gettext';
-import { TunnelState } from '../daemon-rpc-types';
-import {
- InAppNotification,
- InAppNotificationProvider,
- SystemNotification,
- SystemNotificationCategory,
- SystemNotificationProvider,
- SystemNotificationSeverityType,
-} from './notification';
-
-interface BlockWhenDisconnectedNotificationContext {
- tunnelState: TunnelState;
- blockWhenDisconnected: boolean;
- hasExcludedApps: boolean;
-}
-
-export class BlockWhenDisconnectedNotificationProvider
- implements InAppNotificationProvider, SystemNotificationProvider
-{
- public constructor(private context: BlockWhenDisconnectedNotificationContext) {}
-
- public mayDisplay() {
- return (
- (this.context.tunnelState.state === 'disconnecting' ||
- this.context.tunnelState.state === 'disconnected') &&
- this.context.blockWhenDisconnected
- );
- }
-
- public getSystemNotification(): SystemNotification {
- const message = messages.pgettext('notifications', 'Lockdown mode active, connection blocked');
-
- return {
- message,
- severity: SystemNotificationSeverityType.info,
- category: SystemNotificationCategory.tunnelState,
- };
- }
-
- public getInAppNotification(): InAppNotification {
- const lockdownModeSettingName = messages.pgettext('vpn-settings-view', 'Lockdown mode');
- let subtitle = sprintf(
- messages.pgettext('in-app-notifications', '"%(lockdownModeSettingName)s" is enabled.'),
- { lockdownModeSettingName },
- );
- if (this.context.hasExcludedApps) {
- subtitle = `${subtitle} ${sprintf(
- messages.pgettext(
- 'notifications',
- 'The apps excluded with %(splitTunneling)s might not work properly right now.',
- ),
- { splitTunneling: strings.splitTunneling.toLowerCase() },
- )}`;
- }
-
- return {
- indicator: 'warning',
- title: messages.pgettext('in-app-notifications', 'BLOCKING INTERNET'),
- subtitle,
- };
- }
-}
diff --git a/gui/src/shared/notifications/close-to-account-expiry.ts b/gui/src/shared/notifications/close-to-account-expiry.ts
deleted file mode 100644
index 4fde6ab395..0000000000
--- a/gui/src/shared/notifications/close-to-account-expiry.ts
+++ /dev/null
@@ -1,72 +0,0 @@
-import { sprintf } from 'sprintf-js';
-
-import { links } from '../../config.json';
-import { messages } from '../../shared/gettext';
-import { closeToExpiry, formatRemainingTime } from '../account-expiry';
-import {
- InAppNotification,
- InAppNotificationProvider,
- SystemNotification,
- SystemNotificationCategory,
- SystemNotificationProvider,
- SystemNotificationSeverityType,
-} from './notification';
-
-interface CloseToAccountExpiryNotificationContext {
- accountExpiry: string;
- locale: string;
-}
-
-export class CloseToAccountExpiryNotificationProvider
- implements InAppNotificationProvider, SystemNotificationProvider
-{
- public constructor(private context: CloseToAccountExpiryNotificationContext) {}
-
- public mayDisplay = () => closeToExpiry(this.context.accountExpiry);
-
- public getSystemNotification(): SystemNotification {
- const message = sprintf(
- // TRANSLATORS: The system notification displayed to the user when the account credit is close to expiry.
- // TRANSLATORS: Available placeholder:
- // TRANSLATORS: %(duration)s - remaining time, e.g. "2 days"
- messages.pgettext(
- 'notifications',
- 'Account credit expires in %(duration)s. Buy more credit.',
- ),
- {
- duration: formatRemainingTime(this.context.accountExpiry),
- },
- );
-
- return {
- message,
- category: SystemNotificationCategory.expiry,
- severity: SystemNotificationSeverityType.medium,
- action: {
- type: 'open-url',
- url: links.purchase,
- withAuth: true,
- text: messages.pgettext('notifications', 'Buy more'),
- },
- };
- }
-
- public getInAppNotification(): InAppNotification {
- const subtitle = sprintf(
- messages.pgettext('in-app-notifications', '%(duration)s. Buy more credit.'),
- {
- duration: formatRemainingTime(this.context.accountExpiry, {
- capitalize: true,
- suffix: true,
- }),
- },
- );
-
- return {
- indicator: 'warning',
- title: messages.pgettext('in-app-notifications', 'ACCOUNT CREDIT EXPIRES SOON'),
- subtitle,
- action: { type: 'open-url', url: links.purchase, withAuth: true },
- };
- }
-}
diff --git a/gui/src/shared/notifications/connected.ts b/gui/src/shared/notifications/connected.ts
deleted file mode 100644
index c66339fe9e..0000000000
--- a/gui/src/shared/notifications/connected.ts
+++ /dev/null
@@ -1,42 +0,0 @@
-import { sprintf } from 'sprintf-js';
-
-import { messages } from '../../shared/gettext';
-import { TunnelState } from '../daemon-rpc-types';
-import {
- SystemNotification,
- SystemNotificationCategory,
- SystemNotificationProvider,
- SystemNotificationSeverityType,
-} from './notification';
-
-export class ConnectedNotificationProvider implements SystemNotificationProvider {
- public constructor(private context: TunnelState) {}
-
- public mayDisplay = () => this.context.state === 'connected';
-
- public getSystemNotification(): SystemNotification | undefined {
- if (this.context.state === 'connected') {
- let message = messages.pgettext('notifications', 'Connected');
- const location = this.context.details.location?.hostname;
- if (location) {
- message = sprintf(
- // TRANSLATORS: The message showed when a server has been connected to.
- // TRANSLATORS: Available placeholder:
- // TRANSLATORS: %(location) - name of the server location we're connected to (e.g. "se-got-003")
- messages.pgettext('notifications', 'Connected to %(location)s'),
- {
- location,
- },
- );
- }
-
- return {
- message,
- severity: SystemNotificationSeverityType.info,
- category: SystemNotificationCategory.tunnelState,
- };
- } else {
- return undefined;
- }
- }
-}
diff --git a/gui/src/shared/notifications/connecting.ts b/gui/src/shared/notifications/connecting.ts
deleted file mode 100644
index 444dd42beb..0000000000
--- a/gui/src/shared/notifications/connecting.ts
+++ /dev/null
@@ -1,60 +0,0 @@
-import { sprintf } from 'sprintf-js';
-
-import { messages } from '../../shared/gettext';
-import { TunnelState } from '../daemon-rpc-types';
-import {
- InAppNotification,
- InAppNotificationProvider,
- SystemNotification,
- SystemNotificationCategory,
- SystemNotificationProvider,
- SystemNotificationSeverityType,
-} from './notification';
-
-interface ConnectingNotificationContext {
- tunnelState: TunnelState;
- reconnecting?: boolean;
-}
-
-export class ConnectingNotificationProvider
- implements SystemNotificationProvider, InAppNotificationProvider
-{
- public constructor(private context: ConnectingNotificationContext) {}
-
- public mayDisplay() {
- return this.context.tunnelState.state === 'connecting' && !this.context.reconnecting;
- }
-
- public getSystemNotification(): SystemNotification | undefined {
- if (this.context.tunnelState.state === 'connecting') {
- let message = messages.pgettext('notifications', 'Connecting');
- const location = this.context.tunnelState.details?.location?.hostname;
- if (location) {
- message = sprintf(
- // TRANSLATORS: The message showed when a server is being connected to.
- // TRANSLATORS: Available placeholder:
- // TRANSLATORS: %(location) - name of the server location we're connecting to (e.g. "se-got-003")
- messages.pgettext('notifications', 'Connecting to %(location)s'),
- {
- location,
- },
- );
- }
-
- return {
- message,
- severity: SystemNotificationSeverityType.info,
- category: SystemNotificationCategory.tunnelState,
- throttle: true,
- };
- } else {
- return undefined;
- }
- }
-
- public getInAppNotification(): InAppNotification {
- return {
- title: messages.pgettext('in-app-notifications', 'BLOCKING INTERNET'),
- };
- }
-}
diff --git a/gui/src/shared/notifications/daemon-disconnected.ts b/gui/src/shared/notifications/daemon-disconnected.ts
deleted file mode 100644
index 50a62266d0..0000000000
--- a/gui/src/shared/notifications/daemon-disconnected.ts
+++ /dev/null
@@ -1,22 +0,0 @@
-import { messages } from '../../shared/gettext';
-import {
- SystemNotification,
- SystemNotificationCategory,
- SystemNotificationProvider,
- SystemNotificationSeverityType,
-} from './notification';
-
-export class DaemonDisconnectedNotificationProvider implements SystemNotificationProvider {
- public mayDisplay = () => true;
-
- public getSystemNotification(): SystemNotification {
- return {
- message: messages.pgettext(
- 'notifications',
- 'Connection might be unsecured. App lost contact with system service, please troubleshoot.',
- ),
- severity: SystemNotificationSeverityType.high,
- category: SystemNotificationCategory.tunnelState,
- };
- }
-}
diff --git a/gui/src/shared/notifications/disconnected.ts b/gui/src/shared/notifications/disconnected.ts
deleted file mode 100644
index 874cb11b3e..0000000000
--- a/gui/src/shared/notifications/disconnected.ts
+++ /dev/null
@@ -1,28 +0,0 @@
-import { messages } from '../../shared/gettext';
-import { TunnelState } from '../daemon-rpc-types';
-import {
- SystemNotification,
- SystemNotificationCategory,
- SystemNotificationProvider,
- SystemNotificationSeverityType,
-} from './notification';
-
-interface DisconnectedNotificationContext {
- tunnelState: TunnelState;
- blockWhenDisconnected: boolean;
-}
-
-export class DisconnectedNotificationProvider implements SystemNotificationProvider {
- public constructor(private context: DisconnectedNotificationContext) {}
-
- public mayDisplay = () =>
- this.context.tunnelState.state === 'disconnected' && !this.context.blockWhenDisconnected;
-
- public getSystemNotification(): SystemNotification | undefined {
- return {
- message: messages.pgettext('notifications', 'Disconnected and unsecure'),
- severity: SystemNotificationSeverityType.info,
- category: SystemNotificationCategory.tunnelState,
- };
- }
-}
diff --git a/gui/src/shared/notifications/error.ts b/gui/src/shared/notifications/error.ts
deleted file mode 100644
index af82748d7b..0000000000
--- a/gui/src/shared/notifications/error.ts
+++ /dev/null
@@ -1,338 +0,0 @@
-import { sprintf } from 'sprintf-js';
-
-import { strings } from '../../config.json';
-import {
- AuthFailedError,
- ErrorStateCause,
- ErrorStateDetails,
- TunnelParameterError,
- TunnelState,
-} from '../daemon-rpc-types';
-import { messages } from '../gettext';
-import {
- InAppNotification,
- InAppNotificationAction,
- InAppNotificationProvider,
- SystemNotification,
- SystemNotificationCategory,
- SystemNotificationProvider,
- SystemNotificationSeverityType,
-} from './notification';
-
-interface ErrorNotificationContext {
- tunnelState: TunnelState;
- hasExcludedApps: boolean;
- showFullDiskAccessSettings?: () => void;
-}
-
-export class ErrorNotificationProvider
- implements SystemNotificationProvider, InAppNotificationProvider
-{
- public constructor(private context: ErrorNotificationContext) {}
-
- public mayDisplay = () => this.context.tunnelState.state === 'error';
-
- public getSystemNotification(): SystemNotification | undefined {
- if (this.context.tunnelState.state === 'error') {
- let message = this.getMessage(this.context.tunnelState.details);
- if (!this.context.tunnelState.details.blockingError && this.context.hasExcludedApps) {
- message = `${message} ${sprintf(
- messages.pgettext(
- 'notifications',
- 'The apps excluded with %(splitTunneling)s might not work properly right now.',
- ),
- { splitTunneling: strings.splitTunneling.toLowerCase() },
- )}`;
- }
-
- return {
- message,
- severity:
- this.context.tunnelState.details.blockingError === undefined
- ? SystemNotificationSeverityType.low
- : SystemNotificationSeverityType.high,
- category: SystemNotificationCategory.tunnelState,
- };
- } else {
- return undefined;
- }
- }
-
- public getInAppNotification(): InAppNotification | undefined {
- if (this.context.tunnelState.state === 'error') {
- let subtitle = this.getMessage(this.context.tunnelState.details);
- if (!this.context.tunnelState.details.blockingError && this.context.hasExcludedApps) {
- subtitle = `${subtitle} ${sprintf(
- messages.pgettext(
- 'notifications',
- 'The apps excluded with %(splitTunneling)s might not work properly right now.',
- ),
- { splitTunneling: strings.splitTunneling.toLowerCase() },
- )}`;
- }
-
- return {
- indicator:
- this.context.tunnelState.details.cause === ErrorStateCause.isOffline
- ? 'warning'
- : 'error',
- title: this.context.tunnelState.details.blockingError
- ? messages.pgettext('in-app-notifications', 'NETWORK TRAFFIC MIGHT BE LEAKING')
- : messages.pgettext('in-app-notifications', 'BLOCKING INTERNET'),
- subtitle,
- action: this.getActions(this.context.tunnelState.details) ?? undefined,
- };
- } else {
- return undefined;
- }
- }
-
- private getMessage(errorState: ErrorStateDetails): string {
- if (errorState.blockingError) {
- if (errorState.cause === ErrorStateCause.setFirewallPolicyError) {
- switch (process.platform ?? window.env.platform) {
- case 'win32':
- return messages.pgettext(
- 'notifications',
- 'Unable to block all network traffic. Try temporarily disabling any third-party antivirus or security software or send a problem report.',
- );
- case 'linux':
- return messages.pgettext(
- 'notifications',
- 'Unable to block all network traffic. Try updating your kernel or send a problem report.',
- );
- }
- }
-
- return messages.pgettext(
- 'notifications',
- 'Unable to block all network traffic. Please troubleshoot or send a problem report.',
- );
- } else {
- switch (errorState.cause) {
- case ErrorStateCause.authFailed:
- switch (errorState.authFailedError) {
- case AuthFailedError.invalidAccount:
- return messages.pgettext(
- 'auth-failure',
- 'You are logged in with an invalid account number. Please log out and try another one.',
- );
-
- case AuthFailedError.expiredAccount:
- return messages.pgettext('auth-failure', 'Blocking internet: account is out of time');
-
- case AuthFailedError.tooManyConnections:
- return messages.pgettext(
- 'auth-failure',
- 'Too many simultaneous connections on this account. Disconnect another device or try connecting again shortly.',
- );
-
- case AuthFailedError.unknown:
- default:
- return messages.pgettext(
- 'auth-failure',
- 'Unable to authenticate account. Please send a problem report.',
- );
- }
- case ErrorStateCause.ipv6Unavailable:
- return messages.pgettext(
- 'notifications',
- 'Could not configure IPv6. Disable it in the app or enable it on your device.',
- );
- case ErrorStateCause.setFirewallPolicyError:
- switch (process.platform ?? window.env.platform) {
- case 'win32':
- return messages.pgettext(
- 'notifications',
- 'Unable to apply firewall rules. Try temporarily disabling any third-party antivirus or security software.',
- );
- case 'linux':
- return messages.pgettext(
- 'notifications',
- 'Unable to apply firewall rules. Try updating your kernel.',
- );
- default:
- return messages.pgettext('notifications', 'Unable to apply firewall rules.');
- }
- case ErrorStateCause.setDnsError:
- return messages.pgettext(
- 'notifications',
- 'Unable to set system DNS server. Please send a problem report.',
- );
- case ErrorStateCause.startTunnelError:
- return messages.pgettext(
- 'notifications',
- 'Unable to start tunnel connection. Please send a problem report.',
- );
- case ErrorStateCause.createTunnelDeviceError:
- if (errorState.osError === 4319) {
- return messages.pgettext(
- 'notifications',
- 'Unable to start tunnel connection. This could be because of conflicts with VMware, please troubleshoot.',
- );
- }
-
- return messages.pgettext(
- 'notifications',
- 'Unable to start tunnel connection. Please send a problem report.',
- );
- case ErrorStateCause.tunnelParameterError:
- return this.getTunnelParameterMessage(errorState.parameterError);
- case ErrorStateCause.isOffline:
- return messages.pgettext(
- 'notifications',
- 'Your device is offline. The tunnel will automatically connect once your device is back online.',
- );
- case ErrorStateCause.needFullDiskPermissions:
- return messages.pgettext('notifications', 'Failed to enable split tunneling.');
- case ErrorStateCause.splitTunnelError:
- switch (process.platform ?? window.env.platform) {
- case 'darwin':
- return messages.pgettext(
- 'notifications',
- 'Failed to enable split tunneling. Please try reconnecting or disable split tunneling.',
- );
- default:
- return messages.pgettext(
- 'notifications',
- 'Unable to communicate with Mullvad kernel driver. Try reconnecting or send a problem report.',
- );
- }
- }
- }
- }
-
- private getTunnelParameterMessage(error: TunnelParameterError): string {
- switch (error) {
- /// TODO: once bridge constraints can be set, add a more descriptive error message
- case TunnelParameterError.noMatchingBridgeRelay:
- case TunnelParameterError.noMatchingRelay:
- return messages.pgettext(
- 'notifications',
- 'No servers match your settings, try changing server or other settings.',
- );
- case TunnelParameterError.noWireguardKey:
- return sprintf(
- // TRANSLATORS: Available placeholders:
- // TRANSLATORS: %(wireguard)s - will be replaced with "WireGuard"
- messages.pgettext(
- 'notifications',
- 'Valid %(wireguard)s key is missing. Manage keys under Advanced settings.',
- ),
- { wireguard: strings.wireguard },
- );
- case TunnelParameterError.customTunnelHostResolutionError:
- return messages.pgettext(
- 'notifications',
- 'Unable to resolve host of custom tunnel. Try changing your settings.',
- );
- }
- }
-
- private getActions(errorState: ErrorStateDetails): InAppNotificationAction | void {
- const platform = process.platform ?? window.env.platform;
-
- if (errorState.cause === ErrorStateCause.setFirewallPolicyError && platform === 'linux') {
- return {
- type: 'troubleshoot-dialog',
- troubleshoot: {
- details: messages.pgettext('troubleshoot', 'This might be caused by an outdated kernel.'),
- steps: [
- messages.pgettext('troubleshoot', 'Update your kernel.'),
- messages.pgettext('troubleshoot', 'Make sure you have NF tables support.'),
- ],
- },
- };
- } else if (errorState.cause === ErrorStateCause.setDnsError) {
- const troubleshootSteps = [];
- if (platform === 'darwin') {
- troubleshootSteps.push(
- messages.pgettext(
- 'troubleshoot',
- 'Try to turn Wi-Fi Calling off in the FaceTime app settings and restart the Mac.',
- ),
- messages.pgettext(
- 'troubleshoot',
- 'Uninstall or disable other DNS, networking and ads/website blocking apps.',
- ),
- );
- } else if (platform === 'win32') {
- troubleshootSteps.push(
- messages.pgettext(
- 'troubleshoot',
- 'Uninstall or disable other DNS, networking and ads/website blocking apps.',
- ),
- );
- }
-
- return {
- type: 'troubleshoot-dialog',
- troubleshoot: {
- details: messages.pgettext(
- 'troubleshoot',
- 'This error can happen when something other than Mullvad is actively updating the DNS.',
- ),
- steps: troubleshootSteps,
- },
- };
- } else if (errorState.cause === ErrorStateCause.needFullDiskPermissions) {
- let troubleshootButtons = undefined;
- if (this.context.showFullDiskAccessSettings) {
- troubleshootButtons = [
- {
- label: messages.pgettext('troubleshoot', 'Open system settings'),
- action: () => this.context.showFullDiskAccessSettings?.(),
- },
- ];
- }
-
- return {
- type: 'troubleshoot-dialog',
- troubleshoot: {
- details: messages.pgettext(
- 'troubleshoot',
- 'Failed to enable split tunneling. This is because the app is missing system permissions. What you can do:',
- ),
- steps: [
- messages.pgettext(
- 'troubleshoot',
- 'Enable “Full Disk Access” for “Mullvad VPN” in the macOS system settings.',
- ),
- ],
- buttons: troubleshootButtons,
- },
- };
- } else if (platform === 'win32' && errorState.cause === ErrorStateCause.splitTunnelError) {
- return {
- type: 'troubleshoot-dialog',
- troubleshoot: {
- details: messages.pgettext(
- 'troubleshoot',
- 'Unable to communicate with Mullvad kernel driver.',
- ),
- steps: [
- messages.pgettext('troubleshoot', 'Try reconnecting.'),
- messages.pgettext('troubleshoot', 'Try restarting your device.'),
- ],
- },
- };
- } else if (
- errorState.cause === ErrorStateCause.createTunnelDeviceError &&
- errorState.osError === 4319
- ) {
- return {
- type: 'troubleshoot-dialog',
- troubleshoot: {
- details: messages.pgettext(
- 'troubleshoot',
- 'Unable to start tunnel connection because of a failure when creating the tunnel device. This is often caused by conflicts with the VMware Bridge Protocol.',
- ),
- steps: [
- messages.pgettext('troubleshoot', 'Try to reinstall VMware.'),
- messages.pgettext('troubleshoot', 'Try to uninstall VMware.'),
- ],
- },
- };
- }
- }
-}
diff --git a/gui/src/shared/notifications/inconsistent-version.ts b/gui/src/shared/notifications/inconsistent-version.ts
deleted file mode 100644
index e4f7a8ddc1..0000000000
--- a/gui/src/shared/notifications/inconsistent-version.ts
+++ /dev/null
@@ -1,39 +0,0 @@
-import { messages } from '../../shared/gettext';
-import {
- InAppNotification,
- InAppNotificationProvider,
- SystemNotification,
- SystemNotificationCategory,
- SystemNotificationProvider,
- SystemNotificationSeverityType,
-} from './notification';
-
-interface InconsistentVersionNotificationContext {
- consistent: boolean;
-}
-
-export class InconsistentVersionNotificationProvider
- implements SystemNotificationProvider, InAppNotificationProvider
-{
- public constructor(private context: InconsistentVersionNotificationContext) {}
-
- public mayDisplay = () => !this.context.consistent;
-
- public getSystemNotification(): SystemNotification {
- return {
- message: messages.pgettext('notifications', 'App is out of sync. Please quit and restart.'),
- category: SystemNotificationCategory.inconsistentVersion,
- severity: SystemNotificationSeverityType.high,
- presentOnce: { value: true, name: this.constructor.name },
- suppressInDevelopment: true,
- };
- }
-
- public getInAppNotification(): InAppNotification {
- return {
- indicator: 'error',
- title: messages.pgettext('in-app-notifications', 'APP IS OUT OF SYNC'),
- subtitle: messages.pgettext('in-app-notifications', 'Please quit and restart the app.'),
- };
- }
-}
diff --git a/gui/src/shared/notifications/new-device.ts b/gui/src/shared/notifications/new-device.ts
deleted file mode 100644
index 7d0fe9f299..0000000000
--- a/gui/src/shared/notifications/new-device.ts
+++ /dev/null
@@ -1,32 +0,0 @@
-import { sprintf } from 'sprintf-js';
-
-import { messages } from '../../shared/gettext';
-import { capitalizeEveryWord } from '../string-helpers';
-import { InAppNotification, InAppNotificationProvider } from './notification';
-
-interface NewDeviceNotificationContext {
- shouldDisplay: boolean;
- deviceName: string;
- close: () => void;
-}
-
-export class NewDeviceNotificationProvider implements InAppNotificationProvider {
- public constructor(private context: NewDeviceNotificationContext) {}
-
- public mayDisplay = () => this.context.shouldDisplay;
-
- public getInAppNotification(): InAppNotification {
- return {
- indicator: 'success',
- title: messages.pgettext('in-app-notifications', 'NEW DEVICE CREATED'),
- subtitle: sprintf(
- messages.pgettext(
- 'in-app-notifications',
- 'Welcome, this device is now called <b>%(deviceName)s</b>. For more details see the info button in Account.',
- ),
- { deviceName: capitalizeEveryWord(this.context.deviceName) },
- ),
- action: { type: 'close', close: this.context.close },
- };
- }
-}
diff --git a/gui/src/shared/notifications/notification.ts b/gui/src/shared/notifications/notification.ts
deleted file mode 100644
index 87166aab4d..0000000000
--- a/gui/src/shared/notifications/notification.ts
+++ /dev/null
@@ -1,86 +0,0 @@
-export type NotificationAction = {
- type: 'open-url';
- url: string;
- text?: string;
- withAuth?: boolean;
-};
-
-export interface InAppNotificationTroubleshootInfo {
- details: string;
- steps: string[];
- buttons?: Array<InAppNotificationTroubleshootButton>;
-}
-
-export interface InAppNotificationTroubleshootButton {
- label: string;
- action: () => void;
-}
-
-export type InAppNotificationAction =
- | NotificationAction
- | {
- type: 'troubleshoot-dialog';
- troubleshoot: InAppNotificationTroubleshootInfo;
- }
- | {
- type: 'close';
- close: () => void;
- };
-
-export type InAppNotificationIndicatorType = 'success' | 'warning' | 'error';
-
-export enum SystemNotificationSeverityType {
- info = 0,
- low,
- medium,
- high,
-}
-
-export enum SystemNotificationCategory {
- tunnelState,
- expiry,
- newVersion,
- inconsistentVersion,
-}
-
-interface NotificationProvider {
- mayDisplay(): boolean;
-}
-
-export interface SystemNotification {
- message: string;
- severity: SystemNotificationSeverityType;
- category: SystemNotificationCategory;
- throttle?: boolean;
- presentOnce?: { value: boolean; name: string };
- suppressInDevelopment?: boolean;
- action?: NotificationAction;
-}
-
-export interface InAppNotification {
- indicator?: InAppNotificationIndicatorType;
- title: string;
- subtitle?: string;
- action?: InAppNotificationAction;
-}
-
-export interface SystemNotificationProvider extends NotificationProvider {
- getSystemNotification(): SystemNotification | undefined;
-}
-
-export interface InAppNotificationProvider extends NotificationProvider {
- getInAppNotification(): InAppNotification | undefined;
-}
-
-export * from './account-expired';
-export * from './close-to-account-expiry';
-export * from './block-when-disconnected';
-export * from './connected';
-export * from './connecting';
-export * from './disconnected';
-export * from './daemon-disconnected';
-export * from './error';
-export * from './inconsistent-version';
-export * from './reconnecting';
-export * from './unsupported-version';
-export * from './update-available';
diff --git a/gui/src/shared/notifications/reconnecting.ts b/gui/src/shared/notifications/reconnecting.ts
deleted file mode 100644
index 4362c0edb6..0000000000
--- a/gui/src/shared/notifications/reconnecting.ts
+++ /dev/null
@@ -1,35 +0,0 @@
-import { messages } from '../../shared/gettext';
-import { TunnelState } from '../daemon-rpc-types';
-import {
- InAppNotification,
- InAppNotificationProvider,
- SystemNotification,
- SystemNotificationCategory,
- SystemNotificationProvider,
- SystemNotificationSeverityType,
-} from './notification';
-
-export class ReconnectingNotificationProvider
- implements SystemNotificationProvider, InAppNotificationProvider
-{
- public constructor(private context: TunnelState) {}
-
- public mayDisplay() {
- return this.context.state === 'disconnecting' && this.context.details === 'reconnect';
- }
-
- public getSystemNotification(): SystemNotification | undefined {
- return {
- message: messages.pgettext('notifications', 'Reconnecting'),
- severity: SystemNotificationSeverityType.info,
- category: SystemNotificationCategory.tunnelState,
- throttle: true,
- };
- }
-
- public getInAppNotification(): InAppNotification {
- return {
- title: messages.pgettext('in-app-notifications', 'BLOCKING INTERNET'),
- };
- }
-}
diff --git a/gui/src/shared/notifications/unsupported-version.ts b/gui/src/shared/notifications/unsupported-version.ts
deleted file mode 100644
index 15c622703c..0000000000
--- a/gui/src/shared/notifications/unsupported-version.ts
+++ /dev/null
@@ -1,62 +0,0 @@
-import { messages } from '../../shared/gettext';
-import { getDownloadUrl } from '../version';
-import {
- InAppNotification,
- InAppNotificationProvider,
- SystemNotification,
- SystemNotificationCategory,
- SystemNotificationProvider,
- SystemNotificationSeverityType,
-} from './notification';
-
-interface UnsupportedVersionNotificationContext {
- supported: boolean;
- consistent: boolean;
- suggestedUpgrade?: string;
- suggestedIsBeta?: boolean;
-}
-
-export class UnsupportedVersionNotificationProvider
- implements SystemNotificationProvider, InAppNotificationProvider
-{
- public constructor(private context: UnsupportedVersionNotificationContext) {}
-
- public mayDisplay() {
- return this.context.consistent && !this.context.supported;
- }
-
- public getSystemNotification(): SystemNotification {
- return {
- message: this.getMessage(),
- category: SystemNotificationCategory.newVersion,
- severity: SystemNotificationSeverityType.high,
- action: {
- type: 'open-url',
- url: getDownloadUrl(this.context.suggestedIsBeta ?? false),
- text: messages.pgettext('notifications', 'Upgrade'),
- },
- presentOnce: { value: true, name: this.constructor.name },
- suppressInDevelopment: true,
- };
- }
-
- public getInAppNotification(): InAppNotification {
- return {
- indicator: 'error',
- title: messages.pgettext('in-app-notifications', 'UNSUPPORTED VERSION'),
- subtitle: this.getMessage(),
- action: {
- type: 'open-url',
- url: getDownloadUrl(this.context.suggestedIsBeta ?? false),
- },
- };
- }
-
- private getMessage(): string {
- // TRANSLATORS: The in-app banner and system notification which are displayed to the user when the running app becomes unsupported.
- return messages.pgettext(
- 'notifications',
- 'Your privacy might be at risk with this unsupported app version. Please update now.',
- );
- }
-}
diff --git a/gui/src/shared/notifications/update-available.ts b/gui/src/shared/notifications/update-available.ts
deleted file mode 100644
index 732e7bb9a8..0000000000
--- a/gui/src/shared/notifications/update-available.ts
+++ /dev/null
@@ -1,96 +0,0 @@
-import { sprintf } from 'sprintf-js';
-
-import { messages } from '../../shared/gettext';
-import { getDownloadUrl } from '../version';
-import {
- InAppNotification,
- InAppNotificationProvider,
- SystemNotification,
- SystemNotificationCategory,
- SystemNotificationProvider,
- SystemNotificationSeverityType,
-} from './notification';
-
-interface UpdateAvailableNotificationContext {
- suggestedUpgrade?: string;
- suggestedIsBeta?: boolean;
-}
-
-export class UpdateAvailableNotificationProvider
- implements InAppNotificationProvider, SystemNotificationProvider
-{
- public constructor(private context: UpdateAvailableNotificationContext) {}
-
- public mayDisplay() {
- return this.context.suggestedUpgrade ? true : false;
- }
-
- public getInAppNotification(): InAppNotification {
- return {
- indicator: 'warning',
- title: this.context.suggestedIsBeta
- ? messages.pgettext('in-app-notifications', 'BETA UPDATE AVAILABLE')
- : messages.pgettext('in-app-notifications', 'UPDATE AVAILABLE'),
- subtitle: this.inAppMessage(),
- action: {
- type: 'open-url',
- url: getDownloadUrl(this.context.suggestedIsBeta ?? false),
- },
- };
- }
-
- public getSystemNotification(): SystemNotification {
- return {
- message: this.systemMessage(),
- category: SystemNotificationCategory.newVersion,
- severity: SystemNotificationSeverityType.medium,
- action: {
- type: 'open-url',
- url: getDownloadUrl(this.context.suggestedIsBeta ?? false),
- text: messages.pgettext('notifications', 'Upgrade'),
- },
- presentOnce: { value: true, name: this.constructor.name },
- suppressInDevelopment: true,
- };
- }
-
- private inAppMessage(): string {
- if (this.context.suggestedIsBeta) {
- return sprintf(
- // TRANSLATORS: The in-app banner displayed to the user when the app beta update is
- // TRANSLATORS: available.
- // TRANSLATORS: Available placeholders:
- // TRANSLATORS: %(version)s - The version number of the new beta version.
- messages.pgettext('in-app-notifications', 'Try out the newest beta version (%(version)s).'),
- { version: this.context.suggestedUpgrade },
- );
- } else {
- // TRANSLATORS: The in-app banner displayed to the user when the app update is available.
- return messages.pgettext(
- 'in-app-notifications',
- 'Install the latest app version to stay up to date.',
- );
- }
- }
-
- private systemMessage(): string {
- if (this.context.suggestedIsBeta) {
- return sprintf(
- // TRANSLATORS: The system notification that notifies the user when a beta update is
- // TRANSLATORS: available.
- // TRANSLATORS: Available placeholders:
- // TRANSLATORS: %(version)s - The version number of the new beta version.
- messages.pgettext(
- 'notifications',
- 'Beta update available. Try out the newest beta version (%(version)s).',
- ),
- { version: this.context.suggestedUpgrade },
- );
- } else {
- return messages.pgettext(
- 'notifications',
- 'Update available. Install the latest app version to stay up to date',
- );
- }
- }
-}
diff --git a/gui/src/shared/scheduler.ts b/gui/src/shared/scheduler.ts
deleted file mode 100644
index 2716097194..0000000000
--- a/gui/src/shared/scheduler.ts
+++ /dev/null
@@ -1,37 +0,0 @@
-import { useEffect, useMemo } from 'react';
-
-export class Scheduler {
- private timer?: NodeJS.Timeout;
- private running = false;
-
- public schedule(action: () => void, delay = 0) {
- this.cancel();
-
- this.running = true;
- this.timer = global.setTimeout(() => {
- this.running = false;
- action();
- }, delay);
- }
-
- public cancel() {
- if (this.timer) {
- clearTimeout(this.timer);
- this.running = false;
- }
- }
-
- public get isRunning() {
- return this.running;
- }
-}
-
-export function useScheduler() {
- const closeScheduler = useMemo(() => new Scheduler(), []);
-
- useEffect(() => {
- return () => closeScheduler.cancel();
- }, [closeScheduler]);
-
- return closeScheduler;
-}
diff --git a/gui/src/shared/string-helpers.ts b/gui/src/shared/string-helpers.ts
deleted file mode 100644
index 983a8e8796..0000000000
--- a/gui/src/shared/string-helpers.ts
+++ /dev/null
@@ -1,11 +0,0 @@
-export function capitalize(inputString: string): string {
- return inputString.charAt(0).toUpperCase() + inputString.slice(1);
-}
-
-export function capitalizeEveryWord(inputString: string): string {
- return inputString.split(' ').map(capitalize).join(' ');
-}
-
-export function removeNonNumericCharacters(value: string) {
- return value.replace(/[^0-9]/g, '');
-}
diff --git a/gui/src/shared/utils.ts b/gui/src/shared/utils.ts
deleted file mode 100644
index 042c56385a..0000000000
--- a/gui/src/shared/utils.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-export type NonEmptyArray<T> = [T, ...T[]];
-
-export function hasValue<T>(value: T): value is NonNullable<T> {
- return value !== undefined && value !== null;
-}
diff --git a/gui/src/shared/version.ts b/gui/src/shared/version.ts
deleted file mode 100644
index dc87afaae0..0000000000
--- a/gui/src/shared/version.ts
+++ /dev/null
@@ -1,22 +0,0 @@
-import { links } from '../config.json';
-
-export function getDownloadUrl(suggestedIsBeta: boolean): string {
- let url = links.download;
- switch (process.platform ?? window.env.platform) {
- case 'win32':
- url += 'windows/';
- break;
- case 'linux':
- url += 'linux/';
- break;
- case 'darwin':
- url += 'macos/';
- break;
- }
-
- if (suggestedIsBeta) {
- url += 'beta/';
- }
-
- return url;
-}