diff options
Diffstat (limited to 'gui')
| -rw-r--r-- | gui/locales/messages.pot | 8 | ||||
| -rw-r--r-- | gui/src/main/default-settings.ts | 6 | ||||
| -rw-r--r-- | gui/src/main/grpc-type-convertions.ts | 21 | ||||
| -rw-r--r-- | gui/src/renderer/components/ApiAccessMethods.tsx | 23 | ||||
| -rw-r--r-- | gui/src/renderer/components/ContextMenu.tsx | 2 | ||||
| -rw-r--r-- | gui/src/renderer/components/InfoButton.tsx | 2 | ||||
| -rw-r--r-- | gui/src/renderer/components/cell/Label.tsx | 6 | ||||
| -rw-r--r-- | gui/src/shared/daemon-rpc-types.ts | 4 | ||||
| -rw-r--r-- | gui/test/e2e/installed/state-dependent/api-access-methods.spec.ts | 9 |
9 files changed, 74 insertions, 7 deletions
diff --git a/gui/locales/messages.pot b/gui/locales/messages.pot index 3850b7f239..ec571e5364 100644 --- a/gui/locales/messages.pot +++ b/gui/locales/messages.pot @@ -467,6 +467,10 @@ msgid "Enter port" msgstr "" msgctxt "api-access-methods-view" +msgid "If you are not connected to our VPN, then the Encrypted DNS proxy will use your own non-VPN IP when connecting. The DoH servers are hosted by one of the following providers: Quad 9, CloudFlare, or Google." +msgstr "" + +msgctxt "api-access-methods-view" msgid "In use" msgstr "" @@ -567,6 +571,10 @@ msgid "With the “Direct” method, the app communicates with a Mullvad API ser msgstr "" msgctxt "api-access-methods-view" +msgid "With the “Encrypted DNS proxy” method, the app will communicate with our Mullvad API through a proxy address. It does this by retrieving an address from a DNS over HTTPS (DoH) server and then using that to reach our API servers." +msgstr "" + +msgctxt "api-access-methods-view" msgid "With the “Mullvad bridges” method, the app communicates with a Mullvad API server via a Mullvad bridge server. It does this by sending the traffic obfuscated by Shadowsocks." msgstr "" diff --git a/gui/src/main/default-settings.ts b/gui/src/main/default-settings.ts index 46355dd439..bebc5b9a4e 100644 --- a/gui/src/main/default-settings.ts +++ b/gui/src/main/default-settings.ts @@ -104,6 +104,12 @@ export function getDefaultApiAccessMethods(): ApiAccessMethodSettings { enabled: false, type: 'bridges', }, + encryptedDnsProxy: { + id: '', + name: 'Encrypted DNS Proxy', + enabled: false, + type: 'encrypted-dns-proxy', + }, custom: [], }; } diff --git a/gui/src/main/grpc-type-convertions.ts b/gui/src/main/grpc-type-convertions.ts index 6511f10c67..37e5c7fcf5 100644 --- a/gui/src/main/grpc-type-convertions.ts +++ b/gui/src/main/grpc-type-convertions.ts @@ -16,6 +16,7 @@ import { DeviceEvent, DeviceState, DirectMethod, + EncryptedDnsProxy, EndpointObfuscationType, ErrorStateCause, ErrorStateDetails, @@ -1097,6 +1098,11 @@ function fillApiAccessMethodSetting<T extends grpcTypes.NewAccessMethodSetting>( accessMethod.setBridges(bridges); break; } + case 'encrypted-dns-proxy': { + const encryptedDnsProxy = new grpcTypes.AccessMethod.EncryptedDnsProxy(); + accessMethod.setEncryptedDnsProxy(encryptedDnsProxy); + break; + } default: accessMethod.setCustom(convertToCustomProxy(method)); } @@ -1160,6 +1166,12 @@ function convertFromApiAccessMethodSettings( const bridges = convertFromApiAccessMethodSetting( ensureExists(accessMethods.getMullvadBridges(), "no 'Mullvad Bridges' access method was found"), ) as AccessMethodSetting<BridgesMethod>; + const encryptedDnsProxy = convertFromApiAccessMethodSetting( + ensureExists( + accessMethods.getEncryptedDnsProxy(), + "no 'Encrypted DNS proxy' access method was found", + ), + ) as AccessMethodSetting<EncryptedDnsProxy>; const custom = accessMethods .getCustomList() .filter((setting) => setting.hasId() && setting.hasAccessMethod()) @@ -1170,6 +1182,7 @@ function convertFromApiAccessMethodSettings( return { direct, mullvadBridges: bridges, + encryptedDnsProxy, custom, }; } @@ -1177,7 +1190,11 @@ function convertFromApiAccessMethodSettings( function isCustomProxy( accessMethod: AccessMethodSetting, ): accessMethod is AccessMethodSetting<CustomProxy> { - return accessMethod.type !== 'direct' && accessMethod.type !== 'bridges'; + return ( + accessMethod.type !== 'direct' && + accessMethod.type !== 'bridges' && + accessMethod.type !== 'encrypted-dns-proxy' + ); } export function convertFromApiAccessMethodSetting( @@ -1200,6 +1217,8 @@ function convertFromAccessMethod(method: grpcTypes.AccessMethod): AccessMethod { return { type: 'direct' }; case grpcTypes.AccessMethod.AccessMethodCase.BRIDGES: return { type: 'bridges' }; + case grpcTypes.AccessMethod.AccessMethodCase.ENCRYPTED_DNS_PROXY: + return { type: 'encrypted-dns-proxy' }; case grpcTypes.AccessMethod.AccessMethodCase.CUSTOM: { return convertFromCustomProxy(method.getCustom()!); } diff --git a/gui/src/renderer/components/ApiAccessMethods.tsx b/gui/src/renderer/components/ApiAccessMethods.tsx index 57668df787..68b873bd64 100644 --- a/gui/src/renderer/components/ApiAccessMethods.tsx +++ b/gui/src/renderer/components/ApiAccessMethods.tsx @@ -36,6 +36,8 @@ import { StyledContent, StyledNavigationScrollbars, StyledSettingsContent } from import { SmallButton, SmallButtonColor, SmallButtonGroup } from './SmallButton'; const StyledContextMenuButton = styled(Cell.Icon)({ + alignItems: 'center', + justifyContent: 'center', marginRight: '8px', }); @@ -50,6 +52,7 @@ const StyledSpinner = styled(ImageView)({ }); const StyledNameLabel = styled(Cell.Label)({ + display: 'block', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', @@ -133,6 +136,10 @@ export default function ApiAccessMethods() { method={methods.mullvadBridges} inUse={methods.mullvadBridges.id === currentMethod?.id} /> + <ApiAccessMethod + method={methods.encryptedDnsProxy} + inUse={methods.encryptedDnsProxy.id === currentMethod?.id} + /> {methods.custom.map((method) => ( <ApiAccessMethod key={method.id} @@ -211,7 +218,7 @@ function ApiAccessMethod(props: ApiAccessMethodProps) { }, ]; - // Edit and Delete shouldn't be available for direct and bridges. + // Edit and Delete shouldn't be available for direct, bridges or encrypted DNS proxy. if (props.custom) { items.push( { type: 'separator' as const }, @@ -290,6 +297,20 @@ function ApiAccessMethod(props: ApiAccessMethodProps) { ]} /> )} + {props.method.type === 'encrypted-dns-proxy' && ( + <StyledMethodInfoButton + message={[ + messages.pgettext( + 'api-access-methods-view', + 'With the “Encrypted DNS proxy” method, the app will communicate with our Mullvad API through a proxy address. It does this by retrieving an address from a DNS over HTTPS (DoH) server and then using that to reach our API servers.', + ), + messages.pgettext( + 'api-access-methods-view', + 'If you are not connected to our VPN, then the Encrypted DNS proxy will use your own non-VPN IP when connecting. The DoH servers are hosted by one of the following providers: Quad 9, CloudFlare, or Google.', + ), + ]} + /> + )} <ContextMenuContainer> <ContextMenuTrigger> <StyledContextMenuButton diff --git a/gui/src/renderer/components/ContextMenu.tsx b/gui/src/renderer/components/ContextMenu.tsx index fd84a8f0d9..2e01c9375f 100644 --- a/gui/src/renderer/components/ContextMenu.tsx +++ b/gui/src/renderer/components/ContextMenu.tsx @@ -36,6 +36,8 @@ const menuContext = React.createContext<MenuContext>({ const StyledMenuContainer = styled.div({ position: 'relative', padding: '8px 4px', + display: 'flex', + justifyContent: 'center', }); export function ContextMenuContainer(props: React.PropsWithChildren) { diff --git a/gui/src/renderer/components/InfoButton.tsx b/gui/src/renderer/components/InfoButton.tsx index 4ef2aa3c6e..7f77f115e7 100644 --- a/gui/src/renderer/components/InfoButton.tsx +++ b/gui/src/renderer/components/InfoButton.tsx @@ -8,7 +8,7 @@ import ImageView from './ImageView'; import { ModalAlert, ModalAlertType } from './Modal'; const StyledInfoButton = styled.button({ - margin: '0 16px 0 0', + margin: '0 16px 0 8px', borderWidth: 0, padding: 0, cursor: 'default', diff --git a/gui/src/renderer/components/cell/Label.tsx b/gui/src/renderer/components/cell/Label.tsx index 1115edc829..b2b37c1e4c 100644 --- a/gui/src/renderer/components/cell/Label.tsx +++ b/gui/src/renderer/components/cell/Label.tsx @@ -15,11 +15,15 @@ const StyledLabel = styled.div<{ disabled: boolean }>(buttonText, (props) => ({ textAlign: 'left', [`${LabelContainer} &&`]: { - marginTop: '5px', + marginTop: '0px', marginBottom: 0, height: '20px', lineHeight: '20px', }, + + [`${LabelContainer}:has(${StyledSubLabel}) &&`]: { + marginTop: '5px', + }, })); const StyledSubText = styled.span<{ disabled: boolean }>(tinyText, (props) => ({ diff --git a/gui/src/shared/daemon-rpc-types.ts b/gui/src/shared/daemon-rpc-types.ts index bd8f99711b..1522d43b39 100644 --- a/gui/src/shared/daemon-rpc-types.ts +++ b/gui/src/shared/daemon-rpc-types.ts @@ -524,7 +524,8 @@ export type NamedCustomProxy = CustomProxy & { name: string }; export type DirectMethod = { type: 'direct' }; export type BridgesMethod = { type: 'bridges' }; -export type AccessMethod = DirectMethod | BridgesMethod | CustomProxy; +export type EncryptedDnsProxy = { type: 'encrypted-dns-proxy' }; +export type AccessMethod = DirectMethod | BridgesMethod | EncryptedDnsProxy | CustomProxy; export type NamedAccessMethod<T extends AccessMethod> = T & { name: string }; @@ -540,6 +541,7 @@ export type AccessMethodSetting<T extends AccessMethod = AccessMethod> = export type ApiAccessMethodSettings = { direct: AccessMethodSetting<DirectMethod>; mullvadBridges: AccessMethodSetting<BridgesMethod>; + encryptedDnsProxy: AccessMethodSetting<EncryptedDnsProxy>; custom: Array<AccessMethodSetting<CustomProxy>>; }; diff --git a/gui/test/e2e/installed/state-dependent/api-access-methods.spec.ts b/gui/test/e2e/installed/state-dependent/api-access-methods.spec.ts index d17611261b..662ac4bf2a 100644 --- a/gui/test/e2e/installed/state-dependent/api-access-methods.spec.ts +++ b/gui/test/e2e/installed/state-dependent/api-access-methods.spec.ts @@ -15,6 +15,7 @@ import { startInstalledApp } from '../installed-utils'; const DIRECT_NAME = 'Direct'; const BRIDGES_NAME = 'Mullvad Bridges'; +const ENCRYPTED_DNS_PROXY_NAME = 'Encrypted DNS proxy'; const IN_USE_LABEL = 'In use'; const FUNCTIONING_METHOD_NAME = 'Test method'; const NON_FUNCTIONING_METHOD_NAME = 'Non functioning test method'; @@ -42,12 +43,14 @@ test('App should display access methods', async () => { await navigateToAccessMethods(); const accessMethods = page.getByTestId('access-method'); - await expect(accessMethods).toHaveCount(2); + await expect(accessMethods).toHaveCount(3); const direct = accessMethods.first(); - const bridges = accessMethods.last(); + const bridges = accessMethods.nth(1); + const encryptedDnsProxy = accessMethods.nth(2); await expect(direct).toContainText(DIRECT_NAME); await expect(bridges).toContainText(BRIDGES_NAME); + await expect(encryptedDnsProxy).toContainText(ENCRYPTED_DNS_PROXY_NAME); await expect(page.getByText(IN_USE_LABEL)).toHaveCount(1); }); @@ -144,6 +147,7 @@ test('App should use valid method', async () => { const direct = accessMethods.first(); const bridges = accessMethods.nth(1); + const encryptedDnsProxy = accessMethods.nth(2); const functioningTestMethod = accessMethods.last(); await expect(page.getByText(IN_USE_LABEL)).toHaveCount(1); @@ -154,6 +158,7 @@ test('App should use valid method', async () => { await functioningTestMethod.getByText('Use').click(); await expect(direct).not.toContainText(IN_USE_LABEL); await expect(bridges).not.toContainText(IN_USE_LABEL); + await expect(encryptedDnsProxy).not.toContainText(IN_USE_LABEL); await expect(functioningTestMethod).toContainText('API reachable'); await expect(functioningTestMethod).toContainText(IN_USE_LABEL); }); |
