Comparing version 0.3.1 to 0.3.2
@@ -7,3 +7,3 @@ #!/usr/bin/env node | ||
async function main () { | ||
function main () { | ||
// Create IPX instance | ||
@@ -26,2 +26,2 @@ const ipx = new IPX() | ||
main().catch(consola.error) | ||
main() |
@@ -6,2 +6,10 @@ # Change Log | ||
## [0.3.2](https://github.com/nuxt-contrib/ipx/compare/ipx@0.3.1...ipx@0.3.2) (2020-09-23) | ||
**Note:** Version bump only for package ipx | ||
## [0.3.1](https://github.com/jsless/ipx/compare/ipx@0.3.0...ipx@0.3.1) (2019-01-27) | ||
@@ -8,0 +16,0 @@ |
1012
dist/ipx.cjs.js
@@ -8,223 +8,140 @@ 'use strict'; | ||
var consola = _interopDefault(require('consola')); | ||
var path = require('path'); | ||
var isValidPath = _interopDefault(require('is-valid-path')); | ||
var path = require('path'); | ||
var fsExtra = require('fs-extra'); | ||
var recursiveReadDir = _interopDefault(require('recursive-readdir')); | ||
var Sharp = _interopDefault(require('sharp')); | ||
var defu = _interopDefault(require('defu')); | ||
var cron = require('cron'); | ||
var getEtag = _interopDefault(require('etag')); | ||
function _classCallCheck(instance, Constructor) { | ||
if (!(instance instanceof Constructor)) { | ||
throw new TypeError("Cannot call a class as a function"); | ||
} | ||
const MAX_SIZE = 2048; | ||
const numRegex = /^[1-9][0-9]*$/; | ||
class HttpError extends Error { | ||
constructor() { | ||
super(...arguments); | ||
this.statusCode = 500; | ||
} | ||
} | ||
function _defineProperties(target, props) { | ||
for (var i = 0; i < props.length; i++) { | ||
var descriptor = props[i]; | ||
descriptor.enumerable = descriptor.enumerable || false; | ||
descriptor.configurable = true; | ||
if ("value" in descriptor) descriptor.writable = true; | ||
Object.defineProperty(target, descriptor.key, descriptor); | ||
} | ||
} | ||
function _createClass(Constructor, protoProps, staticProps) { | ||
if (protoProps) _defineProperties(Constructor.prototype, protoProps); | ||
if (staticProps) _defineProperties(Constructor, staticProps); | ||
return Constructor; | ||
} | ||
function _toArray(arr) { | ||
return _arrayWithHoles(arr) || _iterableToArray(arr) || _nonIterableRest(); | ||
} | ||
function _toConsumableArray(arr) { | ||
return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _nonIterableSpread(); | ||
} | ||
function _arrayWithoutHoles(arr) { | ||
if (Array.isArray(arr)) { | ||
for (var i = 0, arr2 = new Array(arr.length); i < arr.length; i++) arr2[i] = arr[i]; | ||
return arr2; | ||
} | ||
} | ||
function _arrayWithHoles(arr) { | ||
if (Array.isArray(arr)) return arr; | ||
} | ||
function _iterableToArray(iter) { | ||
if (Symbol.iterator in Object(iter) || Object.prototype.toString.call(iter) === "[object Arguments]") return Array.from(iter); | ||
} | ||
function _nonIterableSpread() { | ||
throw new TypeError("Invalid attempt to spread non-iterable instance"); | ||
} | ||
function _nonIterableRest() { | ||
throw new TypeError("Invalid attempt to destructure non-iterable instance"); | ||
} | ||
var MAX_SIZE = 2048; | ||
var numRegex = /^[1-9][0-9]*$/; | ||
function checkConditionalHeaders(req, lastModified, etag) { | ||
// If-None-Match header | ||
var ifNoneMatch = req.headers['if-none-match']; | ||
if (ifNoneMatch === etag) { | ||
return true; | ||
} // If-Modified-Since header | ||
var ifModifiedSince = req.headers['if-modified-since']; | ||
if (ifModifiedSince) { | ||
if (new Date(ifModifiedSince) >= lastModified) { | ||
return true; | ||
// If-None-Match header | ||
const ifNoneMatch = req.headers['if-none-match']; | ||
if (ifNoneMatch === etag) { | ||
return true; | ||
} | ||
} | ||
return false; | ||
// If-Modified-Since header | ||
const ifModifiedSince = req.headers['if-modified-since']; | ||
if (ifModifiedSince) { | ||
if (new Date(ifModifiedSince) >= lastModified) { | ||
return true; | ||
} | ||
} | ||
return false; | ||
} | ||
function badRequest(msg) { | ||
var err = new Error('Bad Request: ' + msg); | ||
err.statusCode = 400; | ||
return err; | ||
const err = new HttpError('Bad Request: ' + msg); | ||
err.statusCode = 400; | ||
return err; | ||
} | ||
function notFound() { | ||
var err = new Error('Not Found'); | ||
err.statusCode = 404; | ||
return err; | ||
const err = new HttpError('Not Found'); | ||
err.statusCode = 404; | ||
return err; | ||
} | ||
var VMax = function VMax(max) { | ||
return function (num) { | ||
const VMax = (max) => (num) => { | ||
if (!numRegex.test(num)) { | ||
throw badRequest('Invalid numeric argument: ' + num); | ||
throw badRequest('Invalid numeric argument: ' + num); | ||
} | ||
return Math.min(parseInt(num), max) || null; | ||
}; | ||
}; | ||
var VSize = VMax(MAX_SIZE); | ||
var consola$1 = consola.withTag('ipx'); | ||
const VSize = VMax(MAX_SIZE); | ||
const consola$1 = consola.withTag('ipx'); | ||
var OPERATIONS = { | ||
s: { | ||
name: 'resize', | ||
args: [VSize, VSize], | ||
handler: function handler(context, pipe, width, height) { | ||
return pipe.resize(width, height); | ||
s: { | ||
name: 'resize', | ||
args: [VSize, VSize], | ||
handler: (_context, pipe, width, height, fit) => pipe.resize(width, height, { | ||
fit, | ||
background: { r: 0, g: 0, b: 0, alpha: 0 } | ||
}) | ||
}, | ||
w: { | ||
name: 'width', | ||
args: [VSize], | ||
handler: (_context, pipe, width) => pipe.resize(width, null) | ||
}, | ||
h: { | ||
name: 'height', | ||
args: [VSize], | ||
handler: (_context, pipe, height) => pipe.resize(null, height) | ||
}, | ||
embed: { | ||
name: 'embed', | ||
args: [], | ||
handler: (_context, pipe) => pipe.embed() | ||
}, | ||
max: { | ||
name: 'max', | ||
args: [], | ||
handler: (_context, pipe) => pipe.max() | ||
}, | ||
min: { | ||
name: 'min', | ||
args: [], | ||
handler: (_context, pipe) => pipe.min() | ||
} | ||
}, | ||
w: { | ||
name: 'width', | ||
args: [VSize], | ||
handler: function handler(context, pipe, width) { | ||
return pipe.resize(width, null); | ||
} | ||
}, | ||
h: { | ||
name: 'height', | ||
args: [VSize], | ||
handler: function handler(context, pipe, height) { | ||
return pipe.resize(null, height); | ||
} | ||
}, | ||
embed: { | ||
name: 'embed', | ||
handler: function handler(context, pipe) { | ||
return pipe.embed(); | ||
} | ||
}, | ||
max: { | ||
name: 'max', | ||
handler: function handler(context, pipe) { | ||
return pipe.max(); | ||
} | ||
}, | ||
min: { | ||
name: 'min', | ||
handler: function handler(context, pipe) { | ||
return pipe.min(); | ||
} | ||
} | ||
}; | ||
function env(name, defaultValue) { | ||
return process.env[name] || defaultValue; | ||
return process.env[name] || defaultValue; | ||
} | ||
function getConfig() { | ||
return { | ||
port: env('IPX_PORT', env('PORT', 3000)), | ||
input: { | ||
adapter: env('IPX_INPUT_ADAPTER', 'fs'), | ||
dir: env('IPX_INPUT_DIR', 'storage') | ||
}, | ||
cache: { | ||
adapter: env('IPX_CACHE_ADAPTER', 'fs'), | ||
dir: env('IPX_CACHE_DIR', 'cache'), | ||
cleanCron: env('IPX_CACHE_CLEAN_CRON', '0 0 3 * * *'), | ||
maxUnusedMinutes: env('IPX_CACHE_CLEAN_MINUTES', 24 * 60) | ||
} | ||
}; | ||
} | ||
function getConfig() { | ||
return { | ||
port: env('IPX_PORT', env('PORT', 3000)), | ||
input: { | ||
adapter: env('IPX_INPUT_ADAPTER', 'fs'), | ||
dir: env('IPX_INPUT_DIR', 'storage') | ||
}, | ||
cache: { | ||
adapter: env('IPX_CACHE_ADAPTER', 'fs'), | ||
dir: env('IPX_CACHE_DIR', 'cache'), | ||
cleanCron: env('IPX_CACHE_CLEAN_CRON', '0 0 3 * * *'), | ||
maxUnusedMinutes: env('IPX_CACHE_CLEAN_MINUTES', 24 * 60) | ||
class BaseInputAdapter { | ||
constructor(ipx) { | ||
this.ipx = ipx; | ||
this.options = ipx.options; | ||
this.init(); | ||
} | ||
}; | ||
init() { } | ||
} | ||
var isAbsolute = require('path').posix.isAbsolute; | ||
var FSAdapter = | ||
/*#__PURE__*/ | ||
function () { | ||
function FSAdapter(ipx) { | ||
_classCallCheck(this, FSAdapter); | ||
this.options = ipx.options; | ||
} | ||
_createClass(FSAdapter, [{ | ||
key: "_resolve", | ||
value: function _resolve(src) { | ||
return path.resolve(this.dir, src); | ||
class FSAdapter extends BaseInputAdapter { | ||
get dir() { | ||
return this.options.input.dir; | ||
} | ||
}, { | ||
key: "stats", | ||
value: function stats(src) { | ||
return new Promise(function ($return, $error) { | ||
var _src, stats; | ||
if (isAbsolute(src) || !isValidPath(src)) { | ||
return $return(false); | ||
_resolve(src) { | ||
return path.resolve(this.dir, src); | ||
} | ||
async stats(src) { | ||
if (path.posix.isAbsolute(src) || !isValidPath(src)) { | ||
return false; | ||
} | ||
_src = this._resolve(src); | ||
const _src = await this._resolve(src); | ||
if (path.relative(this.dir, _src).includes('..')) { | ||
return $return(false); | ||
return false; | ||
} | ||
var $Try_1_Catch = function (e) { | ||
try { | ||
// TODO: check for permission errors | ||
return $return(false); | ||
} catch ($boundEx) { | ||
return $error($boundEx); | ||
} | ||
}; | ||
try { | ||
return Promise.resolve(fsExtra.stat(_src)).then(function ($await_2) { | ||
try { | ||
stats = $await_2; | ||
return $return(stats); | ||
} catch ($boundEx) { | ||
return $Try_1_Catch($boundEx); | ||
} | ||
}, $Try_1_Catch); | ||
} catch (e) { | ||
$Try_1_Catch(e); | ||
const stats = await fsExtra.stat(_src); | ||
return stats; | ||
} | ||
}.bind(this)); | ||
catch (e) { | ||
// TODO: check for permission errors | ||
return false; | ||
} | ||
} | ||
@@ -235,23 +152,11 @@ /** | ||
*/ | ||
}, { | ||
key: "get", | ||
value: function get(src) { | ||
var _src = this._resolve(src); | ||
var buff = fsExtra.readFile(_src); | ||
return buff; | ||
async get(src) { | ||
const _src = await this._resolve(src); | ||
const buff = fsExtra.readFile(_src); | ||
return buff; | ||
} | ||
}, { | ||
key: "dir", | ||
get: function get() { | ||
return this.options.input.dir; | ||
} | ||
}]); | ||
} | ||
return FSAdapter; | ||
}(); | ||
var InputAdapters = /*#__PURE__*/Object.freeze({ | ||
@@ -261,134 +166,60 @@ fs: FSAdapter | ||
var FSCache = | ||
/*#__PURE__*/ | ||
function () { | ||
function FSCache(ipx) { | ||
_classCallCheck(this, FSCache); | ||
class BaseCacheAdapter { | ||
constructor(ipx) { | ||
this.ipx = ipx; | ||
this.options = ipx.options; | ||
this.init(); | ||
} | ||
init() { } | ||
} | ||
this.ipx = ipx; | ||
this.options = ipx.options; | ||
this.init(); | ||
} | ||
_createClass(FSCache, [{ | ||
key: "init", | ||
value: function init() { | ||
// Ensure cacheDir exists | ||
if (this.options.cache.dir) { | ||
fsExtra.mkdirpSync(this.options.cache.dir); | ||
} | ||
class FSCache extends BaseCacheAdapter { | ||
init() { | ||
// Ensure cacheDir exists | ||
if (this.options.cache.dir) { | ||
fsExtra.mkdirpSync(this.options.cache.dir); | ||
} | ||
} | ||
}, { | ||
key: "resolve", | ||
value: function resolve(key) { | ||
return path.resolve(this.options.cache.dir, key); | ||
resolve(key) { | ||
return path.resolve(this.options.cache.dir, key); | ||
} | ||
}, { | ||
key: "clean", | ||
value: function clean() { | ||
return new Promise(function ($return, $error) { | ||
var now, diffMins, files, items, maxUnusedMinutes; | ||
now = new Date(); | ||
diffMins = function diffMins(t) { | ||
return (now - t) / (1000 * 60); | ||
}; | ||
return Promise.resolve(recursiveReadDir(this.options.cache.dir)).then(function ($await_1) { | ||
try { | ||
files = $await_1; | ||
return Promise.resolve(Promise.all(files.map(function (file) { | ||
return new Promise(function ($return, $error) { | ||
var stats; | ||
return Promise.resolve(fsExtra.stat(file)).then(function ($await_2) { | ||
try { | ||
stats = $await_2; | ||
return $return({ | ||
file: file, | ||
lastAccessAgo: diffMins(stats.atime) | ||
}); | ||
} catch ($boundEx) { | ||
return $error($boundEx); | ||
} | ||
}, $error); | ||
}); | ||
}))).then(function ($await_3) { | ||
try { | ||
items = $await_3; | ||
maxUnusedMinutes = parseInt(this.options.cache.maxUnusedMinutes); | ||
items = items.filter(function (item) { | ||
return item.lastAccessAgo >= maxUnusedMinutes; | ||
}); | ||
return Promise.resolve(Promise.all(items.map(function (item) { | ||
return new Promise(function ($return, $error) { | ||
consola.debug('[DELETE] ' + item.file); | ||
return Promise.resolve(fsExtra.remove(item.file)).then(function ($await_4) { | ||
try { | ||
return $return(); | ||
} catch ($boundEx) { | ||
return $error($boundEx); | ||
} | ||
}, $error); | ||
}); | ||
}))).then(function ($await_5) { | ||
try { | ||
return $return(); | ||
} catch ($boundEx) { | ||
return $error($boundEx); | ||
} | ||
}, $error); | ||
} catch ($boundEx) { | ||
return $error($boundEx); | ||
} | ||
}.bind(this), $error); | ||
} catch ($boundEx) { | ||
return $error($boundEx); | ||
} | ||
}.bind(this), $error); | ||
}.bind(this)); | ||
async clean() { | ||
const now = new Date(); | ||
const diffMins = (t) => ((+now - +t) / (1000 * 60)); | ||
// Scan all files | ||
const files = await recursiveReadDir(this.options.cache.dir); | ||
// Stat all files | ||
let items = await Promise.all(files.map(async (file) => { | ||
const stats = await fsExtra.stat(file); | ||
return { | ||
file, | ||
lastAccessAgo: diffMins(stats.atime) | ||
}; | ||
})); | ||
// Filter items to be invalidated from cache | ||
const maxUnusedMinutes = +this.options.cache.maxUnusedMinutes; | ||
items = items.filter(item => item.lastAccessAgo >= maxUnusedMinutes); | ||
await Promise.all(items.map(async (item) => { | ||
consola.debug('[DELETE] ' + item.file); | ||
await fsExtra.remove(item.file); | ||
})); | ||
} | ||
}, { | ||
key: "get", | ||
value: function get(key) { | ||
return new Promise(function ($return, $error) { | ||
var _path; | ||
_path = this.resolve(key); | ||
return Promise.resolve(fsExtra.exists(_path)).then(function ($await_6) { | ||
try { | ||
if (!$await_6) { | ||
return $return(null); | ||
} | ||
consola.debug('[HIT] ' + key); | ||
return $return(fsExtra.readFile(_path)); | ||
} catch ($boundEx) { | ||
return $error($boundEx); | ||
} | ||
}, $error); | ||
}.bind(this)); | ||
async get(key) { | ||
const _path = await this.resolve(key); | ||
// @ts-ignore | ||
if (!(await fsExtra.exists(_path))) { | ||
return null; | ||
} | ||
consola.debug('[HIT] ' + key); | ||
return fsExtra.readFile(_path); | ||
} | ||
}, { | ||
key: "set", | ||
value: function set(key, data) { | ||
return new Promise(function ($return, $error) { | ||
var _path; | ||
_path = this.resolve(key); | ||
return Promise.resolve(fsExtra.mkdirp(path.dirname(_path))).then(function ($await_7) { | ||
try { | ||
return $return(fsExtra.writeFile(_path, data)); | ||
} catch ($boundEx) { | ||
return $error($boundEx); | ||
} | ||
}, $error); | ||
}.bind(this)); | ||
async set(key, data) { | ||
const _path = await this.resolve(key); | ||
await fsExtra.mkdirp(path.dirname(_path)); | ||
return fsExtra.writeFile(_path, data); | ||
} | ||
}]); | ||
} | ||
return FSCache; | ||
}(); | ||
var CacheAdapters = /*#__PURE__*/Object.freeze({ | ||
@@ -398,72 +229,59 @@ fs: FSCache | ||
var operationSeparator = ','; | ||
var argSeparator = '_'; | ||
var IPX = | ||
/*#__PURE__*/ | ||
function () { | ||
function IPX(options) { | ||
_classCallCheck(this, IPX); | ||
this.options = Object.assign({}, getConfig(), options); | ||
this.operations = {}; | ||
this.adapter = null; | ||
this.init(); | ||
} | ||
_createClass(IPX, [{ | ||
key: "init", | ||
value: function init() { | ||
var _this = this; | ||
// Merge and normalize operations | ||
var _operations = Object.assign({}, OPERATIONS, this.options.operations); | ||
Object.keys(_operations).filter(function (_key) { | ||
return _operations[_key]; | ||
}).forEach(function (_key) { | ||
var operation = _operations[_key]; | ||
var key = _key || operation.key; | ||
_this.operations[key] = { | ||
name: operation.name || key, | ||
handler: operation.handler || operation, | ||
multiply: Boolean(operation.multiply), | ||
order: Boolean(operation.order), | ||
args: operation.args || [] | ||
}; | ||
}); // Create instance of input | ||
var InputCtor = this.options.input.adapter; | ||
if (typeof InputCtor === 'string') { | ||
InputCtor = InputAdapters[InputCtor] || require(path.resolve(InputCtor)); | ||
} | ||
this.input = new InputCtor(this); | ||
if (typeof this.input.init === 'function') { | ||
this.input.init(); | ||
} // Create instance of cache | ||
var CacheCtor = this.options.cache.adapter; | ||
if (typeof CacheCtor === 'string') { | ||
CacheCtor = CacheAdapters[CacheCtor] || require(path.resolve(CacheCtor)); | ||
} | ||
this.cache = new CacheCtor(this); | ||
if (typeof this.cache.init === 'function') { | ||
this.cache.init(); | ||
} // Start cache cleaning cron | ||
if (this.options.cache.cleanCron) { | ||
this.cacheCleanCron = new cron.CronJob(this.options.cache.cleanCron, function () { | ||
return _this.cleanCache().catch(consola$1.error); | ||
const operationSeparator = ','; | ||
const argSeparator = '_'; | ||
class IPX { | ||
constructor(options) { | ||
this.options = defu(options, getConfig()); | ||
this.operations = {}; | ||
this.adapter = null; | ||
this.init(); | ||
} | ||
init() { | ||
// Merge and normalize operations | ||
const _operations = Object.assign({}, OPERATIONS, this.options.operations); | ||
Object.keys(_operations) | ||
.filter(_key => _operations[_key]) | ||
.forEach((_key) => { | ||
const operation = _operations[_key]; | ||
const key = _key || operation.key; | ||
this.operations[key] = { | ||
name: operation.name || key, | ||
handler: operation.handler || operation, | ||
multiply: Boolean(operation.multiply), | ||
order: Boolean(operation.order), | ||
args: operation.args || [] | ||
}; | ||
}); | ||
consola$1.info('Starting cache clean cron ' + this.options.cache.cleanCron); | ||
this.cacheCleanCron.start(); | ||
} | ||
// Create instance of input | ||
let InputCtor; | ||
if (typeof this.options.input.adapter === 'string') { | ||
const adapter = this.options.input.adapter; | ||
InputCtor = InputAdapters[adapter] || require(path.resolve(adapter)); | ||
} | ||
else { | ||
InputCtor = this.options.input.adapter; | ||
} | ||
this.input = new InputCtor(this); | ||
if (typeof this.input.init === 'function') { | ||
this.input.init(); | ||
} | ||
// Create instance of cache | ||
let CacheCtor; | ||
if (typeof this.options.cache.adapter === 'string') { | ||
const adapter = this.options.cache.adapter; | ||
CacheCtor = CacheAdapters[adapter] || require(path.resolve(adapter)); | ||
} | ||
else { | ||
CacheCtor = this.options.cache.adapter; | ||
} | ||
this.cache = new CacheCtor(this); | ||
if (typeof this.cache.init === 'function') { | ||
this.cache.init(); | ||
} | ||
// Start cache cleaning cron | ||
if (this.options.cache.cleanCron) { | ||
this.cacheCleanCron = new cron.CronJob(this.options.cache.cleanCron, () => this.cleanCache().catch(consola$1.error)); | ||
consola$1.info('Starting cache clean cron ' + this.options.cache.cleanCron); | ||
this.cacheCleanCron.start(); | ||
} | ||
} | ||
@@ -474,295 +292,161 @@ /** | ||
*/ | ||
}, { | ||
key: "parseOperations", | ||
value: function parseOperations(operations) { | ||
var _this2 = this; | ||
var ops = {}; | ||
if (operations === '_') { | ||
return []; | ||
} | ||
return operations.split(operationSeparator).map(function (_o) { | ||
var _o$split = _o.split(argSeparator), | ||
_o$split2 = _toArray(_o$split), | ||
key = _o$split2[0], | ||
args = _o$split2.slice(1); | ||
var operation = _this2.operations[key]; | ||
if (!operation) { | ||
throw badRequest('Invalid operation: ' + key); | ||
parseOperations(operations) { | ||
const ops = {}; | ||
if (operations === '_') { | ||
return []; | ||
} | ||
if (operation.args.length !== args.length) { | ||
throw badRequest('Invalid number of args for ' + key + '. Expected ' + operation.args.length + ' got ' + args.length); | ||
return operations.split(operationSeparator).map((_o) => { | ||
const [key, ...args] = _o.split(argSeparator); | ||
const operation = this.operations[key]; | ||
if (!operation) { | ||
throw badRequest('Invalid operation: ' + key); | ||
} | ||
/** | ||
* allow optional arguments for operations | ||
*/ | ||
if (operation.args.length > args.length) { | ||
throw badRequest('Invalid number of args for ' + key + '. Expected ' + operation.args.length + ' got ' + args.length); | ||
} | ||
for (let i = 0; i < operation.args.length; i++) { | ||
args[i] = operation.args[i](args[i]); | ||
} | ||
if (!operation.multiply) { | ||
if (ops[operation.name]) { | ||
throw badRequest(key + ' can be only used once'); | ||
} | ||
ops[operation.name] = true; | ||
} | ||
return { | ||
operation, | ||
args, | ||
cacheKey: key + argSeparator + args.join(argSeparator) | ||
}; | ||
}); | ||
} | ||
async getInfo({ format, operations, src }) { | ||
// Validate format | ||
if (format === '_') { | ||
format = path.extname(src).substr(1); | ||
} | ||
for (var i = 0; i < operation.args.length; i++) { | ||
args[i] = operation.args[i](args[i]); | ||
if (format === 'jpg') { | ||
format = 'jpeg'; | ||
} | ||
if (!operation.multiply) { | ||
if (ops[operation.name]) { | ||
throw badRequest(key + ' can be only used once'); | ||
} | ||
ops[operation.name] = true; | ||
if (!['jpeg', 'webp', 'png'].includes(format)) { | ||
throw badRequest(`Unkown image format ${format}`); | ||
} | ||
// Validate src | ||
if (!src || src.includes('..')) { | ||
throw notFound(); | ||
} | ||
// Get src stat | ||
const stats = await this.input.stats(src); | ||
if (!stats) { | ||
throw notFound(); | ||
} | ||
// Parse and validate operations | ||
let _operations = this.parseOperations(operations); | ||
// Reorder operations | ||
_operations = [ | ||
..._operations.filter(o => o.operation.order !== true).sort(), | ||
..._operations.filter(o => o.operation.order === true) | ||
]; | ||
// Compute unique hash key | ||
const operationsKey = _operations.length ? _operations.map(o => o.cacheKey).join(argSeparator) : '_'; | ||
const statsKey = stats.mtime.getTime().toString(16) + '-' + stats.size.toString(16); | ||
const cacheKey = src + '/' + statsKey + '/' + operationsKey + '.' + format; | ||
// Return info | ||
return { | ||
operation: operation, | ||
args: args, | ||
cacheKey: key + argSeparator + args.join(argSeparator) | ||
operations: _operations, | ||
stats, | ||
cacheKey, | ||
format, | ||
src | ||
}; | ||
}); | ||
} | ||
}, { | ||
key: "getInfo", | ||
value: function getInfo(_ref) { | ||
return new Promise(function ($return, $error) { | ||
var format, operations, src, stats, _operations, operationsKey, statsKey, cacheKey; | ||
format = _ref.format, operations = _ref.operations, src = _ref.src; | ||
// Validate format | ||
if (format === '_') { | ||
format = path.extname(src).substr(1); | ||
async getData({ cacheKey, operations, format, src }) { | ||
// Check cache existence | ||
const cache = await this.cache.get(cacheKey); | ||
if (cache) { | ||
return cache; | ||
} | ||
if (format === 'jpg') { | ||
format = 'jpeg'; | ||
// Read buffer from input | ||
const srcBuff = await this.input.get(src); | ||
// Process using Sharp | ||
let sharp = Sharp(srcBuff); | ||
if (format !== '_') { | ||
sharp = sharp.toFormat(format); | ||
} | ||
if (['jpeg', 'webp', 'png'].indexOf(format) === -1) { | ||
return $error(badRequest("Unkown image format ".concat(format))); | ||
} // Validate src | ||
if (!src || src.indexOf('..') >= 0) { | ||
return $error(notFound()); | ||
} // Get src stat | ||
return Promise.resolve(this.input.stats(src)).then(function ($await_3) { | ||
try { | ||
stats = $await_3; | ||
if (!stats) { | ||
return $error(notFound()); | ||
} // Parse and validate operations | ||
_operations = this.parseOperations(operations); | ||
// Reorder operations | ||
_operations = [].concat(_operations.filter(function (o) { | ||
return o.operation.order !== true; | ||
}).sort(), _operations.filter(function (o) { | ||
return o.operation.order === true; | ||
})); // Compute unique hash key | ||
operationsKey = _operations.length ? _operations.map(function (o) { | ||
return o.cacheKey; | ||
}).join(argSeparator) : '_'; | ||
statsKey = stats.mtime.getTime().toString(16) + '-' + stats.size.toString(16); | ||
cacheKey = src + '/' + statsKey + '/' + operationsKey + '.' + format; | ||
// Return info | ||
return $return({ | ||
operations: _operations, | ||
stats: stats, | ||
cacheKey: cacheKey, | ||
format: format, | ||
src: src | ||
}); | ||
} catch ($boundEx) { | ||
return $error($boundEx); | ||
} | ||
}.bind(this), $error); | ||
}.bind(this)); | ||
operations.forEach(({ operation, args }) => { | ||
sharp = operation.handler(this, sharp, ...args); | ||
}); | ||
const data = await sharp.toBuffer(); | ||
// Put data into cache | ||
try { | ||
await this.cache.set(cacheKey, data); | ||
} | ||
catch (e) { | ||
consola$1.error(e); | ||
} | ||
return data; | ||
} | ||
}, { | ||
key: "getData", | ||
value: function getData(_ref2) { | ||
return new Promise(function ($return, $error) { | ||
var _this3, cacheKey, stats, operations, format, src, cache, srcBuff, sharp, data; | ||
_this3 = this; | ||
cacheKey = _ref2.cacheKey, stats = _ref2.stats, operations = _ref2.operations, format = _ref2.format, src = _ref2.src; | ||
return Promise.resolve(this.cache.get(cacheKey)).then(function ($await_4) { | ||
try { | ||
cache = $await_4; | ||
if (cache) { | ||
return $return(cache); | ||
} // Read buffer from input | ||
return Promise.resolve(this.input.get(src)).then(function ($await_5) { | ||
try { | ||
srcBuff = $await_5; | ||
sharp = new Sharp(srcBuff); | ||
if (format !== '_') { | ||
sharp = sharp.toFormat(format); | ||
} | ||
operations.forEach(function (_ref3) { | ||
var operation = _ref3.operation, | ||
args = _ref3.args; | ||
sharp = operation.handler.apply(operation, [_this3, sharp].concat(_toConsumableArray(args))); | ||
}); | ||
return Promise.resolve(sharp.toBuffer()).then(function ($await_6) { | ||
try { | ||
data = $await_6; | ||
var $Try_1_Post = function () { | ||
try { | ||
return $return(data); | ||
} catch ($boundEx) { | ||
return $error($boundEx); | ||
} | ||
}; | ||
var $Try_1_Catch = function (e) { | ||
try { | ||
consola$1.error(e); | ||
return $Try_1_Post(); | ||
} catch ($boundEx) { | ||
return $error($boundEx); | ||
} | ||
}; | ||
// Put data into cache | ||
try { | ||
return Promise.resolve(this.cache.set(cacheKey, data)).then(function ($await_7) { | ||
try { | ||
return $Try_1_Post(); | ||
} catch ($boundEx) { | ||
return $Try_1_Catch($boundEx); | ||
} | ||
}, $Try_1_Catch); | ||
} catch (e) { | ||
$Try_1_Catch(e); | ||
} | ||
} catch ($boundEx) { | ||
return $error($boundEx); | ||
} | ||
}.bind(this), $error); | ||
} catch ($boundEx) { | ||
return $error($boundEx); | ||
} | ||
}.bind(this), $error); | ||
} catch ($boundEx) { | ||
return $error($boundEx); | ||
} | ||
}.bind(this), $error); | ||
}.bind(this)); | ||
} | ||
}, { | ||
key: "cleanCache", | ||
value: function cleanCache() { | ||
return new Promise(function ($return, $error) { | ||
async cleanCache() { | ||
if (this.cache) { | ||
return Promise.resolve(this.cache.clean()).then(function ($await_8) { | ||
try { | ||
return $If_2.call(this); | ||
} catch ($boundEx) { | ||
return $error($boundEx); | ||
} | ||
}.bind(this), $error); | ||
await this.cache.clean(); | ||
} | ||
function $If_2() { | ||
return $return(); | ||
} | ||
return $If_2.call(this); | ||
}.bind(this)); | ||
} | ||
}]); | ||
} | ||
return IPX; | ||
}(); | ||
function IPXReqHandler(req, res, ipx) { | ||
return new Promise(function ($return, $error) { | ||
var urlArgs, format, operations, src, info, lastModified, etag, data; | ||
urlArgs = req.url.substr(1).split('/'); | ||
format = decodeURIComponent(urlArgs.shift()); | ||
operations = decodeURIComponent(urlArgs.shift()); | ||
src = urlArgs.map(function (c) { | ||
return decodeURIComponent(c); | ||
}).join('/'); | ||
async function IPXReqHandler(req, res, ipx) { | ||
// Parse URL | ||
const url = req.url || '/'; | ||
const urlArgs = url.substr(1).split('/'); | ||
const format = decodeURIComponent(urlArgs.shift() || ''); | ||
const operations = decodeURIComponent(urlArgs.shift() || ''); | ||
const src = urlArgs.map(c => decodeURIComponent(c)).join('/'); | ||
// Validate params | ||
if (!format) { | ||
return $error(badRequest('Missing format')); | ||
throw badRequest('Missing format'); | ||
} | ||
if (!operations) { | ||
return $error(badRequest('Missing operations')); | ||
throw badRequest('Missing operations'); | ||
} | ||
if (!src) { | ||
return $error(badRequest('Missing src')); | ||
} // Get basic info about request | ||
return Promise.resolve(ipx.getInfo({ | ||
format: format, | ||
operations: operations, | ||
src: src | ||
})).then(function ($await_1) { | ||
try { | ||
info = $await_1; | ||
// Set Content-Type header | ||
if (info.format) { | ||
res.setHeader('Content-Type', 'image/' + info.format); | ||
} else { | ||
res.setHeader('Content-Type', 'image'); | ||
} // Set Last-Modified Header | ||
lastModified = info.stats.mtime || new Date(); | ||
res.setHeader('Last-Modified', lastModified); // Set Etag header | ||
etag = getEtag(info.cacheKey); | ||
res.setHeader('Etag', etag); // Check conditional headers for 304 | ||
throw badRequest('Missing src'); | ||
} | ||
// Get basic info about request | ||
const info = await ipx.getInfo({ format, operations, src }); | ||
// Set Content-Type header | ||
if (info.format) { | ||
res.setHeader('Content-Type', 'image/' + info.format); | ||
} | ||
else { | ||
res.setHeader('Content-Type', 'image'); | ||
} | ||
// Set Etag header | ||
const etag = getEtag(info.cacheKey); | ||
res.setHeader('Etag', etag); | ||
if (info.stats) { | ||
// Set Last-Modified Header | ||
const lastModified = info.stats.mtime || new Date(); | ||
res.setHeader('Last-Modified', +lastModified); | ||
// Check conditional headers for 304 | ||
if (checkConditionalHeaders(req, lastModified, etag)) { | ||
res.statusCode = 304; | ||
return $return(res.end()); | ||
} // Process request to get image | ||
return Promise.resolve(ipx.getData(info)).then(function ($await_2) { | ||
try { | ||
data = $await_2; | ||
// Send image | ||
res.end(data); | ||
return $return(); | ||
} catch ($boundEx) { | ||
return $error($boundEx); | ||
} | ||
}, $error); | ||
} catch ($boundEx) { | ||
return $error($boundEx); | ||
} | ||
}, $error); | ||
}); | ||
res.statusCode = 304; | ||
return res.end(); | ||
} | ||
} | ||
// Process request to get image | ||
const data = await ipx.getData(info); | ||
// Send image | ||
res.end(data); | ||
} | ||
function IPXMiddleware(ipx) { | ||
return function IPXMiddleware(req, res) { | ||
IPXReqHandler(req, res, ipx).catch(function (err) { | ||
if (err.statusCode) { | ||
res.statusCode = err.statusCode; | ||
} | ||
res.end('IPX Error: ' + err); | ||
}); | ||
}; | ||
return function IPXMiddleware(req, res) { | ||
IPXReqHandler(req, res, ipx).catch((err) => { | ||
if (err.statusCode) { | ||
res.statusCode = err.statusCode; | ||
} | ||
res.end('IPX Error: ' + err); | ||
}); | ||
}; | ||
} | ||
@@ -769,0 +453,0 @@ |
{ | ||
"name": "ipx", | ||
"version": "0.3.1", | ||
"version": "0.3.2", | ||
"repository": "nuxt-contrib/ipx", | ||
"license": "MIT", | ||
"contributors": [ | ||
{ | ||
"name": "Pooya Parsa", | ||
"email": "pooya@pi0.ir" | ||
} | ||
], | ||
"main": "dist/ipx.cjs.js", | ||
"bin": "bin/ipx.js", | ||
"files": [ | ||
@@ -15,19 +12,17 @@ "dist", | ||
], | ||
"main": "dist/ipx.cjs.js", | ||
"module": "dist/ipx.es.js", | ||
"bin": "bin/ipx.js", | ||
"scripts": { | ||
"build": "bili --format cjs,es src/index.js" | ||
"build": "bili --format cjs src/index.ts" | ||
}, | ||
"dependencies": { | ||
"connect": "^3.6.6", | ||
"consola": "^2.3.2", | ||
"cron": "^1.3.0", | ||
"connect": "^3.7.0", | ||
"consola": "^2.15.0", | ||
"cron": "^1.8.2", | ||
"defu": "^3.1.0", | ||
"etag": "^1.8.1", | ||
"fs-extra": "^7.0.1", | ||
"fs-extra": "^9.0.1", | ||
"is-valid-path": "^0.1.1", | ||
"recursive-readdir": "^2.2.2", | ||
"sharp": "^0.21.3" | ||
"sharp": "^0.26.0" | ||
}, | ||
"gitHead": "8c5b9447bd7532dc23f72b0ad8308a5de44a907c" | ||
"gitHead": "eaa62032f71e4f369141efacdb99d88b75faffb5" | ||
} |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
No contributors or author data
MaintenancePackage does not specify a list of contributors or an author in package.json.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
No repository
Supply chain riskPackage does not have a linked source code repository. Without this field, a package will have no reference to the location of the source code use to generate the package.
Found 1 instance in 1 package
7
17039
9
450
2
1
+ Addeddefu@^3.1.0
+ Addedarray-flatten@3.0.0(transitive)
+ Addedat-least-node@1.0.0(transitive)
+ Addeddecompress-response@6.0.0(transitive)
+ Addeddefu@3.2.2(transitive)
+ Addedfs-extra@9.1.0(transitive)
+ Addedjsonfile@6.1.0(transitive)
+ Addedmimic-response@3.1.0(transitive)
+ Addednode-addon-api@3.2.1(transitive)
+ Addedprebuild-install@6.1.4(transitive)
+ Addedsemver@7.6.3(transitive)
+ Addedsharp@0.26.3(transitive)
+ Addedsimple-get@4.0.1(transitive)
+ Addeduniversalify@2.0.1(transitive)
- Removedbindings@1.5.0(transitive)
- Removedfile-uri-to-path@1.0.0(transitive)
- Removedfs-copy-file-sync@1.1.1(transitive)
- Removedfs-extra@7.0.1(transitive)
- Removedfs-minipass@1.2.7(transitive)
- Removedjsonfile@4.0.0(transitive)
- Removedminipass@2.9.0(transitive)
- Removedminizlib@1.3.3(transitive)
- Removedmkdirp@0.5.6(transitive)
- Removednan@2.20.0(transitive)
- Removednoop-logger@0.1.1(transitive)
- Removedprebuild-install@5.3.6(transitive)
- Removedsafe-buffer@5.2.1(transitive)
- Removedsharp@0.21.3(transitive)
- Removedtar@4.4.19(transitive)
- Removeduniversalify@0.1.2(transitive)
- Removedwhich-pm-runs@1.1.0(transitive)
- Removedyallist@3.1.1(transitive)
Updatedconnect@^3.7.0
Updatedconsola@^2.15.0
Updatedcron@^1.8.2
Updatedfs-extra@^9.0.1
Updatedsharp@^0.26.0