summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorJoakim Hulthe <joakim.hulthe@mullvad.net>2024-12-18 15:31:57 +0100
committerOskar <oskar@mullvad.net>2024-12-19 09:46:21 +0100
commitd00e3b299bcea101e5a34cc760fb3c0d401562c5 (patch)
tree5efa84076a627375a425d279cbee48cb8ed6a103
parent1139f5ca5a86a373fcf96c6d0088c264d4659d66 (diff)
downloadmullvadvpn-fda-fixes-backport.tar.xz
mullvadvpn-fda-fixes-backport.zip
Merge branch 'add-spinner-while-fda-check-is-being-performed-des-1554'fda-fixes-backport
-rw-r--r--desktop/packages/mullvad-vpn/locales/messages.pot4
-rw-r--r--desktop/packages/mullvad-vpn/src/renderer/components/NotificationArea.tsx110
-rw-r--r--desktop/packages/mullvad-vpn/src/renderer/components/SplitTunnelingSettings.tsx65
-rw-r--r--desktop/packages/mullvad-vpn/src/renderer/components/SplitTunnelingSettingsStyles.tsx6
-rw-r--r--desktop/packages/mullvad-vpn/src/shared/notifications/error.ts10
-rw-r--r--desktop/packages/mullvad-vpn/src/shared/notifications/notification.ts1
6 files changed, 136 insertions, 60 deletions
diff --git a/desktop/packages/mullvad-vpn/locales/messages.pot b/desktop/packages/mullvad-vpn/locales/messages.pot
index 1633f99c56..7472de2eaa 100644
--- a/desktop/packages/mullvad-vpn/locales/messages.pot
+++ b/desktop/packages/mullvad-vpn/locales/messages.pot
@@ -1749,6 +1749,10 @@ msgid "Connecting. %(location)s"
msgstr ""
msgctxt "troubleshoot"
+msgid "Disable split tunneling"
+msgstr ""
+
+msgctxt "troubleshoot"
msgid "Enable “Full Disk Access” for “Mullvad VPN” in the macOS system settings."
msgstr ""
diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/NotificationArea.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/NotificationArea.tsx
index dac62db192..ed04e770a3 100644
--- a/desktop/packages/mullvad-vpn/src/renderer/components/NotificationArea.tsx
+++ b/desktop/packages/mullvad-vpn/src/renderer/components/NotificationArea.tsx
@@ -11,7 +11,6 @@ import {
ErrorNotificationProvider,
InAppNotificationAction,
InAppNotificationProvider,
- InAppNotificationTroubleshootInfo,
InconsistentVersionNotificationProvider,
ReconnectingNotificationProvider,
UnsupportedVersionNotificationProvider,
@@ -43,7 +42,7 @@ interface IProps {
}
export default function NotificationArea(props: IProps) {
- const { showFullDiskAccessSettings } = useAppContext();
+ const { showFullDiskAccessSettings, reconnectTunnel } = useAppContext();
const account = useSelector((state: IReduxState) => state.account);
const locale = useSelector((state: IReduxState) => state.userInterface.locale);
@@ -59,6 +58,15 @@ export default function NotificationArea(props: IProps) {
const { hideNewDeviceBanner } = useActions(accountActions);
+ const [isModalOpen, setIsModalOpen] = useState(false);
+
+ const { setSplitTunnelingState } = useAppContext();
+ const disableSplitTunneling = useCallback(async () => {
+ setIsModalOpen(false);
+ await setSplitTunnelingState(false);
+ await reconnectTunnel();
+ }, [reconnectTunnel, setSplitTunnelingState]);
+
const notificationProviders: InAppNotificationProvider[] = [
new ConnectingNotificationProvider({ tunnelState }),
new ReconnectingNotificationProvider(tunnelState),
@@ -67,7 +75,13 @@ export default function NotificationArea(props: IProps) {
blockWhenDisconnected,
hasExcludedApps,
}),
- new ErrorNotificationProvider({ tunnelState, hasExcludedApps, showFullDiskAccessSettings }),
+
+ new ErrorNotificationProvider({
+ tunnelState,
+ hasExcludedApps,
+ showFullDiskAccessSettings,
+ disableSplitTunneling,
+ }),
new InconsistentVersionNotificationProvider({ consistent: version.consistent }),
new UnsupportedVersionNotificationProvider(version),
];
@@ -109,7 +123,13 @@ export default function NotificationArea(props: IProps) {
{formatHtml(notification.subtitle ?? '')}
</NotificationSubtitle>
</NotificationContent>
- {notification.action && <NotificationActionWrapper action={notification.action} />}
+ {notification.action && (
+ <NotificationActionWrapper
+ action={notification.action}
+ isModalOpen={isModalOpen}
+ setIsModalOpen={setIsModalOpen}
+ />
+ )}
</NotificationBanner>
);
} else {
@@ -122,46 +142,51 @@ export default function NotificationArea(props: IProps) {
return <NotificationBanner className={props.className} aria-hidden={true} />;
}
-interface INotificationActionWrapperProps {
+interface NotificationActionWrapperProps {
action: InAppNotificationAction;
+ isModalOpen: boolean;
+ setIsModalOpen: (isOpen: boolean) => void;
}
-function NotificationActionWrapper(props: INotificationActionWrapperProps) {
+function NotificationActionWrapper({
+ action,
+ isModalOpen,
+ setIsModalOpen,
+}: NotificationActionWrapperProps) {
const { push } = useHistory();
const { openLinkWithAuth, openUrl } = useAppContext();
- const [troubleshootInfo, setTroubleshootInfo] = useState<InAppNotificationTroubleshootInfo>();
+
+ const closeTroubleshootModal = useCallback(() => setIsModalOpen(false), [setIsModalOpen]);
const handleClick = useCallback(() => {
- if (props.action) {
- switch (props.action.type) {
+ if (action) {
+ switch (action.type) {
case 'open-url':
- if (props.action.withAuth) {
- return openLinkWithAuth(props.action.url);
+ if (action.withAuth) {
+ return openLinkWithAuth(action.url);
} else {
- return openUrl(props.action.url);
+ return openUrl(action.url);
}
case 'troubleshoot-dialog':
- setTroubleshootInfo(props.action.troubleshoot);
+ setIsModalOpen(true);
break;
case 'close':
- props.action.close();
+ action.close();
break;
}
}
return Promise.resolve();
- }, [openLinkWithAuth, openUrl, props.action]);
+ }, [action, setIsModalOpen, openLinkWithAuth, openUrl]);
const goToProblemReport = useCallback(() => {
- setTroubleshootInfo(undefined);
+ closeTroubleshootModal();
push(RoutePath.problemReport, { transition: transitions.show });
- }, [push]);
-
- const closeTroubleshootInfo = useCallback(() => setTroubleshootInfo(undefined), []);
+ }, [closeTroubleshootModal, push]);
let actionComponent: React.ReactElement | undefined;
- if (props.action) {
- switch (props.action.type) {
+ if (action) {
+ switch (action.type) {
case 'open-url':
actionComponent = <NotificationOpenLinkAction onClick={handleClick} />;
break;
@@ -177,7 +202,11 @@ function NotificationActionWrapper(props: INotificationActionWrapperProps) {
}
}
- const problemReportButton = troubleshootInfo?.buttons ? (
+ if (action.type !== 'troubleshoot-dialog') {
+ return <NotificationActions>{actionComponent}</NotificationActions>;
+ }
+
+ const problemReportButton = action.troubleshoot?.buttons ? (
<AppButton.BlueButton key="problem-report" onClick={goToProblemReport}>
{messages.pgettext('in-app-notifications', 'Send problem report')}
</AppButton.BlueButton>
@@ -189,17 +218,32 @@ function NotificationActionWrapper(props: INotificationActionWrapperProps) {
let buttons = [
problemReportButton,
- <AppButton.BlueButton key="back" onClick={closeTroubleshootInfo}>
+ <AppButton.BlueButton key="back" onClick={closeTroubleshootModal}>
{messages.gettext('Back')}
</AppButton.BlueButton>,
];
- if (troubleshootInfo?.buttons) {
- const actionButtons = troubleshootInfo.buttons.map((button) => (
- <AppButton.GreenButton key={button.label} onClick={button.action}>
- {button.label}
- </AppButton.GreenButton>
- ));
+ if (action.troubleshoot?.buttons) {
+ const actionButtons = action.troubleshoot.buttons.map(({ variant, label, action }) => {
+ if (variant === 'success')
+ return (
+ <AppButton.GreenButton key={label} onClick={action}>
+ {label}
+ </AppButton.GreenButton>
+ );
+ else if (variant === 'destructive')
+ return (
+ <AppButton.RedButton key={label} onClick={action}>
+ {label}
+ </AppButton.RedButton>
+ );
+ else
+ return (
+ <AppButton.BlueButton key={label} onClick={action}>
+ {label}
+ </AppButton.BlueButton>
+ );
+ });
buttons = actionButtons.concat(buttons);
}
@@ -208,14 +252,14 @@ function NotificationActionWrapper(props: INotificationActionWrapperProps) {
<>
<NotificationActions>{actionComponent}</NotificationActions>
<ModalAlert
- isOpen={troubleshootInfo !== undefined}
+ isOpen={isModalOpen}
type={ModalAlertType.info}
buttons={buttons}
- close={closeTroubleshootInfo}>
- <ModalMessage>{troubleshootInfo?.details}</ModalMessage>
+ close={closeTroubleshootModal}>
+ <ModalMessage>{action.troubleshoot?.details}</ModalMessage>
<ModalMessage>
<ModalMessageList>
- {troubleshootInfo?.steps.map((step) => <li key={step}>{step}</li>)}
+ {action.troubleshoot?.steps.map((step) => <li key={step}>{step}</li>)}
</ModalMessageList>
</ModalMessage>
<ModalMessage>
diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/SplitTunnelingSettings.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/SplitTunnelingSettings.tsx
index 072f62728c..d5044d012f 100644
--- a/desktop/packages/mullvad-vpn/src/renderer/components/SplitTunnelingSettings.tsx
+++ b/desktop/packages/mullvad-vpn/src/renderer/components/SplitTunnelingSettings.tsx
@@ -32,6 +32,7 @@ import {
StyledCellLabel,
StyledCellWarningIcon,
StyledContent,
+ StyledFdaSpinner,
StyledHeaderTitle,
StyledHeaderTitleContainer,
StyledIcon,
@@ -318,9 +319,7 @@ export function SplitTunnelingSettings(props: IPlatformSplitTunnelingSettingsPro
needFullDiskPermissions,
setSplitTunnelingState,
} = useAppContext();
- const splitTunnelingEnabledValue = useSelector(
- (state: IReduxState) => state.settings.splitTunneling,
- );
+ const splitTunnelingEnabled = useSelector((state: IReduxState) => state.settings.splitTunneling);
const splitTunnelingApplications = useSelector(
(state: IReduxState) => state.settings.splitTunnelingApplications,
);
@@ -328,15 +327,18 @@ export function SplitTunnelingSettings(props: IPlatformSplitTunnelingSettingsPro
const [searchTerm, setSearchTerm] = useState('');
const [applications, setApplications] = useState<ISplitTunnelingApplication[]>();
+ const [loadingDiskPermissions, setLoadingDiskPermissions] = useState(false);
const [splitTunnelingAvailable, setSplitTunnelingAvailable] = useState(
window.env.platform === 'darwin' ? undefined : true,
);
- const splitTunnelingEnabled = splitTunnelingEnabledValue && (splitTunnelingAvailable ?? false);
+ const canEditSplitTunneling = splitTunnelingEnabled && (splitTunnelingAvailable ?? false);
const fetchNeedFullDiskPermissions = useCallback(async () => {
+ setLoadingDiskPermissions(true);
const needPermissions = await needFullDiskPermissions();
setSplitTunnelingAvailable(!needPermissions);
+ setLoadingDiskPermissions(false);
}, [needFullDiskPermissions]);
useEffect((): void | (() => void) => {
@@ -378,12 +380,12 @@ export function SplitTunnelingSettings(props: IPlatformSplitTunnelingSettingsPro
const addApplication = useCallback(
async (application: ISplitTunnelingApplication | string) => {
- if (!splitTunnelingEnabled) {
+ if (!canEditSplitTunneling) {
await setSplitTunnelingState(true);
}
await addSplitTunnelingApplication(application);
},
- [addSplitTunnelingApplication, splitTunnelingEnabled, setSplitTunnelingState],
+ [addSplitTunnelingApplication, canEditSplitTunneling, setSplitTunnelingState],
);
const addBrowsedForApplication = useCallback(
@@ -406,12 +408,12 @@ export function SplitTunnelingSettings(props: IPlatformSplitTunnelingSettingsPro
const removeApplication = useCallback(
async (application: ISplitTunnelingApplication) => {
- if (!splitTunnelingEnabled) {
+ if (!canEditSplitTunneling) {
await setSplitTunnelingState(true);
}
removeSplitTunnelingApplication(application);
},
- [removeSplitTunnelingApplication, setSplitTunnelingState, splitTunnelingEnabled],
+ [removeSplitTunnelingApplication, setSplitTunnelingState, canEditSplitTunneling],
);
const filePickerCallback = useFilePicker(
@@ -443,9 +445,9 @@ export function SplitTunnelingSettings(props: IPlatformSplitTunnelingSettingsPro
[addApplication, forgetManuallyAddedApplicationAndUpdate],
);
- const showSplitSection = splitTunnelingEnabled && filteredSplitApplications.length > 0;
+ const showSplitSection = canEditSplitTunneling && filteredSplitApplications.length > 0;
const showNonSplitSection =
- splitTunnelingEnabled &&
+ canEditSplitTunneling &&
(!filteredNonSplitApplications || filteredNonSplitApplications.length > 0);
const excludedTitle = (
@@ -465,26 +467,37 @@ export function SplitTunnelingSettings(props: IPlatformSplitTunnelingSettingsPro
<StyledHeaderTitle>{strings.splitTunneling}</StyledHeaderTitle>
<Switch
isOn={splitTunnelingEnabled}
- disabled={!splitTunnelingAvailable}
+ disabled={
+ !splitTunnelingEnabled && (!splitTunnelingAvailable || loadingDiskPermissions)
+ }
onChange={setSplitTunnelingState}
/>
</StyledHeaderTitleContainer>
- <MacOsSplitTunnelingAvailability
- needFullDiskPermissions={
- window.env.platform === 'darwin' && splitTunnelingAvailable === false
- }
- />
- {splitTunnelingAvailable ? (
- <HeaderSubTitle>
- {messages.pgettext(
- 'split-tunneling-view',
- 'Choose the apps you want to exclude from the VPN tunnel.',
+ {!loadingDiskPermissions && (
+ <>
+ <MacOsSplitTunnelingAvailability
+ needFullDiskPermissions={
+ window.env.platform === 'darwin' && splitTunnelingAvailable === false
+ }
+ />
+ {splitTunnelingAvailable && (
+ <HeaderSubTitle>
+ {messages.pgettext(
+ 'split-tunneling-view',
+ 'Choose the apps you want to exclude from the VPN tunnel.',
+ )}
+ </HeaderSubTitle>
)}
- </HeaderSubTitle>
- ) : null}
+ </>
+ )}
</SettingsHeader>
+ {loadingDiskPermissions && (
+ <StyledFdaSpinner>
+ <ImageView source="icon-spinner" height={48} />
+ </StyledFdaSpinner>
+ )}
- {splitTunnelingEnabled && (
+ {canEditSplitTunneling && (
<StyledSearchBar searchTerm={searchTerm} onSearch={setSearchTerm} />
)}
@@ -508,7 +521,7 @@ export function SplitTunnelingSettings(props: IPlatformSplitTunnelingSettingsPro
</Cell.Section>
</Accordion>
- {splitTunnelingEnabled && searchTerm !== '' && !showSplitSection && !showNonSplitSection && (
+ {canEditSplitTunneling && searchTerm !== '' && !showSplitSection && !showNonSplitSection && (
<StyledNoResult>
<StyledNoResultText>
{formatHtml(
@@ -519,7 +532,7 @@ export function SplitTunnelingSettings(props: IPlatformSplitTunnelingSettingsPro
</StyledNoResult>
)}
- {splitTunnelingEnabled && (
+ {canEditSplitTunneling && (
<StyledBrowseButton onClick={addWithFilePicker}>
{messages.pgettext('split-tunneling-view', 'Find another app')}
</StyledBrowseButton>
diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/SplitTunnelingSettingsStyles.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/SplitTunnelingSettingsStyles.tsx
index a2046e61c9..28cb3fce21 100644
--- a/desktop/packages/mullvad-vpn/src/renderer/components/SplitTunnelingSettingsStyles.tsx
+++ b/desktop/packages/mullvad-vpn/src/renderer/components/SplitTunnelingSettingsStyles.tsx
@@ -136,3 +136,9 @@ export const WideSmallButton = styled(SmallButton)({
export const Spacing = styled.div<{ height: string }>((props) => ({
height: props.height,
}));
+
+export const StyledFdaSpinner = styled.div({
+ display: 'flex',
+ justifyContent: 'center',
+ marginTop: '24px',
+});
diff --git a/desktop/packages/mullvad-vpn/src/shared/notifications/error.ts b/desktop/packages/mullvad-vpn/src/shared/notifications/error.ts
index af82748d7b..42e550c466 100644
--- a/desktop/packages/mullvad-vpn/src/shared/notifications/error.ts
+++ b/desktop/packages/mullvad-vpn/src/shared/notifications/error.ts
@@ -13,6 +13,7 @@ import {
InAppNotification,
InAppNotificationAction,
InAppNotificationProvider,
+ InAppNotificationTroubleshootButton,
SystemNotification,
SystemNotificationCategory,
SystemNotificationProvider,
@@ -23,6 +24,7 @@ interface ErrorNotificationContext {
tunnelState: TunnelState;
hasExcludedApps: boolean;
showFullDiskAccessSettings?: () => void;
+ disableSplitTunneling?: () => void;
}
export class ErrorNotificationProvider
@@ -276,12 +278,18 @@ export class ErrorNotificationProvider
},
};
} else if (errorState.cause === ErrorStateCause.needFullDiskPermissions) {
- let troubleshootButtons = undefined;
+ let troubleshootButtons: InAppNotificationTroubleshootButton[] | undefined = undefined;
if (this.context.showFullDiskAccessSettings) {
troubleshootButtons = [
{
label: messages.pgettext('troubleshoot', 'Open system settings'),
action: () => this.context.showFullDiskAccessSettings?.(),
+ variant: 'success',
+ },
+ {
+ label: messages.pgettext('troubleshoot', 'Disable split tunneling'),
+ action: () => this.context.disableSplitTunneling?.(),
+ variant: 'destructive',
},
];
}
diff --git a/desktop/packages/mullvad-vpn/src/shared/notifications/notification.ts b/desktop/packages/mullvad-vpn/src/shared/notifications/notification.ts
index 87166aab4d..2b6af5b7d3 100644
--- a/desktop/packages/mullvad-vpn/src/shared/notifications/notification.ts
+++ b/desktop/packages/mullvad-vpn/src/shared/notifications/notification.ts
@@ -14,6 +14,7 @@ export interface InAppNotificationTroubleshootInfo {
export interface InAppNotificationTroubleshootButton {
label: string;
action: () => void;
+ variant?: 'primary' | 'success' | 'destructive';
}
export type InAppNotificationAction =