• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

gitify-app / gitify / 9319904199

31 May 2024 02:38PM UTC coverage: 96.355% (-0.4%) from 96.787%
9319904199

Pull #1139

github

web-flow
Merge af6de505a into 72b432272
Pull Request #1139: feat(accounts): enhance auth account data structure

402 of 415 branches covered (96.87%)

Branch coverage included in aggregate %.

79 of 89 new or added lines in 12 files covered. (88.76%)

3 existing lines in 2 files now uncovered.

1052 of 1094 relevant lines covered (96.16%)

20.24 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

74.73
/src/context/App.tsx
1
import {
22✔
2
  type ReactNode,
3
  createContext,
4
  useCallback,
5
  useEffect,
6
  useMemo,
7
  useState,
8
} from 'react';
9
import { useInterval } from '../hooks/useInterval';
22✔
10
import { useNotifications } from '../hooks/useNotifications';
22✔
11
import {
22✔
12
  type AccountNotifications,
13
  type AuthState,
14
  type GitifyError,
15
  type SettingsState,
16
  type Status,
17
  Theme,
18
} from '../types';
19
import { headNotifications } from '../utils/api/client';
22✔
20
import { migrateAuthenticatedAccounts } from '../utils/auth/migration';
22✔
21
import type {
22
  LoginOAuthAppOptions,
23
  LoginPersonalAccessTokenOptions,
24
} from '../utils/auth/types';
25
import {
22✔
26
  addAccount,
27
  authGitHub,
28
  getToken,
29
  getUserData,
30
} from '../utils/auth/utils';
31
import { setAutoLaunch, updateTrayTitle } from '../utils/comms';
22✔
32
import Constants from '../utils/constants';
22✔
33
import { getNotificationCount } from '../utils/notifications';
22✔
34
import { clearState, loadState, saveState } from '../utils/storage';
22✔
35
import { setTheme } from '../utils/theme';
22✔
36

37
const defaultAuth: AuthState = {
22✔
38
  accounts: [],
39
  token: null,
40
  enterpriseAccounts: [],
41
  user: null,
42
};
43

44
export const defaultSettings: SettingsState = {
22✔
45
  participating: false,
46
  playSound: true,
47
  showNotifications: true,
48
  showBots: true,
49
  showNotificationsCountInTray: false,
50
  openAtStartup: false,
51
  theme: Theme.SYSTEM,
52
  detailedNotifications: true,
53
  markAsDoneOnOpen: false,
54
  showAccountHostname: false,
55
  delayNotificationState: false,
56
};
57

58
interface AppContextState {
59
  auth: AuthState;
60
  isLoggedIn: boolean;
61
  loginWithGitHubApp: () => void;
62
  loginWithOAuthApp: (data: LoginOAuthAppOptions) => void;
63
  loginWithPersonalAccessToken: (data: LoginPersonalAccessTokenOptions) => void;
64
  logout: () => void;
65

66
  notifications: AccountNotifications[];
67
  status: Status;
68
  errorDetails: GitifyError;
69
  removeNotificationFromState: (
70
    settings: SettingsState,
71
    id: string,
72
    hostname: string,
73
  ) => void;
74
  fetchNotifications: () => Promise<void>;
75
  markNotificationRead: (id: string, hostname: string) => Promise<void>;
76
  markNotificationDone: (id: string, hostname: string) => Promise<void>;
77
  unsubscribeNotification: (id: string, hostname: string) => Promise<void>;
78
  markRepoNotifications: (id: string, hostname: string) => Promise<void>;
79
  markRepoNotificationsDone: (id: string, hostname: string) => Promise<void>;
80

81
  settings: SettingsState;
82
  updateSetting: (
83
    name: keyof SettingsState,
84
    value: boolean | Theme | string | null,
85
  ) => void;
86
}
87

88
export const AppContext = createContext<Partial<AppContextState>>({});
22✔
89

90
export const AppProvider = ({ children }: { children: ReactNode }) => {
22✔
91
  const [auth, setAuth] = useState<AuthState>(defaultAuth);
26✔
92
  const [settings, setSettings] = useState<SettingsState>(defaultSettings);
26✔
93
  const {
94
    fetchNotifications,
95
    notifications,
96
    errorDetails,
97
    status,
98
    removeNotificationFromState,
99
    markNotificationRead,
100
    markNotificationDone,
101
    unsubscribeNotification,
102
    markRepoNotifications,
103
    markRepoNotificationsDone,
104
  } = useNotifications();
26✔
105

106
  useEffect(() => {
26✔
107
    restoreSettings();
22✔
108
  }, []);
109

110
  useEffect(() => {
26✔
111
    setTheme(settings.theme as Theme);
22✔
112
  }, [settings.theme]);
113

114
  // biome-ignore lint/correctness/useExhaustiveDependencies: We only want fetchNotifications to be called for certain account or setting changes.
115
  useEffect(() => {
26✔
116
    fetchNotifications(auth, settings);
24✔
117
  }, [
118
    settings.participating,
119
    settings.showBots,
120
    settings.detailedNotifications,
121
    auth.accounts.length,
122
  ]);
123

124
  useInterval(() => {
26✔
125
    fetchNotifications(auth, settings);
6✔
126
  }, Constants.FETCH_INTERVAL);
127

128
  // biome-ignore lint/correctness/useExhaustiveDependencies: We need to update tray title when settings or notifications changes.
129
  useEffect(() => {
26✔
130
    const count = getNotificationCount(notifications);
22✔
131

132
    if (settings.showNotificationsCountInTray && count > 0) {
22!
133
      updateTrayTitle(count.toString());
×
134
    } else {
135
      updateTrayTitle();
22✔
136
    }
137
  }, [settings.showNotificationsCountInTray, notifications]);
138

139
  const updateSetting = useCallback(
26✔
140
    (name: keyof SettingsState, value: boolean | Theme) => {
141
      if (name === 'openAtStartup') {
4✔
142
        setAutoLaunch(value as boolean);
2✔
143
      }
144

145
      const newSettings = { ...settings, [name]: value };
4✔
146
      setSettings(newSettings);
4✔
147
      saveState({ auth, settings: newSettings });
4✔
148
    },
149
    [auth, settings],
150
  );
151

152
  const isLoggedIn = useMemo(() => {
26✔
153
    return auth.accounts.length > 0;
22✔
154
  }, [auth]);
155

156
  const loginWithGitHubApp = useCallback(async () => {
26✔
157
    const { authCode } = await authGitHub();
×
158
    const { token } = await getToken(authCode);
×
159
    const hostname = Constants.DEFAULT_AUTH_OPTIONS.hostname;
×
160
    const user = await getUserData(token, hostname);
×
NEW
161
    const updatedAuth = addAccount(auth, 'GitHub App', token, hostname, user);
×
162
    setAuth(updatedAuth);
×
163
    saveState({ auth: updatedAuth, settings });
×
164
  }, [auth, settings]);
165

166
  const loginWithOAuthApp = useCallback(
26✔
167
    async (data: LoginOAuthAppOptions) => {
168
      const { authOptions, authCode } = await authGitHub(data);
×
169
      const { token, hostname } = await getToken(authCode, authOptions);
×
NEW
170
      const updatedAuth = addAccount(auth, 'OAuth App', token, hostname);
×
171
      setAuth(updatedAuth);
×
172
      saveState({ auth: updatedAuth, settings });
×
173
    },
174
    [auth, settings],
175
  );
176

177
  const loginWithPersonalAccessToken = useCallback(
26✔
178
    async ({ token, hostname }: LoginPersonalAccessTokenOptions) => {
179
      await headNotifications(hostname, token);
2✔
180

181
      const user = await getUserData(token, hostname);
2✔
NEW
182
      const updatedAuth = addAccount(
×
183
        auth,
184
        'Personal Access Token',
185
        token,
186
        hostname,
187
        user,
188
      );
189
      setAuth(updatedAuth);
×
190
      saveState({ auth: updatedAuth, settings });
×
191
    },
192
    [auth, settings],
193
  );
194

195
  const logout = useCallback(() => {
26✔
196
    setAuth(defaultAuth);
2✔
197
    clearState();
2✔
198
  }, []);
199

200
  const restoreSettings = useCallback(() => {
26✔
201
    // Migrate authenticated accounts
202
    migrateAuthenticatedAccounts();
22✔
203

204
    const existing = loadState();
22✔
205

206
    if (existing.auth) {
22!
UNCOV
207
      setAuth({ ...defaultAuth, ...existing.auth });
×
208
    }
209

210
    if (existing.settings) {
22!
211
      setSettings({ ...defaultSettings, ...existing.settings });
×
UNCOV
212
      return existing.settings;
×
213
    }
214
  }, []);
215

216
  const fetchNotificationsWithAccounts = useCallback(
26✔
217
    async () => await fetchNotifications(auth, settings),
2✔
218
    [auth, settings, notifications],
219
  );
220

221
  const markNotificationReadWithAccounts = useCallback(
26✔
222
    async (id: string, hostname: string) =>
223
      await markNotificationRead(auth, settings, id, hostname),
2✔
224
    [auth, notifications],
225
  );
226

227
  const markNotificationDoneWithAccounts = useCallback(
26✔
228
    async (id: string, hostname: string) =>
229
      await markNotificationDone(auth, settings, id, hostname),
2✔
230
    [auth, notifications],
231
  );
232

233
  const unsubscribeNotificationWithAccounts = useCallback(
26✔
234
    async (id: string, hostname: string) =>
235
      await unsubscribeNotification(auth, settings, id, hostname),
2✔
236
    [auth, notifications],
237
  );
238

239
  const markRepoNotificationsWithAccounts = useCallback(
26✔
240
    async (repoSlug: string, hostname: string) =>
241
      await markRepoNotifications(auth, settings, repoSlug, hostname),
2✔
242
    [auth, notifications],
243
  );
244

245
  const markRepoNotificationsDoneWithAccounts = useCallback(
26✔
246
    async (repoSlug: string, hostname: string) =>
247
      await markRepoNotificationsDone(auth, settings, repoSlug, hostname),
2✔
248
    [auth, notifications],
249
  );
250

251
  return (
26✔
252
    <AppContext.Provider
253
      value={{
254
        auth,
255
        isLoggedIn,
256
        loginWithGitHubApp,
257
        loginWithOAuthApp,
258
        loginWithPersonalAccessToken,
259
        logout,
260

261
        notifications,
262
        status,
263
        errorDetails,
264
        removeNotificationFromState,
265
        fetchNotifications: fetchNotificationsWithAccounts,
266
        markNotificationRead: markNotificationReadWithAccounts,
267
        markNotificationDone: markNotificationDoneWithAccounts,
268
        unsubscribeNotification: unsubscribeNotificationWithAccounts,
269
        markRepoNotifications: markRepoNotificationsWithAccounts,
270
        markRepoNotificationsDone: markRepoNotificationsDoneWithAccounts,
271

272
        settings,
273
        updateSetting,
274
      }}
275
    >
276
      {children}
277
    </AppContext.Provider>
278
  );
279
};
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc