Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

claudia-api-builder

Package Overview
Dependencies
Maintainers
1
Versions
38
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

claudia-api-builder - npm Package Compare versions

Comparing version 1.6.0 to 2.0.0

src/convert-api-gw-proxy-request.js

9

package.json
{
"name": "claudia-api-builder",
"version": "1.6.0",
"version": "2.0.0",
"description": "Simplify AWS ApiGateway handling",

@@ -32,3 +32,4 @@ "license": "MIT",

"pretest": "jshint src spec && jscs src spec",
"test": "node spec/support/jasmine-runner.js"
"test": "node spec/support/jasmine-runner.js",
"debug": "node debug spec/support/jasmine-runner.js"
},

@@ -38,4 +39,4 @@ "author": "Gojko Adzic <gojko@gojko.com> http://gojko.net",

"bluebird": "^3.3.0",
"jasmine": "^2.4.1",
"jasmine-spec-reporter": "^2.4.0",
"jasmine": "^2.5.2",
"jasmine-spec-reporter": "^2.7.0",
"jscs": "^2.9.0",

@@ -42,0 +43,0 @@ "jshint": "^2.9.1"

#Claudia API Builder
[![npm](https://img.shields.io/npm/v/claudia-api-builder.svg?maxAge=2592000?style=plastic)](https://www.npmjs.com/package/claudia-api-builder)
[![npm](https://img.shields.io/npm/dt/claudia-api-builder.svg?maxAge=2592000?style=plastic)](https://www.npmjs.com/package/claudia-api-builder)
[![npm](https://img.shields.io/npm/l/claudia-api-builder.svg?maxAge=2592000?style=plastic)](https://github.com/claudiajs/claudia-api-builder/blob/master/LICENSE)
[![Build Status](https://travis-ci.org/claudiajs/claudia-api-builder.svg?branch=master)](https://travis-ci.org/claudiajs/claudia-api-builder)
[![Join the chat at https://gitter.im/claudiajs/claudia](https://badges.gitter.im/claudiajs/claudia.svg)](https://gitter.im/claudiajs/claudia?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
Claudia API Builder makes it possible to use AWS API Gateway as if it were a lightweight JavaScript web server, so it helps developers get started easily and reduces the learning curve required to launch web APIs in AWS. [Check out this video to see how to create and deploy an API in under 5 minutes](https://vimeo.com/156232471).

@@ -4,0 +10,0 @@

# Release history
## 2.0.0, 27. September 2016
- support for API Gateway [Lambda Proxy Integrations](docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-create-api-as-simple-proxy-for-lambda.html)
- support for routing via .ANY method
- support for selecting request type (either CLAUDIA_API_BUILDER or AWS_PROXY)
- support for dynamic response codes
- completed CORS support (all HTTP method request handlers can now also limit CORS allowed origins, instead of just the OPTIONS one)
- support for asynchronous CORS origin filters
- stopping support for Node 0.10
- (will only work with claudia 2.x)
## 1.6.0, 26 August 2016

@@ -4,0 +15,0 @@

@@ -1,5 +0,21 @@

/*global module, require */
module.exports = function ApiBuilder(components) {
/*global module, require, Promise, console */
var convertApiGWProxyRequest = require('./convert-api-gw-proxy-request'),
lowercaseKeys = require('./lowercase-keys');
module.exports = function ApiBuilder(options) {
'use strict';
var self = this,
getRequestFormat = function (newFormat) {
var supportedFormats = ['AWS_PROXY', 'CLAUDIA_API_BUILDER'];
if (!newFormat) {
return 'CLAUDIA_API_BUILDER';
} else {
if (supportedFormats.indexOf(newFormat) >= 0) {
return newFormat;
} else {
throw 'Unsupported request format ' + newFormat;
}
}
},
requestFormat = getRequestFormat(options && options.requestFormat),
logger = (options && options.logger) || console.log,
methodConfigurations = {},

@@ -12,88 +28,218 @@ routes = {},

authorizers,
v2DeprecationWarning = function (what) {
logger(what + ' are deprecated, and be removed in claudia api builder v3. Check https://claudiajs.com/tutorials/migrating_to_2.html');
},
supportedMethods = ['GET', 'POST', 'PUT', 'DELETE', 'HEAD', 'PATCH'],
interceptCallback,
prompter = (components && components.prompter) || require('./ask'),
prompter = (options && options.prompter) || require('./ask'),
isApiResponse = function (obj) {
return obj && (typeof obj === 'object') && (Object.getPrototypeOf(obj) === self.ApiResponse.prototype);
},
packResult = function (handlerResult, route, method) {
var path = route.replace(/^\//, ''),
customHeaders = methodConfigurations[path] && methodConfigurations[path][method] && methodConfigurations[path][method].success && methodConfigurations[path][method].success.headers;
mergeObjects = function (from, to) {
Object.keys(from).forEach(function (key) {
to[key] = from[key];
});
return to;
},
isRedirect = function (code) {
return /3[0-9][0-9]/.test(code);
},
getContentType = function (configuration, result) {
var staticHeader = (configuration && configuration.headers && lowercaseKeys(configuration.headers)['content-type']),
dynamicHeader = (result && isApiResponse(result) && result.headers && lowercaseKeys(result.headers)['content-type']),
staticConfig = configuration && configuration.contentType;
if (isApiResponse(handlerResult)) {
if (!customHeaders) {
throw 'cannot use ApiResponse without enumerating headers in ' + method + ' ' + route;
return dynamicHeader || staticHeader || staticConfig || 'application/json';
},
getStatusCode = function (configuration, result, resultType) {
var defaultCode = {
'success': 200,
'error': 500
},
staticCode = (configuration && configuration.code) || (typeof configuration === 'number' && configuration),
dynamicCode = (result && isApiResponse(result) && result.code);
return dynamicCode || staticCode || defaultCode[resultType];
},
getRedirectLocation = function (configuration, result) {
var dynamicHeader = result && isApiResponse(result) && result.headers && lowercaseKeys(result.headers).location,
dynamicBody = isApiResponse(result) ? result.response : result,
staticHeader = configuration && configuration.headers && lowercaseKeys(configuration.headers).location;
return dynamicHeader || dynamicBody || staticHeader;
},
getCanonicalContentType = function (contentType) {
return (contentType && contentType.split(';')[0]) || 'application/json';
},
getSuccessBody = function (contentType, handlerResult) {
var contents = isApiResponse(handlerResult) ? handlerResult.response : handlerResult;
if (getCanonicalContentType(contentType) === 'application/json') {
if (contents === '' || contents === undefined) {
return '{}';
} else {
return JSON.stringify(contents);
}
if (!Array.isArray(customHeaders)) {
throw 'cannot use ApiResponse with default header values in ' + method + ' ' + route;
} else {
if (!contents) {
return '';
} else if (typeof contents === 'object') {
return JSON.stringify(contents);
} else {
return String(contents);
}
Object.keys(handlerResult.headers).forEach(function (header) {
if (customHeaders.indexOf(header) < 0) {
throw 'unexpected header ' + header + ' in ' + method + ' ' + route;
}
});
return { response: handlerResult.response, headers: handlerResult.headers };
}
if (customHeaders && Array.isArray(customHeaders)) {
return { response: handlerResult, headers: {} };
},
isError = function (object) {
return object && (object.message !== undefined) && object.stack;
},
logError = function (err) {
var logInfo = err;
if (isApiResponse(err)) {
logInfo = JSON.stringify(err);
} else if (isError(err)) {
logInfo = err.stack;
}
return handlerResult;
logger(logInfo);
},
isThenable = function (param) {
return param && param.then && (typeof param.then === 'function');
getErrorBody = function (contentType, handlerResult) {
var contents = isApiResponse(handlerResult) ? handlerResult.response : handlerResult;
if (isError(contents)) {
contents = contents.message;
}
if (getCanonicalContentType(contentType) === 'application/json') {
return JSON.stringify({errorMessage: contents || '' });
} else {
return contents || '';
}
},
routeEvent = function (event, context /*, callback*/) {
var handler, result, path;
context.callbackWaitsForEmptyEventLoop = false;
if (event && event.context && event.context.path && event.context.method) {
path = event.context.path;
if (event.context.method === 'OPTIONS' && customCorsHandler) {
return context.done(null, customCorsHandler(event));
getBody = function (contentType, handlerResult, resultType) {
return resultType === 'success' ? getSuccessBody(contentType, handlerResult) : getErrorBody(contentType, handlerResult);
},
packResult = function (handlerResult, routingInfo, corsHeaders, resultType) {
var path = routingInfo.path.replace(/^\//, ''),
method = routingInfo.method,
configuration = methodConfigurations[path] && methodConfigurations[path][method] && methodConfigurations[path][method][resultType],
customHeaders = configuration && configuration.headers,
contentType = getContentType(configuration, handlerResult),
statusCode = getStatusCode(configuration, handlerResult, resultType),
result = {
statusCode: statusCode,
headers: { 'Content-Type': contentType },
body: getBody(contentType, handlerResult, resultType)
};
mergeObjects(corsHeaders, result.headers);
if (customHeaders) {
if (Array.isArray(customHeaders)) {
v2DeprecationWarning('enumerated headers');
} else {
mergeObjects(customHeaders, result.headers);
}
handler = routes[path] && routes[path][event.context.method];
if (handler) {
try {
event.lambdaContext = context;
result = handler(event);
if (isThenable(result)) {
return result.then(function (promiseResult) {
context.done(null, packResult(promiseResult, path, event.context.method));
}, function (promiseError) {
context.done(promiseError);
});
} else {
context.done(null, packResult(result, path, event.context.method));
}
} catch (e) {
context.done(e);
}
}
if (isApiResponse(handlerResult)) {
mergeObjects(handlerResult.headers, result.headers);
}
if (isRedirect(statusCode)) {
result.headers.Location = getRedirectLocation(configuration, handlerResult);
}
return result;
},
getCorsHeaders = function (request, methods) {
if (methods.indexOf('ANY') >= 0) {
methods = supportedMethods;
}
return Promise.resolve().then(function () {
if (customCorsHandler === false) {
return '';
} else if (customCorsHandler) {
return customCorsHandler(request);
} else {
context.done('no handler for ' + event.context.method + ' ' + event.context.path);
return '*';
}
} else {
if (unsupportedEventCallback) {
unsupportedEventCallback.apply(this, arguments);
}).then(function (corsOrigin) {
return {
'Access-Control-Allow-Origin': corsOrigin,
'Access-Control-Allow-Headers': corsOrigin && (customCorsHeaders || 'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'),
'Access-Control-Allow-Methods': corsOrigin && methods.sort().join(',') + ',OPTIONS'
};
});
},
routeEvent = function (routingInfo, event, context) {
var handler;
if (!routingInfo) {
throw 'routingInfo not set';
}
handler = routes[routingInfo.path] && (
routes[routingInfo.path][routingInfo.method] ||
routes[routingInfo.path].ANY
);
return getCorsHeaders(event, Object.keys(routes[routingInfo.path] || {})).then(function (corsHeaders) {
if (routingInfo.method === 'OPTIONS') {
return {
statusCode: 200,
body: '',
headers: corsHeaders
};
} else if (handler) {
return Promise.resolve().then(function () {
return handler(event, context);
}).then(function (result) {
return packResult(result, routingInfo, corsHeaders, 'success');
}).catch(function (error) {
logError(error);
return packResult(error, routingInfo, corsHeaders, 'error');
});
} else {
context.done('event must contain context.path and context.method');
return Promise.reject('no handler for ' + routingInfo.method + ' ' + routingInfo.path);
}
});
},
getRequestRoutingInfo = function (request) {
if (requestFormat === 'AWS_PROXY') {
if (!request.requestContext) {
return {};
}
return {
path: request.requestContext.resourcePath,
method: request.requestContext.httpMethod
};
} else {
return request.context || {};
}
};
['GET', 'POST', 'PUT', 'DELETE', 'HEAD', 'PATCH'].forEach(function (method) {
self[method.toLowerCase()] = function (route, handler, options) {
var pathPart = route.replace(/^\//, ''),
canonicalRoute = route;
if (!/^\//.test(canonicalRoute)) {
canonicalRoute = '/' + route;
},
getRequest = function (event, context) {
if (requestFormat === 'AWS_PROXY' || requestFormat === 'DEPRECATED') {
return event;
} else {
return convertApiGWProxyRequest(event, context);
}
if (!methodConfigurations[pathPart]) {
methodConfigurations[pathPart] = {} ;
},
executeInterceptor = function (request, context) {
if (!interceptCallback) {
return Promise.resolve(request);
} else {
return Promise.resolve().then(function () {
return interceptCallback(request, context);
});
}
methodConfigurations[pathPart][method] = (options || {});
if (!routes[canonicalRoute]) {
routes[canonicalRoute] = {};
}
routes[canonicalRoute][method] = handler;
},
setUpHandler = function (method) {
self[method.toLowerCase()] = function (route, handler, options) {
var pathPart = route.replace(/^\//, ''),
canonicalRoute = route;
if (!/^\//.test(canonicalRoute)) {
canonicalRoute = '/' + route;
}
if (!methodConfigurations[pathPart]) {
methodConfigurations[pathPart] = {} ;
}
methodConfigurations[pathPart][method] = (options || {});
if (!routes[canonicalRoute]) {
routes[canonicalRoute] = {};
}
routes[canonicalRoute][method] = handler;
};
};
});
['ANY'].concat(supportedMethods).forEach(setUpHandler);
self.apiConfig = function () {
var result = {version: 2, routes: methodConfigurations};
var result = {version: 3, routes: methodConfigurations};
if (customCorsHandler !== undefined) {

@@ -130,7 +276,9 @@ result.corsHandlers = !!customCorsHandler;

};
self.ApiResponse = function (responseBody, responseHeaders) {
self.ApiResponse = function (responseBody, responseHeaders, code) {
this.response = responseBody;
this.headers = responseHeaders;
this.code = code;
};
self.unsupportedEvent = function (callback) {
v2DeprecationWarning('.unsupportedEvent handlers');
unsupportedEventCallback = callback;

@@ -141,28 +289,35 @@ };

};
self.router = function (event, context, callback) {
var result,
handleResult = function (r) {
if (!r) {
return context.done(null, null);
}
return routeEvent(r, context, callback);
},
self.proxyRouter = function (event, context, callback) {
var request = getRequest(event, context),
routingInfo,
handleError = function (e) {
context.done(e);
};
if (!interceptCallback) {
return routeEvent(event, context, callback);
}
try {
result = interceptCallback(event);
if (isThenable(result)) {
return result.then(handleResult, handleError);
context.callbackWaitsForEmptyEventLoop = false;
return executeInterceptor(request, context).then(function (modifiedRequest) {
if (!modifiedRequest) {
return context.done(null, null);
} else {
handleResult(result);
routingInfo = getRequestRoutingInfo(modifiedRequest);
if (routingInfo && routingInfo.path && routingInfo.method) {
return routeEvent(routingInfo, modifiedRequest, context, callback).then(function (result) {
context.done(null, result);
});
} else {
if (unsupportedEventCallback) {
unsupportedEventCallback(event, context, callback);
} else {
return Promise.reject('event does not contain routing information');
}
}
}
} catch (e) {
handleError(e);
}
}).catch(handleError);
};
self.router = function (event, context, callback) {
requestFormat = 'DEPRECATED';
event.lambdaContext = context;
v2DeprecationWarning('.router methods');
return self.proxyRouter(event, context, callback);
};
self.addPostDeployStep = function (name, stepFunction) {

@@ -169,0 +324,0 @@ if (typeof name !== 'string') {

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