New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

cache-headers

Package Overview
Dependencies
Maintainers
1
Versions
15
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

cache-headers - npm Package Compare versions

Comparing version 0.0.5 to 1.0.0-hella.1

__coverage__/clover.xml

369

dist/index.js

@@ -0,26 +1,293 @@

'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }
var url = _interopDefault(require('fast-url-parser'));
var globject = _interopDefault(require('globject'));
var slasher = _interopDefault(require('glob-slasher'));
var isEmpty = _interopDefault(require('lodash.isempty'));
var moment_src_lib_moment_moment = require('moment/src/lib/moment/moment');
var moment_src_lib_locale_locale = require('moment/src/lib/locale/locale');
var moment_src_lib_format_format = require('moment/src/lib/format/format');
var regular = _interopDefault(require('regular'));
/**
* Maps to keys in the different cache methods
* @type {Object}
*/
var KEY_SURROGATE_CONTROL = 'maxAge';
/**
* @ignore
* User: daletan
* Date: 12/19/15
* Time: 8:49 PM
* Time: 10:25 PM
* Copyright 1stdibs.com, Inc. 2015. All Rights Reserved.
*/
'use strict';
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
var url = require('fast-url-parser');
var globject = require('globject');
var slasher = require('glob-slasher');
var isEmpty = require('lodash.isempty');
// Mon, 01, Jan 2016, 00:00:00 UTC
var defaultDateFormat = 'ddd, DD MMM YYYY HH:mm:ss z';
var _require = require('./cacheControl');
/**
* @param {*} val The value to check if it is an actual object. Arrays are not considered objects in this case
* @return {boolean}
*/
function isNonEmptyObject(val) {
return !Array.isArray(val) && (typeof val === 'undefined' ? 'undefined' : _typeof(val)) === 'object' && !isEmpty(val);
}
var headerTypes = _require.headerTypes;
var generate = _require.generate;
/**
*
* @param {*} val The value to check if it is like a number ie. 100 and "100" would return true
* @return {boolean}
*/
function isNumberLike(val) {
return regular.number.test(val);
}
var additionalHeaders = require('./additionalHeaders');
var utils = require('./utils');
var timeValues = require('./timeValues');
/**
* @param {object} [time] Date object
* @return {object} moment object in UTC format
*/
function getUtcTime() {
var time = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : new Date();
return moment_src_lib_moment_moment.createUTC(time);
}
/**
* Format a UTC Date value
* @param {object} options
* @param {number} [options.date=now()] UTC time format. A JavaScript date must be passed in, not a moment date object
* @param {string} [options.dateFormat=defaultDateFormat] Primarily used for testing
* @return {string} header date string in GMT format
*/
function formatDate() {
var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
var _options$date = options.date,
date = _options$date === undefined ? moment_src_lib_moment_moment.now() : _options$date,
_options$dateFormat = options.dateFormat,
dateFormat = _options$dateFormat === undefined ? defaultDateFormat : _options$dateFormat;
// keeping this here if we want to
// support setting locales in the future
var locale = { key: undefined, config: undefined };
// need to set locale before formatting
moment_src_lib_locale_locale.updateLocale(locale.key, locale.config);
var formatted = moment_src_lib_format_format.formatMoment(getUtcTime(date), dateFormat);
// do browsers require using GMT instead of UTC?
return formatted.replace('UTC', 'GMT');
}
/**
* @ignore
* User: daletan
* Date: 12/24/15
* Time: 9:51 AM
* Copyright 1stdibs.com, Inc. 2015. All Rights Reserved.
*/
// time set in seconds
var ONE_MINUTE = 60;
var TEN_MINUTES = 600;
var HALF_HOUR = 1800;
var ONE_HOUR = 3600;
var ONE_DAY = 86400;
var ONE_WEEK = 604800;
var ONE_MONTH = 2592000; // 30 days
var ONE_YEAR = 31536000;
var timeValues = Object.freeze({
ONE_MINUTE: ONE_MINUTE,
TEN_MINUTES: TEN_MINUTES,
HALF_HOUR: HALF_HOUR,
ONE_HOUR: ONE_HOUR,
ONE_DAY: ONE_DAY,
ONE_WEEK: ONE_WEEK,
ONE_MONTH: ONE_MONTH,
ONE_YEAR: ONE_YEAR
});
var PRIVATE_VALUE = 'private';
var NO_CACHE_NO_STORE = 'no-cache, no-store, must-revalidate';
// TODO: add parser that can understand string input for header values
// ie: 'private, max-age=300
/**
* If a number or number-like, return the value as a number
* If a string, and it is in a the `timeValues` map, return that time value
* @param {number|string|*} value
* @return {number|string}
*/
function getTimeValue(value) {
if (isNumberLike(value)) {
return Number(value);
} else if (typeof value === 'string') {
// checks for values listed in ./timeValues
value = value.toUpperCase();
if (timeValues[value]) {
return timeValues[value];
}
}
// if no valid value, always return a number
console.warn('no cached value found. ' + value + ' was passed in. returning 0');
return 0;
}
/**
*
* @param {boolean} setPrivate Used for user-specific pages
* @returns {*}
*/
function generateBrowserCacheHeader() {
var setPrivate = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
if (setPrivate) {
return PRIVATE_VALUE + ', ' + NO_CACHE_NO_STORE;
}
return NO_CACHE_NO_STORE;
}
/**
* @param {string|number} maxAge
* @returns {string}
*/
function generateMaxAgeHeader(maxAge) {
return 'max-age=' + getTimeValue(maxAge);
}
/**
* @param {string|number} maxAge
* @returns {string}
*/
function generateStaleRevalidateCacheHeader(maxAge) {
return 'stale-while-revalidate=' + getTimeValue(maxAge);
}
/**
* @param {string|number} maxAge
* @returns {string}
*/
function generateStaleError(maxAge) {
return 'stale-if-error=' + getTimeValue(maxAge);
}
/**
* All options can use a string value. See {@link module:timeValues} for all available values
* Returns the cache header name as the key for res.set()
* @memberof module:cacheControl
* @alias generate
* @param {object} [options] Caching options
* @param {number|string} [options.staleRevalidate=false] Time when to refresh the content in the background
* @param {number|string} [options.staleIfError=false] Time to allow for serving cache when there is an error from a back-end service
* @param {boolean} [options.setPrivate=false] use the `private` cache header value for user-specific pages
* @returns {{Cache-Control: string}}
*/
function generateCacheControl() {
var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
var _options$staleRevalid = options.staleRevalidate,
staleRevalidate = _options$staleRevalid === undefined ? false : _options$staleRevalid,
_options$staleError = options.staleError,
staleError = _options$staleError === undefined ? false : _options$staleError,
_options$setPrivate = options.setPrivate,
setPrivate = _options$setPrivate === undefined ? false : _options$setPrivate;
var cacheHeaders = [generateBrowserCacheHeader(setPrivate)];
if (typeof staleRevalidate !== 'boolean') {
cacheHeaders.push(generateStaleRevalidateCacheHeader(staleRevalidate));
}
if (typeof staleError !== 'boolean') {
cacheHeaders.push(generateStaleError(staleError));
}
return {
'Cache-Control': '' + cacheHeaders.join(', ')
};
}
/**
* Returns the cache header name as the key for res.set()
* @param {object} options
* @param {number|string} [options.maxAge=timeValues.TEN_MINUTES] The browser cache length
* @param {boolean} [options.setPrivate=false] Set the max-age value to 0 for user-specific pages
* @returns {{Surrogate-Control: string}}
*/
function generateSurrogateControl() {
var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
var _options$setPrivate2 = options.setPrivate,
setPrivate = _options$setPrivate2 === undefined ? false : _options$setPrivate2;
var maxAge = options.maxAge;
if (maxAge === undefined) {
// only default this if maxAge is undefined
// since 0 can be passed in
maxAge = TEN_MINUTES;
}
// always force time to 0 if setPrivate - usually user-specific content
var time = setPrivate ? 0 : maxAge;
return {
'Surrogate-Control': generateMaxAgeHeader(time)
};
}
function generatePragmaHeader() {
return {
'Pragma': 'no-cache'
};
}
function generateExpiresHeader() {
return {
'Expires': 0
};
}
function generateLastModifiedHeader() {
var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
var _options$lastModified = options.lastModified,
lastModified = _options$lastModified === undefined ? false : _options$lastModified;
if (!lastModified) {
lastModified = formatDate();
}
return {
'Last-Modified': lastModified
};
}
/**
* @see `generateCacheControl`, `generateSurrogateControl`, and `generateLastModifiedHeader` for options
* @param options
* @returns {*[]}
*/
function generateAllCacheHeaders() {
var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
return Object.assign({}, generateCacheControl(options), generateSurrogateControl(options), generatePragmaHeader(), generateExpiresHeader(), generateLastModifiedHeader(options));
}
/**
* @ignore
* User: daletan
* Date: 12/19/15
* Time: 8:49 PM
* Copyright 1stdibs.com, Inc. 2015. All Rights Reserved.
*/
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
/**
* This will either set a specific header or defer to using express' res.set() functionality

@@ -35,4 +302,4 @@ * {{@link http://expressjs.com/en/api.html#res.set}}

if (headerData.name && headerData.value) {
res.setHeader(headerData.name, headerData.value);
} else if (utils.isTrueObject(headerData)) {
res.set(headerData.name, headerData.value);
} else if (isNonEmptyObject(headerData)) {
res.set(headerData);

@@ -42,13 +309,17 @@ }

/**
* @param {object<array>} headers
* @param {string} headers[].name The header name
* @param {string} headers[].value The header value
* @returns {function(*, *=, *)}
*/
function setAdditionalHeaders() {
var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];
var headers = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
return function (req, res, next) {
Object.keys(options).forEach(function (key) {
if (typeof additionalHeaders[key] === 'function') {
var option = options[key];
var headerData = additionalHeaders[key](option);
setHeader(res, headerData);
}
});
if (Array.isArray(headers) && headers.length) {
headers.map(function (headerData) {
return setHeader(res, headerData);
});
}
next();

@@ -67,7 +338,6 @@ };

function middleware(config) {
var _ref = config || {};
var _ref = config || {},
cacheSettings = _ref.cacheSettings,
paths = _ref.paths;
var cacheSettings = _ref.cacheSettings;
var paths = _ref.paths;
return function (req, res, next) {

@@ -77,21 +347,23 @@

var cacheValues = globject(slasher(paths || {}, { value: false }));
var cacheValue = cacheValues(slasher(pathname));
var values = cacheValues(slasher(pathname));
if (utils.isTrueObject(cacheSettings)) {
if (isNonEmptyObject(cacheSettings)) {
// override default cacheValue settings
cacheValue = generate(cacheSettings).value;
} else if (utils.isTrueObject(cacheValue)) {
cacheValue = generate(cacheValue).value;
} else if (cacheValue === false) {
cacheValue = generate({ maxAge: 0, sMaxAge: 0, setNoCache: true }).value;
} else if (utils.isNumberLike(cacheValue)) {
values = generateAllCacheHeaders(cacheSettings);
} else if (isNonEmptyObject(values)) {
values = generateAllCacheHeaders(values);
} else if (values === false) {
var _generateAllCacheHead;
values = generateAllCacheHeaders((_generateAllCacheHead = {}, _defineProperty(_generateAllCacheHead, KEY_SURROGATE_CONTROL, 0), _defineProperty(_generateAllCacheHead, 'setPrivate', true), _generateAllCacheHead));
} else if (isNumberLike(values)) {
// catch `0` before !cacheValue check
// make sure to convert value to actual number
cacheValue = Number(cacheValue);
cacheValue = generate({ maxAge: cacheValue, sMaxAge: cacheValue }).value;
} else if (!cacheValue || isEmpty(cacheValue)) {
cacheValue = generate().value;
values = generateAllCacheHeaders(_defineProperty({}, KEY_SURROGATE_CONTROL, Number(values)));
} else if (!values || isEmpty(values)) {
values = generateAllCacheHeaders();
}
setHeader(res, { name: 'Cache-Control', value: cacheValue });
setHeader(res, values);
next();

@@ -101,10 +373,11 @@ };

/**
* @module index
* @type {object}
*/
module.exports = Object.assign({
headerTypes: headerTypes,
setAdditionalHeaders: setAdditionalHeaders,
middleware: middleware
}, timeValues);
exports.setAdditionalHeaders = setAdditionalHeaders;
exports.middleware = middleware;
exports.ONE_MINUTE = ONE_MINUTE;
exports.TEN_MINUTES = TEN_MINUTES;
exports.HALF_HOUR = HALF_HOUR;
exports.ONE_HOUR = ONE_HOUR;
exports.ONE_DAY = ONE_DAY;
exports.ONE_WEEK = ONE_WEEK;
exports.ONE_MONTH = ONE_MONTH;
exports.ONE_YEAR = ONE_YEAR;

62

package.json
{
"name": "cache-headers",
"version": "0.0.5",
"version": "1.0.0-hella.1",
"description": "Generate browser and cdn cache header values",
"main": "dist/index.js",
"scripts": {
"test": "./node_modules/.bin/mocha --reporter spec",
"compile": "./node_modules/.bin/babel src -d dist",
"gitlint": "node_modules/.bin/dibslint --git --warnings -e",
"docs": "./node_modules/.bin/mr-doc -s src/ -o docs -n 'Cache Headers'",
"updatedocs": "rm -rf ./docs && npm run docs && git add ./docs && git commit -n -m 'updated docs v${npm show . version}'",
"test": "NODE_ENV=test jest",
"compile": "rm -rf ./dist && NODE_ENV=build rollup -c",
"docs": "rm -rf ./docs && esdoc -c ./esdoc.json",
"lint": "dibslint --root=./src -e --git --warnings",
"precommit": "dibslint --git --warnings -e",
"prepublish": "yarn run compile",
"updatedocs": "rm -rf ./docs && yarn run docs && git add ./docs && git commit -n -m 'updated docs v${npm show . version}'",
"generatedist": "./generate.sh compile dist",
"generatedocs": "./generate.sh docs docs",
"preversion": "npm run gitlint && npm test && npm run generatedocs && npm run generatedist"
"preversion": "yarn run lint && yarn test && yarn run generatedist"
},
"pre-commit": {
"run": [
"gitlint"
"jest": {
"collectCoverage": true,
"coverageDirectory": "<rootDir>/__coverage__",
"coverageThreshold": {
"global": {
"branches": 83,
"functions": 100,
"lines": 100,
"statements": 100
}
},
"setupFiles": [
"<rootDir>/__tests__/testHelper.js"
],
"testPathIgnorePatterns": [
"/node_modules/",
"<rootDir>/__tests__/registerBabel.js"
],
"transformIgnorePatterns": [
"!<rootDir>/node_modules/moment/src"
]

@@ -30,3 +49,6 @@ },

],
"author": "dale tan <dale@1stdibs.com> (https://1stdibs.com)",
"author": "1stdibs, Inc. (https://1stdibs.com)",
"contributors": [
"dale tan <dale@1stdibs.com> (https://1stdibs.com)"
],
"repository": {

@@ -41,11 +63,23 @@ "type": "git",

"babel-core": "^6.1.2",
"babel-plugin-istanbul": "^2.0.3",
"babel-preset-es2015": "^6.1.2",
"core-js": "^1.2.6",
"del": "^2.2.0",
"dibslint": "^1.4.2",
"express": "^4.13.3",
"fast-url-parser": "^1.1.3",
"glob-slasher": "^1.0.1",
"globject": "^1.0.1",
"gulp": "^3.9.0",
"gulp-babel": "^6.1.1",
"husky": "^0.11.9",
"jest": "^19.0.2",
"lodash.isempty": "^3.0.4",
"mocha": "^2.3.3",
"moment": "^2.15.2",
"mr-doc": "^3.0.7",
"pre-commit": "^1.1.2",
"regular": "^0.1.6",
"rollup": "^0.36.3",
"rollup-plugin-babel": "^2.6.1",
"rollup-plugin-commonjs": "^5.0.5",
"supertest": "^1.1.0"

@@ -58,8 +92,6 @@ },

"globject": "^1.0.1",
"lodash.clone": "^3.0.3",
"lodash.isempty": "^3.0.4",
"lodash.isnumber": "^3.0.1",
"moment": "^2.10.6",
"moment": "^2.15.2",
"regular": "^0.1.6"
}
}

@@ -1,35 +0,9 @@

/**
* @ignore
* User: daletan
* Date: 12/19/15
* Time: 10:23 PM
* Copyright 1stdibs.com, Inc. 2015. All Rights Reserved.
*/
import {isNumberLike, formatDate} from './utils';
import * as timeValues from './timeValues';
'use strict';
export const PRIVATE_VALUE = 'private';
export const NO_CACHE_NO_STORE = 'no-cache, no-store, must-revalidate';
const utils = require('./utils');
const timeValues = require('./timeValues');
/**
* @memberof module:cacheControl
* @type {Object}
*/
const headerTypes = Object.freeze({
browser: {
varName: 'maxAge',
value: 'max-age'
},
cdn: {
varName: 'sMaxAge',
value: 's-maxage'
},
staleRevalidate: {
varName: 'staleRevalidate',
value: 'stale-while-revalidate'
},
staleError: {
varName: 'staleError',
value: 'stale-if-error'
}
});
// TODO: add parser that can understand string input for header values
// ie: 'private, max-age=300

@@ -39,34 +13,54 @@ /**

* If a string, and it is in a the `timeValues` map, return that time value
* @private
* @param {number|string} value
* @param {number|string|*} value
* @return {number|string}
*/
function getTimeValue(value) {
if (utils.isNumberLike(value)) {
value = Number(value);
if (isNumberLike(value)) {
return Number(value);
} else if (typeof value === 'string') {
// checks for values listed in ./timeValues
value = value.toUpperCase();
if (!timeValues[value]) {
console.warn(`An invalid time value was passed in, received '${value}'. Returning a value of 10`);
return 10;
if (timeValues[value]) {
return timeValues[value];
}
return timeValues[value];
}
return value;
// if no valid value, always return a number
console.warn(`no cached value found. ${value} was passed in. returning 0`);
return 0;
}
function generateBrowserCacheHeader(maxAge) {
return `${headerTypes.browser.value}=${getTimeValue(maxAge)}`;
/**
*
* @param {boolean} setPrivate Used for user-specific pages
* @returns {*}
*/
function generateBrowserCacheHeader(setPrivate = false) {
if (setPrivate) {
return `${PRIVATE_VALUE}, ${NO_CACHE_NO_STORE}`;
}
return NO_CACHE_NO_STORE;
}
function generateCdnCacheHeader(maxAge) {
return `${headerTypes.cdn.value}=${getTimeValue(maxAge)}`;
/**
* @param {string|number} maxAge
* @returns {string}
*/
function generateMaxAgeHeader(maxAge) {
return `max-age=${getTimeValue(maxAge)}`;
}
/**
* @param {string|number} maxAge
* @returns {string}
*/
function generateStaleRevalidateCacheHeader(maxAge) {
return `${headerTypes.staleRevalidate.value}=${getTimeValue(maxAge)}`;
return `stale-while-revalidate=${getTimeValue(maxAge)}`;
}
/**
* @param {string|number} maxAge
* @returns {string}
*/
function generateStaleError(maxAge) {
return `${headerTypes.staleError.value}=${getTimeValue(maxAge)}`;
return `stale-if-error=${getTimeValue(maxAge)}`;
}

@@ -76,58 +70,95 @@

* All options can use a string value. See {@link module:timeValues} for all available values
* Returns the cache header name as the key for res.set()
* @memberof module:cacheControl
* @alias generate
* @param {object} [options] Caching options
* @param {number|string} [options.maxAge=timeValues.TEN_MINUTES] The browser cache length
* @param {number|string} [options.sMaxAge=false] The cdn cache length
* @param {number|string} [options.staleRevalidate=false] Time when to refresh the content in the background
* @param {number|string} [options.staleError=false] Time to allow for serving cache when there is an error from a back-end service
* @return {{name: string, value: string}}
* @param {number|string} [options.staleIfError=false] Time to allow for serving cache when there is an error from a back-end service
* @param {boolean} [options.setPrivate=false] use the `private` cache header value for user-specific pages
* @returns {{Cache-Control: string}}
*/
function generateCacheControl(options) {
const { maxAge = timeValues.TEN_MINUTES,
sMaxAge = false,
function generateCacheControl(options = {}) {
const {
staleRevalidate = false,
staleError = false,
setNoCache = false,
// private should only be used for user-specific pages. ie account pages
// This will not be set if the sMaxAge is set
setPrivate = false } = options || {};
let cacheHeaders;
setPrivate = false } = options;
const cacheHeaders = [generateBrowserCacheHeader(setPrivate)];
if (setNoCache) {
cacheHeaders = ['no-cache', generateBrowserCacheHeader(0)];
} else {
cacheHeaders = [generateBrowserCacheHeader(maxAge)];
if (typeof staleRevalidate !== 'boolean') {
cacheHeaders.push(generateStaleRevalidateCacheHeader(staleRevalidate));
}
if (sMaxAge) {
cacheHeaders.push(generateCdnCacheHeader(sMaxAge));
}
if (typeof staleError !== 'boolean') {
cacheHeaders.push(generateStaleError(staleError));
}
if (staleRevalidate) {
cacheHeaders.push(generateStaleRevalidateCacheHeader(staleRevalidate));
}
return {
'Cache-Control': `${cacheHeaders.join(', ')}`
};
}
if (staleError) {
cacheHeaders.push(generateStaleError(staleError));
}
/**
* Returns the cache header name as the key for res.set()
* @param {object} options
* @param {number|string} [options.maxAge=timeValues.TEN_MINUTES] The browser cache length
* @param {boolean} [options.setPrivate=false] Set the max-age value to 0 for user-specific pages
* @returns {{Surrogate-Control: string}}
*/
function generateSurrogateControl(options = {}) {
const {
// private should only be used for user-specific pages. ie account pages
setPrivate = false
} = options;
let { maxAge } = options;
if (maxAge === undefined) {
// only default this if maxAge is undefined
// since 0 can be passed in
maxAge = timeValues.TEN_MINUTES;
}
// always force time to 0 if setPrivate - usually user-specific content
const time = setPrivate ? 0 : maxAge;
if (setPrivate && !sMaxAge) {
cacheHeaders.unshift('private');
}
return {
'Surrogate-Control': generateMaxAgeHeader(time)
};
}
function generatePragmaHeader() {
return {
name: 'Cache-Control',
value: `${cacheHeaders.join(', ')}`
'Pragma': 'no-cache'
};
}
function generateExpiresHeader() {
return {
'Expires': 0
};
}
function generateLastModifiedHeader(options = {}) {
let {lastModified = false} = options;
if (!lastModified) {
lastModified = formatDate();
}
return {
'Last-Modified': lastModified
};
}
/**
* @module cacheControl
* @type {{generate: generateCacheControl, headerTypes: Object}}
* @see `generateCacheControl`, `generateSurrogateControl`, and `generateLastModifiedHeader` for options
* @param options
* @returns {*[]}
*/
module.exports = {
generate: generateCacheControl,
headerTypes
};
export function generateAllCacheHeaders(options = {}) {
return Object.assign(
{},
generateCacheControl(options),
generateSurrogateControl(options),
generatePragmaHeader(),
generateExpiresHeader(),
generateLastModifiedHeader(options)
);
}

@@ -11,11 +11,14 @@ /**

const url = require('fast-url-parser');
const globject = require('globject');
const slasher = require('glob-slasher');
const isEmpty = require('lodash.isempty');
const { headerTypes, generate } = require('./cacheControl');
const additionalHeaders = require('./additionalHeaders');
const utils = require('./utils');
const timeValues = require('./timeValues');
import url from 'fast-url-parser';
import globject from 'globject';
import slasher from 'glob-slasher';
import isEmpty from 'lodash.isempty';
import {
KEY_SURROGATE_CONTROL
} from './headerTypes';
import { generateAllCacheHeaders } from './cacheControl';
import { isNumberLike, isNonEmptyObject } from './utils';
export * from './timeValues';
/**

@@ -31,4 +34,4 @@ * This will either set a specific header or defer to using express' res.set() functionality

if (headerData.name && headerData.value) {
res.setHeader(headerData.name, headerData.value);
} else if (utils.isTrueObject(headerData)) {
res.set(headerData.name, headerData.value);
} else if (isNonEmptyObject(headerData)) {
res.set(headerData);

@@ -38,11 +41,13 @@ }

function setAdditionalHeaders(options = {}) {
/**
* @param {object<array>} headers
* @param {string} headers[].name The header name
* @param {string} headers[].value The header value
* @returns {function(*, *=, *)}
*/
export function setAdditionalHeaders(headers = []) {
return (req, res, next) => {
Object.keys(options).forEach(key => {
if (typeof additionalHeaders[key] === 'function') {
const option = options[key];
const headerData = additionalHeaders[key](option);
setHeader(res, headerData);
}
});
if (Array.isArray(headers) && headers.length) {
headers.map(headerData => setHeader(res, headerData));
}
next();

@@ -60,3 +65,3 @@ };

*/
function middleware(config) {
export function middleware(config) {

@@ -69,33 +74,26 @@ const { cacheSettings, paths } = config || {};

const cacheValues = globject(slasher(paths || {}, {value: false}));
let cacheValue = cacheValues(slasher(pathname));
let values = cacheValues(slasher(pathname));
if (utils.isTrueObject(cacheSettings)) {
if (isNonEmptyObject(cacheSettings)) {
// override default cacheValue settings
cacheValue = generate(cacheSettings).value;
} else if (utils.isTrueObject(cacheValue)) {
cacheValue = generate(cacheValue).value;
} else if (cacheValue === false) {
cacheValue = generate({ maxAge: 0, sMaxAge: 0, setNoCache: true }).value;
} else if (utils.isNumberLike(cacheValue)) {
values = generateAllCacheHeaders(cacheSettings);
} else if (isNonEmptyObject(values)) {
values = generateAllCacheHeaders(values);
} else if (values === false) {
values = generateAllCacheHeaders({
[KEY_SURROGATE_CONTROL]: 0,
setPrivate: true
});
} else if (isNumberLike(values)) {
// catch `0` before !cacheValue check
// make sure to convert value to actual number
cacheValue = Number(cacheValue);
cacheValue = generate({ maxAge: cacheValue, sMaxAge: cacheValue }).value;
} else if (!cacheValue || isEmpty(cacheValue)) {
cacheValue = generate().value;
values = generateAllCacheHeaders({ [KEY_SURROGATE_CONTROL]: Number(values) });
} else if (!values || isEmpty(values)) {
values = generateAllCacheHeaders();
}
setHeader(res, { name: 'Cache-Control', value: cacheValue });
setHeader(res, values);
next();
};
}
/**
* @module index
* @type {object}
*/
module.exports = Object.assign({
headerTypes,
setAdditionalHeaders,
middleware
}, timeValues);

@@ -14,2 +14,3 @@ /**

const TEN_MINUTES = 600;
const HALF_HOUR = 1800;
const ONE_HOUR = 3600;

@@ -25,5 +26,6 @@ const ONE_DAY = 86400;

*/
module.exports = Object.freeze({
export {
ONE_MINUTE,
TEN_MINUTES,
HALF_HOUR,
ONE_HOUR,

@@ -34,2 +36,2 @@ ONE_DAY,

ONE_YEAR
});
};

@@ -11,20 +11,10 @@ /**

const fs = require('fs');
const path = require('path');
const moment = require('moment');
const regular = require('regular');
const isNumber = require('lodash.isnumber');
const isEmpty = require('lodash.isempty');
const timeValues = require('./timeValues');
import {now, createUTC as utc} from 'moment/src/lib/moment/moment';
import {updateLocale} from 'moment/src/lib/locale/locale';
import {formatMoment} from 'moment/src/lib/format/format';
import regular from 'regular';
import isEmpty from 'lodash.isempty';
/**
* Possible date format output
* @type {Object}
*/
const dateFormats = Object.freeze({
// all numbers have leading zero
normal: 'ddd, DD MMM YYYY HH:mm:ss',
// disregard seconds during tests
test: 'ddd, DD MMM YYYY HH:mm'
});
// Mon, 01, Jan 2016, 00:00:00 UTC
const defaultDateFormat = 'ddd, DD MMM YYYY HH:mm:ss z';

@@ -35,4 +25,4 @@ /**

*/
function isTrueObject(val) {
return !Array.isArray(val) && typeof val === 'object' && !isEmpty(val) ;
export function isNonEmptyObject(val) {
return !Array.isArray(val) && typeof val === 'object' && !isEmpty(val);
}

@@ -45,43 +35,7 @@

*/
function isNumberLike(val) {
return isNumber(val) || regular.number.test(val);
export function isNumberLike(val) {
return regular.number.test(val);
}
/**
* @param {*} val Any JS object
* @private
* @return {string}
*/
function getType(val) {
return typeof val;
}
/**
* @param {object} [time] A Date object
* @return {number}
*/
function createUnixTime(time) {
if (!time || (time === (void 0))) {
// void 0 => undefined
time = new Date();
}
// convert milliseconds to seconds
return Math.round(moment.unix(time) / 1000);
}
/**
* @param {object[]} [timestamps] An array of Dates
* @private
* @return {object} A Date object
*/
function getLatestTimestamp(timestamps = []) {
timestamps.sort((a, b) => {
const unixTimeA = createUnixTime(a);
const unixTimeB = createUnixTime(b);
return unixTimeA > unixTimeB;
});
return timestamps.reverse()[0];
}
/**
* @param {object} [time] Date object

@@ -91,223 +45,25 @@ * @return {object} moment object in UTC format

function getUtcTime(time = new Date()) {
return moment.utc(time);
return utc(time);
}
/**
* Format a UTC Date value
* @param {object} options
* @param {object} [options.date=new Date()] Date object
* @param {number} [options.timeToAdd=timeValues.TEN_MINUTES] A number of time to add, defaults in seconds
* @param {string} [options.timeFormat='s'] The time format based on momentjs {{@link http://momentjs.com/docs/#/manipulating/add/}}
* @return {object} moment object in UTC format with additional time added
* @param {number} [options.date=now()] UTC time format. A JavaScript date must be passed in, not a moment date object
* @param {string} [options.dateFormat=defaultDateFormat] Primarily used for testing
* @return {string} header date string in GMT format
*/
function addTime(options = {}) {
export function formatDate(options = {}) {
const {
date = new Date(),
timeToAdd = timeValues.TEN_MINUTES,
timeFormat = 's' } = options;
const utcTime = getUtcTime(date);
return utcTime.add(timeToAdd, timeFormat);
date = now(),
dateFormat = defaultDateFormat
} = options;
// keeping this here if we want to
// support setting locales in the future
const locale = {key: undefined, config: undefined};
// need to set locale before formatting
updateLocale(locale.key, locale.config);
const formatted = formatMoment(getUtcTime(date), dateFormat);
// do browsers require using GMT instead of UTC?
return formatted.replace('UTC', 'GMT');
}
/**
* Format a UTC Date value
* @param {number} time UTC time format
* @param {string} [formatType='normal'] Primarily used for testing
* @return {string} header date string in GMT format
*/
function formatDate(time, formatType = 'normal') {
if (moment.isMoment(time)) {
time = time.toISOString();
}
const format = dateFormats[formatType] || dateFormats.normal;
return (getUtcTime(time).format(format)).toString() + ' GMT';
}
/**
* Promise returns a number or a moment timestamp object
* @param {number|string|object} time if an object, a Date object
* @return {Promise}
*/
function getTimestamp(time) {
return new Promise((resolve) => {
if (getType(time) === 'number' || (getType(time) === 'string' && +time === +time)) {
return resolve(+time);
}
const timestamp = moment.utc(new Date(time));
if (timestamp.isValid()) {
return resolve(timestamp);
}
time = moment.utc(new Date());
return resolve(+time);
});
}
/**
* Creates a wrapping promise of promises and only resolves
* when all promises have been resolved
* @param {object[]} values
* @private
* @return {Promise}
*/
function arrayOfTimestamps(values) {
const promises = [];
values.forEach(value => {
promises.push(
getTimestamp(value)
);
});
return Promise.all(promises);
}
/**
* Gets the last modified time of a list of files
* Creates a wrapping promise of promises and only resolves
* when all promises have been resolved
* @param {object[]} files An array of file path strings
* @private
* @return {Promise}
*/
function arrayOfTimestampsFiles(files) {
const promises = [];
files.forEach(file => {
promises.push(
function () {
return new Promise((resolve, reject) => {
fs.lstat(file, (err, fileStats) => {
if (err) {
return reject(err);
}
if (fileStats.isFile()) {
return resolve(getTimestamp(fileStats.mtime));
}
return resolve(0);
});
});
}()
);
});
return Promise.all(promises);
}
/**
* @param {string} dirPath The directory to look into
* @private
* @return {Promise}
*/
function getTimestampFromDirectory(dirPath) {
return new Promise((resolve, reject) => {
fs.readdir(dirPath, (err, files) => {
if (err) {
return reject(err);
}
files = files.map(file => {
// prepend the `dirPath` to each file
return path.resolve(dirPath, file);
});
return arrayOfTimestampsFiles(files)
.then(timestamps => {
const latestTimestamp = getLatestTimestamp(timestamps);
const time = createUnixTime(latestTimestamp ? latestTimestamp : null);
return resolve(time);
})
.catch(() => {
reject(createUnixTime());
});
});
});
}
/**
* Gets the stats of the file. This checks whether it is an actual
* file or a directory and delegates to other methods accordingly
* @param {string} filePath
* @private
* @return {Promise}
*/
function checkTimestampFileType(filePath) {
return new Promise((resolve, reject) => {
fs.realpath(filePath, (err, realFilePath) => {
fs.lstat(realFilePath, (statErr, fileStats) => {
if (fileStats.isFile()) {
return resolve(createUnixTime(fileStats.mtime));
}
if (fileStats.isDirectory()) {
return resolve(getTimestampFromDirectory(realFilePath));
}
return reject(false);
});
});
});
}
/**
* @param {string} filePath Path to the file
* @private
* @return {Promise}
*/
function getFileTimestamp(filePath) {
return new Promise((resolve) => {
return checkTimestampFileType(filePath)
.then(resolvedTime => {
resolve(resolvedTime);
})
.catch(() => {
resolve(createUnixTime());
});
});
}
/**
* All promises return a formatted date string to be used for response headers
* in the format of `Mon, 21 Dec 2015 19:45:29 GMT`
* @param {object[]|string|null|boolean} compare Array of timestamps or a single path to check the last modified time
* @param {string} [formatType=normal] Typically used for testing. Values of `test` and `normal` are accepted
* @return {Promise}
*/
function getLastModified(compare = null, formatType = 'normal') {
return new Promise((resolve, reject) => {
if (Array.isArray(compare) && compare.length > 0) {
return arrayOfTimestamps(compare)
.then(timestamps => {
const latestTimestamp = getLatestTimestamp(timestamps);
return resolve(formatDate(latestTimestamp, formatType));
})
.catch(err => reject(err));
} else if (getType(compare) === 'string' && compare !== '') {
return getTimestamp(compare)
.then(timestamp => {
resolve(formatDate(timestamp, formatType));
})
.catch(() => {
getFileTimestamp(compare)
.then(timestamp => {
resolve(formatDate(timestamp, formatType));
})
.catch(err => reject(err));
});
}
return resolve(formatDate(createUnixTime(), formatType));
});
}
/**
* @module utils
* @type {{dateFormats: dateFormats, isTrueObject: isTrueObject, isNumberLike: isNumberLike, formatDate: formatDate, getUtcTime: getUtcTime, getTimestamp: getTimestamp, createUnixTime: createUnixTime, getLastModified: getLastModified, addTime: addTime}}
*/
module.exports = {
dateFormats,
isTrueObject,
isNumberLike,
formatDate,
getUtcTime,
getTimestamp,
createUnixTime,
getLastModified,
addTime
};

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc