diff --git a/package-lock.json b/package-lock.json index e80b49369..c20922abd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,6 +24,7 @@ "bcrypt": "^5.1.0", "bluebird": "^3.7.2", "body-parser": "^1.19.2", + "cache-parser": "^1.2.4", "cloudmersive-virus-api-client": "^1.2.7", "connect-session-sequelize": "^7.1.5", "convict": "^6.2.4", diff --git a/package.json b/package.json index 8df028594..b8951df1f 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ "bcrypt": "^5.1.0", "bluebird": "^3.7.2", "body-parser": "^1.19.2", + "cache-parser": "^1.2.4", "cloudmersive-virus-api-client": "^1.2.7", "connect-session-sequelize": "^7.1.5", "convict": "^6.2.4", diff --git a/src/services/api/AxiosInstance.ts b/src/services/api/AxiosInstance.ts index 6e3cb6aa4..d2ca1d859 100644 --- a/src/services/api/AxiosInstance.ts +++ b/src/services/api/AxiosInstance.ts @@ -8,6 +8,8 @@ import logger from "@logger/logger" import { getAccessToken } from "@utils/token-retrieval-utils" import tracer from "@utils/tracer" +import { customHeaderInterpreter } from "@root/utils/headerInterpreter" + // Env vars const GITHUB_ORG_NAME = config.get("github.orgName") @@ -27,6 +29,7 @@ const requestFormatter = async (axiosConfig: AxiosRequestConfig) => { if (isEmailLoginUser) { const accessToken = await getAccessToken() axiosConfig.headers = { + ...(axiosConfig.headers ?? {}), Authorization: `token ${accessToken}`, } tracer.use("http", { @@ -79,6 +82,7 @@ const isomerRepoAxiosInstance = setupCache( { interpretHeader: true, etag: true, + headerInterpreter: customHeaderInterpreter, } ) isomerRepoAxiosInstance.interceptors.request.use(requestFormatter) diff --git a/src/utils/headerInterpreter.ts b/src/utils/headerInterpreter.ts new file mode 100644 index 000000000..703c28088 --- /dev/null +++ b/src/utils/headerInterpreter.ts @@ -0,0 +1,47 @@ +import { Header, HeadersInterpreter } from "axios-cache-interceptor" +import { parse } from "cache-parser" + +// NOTE: Taken with reference to https://github.com/arthurfiorette/axios-cache-interceptor/blob/v0.9.2/src/header/interpreter.ts +// The change made is to remove the `maxAge` condition, which is not suitable because +// we want to revalidate on update. +// An alternative is to invalidate the cache on `update` but +// this requires an exhaustive search through our codebase. +// This incurs an extra network call on every update, which is not ideal but in 304 +// this is an empty request. +// eslint-disable-next-line import/prefer-default-export +export const customHeaderInterpreter: HeadersInterpreter = (headers) => { + if (!headers) return "not enough headers" + + const cacheControl = headers[Header.CacheControl] + + if (cacheControl) { + const { noCache, noStore, mustRevalidate, maxAge, immutable } = parse( + String(cacheControl) + ) + + // Header told that this response should not be cached. + if (noCache || noStore) { + return "dont cache" + } + + if (immutable) { + // 1 year is sufficient, as Infinity may cause more problems. + // It might not be the best way, but a year is better than none. + return 1000 * 60 * 60 * 24 * 365 + } + + // Already out of date, for cache can be saved, but must be requested again + if (mustRevalidate || maxAge) { + return 0 + } + } + + const expires = headers[Header.Expires] + + if (expires) { + const milliseconds = Date.parse(String(expires)) - Date.now() + return milliseconds >= 0 ? milliseconds : "dont cache" + } + + return "not enough headers" +}