diff options
| author | Oskar <oskar@mullvad.net> | 2024-11-05 07:57:08 +0100 |
|---|---|---|
| committer | Oskar <oskar@mullvad.net> | 2024-11-14 16:43:18 +0100 |
| commit | 84f14d79c4f0dde73337820ec94ba8ff928a3797 (patch) | |
| tree | ce468658e5ba7b0a74950c7ad1b09b3a4d00520b /gui/src/shared | |
| parent | e3ce0eb5cd0610dbff6ec98cb8cb388415c74bf6 (diff) | |
| download | mullvadvpn-84f14d79c4f0dde73337820ec94ba8ff928a3797.tar.xz mullvadvpn-84f14d79c4f0dde73337820ec94ba8ff928a3797.zip | |
Move gui directory to desktop/packages/mullvad-vpn
Diffstat (limited to 'gui/src/shared')
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; -} |
