diff --git a/src/header/interpreter.ts b/src/header/interpreter.ts index 8e2fd22..80bbd92 100644 --- a/src/header/interpreter.ts +++ b/src/header/interpreter.ts @@ -2,16 +2,30 @@ import { parse } from '@tusbar/cache-control'; import { HeaderInterpreter } from './types'; export const defaultHeaderInterpreter: HeaderInterpreter = (headers) => { - const cacheControl = headers?.['cache-control']; + const cacheControl = headers?.['Cache-Control']; if (!cacheControl) { + // Checks if Expires header is present + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Expires + const expires = headers?.['Expires']; + + if (expires) { + const milliseconds = Date.parse(expires) - Date.now(); + + if (milliseconds > 0) { + return milliseconds; + } else { + return false; + } + } + return undefined; } - const { noCache, noStore, maxAge } = parse(cacheControl); + const { noCache, noStore, mustRevalidate, maxAge } = parse(cacheControl); // Header told that this response should not be cached. - if (noCache || noStore) { + if (noCache || noStore || mustRevalidate) { return false; } @@ -19,5 +33,5 @@ export const defaultHeaderInterpreter: HeaderInterpreter = (headers) => { return undefined; } - return Date.now() + maxAge * 1000; + return maxAge * 1000; }; diff --git a/src/header/types.ts b/src/header/types.ts index 8f57777..11012f2 100644 --- a/src/header/types.ts +++ b/src/header/types.ts @@ -5,8 +5,6 @@ * * @returns `false` if cache should not be used. `undefined` when provided * headers was not enough to determine a valid value. Or a `number` containing - * the number of **seconds** to cache the response. + * the number of **milliseconds** to cache the response. */ -export type HeaderInterpreter = ( - headers: Record | undefined -) => false | undefined | number; +export type HeaderInterpreter = (headers?: Record) => false | undefined | number; diff --git a/test/header/interpreter.test.ts b/test/header/interpreter.test.ts new file mode 100644 index 0000000..fda4f46 --- /dev/null +++ b/test/header/interpreter.test.ts @@ -0,0 +1,71 @@ +import { defaultHeaderInterpreter } from '../../src/header'; + +describe('tests header interpreter', () => { + it('tests without cache-control header', () => { + const noHeader = defaultHeaderInterpreter({}); + expect(noHeader).toBeUndefined(); + + const emptyHeader = defaultHeaderInterpreter({ 'Cache-Control': 'public' }); + expect(emptyHeader).toBeUndefined(); + }); + + it('tests with cache preventing headers', () => { + const noStore = defaultHeaderInterpreter({ + 'Cache-Control': 'no-store' + }); + + expect(noStore).toBe(false); + + const noCache = defaultHeaderInterpreter({ + 'Cache-Control': 'no-cache' + }); + + expect(noCache).toBe(false); + + const mustRevalidate = defaultHeaderInterpreter({ + 'Cache-Control': 'must-revalidate' + }); + + expect(mustRevalidate).toBe(false); + }); + + it('tests with maxAge header for 10 seconds', () => { + const result = defaultHeaderInterpreter({ + 'Cache-Control': 'max-age=10' + }); + + // 10 Seconds in milliseconds + expect(result).toBe(10 * 1000); + }); + + it('tests with Expires and Cache-Control present', () => { + const result = defaultHeaderInterpreter({ + 'Cache-Control': 'max-age=10', + Expires: new Date(new Date().getFullYear() + 1, 1, 1).toISOString() + }); + + // Expires should be ignored + // 10 Seconds in milliseconds + expect(result).toBe(10 * 1000); + }); + + it('tests with past Expires', () => { + const result = defaultHeaderInterpreter({ + Expires: new Date(new Date().getFullYear() - 1, 1, 1).toISOString() + }); + + // Past means cache invalid + expect(result).toBe(false); + }); + + it('tests with future Expires', () => { + const date = new Date(new Date().getFullYear() + 1, 1, 1); + const result = defaultHeaderInterpreter({ + Expires: date.toISOString() + }); + + // the result should be what the date is in milliseconds + // minus the actual epoch milliseconds + expect(result).toBeCloseTo(date.getTime() - Date.now()); + }); +});