1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
|
import { useCallback } from 'react';
import { useSelector } from 'react-redux';
import log from '../../shared/logging';
import {
BlockWhenDisconnectedNotificationProvider,
CloseToAccountExpiryNotificationProvider,
ConnectingNotificationProvider,
ErrorNotificationProvider,
InAppNotificationProvider,
InconsistentVersionNotificationProvider,
NotificationAction,
NoValidKeyNotificationProvider,
ReconnectingNotificationProvider,
UnsupportedVersionNotificationProvider,
UpdateAvailableNotificationProvider,
} from '../../shared/notifications/notification';
import { useAppContext } from '../context';
import { IReduxState } from '../redux/store';
import {
NotificationActions,
NotificationBanner,
NotificationContent,
NotificationIndicator,
NotificationOpenLinkAction,
NotificationSubtitle,
NotificationTitle,
} from './NotificationBanner';
interface IProps {
className?: string;
}
export default function NotificationArea(props: IProps) {
const accountExpiry = useSelector((state: IReduxState) => state.account.expiry);
const locale = useSelector((state: IReduxState) => state.userInterface.locale);
const tunnelState = useSelector((state: IReduxState) => state.connection.status);
const version = useSelector((state: IReduxState) => state.version);
const blockWhenDisconnected = useSelector(
(state: IReduxState) => state.settings.blockWhenDisconnected,
);
const tunnelProtocol = useSelector((state: IReduxState) =>
'normal' in state.settings.relaySettings
? state.settings.relaySettings.normal.tunnelProtocol
: undefined,
);
const wireGuardKey = useSelector((state: IReduxState) => state.settings.wireguardKeyState);
const hasExcludedApps = useSelector(
(state: IReduxState) =>
state.settings.splitTunneling && state.settings.splitTunnelingApplications.length > 0,
);
const notificationProviders: InAppNotificationProvider[] = [
new ConnectingNotificationProvider({ tunnelState }),
new ReconnectingNotificationProvider(tunnelState),
new BlockWhenDisconnectedNotificationProvider({
tunnelState,
blockWhenDisconnected,
hasExcludedApps,
}),
new ErrorNotificationProvider({ tunnelState, accountExpiry, hasExcludedApps }),
new NoValidKeyNotificationProvider({ tunnelProtocol, wireGuardKey }),
new InconsistentVersionNotificationProvider({ consistent: version.consistent }),
new UnsupportedVersionNotificationProvider(version),
];
if (accountExpiry) {
notificationProviders.push(
new CloseToAccountExpiryNotificationProvider({ accountExpiry, locale }),
);
}
notificationProviders.push(new UpdateAvailableNotificationProvider(version));
const notificationProvider = notificationProviders.find((notification) =>
notification.mayDisplay(),
);
if (notificationProvider) {
const notification = notificationProvider.getInAppNotification();
if (notification) {
return (
<NotificationBanner className={props.className} visible>
<NotificationIndicator type={notification.indicator} />
<NotificationContent role="status" aria-live="polite">
<NotificationTitle>{notification.title}</NotificationTitle>
<NotificationSubtitle>{notification.subtitle}</NotificationSubtitle>
</NotificationContent>
{notification.action && <NotificationActionWrapper action={notification.action} />}
</NotificationBanner>
);
} else {
log.error(
`Notification providers mayDisplay() returned true but getInAppNotification() returned undefined for ${notificationProvider.constructor.name}`,
);
}
}
return <NotificationBanner className={props.className} visible={false} aria-hidden={true} />;
}
interface INotificationActionWrapperProps {
action: NotificationAction;
}
function NotificationActionWrapper(props: INotificationActionWrapperProps) {
const { openLinkWithAuth, openUrl } = useAppContext();
const handleClick = useCallback(() => {
if (props.action.withAuth) {
return openLinkWithAuth(props.action.url);
} else {
return openUrl(props.action.url);
}
}, []);
return (
<NotificationActions>
<NotificationOpenLinkAction onClick={handleClick} />
</NotificationActions>
);
}
|