Browse Source

Use expires when cache-control is present without max-age. Use no-cache and no-store.

Update dependencies.

Add line break to end of .gitignore
pull/33/head
John Wehr 4 years ago
parent
commit
bbdeabd972
  1. 1
      .gitignore
  2. 18
      package.json
  3. 106
      src/index.js
  4. 35
      test/expired.in.js
  5. 19
      test/expired.js
  6. 79
      test/expired.on.js
  7. 39
      test/headers.js

1
.gitignore

@ -70,3 +70,4 @@ Icon
Network Trash Folder
Temporary Items
.apdisk
yarn.lock

18
package.json

@ -1,11 +1,11 @@
{
"name": "expired",
"version": "1.3.12",
"version": "1.4.0",
"description": "Calculate when HTTP responses expire from the cache headers",
"main": "src/index.js",
"scripts": {
"test": "nyc ava",
"lint": "xo",
"lint": "xo --fix",
"coverage": "nyc report --reporter=text-lcov | coveralls"
},
"repository": {
@ -30,15 +30,13 @@
},
"homepage": "https://github.com/lukechilds/expired",
"dependencies": {
"date-fns": "1.28.5",
"parse-headers": "2.0.1"
"date-fns": "2.19.0",
"parse-headers": "2.0.3"
},
"devDependencies": {
"ava": "^0.25.0",
"coveralls": "^3.0.0",
"date-fns": "^1.28.3",
"nyc": "^11.0.1",
"timekeeper": "^2.0.0",
"xo": "^0.19.0"
"ava": "0.25.0",
"coveralls": "3.1.0",
"nyc": "15.1.0",
"xo": "0.38.2"
}
}

106
src/index.js

@ -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;

35
test/expired.in.js

@ -1,43 +1,36 @@
import test from 'ava';
import tk from 'timekeeper';
import addSeconds from 'date-fns/add_seconds';
import expired from '../';
const test = require('ava');
const addSeconds = require('date-fns/addSeconds');
const expired = require('..');
test('expired.in is a function', t => {
t.is(typeof expired.in, 'function');
});
test('expired.in returns positive ms for valid cache', t => {
const date = new Date().toUTCString();
const date = new Date(new Date().toUTCString());
const maxAge = 300;
const headers = {
date,
date: date.toUTCString(),
age: 0,
'cache-control': `public, max-age=${maxAge}`
};
const expiredIn = maxAge * 1000;
tk.freeze(date);
t.is(expired.in(headers), expiredIn);
tk.reset();
t.is(expired.in(headers, date), expiredIn);
});
test('expired.in returns zero ms for instantly stale cache', 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'
};
const expiredIn = 0;
tk.freeze(date);
t.is(expired.in(headers), expiredIn);
tk.reset();
t.is(expired.in(headers, date), expiredIn);
});
test('expired.in returns negative ms for stale cache', t => {
const date = new Date().toUTCString();
const date = new Date(new Date().toUTCString());
const dateOffset = -600;
const maxAge = 300;
const headers = {
@ -46,10 +39,7 @@ test('expired.in returns negative ms for stale cache', t => {
'cache-control': `public, max-age=${maxAge}`
};
const expiredIn = (maxAge + dateOffset) * 1000;
tk.freeze(date);
t.is(expired.in(headers), expiredIn);
tk.reset();
t.is(expired.in(headers, date), expiredIn);
});
test('expired.in accepts currentDate argument', t => {
@ -59,7 +49,6 @@ test('expired.in accepts currentDate argument', t => {
age: 0,
'cache-control': 'public, max-age=300'
};
t.is(expired.in(headers, date), 300000);
t.is(expired.in(headers, addSeconds(date, 500)), -200000);
});

19
test/expired.js

@ -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)));
});

79
test/expired.on.js

@ -1,17 +1,17 @@
import test from 'ava';
import addSeconds from 'date-fns/add_seconds';
import isEqual from 'date-fns/is_equal';
import expired from '../';
const test = require('ava');
const addSeconds = require('date-fns/addSeconds');
const isEqual = require('date-fns/isEqual');
const expired = require('..');
test('expired.on is a function', t => {
t.is(typeof expired.in, 'function');
});
test('expired.on returns correct expirey date for valid cache', t => {
const date = new Date().toUTCString();
const date = new Date(new Date().toUTCString());
const maxAge = 300;
const headers = {
date,
date: date.toUTCString(),
age: 0,
'cache-control': `public, max-age=${maxAge}`
};
@ -21,18 +21,18 @@ test('expired.on returns correct expirey date for valid cache', t => {
});
test('expired.on returns correct expirey date for instantly stale cache', 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'
};
t.true(isEqual(expired.on(headers), date));
});
test('expired.on returns correct expirey date for stale cache', t => {
const date = new Date().toUTCString();
const date = new Date(new Date().toUTCString());
const dateOffset = -600;
const maxAge = 300;
const headers = {
@ -46,11 +46,11 @@ test('expired.on returns correct expirey date for stale cache', t => {
});
test('expired.on takes age into account', t => {
const date = new Date().toUTCString();
const date = new Date(new Date().toUTCString());
const age = 150;
const maxAge = 300;
const headers = {
date,
date: date.toUTCString(),
age,
'cache-control': `public, max-age=${maxAge}`
};
@ -60,26 +60,67 @@ test('expired.on takes age into account', t => {
});
test('expired.on uses Expires header', t => {
const date = new Date().toUTCString();
const date = new Date(new Date().toUTCString());
const headers = {
date: addSeconds(date, 300),
expires: date
date: addSeconds(date, 300).toUTCString(),
expires: date.toUTCString()
};
t.true(isEqual(expired.on(headers), date));
});
test('expired.on prefers Cache-Control over Expires header', t => {
const date = new Date().toUTCString();
test('expired.on prefers Cache-Control with max-age over Expires header', t => {
const date = new Date(new Date().toUTCString());
const expires = new Date(date.getTime() + (100 * 1000));
const age = 150;
const maxAge = 300;
const headers = {
date,
date: date.toUTCString(),
age,
'cache-control': `public, max-age=${maxAge}`,
expires: date
expires: expires.toUTCString()
};
const expiredOn = addSeconds(date, (maxAge - age));
t.true(isEqual(expired.on(headers), expiredOn));
});
test('expired.on prefers Cache-Control with no-cache over Expires header', t => {
const date = new Date(new Date().toUTCString());
const expires = new Date(date.getTime() + (100 * 1000));
const age = 150;
const headers = {
date: date.toUTCString(),
age,
'cache-control': 'no-cache',
expires: expires.toUTCString()
};
t.true(isEqual(expired.on(headers), date));
});
test('expired.on prefers Cache-Control with no-store over Expires header', t => {
const date = new Date(new Date().toUTCString());
const expires = new Date(date.getTime() + (100 * 1000));
const age = 150;
const headers = {
date: date.toUTCString(),
age,
'cache-control': 'no-store',
expires: expires.toUTCString()
};
t.true(isEqual(expired.on(headers), date));
});
test('expired.on uses Expires header when max-age is not set in Cache-Control', t => {
const date = new Date(new Date().toUTCString());
const expires = new Date(date.getTime() + (100 * 1000));
const age = 150;
const headers = {
date: date.toUTCString(),
age,
'cache-control': 'public',
expires: expires.toUTCString()
};
t.true(isEqual(expired.on(headers), expires));
});

39
test/headers.js

@ -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…
Cancel
Save