diff --git a/package.json b/package.json index 148ce37a7..0524032d9 100644 --- a/package.json +++ b/package.json @@ -91,8 +91,8 @@ "@types/react-dom": "19.2.2", "@types/react-router-dom": "5.3.3", "@types/semver": "7.7.1", - "identity-obj-proxy": "3.0.0", "axios": "1.12.2", + "axios-cache-interceptor": "1.8.3", "clsx": "2.1.1", "concurrently": "9.2.1", "copy-webpack-plugin": "13.0.1", @@ -105,6 +105,7 @@ "graphql-tag": "2.12.6", "html-webpack-plugin": "5.6.4", "husky": "9.1.7", + "identity-obj-proxy": "3.0.0", "jest": "30.2.0", "jest-environment-jsdom": "30.2.0", "mini-css-extract-plugin": "2.9.4", @@ -138,4 +139,4 @@ "*": "biome check --fix --no-errors-on-unmatched", "*.{js,ts,tsx}": "pnpm test --findRelatedTests --passWithNoTests --updateSnapshot" } -} +} \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4a14ba741..251c86ee0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -81,6 +81,9 @@ importers: axios: specifier: 1.12.2 version: 1.12.2 + axios-cache-interceptor: + specifier: 1.8.3 + version: 1.8.3(axios@1.12.2) clsx: specifier: 2.1.1 version: 2.1.1 @@ -1456,6 +1459,12 @@ packages: resolution: {integrity: sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==} engines: {node: '>= 4.0.0'} + axios-cache-interceptor@1.8.3: + resolution: {integrity: sha512-ifuSBoCEkVaiugg1UTjVuTdK+SjSOJ35pdv2OrzhRT3wDMr52QiayQxUqs7jd7GDsfPOjMcw3T3ek0TysbyZZw==} + engines: {node: '>=12'} + peerDependencies: + axios: ^1 + axios@1.12.2: resolution: {integrity: sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw==} @@ -1568,6 +1577,9 @@ packages: resolution: {integrity: sha512-/+Emcj9DAXxX4cwlLmRI9c166RuL3w30zp4R7Joiv2cQTtTtA+jeuCAjH3ZlGnYS3tKENSrKhAzVVP9GVyzeYQ==} engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + cache-parser@1.2.5: + resolution: {integrity: sha512-Md/4VhAHByQ9frQ15WD6LrMNiVw9AEl/J7vWIXw+sxT6fSOpbtt6LHTp76vy8+bOESPBO94117Hm2bIjlI7XjA==} + cacheable-lookup@5.0.4: resolution: {integrity: sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==} engines: {node: '>=10.6.0'} @@ -2227,6 +2239,9 @@ packages: fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + fast-defer@1.1.8: + resolution: {integrity: sha512-lEJeOH5VL5R09j6AA0D4Uvq7AgsHw0dAImQQ+F3iSyHZuAxyQfWobsagGpTcOPvJr3urmKRHrs+Gs9hV+/Qm/Q==} + fast-json-stable-stringify@2.1.0: resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} @@ -3299,6 +3314,9 @@ packages: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} + object-code@1.3.3: + resolution: {integrity: sha512-/Ds4Xd5xzrtUOJ+xJQ57iAy0BZsZltOHssnDgcZ8DOhgh41q1YJCnTPnWdWSLkNGNnxYzhYChjc5dgC9mEERCA==} + object-keys@1.1.1: resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} engines: {node: '>= 0.4'} @@ -6014,6 +6032,13 @@ snapshots: at-least-node@1.0.0: {} + axios-cache-interceptor@1.8.3(axios@1.12.2): + dependencies: + axios: 1.12.2 + cache-parser: 1.2.5 + fast-defer: 1.1.8 + object-code: 1.3.3 + axios@1.12.2: dependencies: follow-redirects: 1.15.6 @@ -6225,6 +6250,8 @@ snapshots: transitivePeerDependencies: - bluebird + cache-parser@1.2.5: {} + cacheable-lookup@5.0.4: {} cacheable-request@7.0.4: @@ -6926,6 +6953,8 @@ snapshots: fast-deep-equal@3.1.3: {} + fast-defer@1.1.8: {} + fast-json-stable-stringify@2.1.0: {} fast-uri@3.0.2: {} @@ -8186,6 +8215,8 @@ snapshots: object-assign@4.1.1: {} + object-code@1.3.3: {} + object-keys@1.1.1: optional: true diff --git a/src/renderer/components/Sidebar.tsx b/src/renderer/components/Sidebar.tsx index b127be877..151027a2e 100644 --- a/src/renderer/components/Sidebar.tsx +++ b/src/renderer/components/Sidebar.tsx @@ -62,6 +62,7 @@ export const Sidebar: FC = () => { const refreshNotifications = () => { navigate('/', { replace: true }); + fetchNotifications(); }; diff --git a/src/renderer/constants.ts b/src/renderer/constants.ts index 1f9e9f6d3..46d06ccde 100644 --- a/src/renderer/constants.ts +++ b/src/renderer/constants.ts @@ -20,8 +20,8 @@ export const Constants = { ALL_READ_EMOJIS: ['🎉', '🎊', '🥳', '👏', '🙌', '😎', '🏖️', '🚀', '✨', '🏆'], - DEFAULT_FETCH_NOTIFICATIONS_INTERVAL_MS: 60 * 1000, // 1 minute - MIN_FETCH_NOTIFICATIONS_INTERVAL_MS: 60 * 1000, // 1 minute + DEFAULT_FETCH_NOTIFICATIONS_INTERVAL_MS: 5 * 1000, // 1 minute + MIN_FETCH_NOTIFICATIONS_INTERVAL_MS: 5 * 1000, // 1 minute MAX_FETCH_NOTIFICATIONS_INTERVAL_MS: 60 * 60 * 1000, // 1 hour FETCH_NOTIFICATIONS_INTERVAL_STEP_MS: 60 * 1000, // 1 minute diff --git a/src/renderer/hooks/useNotifications.ts b/src/renderer/hooks/useNotifications.ts index bd9b9f4ae..7bb0e27ac 100644 --- a/src/renderer/hooks/useNotifications.ts +++ b/src/renderer/hooks/useNotifications.ts @@ -111,11 +111,7 @@ export const useNotifications = (): NotificationsState => { try { await Promise.all( readNotifications.map((notification) => - markNotificationThreadAsRead( - notification.id, - notification.account.hostname, - notification.account.token, - ), + markNotificationThreadAsRead(notification.account, notification.id), ), ); @@ -149,9 +145,8 @@ export const useNotifications = (): NotificationsState => { await Promise.all( doneNotifications.map((notification) => markNotificationThreadAsDone( + notification.account, notification.id, - notification.account.hostname, - notification.account.token, ), ), ); @@ -184,9 +179,8 @@ export const useNotifications = (): NotificationsState => { try { await ignoreNotificationThreadSubscription( + notification.account, notification.id, - notification.account.hostname, - notification.account.token, ); if (state.settings.markAsDoneOnUnsubscribe) { diff --git a/src/renderer/utils/api/client.test.ts b/src/renderer/utils/api/client.test.ts index 87dfe326b..d6c084993 100644 --- a/src/renderer/utils/api/client.test.ts +++ b/src/renderer/utils/api/client.test.ts @@ -5,7 +5,7 @@ import { mockGitHubEnterpriseServerAccount, mockToken, } from '../../__mocks__/state-mocks'; -import type { Hostname, Link, SettingsState, Token } from '../../types'; +import type { Hostname, Link, SettingsState } from '../../types'; import * as logger from '../../utils/logger'; import { getAuthenticatedUser, @@ -31,7 +31,7 @@ describe('renderer/utils/api/client.ts', () => { describe('getAuthenticatedUser', () => { it('should fetch authenticated user - github', async () => { - await getAuthenticatedUser(mockGitHubHostname, mockToken); + await getAuthenticatedUser(mockGitHubCloudAccount); expect(axios).toHaveBeenCalledWith({ url: 'https://api.github.com/user', @@ -47,10 +47,10 @@ describe('renderer/utils/api/client.ts', () => { }); it('should fetch authenticated user - enterprise', async () => { - await getAuthenticatedUser(mockEnterpriseHostname, mockToken); + await getAuthenticatedUser(mockGitHubEnterpriseServerAccount); expect(axios).toHaveBeenCalledWith({ - url: 'https://example.com/api/v3/user', + url: 'https://github.gitify.io/api/v3/user', headers: { Accept: 'application/json', Authorization: 'token decrypted', @@ -172,11 +172,7 @@ describe('renderer/utils/api/client.ts', () => { describe('markNotificationThreadAsRead', () => { it('should mark notification thread as read - github', async () => { - await markNotificationThreadAsRead( - mockThreadId, - mockGitHubHostname, - mockToken, - ); + await markNotificationThreadAsRead(mockGitHubCloudAccount, mockThreadId); expect(axios).toHaveBeenCalledWith({ url: `https://api.github.com/notifications/threads/${mockThreadId}`, @@ -193,13 +189,12 @@ describe('renderer/utils/api/client.ts', () => { it('should mark notification thread as read - enterprise', async () => { await markNotificationThreadAsRead( + mockGitHubEnterpriseServerAccount, mockThreadId, - mockEnterpriseHostname, - mockToken, ); expect(axios).toHaveBeenCalledWith({ - url: `https://example.com/api/v3/notifications/threads/${mockThreadId}`, + url: `https://github.gitify.io/api/v3/notifications/threads/${mockThreadId}`, headers: { Accept: 'application/json', Authorization: 'token decrypted', @@ -214,11 +209,7 @@ describe('renderer/utils/api/client.ts', () => { describe('markNotificationThreadAsDone', () => { it('should mark notification thread as done - github', async () => { - await markNotificationThreadAsDone( - mockThreadId, - mockGitHubHostname, - mockToken, - ); + await markNotificationThreadAsDone(mockGitHubCloudAccount, mockThreadId); expect(axios).toHaveBeenCalledWith({ url: `https://api.github.com/notifications/threads/${mockThreadId}`, @@ -235,9 +226,8 @@ describe('renderer/utils/api/client.ts', () => { it('should mark notification thread as done - enterprise', async () => { await markNotificationThreadAsDone( + mockGitHubEnterpriseServerAccount, mockThreadId, - mockEnterpriseHostname, - mockToken, ); expect(axios).toHaveBeenCalledWith({ @@ -257,9 +247,8 @@ describe('renderer/utils/api/client.ts', () => { describe('ignoreNotificationThreadSubscription', () => { it('should ignore notification thread subscription - github', async () => { await ignoreNotificationThreadSubscription( + mockGitHubCloudAccount, mockThreadId, - mockGitHubHostname, - mockToken, ); expect(axios).toHaveBeenCalledWith({ @@ -277,13 +266,12 @@ describe('renderer/utils/api/client.ts', () => { it('should ignore notification thread subscription - enterprise', async () => { await ignoreNotificationThreadSubscription( + mockGitHubEnterpriseServerAccount, mockThreadId, - mockEnterpriseHostname, - mockToken, ); expect(axios).toHaveBeenCalledWith({ - url: `https://example.com/api/v3/notifications/threads/${mockThreadId}/subscription`, + url: `https://github.gitify.io/api/v3/notifications/threads/${mockThreadId}/subscription`, headers: { Accept: 'application/json', Authorization: 'token decrypted', @@ -312,8 +300,8 @@ describe('renderer/utils/api/client.ts', () => { apiRequestAuthMock.mockResolvedValue(requestPromise); const result = await getHtmlUrl( + mockGitHubCloudAccount, 'https://api.github.com/repos/gitify-app/notifications-test/issues/785' as Link, - '123' as Token, ); expect(result).toBe( 'https://github.com/gitify-app/notifications-test/issues/785', @@ -332,8 +320,8 @@ describe('renderer/utils/api/client.ts', () => { apiRequestAuthMock.mockRejectedValue(mockError); await getHtmlUrl( + mockGitHubCloudAccount, 'https://api.github.com/repos/gitify-app/gitify/issues/785' as Link, - '123' as Token, ); expect(rendererLogErrorSpy).toHaveBeenCalledTimes(1); diff --git a/src/renderer/utils/api/client.ts b/src/renderer/utils/api/client.ts index 5b09beae9..d7a8d491d 100644 --- a/src/renderer/utils/api/client.ts +++ b/src/renderer/utils/api/client.ts @@ -35,13 +35,12 @@ import { getGitHubAPIBaseUrl, getGitHubGraphQLUrl } from './utils'; * Endpoint documentation: https://docs.github.com/en/rest/users/users#get-the-authenticated-user */ export function getAuthenticatedUser( - hostname: Hostname, - token: Token, + account: Account, ): AxiosPromise { - const url = getGitHubAPIBaseUrl(hostname); + const url = getGitHubAPIBaseUrl(account.hostname); url.pathname += 'user'; - return apiRequestAuth(url.toString() as Link, 'GET', token); + return apiRequestAuth(url.toString() as Link, 'GET', account); } /** @@ -56,7 +55,12 @@ export function headNotifications( const url = getGitHubAPIBaseUrl(hostname); url.pathname += 'notifications'; - return apiRequestAuth(url.toString() as Link, 'HEAD', token); + const tmpAccount: Account = { + hostname, + token, + } as Partial as Account; + + return apiRequestAuth(url.toString() as Link, 'HEAD', tmpAccount); } /** @@ -71,10 +75,11 @@ export function listNotificationsForAuthenticatedUser( const url = getGitHubAPIBaseUrl(account.hostname); url.pathname += 'notifications'; url.searchParams.append('participating', String(settings.participating)); + return apiRequestAuth( url.toString() as Link, 'GET', - account.token, + account, {}, settings.fetchAllNotifications, ); @@ -87,14 +92,13 @@ export function listNotificationsForAuthenticatedUser( * Endpoint documentation: https://docs.github.com/en/rest/activity/notifications#mark-a-thread-as-read */ export function markNotificationThreadAsRead( + account: Account, threadId: string, - hostname: Hostname, - token: Token, ): AxiosPromise { - const url = getGitHubAPIBaseUrl(hostname); + const url = getGitHubAPIBaseUrl(account.hostname); url.pathname += `notifications/threads/${threadId}`; - return apiRequestAuth(url.toString() as Link, 'PATCH', token, {}); + return apiRequestAuth(url.toString() as Link, 'PATCH', account, {}, false); } /** @@ -106,13 +110,13 @@ export function markNotificationThreadAsRead( * Endpoint documentation: https://docs.github.com/en/rest/activity/notifications#mark-a-thread-as-done */ export function markNotificationThreadAsDone( + account: Account, threadId: string, - hostname: Hostname, - token: Token, ): AxiosPromise { - const url = getGitHubAPIBaseUrl(hostname); + const url = getGitHubAPIBaseUrl(account.hostname); url.pathname += `notifications/threads/${threadId}`; - return apiRequestAuth(url.toString() as Link, 'DELETE', token, {}); + + return apiRequestAuth(url.toString() as Link, 'DELETE', account, {}, false); } /** @@ -121,16 +125,21 @@ export function markNotificationThreadAsDone( * Endpoint documentation: https://docs.github.com/en/rest/activity/notifications#delete-a-thread-subscription */ export function ignoreNotificationThreadSubscription( + account: Account, threadId: string, - hostname: Hostname, - token: Token, ): AxiosPromise { - const url = getGitHubAPIBaseUrl(hostname); + const url = getGitHubAPIBaseUrl(account.hostname); url.pathname += `notifications/threads/${threadId}/subscription`; - return apiRequestAuth(url.toString() as Link, 'PUT', token, { - ignored: true, - }); + return apiRequestAuth( + url.toString() as Link, + 'PUT', + account, + { + ignored: true, + }, + false, + ); } /** @@ -138,8 +147,8 @@ export function ignoreNotificationThreadSubscription( * * Endpoint documentation: https://docs.github.com/en/rest/commits/commits#get-a-commit */ -export function getCommit(url: Link, token: Token): AxiosPromise { - return apiRequestAuth(url, 'GET', token); +export function getCommit(account: Account, url: Link): AxiosPromise { + return apiRequestAuth(url, 'GET', account, {}, false); } /** @@ -149,10 +158,10 @@ export function getCommit(url: Link, token: Token): AxiosPromise { */ export function getCommitComment( + account: Account, url: Link, - token: Token, ): AxiosPromise { - return apiRequestAuth(url, 'GET', token); + return apiRequestAuth(url, 'GET', account, {}, false); } /** @@ -160,8 +169,8 @@ export function getCommitComment( * * Endpoint documentation: https://docs.github.com/en/rest/issues/issues#get-an-issue */ -export function getIssue(url: Link, token: Token): AxiosPromise { - return apiRequestAuth(url, 'GET', token); +export function getIssue(account: Account, url: Link): AxiosPromise { + return apiRequestAuth(url, 'GET', account, {}, false); } /** @@ -171,10 +180,10 @@ export function getIssue(url: Link, token: Token): AxiosPromise { * Endpoint documentation: https://docs.github.com/en/rest/issues/comments#get-an-issue-comment */ export function getIssueOrPullRequestComment( + account: Account, url: Link, - token: Token, ): AxiosPromise { - return apiRequestAuth(url, 'GET', token); + return apiRequestAuth(url, 'GET', account, {}, false); } /** @@ -183,10 +192,10 @@ export function getIssueOrPullRequestComment( * Endpoint documentation: https://docs.github.com/en/rest/pulls/pulls#get-a-pull-request */ export function getPullRequest( + account: Account, url: Link, - token: Token, ): AxiosPromise { - return apiRequestAuth(url, 'GET', token); + return apiRequestAuth(url, 'GET', account, {}, false); } /** @@ -195,10 +204,10 @@ export function getPullRequest( * Endpoint documentation: https://docs.github.com/en/rest/pulls/reviews#list-reviews-for-a-pull-request */ export function getPullRequestReviews( + account: Account, url: Link, - token: Token, ): AxiosPromise { - return apiRequestAuth(url, 'GET', token); + return apiRequestAuth(url, 'GET', account, {}, false); } /** @@ -206,16 +215,17 @@ export function getPullRequestReviews( * * Endpoint documentation: https://docs.github.com/en/rest/releases/releases#get-a-release */ -export function getRelease(url: Link, token: Token): AxiosPromise { - return apiRequestAuth(url, 'GET', token); +export function getRelease(account: Account, url: Link): AxiosPromise { + return apiRequestAuth(url, 'GET', account); } /** * Get the `html_url` from the GitHub response */ -export async function getHtmlUrl(url: Link, token: Token): Promise { +export async function getHtmlUrl(account: Account, url: Link): Promise { try { - const response = (await apiRequestAuth(url, 'GET', token)).data; + const response = (await apiRequestAuth(url, 'GET', account, {}, false)) + .data; return response.html_url; } catch (err) { rendererLogError( @@ -239,7 +249,7 @@ export async function searchDiscussions( return apiRequestAuth( url.toString() as Link, 'POST', - notification.account.token, + notification.account, { query: print(QUERY_SEARCH_DISCUSSIONS), variables: { @@ -256,6 +266,7 @@ export async function searchDiscussions( ), }, }, + false, ); } diff --git a/src/renderer/utils/api/request.test.ts b/src/renderer/utils/api/request.test.ts index c759c6b1e..450f251bb 100644 --- a/src/renderer/utils/api/request.test.ts +++ b/src/renderer/utils/api/request.test.ts @@ -1,6 +1,7 @@ import axios from 'axios'; -import type { Link, Token } from '../../types'; +import { mockGitHubCloudAccount } from '../../__mocks__/state-mocks'; +import type { Link } from '../../types'; import { apiRequest, apiRequestAuth } from './request'; jest.mock('axios'); @@ -48,8 +49,6 @@ describe('renderer/utils/api/request.ts', () => { }); describe('apiRequestAuth', () => { - const token = 'yourAuthToken' as Token; - afterEach(() => { jest.clearAllMocks(); }); @@ -57,7 +56,7 @@ describe('apiRequestAuth', () => { it('should make an authenticated request with the correct parameters', async () => { const data = { key: 'value' }; - await apiRequestAuth(url, method, token, data); + await apiRequestAuth(url, method, mockGitHubCloudAccount, data); expect(axios).toHaveBeenCalledWith({ method, @@ -75,7 +74,7 @@ describe('apiRequestAuth', () => { it('should make an authenticated request with the correct parameters and default data', async () => { const data = {}; - await apiRequestAuth(url, method, token); + await apiRequestAuth(url, method, mockGitHubCloudAccount); expect(axios).toHaveBeenCalledWith({ method, diff --git a/src/renderer/utils/api/request.ts b/src/renderer/utils/api/request.ts index a3d445015..ace30bb48 100644 --- a/src/renderer/utils/api/request.ts +++ b/src/renderer/utils/api/request.ts @@ -1,14 +1,40 @@ -import axios, { +import Axios, { type AxiosPromise, + type AxiosRequestConfig, type AxiosResponse, type Method, } from 'axios'; +import { buildKeyGenerator, setupCache } from 'axios-cache-interceptor'; -import type { Link, Token } from '../../types'; +import type { Account, Link, Token } from '../../types'; import { decryptValue } from '../comms'; import { rendererLogError } from '../logger'; import { getNextURLFromLinkHeader } from './utils'; +type AxiosRequestConfigWithAccount = AxiosRequestConfig & { account: Account }; + +const instance = Axios.create(); +const axios = setupCache(instance, { + location: 'client', + + interpretHeader: true, + + methods: ['get'], + + cachePredicate: { + ignoreUrls: ['login/oauth/access_token', 'notifications'], + }, + + // Generate unique cache keys per account to prevent cross-account pollution + generateKey: buildKeyGenerator((request: AxiosRequestConfigWithAccount) => { + return { + method: request.method, + url: request.url, + custom: request.account.user.id, + }; + }), +}); + /** * Perform an unauthenticated API request * @@ -22,7 +48,7 @@ export async function apiRequest( method: Method, data = {}, ): Promise { - const headers = await getHeaders(url); + const headers = await getHeaders(); return axios({ method, url, data, headers }); } @@ -32,7 +58,7 @@ export async function apiRequest( * * @param url * @param method - * @param token + * @param account * @param data * @param fetchAllRecords whether to fetch all records or just the first page * @returns @@ -40,31 +66,43 @@ export async function apiRequest( export async function apiRequestAuth( url: Link, method: Method, - token: Token, - data = {}, + account: Account, + data: Record = {}, fetchAllRecords = false, ): AxiosPromise | null { - const headers = await getHeaders(url, token); + const headers = await getHeaders(account.token); + + const baseConfig: AxiosRequestConfigWithAccount = { + method, + url, + data, + headers, + account, + }; if (!fetchAllRecords) { - return axios({ method, url, data, headers }); + return axios(baseConfig); } let response: AxiosResponse | null = null; - let combinedData = []; + let combinedData: unknown[] = []; try { let nextUrl: string | null = url; while (nextUrl) { - response = await axios({ method, url: nextUrl, data, headers }); + const cfg: AxiosRequestConfigWithAccount = { + ...baseConfig, + url: nextUrl, + }; + response = await axios(cfg); // If no data is returned, break the loop if (!response?.data) { break; } - combinedData = combinedData.concat(response.data); // Accumulate data + combinedData = combinedData.concat(response.data as unknown[]); // Accumulate data nextUrl = getNextURLFromLinkHeader(response); } @@ -80,37 +118,18 @@ export async function apiRequestAuth( } as AxiosResponse; } -/** - * Return true if the request should be made with no-cache - * - * @param url - * @returns boolean - */ -function shouldRequestWithNoCache(url: string) { - const parsedUrl = new URL(url); - - switch (parsedUrl.pathname) { - case '/api/v3/notifications': - case '/login/oauth/access_token': - case '/notifications': - return true; - default: - return false; - } -} - /** * Construct headers for API requests * - * @param username * @param token + * @param skipCache - If true, adds cache-control: no-cache to force fresh data * @returns */ -async function getHeaders(url: Link, token?: Token) { +async function getHeaders(token?: Token) { const headers: Record = { Accept: 'application/json', - 'Cache-Control': shouldRequestWithNoCache(url) ? 'no-cache' : '', 'Content-Type': 'application/json', + 'Cache-Control': 'no-cache', }; if (token) { diff --git a/src/renderer/utils/auth/utils.ts b/src/renderer/utils/auth/utils.ts index 42f59aeef..f825eca07 100644 --- a/src/renderer/utils/auth/utils.ts +++ b/src/renderer/utils/auth/utils.ts @@ -9,12 +9,10 @@ import type { AuthCode, AuthState, ClientID, - GitifyUser, Hostname, Link, Token, } from '../../types'; -import type { UserDetails } from '../../typesGitHub'; import { getAuthenticatedUser } from '../api/client'; import { apiRequest } from '../api/request'; import { encryptValue, openExternalLink } from '../comms'; @@ -73,21 +71,6 @@ export function authGitHub( }); } -export async function getUserData( - token: Token, - hostname: Hostname, -): Promise { - const response: UserDetails = (await getAuthenticatedUser(hostname, token)) - .data; - - return { - id: response.id, - login: response.login, - name: response.name, - avatar: response.avatar_url, - }; -} - export async function getToken( authCode: AuthCode, authOptions = Constants.DEFAULT_AUTH_OPTIONS, @@ -156,7 +139,7 @@ export function removeAccount(auth: AuthState, account: Account): AuthState { export async function refreshAccount(account: Account): Promise { try { - const res = await getAuthenticatedUser(account.hostname, account.token); + const res = await getAuthenticatedUser(account); // Refresh user data account.user = { diff --git a/src/renderer/utils/helpers.ts b/src/renderer/utils/helpers.ts index b00facda3..61c706854 100644 --- a/src/renderer/utils/helpers.ts +++ b/src/renderer/utils/helpers.ts @@ -117,13 +117,13 @@ export async function generateGitHubWebUrl( try { if (notification.subject.latest_comment_url) { url.href = await getHtmlUrl( + notification.account, notification.subject.latest_comment_url, - notification.account.token, ); } else if (notification.subject.url) { url.href = await getHtmlUrl( + notification.account, notification.subject.url, - notification.account.token, ); } else { // Perform any specific notification type handling (only required for a few special notification scenarios) diff --git a/src/renderer/utils/notifications/handlers/commit.ts b/src/renderer/utils/notifications/handlers/commit.ts index 882134f03..753d08910 100644 --- a/src/renderer/utils/notifications/handlers/commit.ts +++ b/src/renderer/utils/notifications/handlers/commit.ts @@ -35,15 +35,15 @@ class CommitHandler extends DefaultHandler { if (notification.subject.latest_comment_url) { const commitComment = ( await getCommitComment( + notification.account, notification.subject.latest_comment_url, - notification.account.token, ) ).data; user = commitComment.user; } else { const commit = ( - await getCommit(notification.subject.url, notification.account.token) + await getCommit(notification.account, notification.subject.url) ).data; user = commit.author; diff --git a/src/renderer/utils/notifications/handlers/issue.ts b/src/renderer/utils/notifications/handlers/issue.ts index 74b637aad..a95139934 100644 --- a/src/renderer/utils/notifications/handlers/issue.ts +++ b/src/renderer/utils/notifications/handlers/issue.ts @@ -29,7 +29,7 @@ class IssueHandler extends DefaultHandler { settings: SettingsState, ): Promise { const issue = ( - await getIssue(notification.subject.url, notification.account.token) + await getIssue(notification.account, notification.subject.url) ).data; const issueState = issue.state_reason ?? issue.state; @@ -44,8 +44,8 @@ class IssueHandler extends DefaultHandler { if (notification.subject.latest_comment_url) { const issueComment = ( await getIssueOrPullRequestComment( + notification.account, notification.subject.latest_comment_url, - notification.account.token, ) ).data; issueCommentUser = issueComment.user; diff --git a/src/renderer/utils/notifications/handlers/pullRequest.ts b/src/renderer/utils/notifications/handlers/pullRequest.ts index f58eccd98..05c292e0c 100644 --- a/src/renderer/utils/notifications/handlers/pullRequest.ts +++ b/src/renderer/utils/notifications/handlers/pullRequest.ts @@ -36,7 +36,7 @@ class PullRequestHandler extends DefaultHandler { settings: SettingsState, ): Promise { const pr = ( - await getPullRequest(notification.subject.url, notification.account.token) + await getPullRequest(notification.account, notification.subject.url) ).data; let prState: PullRequestStateType = pr.state; @@ -58,8 +58,8 @@ class PullRequestHandler extends DefaultHandler { ) { const prComment = ( await getIssueOrPullRequestComment( + notification.account, notification.subject.latest_comment_url, - notification.account.token, ) ).data; prCommentUser = prComment.user; @@ -111,8 +111,8 @@ export async function getLatestReviewForReviewers( } const prReviews = await getPullRequestReviews( + notification.account, `${notification.subject.url}/reviews` as Link, - notification.account.token, ); if (!prReviews.data.length) { diff --git a/src/renderer/utils/notifications/handlers/release.ts b/src/renderer/utils/notifications/handlers/release.ts index 9caadc1a7..1a59525c2 100644 --- a/src/renderer/utils/notifications/handlers/release.ts +++ b/src/renderer/utils/notifications/handlers/release.ts @@ -30,7 +30,7 @@ class ReleaseHandler extends DefaultHandler { } const release = ( - await getRelease(notification.subject.url, notification.account.token) + await getRelease(notification.account, notification.subject.url) ).data; return {