summaryrefslogtreecommitdiffhomepage
path: root/gui
diff options
context:
space:
mode:
Diffstat (limited to 'gui')
-rw-r--r--gui/locales/messages.pot8
-rw-r--r--gui/src/main/default-settings.ts6
-rw-r--r--gui/src/main/grpc-type-convertions.ts21
-rw-r--r--gui/src/renderer/components/ApiAccessMethods.tsx23
-rw-r--r--gui/src/renderer/components/ContextMenu.tsx2
-rw-r--r--gui/src/renderer/components/InfoButton.tsx2
-rw-r--r--gui/src/renderer/components/cell/Label.tsx6
-rw-r--r--gui/src/shared/daemon-rpc-types.ts4
-rw-r--r--gui/test/e2e/installed/state-dependent/api-access-methods.spec.ts9
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);
});