7 changed files with 177 additions and 120 deletions
@ -1,55 +1,95 @@ |
|||
'use strict'; |
|||
|
|||
const isBefore = require('date-fns/is_before'); |
|||
const differenceInMilliseconds = require('date-fns/difference_in_milliseconds'); |
|||
const addSeconds = require('date-fns/add_seconds'); |
|||
const isBefore = require('date-fns/isBefore'); |
|||
const differenceInMilliseconds = require('date-fns/differenceInMilliseconds'); |
|||
const addSeconds = require('date-fns/addSeconds'); |
|||
const parse = require('parse-headers'); |
|||
|
|||
// Returns boolean for whether or not the cache has expired
|
|||
const expired = (headers, date) => isBefore(expired.on(headers), (date || new Date())); |
|||
|
|||
// Return ms until cache expires
|
|||
expired.in = (headers, date) => differenceInMilliseconds(expired.on(headers), (date || new Date())); |
|||
const maxAgeRegex = /max-age=(\d+)/; |
|||
const noCacheRegex = /(no-cache)|(no-store)/; |
|||
|
|||
// Returns date when cache will expire
|
|||
expired.on = headers => { |
|||
const getExpirationDate = headers => { |
|||
// Check we have headers
|
|||
if (!headers) { |
|||
throw new Error('Headers argument is missing'); |
|||
throw new Error('Missing required argument "headers"'); |
|||
} |
|||
|
|||
const { |
|||
date: dateHeader, |
|||
age: ageHeader, |
|||
'cache-control': cacheControlHeader, |
|||
expires: expiresHeader |
|||
} = typeof headers === 'string' ? parse(headers) : headers; |
|||
|
|||
// Confirm we have date header
|
|||
if (typeof dateHeader !== 'string') { |
|||
throw new TypeError('Missing required header "Date"'); |
|||
} |
|||
|
|||
// Parse headers if we got a raw string
|
|||
headers = (typeof headers === 'string') ? parse(headers) : headers; |
|||
if (typeof cacheControlHeader === 'string') { |
|||
// Prioritize no-cache and no-store
|
|||
const noCacheMatches = cacheControlHeader.match(noCacheRegex); |
|||
if (noCacheMatches) { |
|||
return new Date(dateHeader); |
|||
} |
|||
|
|||
// Prioritize Cache-Control with max-age header
|
|||
const maxAgeMatches = cacheControlHeader.match(maxAgeRegex); |
|||
if (maxAgeMatches) { |
|||
const maxAge = Number.parseInt(maxAgeMatches ? maxAgeMatches[1] : 0, 10); |
|||
if (typeof ageHeader === 'number') { |
|||
return addSeconds(new Date(dateHeader), maxAge - ageHeader); |
|||
} |
|||
|
|||
// Check we have date header
|
|||
if (!headers.date) { |
|||
throw new Error('Date header is missing'); |
|||
return addSeconds(new Date(dateHeader), maxAge); |
|||
} |
|||
} |
|||
|
|||
// Default to Date header
|
|||
let expiredOn = new Date(headers.date); |
|||
if (expiresHeader) { |
|||
return new Date(expiresHeader); |
|||
} |
|||
|
|||
// Return expiry dateHeader
|
|||
return new Date(dateHeader); |
|||
}; |
|||
|
|||
// Returns boolean for whether or not the cache has expired
|
|||
const expired = (headers, date) => { |
|||
if (date) { |
|||
if (date instanceof Date) { |
|||
return isBefore(expired.on(headers), date); |
|||
} |
|||
|
|||
if (typeof date === 'string') { |
|||
return isBefore(expired.on(headers), new Date(date)); |
|||
} |
|||
|
|||
throw new Error(`Optional argument "date" must be a string or Date object, found ${typeof date}`); |
|||
} |
|||
|
|||
// Prefer Cache-Control
|
|||
if (headers['cache-control']) { |
|||
// Get max age ms
|
|||
let maxAge = headers['cache-control'].match(/max-age=(\d+)/); |
|||
maxAge = parseInt(maxAge ? maxAge[1] : 0, 10); |
|||
return isBefore(expired.on(headers), new Date()); |
|||
}; |
|||
|
|||
// Take current age into account
|
|||
if (headers.age) { |
|||
maxAge -= headers.age; |
|||
// Return ms until cache expires
|
|||
expired.in = (headers, date) => { |
|||
if (date) { |
|||
if (date instanceof Date) { |
|||
return differenceInMilliseconds(expired.on(headers), date); |
|||
} |
|||
|
|||
// Calculate expiry date
|
|||
expiredOn = addSeconds(expiredOn, maxAge); |
|||
if (typeof date === 'string') { |
|||
return differenceInMilliseconds(expired.on(headers), new Date(date)); |
|||
} |
|||
|
|||
// Fall back to Expires if it exists
|
|||
} else if (headers.expires) { |
|||
expiredOn = new Date(headers.expires); |
|||
throw new Error(`Optional argument "date" must be a string or Date object, found ${typeof date}`); |
|||
} |
|||
|
|||
// Return expiry date
|
|||
return expiredOn; |
|||
return differenceInMilliseconds(expired.on(headers), new Date()); |
|||
}; |
|||
|
|||
// Returns date when cache will expire
|
|||
expired.on = headers => { |
|||
return getExpirationDate(headers); |
|||
}; |
|||
|
|||
module.exports = expired; |
|||
|
@ -1,40 +1,39 @@ |
|||
import test from 'ava'; |
|||
import subSeconds from 'date-fns/sub_seconds'; |
|||
import addSeconds from 'date-fns/add_seconds'; |
|||
import expired from '../'; |
|||
const test = require('ava'); |
|||
const subSeconds = require('date-fns/subSeconds'); |
|||
const addSeconds = require('date-fns/addSeconds'); |
|||
const expired = require('..'); |
|||
|
|||
test('expired is a function', t => { |
|||
t.is(typeof expired, 'function'); |
|||
}); |
|||
|
|||
test('expired returns false for valid cache', t => { |
|||
const date = new Date(new Date().toUTCString()); |
|||
const headers = { |
|||
date: new Date().toUTCString(), |
|||
date: date.toUTCString(), |
|||
age: 0, |
|||
'cache-control': 'public, max-age=300' |
|||
}; |
|||
|
|||
t.false(expired(headers)); |
|||
}); |
|||
|
|||
test('expired returns true for stale cache', t => { |
|||
const date = new Date(new Date().toUTCString()); |
|||
const headers = { |
|||
date: subSeconds(new Date(), 500).toUTCString(), |
|||
age: 0, |
|||
'cache-control': 'public, max-age=300' |
|||
}; |
|||
|
|||
t.true(expired(headers)); |
|||
t.true(expired(headers, date)); |
|||
}); |
|||
|
|||
test('expired accepts currentDate argument', t => { |
|||
const date = new Date(); |
|||
const date = new Date(new Date().toUTCString()); |
|||
const headers = { |
|||
date: date.toUTCString(), |
|||
age: 0, |
|||
'cache-control': 'public, max-age=300' |
|||
}; |
|||
|
|||
t.false(expired(headers, date)); |
|||
t.true(expired(headers, addSeconds(date, 500))); |
|||
}); |
|||
|
@ -1,54 +1,43 @@ |
|||
import test from 'ava'; |
|||
import tk from 'timekeeper'; |
|||
import isEqual from 'date-fns/is_equal'; |
|||
import expired from '../'; |
|||
const test = require('ava'); |
|||
const isEqual = require('date-fns/isEqual'); |
|||
const expired = require('..'); |
|||
|
|||
test('throw error if header argument is missing', t => { |
|||
const err = t.throws(() => expired()); |
|||
t.is(err.message, 'Headers argument is missing'); |
|||
const error = t.throws(() => expired()); |
|||
t.is(error.message, 'Missing required argument "headers"'); |
|||
}); |
|||
|
|||
test('throw error if Date header is missing', t => { |
|||
const headers = {}; |
|||
|
|||
const err = t.throws(() => expired(headers)); |
|||
t.is(err.message, 'Date header is missing'); |
|||
const error = t.throws(() => expired(headers)); |
|||
t.is(error.message, 'Missing required header "Date"'); |
|||
}); |
|||
|
|||
test('headers can be passed in as an object', t => { |
|||
const date = new Date().toUTCString(); |
|||
const date = new Date(new Date().toUTCString()); |
|||
const headers = { |
|||
date, |
|||
date: date.toUTCString(), |
|||
age: 0, |
|||
'cache-control': `public, max-age=0` |
|||
'cache-control': 'public, max-age=0' |
|||
}; |
|||
|
|||
tk.freeze(date); |
|||
t.true(isEqual(expired.on(headers), date)); |
|||
tk.reset(); |
|||
}); |
|||
|
|||
test('headers can be passed in as a string', t => { |
|||
const date = new Date().toUTCString(); |
|||
const date = new Date(new Date().toUTCString()); |
|||
const headers = ` |
|||
Date: ${date} |
|||
Date: ${date.toUTCString()} |
|||
Age: 0 |
|||
Cache-Control public, max-age=0`;
|
|||
|
|||
tk.freeze(date); |
|||
t.true(isEqual(expired.on(headers), date)); |
|||
tk.reset(); |
|||
}); |
|||
|
|||
test('headers can contain status code', t => { |
|||
const date = new Date().toUTCString(); |
|||
const date = new Date(new Date().toUTCString()); |
|||
const headers = ` |
|||
HTTP/1.1 200 OK |
|||
Date: ${date} |
|||
Date: ${date.toUTCString()} |
|||
Age: 0 |
|||
Cache-Control public, max-age=0`;
|
|||
|
|||
tk.freeze(date); |
|||
t.true(isEqual(expired.on(headers), date)); |
|||
tk.reset(); |
|||
}); |
|||
|
Loading…
Reference in new issue