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

lambda-api

Package Overview
Dependencies
Maintainers
1
Versions
37
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

lambda-api - npm Package Compare versions

Comparing version 0.10.7 to 0.11.0

lib/compression.js

75

index.d.ts
import {
APIGatewayEvent,
APIGatewayEventRequestContext,
Context
Context,
} from 'aws-lambda';

@@ -45,6 +45,19 @@

export declare type Middleware = (req: Request, res: Response, next: () => void) => void;
export declare type ErrorHandlingMiddleware = (error: Error, req: Request, res: Response, next: () => void) => void;
export declare type Middleware = (
req: Request,
res: Response,
next: () => void
) => void;
export declare type ErrorHandlingMiddleware = (
error: Error,
req: Request,
res: Response,
next: () => void
) => void;
export declare type ErrorCallback = (error?: Error) => void;
export declare type HandlerFunction = (req: Request, res: Response, next?: NextFunction) => void | any | Promise<any>;
export declare type HandlerFunction = (
req: Request,
res: Response,
next?: NextFunction
) => void | any | Promise<any>;
export declare type LoggerFunction = (message: string) => void;

@@ -55,10 +68,11 @@ export declare type NextFunction = () => void;

export declare type FinallyFunction = (req: Request, res: Response) => void;
export declare type METHODS = 'GET'
| 'POST'
| 'PUT'
| 'PATCH'
| 'DELETE'
| 'OPTIONS'
| 'HEAD'
| 'ANY';
export declare type METHODS =
| 'GET'
| 'POST'
| 'PUT'
| 'PATCH'
| 'DELETE'
| 'OPTIONS'
| 'HEAD'
| 'ANY';

@@ -68,3 +82,3 @@ export declare interface SamplingOptions {

target?: number;
rate?: number
rate?: number;
period?: number;

@@ -109,2 +123,3 @@ method?: string | string[];

isBase64?: boolean;
compression?: boolean;
headers?: object;

@@ -126,3 +141,3 @@ }

multiValueQuery: {
[key: string]: string[] | undefined
[key: string]: string[] | undefined;
};

@@ -178,3 +193,7 @@ headers: {

removeHeader(key: string): this;
getLink(s3Path: string, expires?: number, callback?: ErrorCallback): Promise<string>;
getLink(
s3Path: string,
expires?: number,
callback?: ErrorCallback
): Promise<string>;
send(body: any): void;

@@ -197,4 +216,13 @@ json(body: any): void;

attachment(fileName?: string): this;
download(file: string | Buffer, fileName?: string, options?: FileOptions, callback?: ErrorCallback): void;
sendFile(file: string | Buffer, options?: FileOptions, callback?: ErrorCallback): Promise<void>;
download(
file: string | Buffer,
fileName?: string,
options?: FileOptions,
callback?: ErrorCallback
): void;
sendFile(
file: string | Buffer,
options?: FileOptions,
callback?: ErrorCallback
): Promise<void>;
}

@@ -224,3 +252,6 @@

METHOD(method: METHODS, ...handler: HandlerFunction[]): void;
register(routes: (api: API, options?: RegisterOptions) => void, options?: RegisterOptions): void;
register(
routes: (api: API, options?: RegisterOptions) => void,
options?: RegisterOptions
): void;
routes(format: true): void;

@@ -230,4 +261,2 @@ routes(format: false): string[][];

use(path: string, ...middleware: Middleware[]): void;

@@ -240,3 +269,7 @@ use(paths: string[], ...middleware: Middleware[]): void;

run(event: APIGatewayEvent, context: Context, cb: (err: Error, result: any) => void): void;
run(
event: APIGatewayEvent,
context: Context,
cb: (err: Error, result: any) => void
): void;
run(event: APIGatewayEvent, context: Context): Promise<any>;

@@ -243,0 +276,0 @@ }

@@ -1,2 +0,2 @@

'use strict'
'use strict';

@@ -6,37 +6,56 @@ /**

* @author Jeremy Daly <jeremy@jeremydaly.com>
* @version 0.10.7
* @version 0.11.0
* @license MIT
*/
const REQUEST = require('./lib/request') // Resquest object
const RESPONSE = require('./lib/response') // Response object
const UTILS = require('./lib/utils') // Require utils library
const LOGGER = require('./lib/logger') // Require logger library
const prettyPrint = require('./lib/prettyPrint') // Pretty print for debugging
const { ConfigurationError } = require('./lib/errors') // Require custom errors
const REQUEST = require('./lib/request'); // Resquest object
const RESPONSE = require('./lib/response'); // Response object
const UTILS = require('./lib/utils'); // Require utils library
const LOGGER = require('./lib/logger'); // Require logger library
const prettyPrint = require('./lib/prettyPrint'); // Pretty print for debugging
const { ConfigurationError } = require('./lib/errors'); // Require custom errors
// Create the API class
class API {
// Create the constructor function.
constructor(props) {
// Set the version and base paths
this._version = props && props.version ? props.version : 'v1'
this._base = props && props.base && typeof props.base === 'string' ? props.base.trim() : ''
this._callbackName = props && props.callback ? props.callback.trim() : 'callback'
this._mimeTypes = props && props.mimeTypes && typeof props.mimeTypes === 'object' ? props.mimeTypes : {}
this._serializer = props && props.serializer && typeof props.serializer === 'function' ? props.serializer : JSON.stringify
this._errorHeaderWhitelist = props && Array.isArray(props.errorHeaderWhitelist) ? props.errorHeaderWhitelist.map(header => header.toLowerCase()) : []
this._isBase64 = props && typeof props.isBase64 === 'boolean' ? props.isBase64 : false
this._headers = props && props.headers && typeof props.headers === 'object' ? props.headers : {}
this._version = props && props.version ? props.version : 'v1';
this._base =
props && props.base && typeof props.base === 'string'
? props.base.trim()
: '';
this._callbackName =
props && props.callback ? props.callback.trim() : 'callback';
this._mimeTypes =
props && props.mimeTypes && typeof props.mimeTypes === 'object'
? props.mimeTypes
: {};
this._serializer =
props && props.serializer && typeof props.serializer === 'function'
? props.serializer
: JSON.stringify;
this._errorHeaderWhitelist =
props && Array.isArray(props.errorHeaderWhitelist)
? props.errorHeaderWhitelist.map((header) => header.toLowerCase())
: [];
this._isBase64 =
props && typeof props.isBase64 === 'boolean' ? props.isBase64 : false;
this._headers =
props && props.headers && typeof props.headers === 'object'
? props.headers
: {};
this._compression =
props && typeof props.compression === 'boolean'
? props.compression
: false;
// Set sampling info
this._sampleCounts = {}
this._sampleCounts = {};
// Init request counter
this._requestCount = 0
this._requestCount = 0;
// Track init date/time
this._initTime = Date.now()
this._initTime = Date.now();

@@ -50,176 +69,238 @@ // Logging levels

error: 50,
fatal: 60
}
fatal: 60,
};
// Configure logger
this._logger = LOGGER.config(props && props.logger,this._logLevels)
this._logger = LOGGER.config(props && props.logger, this._logLevels);
// Prefix stack w/ base
this._prefix = this.parseRoute(this._base)
this._prefix = this.parseRoute(this._base);
// Stores route mappings
this._routes = {}
this._routes = {};
// Init callback
this._cb
this._cb;
// Error middleware stack
this._errors = []
this._errors = [];
// Store app packages and namespaces
this._app = {}
this._app = {};
// Executed after the callback
this._finally = () => {}
this._finally = () => {};
// Global error status (used for response parsing errors)
this._errorStatus = 500
this._errorStatus = 500;
// Methods
this._methods = ['get','post','put','patch','delete','options','head','any']
this._methods = [
'get',
'post',
'put',
'patch',
'delete',
'options',
'head',
'any',
];
// Convenience methods for METHOD
this._methods.forEach(m => {
this[m] = (...a) => this.METHOD(m.toUpperCase(),...a)
})
this._methods.forEach((m) => {
this[m] = (...a) => this.METHOD(m.toUpperCase(), ...a);
});
} // end constructor
// METHOD: Adds method, middleware, and handlers to routes
METHOD(method,...args) {
METHOD(method, ...args) {
// Extract path if provided, otherwise default to global wildcard
let path = typeof args[0] === 'string' ? args.shift() : '/*'
let path = typeof args[0] === 'string' ? args.shift() : '/*';
// Extract the execution stack
let stack = args.map((fn,i) => {
if (typeof fn === 'function' && (fn.length === 3 || (i === args.length-1)))
return fn
throw new ConfigurationError('Route-based middleware must have 3 parameters')
})
let stack = args.map((fn, i) => {
if (
typeof fn === 'function' &&
(fn.length === 3 || i === args.length - 1)
)
return fn;
throw new ConfigurationError(
'Route-based middleware must have 3 parameters'
);
});
if (stack.length === 0)
throw new ConfigurationError(`No handler or middleware specified for ${method} method on ${path} route.`)
throw new ConfigurationError(
`No handler or middleware specified for ${method} method on ${path} route.`
);
// Ensure method is an array
let methods = Array.isArray(method) ? method : method.split(',')
// Ensure methods is an array and upper case
let methods = (Array.isArray(method) ? method : method.split(',')).map(
(x) => (typeof x === 'string' ? x.trim().toUpperCase() : null)
);
// Parse the path
let parsedPath = this.parseRoute(path)
let parsedPath = this.parseRoute(path);
// Split the route and clean it up
let route = this._prefix.concat(parsedPath)
let route = this._prefix.concat(parsedPath);
// For root path support
if (route.length === 0) { route.push('') }
if (route.length === 0) {
route.push('');
}
// Keep track of path variables
let pathVars = {}
let pathVars = {};
// Make a local copy of routes
let routes = this._routes
let routes = this._routes;
// Create a local stack for inheritance
let _stack = {}
let _stack = { '*': [], m: [] };
// Loop through the paths
for (let i=0; i<route.length; i++) {
// Loop through the path levels
for (let i = 0; i < route.length; i++) {
// Flag as end of the path
let end = i === route.length - 1;
let end = i === route.length-1
// If this is a variable
// If this is a parameter variable
if (/^:(.*)$/.test(route[i])) {
// Assign it to the pathVars (trim off the : at the beginning)
pathVars[i] = [route[i].substr(1)]
pathVars[i] = [route[i].substr(1)];
// Set the route to __VAR__
route[i] = '__VAR__'
route[i] = '__VAR__';
} // end if variable
// Add methods to routess
methods.forEach(_method => {
// Create routes and add path if they don't exist
if (!routes['ROUTES']) {
routes['ROUTES'] = {};
}
if (!routes['ROUTES'][route[i]]) {
routes['ROUTES'][route[i]] = {};
}
// Loop through methods for the route
methods.forEach((_method) => {
// Method must be a string
if (typeof _method === 'string') {
// Check for wild card at this level
if (routes['ROUTES']['*']) {
if (
routes['ROUTES']['*']['MIDDLEWARE'] &&
(route[i] !== '*' || _method !== '__MW__')
) {
_stack['*'][method] = routes['ROUTES']['*']['MIDDLEWARE'].stack;
}
if (
routes['ROUTES']['*']['METHODS'] &&
routes['ROUTES']['*']['METHODS'][method]
) {
_stack['m'][method] =
routes['ROUTES']['*']['METHODS'][method].stack;
}
} // end if wild card
if (routes['ROUTES']) {
// If this is the end of the path
if (end) {
// Check for matching middleware
if (
route[i] !== '*' &&
routes['ROUTES'][route[i]] &&
routes['ROUTES'][route[i]]['MIDDLEWARE']
) {
_stack['m'][method] =
routes['ROUTES'][route[i]]['MIDDLEWARE'].stack;
} // end if
// Wildcard routes
if (routes['ROUTES']['*']) {
// Generate the route/method meta data
let meta = {
vars: pathVars,
stack: _stack['m'][method]
? _stack['m'][method].concat(stack)
: _stack['*'][method]
? _stack['*'][method].concat(stack)
: stack,
// inherited: _stack[method] ? _stack[method] : [],
route: '/' + parsedPath.join('/'),
path: '/' + this._prefix.concat(parsedPath).join('/'),
};
// Inherit middleware
if (routes['ROUTES']['*']['MIDDLEWARE']) {
_stack[method] = routes['ROUTES']['*']['MIDDLEWARE'].stack
//_stack[method] ?
// _stack[method].concat(routes['ROUTES']['*']['MIDDLEWARE'].stack)
// : routes['ROUTES']['*']['MIDDLEWARE'].stack
// If mounting middleware
if (method === '__MW__') {
// Merge stacks if middleware exists
if (routes['ROUTES'][route[i]]['MIDDLEWARE']) {
meta.stack =
routes['ROUTES'][route[i]]['MIDDLEWARE'].stack.concat(stack);
meta.vars = UTILS.mergeObjects(
routes['ROUTES'][route[i]]['MIDDLEWARE'].vars,
pathVars
);
}
// Add/update middleware
routes['ROUTES'][route[i]]['MIDDLEWARE'] = meta;
// Inherit methods and ANY
if (routes['ROUTES']['*']['METHODS'] && routes['ROUTES']['*']['METHODS']) {
['ANY',method].forEach(m => {
if (routes['ROUTES']['*']['METHODS'][m]) {
_stack[method] = _stack[method] ?
_stack[method].concat(routes['ROUTES']['*']['METHODS'][m].stack)
: routes['ROUTES']['*']['METHODS'][m].stack
}
}) // end for
}
}
// Apply middleware to all child middlware routes
// if (route[i] === "*") {
// // console.log("APPLY NESTED MIDDLEWARE");
// // console.log(JSON.stringify(routes["ROUTES"], null, 2));
// Object.keys(routes["ROUTES"]).forEach((nestedRoute) => {
// if (nestedRoute != "*") {
// console.log(nestedRoute);
// }
// });
// }
} else {
// Create the methods section if it doesn't exist
if (!routes['ROUTES'][route[i]]['METHODS'])
routes['ROUTES'][route[i]]['METHODS'] = {};
// Matching routes
if (routes['ROUTES'][route[i]]) {
// Inherit middleware
if (end && routes['ROUTES'][route[i]]['MIDDLEWARE']) {
_stack[method] = _stack[method] ?
_stack[method].concat(routes['ROUTES'][route[i]]['MIDDLEWARE'].stack)
: routes['ROUTES'][route[i]]['MIDDLEWARE'].stack
// Merge stacks if method already exists for this route
if (routes['ROUTES'][route[i]]['METHODS'][_method]) {
meta.stack =
routes['ROUTES'][route[i]]['METHODS'][_method].stack.concat(
stack
);
meta.vars = UTILS.mergeObjects(
routes['ROUTES'][route[i]]['METHODS'][_method].vars,
pathVars
);
}
// Inherit ANY methods (DISABLED)
// if (end && routes['ROUTES'][route[i]]['METHODS'] && routes['ROUTES'][route[i]]['METHODS']['ANY']) {
// _stack[method] = _stack[method] ?
// _stack[method].concat(routes['ROUTES'][route[i]]['METHODS']['ANY'].stack)
// : routes['ROUTES'][route[i]]['METHODS']['ANY'].stack
// }
}
}
// Add method and meta data
routes['ROUTES'][route[i]]['METHODS'][_method] = meta;
} // end else
// Add the route to the global _routes
this.setRoute(
this._routes,
_method.trim().toUpperCase(),
(end ? {
vars: pathVars,
stack,
inherited: _stack[method] ? _stack[method] : [],
route: '/'+parsedPath.join('/'),
path: '/'+this._prefix.concat(parsedPath).join('/')
} : null),
route.slice(0,i+1)
)
// console.log('STACK:',meta);
}
}) // end methods loop
// If there's a wild card that's not at the end
} else if (route[i] === '*') {
throw new ConfigurationError(
'Wildcards can only be at the end of a route definition'
);
} // end if end of path
} // end if method is string
}); // end methods loop
routes = routes['ROUTES'][route[i]]
// Update the current routes pointer
routes = routes['ROUTES'][route[i]];
} // end path traversal loop
} // end for loop
// console.log(JSON.stringify(this._routes,null,2));
} // end main METHOD function
// RUN: This runs the routes
async run(event,context,cb) {
async run(event, context, cb) {
// Set the event, context and callback
this._event = event || {}
this._context = this.context = typeof context === 'object' ? context : {}
this._cb = cb ? cb : undefined
this._event = event || {};
this._context = this.context = typeof context === 'object' ? context : {};
this._cb = cb ? cb : undefined;
// Initalize request and response objects
let request = new REQUEST(this)
let response = new RESPONSE(this,request)
let request = new REQUEST(this);
let response = new RESPONSE(this, request);
try {
// Parse the request
await request.parseRequest()
await request.parseRequest();

@@ -229,50 +310,50 @@ // Loop through the execution stack

// Only run if in processing state
if (response._state !== 'processing') break
if (response._state !== 'processing') break;
await new Promise(async r => {
// eslint-disable-next-line
await new Promise(async (r) => {
try {
let rtn = await fn(request,response,() => { r() })
if (rtn) response.send(rtn)
if (response._state === 'done') r() // if state is done, resolve promise
} catch(e) {
await this.catchErrors(e,response)
r() // resolve the promise
let rtn = await fn(request, response, () => {
r();
});
if (rtn) response.send(rtn);
if (response._state === 'done') r(); // if state is done, resolve promise
} catch (e) {
await this.catchErrors(e, response);
r(); // resolve the promise
}
})
});
} // end for
} catch(e) {
await this.catchErrors(e,response)
} catch (e) {
// console.log(e);
await this.catchErrors(e, response);
}
// Return the final response
return response._response
return response._response;
} // end run function
// Catch all async/sync errors
async catchErrors(e,response,code,detail) {
async catchErrors(e, response, code, detail) {
// Error messages should respect the app's base64 configuration
response._isBase64 = this._isBase64
response._isBase64 = this._isBase64;
// Strip the headers, keep whitelist
const strippedHeaders = Object.entries(response._headers).reduce((acc, [headerName, value]) => {
if (!this._errorHeaderWhitelist.includes(headerName.toLowerCase())) { return acc }
const strippedHeaders = Object.entries(response._headers).reduce(
(acc, [headerName, value]) => {
if (!this._errorHeaderWhitelist.includes(headerName.toLowerCase())) {
return acc;
}
return Object.assign(
acc,
{ [headerName]: value }
)
}, {})
return Object.assign(acc, { [headerName]: value });
},
{}
);
response._headers = Object.assign(strippedHeaders, this._headers)
response._headers = Object.assign(strippedHeaders, this._headers);
let message
let message;
// Set the status code
response.status(code ? code : this._errorStatus)
response.status(code ? code : this._errorStatus);

@@ -283,14 +364,14 @@ let info = {

coldStart: response._request.coldStart,
stack: this._logger.stack && e.stack || undefined
}
stack: (this._logger.stack && e.stack) || undefined,
};
if (e instanceof Error) {
message = e.message
message = e.message;
if (this._logger.errorLogging) {
this.log.fatal(message, info)
this.log.fatal(message, info);
}
} else {
message = e
message = e;
if (this._logger.errorLogging) {
this.log.error(message, info)
this.log.error(message, info);
}

@@ -301,14 +382,15 @@ }

if (response._state === 'processing') {
// Flag error state (this will avoid infinite error loops)
response._state = 'error'
response._state = 'error';
// Execute error middleware
for (const err of this._errors) {
if (response._state === 'done') break
if (response._state === 'done') break;
// Promisify error middleware
await new Promise(r => {
let rtn = err(e,response._request,response,() => { r() })
if (rtn) response.send(rtn)
})
await new Promise((r) => {
let rtn = err(e, response._request, response, () => {
r();
});
if (rtn) response.send(rtn);
});
} // end for

@@ -318,50 +400,66 @@ }

// Throw standard error unless callback has already been executed
if (response._state !== 'done') response.json({'error':message})
if (response._state !== 'done') response.json({ error: message });
} // end catch
// Custom callback
async _callback(err,res,response) {
async _callback(err, res, response) {
// Set done status
response._state = 'done'
response._state = 'done';
// Execute finally
await this._finally(response._request,response)
await this._finally(response._request, response);
// Output logs
response._request._logs.forEach(log => {
this._logger.logger(JSON.stringify(this._logger.detail ?
this._logger.format(log,response._request,response) : log))
})
response._request._logs.forEach((log) => {
this._logger.logger(
JSON.stringify(
this._logger.detail
? this._logger.format(log, response._request, response)
: log
)
);
});
// Generate access log
if ((this._logger.access || response._request._logs.length > 0) && this._logger.access !== 'never') {
if (
(this._logger.access || response._request._logs.length > 0) &&
this._logger.access !== 'never'
) {
let access = Object.assign(
this._logger.log('access',undefined,response._request,response._request.context),
{ statusCode: res.statusCode, coldStart: response._request.coldStart, count: response._request.requestCount }
)
this._logger.logger(JSON.stringify(this._logger.format(access,response._request,response)))
this._logger.log(
'access',
undefined,
response._request,
response._request.context
),
{
statusCode: res.statusCode,
coldStart: response._request.coldStart,
count: response._request.requestCount,
}
);
this._logger.logger(
JSON.stringify(this._logger.format(access, response._request, response))
);
}
// Reset global error code
this._errorStatus = 500
this._errorStatus = 500;
// Execute the primary callback
typeof this._cb === 'function' && this._cb(err,res)
typeof this._cb === 'function' && this._cb(err, res);
} // end _callback
// Middleware handler
use(...args) {
// Extract routes
let routes = typeof args[0] === 'string' ? Array.of(args.shift()) : (Array.isArray(args[0]) ? args.shift() : ['/*'])
let routes =
typeof args[0] === 'string'
? Array.of(args.shift())
: Array.isArray(args[0])
? args.shift()
: ['/*'];
// Init middleware stack
let middleware = []
let middleware = [];

@@ -372,7 +470,9 @@ // Add func args as middleware

if (args[arg].length === 3) {
middleware.push(args[arg])
middleware.push(args[arg]);
} else if (args[arg].length === 4) {
this._errors.push(args[arg])
this._errors.push(args[arg]);
} else {
throw new ConfigurationError('Middleware must have 3 or 4 parameters')
throw new ConfigurationError(
'Middleware must have 3 or 4 parameters'
);
}

@@ -382,19 +482,15 @@ }

// Add middleware to path
// Add middleware for all methods
if (middleware.length > 0) {
routes.forEach(route => {
this.METHOD('__MW__',route,...middleware)
})
routes.forEach((route) => {
this.METHOD('__MW__', route, ...middleware);
});
}
} // end use
// Finally handler
finally(fn) {
this._finally = fn
this._finally = fn;
}
//-------------------------------------------------------------------------//

@@ -405,56 +501,11 @@ // UTILITY FUNCTIONS

parseRoute(path) {
return path.trim().replace(/^\/(.*?)(\/)*$/,'$1').split('/').filter(x => x.trim() !== '')
return path
.trim()
.replace(/^\/(.*?)(\/)*$/, '$1')
.split('/')
.filter((x) => x.trim() !== '');
}
// Recursive function to create/merge routes object
setRoute(obj, method, value, path) {
if (path.length > 1) {
let p = path.shift()
if (p === '*') { throw new ConfigurationError('Wildcards can only be at the end of a route definition') }
this.setRoute(obj['ROUTES'][p], method, value, path)
} else {
// Create routes and add path if they don't exist
if (!obj['ROUTES']) obj['ROUTES'] = {}
if (!obj['ROUTES'][path[0]]) obj['ROUTES'][path[0]] = {}
// If a value exists in this iteration
if (value !== null) {
// If mounting middleware
if (method === '__MW__') {
// Merge stacks if middleware exists
if (obj['ROUTES'][path[0]]['MIDDLEWARE']) {
value.stack = obj['ROUTES'][path[0]]['MIDDLEWARE'].stack.concat(value.stack)
value.vars = UTILS.mergeObjects(obj['ROUTES'][path[0]]['MIDDLEWARE'].vars,value.vars)
}
// Add/Update the middleware
obj['ROUTES'][path[0]]['MIDDLEWARE'] = value
// Else if mounting a regular route
} else {
// Create the methods section if it doesn't exist
if (!obj['ROUTES'][path[0]]['METHODS']) obj['ROUTES'][path[0]]['METHODS'] = {}
// Merge stacks if method exists
if (obj['ROUTES'][path[0]]['METHODS'][method]) {
value.stack = obj['ROUTES'][path[0]]['METHODS'][method].stack.concat(value.stack)
value.vars = UTILS.mergeObjects(obj['ROUTES'][path[0]]['METHODS'][method].vars,value.vars)
}
// Add/Update the method
obj['ROUTES'][path[0]]['METHODS'] = Object.assign(
{},obj['ROUTES'][path[0]]['METHODS'],{ [method]: value }
)
}
}
}
} // end setRoute
// Load app packages
app(packages) {
// Check for supplied packages

@@ -465,56 +516,53 @@ if (typeof packages === 'object') {

try {
this._app[namespace] = packages[namespace]
} catch(e) {
console.error(e.message) // eslint-disable-line no-console
this._app[namespace] = packages[namespace];
} catch (e) {
console.error(e.message); // eslint-disable-line no-console
}
}
} else if (arguments.length === 2 && typeof packages === 'string') {
this._app[packages] = arguments[1]
}// end if
this._app[packages] = arguments[1];
} // end if
// Return a reference
return this._app
return this._app;
}
// Register routes with options
register(fn,opts) {
register(fn, opts) {
let options = typeof opts === 'object' ? opts : {};
let options = typeof opts === 'object' ? opts : {}
// Extract Prefix
let prefix = options.prefix && options.prefix.toString().trim() !== '' ?
this.parseRoute(options.prefix) : []
let prefix =
options.prefix && options.prefix.toString().trim() !== ''
? this.parseRoute(options.prefix)
: [];
// Concat to existing prefix
this._prefix = this._prefix.concat(prefix)
this._prefix = this._prefix.concat(prefix);
// Execute the routing function
fn(this,options)
fn(this, options);
// Remove the last prefix (if a prefix exists)
if (prefix.length > 0) {
this._prefix = this._prefix.slice(0,-(prefix.length))
this._prefix = this._prefix.slice(0, -prefix.length);
}
} // end register
// prettyPrint debugger
routes(format) {
// Parse the routes
let routes = UTILS.extractRoutes(this._routes)
let routes = UTILS.extractRoutes(this._routes);
if (format) {
console.log(prettyPrint(routes)) // eslint-disable-line no-console
console.log(prettyPrint(routes)); // eslint-disable-line no-console
} else {
return routes
return routes;
}
}
} // end API class
// Export the API class as a new instance
module.exports = opts => new API(opts)
module.exports = (opts) => new API(opts);
// Add createAPI as default export (to match index.d.ts)
module.exports.default = module.exports
module.exports.default = module.exports;

@@ -1,2 +0,2 @@

'use strict'
'use strict';

@@ -12,6 +12,6 @@ /**

class RouteError extends Error {
constructor(message,path) {
super(message)
this.name = this.constructor.name
this.path = path
constructor(message, path) {
super(message);
this.name = this.constructor.name;
this.path = path;
}

@@ -21,7 +21,7 @@ }

class MethodError extends Error {
constructor(message,method,path) {
super(message)
this.name = this.constructor.name
this.method = method
this.path = path
constructor(message, method, path) {
super(message);
this.name = this.constructor.name;
this.method = method;
this.path = path;
}

@@ -32,4 +32,4 @@ }

constructor(message) {
super(message)
this.name = this.constructor.name
super(message);
this.name = this.constructor.name;
}

@@ -39,6 +39,6 @@ }

class ResponseError extends Error {
constructor(message,code) {
super(message)
this.name = this.constructor.name
this.code = code
constructor(message, code) {
super(message);
this.name = this.constructor.name;
this.code = code;
}

@@ -48,6 +48,6 @@ }

class FileError extends Error {
constructor(message,err) {
super(message)
this.name = this.constructor.name
for (let e in err) this[e] = err[e]
constructor(message, err) {
super(message);
this.name = this.constructor.name;
for (let e in err) this[e] = err[e];
}

@@ -62,3 +62,3 @@ }

ResponseError,
FileError
}
FileError,
};

@@ -1,2 +0,2 @@

'use strict'
'use strict';

@@ -7,3 +7,3 @@ /**

* @license MIT
*/
*/

@@ -14,10 +14,9 @@ // IDEA: add unique function identifier

const UTILS = require('./utils') // Require utils library
const { ConfigurationError } = require('./errors') // Require custom errors
const UTILS = require('./utils'); // Require utils library
const { ConfigurationError } = require('./errors'); // Require custom errors
// Config logger
exports.config = (config,levels) => {
exports.config = (config, levels) => {
let cfg = config ? config : {};
let cfg = config ? config : {}
// Add custom logging levels

@@ -27,37 +26,55 @@ if (cfg.levels && typeof cfg.levels === 'object') {

if (!/^[A-Za-z_]\w*$/.test(lvl) || isNaN(cfg.levels[lvl])) {
throw new ConfigurationError('Invalid level configuration')
throw new ConfigurationError('Invalid level configuration');
}
}
levels = Object.assign(levels,cfg.levels)
levels = Object.assign(levels, cfg.levels);
}
// Configure sampling rules
let sampling = cfg.sampling ? parseSamplerConfig(cfg.sampling,levels) : false
let sampling = cfg.sampling
? parseSamplerConfig(cfg.sampling, levels)
: false;
// Parse/default the logging level
let level = cfg === true ? 'info' :
cfg.level && levels[cfg.level.toLowerCase()] ?
cfg.level.toLowerCase() : cfg.level === 'none' ?
'none' : Object.keys(cfg).length > 0 ? 'info' : 'none'
let level =
cfg === true
? 'info'
: cfg.level && levels[cfg.level.toLowerCase()]
? cfg.level.toLowerCase()
: cfg.level === 'none'
? 'none'
: Object.keys(cfg).length > 0
? 'info'
: 'none';
let messageKey = cfg.messageKey && typeof cfg.messageKey === 'string' ?
cfg.messageKey.trim() : 'msg'
let messageKey =
cfg.messageKey && typeof cfg.messageKey === 'string'
? cfg.messageKey.trim()
: 'msg';
let customKey = cfg.customKey && typeof cfg.customKey === 'string' ?
cfg.customKey.trim() : 'custom'
let customKey =
cfg.customKey && typeof cfg.customKey === 'string'
? cfg.customKey.trim()
: 'custom';
let timestamp = cfg.timestamp === false ? () => undefined :
typeof cfg.timestamp === 'function' ? cfg.timestamp : () => Date.now()
let timestamp =
cfg.timestamp === false
? () => undefined
: typeof cfg.timestamp === 'function'
? cfg.timestamp
: () => Date.now();
let timer = cfg.timer === false ? () => undefined : (start) => (Date.now()-start)
let timer =
cfg.timer === false ? () => undefined : (start) => Date.now() - start;
let nested = cfg.nested === true ? true : false // nest serializers
let stack = cfg.stack === true ? true : false // show stack traces in errors
let access = cfg.access === true ? true : cfg.access === 'never' ? 'never' : false // create access logs
let detail = cfg.detail === true ? true : false // add req/res detail to all logs
let nested = cfg.nested === true ? true : false; // nest serializers
let stack = cfg.stack === true ? true : false; // show stack traces in errors
let access =
cfg.access === true ? true : cfg.access === 'never' ? 'never' : false; // create access logs
let detail = cfg.detail === true ? true : false; // add req/res detail to all logs
let multiValue = cfg.multiValue === true ? true : false // return qs as multiValue
let multiValue = cfg.multiValue === true ? true : false; // return qs as multiValue
let defaults = {
req: req => {
req: (req) => {
return {

@@ -70,42 +87,72 @@ path: req.path,

version: req.version,
qs: multiValue ? (Object.keys(req.multiValueQuery).length > 0 ? req.multiValueQuery : undefined)
: (Object.keys(req.query).length > 0 ? req.query : undefined)
}
qs: multiValue
? Object.keys(req.multiValueQuery).length > 0
? req.multiValueQuery
: undefined
: Object.keys(req.query).length > 0
? req.query
: undefined,
};
},
res: () => {
return { }
return {};
},
context: context => {
context: (context) => {
return {
remaining: context.getRemainingTimeInMillis && context.getRemainingTimeInMillis(),
remaining:
context.getRemainingTimeInMillis &&
context.getRemainingTimeInMillis(),
function: context.functionName && context.functionName,
memory: context.memoryLimitInMB && context.memoryLimitInMB
}
memory: context.memoryLimitInMB && context.memoryLimitInMB,
};
},
custom: custom => typeof custom === 'object' && !Array.isArray(custom)
|| nested ? custom : { [customKey]: custom }
}
custom: (custom) =>
(typeof custom === 'object' && !Array.isArray(custom)) || nested
? custom
: { [customKey]: custom },
};
let serializers = {
main: cfg.serializers && typeof cfg.serializers.main === 'function' ? cfg.serializers.main : () => {},
req: cfg.serializers && typeof cfg.serializers.req === 'function' ? cfg.serializers.req : () => {},
res: cfg.serializers && typeof cfg.serializers.res === 'function' ? cfg.serializers.res : () => {},
context: cfg.serializers && typeof cfg.serializers.context === 'function' ? cfg.serializers.context : () => {},
custom: cfg.serializers && typeof cfg.serializers.custom === 'function' ? cfg.serializers.custom : () => {}
}
main:
cfg.serializers && typeof cfg.serializers.main === 'function'
? cfg.serializers.main
: () => {},
req:
cfg.serializers && typeof cfg.serializers.req === 'function'
? cfg.serializers.req
: () => {},
res:
cfg.serializers && typeof cfg.serializers.res === 'function'
? cfg.serializers.res
: () => {},
context:
cfg.serializers && typeof cfg.serializers.context === 'function'
? cfg.serializers.context
: () => {},
custom:
cfg.serializers && typeof cfg.serializers.custom === 'function'
? cfg.serializers.custom
: () => {},
};
// Overridable logging function
let logger = cfg.log && typeof cfg.log === 'function' ?
cfg.log :
(...a) => console.log(...a) // eslint-disable-line no-console
let logger =
cfg.log && typeof cfg.log === 'function'
? cfg.log
: (...a) => console.log(...a); // eslint-disable-line no-console
// Main logging function
let log = (level,msg,req,context,custom) => {
let log = (level, msg, req, context, custom) => {
let _context = Object.assign(
{},
defaults.context(context),
serializers.context(context)
);
let _custom =
typeof custom === 'object' && !Array.isArray(custom)
? Object.assign({}, defaults.custom(custom), serializers.custom(custom))
: defaults.custom(custom);
let _context = Object.assign({},defaults.context(context),serializers.context(context))
let _custom = typeof custom === 'object' && !Array.isArray(custom) ?
Object.assign({},defaults.custom(custom),serializers.custom(custom)) :
defaults.custom(custom)
return Object.assign({},
return Object.assign(
{},
{

@@ -120,3 +167,3 @@ level,

int: req.interface,
sample: req._sample ? true : undefined
sample: req._sample ? true : undefined,
},

@@ -126,18 +173,17 @@ serializers.main(req),

nested ? { context: _context } : _context
)
);
}; // end log
} // end log
// Formatting function for additional log data enrichment
let format = function(info,req,res) {
let format = function (info, req, res) {
let _req = Object.assign({}, defaults.req(req), serializers.req(req));
let _res = Object.assign({}, defaults.res(res), serializers.res(res));
let _req = Object.assign({},defaults.req(req),serializers.req(req))
let _res = Object.assign({},defaults.res(res),serializers.res(res))
return Object.assign({},
return Object.assign(
{},
info,
nested ? { req: _req } : _req,
nested ? { res: _res } : _res
)
} // end format
);
}; // end format

@@ -154,74 +200,99 @@ // Return logger object

sampling,
errorLogging: cfg.errorLogging !== false
}
}
errorLogging: cfg.errorLogging !== false,
};
};
// Determine if we should sample this request
exports.sampler = (app,req) => {
exports.sampler = (app, req) => {
if (app._logger.sampling) {
// Default level to false
let level = false
let level = false;
// Create local reference to the rulesMap
let map = app._logger.sampling.rulesMap
let map = app._logger.sampling.rulesMap;
// Parse the current route
let route = UTILS.parsePath(req.route)
let route = UTILS.parsePath(req.route);
// Default wildcard mapping
let wildcard = {}
let wildcard = {};
// Loop the map and see if this route matches
route.forEach(part => {
route.forEach((part) => {
// Capture wildcard mappings
if (map['*']) wildcard = map['*']
if (map['*']) wildcard = map['*'];
// Traverse map
map = map[part] ? map[part] : {}
}) // end for loop
map = map[part] ? map[part] : {};
}); // end for loop
// Set rule reference based on route
let ref = map['__'+req.method] ? map['__'+req.method] :
map['__ANY'] ? map['__ANY'] : wildcard['__'+req.method] ?
wildcard['__'+req.method] : wildcard['__ANY'] ?
wildcard['__ANY'] : -1
let ref = map['__' + req.method]
? map['__' + req.method]
: map['__ANY']
? map['__ANY']
: wildcard['__' + req.method]
? wildcard['__' + req.method]
: wildcard['__ANY']
? wildcard['__ANY']
: -1;
let rule = ref >= 0 ? app._logger.sampling.rules[ref] : app._logger.sampling.defaults
let rule =
ref >= 0
? app._logger.sampling.rules[ref]
: app._logger.sampling.defaults;
// Assign rule reference to the REQUEST
req._sampleRule = rule
req._sampleRule = rule;
// Get last sample time (default start, last, fixed count, period count and total count)
let counts = app._sampleCounts[rule.default ? 'default' : req.route]
|| Object.assign(app._sampleCounts, {
[rule.default ? 'default' : req.route]: { start: 0, fCount: 0, pCount: 0, tCount: 0 }
})[rule.default ? 'default' : req.route]
let counts =
app._sampleCounts[rule.default ? 'default' : req.route] ||
Object.assign(app._sampleCounts, {
[rule.default ? 'default' : req.route]: {
start: 0,
fCount: 0,
pCount: 0,
tCount: 0,
},
})[rule.default ? 'default' : req.route];
let now = Date.now()
let now = Date.now();
// Calculate the current velocity
let velocity = rule.rate > 0 ? rule.period*1000/(counts.tCount/(now-app._initTime)*rule.period*1000*rule.rate) : 0
let velocity =
rule.rate > 0
? (rule.period * 1000) /
((counts.tCount / (now - app._initTime)) *
rule.period *
1000 *
rule.rate)
: 0;
// If this is a new period, reset values
if ((now-counts.start) > rule.period*1000) {
counts.start = now
counts.pCount = 0
if (now - counts.start > rule.period * 1000) {
counts.start = now;
counts.pCount = 0;
// If a rule target is set, sample the start
if (rule.target > 0) {
counts.fCount = 1
level = rule.level // set the sample level
counts.fCount = 1;
level = rule.level; // set the sample level
// console.log('\n*********** NEW PERIOD ***********');
}
// Enable sampling if last sample is passed target split
} else if (rule.target > 0 &&
counts.start+Math.floor(rule.period*1000/rule.target*counts.fCount) < now) {
level = rule.level
counts.fCount++
// Enable sampling if last sample is passed target split
} else if (
rule.target > 0 &&
counts.start +
Math.floor(((rule.period * 1000) / rule.target) * counts.fCount) <
now
) {
level = rule.level;
counts.fCount++;
// console.log('\n*********** FIXED ***********');
} else if (rule.rate > 0 &&
counts.start+Math.floor(velocity*counts.pCount+velocity/2) < now) {
level = rule.level
counts.pCount++
} else if (
rule.rate > 0 &&
counts.start + Math.floor(velocity * counts.pCount + velocity / 2) < now
) {
level = rule.level;
counts.pCount++;
// console.log('\n*********** RATE ***********');

@@ -231,68 +302,78 @@ }

// Increment total count
counts.tCount++
counts.tCount++;
return level
return level;
} // end if sampling
return false
}
return false;
};
// Parse sampler configuration
const parseSamplerConfig = (config,levels) => {
const parseSamplerConfig = (config, levels) => {
// Default config
let cfg = typeof config === 'object' ? config : config === true ? {} : false
let cfg = typeof config === 'object' ? config : config === true ? {} : false;
// Error on invalid config
if (cfg === false) throw new ConfigurationError('Invalid sampler configuration')
if (cfg === false)
throw new ConfigurationError('Invalid sampler configuration');
// Create rule default
let defaults = (inputs) => {
return { // target, rate, period, method, level
return {
// target, rate, period, method, level
target: Number.isInteger(inputs.target) ? inputs.target : 1,
rate: !isNaN(inputs.rate) && inputs.rate <= 1 ? inputs.rate : 0.1,
period: Number.isInteger(inputs.period) ? inputs.period : 60, // in seconds
level: Object.keys(levels).includes(inputs.level) ? inputs.level : 'trace'
}
}
level: Object.keys(levels).includes(inputs.level)
? inputs.level
: 'trace',
};
};
// Init ruleMap
let rulesMap = {}
let rulesMap = {};
// Parse and default rules
let rules = Array.isArray(cfg.rules) ? cfg.rules.map((rule,i) => {
// Error if missing route or not a string
if (!rule.route || typeof rule.route !== 'string')
throw new ConfigurationError('Invalid route specified in rule')
let rules = Array.isArray(cfg.rules)
? cfg.rules.map((rule, i) => {
// Error if missing route or not a string
if (!rule.route || typeof rule.route !== 'string')
throw new ConfigurationError('Invalid route specified in rule');
// Parse methods into array (if not already)
let methods = (Array.isArray(rule.method) ? rule.method :
typeof rule.method === 'string' ?
rule.method.split(',') : ['ANY']).map(x => x.toString().trim().toUpperCase())
// Parse methods into array (if not already)
let methods = (
Array.isArray(rule.method)
? rule.method
: typeof rule.method === 'string'
? rule.method.split(',')
: ['ANY']
).map((x) => x.toString().trim().toUpperCase());
let map = {}
let recursive = map // create recursive reference
let map = {};
let recursive = map; // create recursive reference
UTILS.parsePath(rule.route).forEach(part => {
Object.assign(recursive,{ [part === '' ? '/' : part]: {} })
recursive = recursive[part === '' ? '/' : part]
})
UTILS.parsePath(rule.route).forEach((part) => {
Object.assign(recursive, { [part === '' ? '/' : part]: {} });
recursive = recursive[part === '' ? '/' : part];
});
Object.assign(recursive, methods.reduce((acc,method) => {
return Object.assign(acc, { ['__'+method]: i })
},{}))
Object.assign(
recursive,
methods.reduce((acc, method) => {
return Object.assign(acc, { ['__' + method]: i });
}, {})
);
// Deep merge the maps
UTILS.deepMerge(rulesMap,map)
// Deep merge the maps
UTILS.deepMerge(rulesMap, map);
return defaults(rule)
},{}) : {}
return defaults(rule);
}, {})
: {};
return {
defaults: Object.assign(defaults(cfg),{ default:true }),
defaults: Object.assign(defaults(cfg), { default: true }),
rules,
rulesMap
}
} // end parseSamplerConfig
rulesMap,
};
}; // end parseSamplerConfig

@@ -1,2 +0,2 @@

'use strict'
'use strict';

@@ -71,4 +71,3 @@ /**

ppsm: 'application/vnd.ms-powerpoint.slideshow.macroEnabled.12',
mdb: 'application/vnd.ms-access'
}
mdb: 'application/vnd.ms-access',
};

@@ -1,2 +0,2 @@

'use strict'
'use strict';

@@ -9,26 +9,77 @@ /**

module.exports = routes => {
module.exports = (routes) => {
let out = '';
let out = ''
// Calculate column widths
let widths = routes.reduce((acc,row) => {
return [
Math.max(acc[0],Math.max(6,row[0].length)),
Math.max(acc[1],Math.max(5,row[1].length))
]
},[0,0])
let widths = routes.reduce(
(acc, row) => {
return [
Math.max(acc[0], Math.max(6, row[0].length)),
Math.max(acc[1], Math.max(5, row[1].length)),
Math.max(acc[2], Math.max(6, row[2].join(', ').length)),
];
},
[0, 0, 0]
);
out += '╔══' + ''.padEnd(widths[0],'═') + '══╤══' + ''.padEnd(widths[1],'═') + '══╗\n'
out += '║ ' + '\u001b[1m' + 'METHOD'.padEnd(widths[0]) + '\u001b[0m' + ' │ ' + '\u001b[1m' + 'ROUTE'.padEnd(widths[1]) + '\u001b[0m' + ' ║\n'
out += '╟──' + ''.padEnd(widths[0],'─') + '──┼──' + ''.padEnd(widths[1],'─') + '──╢\n'
routes.forEach((route,i) => {
out += '║ ' + route[0].padEnd(widths[0]) + ' │ ' + route[1].padEnd(widths[1]) + ' ║\n'
if (i < routes.length-1) {
out += '╟──' + ''.padEnd(widths[0],'─') + '──┼──' + ''.padEnd(widths[1],'─') + '──╢\n'
out +=
'╔══' +
''.padEnd(widths[0], '═') +
'══╤══' +
''.padEnd(widths[1], '═') +
'══╤══' +
''.padEnd(widths[2], '═') +
'══╗\n';
out +=
'║ ' +
'\u001b[1m' +
'METHOD'.padEnd(widths[0]) +
'\u001b[0m' +
' │ ' +
'\u001b[1m' +
'ROUTE'.padEnd(widths[1]) +
'\u001b[0m' +
' │ ' +
'\u001b[1m' +
'STACK'.padEnd(widths[2]) +
'\u001b[0m' +
' ║\n';
out +=
'╟──' +
''.padEnd(widths[0], '─') +
'──┼──' +
''.padEnd(widths[1], '─') +
'──┼──' +
''.padEnd(widths[2], '─') +
'──╢\n';
routes.forEach((route, i) => {
out +=
'║ ' +
route[0].padEnd(widths[0]) +
' │ ' +
route[1].padEnd(widths[1]) +
' │ ' +
route[2].join(', ').padEnd(widths[2]) +
' ║\n';
if (i < routes.length - 1) {
out +=
'╟──' +
''.padEnd(widths[0], '─') +
'──┼──' +
''.padEnd(widths[1], '─') +
'──┼──' +
''.padEnd(widths[2], '─') +
'──╢\n';
} // end if
})
out += '╚══' + ''.padEnd(widths[0],'═') + '══╧══' + ''.padEnd(widths[1],'═') + '══╝'
});
out +=
'╚══' +
''.padEnd(widths[0], '═') +
'══╧══' +
''.padEnd(widths[1], '═') +
'══╧══' +
''.padEnd(widths[2], '═') +
'══╝';
return out
}
return out;
};

@@ -1,2 +0,2 @@

'use strict'
'use strict';

@@ -9,52 +9,54 @@ /**

const QS = require('querystring') // Require the querystring library
const UTILS = require('./utils') // Require utils library
const LOGGER = require('./logger') // Require logger library
const { RouteError, MethodError } = require('./errors') // Require custom errors
const QS = require('querystring'); // Require the querystring library
const UTILS = require('./utils'); // Require utils library
const LOGGER = require('./logger'); // Require logger library
const { RouteError, MethodError } = require('./errors'); // Require custom errors
class REQUEST {
// Create the constructor function.
constructor(app) {
// Record start time
this._start = Date.now()
this._start = Date.now();
// Create a reference to the app
this.app = app
this.app = app;
// Flag cold starts
this.coldStart = app._requestCount === 0 ? true : false
this.coldStart = app._requestCount === 0 ? true : false;
// Increment the requests counter
this.requestCount = ++app._requestCount
this.requestCount = ++app._requestCount;
// Init the handler
this._handler
this._handler;
// Init the execution stack
this._stack
this._stack;
// Expose Namespaces
this.namespace = this.ns = app._app
this.namespace = this.ns = app._app;
// Set the version
this.version = app._version
this.version = app._version;
// Init the params
this.params = {}
this.params = {};
// Init headers
this.headers = {}
this.headers = {};
// Init multi-value support flag
this._multiValueSupport = null
this._multiValueSupport = null;
// Init log helpers (message,custom) and create app reference
app.log = this.log = Object.keys(app._logLevels).reduce((acc,lvl) =>
Object.assign(acc,{ [lvl]: (m,c) => this.logger(lvl, m, this, this.context, c) }),{})
app.log = this.log = Object.keys(app._logLevels).reduce(
(acc, lvl) =>
Object.assign(acc, {
[lvl]: (m, c) => this.logger(lvl, m, this, this.context, c),
}),
{}
);
// Init _logs array for storage
this._logs = []
this._logs = [];
} // end constructor

@@ -64,195 +66,273 @@

async parseRequest() {
// Set the payload version
this.payloadVersion = this.app._event.version
? this.app._event.version
: null;
// Detect multi-value support
this._multiValueSupport = 'multiValueHeaders' in this.app._event
this._multiValueSupport = 'multiValueHeaders' in this.app._event;
// Set the method
this.method = this.app._event.httpMethod ? this.app._event.httpMethod.toUpperCase() : 'GET'
this.method = this.app._event.httpMethod
? this.app._event.httpMethod.toUpperCase()
: this.app._event.requestContext && this.app._event.requestContext.http
? this.app._event.requestContext.http.method.toUpperCase()
: 'GET';
// Set the path
this.path = this.app._event.path
this.path =
this.payloadVersion === '2.0'
? this.app._event.rawPath
: this.app._event.path;
// Set the query parameters (backfill for ALB)
this.query = Object.assign({}, this.app._event.queryStringParameters,
'queryStringParameters' in this.app._event ? {} // do nothing
: Object.keys(Object.assign({},this.app._event.multiValueQueryStringParameters))
.reduce((qs,key) => Object.assign(qs, // get the last value of the array
{ [key]: decodeURIComponent(this.app._event.multiValueQueryStringParameters[key].slice(-1)[0]) }
), {})
)
this.query = Object.assign(
{},
this.app._event.queryStringParameters,
'queryStringParameters' in this.app._event
? {} // do nothing
: Object.keys(
Object.assign({}, this.app._event.multiValueQueryStringParameters)
).reduce(
(qs, key) =>
Object.assign(
qs, // get the last value of the array
{
[key]: decodeURIComponent(
this.app._event.multiValueQueryStringParameters[key].slice(
-1
)[0]
),
}
),
{}
)
);
// Set the multi-value query parameters (simulate if no multi-value support)
this.multiValueQuery = Object.assign({},
this._multiValueSupport ? {} : Object.keys(this.query)
.reduce((qs,key) => Object.assign(qs, { [key]: [this.query[key]] }), {}),
this.app._event.multiValueQueryStringParameters)
this.multiValueQuery = Object.assign(
{},
this._multiValueSupport
? {}
: Object.keys(this.query).reduce(
(qs, key) =>
Object.assign(qs, { [key]: this.query[key].split(',') }),
{}
),
this.app._event.multiValueQueryStringParameters
);
// Set the raw headers (normalize multi-values)
// per https://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2
this.rawHeaders = this._multiValueSupport && this.app._event.multiValueHeaders !== null ?
Object.keys(this.app._event.multiValueHeaders).reduce((headers,key) =>
Object.assign(headers,{ [key]: UTILS.fromArray(this.app._event.multiValueHeaders[key]) }),{})
: this.app._event.headers || {}
this.rawHeaders =
this._multiValueSupport && this.app._event.multiValueHeaders !== null
? Object.keys(this.app._event.multiValueHeaders).reduce(
(headers, key) =>
Object.assign(headers, {
[key]: UTILS.fromArray(this.app._event.multiValueHeaders[key]),
}),
{}
)
: this.app._event.headers || {};
// Set the headers to lowercase
this.headers = Object.keys(this.rawHeaders).reduce((acc,header) =>
Object.assign(acc,{[header.toLowerCase()]:this.rawHeaders[header]}), {})
this.headers = Object.keys(this.rawHeaders).reduce(
(acc, header) =>
Object.assign(acc, { [header.toLowerCase()]: this.rawHeaders[header] }),
{}
);
this.multiValueHeaders = this._multiValueSupport ? this.app._event.multiValueHeaders
: Object.keys(this.headers).reduce((headers,key) =>
Object.assign(headers,{ [key.toLowerCase()]: [this.headers[key]] }),{})
this.multiValueHeaders = this._multiValueSupport
? this.app._event.multiValueHeaders
: Object.keys(this.headers).reduce(
(headers, key) =>
Object.assign(headers, {
[key.toLowerCase()]: this.headers[key].split(','),
}),
{}
);
// Extract user agent
this.userAgent = this.headers['user-agent']
this.userAgent = this.headers['user-agent'];
// Get cookies from event
let cookies = this.app._event.cookies
? this.app._event.cookies
: this.headers.cookie
? this.headers.cookie.split(';')
: [];
// Set and parse cookies
this.cookies = this.headers.cookie ?
this.headers.cookie.split(';')
.reduce(
(acc,cookie) => {
cookie = cookie.trim().split('=')
return Object.assign(acc,{ [cookie[0]] : UTILS.parseBody(decodeURIComponent(cookie[1])) })
},
{}
) : {}
this.cookies = cookies.reduce((acc, cookie) => {
cookie = cookie.trim().split('=');
return Object.assign(acc, {
[cookie[0]]: UTILS.parseBody(decodeURIComponent(cookie[1])),
});
}, {});
// Attempt to parse the auth
this.auth = UTILS.parseAuth(this.headers.authorization)
this.auth = UTILS.parseAuth(this.headers.authorization);
// Set the requestContext
this.requestContext = this.app._event.requestContext || {}
this.requestContext = this.app._event.requestContext || {};
// Extract IP (w/ sourceIp fallback)
this.ip = (this.headers['x-forwarded-for'] && this.headers['x-forwarded-for'].split(',')[0].trim())
|| (this.requestContext['identity'] && this.requestContext['identity']['sourceIp']
&& this.requestContext['identity']['sourceIp'].split(',')[0].trim())
this.ip =
(this.headers['x-forwarded-for'] &&
this.headers['x-forwarded-for'].split(',')[0].trim()) ||
(this.requestContext['identity'] &&
this.requestContext['identity']['sourceIp'] &&
this.requestContext['identity']['sourceIp'].split(',')[0].trim());
// Assign the requesting interface
this.interface = this.requestContext.elb ? 'alb' : 'apigateway'
this.interface = this.requestContext.elb ? 'alb' : 'apigateway';
// Set the pathParameters
this.pathParameters = this.app._event.pathParameters || {}
this.pathParameters = this.app._event.pathParameters || {};
// Set the stageVariables
this.stageVariables = this.app._event.stageVariables || {}
this.stageVariables = this.app._event.stageVariables || {};
// Set the isBase64Encoded
this.isBase64Encoded = this.app._event.isBase64Encoded || false
this.isBase64Encoded = this.app._event.isBase64Encoded || false;
// Add context
this.context = this.app.context && typeof this.app.context === 'object' ? this.app.context : {}
this.context =
this.app.context && typeof this.app.context === 'object'
? this.app.context
: {};
// Parse id from context
this.id = this.context.awsRequestId ? this.context.awsRequestId : null
this.id = this.context.awsRequestId ? this.context.awsRequestId : null;
// Determine client type
this.clientType =
this.headers['cloudfront-is-desktop-viewer'] === 'true' ? 'desktop' :
this.headers['cloudfront-is-mobile-viewer'] === 'true' ? 'mobile' :
this.headers['cloudfront-is-smarttv-viewer'] === 'true' ? 'tv' :
this.headers['cloudfront-is-tablet-viewer'] === 'true' ? 'tablet' :
'unknown'
this.headers['cloudfront-is-desktop-viewer'] === 'true'
? 'desktop'
: this.headers['cloudfront-is-mobile-viewer'] === 'true'
? 'mobile'
: this.headers['cloudfront-is-smarttv-viewer'] === 'true'
? 'tv'
: this.headers['cloudfront-is-tablet-viewer'] === 'true'
? 'tablet'
: 'unknown';
// Parse country
this.clientCountry = this.headers['cloudfront-viewer-country'] ?
this.headers['cloudfront-viewer-country'].toUpperCase() : 'unknown'
this.clientCountry = this.headers['cloudfront-viewer-country']
? this.headers['cloudfront-viewer-country'].toUpperCase()
: 'unknown';
// Capture the raw body
this.rawBody = this.app._event.body
this.rawBody = this.app._event.body;
// Set the body (decode it if base64 encoded)
this.body = this.app._event.isBase64Encoded ? Buffer.from(this.app._event.body || '', 'base64').toString() : this.app._event.body
this.body = this.app._event.isBase64Encoded
? Buffer.from(this.app._event.body || '', 'base64').toString()
: this.app._event.body;
// Set the body
if (this.headers['content-type'] && this.headers['content-type'].includes('application/x-www-form-urlencoded')) {
this.body = QS.parse(this.body)
if (
this.headers['content-type'] &&
this.headers['content-type'].includes('application/x-www-form-urlencoded')
) {
this.body = QS.parse(this.body);
} else if (typeof this.body === 'object') {
this.body = this.body
// Do nothing
} else {
this.body = UTILS.parseBody(this.body)
this.body = UTILS.parseBody(this.body);
}
// Init the stack reporter
this.stack = null
this.stack = null;
// Extract path from event (strip querystring just in case)
let path = UTILS.parsePath(this.path)
let path = UTILS.parsePath(this.path);
// Init the route
this.route = null
this.route = null;
// Create a local routes reference
let routes = this.app._routes
let routes = this.app._routes;
// Init wildcard
let wc = []
let wc = [];
// Loop the routes and see if this matches
for (let i=0; i<path.length; i++) {
for (let i = 0; i < path.length; i++) {
// Capture wildcard routes
if (routes['ROUTES'] && routes['ROUTES']['*']) { wc.push(routes['ROUTES']['*']) }
if (routes['ROUTES'] && routes['ROUTES']['*']) {
wc.push(routes['ROUTES']['*']);
}
// Traverse routes
if (routes['ROUTES'] && routes['ROUTES'][path[i]]) {
routes = routes['ROUTES'][path[i]]
routes = routes['ROUTES'][path[i]];
} else if (routes['ROUTES'] && routes['ROUTES']['__VAR__']) {
routes = routes['ROUTES']['__VAR__']
routes = routes['ROUTES']['__VAR__'];
} else if (
wc[wc.length-1]
&& wc[wc.length-1]['METHODS']
wc[wc.length - 1] &&
wc[wc.length - 1]['METHODS'] &&
// && (wc[wc.length-1]['METHODS'][this.method] || wc[wc.length-1]['METHODS']['ANY'])
&& (
(this.method !== 'OPTIONS'
&& Object.keys(wc[wc.length-1]['METHODS']).toString() !== 'OPTIONS')
|| this.validWildcard(wc,this.method)
)
((this.method !== 'OPTIONS' &&
Object.keys(wc[wc.length - 1]['METHODS']).toString() !== 'OPTIONS') ||
this.validWildcard(wc, this.method))
) {
routes = wc[wc.length-1]
routes = wc[wc.length - 1];
} else {
this.app._errorStatus = 404
throw new RouteError('Route not found','/'+path.join('/'))
this.app._errorStatus = 404;
throw new RouteError('Route not found', '/' + path.join('/'));
}
} // end for loop
// Grab the deepest wildcard path
let wildcard = wc.pop()
let wildcard = wc.pop();
// Select ROUTE if exist for method, default ANY, apply wildcards, alias HEAD requests
let route = routes['METHODS'] && routes['METHODS'][this.method] ? routes['METHODS'][this.method] :
(routes['METHODS'] && routes['METHODS']['ANY'] ? routes['METHODS']['ANY'] :
(wildcard && wildcard['METHODS'] && wildcard['METHODS'][this.method] ? wildcard['METHODS'][this.method] :
(wildcard && wildcard['METHODS'] && wildcard['METHODS']['ANY'] ? wildcard['METHODS']['ANY'] :
(this.method === 'HEAD' && routes['METHODS'] && routes['METHODS']['GET'] ? routes['METHODS']['GET'] :
undefined))))
let route =
routes['METHODS'] && routes['METHODS'][this.method]
? routes['METHODS'][this.method]
: routes['METHODS'] && routes['METHODS']['ANY']
? routes['METHODS']['ANY']
: wildcard && wildcard['METHODS'] && wildcard['METHODS'][this.method]
? wildcard['METHODS'][this.method]
: wildcard && wildcard['METHODS'] && wildcard['METHODS']['ANY']
? wildcard['METHODS']['ANY']
: this.method === 'HEAD' &&
routes['METHODS'] &&
routes['METHODS']['GET']
? routes['METHODS']['GET']
: undefined;
// Check for the requested method
if (route) {
// Assign path parameters
for (let x in route.vars) {
route.vars[x].map(y => this.params[y] = path[x])
route.vars[x].map((y) => (this.params[y] = path[x]));
} // end for
// Set the route used
this.route = route.route
this.route = route.route;
// Set the execution stack
this._stack = route.inherited.concat(route.stack)
// this._stack = route.inherited.concat(route.stack);
this._stack = route.stack;
// Set the stack reporter
this.stack = this._stack.map(x => x.name.trim() !== '' ? x.name : 'unnamed')
this.stack = this._stack.map((x) =>
x.name.trim() !== '' ? x.name : 'unnamed'
);
} else {
this.app._errorStatus = 405
throw new MethodError('Method not allowed',this.method,'/'+path.join('/'))
this.app._errorStatus = 405;
throw new MethodError(
'Method not allowed',
this.method,
'/' + path.join('/')
);
}
// Reference to sample rule
this._sampleRule = {}
this._sampleRule = {};
// Enable sampling
this._sample = LOGGER.sampler(this.app,this)
this._sample = LOGGER.sampler(this.app, this);
} // end parseRequest

@@ -264,4 +344,6 @@

this.app._logLevels[args[0]] >=
this.app._logLevels[this._sample ? this._sample : this.app._logger.level] &&
this._logs.push(this.app._logger.log(...args))
this.app._logLevels[
this._sample ? this._sample : this.app._logger.level
] &&
this._logs.push(this.app._logger.log(...args));
}

@@ -271,9 +353,10 @@

validWildcard(wc) {
return Object.keys(wc[wc.length-1]['METHODS']).length > 1
|| (wc.length > 1 && this.validWildcard(wc.slice(0,-1)))
return (
Object.keys(wc[wc.length - 1]['METHODS']).length > 1 ||
(wc.length > 1 && this.validWildcard(wc.slice(0, -1)))
);
}
} // end REQUEST class
// Export the response object
module.exports = REQUEST
module.exports = REQUEST;

@@ -1,2 +0,2 @@

'use strict'
'use strict';

@@ -9,51 +9,56 @@ /**

const UTILS = require('./utils.js')
const UTILS = require('./utils.js');
const fs = require('fs') // Require Node.js file system
const path = require('path') // Require Node.js path
const { ResponseError, FileError } = require('./errors') // Require custom errors
const fs = require('fs'); // Require Node.js file system
const path = require('path'); // Require Node.js path
const compression = require('./compression'); // Require compression lib
const { ResponseError, FileError } = require('./errors'); // Require custom errors
// Require AWS S3 service
const S3 = require('./s3-service')
const S3 = require('./s3-service');
class RESPONSE {
// Create the constructor function.
constructor(app,request) {
constructor(app, request) {
// Add a reference to the main app
app._response = this
app._response = this;
// Create a reference to the app
this.app = app
this.app = app;
// Create a reference to the request
this._request = request
this._request = request;
// Create a reference to the JSON serializer
this._serializer = app._serializer
this._serializer = app._serializer;
// Set the default state to processing
this._state = 'processing'
this._state = 'processing';
// Default statusCode to 200
this._statusCode = 200
this._statusCode = 200;
// Default the header
this._headers = Object.assign({
// Set the Content-Type by default
'content-type': ['application/json'], //charset=UTF-8
}, app._headers)
this._headers = Object.assign(
{
// Set the Content-Type by default
'content-type': ['application/json'], //charset=UTF-8
},
app._headers
);
// base64 encoding flag
this._isBase64 = app._isBase64
this._isBase64 = app._isBase64;
// compression flag
this._compression = app._compression;
// Default callback function
this._callback = 'callback'
this._callback = 'callback';
// Default Etag support
this._etag = false
this._etag = false;
// Default response object
this._response = {}
this._response = {};
}

@@ -63,29 +68,37 @@

status(code) {
this._statusCode = code
return this
this._statusCode = code;
return this;
}
// Adds a header field
header(key,value,append) {
let _key = key.toLowerCase() // store as lowercase
let _values = value ? (Array.isArray(value) ? value : [value]) : ['']
this._headers[_key] = append ?
this.hasHeader(_key) ? this._headers[_key].concat(_values) : _values
: _values
return this
header(key, value, append) {
let _key = key.toLowerCase(); // store as lowercase
let _values = value ? (Array.isArray(value) ? value : [value]) : [''];
this._headers[_key] = append
? this.hasHeader(_key)
? this._headers[_key].concat(_values)
: _values
: _values;
return this;
}
// Gets a header field
getHeader(key,asArr) {
if (!key) return asArr ? this._headers :
Object.keys(this._headers).reduce((headers,key) =>
Object.assign(headers, { [key]: this._headers[key].toString() })
,{}) // return all headers
return asArr ? this._headers[key.toLowerCase()]
: this._headers[key.toLowerCase()] ?
this._headers[key.toLowerCase()].toString() : undefined
getHeader(key, asArr) {
if (!key)
return asArr
? this._headers
: Object.keys(this._headers).reduce(
(headers, key) =>
Object.assign(headers, { [key]: this._headers[key].toString() }),
{}
); // return all headers
return asArr
? this._headers[key.toLowerCase()]
: this._headers[key.toLowerCase()]
? this._headers[key.toLowerCase()].toString()
: undefined;
}
getHeaders() {
return this._headers
return this._headers;
}

@@ -95,4 +108,4 @@

removeHeader(key) {
delete this._headers[key.toLowerCase()]
return this
delete this._headers[key.toLowerCase()];
return this;
}

@@ -102,3 +115,3 @@

hasHeader(key) {
return this.getHeader(key ? key : '') !== undefined
return this.getHeader(key ? key : '') !== undefined;
}

@@ -108,3 +121,5 @@

json(body) {
this.header('Content-Type','application/json').send(this._serializer(body))
this.header('Content-Type', 'application/json').send(
this._serializer(body)
);
}

@@ -115,7 +130,11 @@

// Check the querystring for callback or cb
let query = this.app._event.queryStringParameters || {}
let cb = query[this.app._callbackName]
let query = this.app._event.queryStringParameters || {};
let cb = query[this.app._callbackName];
this.header('Content-Type','application/json')
.send((cb ? cb.replace(' ','_') : 'callback') + '(' + this._serializer(body) + ')')
this.header('Content-Type', 'application/json').send(
(cb ? cb.replace(' ', '_') : 'callback') +
'(' +
this._serializer(body) +
')'
);
}

@@ -125,3 +144,3 @@

html(body) {
this.header('Content-Type','text/html').send(body)
this.header('Content-Type', 'text/html').send(body);
}

@@ -131,4 +150,4 @@

location(path) {
this.header('Location',UTILS.encodeUrl(path))
return this
this.header('Location', UTILS.encodeUrl(path));
return this;
}

@@ -138,3 +157,3 @@

async redirect(path) {
let statusCode = 302 // default
let statusCode = 302; // default

@@ -144,7 +163,10 @@ try {

if (arguments.length === 2) {
if ([300,301,302,303,307,308].includes(arguments[0])) {
statusCode = arguments[0]
path = arguments[1]
if ([300, 301, 302, 303, 307, 308].includes(arguments[0])) {
statusCode = arguments[0];
path = arguments[1];
} else {
throw new ResponseError(arguments[0] + ' is an invalid redirect status code',arguments[0])
throw new ResponseError(
arguments[0] + ' is an invalid redirect status code',
arguments[0]
);
}

@@ -154,12 +176,13 @@ }

// Auto convert S3 paths to signed URLs
if (UTILS.isS3(path)) path = await this.getLink(path)
if (UTILS.isS3(path)) path = await this.getLink(path);
let url = UTILS.escapeHtml(path)
let url = UTILS.escapeHtml(path);
this.location(path)
.status(statusCode)
.html(`<p>${statusCode} Redirecting to <a href="${url}">${url}</a></p>`)
} catch(e) {
this.error(e)
.html(
`<p>${statusCode} Redirecting to <a href="${url}">${url}</a></p>`
);
} catch (e) {
this.error(e);
}

@@ -169,21 +192,29 @@ } // end redirect

// Convenience method for retrieving a signed link to an S3 bucket object
async getLink(path,expires,callback) {
let params = UTILS.parseS3(path)
async getLink(path, expires, callback) {
let params = UTILS.parseS3(path);
// Default Expires
params.Expires = !isNaN(expires) ? parseInt(expires) : 900
params.Expires = !isNaN(expires) ? parseInt(expires) : 900;
// Default callback
let fn = typeof expires === 'function' ? expires :
typeof callback === 'function' ? callback : e => { if (e) this.error(e) }
let fn =
typeof expires === 'function'
? expires
: typeof callback === 'function'
? callback
: (e) => {
if (e) this.error(e);
};
// getSignedUrl doesn't support .promise()
return await new Promise(r => S3.getSignedUrl('getObject',params, async (e,url) => {
if (e) {
// Execute callback with caught error
await fn(e)
this.error(e) // Throw error if not done in callback
}
r(url) // return the url
}))
return await new Promise((r) =>
S3.getSignedUrl('getObject', params, async (e, url) => {
if (e) {
// Execute callback with caught error
await fn(e);
this.error(e); // Throw error if not done in callback
}
r(url); // return the url
})
);
} // end getLink

@@ -193,73 +224,90 @@

// see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie
cookie(name,value,opts={}) {
cookie(name, value, opts = {}) {
// Set the name and value of the cookie
let cookieString = (typeof name !== 'string' ? name.toString() : name)
+ '=' + encodeURIComponent(UTILS.encodeBody(value))
let cookieString =
(typeof name !== 'string' ? name.toString() : name) +
'=' +
encodeURIComponent(UTILS.encodeBody(value));
// domain (String): Domain name for the cookie
cookieString += opts.domain ? '; Domain=' + opts.domain : ''
cookieString += opts.domain ? '; Domain=' + opts.domain : '';
// expires (Date): Expiry date of the cookie, convert to GMT
cookieString += opts.expires && typeof opts.expires.toUTCString === 'function' ?
'; Expires=' + opts.expires.toUTCString() : ''
cookieString +=
opts.expires && typeof opts.expires.toUTCString === 'function'
? '; Expires=' + opts.expires.toUTCString()
: '';
// httpOnly (Boolean): Flags the cookie to be accessible only by the web server
cookieString += opts.httpOnly && opts.httpOnly === true ? '; HttpOnly' : ''
cookieString += opts.httpOnly && opts.httpOnly === true ? '; HttpOnly' : '';
// maxAge (Number) Set expiry time relative to the current time in milliseconds
cookieString += opts.maxAge && !isNaN(opts.maxAge) ?
'; MaxAge=' + (opts.maxAge/1000|0)
+ (!opts.expires ? '; Expires=' + new Date(Date.now() + opts.maxAge).toUTCString() : '')
: ''
cookieString +=
opts.maxAge && !isNaN(opts.maxAge)
? '; MaxAge=' +
((opts.maxAge / 1000) | 0) +
(!opts.expires
? '; Expires=' + new Date(Date.now() + opts.maxAge).toUTCString()
: '')
: '';
// path (String): Path for the cookie
cookieString += opts.path ? '; Path=' + opts.path : '; Path=/'
cookieString += opts.path ? '; Path=' + opts.path : '; Path=/';
// secure (Boolean): Marks the cookie to be used with HTTPS only
cookieString += opts.secure && opts.secure === true ? '; Secure' : ''
cookieString += opts.secure && opts.secure === true ? '; Secure' : '';
// sameSite (Boolean or String) Value of the “SameSite” Set-Cookie attribute
// see https://tools.ietf.org/html/draft-ietf-httpbis-cookie-same-site-00#section-4.1.1.
cookieString += opts.sameSite !== undefined ? '; SameSite='
+ (opts.sameSite === true ? 'Strict' :
(opts.sameSite === false ? 'Lax' : opts.sameSite ))
: ''
cookieString +=
opts.sameSite !== undefined
? '; SameSite=' +
(opts.sameSite === true
? 'Strict'
: opts.sameSite === false
? 'Lax'
: opts.sameSite)
: '';
this.header('Set-Cookie',cookieString,true)
return this
this.header('Set-Cookie', cookieString, true);
return this;
}
// Convenience method for clearing cookies
clearCookie(name,opts={}) {
let options = Object.assign(opts, { expires: new Date(1), maxAge: -1000 })
return this.cookie(name,'',options)
clearCookie(name, opts = {}) {
let options = Object.assign(opts, { expires: new Date(1), maxAge: -1000 });
return this.cookie(name, '', options);
}
// Set content-disposition header and content type
attachment(filename) {
// Check for supplied filename/path
let name = typeof filename === 'string' && filename.trim().length > 0 ? path.parse(filename) : undefined
this.header('Content-Disposition','attachment' + (name ? '; filename="' + name.base + '"' : ''))
let name =
typeof filename === 'string' && filename.trim().length > 0
? path.parse(filename)
: undefined;
this.header(
'Content-Disposition',
'attachment' + (name ? '; filename="' + name.base + '"' : '')
);
// If name exits, attempt to set the type
if (name) { this.type(name.ext) }
return this
if (name) {
this.type(name.ext);
}
return this;
}
// Convenience method combining attachment() and sendFile()
download(file, filename, options, callback) {
let name = filename;
let opts = typeof options === 'object' ? options : {};
let fn = typeof callback === 'function' ? callback : undefined;
let name = filename
let opts = typeof options === 'object' ? options : {}
let fn = typeof callback === 'function' ? callback : undefined
// Add optional parameter support for callback
if (typeof filename === 'function') {
name = undefined
fn = filename
name = undefined;
fn = filename;
} else if (typeof options === 'function') {
fn = options
fn = options;
}

@@ -269,26 +317,25 @@

if (typeof filename === 'object') {
name = undefined
opts = filename
name = undefined;
opts = filename;
}
// Add the Content-Disposition header
this.attachment(name ? name : (typeof file === 'string' ? path.basename(file) : null) )
this.attachment(
name ? name : typeof file === 'string' ? path.basename(file) : null
);
// Send the file
this.sendFile(file, opts, fn)
this.sendFile(file, opts, fn);
}
// Convenience method for returning static files
async sendFile(file, options, callback) {
let buffer, modified;
let buffer, modified
let opts = typeof options === 'object' ? options : {};
let fn = typeof callback === 'function' ? callback : () => {};
let opts = typeof options === 'object' ? options : {}
let fn = typeof callback === 'function' ? callback : () => {}
// Add optional parameter support
if (typeof options === 'function') {
fn = options
fn = options;
}

@@ -298,33 +345,33 @@

try {
// Create buffer based on input
if (typeof file === 'string') {
let filepath = file.trim();
let filepath = file.trim()
// If an S3 file identifier
if (/^s3:\/\//i.test(filepath)) {
let params = UTILS.parseS3(filepath);
let params = UTILS.parseS3(filepath)
// Attempt to get the object from S3
let data = await S3.getObject(params).promise()
let data = await S3.getObject(params).promise();
// Set results, type and header
buffer = data.Body
modified = data.LastModified
this.type(data.ContentType)
this.header('ETag',data.ETag)
buffer = data.Body;
modified = data.LastModified;
this.type(data.ContentType);
this.header('ETag', data.ETag);
// else try and load the file locally
// else try and load the file locally
} else {
buffer = fs.readFileSync((opts.root ? opts.root : '') + filepath)
modified = opts.lastModified !== false ? fs.statSync((opts.root ? opts.root : '') + filepath).mtime : undefined
this.type(path.extname(filepath))
buffer = fs.readFileSync((opts.root ? opts.root : '') + filepath);
modified =
opts.lastModified !== false
? fs.statSync((opts.root ? opts.root : '') + filepath).mtime
: undefined;
this.type(path.extname(filepath));
}
// If the input is a buffer, pass through
// If the input is a buffer, pass through
} else if (Buffer.isBuffer(file)) {
buffer = file
buffer = file;
} else {
throw new FileError('Invalid file',{path:file})
throw new FileError('Invalid file', { path: file });
}

@@ -334,5 +381,5 @@

if (typeof opts.headers === 'object') {
Object.keys(opts.headers).map(header => {
this.header(header,opts.headers[header])
})
Object.keys(opts.headers).map((header) => {
this.header(header, opts.headers[header]);
});
}

@@ -343,8 +390,5 @@

if (opts.cacheControl !== true && opts.cacheControl !== undefined) {
this.cache(opts.cacheControl)
this.cache(opts.cacheControl);
} else {
this.cache(
!isNaN(opts.maxAge) ? opts.maxAge : 0,
opts.private
)
this.cache(!isNaN(opts.maxAge) ? opts.maxAge : 0, opts.private);
}

@@ -355,87 +399,110 @@ }

if (opts.lastModified !== false) {
this.modified(opts.lastModified ? opts.lastModified : modified)
this.modified(opts.lastModified ? opts.lastModified : modified);
}
// Execute callback
await fn()
await fn();
// Set base64 encoding flag
this._isBase64 = true
this._isBase64 = true;
// Convert buffer to base64 string
this.send(buffer.toString('base64'))
} catch(e) { // TODO: Add second catch?
this.send(buffer.toString('base64'));
} catch (e) {
// TODO: Add second catch?
// Execute callback with caught error
await fn(e)
await fn(e);
// If missing file
if (e.code === 'ENOENT') {
this.error(new FileError('No such file',e))
this.error(new FileError('No such file', e));
} else {
this.error(e) // Throw error if not done in callback
this.error(e); // Throw error if not done in callback
}
}
} // end sendFile
// Convenience method for setting type
type(type) {
let mimeType = UTILS.mimeLookup(type,this.app._mimeTypes)
let mimeType = UTILS.mimeLookup(type, this.app._mimeTypes);
if (mimeType) {
this.header('Content-Type',mimeType)
this.header('Content-Type', mimeType);
}
return this
return this;
}
// Convenience method for sending status codes
sendStatus(status) {
this.status(status).send(UTILS.statusLookup(status))
this.status(status).send(UTILS.statusLookup(status));
}
// Convenience method for setting CORS headers
cors(options) {
const opts = typeof options === 'object' ? options : {}
const opts = typeof options === 'object' ? options : {};
// Check for existing headers
let acao = this.getHeader('Access-Control-Allow-Origin')
let acam = this.getHeader('Access-Control-Allow-Methods')
let acah = this.getHeader('Access-Control-Allow-Headers')
let acao = this.getHeader('Access-Control-Allow-Origin');
let acam = this.getHeader('Access-Control-Allow-Methods');
let acah = this.getHeader('Access-Control-Allow-Headers');
// Default CORS headers
this.header('Access-Control-Allow-Origin',opts.origin ? opts.origin : (acao ? acao : '*'))
this.header('Access-Control-Allow-Methods',opts.methods ? opts.methods : (acam ? acam : 'GET, PUT, POST, DELETE, OPTIONS'))
this.header('Access-Control-Allow-Headers',opts.headers ? opts.headers : (acah ? acah : 'Content-Type, Authorization, Content-Length, X-Requested-With'))
this.header(
'Access-Control-Allow-Origin',
opts.origin ? opts.origin : acao ? acao : '*'
);
this.header(
'Access-Control-Allow-Methods',
opts.methods
? opts.methods
: acam
? acam
: 'GET, PUT, POST, DELETE, OPTIONS'
);
this.header(
'Access-Control-Allow-Headers',
opts.headers
? opts.headers
: acah
? acah
: 'Content-Type, Authorization, Content-Length, X-Requested-With'
);
// Optional CORS headers
if(opts.maxAge && !isNaN(opts.maxAge)) this.header('Access-Control-Max-Age',(opts.maxAge/1000|0).toString())
if(opts.credentials) this.header('Access-Control-Allow-Credentials',opts.credentials.toString())
if(opts.exposeHeaders) this.header('Access-Control-Expose-Headers',opts.exposeHeaders)
if (opts.maxAge && !isNaN(opts.maxAge))
this.header(
'Access-Control-Max-Age',
((opts.maxAge / 1000) | 0).toString()
);
if (opts.credentials)
this.header(
'Access-Control-Allow-Credentials',
opts.credentials.toString()
);
if (opts.exposeHeaders)
this.header('Access-Control-Expose-Headers', opts.exposeHeaders);
return this
return this;
}
// Enable/Disable Etag
etag(enable) {
this._etag = enable === true ? true : false
return this
this._etag = enable === true ? true : false;
return this;
}
// Add cache-control headers
cache(maxAge,isPrivate=false) {
cache(maxAge, isPrivate = false) {
// if custom string value
if (maxAge !== true && maxAge !== undefined && typeof maxAge === 'string') {
this.header('Cache-Control', maxAge)
this.header('Cache-Control', maxAge);
} else if (maxAge === false) {
this.header('Cache-Control', 'no-cache, no-store, must-revalidate')
this.header('Cache-Control', 'no-cache, no-store, must-revalidate');
} else {
maxAge = maxAge && !isNaN(maxAge) ? (maxAge/1000|0) : 0
this.header('Cache-Control', (isPrivate === true ? 'private, ' : '') + 'max-age=' + maxAge)
this.header('Expires',new Date(Date.now() + maxAge).toUTCString())
maxAge = maxAge && !isNaN(maxAge) ? (maxAge / 1000) | 0 : 0;
this.header(
'Cache-Control',
(isPrivate === true ? 'private, ' : '') + 'max-age=' + maxAge
);
this.header('Expires', new Date(Date.now() + maxAge).toUTCString());
}
return this
return this;
}

@@ -446,7 +513,11 @@

if (date !== false) {
let lastModified = date && typeof date.toUTCString === 'function' ? date :
date && Date.parse(date) ? new Date(date) : new Date()
this.header('Last-Modified', lastModified.toUTCString())
let lastModified =
date && typeof date.toUTCString === 'function'
? date
: date && Date.parse(date)
? new Date(date)
: new Date();
this.header('Last-Modified', lastModified.toUTCString());
}
return this
return this;
}

@@ -456,10 +527,10 @@

send(body) {
// Generate Etag
if ( this._etag // if etag support enabled
&& ['GET','HEAD'].includes(this._request.method)
&& !this.hasHeader('etag')
&& this._statusCode === 200
if (
this._etag && // if etag support enabled
['GET', 'HEAD'].includes(this._request.method) &&
!this.hasHeader('etag') &&
this._statusCode === 200
) {
this.header('etag','"'+UTILS.generateEtag(body)+'"')
this.header('etag', '"' + UTILS.generateEtag(body) + '"');
}

@@ -469,39 +540,82 @@

if (
this._request.headers['if-none-match']
&& this._request.headers['if-none-match'] === this.getHeader('etag')
this._request.headers['if-none-match'] &&
this._request.headers['if-none-match'] === this.getHeader('etag')
) {
this.status(304)
body = ''
this.status(304);
body = '';
}
let headers = {};
let cookies = {};
if (this._request.payloadVersion === '2.0') {
if (this._headers['set-cookie']) {
cookies = { cookies: this._headers['set-cookie'] };
delete this._headers['set-cookie'];
}
}
if (this._request._multiValueSupport) {
headers = { multiValueHeaders: this._headers };
} else {
headers = { headers: UTILS.stringifyHeaders(this._headers) };
}
// Create the response
this._response = Object.assign({},
this._request._multiValueSupport ? { multiValueHeaders: this._headers }
: { headers: UTILS.stringifyHeaders(this._headers) },
this._response = Object.assign(
{},
headers,
cookies,
{
statusCode: this._statusCode,
body: this._request.method === 'HEAD' ? '' : UTILS.encodeBody(body,this._serializer),
isBase64Encoded: this._isBase64
body:
this._request.method === 'HEAD'
? ''
: UTILS.encodeBody(body, this._serializer),
isBase64Encoded: this._isBase64,
},
this._request.interface === 'alb' ? { statusDescription: `${this._statusCode} ${UTILS.statusLookup(this._statusCode)}` } : {}
)
this._request.interface === 'alb'
? {
statusDescription: `${this._statusCode} ${UTILS.statusLookup(
this._statusCode
)}`,
}
: {}
);
// Compress the body
if (this._compression && this._response.body) {
const { data, contentEncoding } = compression.compress(
this._response.body,
this._request.headers
);
if (contentEncoding) {
Object.assign(this._response, {
body: data.toString('base64'),
isBase64Encoded: true,
});
if (this._response.multiValueHeaders) {
this._response.multiValueHeaders['content-encoding'] = [
contentEncoding,
];
} else {
this._response.headers['content-encoding'] = contentEncoding;
}
}
}
// Trigger the callback function
this.app._callback(null, this._response, this)
this.app._callback(null, this._response, this);
} // end send
// Trigger API error
error(code,e,detail) {
detail = typeof code !== 'number' && e !== undefined ? e : detail
e = typeof code !== 'number' ? code : e
code = typeof code === 'number' ? code : undefined
this.app.catchErrors(e,this,code,detail)
error(code, e, detail) {
detail = typeof code !== 'number' && e !== undefined ? e : detail;
e = typeof code !== 'number' ? code : e;
code = typeof code === 'number' ? code : undefined;
this.app.catchErrors(e, this, code, detail);
} // end error
} // end Response class
// Export the response object
module.exports = RESPONSE
module.exports = RESPONSE;

@@ -1,2 +0,2 @@

'use strict'
'use strict';

@@ -10,5 +10,5 @@ /**

// Require AWS SDK
const AWS = require('aws-sdk') // AWS SDK
const AWS = require('aws-sdk'); // AWS SDK
// Export
module.exports = new AWS.S3()
module.exports = new AWS.S3();

@@ -1,2 +0,2 @@

'use strict'
'use strict';

@@ -81,3 +81,3 @@ /**

510: 'Not Extended',
511: 'Network Authentication Required'
}
511: 'Network Authentication Required',
};

@@ -1,2 +0,2 @@

'use strict'
'use strict';

@@ -9,5 +9,5 @@ /**

const QS = require('querystring') // Require the querystring library
const crypto = require('crypto') // Require Node.js crypto library
const { FileError } = require('./errors') // Require custom errors
const QS = require('querystring'); // Require the querystring library
const crypto = require('crypto'); // Require Node.js crypto library
const { FileError } = require('./errors'); // Require custom errors

@@ -19,91 +19,105 @@ const entityMap = {

'"': '&quot;',
'\'': '&#39;'
}
"'": '&#39;',
};
exports.escapeHtml = html => html.replace(/[&<>"']/g, s => entityMap[s])
exports.escapeHtml = (html) => html.replace(/[&<>"']/g, (s) => entityMap[s]);
// From encodeurl by Douglas Christopher Wilson
let ENCODE_CHARS_REGEXP = /(?:[^\x21\x25\x26-\x3B\x3D\x3F-\x5B\x5D\x5F\x61-\x7A\x7E]|%(?:[^0-9A-Fa-f]|[0-9A-Fa-f][^0-9A-Fa-f]|$))+/g
let UNMATCHED_SURROGATE_PAIR_REGEXP = /(^|[^\uD800-\uDBFF])[\uDC00-\uDFFF]|[\uD800-\uDBFF]([^\uDC00-\uDFFF]|$)/g
let UNMATCHED_SURROGATE_PAIR_REPLACE = '$1\uFFFD$2'
let ENCODE_CHARS_REGEXP =
/(?:[^\x21\x25\x26-\x3B\x3D\x3F-\x5B\x5D\x5F\x61-\x7A\x7E]|%(?:[^0-9A-Fa-f]|[0-9A-Fa-f][^0-9A-Fa-f]|$))+/g;
let UNMATCHED_SURROGATE_PAIR_REGEXP =
/(^|[^\uD800-\uDBFF])[\uDC00-\uDFFF]|[\uD800-\uDBFF]([^\uDC00-\uDFFF]|$)/g;
let UNMATCHED_SURROGATE_PAIR_REPLACE = '$1\uFFFD$2';
exports.encodeUrl = url => String(url)
.replace(UNMATCHED_SURROGATE_PAIR_REGEXP, UNMATCHED_SURROGATE_PAIR_REPLACE)
.replace(ENCODE_CHARS_REGEXP, encodeURI)
exports.encodeUrl = (url) =>
String(url)
.replace(UNMATCHED_SURROGATE_PAIR_REGEXP, UNMATCHED_SURROGATE_PAIR_REPLACE)
.replace(ENCODE_CHARS_REGEXP, encodeURI);
const encodeBody = (body, serializer) => {
const encode = typeof serializer === 'function' ? serializer : JSON.stringify;
return typeof body === 'object'
? encode(body)
: body && typeof body !== 'string'
? body.toString()
: body
? body
: '';
};
exports.encodeBody = encodeBody;
const encodeBody = (body,serializer) => {
const encode = typeof serializer === 'function' ? serializer : JSON.stringify
return typeof body === 'object' ? encode(body) : (body && typeof body !== 'string' ? body.toString() : (body ? body : ''))
}
exports.parsePath = (path) => {
return path
? path
.trim()
.split('?')[0]
.replace(/^\/(.*?)(\/)*$/, '$1')
.split('/')
: [];
};
exports.encodeBody = encodeBody
exports.parsePath = path => {
return path ? path.trim().split('?')[0].replace(/^\/(.*?)(\/)*$/,'$1').split('/') : []
}
exports.parseBody = body => {
exports.parseBody = (body) => {
try {
return JSON.parse(body)
} catch(e) {
return body
return JSON.parse(body);
} catch (e) {
return body;
}
}
};
// Parses auth values into known formats
const parseAuthValue = (type,value) => {
const parseAuthValue = (type, value) => {
switch (type) {
case 'Basic': {
let creds = Buffer.from(value, 'base64').toString().split(':')
return { type, value, username: creds[0], password: creds[1] ? creds[1] : null }
let creds = Buffer.from(value, 'base64').toString().split(':');
return {
type,
value,
username: creds[0],
password: creds[1] ? creds[1] : null,
};
}
case 'OAuth': {
let params = QS.parse(value.replace(/",\s*/g,'&').replace(/"/g,'').trim())
return Object.assign({ type, value }, params)
let params = QS.parse(
value.replace(/",\s*/g, '&').replace(/"/g, '').trim()
);
return Object.assign({ type, value }, params);
}
default: {
return { type, value }
return { type, value };
}
}
}
};
exports.parseAuth = authStr => {
let auth = authStr && typeof authStr === 'string' ? authStr.split(' ') : []
return auth.length > 1 && ['Bearer','Basic','Digest','OAuth'].includes(auth[0]) ?
parseAuthValue(auth[0], auth.slice(1).join(' ').trim()) :
{ type: 'none', value: null }
}
exports.parseAuth = (authStr) => {
let auth = authStr && typeof authStr === 'string' ? authStr.split(' ') : [];
return auth.length > 1 &&
['Bearer', 'Basic', 'Digest', 'OAuth'].includes(auth[0])
? parseAuthValue(auth[0], auth.slice(1).join(' ').trim())
: { type: 'none', value: null };
};
const mimeMap = require('./mimemap.js'); // MIME Map
exports.mimeLookup = (input, custom = {}) => {
let type = input.trim().replace(/^\./, '');
const mimeMap = require('./mimemap.js') // MIME Map
exports.mimeLookup = (input,custom={}) => {
let type = input.trim().replace(/^\./,'')
// If it contains a slash, return unmodified
if (/.*\/.*/.test(type)) {
return input.trim()
return input.trim();
} else {
// Lookup mime type
let mime = Object.assign(mimeMap,custom)[type]
return mime ? mime : false
let mime = Object.assign(mimeMap, custom)[type];
return mime ? mime : false;
}
}
};
const statusCodes = require('./statusCodes.js') // MIME Map
const statusCodes = require('./statusCodes.js'); // MIME Map
exports.statusLookup = status => {
return status in statusCodes ? statusCodes[status] : 'Unknown'
}
exports.statusLookup = (status) => {
return status in statusCodes ? statusCodes[status] : 'Unknown';
};
// Parses routes into readable array
const extractRoutes = (routes,table=[]) => {
const extractRoutes = (routes, table = []) => {
// Loop through all routes

@@ -113,59 +127,79 @@ for (let route in routes['ROUTES']) {

for (let method in routes['ROUTES'][route]['METHODS']) {
table.push([method,routes['ROUTES'][route]['METHODS'][method].path])
table.push([
method,
routes['ROUTES'][route]['METHODS'][method].path,
routes['ROUTES'][route]['METHODS'][method].stack.map((x) =>
x.name.trim() !== '' ? x.name : 'unnamed'
),
]);
}
extractRoutes(routes['ROUTES'][route],table)
extractRoutes(routes['ROUTES'][route], table);
}
return table
}
return table;
};
exports.extractRoutes = extractRoutes
exports.extractRoutes = extractRoutes;
// Generate an Etag for the supplied value
exports.generateEtag = data =>
crypto.createHash('sha256').update(encodeBody(data)).digest('hex').substr(0,32)
exports.generateEtag = (data) =>
crypto
.createHash('sha256')
.update(encodeBody(data))
.digest('hex')
.substr(0, 32);
// Check if valid S3 path
exports.isS3 = path => /^s3:\/\/.+\/.+/i.test(path)
exports.isS3 = (path) => /^s3:\/\/.+\/.+/i.test(path);
// Parse S3 path
exports.parseS3 = path => {
if (!this.isS3(path)) throw new FileError('Invalid S3 path',{path})
let s3object = path.replace(/^s3:\/\//i,'').split('/')
return { Bucket: s3object.shift(), Key: s3object.join('/') }
}
exports.parseS3 = (path) => {
if (!this.isS3(path)) throw new FileError('Invalid S3 path', { path });
let s3object = path.replace(/^s3:\/\//i, '').split('/');
return { Bucket: s3object.shift(), Key: s3object.join('/') };
};
// Deep Merge
exports.deepMerge = (a,b) => {
Object.keys(b).forEach(key => (key in a) ?
this.deepMerge(a[key],b[key]) : Object.assign(a,b) )
return a
}
exports.deepMerge = (a, b) => {
Object.keys(b).forEach((key) =>
key in a ? this.deepMerge(a[key], b[key]) : Object.assign(a, b)
);
return a;
};
exports.mergeObjects = (obj1,obj2) =>
Object.keys(Object.assign({},obj1,obj2)).reduce((acc,key) => {
if (obj1[key] && obj2[key] && obj1[key].every(e => obj2[key].includes(e))) {
return Object.assign(acc,{ [key]: obj1[key] })
// Concatenate arrays when merging two objects
exports.mergeObjects = (obj1, obj2) =>
Object.keys(Object.assign({}, obj1, obj2)).reduce((acc, key) => {
if (
obj1[key] &&
obj2[key] &&
obj1[key].every((e) => obj2[key].includes(e))
) {
return Object.assign(acc, { [key]: obj1[key] });
} else {
return Object.assign(acc,{
[key]: obj1[key] ? obj2[key] ? obj1[key].concat(obj2[key]) : obj1[key] : obj2[key]
})
return Object.assign(acc, {
[key]: obj1[key]
? obj2[key]
? obj1[key].concat(obj2[key])
: obj1[key]
: obj2[key],
});
}
},{})
}, {});
// Concats values from an array to ',' separated string
exports.fromArray = val =>
val && val instanceof Array ? val.toString() : undefined
exports.fromArray = (val) =>
val && val instanceof Array ? val.toString() : undefined;
// Stringify multi-value headers
exports.stringifyHeaders = headers =>
Object.keys(headers)
.reduce((acc,key) =>
Object.assign(acc,{
exports.stringifyHeaders = (headers) =>
Object.keys(headers).reduce(
(acc, key) =>
Object.assign(acc, {
// set-cookie cannot be concatenated with a comma
[key]: key === 'set-cookie' ? headers[key].slice(-1)[0] : headers[key].toString()
})
,{})
[key]:
key === 'set-cookie'
? headers[key].slice(-1)[0]
: headers[key].toString(),
}),
{}
);
{
"name": "lambda-api",
"version": "0.10.7",
"version": "0.11.0",
"description": "Lightweight web framework for your serverless applications",

@@ -8,6 +8,9 @@ "main": "index.js",

"scripts": {
"test": "mocha --check-leaks --recursive",
"test-cov": "nyc --reporter=html mocha --check-leaks --recursive",
"test-ci": "eslint . && nyc npm test && nyc report --reporter=text-lcov | ./node_modules/coveralls/bin/coveralls.js",
"lint": "eslint ."
"test": "jest unit",
"prettier": "prettier --check .",
"test-cov": "jest unit --coverage",
"test-ci": "eslint . && prettier --check . && jest unit --coverage && cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js",
"lint": "eslint .",
"prepublishOnly": "npm test && npm run lint",
"changelog": "git log $(git describe --tags --abbrev=0)..HEAD --oneline"
},

@@ -40,23 +43,14 @@ "repository": {

"bluebird": "^3.7.2",
"chai": "^4.2.0",
"coveralls": "^3.1.0",
"eslint": "^4.19.1",
"eslint-config-airbnb-base": "^12.1.0",
"eslint-plugin-import": "^2.20.2",
"istanbul": "^0.4.5",
"mocha": "^4.1.0",
"mocha-lcov-reporter": "^1.3.0",
"nyc": "^14.1.1",
"eslint": "^7.22.0",
"eslint-config-prettier": "^8.3.0",
"jest": "^26.6.3",
"prettier": "^2.3.2",
"sinon": "^4.5.0"
},
"files": [
"LICENSE",
"README.md",
"index.js",
"index.d.ts",
"lib/"
],
"engines": {
"node": ">= 8.10.0"
}
]
}

Sorry, the diff of this file is too big to display

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