From bf848c1e484684c0226d42c98538432530c690e2 Mon Sep 17 00:00:00 2001 From: dammy95 Date: Mon, 10 Jun 2024 13:35:17 +0100 Subject: [PATCH 1/9] Add the hostname Branded type --- src/__mocks__/partial-mocks.ts | 3 +- src/__mocks__/state-mocks.ts | 7 ++-- src/components/AccountNotifications.test.tsx | 5 ++- src/components/AccountNotifications.tsx | 3 +- src/components/NotificationRow.test.tsx | 39 ++++++++++--------- src/components/NotificationRow.tsx | 3 +- src/components/Repository.test.tsx | 3 +- src/components/Repository.tsx | 3 +- src/context/App.test.tsx | 29 ++++++++------ src/context/App.tsx | 23 +++++------ src/hooks/useNotifications.test.ts | 11 +++--- src/hooks/useNotifications.ts | 25 ++++++------ src/routes/Accounts.tsx | 3 +- src/routes/LoginWithOAuthApp.test.tsx | 3 +- src/routes/LoginWithOAuthApp.tsx | 7 ++-- src/routes/LoginWithPersonalAccessToken.tsx | 3 +- src/types.ts | 3 +- src/utils/api/client.test.ts | 5 ++- src/utils/api/client.ts | 13 ++++--- src/utils/api/utils.test.ts | 9 +++-- src/utils/api/utils.ts | 5 ++- src/utils/auth/migration.test.ts | 15 +++---- src/utils/auth/types.ts | 10 +++-- src/utils/auth/utils.test.ts | 41 ++++++++++---------- src/utils/auth/utils.ts | 11 +++--- src/utils/constants.ts | 3 +- src/utils/helpers.test.ts | 21 ++++++---- src/utils/helpers.ts | 10 +++-- src/utils/remove-notification.ts | 3 +- src/utils/remove-notifications.ts | 3 +- 30 files changed, 183 insertions(+), 139 deletions(-) diff --git a/src/__mocks__/partial-mocks.ts b/src/__mocks__/partial-mocks.ts index aefbbe9be..64ae1dd78 100644 --- a/src/__mocks__/partial-mocks.ts +++ b/src/__mocks__/partial-mocks.ts @@ -1,4 +1,5 @@ import type { Notification, Subject, User } from '../typesGitHub'; +import type { HostName } from '../utils/branded-types'; import Constants from '../utils/constants'; import { mockGitifyUser, mockToken } from './state-mocks'; @@ -9,7 +10,7 @@ export function partialMockNotification( account: { method: 'Personal Access Token', platform: 'GitHub Cloud', - hostname: Constants.GITHUB_API_BASE_URL, + hostname: Constants.GITHUB_API_BASE_URL as HostName, token: mockToken, user: mockGitifyUser, }, diff --git a/src/__mocks__/state-mocks.ts b/src/__mocks__/state-mocks.ts index e8828d638..45573a2ff 100644 --- a/src/__mocks__/state-mocks.ts +++ b/src/__mocks__/state-mocks.ts @@ -7,11 +7,12 @@ import { Theme, } from '../types'; import type { EnterpriseAccount } from '../utils/auth/types'; +import type { HostName } from '../utils/branded-types'; import Constants from '../utils/constants'; export const mockEnterpriseAccounts: EnterpriseAccount[] = [ { - hostname: 'github.gitify.io', + hostname: 'github.gitify.io' as HostName, token: '1234568790', }, ]; @@ -34,7 +35,7 @@ export const mockOAuthAccount: Account = { platform: 'GitHub Enterprise Server', method: 'OAuth App', token: '1234568790', - hostname: 'github.gitify.io', + hostname: 'github.gitify.io' as HostName, user: mockGitifyUser, }; @@ -50,7 +51,7 @@ export const mockGitHubEnterpriseServerAccount: Account = { platform: 'GitHub Enterprise Server', method: 'Personal Access Token', token: '1234568790', - hostname: 'github.gitify.io', + hostname: 'github.gitify.io' as HostName, user: mockGitifyUser, }; diff --git a/src/components/AccountNotifications.test.tsx b/src/components/AccountNotifications.test.tsx index f52955e5b..78bfadb1b 100644 --- a/src/components/AccountNotifications.test.tsx +++ b/src/components/AccountNotifications.test.tsx @@ -1,5 +1,6 @@ import { render } from '@testing-library/react'; import { mockGitHubNotifications } from '../utils/api/__mocks__/response-mocks'; +import type { HostName } from '../utils/branded-types'; import { AccountNotifications } from './AccountNotifications'; jest.mock('./Repository', () => ({ @@ -9,7 +10,7 @@ jest.mock('./Repository', () => ({ describe('components/AccountNotifications.tsx', () => { it('should render itself (github.com with notifications)', () => { const props = { - hostname: 'github.com', + hostname: 'github.com' as HostName, notifications: mockGitHubNotifications, showAccountHostname: true, }; @@ -20,7 +21,7 @@ describe('components/AccountNotifications.tsx', () => { it('should render itself (github.com without notifications)', () => { const props = { - hostname: 'github.com', + hostname: 'github.com' as HostName, notifications: [], showAccountHostname: true, }; diff --git a/src/components/AccountNotifications.tsx b/src/components/AccountNotifications.tsx index 738dd8098..dc28d36fa 100644 --- a/src/components/AccountNotifications.tsx +++ b/src/components/AccountNotifications.tsx @@ -1,9 +1,10 @@ import { ChevronDownIcon, ChevronLeftIcon } from '@primer/octicons-react'; import type { Notification } from '../typesGitHub'; +import type { HostName } from '../utils/branded-types'; import { RepositoryNotifications } from './Repository'; interface IProps { - hostname: string; + hostname: HostName; notifications: Notification[]; showAccountHostname: boolean; } diff --git a/src/components/NotificationRow.test.tsx b/src/components/NotificationRow.test.tsx index 515386923..0c2fba41f 100644 --- a/src/components/NotificationRow.test.tsx +++ b/src/components/NotificationRow.test.tsx @@ -4,6 +4,7 @@ import { mockAuth, mockSettings } from '../__mocks__/state-mocks'; import { AppContext } from '../context/App'; import type { Milestone, UserType } from '../typesGitHub'; import { mockSingleNotification } from '../utils/api/__mocks__/response-mocks'; +import type { HostName } from '../utils/branded-types'; import * as helpers from '../utils/helpers'; import { NotificationRow } from './NotificationRow'; @@ -23,7 +24,7 @@ describe('components/NotificationRow.tsx', () => { const props = { notification: mockSingleNotification, - hostname: 'github.com', + hostname: 'github.com' as HostName, }; const tree = render( @@ -44,7 +45,7 @@ describe('components/NotificationRow.tsx', () => { const props = { notification: mockNotification, - hostname: 'github.com', + hostname: 'github.com' as HostName, }; const tree = render( @@ -65,7 +66,7 @@ describe('components/NotificationRow.tsx', () => { const props = { notification: mockNotification, - hostname: 'github.com', + hostname: 'github.com' as HostName, }; const tree = render( @@ -88,7 +89,7 @@ describe('components/NotificationRow.tsx', () => { const props = { notification: mockNotification, - hostname: 'github.com', + hostname: 'github.com' as HostName, }; const tree = render( @@ -115,7 +116,7 @@ describe('components/NotificationRow.tsx', () => { const props = { notification: mockNotification, - hostname: 'github.com', + hostname: 'github.com' as HostName, }; const tree = render( @@ -140,7 +141,7 @@ describe('components/NotificationRow.tsx', () => { const props = { notification: mockNotification, - hostname: 'github.com', + hostname: 'github.com' as HostName, }; const tree = render( @@ -167,7 +168,7 @@ describe('components/NotificationRow.tsx', () => { const props = { notification: mockNotification, - hostname: 'github.com', + hostname: 'github.com' as HostName, }; const tree = render( @@ -192,7 +193,7 @@ describe('components/NotificationRow.tsx', () => { const props = { notification: mockNotification, - hostname: 'github.com', + hostname: 'github.com' as HostName, }; const tree = render( @@ -217,7 +218,7 @@ describe('components/NotificationRow.tsx', () => { const props = { notification: mockNotification, - hostname: 'github.com', + hostname: 'github.com' as HostName, }; const tree = render( @@ -244,7 +245,7 @@ describe('components/NotificationRow.tsx', () => { const props = { notification: mockNotification, - hostname: 'github.com', + hostname: 'github.com' as HostName, }; const tree = render( @@ -274,7 +275,7 @@ describe('components/NotificationRow.tsx', () => { const props = { notification: mockNotification, - hostname: 'github.com', + hostname: 'github.com' as HostName, }; const tree = render( @@ -302,7 +303,7 @@ describe('components/NotificationRow.tsx', () => { const props = { notification: mockNotification, - hostname: 'github.com', + hostname: 'github.com' as HostName, }; const tree = render( @@ -325,7 +326,7 @@ describe('components/NotificationRow.tsx', () => { const props = { notification: mockSingleNotification, - hostname: 'github.com', + hostname: 'github.com' as HostName, }; render( @@ -350,7 +351,7 @@ describe('components/NotificationRow.tsx', () => { const props = { notification: mockSingleNotification, - hostname: 'github.com', + hostname: 'github.com' as HostName, }; render( @@ -375,7 +376,7 @@ describe('components/NotificationRow.tsx', () => { const props = { notification: mockSingleNotification, - hostname: 'github.com', + hostname: 'github.com' as HostName, }; render( @@ -400,7 +401,7 @@ describe('components/NotificationRow.tsx', () => { const props = { notification: mockSingleNotification, - hostname: 'github.com', + hostname: 'github.com' as HostName, }; render( @@ -423,7 +424,7 @@ describe('components/NotificationRow.tsx', () => { const props = { notification: mockSingleNotification, - hostname: 'github.com', + hostname: 'github.com' as HostName, }; render( @@ -443,7 +444,7 @@ describe('components/NotificationRow.tsx', () => { const props = { notification: mockSingleNotification, - hostname: 'github.com', + hostname: 'github.com' as HostName, }; render( @@ -475,7 +476,7 @@ describe('components/NotificationRow.tsx', () => { reviews: null, }, }, - hostname: 'github.com', + hostname: 'github.com' as HostName, }; render( diff --git a/src/components/NotificationRow.tsx b/src/components/NotificationRow.tsx index 76575e2fc..2bcd1f392 100644 --- a/src/components/NotificationRow.tsx +++ b/src/components/NotificationRow.tsx @@ -19,6 +19,7 @@ import { import { AppContext } from '../context/App'; import { IconColor } from '../types'; import type { Notification } from '../typesGitHub'; +import type { HostName } from '../utils/branded-types'; import { openExternalLink } from '../utils/comms'; import Constants from '../utils/constants'; import { @@ -34,7 +35,7 @@ import { import { formatReason } from '../utils/reason'; interface IProps { - hostname: string; + hostname: HostName; notification: Notification; } diff --git a/src/components/Repository.test.tsx b/src/components/Repository.test.tsx index 888e76584..4f1ff0306 100644 --- a/src/components/Repository.test.tsx +++ b/src/components/Repository.test.tsx @@ -2,6 +2,7 @@ import { fireEvent, render, screen } from '@testing-library/react'; import { shell } from 'electron'; import { AppContext } from '../context/App'; import { mockGitHubNotifications } from '../utils/api/__mocks__/response-mocks'; +import type { HostName } from '../utils/branded-types'; import { RepositoryNotifications } from './Repository'; jest.mock('./NotificationRow', () => ({ @@ -13,7 +14,7 @@ describe('components/Repository.tsx', () => { const markRepoNotificationsDone = jest.fn(); const props = { - hostname: 'github.com', + hostname: 'github.com' as HostName, repoName: 'gitify-app/notifications-test', repoNotifications: mockGitHubNotifications, }; diff --git a/src/components/Repository.tsx b/src/components/Repository.tsx index 2b7877617..197b377b3 100644 --- a/src/components/Repository.tsx +++ b/src/components/Repository.tsx @@ -3,11 +3,12 @@ import { type FC, useCallback, useContext } from 'react'; import { CSSTransition, TransitionGroup } from 'react-transition-group'; import { AppContext } from '../context/App'; import type { Notification } from '../typesGitHub'; +import type { HostName } from '../utils/branded-types'; import { openExternalLink } from '../utils/comms'; import { NotificationRow } from './NotificationRow'; interface IProps { - hostname: string; + hostname: HostName; repoNotifications: Notification[]; repoName: string; } diff --git a/src/context/App.test.tsx b/src/context/App.test.tsx index 5773b80e5..f6a834081 100644 --- a/src/context/App.test.tsx +++ b/src/context/App.test.tsx @@ -4,6 +4,7 @@ import { mockAuth, mockSettings } from '../__mocks__/state-mocks'; import { useNotifications } from '../hooks/useNotifications'; import type { AuthState, SettingsState } from '../types'; import * as apiRequests from '../utils/api/request'; +import type { HostName } from '../utils/branded-types'; import * as comms from '../utils/comms'; import Constants from '../utils/constants'; import * as notifications from '../utils/notifications'; @@ -121,7 +122,9 @@ describe('context/App.tsx', () => { return ( @@ -138,7 +141,7 @@ describe('context/App.tsx', () => { expect(markNotificationReadMock).toHaveBeenCalledWith( mockDefaultState, '123-456', - 'github.com', + 'github.com' as HostName, ); }); @@ -149,7 +152,9 @@ describe('context/App.tsx', () => { return ( @@ -166,7 +171,7 @@ describe('context/App.tsx', () => { expect(markNotificationDoneMock).toHaveBeenCalledWith( mockDefaultState, '123-456', - 'github.com', + 'github.com' as HostName, ); }); @@ -177,7 +182,9 @@ describe('context/App.tsx', () => { return ( @@ -194,7 +201,7 @@ describe('context/App.tsx', () => { expect(unsubscribeNotificationMock).toHaveBeenCalledWith( mockDefaultState, '123-456', - 'github.com', + 'github.com' as HostName, ); }); @@ -208,7 +215,7 @@ describe('context/App.tsx', () => { onClick={() => markRepoNotifications( 'gitify-app/notifications-test', - 'github.com', + 'github.com' as HostName, ) } > @@ -227,7 +234,7 @@ describe('context/App.tsx', () => { expect(markRepoNotificationsMock).toHaveBeenCalledWith( mockDefaultState, 'gitify-app/notifications-test', - 'github.com', + 'github.com' as HostName, ); }); @@ -241,7 +248,7 @@ describe('context/App.tsx', () => { onClick={() => markRepoNotificationsDone( 'gitify-app/notifications-test', - 'github.com', + 'github.com' as HostName, ) } > @@ -268,7 +275,7 @@ describe('context/App.tsx', () => { settings: mockSettings, }, 'gitify-app/notifications-test', - 'github.com', + 'github.com' as HostName, ); }); }); @@ -294,7 +301,7 @@ describe('context/App.tsx', () => { type="button" onClick={() => loginWithPersonalAccessToken({ - hostname: 'github.com', + hostname: 'github.com' as HostName as HostName, token: '123-456', }) } diff --git a/src/context/App.tsx b/src/context/App.tsx index 6f138eaed..42f8538d1 100644 --- a/src/context/App.tsx +++ b/src/context/App.tsx @@ -30,6 +30,7 @@ import { getUserData, removeAccount, } from '../utils/auth/utils'; +import type { HostName } from '../utils/branded-types'; import { setAutoLaunch, updateTrayTitle } from '../utils/comms'; import Constants from '../utils/constants'; import { getNotificationCount } from '../utils/notifications'; @@ -73,14 +74,14 @@ interface AppContextState { removeNotificationFromState: ( settings: SettingsState, id: string, - hostname: string, + hostname: HostName, ) => void; fetchNotifications: () => Promise; - markNotificationRead: (id: string, hostname: string) => Promise; - markNotificationDone: (id: string, hostname: string) => Promise; - unsubscribeNotification: (id: string, hostname: string) => Promise; - markRepoNotifications: (id: string, hostname: string) => Promise; - markRepoNotificationsDone: (id: string, hostname: string) => Promise; + markNotificationRead: (id: string, hostname: HostName) => Promise; + markNotificationDone: (id: string, hostname: HostName) => Promise; + unsubscribeNotification: (id: string, hostname: HostName) => Promise; + markRepoNotifications: (id: string, hostname: HostName) => Promise; + markRepoNotificationsDone: (id: string, hostname: HostName) => Promise; settings: SettingsState; updateSetting: ( @@ -231,31 +232,31 @@ export const AppProvider = ({ children }: { children: ReactNode }) => { ); const markNotificationReadWithAccounts = useCallback( - async (id: string, hostname: string) => + async (id: string, hostname: HostName) => await markNotificationRead({ auth, settings }, id, hostname), [auth, notifications], ); const markNotificationDoneWithAccounts = useCallback( - async (id: string, hostname: string) => + async (id: string, hostname: HostName) => await markNotificationDone({ auth, settings }, id, hostname), [auth, notifications], ); const unsubscribeNotificationWithAccounts = useCallback( - async (id: string, hostname: string) => + async (id: string, hostname: HostName) => await unsubscribeNotification({ auth, settings }, id, hostname), [auth, notifications], ); const markRepoNotificationsWithAccounts = useCallback( - async (repoSlug: string, hostname: string) => + async (repoSlug: string, hostname: HostName) => await markRepoNotifications({ auth, settings }, repoSlug, hostname), [auth, notifications], ); const markRepoNotificationsDoneWithAccounts = useCallback( - async (repoSlug: string, hostname: string) => + async (repoSlug: string, hostname: HostName) => await markRepoNotificationsDone({ auth, settings }, repoSlug, hostname), [auth, notifications], ); diff --git a/src/hooks/useNotifications.test.ts b/src/hooks/useNotifications.test.ts index 049732824..3a3cb7ba8 100644 --- a/src/hooks/useNotifications.test.ts +++ b/src/hooks/useNotifications.test.ts @@ -9,6 +9,7 @@ import { mockState, } from '../__mocks__/state-mocks'; import { mockNotificationUser } from '../utils/api/__mocks__/response-mocks'; +import type { HostName } from '../utils/branded-types'; import { Errors } from '../utils/constants'; import { useNotifications } from './useNotifications'; @@ -333,7 +334,7 @@ describe('hooks/useNotifications.ts', () => { }); describe('markNotificationRead', () => { - const hostname = 'github.com'; + const hostname = 'github.com' as HostName; const id = 'notification-123'; it('should mark a notification as read with success', async () => { @@ -374,7 +375,7 @@ describe('hooks/useNotifications.ts', () => { }); describe('markNotificationDone', () => { - const hostname = 'github.com'; + const hostname = 'github.com' as HostName; const id = 'notification-123'; it('should mark a notification as done with success', async () => { @@ -415,7 +416,7 @@ describe('hooks/useNotifications.ts', () => { }); describe('unsubscribeNotification', () => { - const hostname = 'github.com'; + const hostname = 'github.com' as HostName; const id = 'notification-123'; it('should unsubscribe from a notification with success', async () => { @@ -468,7 +469,7 @@ describe('hooks/useNotifications.ts', () => { }); describe('markRepoNotifications', () => { - const hostname = 'github.com'; + const hostname = 'github.com' as HostName; const repoSlug = 'gitify-app/notifications-test'; it("should mark a repository's notifications as read with success", async () => { @@ -509,7 +510,7 @@ describe('hooks/useNotifications.ts', () => { }); describe('markRepoNotificationsDone', () => { - const hostname = 'github.com'; + const hostname = 'github.com' as HostName; const repoSlug = 'gitify-app/notifications-test'; const id = 'notification-123'; diff --git a/src/hooks/useNotifications.ts b/src/hooks/useNotifications.ts index 7dd93dff4..d62b2c0a4 100644 --- a/src/hooks/useNotifications.ts +++ b/src/hooks/useNotifications.ts @@ -13,6 +13,7 @@ import { markRepositoryNotificationsAsRead, } from '../utils/api/client'; import { determineFailureType } from '../utils/api/errors'; +import type { HostName } from '../utils/branded-types'; import { getAccountForHost } from '../utils/helpers'; import { getAllNotifications, @@ -27,33 +28,33 @@ interface NotificationsState { removeNotificationFromState: ( settings: SettingsState, id: string, - hostname: string, + hostname: HostName, ) => void; fetchNotifications: (state: GitifyState) => Promise; markNotificationRead: ( state: GitifyState, id: string, - hostname: string, + hostname: HostName, ) => Promise; markNotificationDone: ( state: GitifyState, id: string, - hostname: string, + hostname: HostName, ) => Promise; unsubscribeNotification: ( state: GitifyState, id: string, - hostname: string, + hostname: HostName, ) => Promise; markRepoNotifications: ( state: GitifyState, repoSlug: string, - hostname: string, + hostname: HostName, ) => Promise; markRepoNotificationsDone: ( state: GitifyState, repoSlug: string, - hostname: string, + hostname: HostName, ) => Promise; status: Status; errorDetails: GitifyError; @@ -86,7 +87,7 @@ export const useNotifications = (): NotificationsState => { ); const markNotificationRead = useCallback( - async (state: GitifyState, id: string, hostname: string) => { + async (state: GitifyState, id: string, hostname: HostName) => { setStatus('loading'); const account = getAccountForHost(hostname, state.auth); @@ -112,7 +113,7 @@ export const useNotifications = (): NotificationsState => { ); const markNotificationDone = useCallback( - async (state: GitifyState, id: string, hostname: string) => { + async (state: GitifyState, id: string, hostname: HostName) => { setStatus('loading'); const account = getAccountForHost(hostname, state.auth); @@ -138,7 +139,7 @@ export const useNotifications = (): NotificationsState => { ); const unsubscribeNotification = useCallback( - async (state: GitifyState, id: string, hostname: string) => { + async (state: GitifyState, id: string, hostname: HostName) => { setStatus('loading'); const account = getAccountForHost(hostname, state.auth); @@ -155,7 +156,7 @@ export const useNotifications = (): NotificationsState => { ); const markRepoNotifications = useCallback( - async (state: GitifyState, repoSlug: string, hostname: string) => { + async (state: GitifyState, repoSlug: string, hostname: HostName) => { setStatus('loading'); const account = getAccountForHost(hostname, state.auth); @@ -183,7 +184,7 @@ export const useNotifications = (): NotificationsState => { ); const markRepoNotificationsDone = useCallback( - async (state: GitifyState, repoSlug: string, hostname: string) => { + async (state: GitifyState, repoSlug: string, hostname: HostName) => { setStatus('loading'); try { @@ -227,7 +228,7 @@ export const useNotifications = (): NotificationsState => { ); const removeNotificationFromState = useCallback( - (settings: SettingsState, id: string, hostname: string) => { + (settings: SettingsState, id: string, hostname: HostName) => { const updatedNotifications = removeNotification( settings, id, diff --git a/src/routes/Accounts.tsx b/src/routes/Accounts.tsx index 69c597cc9..50d7454f2 100644 --- a/src/routes/Accounts.tsx +++ b/src/routes/Accounts.tsx @@ -15,6 +15,7 @@ import { AuthMethodIcon } from '../components/icons/AuthMethodIcon'; import { PlatformIcon } from '../components/icons/PlatformIcon'; import type { Account } from '../types'; import { getAccountUUID, getDeveloperSettingsURL } from '../utils/auth/utils'; +import type { HostName } from '../utils/branded-types'; import { openExternalLink, updateTrayIcon, @@ -42,7 +43,7 @@ export const AccountsRoute: FC = () => { openExternalLink(url.toString()); }; - const openHost = (hostname: string) => { + const openHost = (hostname: HostName) => { openExternalLink(`https://${hostname}`); }; diff --git a/src/routes/LoginWithOAuthApp.test.tsx b/src/routes/LoginWithOAuthApp.test.tsx index cd062d6a4..64f0938f1 100644 --- a/src/routes/LoginWithOAuthApp.test.tsx +++ b/src/routes/LoginWithOAuthApp.test.tsx @@ -3,6 +3,7 @@ import { shell } from 'electron'; import { MemoryRouter } from 'react-router-dom'; import { AppContext } from '../context/App'; import type { AuthState } from '../types'; +import type { HostName } from '../utils/branded-types'; import { LoginWithOAuthApp, validate } from './LoginWithOAuthApp'; const mockNavigate = jest.fn(); @@ -64,7 +65,7 @@ describe('routes/LoginWithOAuthApp.tsx', () => { values = { ...emptyValues, - hostname: 'hello', + hostname: 'hello' as HostName, clientId: '!@£INVALID-.1', clientSecret: '!@£INVALID-.1', }; diff --git a/src/routes/LoginWithOAuthApp.tsx b/src/routes/LoginWithOAuthApp.tsx index f95c54153..e76ab3b3b 100644 --- a/src/routes/LoginWithOAuthApp.tsx +++ b/src/routes/LoginWithOAuthApp.tsx @@ -17,12 +17,13 @@ import { isValidHostname, isValidToken, } from '../utils/auth/utils'; +import type { ClientID, ClientSecret, HostName } from '../utils/branded-types'; import Constants from '../utils/constants'; interface IValues { - hostname?: string; - clientId?: string; - clientSecret?: string; + hostname?: HostName; + clientId?: ClientID; + clientSecret?: ClientSecret; } interface IFormErrors { diff --git a/src/routes/LoginWithPersonalAccessToken.tsx b/src/routes/LoginWithPersonalAccessToken.tsx index 26b79cea6..31f1d9590 100644 --- a/src/routes/LoginWithPersonalAccessToken.tsx +++ b/src/routes/LoginWithPersonalAccessToken.tsx @@ -16,11 +16,12 @@ import { isValidHostname, isValidToken, } from '../utils/auth/utils'; +import type { HostName } from '../utils/branded-types'; import { Constants } from '../utils/constants'; interface IValues { token?: string; - hostname?: string; + hostname?: HostName; } interface IFormErrors { diff --git a/src/types.ts b/src/types.ts index b9688f535..f219a77ab 100644 --- a/src/types.ts +++ b/src/types.ts @@ -6,6 +6,7 @@ import type { EnterpriseAccount, PlatformType, } from './utils/auth/types'; +import type { HostName } from './utils/branded-types'; export type Status = 'loading' | 'success' | 'error'; @@ -28,7 +29,7 @@ export interface AuthState { export interface Account { method: AuthMethod; platform: PlatformType; - hostname: string; + hostname: HostName; token: string; user: GitifyUser | null; } diff --git a/src/utils/api/client.test.ts b/src/utils/api/client.test.ts index 526866f8f..89219d277 100644 --- a/src/utils/api/client.test.ts +++ b/src/utils/api/client.test.ts @@ -5,6 +5,7 @@ import { mockToken, } from '../../__mocks__/state-mocks'; import type { SettingsState } from '../../types'; +import type { HostName } from '../branded-types'; import { getAuthenticatedUser, getHtmlUrl, @@ -19,8 +20,8 @@ import * as apiRequests from './request'; jest.mock('axios'); -const mockGitHubHostname = 'github.com'; -const mockEnterpriseHostname = 'example.com'; +const mockGitHubHostname = 'github.com' as HostName; +const mockEnterpriseHostname = 'example.com' as HostName; const mockThreadId = '1234'; const mockRepoSlug = 'gitify-app/notifications-test'; diff --git a/src/utils/api/client.ts b/src/utils/api/client.ts index 3c74a8bd9..3c3cbb6b3 100644 --- a/src/utils/api/client.ts +++ b/src/utils/api/client.ts @@ -15,6 +15,7 @@ import type { Release, UserDetails, } from '../../typesGitHub'; +import type { HostName } from '../branded-types'; import { QUERY_SEARCH_DISCUSSIONS } from './graphql/discussions'; import { formatAsGitHubSearchSyntax } from './graphql/utils'; import { apiRequestAuth } from './request'; @@ -26,7 +27,7 @@ import { getGitHubAPIBaseUrl, getGitHubGraphQLUrl } from './utils'; * Endpoint documentation: https://docs.github.com/en/rest/users/users#get-the-authenticated-user */ export function getAuthenticatedUser( - hostname: string, + hostname: HostName, token: string, ): AxiosPromise { const url = getGitHubAPIBaseUrl(hostname); @@ -37,7 +38,7 @@ export function getAuthenticatedUser( // export function headNotifications( - hostname: string, + hostname: HostName, token: string, ): AxiosPromise { const url = getGitHubAPIBaseUrl(hostname); @@ -70,7 +71,7 @@ export function listNotificationsForAuthenticatedUser( */ export function markNotificationThreadAsRead( threadId: string, - hostname: string, + hostname: HostName, token: string, ): AxiosPromise { const url = getGitHubAPIBaseUrl(hostname); @@ -87,7 +88,7 @@ export function markNotificationThreadAsRead( */ export function markNotificationThreadAsDone( threadId: string, - hostname: string, + hostname: HostName, token: string, ): AxiosPromise { const url = getGitHubAPIBaseUrl(hostname); @@ -103,7 +104,7 @@ export function markNotificationThreadAsDone( */ export function ignoreNotificationThreadSubscription( threadId: string, - hostname: string, + hostname: HostName, token: string, ): AxiosPromise { const url = getGitHubAPIBaseUrl(hostname); @@ -122,7 +123,7 @@ export function ignoreNotificationThreadSubscription( */ export function markRepositoryNotificationsAsRead( repoSlug: string, - hostname: string, + hostname: HostName, token: string, ): AxiosPromise { const url = getGitHubAPIBaseUrl(hostname); diff --git a/src/utils/api/utils.test.ts b/src/utils/api/utils.test.ts index 8c05e4acd..0aa3baf36 100644 --- a/src/utils/api/utils.test.ts +++ b/src/utils/api/utils.test.ts @@ -1,26 +1,27 @@ +import type { HostName } from '../branded-types'; import { getGitHubAPIBaseUrl, getGitHubGraphQLUrl } from './utils'; describe('utils/api/utils.ts', () => { describe('getGitHubAPIBaseUrl', () => { it('should generate a GitHub API url - non enterprise', () => { - const result = getGitHubAPIBaseUrl('github.com'); + const result = getGitHubAPIBaseUrl('github.com' as HostName); expect(result.toString()).toBe('https://api.github.com/'); }); it('should generate a GitHub API url - enterprise', () => { - const result = getGitHubAPIBaseUrl('github.gitify.io'); + const result = getGitHubAPIBaseUrl('github.gitify.io' as HostName); expect(result.toString()).toBe('https://github.gitify.io/api/v3/'); }); }); describe('getGitHubGraphQLUrl', () => { it('should generate a GitHub GraphQL url - non enterprise', () => { - const result = getGitHubGraphQLUrl('github.com'); + const result = getGitHubGraphQLUrl('github.com' as HostName); expect(result.toString()).toBe('https://api.github.com/graphql'); }); it('should generate a GitHub GraphQL url - enterprise', () => { - const result = getGitHubGraphQLUrl('github.gitify.io'); + const result = getGitHubGraphQLUrl('github.gitify.io' as HostName); expect(result.toString()).toBe('https://github.gitify.io/api/graphql'); }); }); diff --git a/src/utils/api/utils.ts b/src/utils/api/utils.ts index f217ef6d9..ef5f1f41c 100644 --- a/src/utils/api/utils.ts +++ b/src/utils/api/utils.ts @@ -1,7 +1,8 @@ +import type { HostName } from '../branded-types'; import Constants from '../constants'; import { isEnterpriseHost } from '../helpers'; -export function getGitHubAPIBaseUrl(hostname: string): URL { +export function getGitHubAPIBaseUrl(hostname: HostName): URL { const url = new URL(Constants.GITHUB_API_BASE_URL); if (isEnterpriseHost(hostname)) { @@ -11,7 +12,7 @@ export function getGitHubAPIBaseUrl(hostname: string): URL { return url; } -export function getGitHubGraphQLUrl(hostname: string): URL { +export function getGitHubGraphQLUrl(hostname: HostName): URL { const url = new URL(Constants.GITHUB_API_GRAPHQL_URL); if (isEnterpriseHost(hostname)) { diff --git a/src/utils/auth/migration.test.ts b/src/utils/auth/migration.test.ts index 12832ab4c..16ded5b54 100644 --- a/src/utils/auth/migration.test.ts +++ b/src/utils/auth/migration.test.ts @@ -2,6 +2,7 @@ import axios from 'axios'; import nock from 'nock'; import { mockGitifyUser, mockToken } from '../../__mocks__/state-mocks'; import type { AuthState } from '../../types'; +import type { HostName } from '../branded-types'; import Constants from '../constants'; import { convertAccounts, @@ -61,7 +62,7 @@ describe('utils/auth/migration.ts', () => { expect( hasAccountsToMigrate({ enterpriseAccounts: [ - { hostname: 'github.gitify.io', token: mockToken }, + { hostname: 'github.gitify.io' as HostName, token: mockToken }, ], } as AuthState), ).toBe(true); @@ -72,9 +73,9 @@ describe('utils/auth/migration.ts', () => { hasAccountsToMigrate({ token: mockToken, enterpriseAccounts: [ - { hostname: 'github.gitify.io', token: mockToken }, + { hostname: 'github.gitify.io' as HostName, token: mockToken }, ], - accounts: [{ hostname: 'github.com', token: mockToken }], + accounts: [{ hostname: 'github.com' as HostName, token: mockToken }], } as AuthState), ).toBe(false); }); @@ -107,13 +108,13 @@ describe('utils/auth/migration.ts', () => { const result = await convertAccounts({ enterpriseAccounts: [ - { hostname: 'github.gitify.io', token: mockToken }, + { hostname: 'github.gitify.io' as HostName, token: mockToken }, ], } as AuthState); expect(result).toEqual([ { - hostname: 'github.gitify.io', + hostname: 'github.gitify.io' as HostName, platform: 'GitHub Enterprise Server', method: 'OAuth App', token: mockToken, @@ -132,7 +133,7 @@ describe('utils/auth/migration.ts', () => { token: mockToken, user: mockGitifyUser, enterpriseAccounts: [ - { hostname: 'github.gitify.io', token: mockToken }, + { hostname: 'github.gitify.io' as HostName, token: mockToken }, ], } as AuthState); @@ -145,7 +146,7 @@ describe('utils/auth/migration.ts', () => { user: mockGitifyUser, }, { - hostname: 'github.gitify.io', + hostname: 'github.gitify.io' as HostName, platform: 'GitHub Enterprise Server', method: 'OAuth App', token: mockToken, diff --git a/src/utils/auth/types.ts b/src/utils/auth/types.ts index f51261d80..1f2bdbffd 100644 --- a/src/utils/auth/types.ts +++ b/src/utils/auth/types.ts @@ -1,15 +1,17 @@ +import type { ClientID, ClientSecret, HostName, Token } from '../branded-types'; + export type AuthMethod = 'GitHub App' | 'Personal Access Token' | 'OAuth App'; export type PlatformType = 'GitHub Cloud' | 'GitHub Enterprise Server'; export interface LoginOAuthAppOptions { - hostname: string; + hostname: HostName; clientId: string; clientSecret: string; } export interface LoginPersonalAccessTokenOptions { - hostname: string; + hostname: HostName; token: string; } @@ -19,7 +21,7 @@ export interface AuthResponse { } export interface AuthTokenResponse { - hostname: string; + hostname: HostName; token: string; } @@ -27,6 +29,6 @@ export interface AuthTokenResponse { * @deprecated This type is deprecated and will be removed in a future release. */ export interface EnterpriseAccount { - hostname: string; + hostname: HostName; token: string; } diff --git a/src/utils/auth/utils.test.ts b/src/utils/auth/utils.test.ts index f120fd49a..d801bcd97 100644 --- a/src/utils/auth/utils.test.ts +++ b/src/utils/auth/utils.test.ts @@ -3,6 +3,7 @@ import type { AxiosPromise, AxiosResponse } from 'axios'; import { mockAuth, mockGitHubCloudAccount } from '../../__mocks__/state-mocks'; import type { Account, AuthState } from '../../types'; import * as apiRequests from '../api/request'; +import type { HostName } from '../branded-types'; import * as auth from './utils'; import { getNewOAuthAppURL, getNewTokenURL } from './utils'; @@ -85,7 +86,7 @@ describe('utils/auth/utils.ts', () => { }, ); expect(res.token).toBe('this-is-a-token'); - expect(res.hostname).toBe('github.com'); + expect(res.hostname).toBe('github.com' as HostName); }); it('should get a token - failure', async () => { @@ -118,12 +119,12 @@ describe('utils/auth/utils.ts', () => { mockAuthState, 'Personal Access Token', '123-456', - 'github.com', + 'github.com' as HostName, ); expect(result.accounts).toEqual([ { - hostname: 'github.com', + hostname: 'github.com' as HostName, method: 'Personal Access Token', platform: 'GitHub Cloud', token: '123-456', @@ -137,12 +138,12 @@ describe('utils/auth/utils.ts', () => { mockAuthState, 'OAuth App', '123-456', - 'github.com', + 'github.com' as HostName, ); expect(result.accounts).toEqual([ { - hostname: 'github.com', + hostname: 'github.com' as HostName, method: 'OAuth App', platform: 'GitHub Cloud', token: '123-456', @@ -158,12 +159,12 @@ describe('utils/auth/utils.ts', () => { mockAuthState, 'Personal Access Token', '123-456', - 'github.gitify.io', + 'github.gitify.io' as HostName, ); expect(result.accounts).toEqual([ { - hostname: 'github.gitify.io', + hostname: 'github.gitify.io' as HostName, method: 'Personal Access Token', platform: 'GitHub Enterprise Server', token: '123-456', @@ -177,12 +178,12 @@ describe('utils/auth/utils.ts', () => { mockAuthState, 'OAuth App', '123-456', - 'github.gitify.io', + 'github.gitify.io' as HostName, ); expect(result.accounts).toEqual([ { - hostname: 'github.gitify.io', + hostname: 'github.gitify.io' as HostName, method: 'OAuth App', platform: 'GitHub Enterprise Server', token: '123-456', @@ -218,19 +219,19 @@ describe('utils/auth/utils.ts', () => { it('getDeveloperSettingsURL', () => { expect( auth.getDeveloperSettingsURL({ - hostname: 'github.com', + hostname: 'github.com' as HostName, method: 'GitHub App', } as Account), ).toBe('https://github.com/settings/apps'); expect( auth.getDeveloperSettingsURL({ - hostname: 'github.com', + hostname: 'github.com' as HostName, method: 'OAuth App', } as Account), ).toBe('https://github.com/settings/developers'); expect( auth.getDeveloperSettingsURL({ - hostname: 'github.com', + hostname: 'github.com' as HostName, method: 'Personal Access Token', } as Account), ).toBe('https://github.com/settings/tokens'); @@ -239,7 +240,7 @@ describe('utils/auth/utils.ts', () => { describe('getNewTokenURL', () => { it('should generate new PAT url - github cloud', () => { expect( - getNewTokenURL('github.com').startsWith( + getNewTokenURL('github.com' as HostName).startsWith( 'https://github.com/settings/tokens/new', ), ).toBeTruthy(); @@ -247,7 +248,7 @@ describe('utils/auth/utils.ts', () => { it('should generate new PAT url - github server', () => { expect( - getNewTokenURL('github.gitify.io').startsWith( + getNewTokenURL('github.gitify.io' as HostName).startsWith( 'https://github.gitify.io/settings/tokens/new', ), ).toBeTruthy(); @@ -257,7 +258,7 @@ describe('utils/auth/utils.ts', () => { describe('getNewOAuthAppURL', () => { it('should generate new oauth app url - github cloud', () => { expect( - getNewOAuthAppURL('github.com').startsWith( + getNewOAuthAppURL('github.com' as HostName).startsWith( 'https://github.com/settings/applications/new', ), ).toBeTruthy(); @@ -265,7 +266,7 @@ describe('utils/auth/utils.ts', () => { it('should generate new oauth app url - github server', () => { expect( - getNewOAuthAppURL('github.gitify.io').startsWith( + getNewOAuthAppURL('github.gitify.io' as HostName).startsWith( 'https://github.gitify.io/settings/applications/new', ), ).toBeTruthy(); @@ -274,19 +275,19 @@ describe('utils/auth/utils.ts', () => { describe('isValidHostname', () => { it('should validate hostname - github cloud', () => { - expect(auth.isValidHostname('github.com')).toBeTruthy(); + expect(auth.isValidHostname('github.com' as HostName)).toBeTruthy(); }); it('should validate hostname - github enterprise server', () => { - expect(auth.isValidHostname('github.gitify.io')).toBeTruthy(); + expect(auth.isValidHostname('github.gitify.io' as HostName)).toBeTruthy(); }); it('should invalidate hostname - empty', () => { - expect(auth.isValidHostname('')).toBeFalsy(); + expect(auth.isValidHostname('' as HostName)).toBeFalsy(); }); it('should invalidate hostname - invalid', () => { - expect(auth.isValidHostname('github')).toBeFalsy(); + expect(auth.isValidHostname('github' as HostName)).toBeFalsy(); }); }); diff --git a/src/utils/auth/utils.ts b/src/utils/auth/utils.ts index f4ab7fe4a..57d889a98 100644 --- a/src/utils/auth/utils.ts +++ b/src/utils/auth/utils.ts @@ -4,6 +4,7 @@ import type { Account, AuthState, GitifyUser } from '../../types'; import type { UserDetails } from '../../typesGitHub'; import { getAuthenticatedUser } from '../api/client'; import { apiRequest } from '../api/request'; +import type { HostName } from '../branded-types'; import { Constants } from '../constants'; import { getPlatformFromHostname } from '../helpers'; import type { AuthMethod, AuthResponse, AuthTokenResponse } from './types'; @@ -77,7 +78,7 @@ export const authGitHub = ( export const getUserData = async ( token: string, - hostname: string, + hostname: HostName, ): Promise => { const response: UserDetails = (await getAuthenticatedUser(hostname, token)) .data; @@ -111,7 +112,7 @@ export function addAccount( auth: AuthState, method: AuthMethod, token: string, - hostname: string, + hostname: HostName, user?: GitifyUser, ): AuthState { return { @@ -155,7 +156,7 @@ export function getDeveloperSettingsURL(account: Account): string { return settingsURL.toString(); } -export function getNewTokenURL(hostname: string): string { +export function getNewTokenURL(hostname: HostName): string { const date = format(new Date(), 'PP p'); const newTokenURL = new URL(`https://${hostname}/settings/tokens/new`); newTokenURL.searchParams.append('description', `Gitify (Created on ${date})`); @@ -164,7 +165,7 @@ export function getNewTokenURL(hostname: string): string { return newTokenURL.toString(); } -export function getNewOAuthAppURL(hostname: string): string { +export function getNewOAuthAppURL(hostname: HostName): string { const date = format(new Date(), 'PP p'); const newOAuthAppURL = new URL( `https://${hostname}/settings/applications/new`, @@ -185,7 +186,7 @@ export function getNewOAuthAppURL(hostname: string): string { return newOAuthAppURL.toString(); } -export function isValidHostname(hostname: string) { +export function isValidHostname(hostname: HostName) { return /^([a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,6}$/i.test( hostname, ); diff --git a/src/utils/constants.ts b/src/utils/constants.ts index a3d607f94..081303425 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -1,11 +1,12 @@ import type { ErrorType, GitifyError } from '../types'; +import type { HostName } from './branded-types'; export const Constants = { // GitHub OAuth AUTH_SCOPE: ['read:user', 'notifications', 'repo'], DEFAULT_AUTH_OPTIONS: { - hostname: 'github.com', + hostname: 'github.com' as HostName, clientId: process.env.OAUTH_CLIENT_ID, clientSecret: process.env.OAUTH_CLIENT_SECRET, }, diff --git a/src/utils/helpers.test.ts b/src/utils/helpers.test.ts index ebabde895..051f4e9e5 100644 --- a/src/utils/helpers.test.ts +++ b/src/utils/helpers.test.ts @@ -10,6 +10,7 @@ import { mockSingleNotification, } from './api/__mocks__/response-mocks'; import * as apiRequests from './api/request'; +import type { HostName } from './branded-types'; import { formatForDisplay, formatNotificationUpdatedAt, @@ -66,15 +67,19 @@ describe('utils/helpers.ts', () => { describe('getPlatformFromHostname', () => { it('should return GitHub Cloud', () => { - expect(getPlatformFromHostname('github.com')).toBe('GitHub Cloud'); - expect(getPlatformFromHostname('api.github.com')).toBe('GitHub Cloud'); + expect(getPlatformFromHostname('github.com' as HostName)).toBe( + 'GitHub Cloud', + ); + expect(getPlatformFromHostname('api.github.com' as HostName)).toBe( + 'GitHub Cloud', + ); }); it('should return GitHub Enterprise Server', () => { - expect(getPlatformFromHostname('github.gitify.app')).toBe( + expect(getPlatformFromHostname('github.gitify.app' as HostName)).toBe( 'GitHub Enterprise Server', ); - expect(getPlatformFromHostname('api.github.gitify.app')).toBe( + expect(getPlatformFromHostname('api.github.gitify.app' as HostName)).toBe( 'GitHub Enterprise Server', ); }); @@ -82,13 +87,13 @@ describe('utils/helpers.ts', () => { describe('isEnterpriseHost', () => { it('should return true for enterprise host', () => { - expect(isEnterpriseHost('github.gitify.app')).toBe(true); - expect(isEnterpriseHost('api.github.gitify.app')).toBe(true); + expect(isEnterpriseHost('github.gitify.app' as HostName)).toBe(true); + expect(isEnterpriseHost('api.github.gitify.app' as HostName)).toBe(true); }); it('should return false for non-enterprise host', () => { - expect(isEnterpriseHost('github.com')).toBe(false); - expect(isEnterpriseHost('api.github.com')).toBe(false); + expect(isEnterpriseHost('github.com' as HostName)).toBe(false); + expect(isEnterpriseHost('api.github.com' as HostName)).toBe(false); }); }); diff --git a/src/utils/helpers.ts b/src/utils/helpers.ts index a69d121bb..96d1784d6 100644 --- a/src/utils/helpers.ts +++ b/src/utils/helpers.ts @@ -4,6 +4,7 @@ import type { Notification } from '../typesGitHub'; import { openExternalLink } from '../utils/comms'; import { getHtmlUrl, getLatestDiscussion } from './api/client'; import type { PlatformType } from './auth/types'; +import type { HostName } from './branded-types'; import { Constants } from './constants'; import { getCheckSuiteAttributes, @@ -21,17 +22,20 @@ export function isOAuthAppLoggedIn(auth: AuthState): boolean { return auth.accounts.some((account) => account.method === 'OAuth App'); } -export function getAccountForHost(hostname: string, auth: AuthState): Account { +export function getAccountForHost( + hostname: HostName, + auth: AuthState, +): Account { return auth.accounts.find((account) => hostname.endsWith(account.hostname)); } -export function getPlatformFromHostname(hostname: string): PlatformType { +export function getPlatformFromHostname(hostname: HostName): PlatformType { return hostname.endsWith(Constants.DEFAULT_AUTH_OPTIONS.hostname) ? 'GitHub Cloud' : 'GitHub Enterprise Server'; } -export function isEnterpriseHost(hostname: string): boolean { +export function isEnterpriseHost(hostname: HostName): boolean { return !hostname.endsWith(Constants.DEFAULT_AUTH_OPTIONS.hostname); } diff --git a/src/utils/remove-notification.ts b/src/utils/remove-notification.ts index 03e0fcd15..71d71e9e7 100644 --- a/src/utils/remove-notification.ts +++ b/src/utils/remove-notification.ts @@ -1,11 +1,12 @@ import type { AccountNotifications, SettingsState } from '../types'; +import type { HostName } from './branded-types'; import Constants from './constants'; export const removeNotification = ( settings: SettingsState, id: string, notifications: AccountNotifications[], - hostname: string, + hostname: HostName, ): AccountNotifications[] => { if (settings.delayNotificationState) { const notificationRow = document.getElementById(id); diff --git a/src/utils/remove-notifications.ts b/src/utils/remove-notifications.ts index 09ed0728e..a02ff1ea2 100644 --- a/src/utils/remove-notifications.ts +++ b/src/utils/remove-notifications.ts @@ -1,9 +1,10 @@ import type { AccountNotifications } from '../types'; +import type { HostName } from './branded-types'; export const removeNotifications = ( repoSlug: string, notifications: AccountNotifications[], - hostname: string, + hostname: HostName, ): AccountNotifications[] => { const accountIndex = notifications.findIndex( (accountNotifications) => From 0591dc04dffdccdb5d96c987edb63917a53fc340 Mon Sep 17 00:00:00 2001 From: dammy95 Date: Mon, 10 Jun 2024 13:36:47 +0100 Subject: [PATCH 2/9] Commit branded-types file --- src/utils/branded-types.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 src/utils/branded-types.ts diff --git a/src/utils/branded-types.ts b/src/utils/branded-types.ts new file mode 100644 index 000000000..c74194316 --- /dev/null +++ b/src/utils/branded-types.ts @@ -0,0 +1,15 @@ +declare const __brand: unique symbol; + +type Brand = { [__brand]: B }; + +export type Branded = T & Brand; + +export type URL = Branded; + +export type Token = Branded; + +export type ClientID = Branded; + +export type ClientSecret = Branded; + +export type HostName = Branded; From f21c268f3489d1b339ed5fe604a3bcf2efcf1804 Mon Sep 17 00:00:00 2001 From: dammy95 Date: Mon, 10 Jun 2024 14:12:17 +0100 Subject: [PATCH 3/9] Add the token Branded type --- src/__mocks__/state-mocks.ts | 16 +++++------ src/context/App.test.tsx | 6 ++--- src/routes/LoginWithOAuthApp.tsx | 9 +++++-- src/routes/LoginWithPersonalAccessToken.tsx | 6 ++--- src/types.ts | 6 ++--- src/utils/api/client.test.ts | 6 ++--- src/utils/api/client.ts | 30 ++++++++++----------- src/utils/api/request.test.ts | 3 ++- src/utils/api/request.ts | 3 ++- src/utils/auth/types.ts | 6 ++--- src/utils/auth/utils.test.ts | 24 ++++++++--------- src/utils/auth/utils.ts | 8 +++--- src/utils/storage.test.ts | 7 ++--- 13 files changed, 69 insertions(+), 61 deletions(-) diff --git a/src/__mocks__/state-mocks.ts b/src/__mocks__/state-mocks.ts index 45573a2ff..1d5291370 100644 --- a/src/__mocks__/state-mocks.ts +++ b/src/__mocks__/state-mocks.ts @@ -7,13 +7,13 @@ import { Theme, } from '../types'; import type { EnterpriseAccount } from '../utils/auth/types'; -import type { HostName } from '../utils/branded-types'; +import type { HostName, Token } from '../utils/branded-types'; import Constants from '../utils/constants'; export const mockEnterpriseAccounts: EnterpriseAccount[] = [ { hostname: 'github.gitify.io' as HostName, - token: '1234568790', + token: '1234568790' as Token, }, ]; @@ -26,7 +26,7 @@ export const mockGitifyUser: GitifyUser = { export const mockPersonalAccessTokenAccount: Account = { platform: 'GitHub Cloud', method: 'Personal Access Token', - token: 'token-123-456', + token: 'token-123-456' as Token, hostname: Constants.DEFAULT_AUTH_OPTIONS.hostname, user: mockGitifyUser, }; @@ -34,7 +34,7 @@ export const mockPersonalAccessTokenAccount: Account = { export const mockOAuthAccount: Account = { platform: 'GitHub Enterprise Server', method: 'OAuth App', - token: '1234568790', + token: '1234568790' as Token, hostname: 'github.gitify.io' as HostName, user: mockGitifyUser, }; @@ -42,7 +42,7 @@ export const mockOAuthAccount: Account = { export const mockGitHubCloudAccount: Account = { platform: 'GitHub Cloud', method: 'Personal Access Token', - token: 'token-123-456', + token: 'token-123-456' as Token, hostname: Constants.DEFAULT_AUTH_OPTIONS.hostname, user: mockGitifyUser, }; @@ -50,7 +50,7 @@ export const mockGitHubCloudAccount: Account = { export const mockGitHubEnterpriseServerAccount: Account = { platform: 'GitHub Enterprise Server', method: 'Personal Access Token', - token: '1234568790', + token: '1234568790' as Token, hostname: 'github.gitify.io' as HostName, user: mockGitifyUser, }; @@ -58,7 +58,7 @@ export const mockGitHubEnterpriseServerAccount: Account = { export const mockGitHubAppAccount: Account = { platform: 'GitHub Cloud', method: 'GitHub App', - token: '987654321', + token: '987654321' as Token, hostname: Constants.DEFAULT_AUTH_OPTIONS.hostname, user: mockGitifyUser, }; @@ -67,7 +67,7 @@ export const mockAuth: AuthState = { accounts: [mockGitHubCloudAccount, mockGitHubEnterpriseServerAccount], }; -export const mockToken = 'token-123-456'; +export const mockToken = 'token-123-456' as Token; export const mockSettings: SettingsState = { participating: false, diff --git a/src/context/App.test.tsx b/src/context/App.test.tsx index f6a834081..9cc5b1fe8 100644 --- a/src/context/App.test.tsx +++ b/src/context/App.test.tsx @@ -4,7 +4,7 @@ import { mockAuth, mockSettings } from '../__mocks__/state-mocks'; import { useNotifications } from '../hooks/useNotifications'; import type { AuthState, SettingsState } from '../types'; import * as apiRequests from '../utils/api/request'; -import type { HostName } from '../utils/branded-types'; +import type { HostName, Token } from '../utils/branded-types'; import * as comms from '../utils/comms'; import Constants from '../utils/constants'; import * as notifications from '../utils/notifications'; @@ -301,8 +301,8 @@ describe('context/App.tsx', () => { type="button" onClick={() => loginWithPersonalAccessToken({ - hostname: 'github.com' as HostName as HostName, - token: '123-456', + hostname: 'github.com' as HostName, + token: '123-456' as Token, }) } > diff --git a/src/routes/LoginWithOAuthApp.tsx b/src/routes/LoginWithOAuthApp.tsx index e76ab3b3b..996762cdb 100644 --- a/src/routes/LoginWithOAuthApp.tsx +++ b/src/routes/LoginWithOAuthApp.tsx @@ -17,7 +17,12 @@ import { isValidHostname, isValidToken, } from '../utils/auth/utils'; -import type { ClientID, ClientSecret, HostName } from '../utils/branded-types'; +import type { + ClientID, + ClientSecret, + HostName, + Token, +} from '../utils/branded-types'; import Constants from '../utils/constants'; interface IValues { @@ -49,7 +54,7 @@ export const validate = (values: IValues): IFormErrors => { if (!values.clientSecret) { errors.clientSecret = 'Required'; - } else if (!isValidToken(values.clientSecret)) { + } else if (!isValidToken(values.clientSecret as unknown as Token)) { errors.clientSecret = 'Invalid client secret.'; } diff --git a/src/routes/LoginWithPersonalAccessToken.tsx b/src/routes/LoginWithPersonalAccessToken.tsx index 31f1d9590..713827f9c 100644 --- a/src/routes/LoginWithPersonalAccessToken.tsx +++ b/src/routes/LoginWithPersonalAccessToken.tsx @@ -16,11 +16,11 @@ import { isValidHostname, isValidToken, } from '../utils/auth/utils'; -import type { HostName } from '../utils/branded-types'; +import type { HostName, Token } from '../utils/branded-types'; import { Constants } from '../utils/constants'; interface IValues { - token?: string; + token?: Token; hostname?: HostName; } @@ -159,7 +159,7 @@ export const LoginWithPersonalAccessToken: FC = () => {
{ const result = await getHtmlUrl( 'https://api.github.com/repos/gitify-app/notifications-test/issues/785', - '123', + '123' as Token, ); expect(result).toBe( 'https://github.com/gitify-app/notifications-test/issues/785', @@ -289,7 +289,7 @@ describe('utils/api/client.ts', () => { await getHtmlUrl( 'https://api.github.com/repos/gitify-app/gitify/issues/785', - '123', + '123' as Token, ); expect(consoleErrorSpy).toHaveBeenCalledWith('Failed to get html url'); diff --git a/src/utils/api/client.ts b/src/utils/api/client.ts index 3c3cbb6b3..a234c176a 100644 --- a/src/utils/api/client.ts +++ b/src/utils/api/client.ts @@ -15,7 +15,7 @@ import type { Release, UserDetails, } from '../../typesGitHub'; -import type { HostName } from '../branded-types'; +import type { HostName, Token } from '../branded-types'; import { QUERY_SEARCH_DISCUSSIONS } from './graphql/discussions'; import { formatAsGitHubSearchSyntax } from './graphql/utils'; import { apiRequestAuth } from './request'; @@ -28,7 +28,7 @@ import { getGitHubAPIBaseUrl, getGitHubGraphQLUrl } from './utils'; */ export function getAuthenticatedUser( hostname: HostName, - token: string, + token: Token, ): AxiosPromise { const url = getGitHubAPIBaseUrl(hostname); url.pathname += 'user'; @@ -39,7 +39,7 @@ export function getAuthenticatedUser( // export function headNotifications( hostname: HostName, - token: string, + token: Token, ): AxiosPromise { const url = getGitHubAPIBaseUrl(hostname); url.pathname += 'notifications'; @@ -72,7 +72,7 @@ export function listNotificationsForAuthenticatedUser( export function markNotificationThreadAsRead( threadId: string, hostname: HostName, - token: string, + token: Token, ): AxiosPromise { const url = getGitHubAPIBaseUrl(hostname); url.pathname += `notifications/threads/${threadId}`; @@ -89,7 +89,7 @@ export function markNotificationThreadAsRead( export function markNotificationThreadAsDone( threadId: string, hostname: HostName, - token: string, + token: Token, ): AxiosPromise { const url = getGitHubAPIBaseUrl(hostname); url.pathname += `notifications/threads/${threadId}`; @@ -105,7 +105,7 @@ export function markNotificationThreadAsDone( export function ignoreNotificationThreadSubscription( threadId: string, hostname: HostName, - token: string, + token: Token, ): AxiosPromise { const url = getGitHubAPIBaseUrl(hostname); url.pathname += `notifications/threads/${threadId}/subscription`; @@ -124,7 +124,7 @@ export function ignoreNotificationThreadSubscription( export function markRepositoryNotificationsAsRead( repoSlug: string, hostname: HostName, - token: string, + token: Token, ): AxiosPromise { const url = getGitHubAPIBaseUrl(hostname); url.pathname += `repos/${repoSlug}/notifications`; @@ -137,7 +137,7 @@ export function markRepositoryNotificationsAsRead( * * Endpoint documentation: https://docs.github.com/en/rest/commits/commits#get-a-commit */ -export function getCommit(url: string, token: string): AxiosPromise { +export function getCommit(url: string, token: Token): AxiosPromise { return apiRequestAuth(url, 'GET', token); } @@ -149,7 +149,7 @@ export function getCommit(url: string, token: string): AxiosPromise { */ export function getCommitComment( url: string, - token: string, + token: Token, ): AxiosPromise { return apiRequestAuth(url, 'GET', token); } @@ -159,7 +159,7 @@ export function getCommitComment( * * Endpoint documentation: https://docs.github.com/en/rest/issues/issues#get-an-issue */ -export function getIssue(url: string, token: string): AxiosPromise { +export function getIssue(url: string, token: Token): AxiosPromise { return apiRequestAuth(url, 'GET', token); } @@ -171,7 +171,7 @@ export function getIssue(url: string, token: string): AxiosPromise { */ export function getIssueOrPullRequestComment( url: string, - token: string, + token: Token, ): AxiosPromise { return apiRequestAuth(url, 'GET', token); } @@ -183,7 +183,7 @@ export function getIssueOrPullRequestComment( */ export function getPullRequest( url: string, - token: string, + token: Token, ): AxiosPromise { return apiRequestAuth(url, 'GET', token); } @@ -195,7 +195,7 @@ export function getPullRequest( */ export function getPullRequestReviews( url: string, - token: string, + token: Token, ): AxiosPromise { return apiRequestAuth(url, 'GET', token); } @@ -205,14 +205,14 @@ export function getPullRequestReviews( * * Endpoint documentation: https://docs.github.com/en/rest/releases/releases#get-a-release */ -export function getRelease(url: string, token: string): AxiosPromise { +export function getRelease(url: string, token: Token): AxiosPromise { return apiRequestAuth(url, 'GET', token); } /** * Get the `html_url` from the GitHub response */ -export async function getHtmlUrl(url: string, token: string): Promise { +export async function getHtmlUrl(url: string, token: Token): Promise { try { const response = (await apiRequestAuth(url, 'GET', token)).data; return response.html_url; diff --git a/src/utils/api/request.test.ts b/src/utils/api/request.test.ts index 45b5ee7a7..a1f4d391e 100644 --- a/src/utils/api/request.test.ts +++ b/src/utils/api/request.test.ts @@ -1,4 +1,5 @@ import axios from 'axios'; +import type { Token } from '../branded-types'; import { apiRequest, apiRequestAuth } from './request'; jest.mock('axios'); @@ -40,7 +41,7 @@ describe('utils/api/request.ts', () => { }); describe('apiRequestAuth', () => { - const token = 'yourAuthToken'; + const token = 'yourAuthToken' as Token; afterEach(() => { jest.resetAllMocks(); diff --git a/src/utils/api/request.ts b/src/utils/api/request.ts index 17132d31e..5a9615d2d 100644 --- a/src/utils/api/request.ts +++ b/src/utils/api/request.ts @@ -1,4 +1,5 @@ import axios, { type AxiosPromise, type Method } from 'axios'; +import type { Token } from '../branded-types'; export function apiRequest( url: string, @@ -14,7 +15,7 @@ export function apiRequest( export function apiRequestAuth( url: string, method: Method, - token: string, + token: Token, data = {}, ): AxiosPromise | null { axios.defaults.headers.common.Accept = 'application/json'; diff --git a/src/utils/auth/types.ts b/src/utils/auth/types.ts index 1f2bdbffd..a4d06c7fb 100644 --- a/src/utils/auth/types.ts +++ b/src/utils/auth/types.ts @@ -12,7 +12,7 @@ export interface LoginOAuthAppOptions { export interface LoginPersonalAccessTokenOptions { hostname: HostName; - token: string; + token: Token; } export interface AuthResponse { @@ -22,7 +22,7 @@ export interface AuthResponse { export interface AuthTokenResponse { hostname: HostName; - token: string; + token: Token; } /** @@ -30,5 +30,5 @@ export interface AuthTokenResponse { */ export interface EnterpriseAccount { hostname: HostName; - token: string; + token: Token; } diff --git a/src/utils/auth/utils.test.ts b/src/utils/auth/utils.test.ts index d801bcd97..e21920c2c 100644 --- a/src/utils/auth/utils.test.ts +++ b/src/utils/auth/utils.test.ts @@ -3,7 +3,7 @@ import type { AxiosPromise, AxiosResponse } from 'axios'; import { mockAuth, mockGitHubCloudAccount } from '../../__mocks__/state-mocks'; import type { Account, AuthState } from '../../types'; import * as apiRequests from '../api/request'; -import type { HostName } from '../branded-types'; +import type { HostName, Token } from '../branded-types'; import * as auth from './utils'; import { getNewOAuthAppURL, getNewTokenURL } from './utils'; @@ -118,7 +118,7 @@ describe('utils/auth/utils.ts', () => { const result = auth.addAccount( mockAuthState, 'Personal Access Token', - '123-456', + '123-456' as Token, 'github.com' as HostName, ); @@ -127,7 +127,7 @@ describe('utils/auth/utils.ts', () => { hostname: 'github.com' as HostName, method: 'Personal Access Token', platform: 'GitHub Cloud', - token: '123-456', + token: '123-456' as Token, user: undefined, }, ]); @@ -137,7 +137,7 @@ describe('utils/auth/utils.ts', () => { const result = auth.addAccount( mockAuthState, 'OAuth App', - '123-456', + '123-456' as Token, 'github.com' as HostName, ); @@ -146,7 +146,7 @@ describe('utils/auth/utils.ts', () => { hostname: 'github.com' as HostName, method: 'OAuth App', platform: 'GitHub Cloud', - token: '123-456', + token: '123-456' as Token, user: undefined, }, ]); @@ -158,7 +158,7 @@ describe('utils/auth/utils.ts', () => { const result = auth.addAccount( mockAuthState, 'Personal Access Token', - '123-456', + '123-456' as Token, 'github.gitify.io' as HostName, ); @@ -167,7 +167,7 @@ describe('utils/auth/utils.ts', () => { hostname: 'github.gitify.io' as HostName, method: 'Personal Access Token', platform: 'GitHub Enterprise Server', - token: '123-456', + token: '123-456' as Token, user: undefined, }, ]); @@ -177,7 +177,7 @@ describe('utils/auth/utils.ts', () => { const result = auth.addAccount( mockAuthState, 'OAuth App', - '123-456', + '123-456' as Token, 'github.gitify.io' as HostName, ); @@ -186,7 +186,7 @@ describe('utils/auth/utils.ts', () => { hostname: 'github.gitify.io' as HostName, method: 'OAuth App', platform: 'GitHub Enterprise Server', - token: '123-456', + token: '123-456' as Token, user: undefined, }, ]); @@ -308,16 +308,16 @@ describe('utils/auth/utils.ts', () => { describe('isValidToken', () => { it('should validate token - valid', () => { expect( - auth.isValidToken('1234567890_asdfghjklPOIUYTREWQ0987654321'), + auth.isValidToken('1234567890_asdfghjklPOIUYTREWQ0987654321' as Token), ).toBeTruthy(); }); it('should validate token - empty', () => { - expect(auth.isValidToken('')).toBeFalsy(); + expect(auth.isValidToken('' as Token)).toBeFalsy(); }); it('should validate token - invalid', () => { - expect(auth.isValidToken('1234567890asdfg')).toBeFalsy(); + expect(auth.isValidToken('1234567890asdfg' as Token)).toBeFalsy(); }); }); }); diff --git a/src/utils/auth/utils.ts b/src/utils/auth/utils.ts index 57d889a98..b55f0f38c 100644 --- a/src/utils/auth/utils.ts +++ b/src/utils/auth/utils.ts @@ -4,7 +4,7 @@ import type { Account, AuthState, GitifyUser } from '../../types'; import type { UserDetails } from '../../typesGitHub'; import { getAuthenticatedUser } from '../api/client'; import { apiRequest } from '../api/request'; -import type { HostName } from '../branded-types'; +import type { HostName, Token } from '../branded-types'; import { Constants } from '../constants'; import { getPlatformFromHostname } from '../helpers'; import type { AuthMethod, AuthResponse, AuthTokenResponse } from './types'; @@ -77,7 +77,7 @@ export const authGitHub = ( }; export const getUserData = async ( - token: string, + token: Token, hostname: HostName, ): Promise => { const response: UserDetails = (await getAuthenticatedUser(hostname, token)) @@ -111,7 +111,7 @@ export const getToken = async ( export function addAccount( auth: AuthState, method: AuthMethod, - token: string, + token: Token, hostname: HostName, user?: GitifyUser, ): AuthState { @@ -196,7 +196,7 @@ export function isValidClientId(clientId: string) { return /^[A-Z0-9_]{20}$/i.test(clientId); } -export function isValidToken(token: string) { +export function isValidToken(token: Token) { return /^[A-Z0-9_]{40}$/i.test(token); } diff --git a/src/utils/storage.test.ts b/src/utils/storage.test.ts index 42ca9a879..bc916a9c4 100644 --- a/src/utils/storage.test.ts +++ b/src/utils/storage.test.ts @@ -1,4 +1,5 @@ import { mockSettings } from '../__mocks__/state-mocks'; +import type { Token } from './branded-types'; import Constants from './constants'; import { clearState, loadState, saveState } from './storage'; @@ -12,7 +13,7 @@ describe('utils/storage.ts', () => { hostname: Constants.DEFAULT_AUTH_OPTIONS.hostname, platform: 'GitHub Cloud', method: 'Personal Access Token', - token: '123-456', + token: '123-456' as Token, user: null, }, ], @@ -27,7 +28,7 @@ describe('utils/storage.ts', () => { hostname: Constants.DEFAULT_AUTH_OPTIONS.hostname, platform: 'GitHub Cloud', method: 'Personal Access Token', - token: '123-456', + token: '123-456' as Token, user: null, }, ]); @@ -56,7 +57,7 @@ describe('utils/storage.ts', () => { hostname: Constants.DEFAULT_AUTH_OPTIONS.hostname, platform: 'GitHub Cloud', method: 'Personal Access Token', - token: '123-456', + token: '123-456' as Token, user: null, }, ], From 311c9696027ac21fb243c14b37d101f7a8dee104 Mon Sep 17 00:00:00 2001 From: dammy95 Date: Tue, 11 Jun 2024 14:09:21 +0100 Subject: [PATCH 4/9] Add the ClientSecret and ClientID Branded types --- src/routes/LoginWithOAuthApp.test.tsx | 6 +++--- src/utils/auth/types.ts | 4 ++-- src/utils/auth/utils.test.ts | 10 ++++++---- src/utils/auth/utils.ts | 4 ++-- src/utils/constants.ts | 6 +++--- 5 files changed, 16 insertions(+), 14 deletions(-) diff --git a/src/routes/LoginWithOAuthApp.test.tsx b/src/routes/LoginWithOAuthApp.test.tsx index 64f0938f1..fd5d4c7cf 100644 --- a/src/routes/LoginWithOAuthApp.test.tsx +++ b/src/routes/LoginWithOAuthApp.test.tsx @@ -3,7 +3,7 @@ import { shell } from 'electron'; import { MemoryRouter } from 'react-router-dom'; import { AppContext } from '../context/App'; import type { AuthState } from '../types'; -import type { HostName } from '../utils/branded-types'; +import type { ClientID, ClientSecret, HostName } from '../utils/branded-types'; import { LoginWithOAuthApp, validate } from './LoginWithOAuthApp'; const mockNavigate = jest.fn(); @@ -66,8 +66,8 @@ describe('routes/LoginWithOAuthApp.tsx', () => { values = { ...emptyValues, hostname: 'hello' as HostName, - clientId: '!@£INVALID-.1', - clientSecret: '!@£INVALID-.1', + clientId: '!@£INVALID-.1' as ClientID, + clientSecret: '!@£INVALID-.1' as ClientSecret, }; expect(validate(values).hostname).toBe('Invalid hostname.'); expect(validate(values).clientId).toBe('Invalid client id.'); diff --git a/src/utils/auth/types.ts b/src/utils/auth/types.ts index a4d06c7fb..d1d3782ae 100644 --- a/src/utils/auth/types.ts +++ b/src/utils/auth/types.ts @@ -6,8 +6,8 @@ export type PlatformType = 'GitHub Cloud' | 'GitHub Enterprise Server'; export interface LoginOAuthAppOptions { hostname: HostName; - clientId: string; - clientSecret: string; + clientId: ClientID; + clientSecret: ClientSecret; } export interface LoginPersonalAccessTokenOptions { diff --git a/src/utils/auth/utils.test.ts b/src/utils/auth/utils.test.ts index e21920c2c..c76dda519 100644 --- a/src/utils/auth/utils.test.ts +++ b/src/utils/auth/utils.test.ts @@ -3,7 +3,7 @@ import type { AxiosPromise, AxiosResponse } from 'axios'; import { mockAuth, mockGitHubCloudAccount } from '../../__mocks__/state-mocks'; import type { Account, AuthState } from '../../types'; import * as apiRequests from '../api/request'; -import type { HostName, Token } from '../branded-types'; +import type { ClientID, HostName, Token } from '../branded-types'; import * as auth from './utils'; import { getNewOAuthAppURL, getNewTokenURL } from './utils'; @@ -293,15 +293,17 @@ describe('utils/auth/utils.ts', () => { describe('isValidClientId', () => { it('should validate client id - valid', () => { - expect(auth.isValidClientId('1234567890_ASDFGHJKL')).toBeTruthy(); + expect( + auth.isValidClientId('1234567890_ASDFGHJKL' as ClientID), + ).toBeTruthy(); }); it('should validate client id - empty', () => { - expect(auth.isValidClientId('')).toBeFalsy(); + expect(auth.isValidClientId('' as ClientID)).toBeFalsy(); }); it('should validate client id - invalid', () => { - expect(auth.isValidClientId('1234567890asdfg')).toBeFalsy(); + expect(auth.isValidClientId('1234567890asdfg' as ClientID)).toBeFalsy(); }); }); diff --git a/src/utils/auth/utils.ts b/src/utils/auth/utils.ts index b55f0f38c..3d4ad7111 100644 --- a/src/utils/auth/utils.ts +++ b/src/utils/auth/utils.ts @@ -4,7 +4,7 @@ import type { Account, AuthState, GitifyUser } from '../../types'; import type { UserDetails } from '../../typesGitHub'; import { getAuthenticatedUser } from '../api/client'; import { apiRequest } from '../api/request'; -import type { HostName, Token } from '../branded-types'; +import type { ClientID, HostName, Token } from '../branded-types'; import { Constants } from '../constants'; import { getPlatformFromHostname } from '../helpers'; import type { AuthMethod, AuthResponse, AuthTokenResponse } from './types'; @@ -192,7 +192,7 @@ export function isValidHostname(hostname: HostName) { ); } -export function isValidClientId(clientId: string) { +export function isValidClientId(clientId: ClientID) { return /^[A-Z0-9_]{20}$/i.test(clientId); } diff --git a/src/utils/constants.ts b/src/utils/constants.ts index 081303425..be91dc0f0 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -1,5 +1,5 @@ import type { ErrorType, GitifyError } from '../types'; -import type { HostName } from './branded-types'; +import type { ClientID, ClientSecret, HostName } from './branded-types'; export const Constants = { // GitHub OAuth @@ -7,8 +7,8 @@ export const Constants = { DEFAULT_AUTH_OPTIONS: { hostname: 'github.com' as HostName, - clientId: process.env.OAUTH_CLIENT_ID, - clientSecret: process.env.OAUTH_CLIENT_SECRET, + clientId: process.env.OAUTH_CLIENT_ID as ClientID, + clientSecret: process.env.OAUTH_CLIENT_SECRET as ClientSecret, }, GITHUB_API_BASE_URL: 'https://api.github.com', From bce7ca477520276e3405e416826ae8b82cbcfaed Mon Sep 17 00:00:00 2001 From: dammy95 Date: Tue, 11 Jun 2024 14:21:21 +0100 Subject: [PATCH 5/9] Add the AuthCode Branded type --- src/utils/auth/types.ts | 10 ++++++++-- src/utils/auth/utils.test.ts | 4 ++-- src/utils/auth/utils.ts | 7 ++++--- src/utils/branded-types.ts | 2 +- 4 files changed, 15 insertions(+), 8 deletions(-) diff --git a/src/utils/auth/types.ts b/src/utils/auth/types.ts index d1d3782ae..e30b1a4dc 100644 --- a/src/utils/auth/types.ts +++ b/src/utils/auth/types.ts @@ -1,4 +1,10 @@ -import type { ClientID, ClientSecret, HostName, Token } from '../branded-types'; +import type { + AuthCode, + ClientID, + ClientSecret, + HostName, + Token, +} from '../branded-types'; export type AuthMethod = 'GitHub App' | 'Personal Access Token' | 'OAuth App'; @@ -16,7 +22,7 @@ export interface LoginPersonalAccessTokenOptions { } export interface AuthResponse { - authCode: string; + authCode: AuthCode; authOptions: LoginOAuthAppOptions; } diff --git a/src/utils/auth/utils.test.ts b/src/utils/auth/utils.test.ts index c76dda519..95837d8a9 100644 --- a/src/utils/auth/utils.test.ts +++ b/src/utils/auth/utils.test.ts @@ -3,7 +3,7 @@ import type { AxiosPromise, AxiosResponse } from 'axios'; import { mockAuth, mockGitHubCloudAccount } from '../../__mocks__/state-mocks'; import type { Account, AuthState } from '../../types'; import * as apiRequests from '../api/request'; -import type { ClientID, HostName, Token } from '../branded-types'; +import type { AuthCode, ClientID, HostName, Token } from '../branded-types'; import * as auth from './utils'; import { getNewOAuthAppURL, getNewTokenURL } from './utils'; @@ -64,7 +64,7 @@ describe('utils/auth/utils.ts', () => { }); describe('getToken', () => { - const authCode = '123-456'; + const authCode = '123-456' as AuthCode; const apiRequestMock = jest.spyOn(apiRequests, 'apiRequest'); it('should get a token - success', async () => { diff --git a/src/utils/auth/utils.ts b/src/utils/auth/utils.ts index 3d4ad7111..25b19c895 100644 --- a/src/utils/auth/utils.ts +++ b/src/utils/auth/utils.ts @@ -4,7 +4,7 @@ import type { Account, AuthState, GitifyUser } from '../../types'; import type { UserDetails } from '../../typesGitHub'; import { getAuthenticatedUser } from '../api/client'; import { apiRequest } from '../api/request'; -import type { ClientID, HostName, Token } from '../branded-types'; +import type { AuthCode, ClientID, HostName, Token } from '../branded-types'; import { Constants } from '../constants'; import { getPlatformFromHostname } from '../helpers'; import type { AuthMethod, AuthResponse, AuthTokenResponse } from './types'; @@ -30,7 +30,8 @@ export const authGitHub = ( const handleCallback = (url: string) => { const raw_code = /code=([^&]*)/.exec(url) || null; - const authCode = raw_code && raw_code.length > 1 ? raw_code[1] : null; + const authCode = + raw_code && raw_code.length > 1 ? (raw_code[1] as AuthCode) : null; const error = /\?error=(.+)$/.exec(url); if (authCode || error) { // Close the browser if code found or error @@ -91,7 +92,7 @@ export const getUserData = async ( }; export const getToken = async ( - authCode: string, + authCode: AuthCode, authOptions = Constants.DEFAULT_AUTH_OPTIONS, ): Promise => { const url = `https://${authOptions.hostname}/login/oauth/access_token`; diff --git a/src/utils/branded-types.ts b/src/utils/branded-types.ts index c74194316..a4f32af91 100644 --- a/src/utils/branded-types.ts +++ b/src/utils/branded-types.ts @@ -4,7 +4,7 @@ type Brand = { [__brand]: B }; export type Branded = T & Brand; -export type URL = Branded; +export type AuthCode = Branded; export type Token = Branded; From c02697e53596273f352bc9f4175c566b1b1974ba Mon Sep 17 00:00:00 2001 From: dammy95 Date: Thu, 13 Jun 2024 12:12:01 +0100 Subject: [PATCH 6/9] Rename HostName to Hostname --- src/__mocks__/partial-mocks.ts | 4 +- src/__mocks__/state-mocks.ts | 8 ++-- src/context/App.test.tsx | 4 +- src/routes/LoginWithOAuthApp.test.tsx | 4 +- src/routes/LoginWithOAuthApp.tsx | 4 +- src/routes/LoginWithPersonalAccessToken.tsx | 4 +- src/types.ts | 4 +- src/utils/api/client.test.ts | 6 +-- src/utils/api/client.ts | 14 +++---- src/utils/api/utils.test.ts | 10 ++--- src/utils/api/utils.ts | 6 +-- src/utils/auth/migration.test.ts | 16 ++++---- src/utils/auth/types.ts | 10 ++--- src/utils/auth/utils.test.ts | 42 ++++++++++----------- src/utils/auth/utils.ts | 12 +++--- src/utils/branded-types.ts | 2 +- src/utils/constants.ts | 4 +- src/utils/helpers.test.ts | 18 ++++----- src/utils/helpers.ts | 4 +- src/utils/links.test.ts | 4 +- src/utils/links.ts | 4 +- 21 files changed, 92 insertions(+), 92 deletions(-) diff --git a/src/__mocks__/partial-mocks.ts b/src/__mocks__/partial-mocks.ts index 64ae1dd78..bc6585451 100644 --- a/src/__mocks__/partial-mocks.ts +++ b/src/__mocks__/partial-mocks.ts @@ -1,5 +1,5 @@ import type { Notification, Subject, User } from '../typesGitHub'; -import type { HostName } from '../utils/branded-types'; +import type { Hostname } from '../utils/branded-types'; import Constants from '../utils/constants'; import { mockGitifyUser, mockToken } from './state-mocks'; @@ -10,7 +10,7 @@ export function partialMockNotification( account: { method: 'Personal Access Token', platform: 'GitHub Cloud', - hostname: Constants.GITHUB_API_BASE_URL as HostName, + hostname: Constants.GITHUB_API_BASE_URL as Hostname, token: mockToken, user: mockGitifyUser, }, diff --git a/src/__mocks__/state-mocks.ts b/src/__mocks__/state-mocks.ts index 1d5291370..1d94a2c39 100644 --- a/src/__mocks__/state-mocks.ts +++ b/src/__mocks__/state-mocks.ts @@ -7,12 +7,12 @@ import { Theme, } from '../types'; import type { EnterpriseAccount } from '../utils/auth/types'; -import type { HostName, Token } from '../utils/branded-types'; +import type { Hostname, Token } from '../utils/branded-types'; import Constants from '../utils/constants'; export const mockEnterpriseAccounts: EnterpriseAccount[] = [ { - hostname: 'github.gitify.io' as HostName, + hostname: 'github.gitify.io' as Hostname, token: '1234568790' as Token, }, ]; @@ -35,7 +35,7 @@ export const mockOAuthAccount: Account = { platform: 'GitHub Enterprise Server', method: 'OAuth App', token: '1234568790' as Token, - hostname: 'github.gitify.io' as HostName, + hostname: 'github.gitify.io' as Hostname, user: mockGitifyUser, }; @@ -51,7 +51,7 @@ export const mockGitHubEnterpriseServerAccount: Account = { platform: 'GitHub Enterprise Server', method: 'Personal Access Token', token: '1234568790' as Token, - hostname: 'github.gitify.io' as HostName, + hostname: 'github.gitify.io' as Hostname, user: mockGitifyUser, }; diff --git a/src/context/App.test.tsx b/src/context/App.test.tsx index c0bbf002b..01bdf825f 100644 --- a/src/context/App.test.tsx +++ b/src/context/App.test.tsx @@ -5,7 +5,7 @@ import { useNotifications } from '../hooks/useNotifications'; import type { AuthState, SettingsState } from '../types'; import { mockSingleNotification } from '../utils/api/__mocks__/response-mocks'; import * as apiRequests from '../utils/api/request'; -import type { HostName, Token } from '../utils/branded-types'; +import type { Hostname, Token } from '../utils/branded-types'; import * as comms from '../utils/comms'; import Constants from '../utils/constants'; import * as notifications from '../utils/notifications'; @@ -281,7 +281,7 @@ describe('context/App.tsx', () => { type="button" onClick={() => loginWithPersonalAccessToken({ - hostname: 'github.com' as HostName, + hostname: 'github.com' as Hostname, token: '123-456' as Token, }) } diff --git a/src/routes/LoginWithOAuthApp.test.tsx b/src/routes/LoginWithOAuthApp.test.tsx index fd5d4c7cf..78e081d6b 100644 --- a/src/routes/LoginWithOAuthApp.test.tsx +++ b/src/routes/LoginWithOAuthApp.test.tsx @@ -3,7 +3,7 @@ import { shell } from 'electron'; import { MemoryRouter } from 'react-router-dom'; import { AppContext } from '../context/App'; import type { AuthState } from '../types'; -import type { ClientID, ClientSecret, HostName } from '../utils/branded-types'; +import type { ClientID, ClientSecret, Hostname } from '../utils/branded-types'; import { LoginWithOAuthApp, validate } from './LoginWithOAuthApp'; const mockNavigate = jest.fn(); @@ -65,7 +65,7 @@ describe('routes/LoginWithOAuthApp.tsx', () => { values = { ...emptyValues, - hostname: 'hello' as HostName, + hostname: 'hello' as Hostname, clientId: '!@£INVALID-.1' as ClientID, clientSecret: '!@£INVALID-.1' as ClientSecret, }; diff --git a/src/routes/LoginWithOAuthApp.tsx b/src/routes/LoginWithOAuthApp.tsx index 996762cdb..176ab5eb5 100644 --- a/src/routes/LoginWithOAuthApp.tsx +++ b/src/routes/LoginWithOAuthApp.tsx @@ -20,13 +20,13 @@ import { import type { ClientID, ClientSecret, - HostName, + Hostname, Token, } from '../utils/branded-types'; import Constants from '../utils/constants'; interface IValues { - hostname?: HostName; + hostname?: Hostname; clientId?: ClientID; clientSecret?: ClientSecret; } diff --git a/src/routes/LoginWithPersonalAccessToken.tsx b/src/routes/LoginWithPersonalAccessToken.tsx index 713827f9c..3eaadc133 100644 --- a/src/routes/LoginWithPersonalAccessToken.tsx +++ b/src/routes/LoginWithPersonalAccessToken.tsx @@ -16,12 +16,12 @@ import { isValidHostname, isValidToken, } from '../utils/auth/utils'; -import type { HostName, Token } from '../utils/branded-types'; +import type { Hostname, Token } from '../utils/branded-types'; import { Constants } from '../utils/constants'; interface IValues { token?: Token; - hostname?: HostName; + hostname?: Hostname; } interface IFormErrors { diff --git a/src/types.ts b/src/types.ts index 8826e1afd..c58ac148e 100644 --- a/src/types.ts +++ b/src/types.ts @@ -6,7 +6,7 @@ import type { EnterpriseAccount, PlatformType, } from './utils/auth/types'; -import type { HostName, Token } from './utils/branded-types'; +import type { Hostname, Token } from './utils/branded-types'; export type Status = 'loading' | 'success' | 'error'; @@ -29,7 +29,7 @@ export interface AuthState { export interface Account { method: AuthMethod; platform: PlatformType; - hostname: HostName; + hostname: Hostname; token: Token; user: GitifyUser | null; } diff --git a/src/utils/api/client.test.ts b/src/utils/api/client.test.ts index 935f88a9c..34fd00550 100644 --- a/src/utils/api/client.test.ts +++ b/src/utils/api/client.test.ts @@ -5,7 +5,7 @@ import { mockToken, } from '../../__mocks__/state-mocks'; import type { SettingsState } from '../../types'; -import type { HostName, Token } from '../branded-types'; +import type { Hostname, Token } from '../branded-types'; import { getAuthenticatedUser, getHtmlUrl, @@ -20,8 +20,8 @@ import * as apiRequests from './request'; jest.mock('axios'); -const mockGitHubHostname = 'github.com' as HostName; -const mockEnterpriseHostname = 'example.com' as HostName; +const mockGitHubHostname = 'github.com' as Hostname; +const mockEnterpriseHostname = 'example.com' as Hostname; const mockThreadId = '1234'; const mockRepoSlug = 'gitify-app/notifications-test'; diff --git a/src/utils/api/client.ts b/src/utils/api/client.ts index a234c176a..2ced08053 100644 --- a/src/utils/api/client.ts +++ b/src/utils/api/client.ts @@ -15,7 +15,7 @@ import type { Release, UserDetails, } from '../../typesGitHub'; -import type { HostName, Token } from '../branded-types'; +import type { Hostname, Token } from '../branded-types'; import { QUERY_SEARCH_DISCUSSIONS } from './graphql/discussions'; import { formatAsGitHubSearchSyntax } from './graphql/utils'; import { apiRequestAuth } from './request'; @@ -27,7 +27,7 @@ import { getGitHubAPIBaseUrl, getGitHubGraphQLUrl } from './utils'; * Endpoint documentation: https://docs.github.com/en/rest/users/users#get-the-authenticated-user */ export function getAuthenticatedUser( - hostname: HostName, + hostname: Hostname, token: Token, ): AxiosPromise { const url = getGitHubAPIBaseUrl(hostname); @@ -38,7 +38,7 @@ export function getAuthenticatedUser( // export function headNotifications( - hostname: HostName, + hostname: Hostname, token: Token, ): AxiosPromise { const url = getGitHubAPIBaseUrl(hostname); @@ -71,7 +71,7 @@ export function listNotificationsForAuthenticatedUser( */ export function markNotificationThreadAsRead( threadId: string, - hostname: HostName, + hostname: Hostname, token: Token, ): AxiosPromise { const url = getGitHubAPIBaseUrl(hostname); @@ -88,7 +88,7 @@ export function markNotificationThreadAsRead( */ export function markNotificationThreadAsDone( threadId: string, - hostname: HostName, + hostname: Hostname, token: Token, ): AxiosPromise { const url = getGitHubAPIBaseUrl(hostname); @@ -104,7 +104,7 @@ export function markNotificationThreadAsDone( */ export function ignoreNotificationThreadSubscription( threadId: string, - hostname: HostName, + hostname: Hostname, token: Token, ): AxiosPromise { const url = getGitHubAPIBaseUrl(hostname); @@ -123,7 +123,7 @@ export function ignoreNotificationThreadSubscription( */ export function markRepositoryNotificationsAsRead( repoSlug: string, - hostname: HostName, + hostname: Hostname, token: Token, ): AxiosPromise { const url = getGitHubAPIBaseUrl(hostname); diff --git a/src/utils/api/utils.test.ts b/src/utils/api/utils.test.ts index 0aa3baf36..11df45df0 100644 --- a/src/utils/api/utils.test.ts +++ b/src/utils/api/utils.test.ts @@ -1,27 +1,27 @@ -import type { HostName } from '../branded-types'; +import type { Hostname } from '../branded-types'; import { getGitHubAPIBaseUrl, getGitHubGraphQLUrl } from './utils'; describe('utils/api/utils.ts', () => { describe('getGitHubAPIBaseUrl', () => { it('should generate a GitHub API url - non enterprise', () => { - const result = getGitHubAPIBaseUrl('github.com' as HostName); + const result = getGitHubAPIBaseUrl('github.com' as Hostname); expect(result.toString()).toBe('https://api.github.com/'); }); it('should generate a GitHub API url - enterprise', () => { - const result = getGitHubAPIBaseUrl('github.gitify.io' as HostName); + const result = getGitHubAPIBaseUrl('github.gitify.io' as Hostname); expect(result.toString()).toBe('https://github.gitify.io/api/v3/'); }); }); describe('getGitHubGraphQLUrl', () => { it('should generate a GitHub GraphQL url - non enterprise', () => { - const result = getGitHubGraphQLUrl('github.com' as HostName); + const result = getGitHubGraphQLUrl('github.com' as Hostname); expect(result.toString()).toBe('https://api.github.com/graphql'); }); it('should generate a GitHub GraphQL url - enterprise', () => { - const result = getGitHubGraphQLUrl('github.gitify.io' as HostName); + const result = getGitHubGraphQLUrl('github.gitify.io' as Hostname); expect(result.toString()).toBe('https://github.gitify.io/api/graphql'); }); }); diff --git a/src/utils/api/utils.ts b/src/utils/api/utils.ts index ef5f1f41c..5b8c6ceba 100644 --- a/src/utils/api/utils.ts +++ b/src/utils/api/utils.ts @@ -1,8 +1,8 @@ -import type { HostName } from '../branded-types'; +import type { Hostname } from '../branded-types'; import Constants from '../constants'; import { isEnterpriseHost } from '../helpers'; -export function getGitHubAPIBaseUrl(hostname: HostName): URL { +export function getGitHubAPIBaseUrl(hostname: Hostname): URL { const url = new URL(Constants.GITHUB_API_BASE_URL); if (isEnterpriseHost(hostname)) { @@ -12,7 +12,7 @@ export function getGitHubAPIBaseUrl(hostname: HostName): URL { return url; } -export function getGitHubGraphQLUrl(hostname: HostName): URL { +export function getGitHubGraphQLUrl(hostname: Hostname): URL { const url = new URL(Constants.GITHUB_API_GRAPHQL_URL); if (isEnterpriseHost(hostname)) { diff --git a/src/utils/auth/migration.test.ts b/src/utils/auth/migration.test.ts index 16ded5b54..c6d8bc6e1 100644 --- a/src/utils/auth/migration.test.ts +++ b/src/utils/auth/migration.test.ts @@ -2,7 +2,7 @@ import axios from 'axios'; import nock from 'nock'; import { mockGitifyUser, mockToken } from '../../__mocks__/state-mocks'; import type { AuthState } from '../../types'; -import type { HostName } from '../branded-types'; +import type { Hostname } from '../branded-types'; import Constants from '../constants'; import { convertAccounts, @@ -62,7 +62,7 @@ describe('utils/auth/migration.ts', () => { expect( hasAccountsToMigrate({ enterpriseAccounts: [ - { hostname: 'github.gitify.io' as HostName, token: mockToken }, + { hostname: 'github.gitify.io' as Hostname, token: mockToken }, ], } as AuthState), ).toBe(true); @@ -73,9 +73,9 @@ describe('utils/auth/migration.ts', () => { hasAccountsToMigrate({ token: mockToken, enterpriseAccounts: [ - { hostname: 'github.gitify.io' as HostName, token: mockToken }, + { hostname: 'github.gitify.io' as Hostname, token: mockToken }, ], - accounts: [{ hostname: 'github.com' as HostName, token: mockToken }], + accounts: [{ hostname: 'github.com' as Hostname, token: mockToken }], } as AuthState), ).toBe(false); }); @@ -108,13 +108,13 @@ describe('utils/auth/migration.ts', () => { const result = await convertAccounts({ enterpriseAccounts: [ - { hostname: 'github.gitify.io' as HostName, token: mockToken }, + { hostname: 'github.gitify.io' as Hostname, token: mockToken }, ], } as AuthState); expect(result).toEqual([ { - hostname: 'github.gitify.io' as HostName, + hostname: 'github.gitify.io' as Hostname, platform: 'GitHub Enterprise Server', method: 'OAuth App', token: mockToken, @@ -133,7 +133,7 @@ describe('utils/auth/migration.ts', () => { token: mockToken, user: mockGitifyUser, enterpriseAccounts: [ - { hostname: 'github.gitify.io' as HostName, token: mockToken }, + { hostname: 'github.gitify.io' as Hostname, token: mockToken }, ], } as AuthState); @@ -146,7 +146,7 @@ describe('utils/auth/migration.ts', () => { user: mockGitifyUser, }, { - hostname: 'github.gitify.io' as HostName, + hostname: 'github.gitify.io' as Hostname, platform: 'GitHub Enterprise Server', method: 'OAuth App', token: mockToken, diff --git a/src/utils/auth/types.ts b/src/utils/auth/types.ts index e30b1a4dc..d5592a8b7 100644 --- a/src/utils/auth/types.ts +++ b/src/utils/auth/types.ts @@ -2,7 +2,7 @@ import type { AuthCode, ClientID, ClientSecret, - HostName, + Hostname, Token, } from '../branded-types'; @@ -11,13 +11,13 @@ export type AuthMethod = 'GitHub App' | 'Personal Access Token' | 'OAuth App'; export type PlatformType = 'GitHub Cloud' | 'GitHub Enterprise Server'; export interface LoginOAuthAppOptions { - hostname: HostName; + hostname: Hostname; clientId: ClientID; clientSecret: ClientSecret; } export interface LoginPersonalAccessTokenOptions { - hostname: HostName; + hostname: Hostname; token: Token; } @@ -27,7 +27,7 @@ export interface AuthResponse { } export interface AuthTokenResponse { - hostname: HostName; + hostname: Hostname; token: Token; } @@ -35,6 +35,6 @@ export interface AuthTokenResponse { * @deprecated This type is deprecated and will be removed in a future release. */ export interface EnterpriseAccount { - hostname: HostName; + hostname: Hostname; token: Token; } diff --git a/src/utils/auth/utils.test.ts b/src/utils/auth/utils.test.ts index 95837d8a9..f5f576bab 100644 --- a/src/utils/auth/utils.test.ts +++ b/src/utils/auth/utils.test.ts @@ -3,7 +3,7 @@ import type { AxiosPromise, AxiosResponse } from 'axios'; import { mockAuth, mockGitHubCloudAccount } from '../../__mocks__/state-mocks'; import type { Account, AuthState } from '../../types'; import * as apiRequests from '../api/request'; -import type { AuthCode, ClientID, HostName, Token } from '../branded-types'; +import type { AuthCode, ClientID, Hostname, Token } from '../branded-types'; import * as auth from './utils'; import { getNewOAuthAppURL, getNewTokenURL } from './utils'; @@ -86,7 +86,7 @@ describe('utils/auth/utils.ts', () => { }, ); expect(res.token).toBe('this-is-a-token'); - expect(res.hostname).toBe('github.com' as HostName); + expect(res.hostname).toBe('github.com' as Hostname); }); it('should get a token - failure', async () => { @@ -119,12 +119,12 @@ describe('utils/auth/utils.ts', () => { mockAuthState, 'Personal Access Token', '123-456' as Token, - 'github.com' as HostName, + 'github.com' as Hostname, ); expect(result.accounts).toEqual([ { - hostname: 'github.com' as HostName, + hostname: 'github.com' as Hostname, method: 'Personal Access Token', platform: 'GitHub Cloud', token: '123-456' as Token, @@ -138,12 +138,12 @@ describe('utils/auth/utils.ts', () => { mockAuthState, 'OAuth App', '123-456' as Token, - 'github.com' as HostName, + 'github.com' as Hostname, ); expect(result.accounts).toEqual([ { - hostname: 'github.com' as HostName, + hostname: 'github.com' as Hostname, method: 'OAuth App', platform: 'GitHub Cloud', token: '123-456' as Token, @@ -159,12 +159,12 @@ describe('utils/auth/utils.ts', () => { mockAuthState, 'Personal Access Token', '123-456' as Token, - 'github.gitify.io' as HostName, + 'github.gitify.io' as Hostname, ); expect(result.accounts).toEqual([ { - hostname: 'github.gitify.io' as HostName, + hostname: 'github.gitify.io' as Hostname, method: 'Personal Access Token', platform: 'GitHub Enterprise Server', token: '123-456' as Token, @@ -178,12 +178,12 @@ describe('utils/auth/utils.ts', () => { mockAuthState, 'OAuth App', '123-456' as Token, - 'github.gitify.io' as HostName, + 'github.gitify.io' as Hostname, ); expect(result.accounts).toEqual([ { - hostname: 'github.gitify.io' as HostName, + hostname: 'github.gitify.io' as Hostname, method: 'OAuth App', platform: 'GitHub Enterprise Server', token: '123-456' as Token, @@ -219,19 +219,19 @@ describe('utils/auth/utils.ts', () => { it('getDeveloperSettingsURL', () => { expect( auth.getDeveloperSettingsURL({ - hostname: 'github.com' as HostName, + hostname: 'github.com' as Hostname, method: 'GitHub App', } as Account), ).toBe('https://github.com/settings/apps'); expect( auth.getDeveloperSettingsURL({ - hostname: 'github.com' as HostName, + hostname: 'github.com' as Hostname, method: 'OAuth App', } as Account), ).toBe('https://github.com/settings/developers'); expect( auth.getDeveloperSettingsURL({ - hostname: 'github.com' as HostName, + hostname: 'github.com' as Hostname, method: 'Personal Access Token', } as Account), ).toBe('https://github.com/settings/tokens'); @@ -240,7 +240,7 @@ describe('utils/auth/utils.ts', () => { describe('getNewTokenURL', () => { it('should generate new PAT url - github cloud', () => { expect( - getNewTokenURL('github.com' as HostName).startsWith( + getNewTokenURL('github.com' as Hostname).startsWith( 'https://github.com/settings/tokens/new', ), ).toBeTruthy(); @@ -248,7 +248,7 @@ describe('utils/auth/utils.ts', () => { it('should generate new PAT url - github server', () => { expect( - getNewTokenURL('github.gitify.io' as HostName).startsWith( + getNewTokenURL('github.gitify.io' as Hostname).startsWith( 'https://github.gitify.io/settings/tokens/new', ), ).toBeTruthy(); @@ -258,7 +258,7 @@ describe('utils/auth/utils.ts', () => { describe('getNewOAuthAppURL', () => { it('should generate new oauth app url - github cloud', () => { expect( - getNewOAuthAppURL('github.com' as HostName).startsWith( + getNewOAuthAppURL('github.com' as Hostname).startsWith( 'https://github.com/settings/applications/new', ), ).toBeTruthy(); @@ -266,7 +266,7 @@ describe('utils/auth/utils.ts', () => { it('should generate new oauth app url - github server', () => { expect( - getNewOAuthAppURL('github.gitify.io' as HostName).startsWith( + getNewOAuthAppURL('github.gitify.io' as Hostname).startsWith( 'https://github.gitify.io/settings/applications/new', ), ).toBeTruthy(); @@ -275,19 +275,19 @@ describe('utils/auth/utils.ts', () => { describe('isValidHostname', () => { it('should validate hostname - github cloud', () => { - expect(auth.isValidHostname('github.com' as HostName)).toBeTruthy(); + expect(auth.isValidHostname('github.com' as Hostname)).toBeTruthy(); }); it('should validate hostname - github enterprise server', () => { - expect(auth.isValidHostname('github.gitify.io' as HostName)).toBeTruthy(); + expect(auth.isValidHostname('github.gitify.io' as Hostname)).toBeTruthy(); }); it('should invalidate hostname - empty', () => { - expect(auth.isValidHostname('' as HostName)).toBeFalsy(); + expect(auth.isValidHostname('' as Hostname)).toBeFalsy(); }); it('should invalidate hostname - invalid', () => { - expect(auth.isValidHostname('github' as HostName)).toBeFalsy(); + expect(auth.isValidHostname('github' as Hostname)).toBeFalsy(); }); }); diff --git a/src/utils/auth/utils.ts b/src/utils/auth/utils.ts index 0568efd85..65284fae1 100644 --- a/src/utils/auth/utils.ts +++ b/src/utils/auth/utils.ts @@ -4,7 +4,7 @@ import type { Account, AuthState, GitifyUser } from '../../types'; import type { UserDetails } from '../../typesGitHub'; import { getAuthenticatedUser } from '../api/client'; import { apiRequest } from '../api/request'; -import type { AuthCode, ClientID, HostName, Token } from '../branded-types'; +import type { AuthCode, ClientID, Hostname, Token } from '../branded-types'; import { Constants } from '../constants'; import { getPlatformFromHostname } from '../helpers'; import type { AuthMethod, AuthResponse, AuthTokenResponse } from './types'; @@ -79,7 +79,7 @@ export const authGitHub = ( export const getUserData = async ( token: Token, - hostname: HostName, + hostname: Hostname, ): Promise => { const response: UserDetails = (await getAuthenticatedUser(hostname, token)) .data; @@ -113,7 +113,7 @@ export function addAccount( auth: AuthState, method: AuthMethod, token: Token, - hostname: HostName, + hostname: Hostname, user?: GitifyUser, ): AuthState { return { @@ -157,7 +157,7 @@ export function getDeveloperSettingsURL(account: Account): string { return settingsURL.toString(); } -export function getNewTokenURL(hostname: HostName): string { +export function getNewTokenURL(hostname: Hostname): string { const date = format(new Date(), 'PP p'); const newTokenURL = new URL(`https://${hostname}/settings/tokens/new`); newTokenURL.searchParams.append('description', `Gitify (Created on ${date})`); @@ -166,7 +166,7 @@ export function getNewTokenURL(hostname: HostName): string { return newTokenURL.toString(); } -export function getNewOAuthAppURL(hostname: HostName): string { +export function getNewOAuthAppURL(hostname: Hostname): string { const date = format(new Date(), 'PP p'); const newOAuthAppURL = new URL( `https://${hostname}/settings/applications/new`, @@ -187,7 +187,7 @@ export function getNewOAuthAppURL(hostname: HostName): string { return newOAuthAppURL.toString(); } -export function isValidHostname(hostname: HostName) { +export function isValidHostname(hostname: Hostname) { return /^([a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,6}$/i.test( hostname, ); diff --git a/src/utils/branded-types.ts b/src/utils/branded-types.ts index a4f32af91..9a2d68253 100644 --- a/src/utils/branded-types.ts +++ b/src/utils/branded-types.ts @@ -12,4 +12,4 @@ export type ClientID = Branded; export type ClientSecret = Branded; -export type HostName = Branded; +export type Hostname = Branded; diff --git a/src/utils/constants.ts b/src/utils/constants.ts index ccf2a2874..9913a052a 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -1,12 +1,12 @@ import type { ErrorType, GitifyError } from '../types'; -import type { ClientID, ClientSecret, HostName } from './branded-types'; +import type { ClientID, ClientSecret, Hostname } from './branded-types'; export const Constants = { // GitHub OAuth AUTH_SCOPE: ['read:user', 'notifications', 'repo'], DEFAULT_AUTH_OPTIONS: { - hostname: 'github.com' as HostName, + hostname: 'github.com' as Hostname, clientId: process.env.OAUTH_CLIENT_ID as ClientID, clientSecret: process.env.OAUTH_CLIENT_SECRET as ClientSecret, }, diff --git a/src/utils/helpers.test.ts b/src/utils/helpers.test.ts index 704628239..b5c736f50 100644 --- a/src/utils/helpers.test.ts +++ b/src/utils/helpers.test.ts @@ -7,7 +7,7 @@ import { mockSingleNotification, } from './api/__mocks__/response-mocks'; import * as apiRequests from './api/request'; -import type { HostName } from './branded-types'; +import type { Hostname } from './branded-types'; import { formatForDisplay, formatNotificationUpdatedAt, @@ -20,19 +20,19 @@ import { describe('utils/helpers.ts', () => { describe('getPlatformFromHostname', () => { it('should return GitHub Cloud', () => { - expect(getPlatformFromHostname('github.com' as HostName)).toBe( + expect(getPlatformFromHostname('github.com' as Hostname)).toBe( 'GitHub Cloud', ); - expect(getPlatformFromHostname('api.github.com' as HostName)).toBe( + expect(getPlatformFromHostname('api.github.com' as Hostname)).toBe( 'GitHub Cloud', ); }); it('should return GitHub Enterprise Server', () => { - expect(getPlatformFromHostname('github.gitify.app' as HostName)).toBe( + expect(getPlatformFromHostname('github.gitify.app' as Hostname)).toBe( 'GitHub Enterprise Server', ); - expect(getPlatformFromHostname('api.github.gitify.app' as HostName)).toBe( + expect(getPlatformFromHostname('api.github.gitify.app' as Hostname)).toBe( 'GitHub Enterprise Server', ); }); @@ -40,13 +40,13 @@ describe('utils/helpers.ts', () => { describe('isEnterpriseHost', () => { it('should return true for enterprise host', () => { - expect(isEnterpriseHost('github.gitify.app' as HostName)).toBe(true); - expect(isEnterpriseHost('api.github.gitify.app' as HostName)).toBe(true); + expect(isEnterpriseHost('github.gitify.app' as Hostname)).toBe(true); + expect(isEnterpriseHost('api.github.gitify.app' as Hostname)).toBe(true); }); it('should return false for non-enterprise host', () => { - expect(isEnterpriseHost('github.com' as HostName)).toBe(false); - expect(isEnterpriseHost('api.github.com' as HostName)).toBe(false); + expect(isEnterpriseHost('github.com' as Hostname)).toBe(false); + expect(isEnterpriseHost('api.github.com' as Hostname)).toBe(false); }); }); diff --git a/src/utils/helpers.ts b/src/utils/helpers.ts index 9aed65807..be018fa83 100644 --- a/src/utils/helpers.ts +++ b/src/utils/helpers.ts @@ -2,7 +2,7 @@ import { formatDistanceToNowStrict, parseISO } from 'date-fns'; import type { Notification } from '../typesGitHub'; import { getHtmlUrl, getLatestDiscussion } from './api/client'; import type { PlatformType } from './auth/types'; -import type { HostName } from './branded-types'; +import type { Hostname } from './branded-types'; import { Constants } from './constants'; import { getCheckSuiteAttributes, @@ -16,7 +16,7 @@ export function getPlatformFromHostname(hostname: string): PlatformType { : 'GitHub Enterprise Server'; } -export function isEnterpriseHost(hostname: HostName): boolean { +export function isEnterpriseHost(hostname: Hostname): boolean { return !hostname.endsWith(Constants.DEFAULT_AUTH_OPTIONS.hostname); } diff --git a/src/utils/links.test.ts b/src/utils/links.test.ts index 2ef4f7c41..eaaead0ac 100644 --- a/src/utils/links.test.ts +++ b/src/utils/links.test.ts @@ -3,7 +3,7 @@ import { mockGitHubCloudAccount } from '../__mocks__/state-mocks'; import type { Repository } from '../typesGitHub'; import { mockSingleNotification } from './api/__mocks__/response-mocks'; import * as authUtils from './auth/utils'; -import type { HostName } from './branded-types'; +import type { Hostname } from './branded-types'; import * as comms from './comms'; import Constants from './constants'; import * as helpers from './helpers'; @@ -66,7 +66,7 @@ describe('utils/links.ts', () => { }); it('openHost', () => { - openHost('github.com' as HostName); + openHost('github.com' as Hostname); expect(comms.openExternalLink).toHaveBeenCalledWith('https://github.com'); }); diff --git a/src/utils/links.ts b/src/utils/links.ts index e54e6ee59..6a2ffed8f 100644 --- a/src/utils/links.ts +++ b/src/utils/links.ts @@ -1,7 +1,7 @@ import type { Account } from '../types'; import type { Notification, Repository, SubjectUser } from '../typesGitHub'; import { getDeveloperSettingsURL } from './auth/utils'; -import type { HostName } from './branded-types'; +import type { Hostname } from './branded-types'; import { openExternalLink } from './comms'; import Constants from './constants'; import { generateGitHubWebUrl } from './helpers'; @@ -30,7 +30,7 @@ export function openUserProfile(user: SubjectUser) { openExternalLink(user.html_url); } -export function openHost(hostname: HostName) { +export function openHost(hostname: Hostname) { openExternalLink(`https://${hostname}`); } From 48be47032e8da9c095a3347ea8fda0419fdf7ce6 Mon Sep 17 00:00:00 2001 From: dammy95 Date: Thu, 13 Jun 2024 13:51:47 +0100 Subject: [PATCH 7/9] Move types in branded-types to src/types --- src/__mocks__/partial-mocks.ts | 2 +- src/__mocks__/state-mocks.ts | 3 ++- src/context/App.test.tsx | 3 +-- src/routes/LoginWithOAuthApp.test.tsx | 3 +-- src/routes/LoginWithOAuthApp.tsx | 7 +------ src/routes/LoginWithPersonalAccessToken.tsx | 2 +- src/types.ts | 19 +++++++++++++++++-- src/utils/api/client.test.ts | 3 +-- src/utils/api/client.ts | 3 +-- src/utils/api/request.test.ts | 2 +- src/utils/api/request.ts | 2 +- src/utils/api/utils.test.ts | 2 +- src/utils/api/utils.ts | 2 +- src/utils/auth/migration.test.ts | 3 +-- src/utils/auth/types.ts | 2 +- src/utils/auth/utils.test.ts | 10 ++++++++-- src/utils/auth/utils.ts | 11 +++++++++-- src/utils/branded-types.ts | 15 --------------- src/utils/constants.ts | 2 +- src/utils/helpers.test.ts | 2 +- src/utils/helpers.ts | 2 +- src/utils/links.test.ts | 2 +- src/utils/links.ts | 3 +-- src/utils/storage.test.ts | 2 +- 24 files changed, 55 insertions(+), 52 deletions(-) delete mode 100644 src/utils/branded-types.ts diff --git a/src/__mocks__/partial-mocks.ts b/src/__mocks__/partial-mocks.ts index bc6585451..5631d457f 100644 --- a/src/__mocks__/partial-mocks.ts +++ b/src/__mocks__/partial-mocks.ts @@ -1,5 +1,5 @@ +import type { Hostname } from '../types'; import type { Notification, Subject, User } from '../typesGitHub'; -import type { Hostname } from '../utils/branded-types'; import Constants from '../utils/constants'; import { mockGitifyUser, mockToken } from './state-mocks'; diff --git a/src/__mocks__/state-mocks.ts b/src/__mocks__/state-mocks.ts index 1d94a2c39..cc2848e69 100644 --- a/src/__mocks__/state-mocks.ts +++ b/src/__mocks__/state-mocks.ts @@ -3,11 +3,12 @@ import { type AuthState, type GitifyState, type GitifyUser, + type Hostname, type SettingsState, Theme, + type Token, } from '../types'; import type { EnterpriseAccount } from '../utils/auth/types'; -import type { Hostname, Token } from '../utils/branded-types'; import Constants from '../utils/constants'; export const mockEnterpriseAccounts: EnterpriseAccount[] = [ diff --git a/src/context/App.test.tsx b/src/context/App.test.tsx index 01bdf825f..f7cc61913 100644 --- a/src/context/App.test.tsx +++ b/src/context/App.test.tsx @@ -2,10 +2,9 @@ import { act, fireEvent, render, waitFor } from '@testing-library/react'; import { useContext } from 'react'; import { mockAuth, mockSettings } from '../__mocks__/state-mocks'; import { useNotifications } from '../hooks/useNotifications'; -import type { AuthState, SettingsState } from '../types'; +import type { AuthState, Hostname, SettingsState, Token } from '../types'; import { mockSingleNotification } from '../utils/api/__mocks__/response-mocks'; import * as apiRequests from '../utils/api/request'; -import type { Hostname, Token } from '../utils/branded-types'; import * as comms from '../utils/comms'; import Constants from '../utils/constants'; import * as notifications from '../utils/notifications'; diff --git a/src/routes/LoginWithOAuthApp.test.tsx b/src/routes/LoginWithOAuthApp.test.tsx index 78e081d6b..011562677 100644 --- a/src/routes/LoginWithOAuthApp.test.tsx +++ b/src/routes/LoginWithOAuthApp.test.tsx @@ -2,8 +2,7 @@ import { fireEvent, render, screen } from '@testing-library/react'; import { shell } from 'electron'; import { MemoryRouter } from 'react-router-dom'; import { AppContext } from '../context/App'; -import type { AuthState } from '../types'; -import type { ClientID, ClientSecret, Hostname } from '../utils/branded-types'; +import type { AuthState, ClientID, ClientSecret, Hostname } from '../types'; import { LoginWithOAuthApp, validate } from './LoginWithOAuthApp'; const mockNavigate = jest.fn(); diff --git a/src/routes/LoginWithOAuthApp.tsx b/src/routes/LoginWithOAuthApp.tsx index 176ab5eb5..bf667fbc2 100644 --- a/src/routes/LoginWithOAuthApp.tsx +++ b/src/routes/LoginWithOAuthApp.tsx @@ -10,6 +10,7 @@ import { useNavigate } from 'react-router-dom'; import { Button } from '../components/fields/Button'; import { FieldInput } from '../components/fields/FieldInput'; import { AppContext } from '../context/App'; +import type { ClientID, ClientSecret, Hostname, Token } from '../types'; import type { LoginOAuthAppOptions } from '../utils/auth/types'; import { getNewOAuthAppURL, @@ -17,12 +18,6 @@ import { isValidHostname, isValidToken, } from '../utils/auth/utils'; -import type { - ClientID, - ClientSecret, - Hostname, - Token, -} from '../utils/branded-types'; import Constants from '../utils/constants'; interface IValues { diff --git a/src/routes/LoginWithPersonalAccessToken.tsx b/src/routes/LoginWithPersonalAccessToken.tsx index 3eaadc133..caadf4f24 100644 --- a/src/routes/LoginWithPersonalAccessToken.tsx +++ b/src/routes/LoginWithPersonalAccessToken.tsx @@ -10,13 +10,13 @@ import { useNavigate } from 'react-router-dom'; import { Button } from '../components/fields/Button'; import { FieldInput } from '../components/fields/FieldInput'; import { AppContext } from '../context/App'; +import type { Hostname, Token } from '../types'; import type { LoginPersonalAccessTokenOptions } from '../utils/auth/types'; import { getNewTokenURL, isValidHostname, isValidToken, } from '../utils/auth/utils'; -import type { Hostname, Token } from '../utils/branded-types'; import { Constants } from '../utils/constants'; interface IValues { diff --git a/src/types.ts b/src/types.ts index c58ac148e..ab258a8c7 100644 --- a/src/types.ts +++ b/src/types.ts @@ -6,9 +6,10 @@ import type { EnterpriseAccount, PlatformType, } from './utils/auth/types'; -import type { Hostname, Token } from './utils/branded-types'; -export type Status = 'loading' | 'success' | 'error'; +declare const __brand: unique symbol; + +type Brand = { [__brand]: B }; export interface AuthState { accounts: Account[]; @@ -26,6 +27,20 @@ export interface AuthState { user?: GitifyUser | null; } +export type Branded = T & Brand; + +export type AuthCode = Branded; + +export type Token = Branded; + +export type ClientID = Branded; + +export type ClientSecret = Branded; + +export type Hostname = Branded; + +export type Status = 'loading' | 'success' | 'error'; + export interface Account { method: AuthMethod; platform: PlatformType; diff --git a/src/utils/api/client.test.ts b/src/utils/api/client.test.ts index 34fd00550..ddb7f22bd 100644 --- a/src/utils/api/client.test.ts +++ b/src/utils/api/client.test.ts @@ -4,8 +4,7 @@ import { mockGitHubEnterpriseServerAccount, mockToken, } from '../../__mocks__/state-mocks'; -import type { SettingsState } from '../../types'; -import type { Hostname, Token } from '../branded-types'; +import type { Hostname, SettingsState, Token } from '../../types'; import { getAuthenticatedUser, getHtmlUrl, diff --git a/src/utils/api/client.ts b/src/utils/api/client.ts index 2ced08053..e11eaf7db 100644 --- a/src/utils/api/client.ts +++ b/src/utils/api/client.ts @@ -1,6 +1,6 @@ import type { AxiosPromise } from 'axios'; import { print } from 'graphql/language/printer'; -import type { Account, SettingsState } from '../../types'; +import type { Account, Hostname, SettingsState, Token } from '../../types'; import type { Commit, CommitComment, @@ -15,7 +15,6 @@ import type { Release, UserDetails, } from '../../typesGitHub'; -import type { Hostname, Token } from '../branded-types'; import { QUERY_SEARCH_DISCUSSIONS } from './graphql/discussions'; import { formatAsGitHubSearchSyntax } from './graphql/utils'; import { apiRequestAuth } from './request'; diff --git a/src/utils/api/request.test.ts b/src/utils/api/request.test.ts index a1f4d391e..3958a6757 100644 --- a/src/utils/api/request.test.ts +++ b/src/utils/api/request.test.ts @@ -1,5 +1,5 @@ import axios from 'axios'; -import type { Token } from '../branded-types'; +import type { Token } from '../../types'; import { apiRequest, apiRequestAuth } from './request'; jest.mock('axios'); diff --git a/src/utils/api/request.ts b/src/utils/api/request.ts index 5a9615d2d..f89687d33 100644 --- a/src/utils/api/request.ts +++ b/src/utils/api/request.ts @@ -1,5 +1,5 @@ import axios, { type AxiosPromise, type Method } from 'axios'; -import type { Token } from '../branded-types'; +import type { Token } from '../../types'; export function apiRequest( url: string, diff --git a/src/utils/api/utils.test.ts b/src/utils/api/utils.test.ts index 11df45df0..e6d488af5 100644 --- a/src/utils/api/utils.test.ts +++ b/src/utils/api/utils.test.ts @@ -1,4 +1,4 @@ -import type { Hostname } from '../branded-types'; +import type { Hostname } from '../../types'; import { getGitHubAPIBaseUrl, getGitHubGraphQLUrl } from './utils'; describe('utils/api/utils.ts', () => { diff --git a/src/utils/api/utils.ts b/src/utils/api/utils.ts index 5b8c6ceba..52fdf25fb 100644 --- a/src/utils/api/utils.ts +++ b/src/utils/api/utils.ts @@ -1,4 +1,4 @@ -import type { Hostname } from '../branded-types'; +import type { Hostname } from '../../types'; import Constants from '../constants'; import { isEnterpriseHost } from '../helpers'; diff --git a/src/utils/auth/migration.test.ts b/src/utils/auth/migration.test.ts index c6d8bc6e1..82c256698 100644 --- a/src/utils/auth/migration.test.ts +++ b/src/utils/auth/migration.test.ts @@ -1,8 +1,7 @@ import axios from 'axios'; import nock from 'nock'; import { mockGitifyUser, mockToken } from '../../__mocks__/state-mocks'; -import type { AuthState } from '../../types'; -import type { Hostname } from '../branded-types'; +import type { AuthState, Hostname } from '../../types'; import Constants from '../constants'; import { convertAccounts, diff --git a/src/utils/auth/types.ts b/src/utils/auth/types.ts index d5592a8b7..d66bbbce4 100644 --- a/src/utils/auth/types.ts +++ b/src/utils/auth/types.ts @@ -4,7 +4,7 @@ import type { ClientSecret, Hostname, Token, -} from '../branded-types'; +} from '../../types'; export type AuthMethod = 'GitHub App' | 'Personal Access Token' | 'OAuth App'; diff --git a/src/utils/auth/utils.test.ts b/src/utils/auth/utils.test.ts index f5f576bab..0aa078534 100644 --- a/src/utils/auth/utils.test.ts +++ b/src/utils/auth/utils.test.ts @@ -1,9 +1,15 @@ import remote from '@electron/remote'; import type { AxiosPromise, AxiosResponse } from 'axios'; import { mockAuth, mockGitHubCloudAccount } from '../../__mocks__/state-mocks'; -import type { Account, AuthState } from '../../types'; +import type { + Account, + AuthCode, + AuthState, + ClientID, + Hostname, + Token, +} from '../../types'; import * as apiRequests from '../api/request'; -import type { AuthCode, ClientID, Hostname, Token } from '../branded-types'; import * as auth from './utils'; import { getNewOAuthAppURL, getNewTokenURL } from './utils'; diff --git a/src/utils/auth/utils.ts b/src/utils/auth/utils.ts index 65284fae1..e623816f2 100644 --- a/src/utils/auth/utils.ts +++ b/src/utils/auth/utils.ts @@ -1,10 +1,17 @@ import { BrowserWindow } from '@electron/remote'; import { format } from 'date-fns'; -import type { Account, AuthState, GitifyUser } from '../../types'; +import type { + Account, + AuthCode, + AuthState, + ClientID, + GitifyUser, + Hostname, + Token, +} from '../../types'; import type { UserDetails } from '../../typesGitHub'; import { getAuthenticatedUser } from '../api/client'; import { apiRequest } from '../api/request'; -import type { AuthCode, ClientID, Hostname, Token } from '../branded-types'; import { Constants } from '../constants'; import { getPlatformFromHostname } from '../helpers'; import type { AuthMethod, AuthResponse, AuthTokenResponse } from './types'; diff --git a/src/utils/branded-types.ts b/src/utils/branded-types.ts deleted file mode 100644 index 9a2d68253..000000000 --- a/src/utils/branded-types.ts +++ /dev/null @@ -1,15 +0,0 @@ -declare const __brand: unique symbol; - -type Brand = { [__brand]: B }; - -export type Branded = T & Brand; - -export type AuthCode = Branded; - -export type Token = Branded; - -export type ClientID = Branded; - -export type ClientSecret = Branded; - -export type Hostname = Branded; diff --git a/src/utils/constants.ts b/src/utils/constants.ts index 9913a052a..a9b036460 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -1,5 +1,5 @@ import type { ErrorType, GitifyError } from '../types'; -import type { ClientID, ClientSecret, Hostname } from './branded-types'; +import type { ClientID, ClientSecret, Hostname } from '../types'; export const Constants = { // GitHub OAuth diff --git a/src/utils/helpers.test.ts b/src/utils/helpers.test.ts index b5c736f50..41b60f799 100644 --- a/src/utils/helpers.test.ts +++ b/src/utils/helpers.test.ts @@ -1,13 +1,13 @@ import type { AxiosPromise, AxiosResponse } from 'axios'; import { mockPersonalAccessTokenAccount } from '../__mocks__/state-mocks'; +import type { Hostname } from '../types'; import type { SubjectType } from '../typesGitHub'; import { mockGraphQLResponse, mockSingleNotification, } from './api/__mocks__/response-mocks'; import * as apiRequests from './api/request'; -import type { Hostname } from './branded-types'; import { formatForDisplay, formatNotificationUpdatedAt, diff --git a/src/utils/helpers.ts b/src/utils/helpers.ts index be018fa83..02f23d7af 100644 --- a/src/utils/helpers.ts +++ b/src/utils/helpers.ts @@ -1,8 +1,8 @@ import { formatDistanceToNowStrict, parseISO } from 'date-fns'; +import type { Hostname } from '../types'; import type { Notification } from '../typesGitHub'; import { getHtmlUrl, getLatestDiscussion } from './api/client'; import type { PlatformType } from './auth/types'; -import type { Hostname } from './branded-types'; import { Constants } from './constants'; import { getCheckSuiteAttributes, diff --git a/src/utils/links.test.ts b/src/utils/links.test.ts index eaaead0ac..08155b6a5 100644 --- a/src/utils/links.test.ts +++ b/src/utils/links.test.ts @@ -1,9 +1,9 @@ import { partialMockUser } from '../__mocks__/partial-mocks'; import { mockGitHubCloudAccount } from '../__mocks__/state-mocks'; +import type { Hostname } from '../types'; import type { Repository } from '../typesGitHub'; import { mockSingleNotification } from './api/__mocks__/response-mocks'; import * as authUtils from './auth/utils'; -import type { Hostname } from './branded-types'; import * as comms from './comms'; import Constants from './constants'; import * as helpers from './helpers'; diff --git a/src/utils/links.ts b/src/utils/links.ts index 6a2ffed8f..3f3d7aef7 100644 --- a/src/utils/links.ts +++ b/src/utils/links.ts @@ -1,7 +1,6 @@ -import type { Account } from '../types'; +import type { Account, Hostname } from '../types'; import type { Notification, Repository, SubjectUser } from '../typesGitHub'; import { getDeveloperSettingsURL } from './auth/utils'; -import type { Hostname } from './branded-types'; import { openExternalLink } from './comms'; import Constants from './constants'; import { generateGitHubWebUrl } from './helpers'; diff --git a/src/utils/storage.test.ts b/src/utils/storage.test.ts index bc916a9c4..41794ce84 100644 --- a/src/utils/storage.test.ts +++ b/src/utils/storage.test.ts @@ -1,5 +1,5 @@ import { mockSettings } from '../__mocks__/state-mocks'; -import type { Token } from './branded-types'; +import type { Token } from '../types'; import Constants from './constants'; import { clearState, loadState, saveState } from './storage'; From 5558f521cad8b8706677f1da8075409d04eedaff Mon Sep 17 00:00:00 2001 From: dammy95 Date: Thu, 13 Jun 2024 16:19:32 +0100 Subject: [PATCH 8/9] Create WebUrl Branded type --- src/__mocks__/partial-mocks.ts | 6 +- src/components/NotificationRow.test.tsx | 5 +- src/components/Repository.test.tsx | 3 +- src/components/fields/Button.test.tsx | 3 +- src/components/fields/Button.tsx | 3 +- src/types.ts | 2 + src/typesGitHub.ts | 236 +++++++++---------- src/utils/api/__mocks__/response-mocks.ts | 270 ++++++++++++---------- src/utils/api/client.test.ts | 6 +- src/utils/api/client.ts | 67 +++--- src/utils/api/errors.test.ts | 3 +- src/utils/api/request.test.ts | 4 +- src/utils/api/request.ts | 6 +- src/utils/auth/utils.ts | 22 +- src/utils/comms.test.ts | 5 +- src/utils/comms.ts | 3 +- src/utils/constants.ts | 8 +- src/utils/helpers.test.ts | 8 +- src/utils/helpers.ts | 18 +- src/utils/links.test.ts | 4 +- src/utils/links.ts | 12 +- src/utils/notifications.test.ts | 10 +- src/utils/subject.test.ts | 27 +-- src/utils/subject.ts | 3 +- 24 files changed, 390 insertions(+), 344 deletions(-) diff --git a/src/__mocks__/partial-mocks.ts b/src/__mocks__/partial-mocks.ts index 5631d457f..2070a32d4 100644 --- a/src/__mocks__/partial-mocks.ts +++ b/src/__mocks__/partial-mocks.ts @@ -1,4 +1,4 @@ -import type { Hostname } from '../types'; +import type { Hostname, WebUrl } from '../types'; import type { Notification, Subject, User } from '../typesGitHub'; import Constants from '../utils/constants'; import { mockGitifyUser, mockToken } from './state-mocks'; @@ -23,8 +23,8 @@ export function partialMockNotification( export function partialMockUser(login: string): User { const mockUser: Partial = { login: login, - html_url: `https://github.com/${login}`, - avatar_url: 'https://avatars.githubusercontent.com/u/583231?v=4', + html_url: `https://github.com/${login}` as WebUrl, + avatar_url: 'https://avatars.githubusercontent.com/u/583231?v=4' as WebUrl, type: 'User', }; diff --git a/src/components/NotificationRow.test.tsx b/src/components/NotificationRow.test.tsx index 99f249a5a..4b1b3b0cb 100644 --- a/src/components/NotificationRow.test.tsx +++ b/src/components/NotificationRow.test.tsx @@ -5,6 +5,7 @@ import { mockSettings, } from '../__mocks__/state-mocks'; import { AppContext } from '../context/App'; +import type { WebUrl } from '../types'; import type { Milestone, UserType } from '../typesGitHub'; import { mockSingleNotification } from '../utils/api/__mocks__/response-mocks'; import * as comms from '../utils/comms'; @@ -473,9 +474,9 @@ describe('components/NotificationRow.tsx', () => { ...mockSingleNotification.subject, user: { login: 'some-user', - html_url: 'https://github.com/some-user', + html_url: 'https://github.com/some-user' as WebUrl, avatar_url: - 'https://avatars.githubusercontent.com/u/123456789?v=4', + 'https://avatars.githubusercontent.com/u/123456789?v=4' as WebUrl, type: 'User' as UserType, }, reviews: null, diff --git a/src/components/Repository.test.tsx b/src/components/Repository.test.tsx index 5cf5dde2e..e6db460e2 100644 --- a/src/components/Repository.test.tsx +++ b/src/components/Repository.test.tsx @@ -1,6 +1,7 @@ import { fireEvent, render, screen } from '@testing-library/react'; import { mockGitHubCloudAccount } from '../__mocks__/state-mocks'; import { AppContext } from '../context/App'; +import type { WebUrl } from '../types'; import { mockGitHubNotifications, mockSingleNotification, @@ -81,7 +82,7 @@ describe('components/Repository.tsx', () => { }); it('should use default repository icon when avatar is not available', () => { - props.repoNotifications[0].repository.owner.avatar_url = ''; + props.repoNotifications[0].repository.owner.avatar_url = '' as WebUrl; const tree = render( diff --git a/src/components/fields/Button.test.tsx b/src/components/fields/Button.test.tsx index e0472eb46..4dc19db2c 100644 --- a/src/components/fields/Button.test.tsx +++ b/src/components/fields/Button.test.tsx @@ -1,6 +1,7 @@ import { MarkGithubIcon } from '@primer/octicons-react'; import { fireEvent, render, screen } from '@testing-library/react'; import { shell } from 'electron'; +import type { WebUrl } from '../../types'; import { Button, type IButton } from './Button'; describe('components/fields/Button.tsx', () => { @@ -27,7 +28,7 @@ describe('components/fields/Button.tsx', () => { }); it('should render with url', () => { - render(