UNPKG

3.46 kBJavaScriptView Raw
1/**
2 * Get the expiry time of an XMLHttpRequest response
3 * returns a Date object for when the request expires
4 * returns null if there is valid data that says not to cache
5 * returns undefined if there is no caching information
6 **/
7
8function getCacheExpiry(xhr){
9 const expiresHeader = xhr.getResponseHeader('Expires');
10 const cacheControlHeader = xhr.getResponseHeader('Cache-Control');
11
12 let expiry = parseCacheControlHeader(cacheControlHeader);
13 if(expiry === undefined) {
14 expiry = parseExpiresHeader(expiresHeader);
15 }
16 expiry = nullifyInvalidExpiration(expiry);
17 return expiry;
18}
19
20/**
21 * Parse the data in the Cache-Control header for information
22 * returns Date object if the Cache-Control header is valid
23 * returns null if there is a valid Cache-Control header that says not to cache
24 * returns undefined if there is no valid Cache-Control header
25 **/
26function parseCacheControlHeader(cacheControlHeader){
27 if(cacheControlHeader === null || cacheControlHeader === undefined){
28 return undefined;
29 }
30 const headerData = cacheControlHeader.split(",");
31 let expiry, keyword;
32 for(let i=0; i<headerData.length; i++){
33 keyword = headerData[i].trim();
34 if(keyword.includes("max-age")){
35 expiry = parseCacheControlAge(keyword, expiry);
36 }else{
37 expiry = parseCacheControlKeyword(keyword, expiry);
38 }
39 }
40 return expiry;
41}
42
43/**
44 * Parse the max-age value from the Cache-Control header data
45 * returns Date object if the max-age value is valid
46 * returns undefined if there is no valid data
47 **/
48function parseCacheControlAge(maxAge, expiry){
49 if(expiry === null){
50 return expiry;
51 }
52 let seconds = maxAge.split('=')[1].trim();
53 seconds = parseInt(seconds, 10);
54 if(isNaN(seconds)){
55 return undefined;
56 }else{
57 return nowPlusSeconds(seconds);
58 }
59}
60
61/**
62 * Parse non-max-age keywords in the Cache-Control header
63 * returns expiry Date, undefined, or null depending on the keyword behavior
64 **/
65function parseCacheControlKeyword(keyword, expiry){
66 if(keyword.includes("public") || keyword.includes("private")){
67 return expiry;
68 }
69 if(keyword.includes("no-cache") || keyword.includes("no-store")){
70 return null;
71 }
72 if(keyword.includes("must-revalidate") || keyword.includes("proxy-revalidate")){
73 return expiry; // Noop
74 }
75 if(keyword.includes("s-maxage")){
76 return expiry; // Noop
77 }
78 return expiry; // Unknown keyword, Noop
79}
80
81/**
82 * Parse the data in the Expires header for information
83 * returns Date object if the Expires header is valid
84 * returns null if there is a valid Expires header that says not to cache
85 * returns undefined if there is no valid Cache-Control header
86 **/
87function parseExpiresHeader(expiresHeader){
88 if(expiresHeader === null || expiresHeader === undefined) {
89 return undefined;
90 }
91 expires = new Date(expiresHeader);
92 if(expires.toString() === "Invalid Date"){
93 return null;
94 }
95 return expires;
96}
97
98/**
99 * Nullify any invalid expiration Date objects (if they're in the past)
100 **/
101function nullifyInvalidExpiration(expiration){
102 if(expiration instanceof Date && expiration < new Date()){
103 return null;
104 }
105 return expiration;
106}
107
108/**
109 * Returns the current time plus the given number of seconds into the future
110 **/
111function nowPlusSeconds(seconds){
112 const now = new Date();
113 now.setTime(now.getTime() + seconds * 1000);
114 return now;
115}
116
117module.exports.getCacheExpiry = getCacheExpiry;
118module.exports.nowPlusSeconds = nowPlusSeconds;