swagger-stats
Advanced tools
Comparing version 0.95.7 to 0.95.8
@@ -67,3 +67,3 @@ 'use strict'; | ||
name: 'swagger-stats-authtest', | ||
version: '0.95.7', | ||
version: '0.95.8', | ||
hostname: "hostname", | ||
@@ -70,0 +70,0 @@ ip: "127.0.0.1", |
@@ -79,3 +79,3 @@ 'use strict'; | ||
name: 'swagger-stats-spectest', | ||
version: '0.95.7', | ||
version: '0.95.8', | ||
hostname: "hostname", | ||
@@ -82,0 +82,0 @@ ip: "127.0.0.1", |
@@ -126,3 +126,3 @@ 'use strict'; | ||
apirouter.get('/redirect', function (req, res) { | ||
res.redirect('/api/v1/success'); | ||
res.redirect('/v2/success'); | ||
}); | ||
@@ -129,0 +129,0 @@ |
@@ -34,3 +34,3 @@ 'use strict'; | ||
// all environments | ||
app.set('port', process.env.PORT || 3030); | ||
app.set('port', process.env.PORT || 3040); | ||
@@ -113,3 +113,3 @@ // Suppress cache on the GET API responses | ||
name: 'swagger-stats-testapp', | ||
version: '0.95.7', | ||
version: '0.95.8', | ||
timelineBucketDuration: tlBucket, | ||
@@ -130,3 +130,3 @@ uriPath: '/swagger-stats', | ||
// Connect API Router - it should be the end of the chain | ||
app.use('/api/v1', API); | ||
app.use('/v2', API); | ||
@@ -133,0 +133,0 @@ // Setup server |
@@ -323,3 +323,3 @@ /** | ||
var url = req.originalUrl; | ||
var url = req.sws.originalUrl; | ||
@@ -411,3 +411,3 @@ req.sws.match = false; // No match by default | ||
case "query": | ||
if( ('query' in req) && (pname in req.query) ){ | ||
if( ('query' in req.sws) && (pname in req.sws.query) ){ | ||
param.hits++; | ||
@@ -450,4 +450,4 @@ }else if(isRrequired){ | ||
case "query": | ||
if(('query' in req) && (pname in req.query)) { | ||
paramValues[pname] = swsUtil.swsStringValue(req.query[pname]); | ||
if(('query' in req.sws) && (pname in req.sws.query)) { | ||
paramValues[pname] = swsUtil.swsStringValue(req.sws.query[pname]); | ||
} | ||
@@ -476,3 +476,3 @@ break; | ||
var req = res.req; | ||
var req = res._swsReq; | ||
var codeclass = swsUtil.getStatusCodeClass(res.statusCode); | ||
@@ -479,0 +479,0 @@ |
@@ -194,2 +194,5 @@ /** | ||
this.promClientMetrics.api_all_request_in_processing_total.inc(); | ||
req.sws.inflightTimer = setTimeout(function() { | ||
this.promClientMetrics.api_all_request_in_processing_total.dec(); | ||
}.bind(this), 250000); | ||
}; | ||
@@ -201,3 +204,3 @@ | ||
var req = res.req; | ||
var req = res._swsReq; | ||
@@ -209,3 +212,3 @@ // Defaults | ||
var timelineid = 0; | ||
var path = req.originalUrl; | ||
var path = req.sws.originalUrl; | ||
@@ -233,2 +236,3 @@ // TODO move all this to Processor, so it'll be in single place | ||
path = req['sws'].api_path; | ||
clearTimeout(req.sws.inflightTimer); | ||
} | ||
@@ -235,0 +239,0 @@ |
@@ -38,2 +38,5 @@ /** | ||
this.elasticUsername = null; | ||
this.elasticPassword = null; | ||
this.indexPrefix = "api-"; | ||
@@ -71,2 +74,10 @@ | ||
if(swsUtil.supportedOptions.elasticsearchUsername in swsOptions) { | ||
this.elasticUsername = swsOptions[swsUtil.supportedOptions.elasticsearchUsername]; | ||
} | ||
if(swsUtil.supportedOptions.elasticsearchPassword in swsOptions) { | ||
this.elasticPassword = swsOptions[swsUtil.supportedOptions.elasticsearchPassword]; | ||
} | ||
// Check / Initialize schema | ||
@@ -88,3 +99,15 @@ this.initTemplate(); | ||
var templateURL = this.elasticURL+'/_template/template_api'; | ||
request.get({url:templateURL, json:true}, function (error, response, body) { | ||
var getOptions = {url:templateURL, json:true}; | ||
var putOptions = {url:templateURL, json:indexTemplate}; | ||
if (this.elasticUsername && this.elasticPassword) { | ||
var auth = { | ||
username: this.elasticUsername, | ||
password: this.elasticPassword, | ||
} | ||
getOptions.auth = auth; | ||
putOptions.auth = auth; | ||
} | ||
request.get(getOptions, function (error, response, body) { | ||
if(error) { | ||
@@ -107,3 +130,3 @@ debug("Error querying template:", JSON.stringify(error) ); | ||
if(initializeNeeded){ | ||
request.put({url:templateURL,json:indexTemplate}, function (error, response, body) { | ||
request.put(putOptions, function (error, response, body) { | ||
if(error) { | ||
@@ -193,2 +216,9 @@ debug("Failed to update template:", JSON.stringify(error)); | ||
if (this.elasticUsername && this.elasticPassword) { | ||
options.auth = { | ||
username: this.elasticUsername, | ||
password: this.elasticPassword, | ||
} | ||
} | ||
request.post(options, function (error, response, body) { | ||
@@ -195,0 +225,0 @@ if (error) { |
@@ -40,5 +40,5 @@ /** | ||
if(res.statusCode==404){ | ||
this.countPathHit(res.req.originalUrl,this.top_not_found); | ||
this.countPathHit(res._swsReq.sws.originalUrl,this.top_not_found); | ||
}else if(res.statusCode==500){ | ||
this.countPathHit(res.req.originalUrl,this.top_server_error); | ||
this.countPathHit(res._swsReq.sws.originalUrl,this.top_server_error); | ||
} | ||
@@ -45,0 +45,0 @@ |
@@ -7,13 +7,18 @@ /** | ||
var fs = require('fs'); | ||
var path = require('path'); | ||
var debug = require('debug')('sws:interface'); | ||
var promClient = require("prom-client"); | ||
var basicAuth = require("basic-auth"); | ||
var Cookies = require('cookies'); | ||
const fs = require('fs'); | ||
const path = require('path'); | ||
const url = require('url'); | ||
const debug = require('debug')('sws:interface'); | ||
const promClient = require("prom-client"); | ||
const basicAuth = require("basic-auth"); | ||
const Cookies = require('cookies'); | ||
const uuidv1 = require('uuid/v1'); | ||
var swsUtil = require('./swsUtil'); | ||
var swsProcessor = require('./swsProcessor'); | ||
const swsUtil = require('./swsUtil'); | ||
const swsProcessor = require('./swsProcessor'); | ||
const send = require('send'); | ||
const qs = require('qs'); | ||
const {swsHapi,swsHapiPlugin} = require('./swsHapi'); | ||
// API data processor | ||
@@ -23,3 +28,3 @@ var processor = null; | ||
// swagger-stats default options | ||
var swsOptions = { | ||
let swsOptions = { | ||
version:'', | ||
@@ -38,2 +43,5 @@ swaggerSpec: null, | ||
swsHapi.setDefaultOptions(swsOptions); | ||
var uiMarkup = swsUtil.swsEmbeddedUIMarkup; | ||
@@ -110,3 +118,3 @@ | ||
for(var op in swsUtil.supportedOptions){ | ||
for(let op in swsUtil.supportedOptions){ | ||
if(op in options){ | ||
@@ -181,3 +189,4 @@ swsOptions[op] = options[op]; | ||
msg = 'Invalid credentials'; | ||
res.status(403).end(msg); | ||
res.statusCode = 403; | ||
res.end(msg); | ||
return resolve(false); | ||
@@ -188,7 +197,9 @@ } | ||
}else{ | ||
res.status(403).end(msg); | ||
res.statusCode = 403; | ||
res.end(msg); | ||
return resolve(false); | ||
} | ||
}else{ | ||
res.status(403).end(msg); | ||
res.statusCode = 403; | ||
res.end(msg); | ||
return resolve(false); | ||
@@ -199,60 +210,2 @@ } | ||
/* | ||
if( !swsOptions.authentication ){ | ||
return true; | ||
} | ||
var cookies = new Cookies( req, res ); | ||
// Check session cookie | ||
var sessionIdCookie = cookies.get('sws-session-id'); | ||
if( (sessionIdCookie !== undefined) && (sessionIdCookie !== null) ){ | ||
if( sessionIdCookie in sessionIDs ){ | ||
// renew it | ||
//sessionIDs[sessionIdCookie] = Date.now(); | ||
storeSessionID(sessionIdCookie); | ||
cookies.set('sws-session-id',sessionIdCookie,{path:swsOptions.uriPath,maxAge:swsOptions.sessionMaxAge*1000}); | ||
// Ok | ||
req['sws-auth'] = true; | ||
return true; | ||
} | ||
} | ||
var authInfo = basicAuth(req); | ||
var authenticated = false; | ||
var msg = 'Authentication required'; | ||
if( (authInfo !== undefined) && (authInfo!==null) && ('name' in authInfo) && ('pass' in authInfo)){ | ||
if(typeof swsOptions.onAuthenticate === 'function'){ | ||
if( swsOptions.onAuthenticate(req, authInfo.name, authInfo.pass) ){ | ||
authenticated = true; | ||
// Session is only for stats requests | ||
if(req.url.startsWith(pathStats)){ | ||
// Generate session id | ||
var sessid = uuidv1(); | ||
storeSessionID(sessid); | ||
// Set session cookie with expiration in 15 min | ||
cookies.set('sws-session-id',sessid,{path:swsOptions.uriPath,maxAge:swsOptions.sessionMaxAge*1000}); | ||
} | ||
}else{ | ||
msg = 'Invalid credentials'; | ||
} | ||
} | ||
} | ||
if( !authenticated ){ | ||
// Reconsidered 401 response. Make it be 403 to prevent browser Basic Auth pop-up in UI | ||
res.status(403).end(msg); | ||
return false; | ||
} | ||
req['sws-auth'] = true; | ||
return true; | ||
*/ | ||
} | ||
@@ -273,3 +226,4 @@ | ||
res.status(200).end('Logged out'); | ||
res.statusCode = 200; | ||
res.end('Logged out'); | ||
} | ||
@@ -287,20 +241,9 @@ | ||
} | ||
res.status(200); | ||
res.statusCode = 200; | ||
if(('sws-auth' in req) && req['sws-auth']){ | ||
res.setHeader('x-sws-authenticated','true'); | ||
} | ||
res.json( processor.getStats( req.query ) ); | ||
res.setHeader('Content-Type', 'application/json'); | ||
res.end(JSON.stringify(processor.getStats(req.sws.query))); | ||
}); | ||
/* | ||
if(!processAuth(req,res)) { | ||
return; | ||
} | ||
if(('sws-auth' in req) && req['sws-auth']){ | ||
res.setHeader('x-sws-authenticated','true'); | ||
} | ||
res.status(200).json( processor.getStats( req.query ) ); | ||
*/ | ||
} | ||
@@ -317,14 +260,6 @@ | ||
} | ||
res.status(200).set('Content-Type', 'text/plain'); | ||
res.statusCode = 200; | ||
res.setHeader('Content-Type', 'text/plain'); | ||
res.end(promClient.register.metrics()); | ||
}); | ||
/* | ||
if(!processAuth(req,res)) { | ||
return; | ||
} | ||
res.status(200).set('Content-Type', 'text/plain'); | ||
res.end(promClient.register.metrics()); | ||
*/ | ||
} | ||
@@ -334,2 +269,5 @@ | ||
// Returns Hapi plugin | ||
getHapiPlugin: swsHapiPlugin, | ||
// Initialize swagger-stats and return | ||
@@ -346,2 +284,6 @@ // middleware to perform API Data collection | ||
res._swsReq = req; | ||
req.sws = {}; | ||
req.sws.query = qs.parse(url.parse(req.url).query); | ||
// Respond to requests handled by swagger-stats | ||
@@ -357,3 +299,5 @@ // swagger-stats requests will not be counted in statistics | ||
}else if(req.url.startsWith(pathUI) ){ | ||
res.status(200).send(uiMarkup); | ||
res.statusCode = 200; | ||
res.setHeader('Content-Type', 'text/html'); | ||
res.end(uiMarkup); | ||
return; | ||
@@ -370,7 +314,4 @@ }else if(req.url.startsWith(pathDist)) { | ||
}; | ||
res.sendFile(fileName, options, function (err) { | ||
if (err) { | ||
debug('unable to send file: %s',fileName); | ||
} | ||
}); | ||
res.setHeader('Content-Type', send.mime.lookup(path.basename(fileName))); | ||
send(req, fileName, options).pipe(res); | ||
return; | ||
@@ -388,9 +329,27 @@ } | ||
getCoreStats: function() { | ||
return processor.getStats(); | ||
if(processor) { | ||
return processor.getStats(); | ||
}else if(swsHapi.processor){ | ||
return swsHapi.processor.getStats(); | ||
} | ||
}, | ||
// Allow get stats as prometheus format | ||
getPromStats: function() { | ||
return promClient.register.metrics(); | ||
}, | ||
// Expose promClient to allow for custom metrics by application | ||
getPromClient: function () { | ||
return promClient; | ||
}, | ||
// Stop the processor so that Node.js can exit | ||
stop: function () { | ||
if(processor) { | ||
return processor.stop(); | ||
}else if(swsHapi.processor){ | ||
return swsHapi.processor.stop(); | ||
} | ||
} | ||
}; |
@@ -91,5 +91,12 @@ /** | ||
// Start tick | ||
setInterval(this.tick, 200, this); | ||
this.timer = setInterval(this.tick, 200, this); | ||
}; | ||
// Stop | ||
swsProcessor.prototype.stop = function () { | ||
clearInterval(this.timer); | ||
}; | ||
swsProcessor.prototype.processOptions = function (swsOptions) { | ||
@@ -169,3 +176,3 @@ | ||
var req = res.req; | ||
var req = res._swsReq; | ||
@@ -175,5 +182,5 @@ var codeclass = swsUtil.getStatusCodeClass(res.statusCode); | ||
var rrr = { | ||
'path': req.originalUrl, | ||
'path': req.sws.originalUrl, | ||
'method': req.method, | ||
'query' : req.method + ' ' + req.originalUrl, | ||
'query' : req.method + ' ' + req.sws.originalUrl, | ||
'startts': 0, | ||
@@ -254,3 +261,3 @@ 'endts': 0, | ||
// Express parameters: req.param and req.query | ||
// Express/Koa parameters: req.params (router) and req.body (body parser) | ||
if (req.hasOwnProperty("params")) { | ||
@@ -261,5 +268,5 @@ rrr.http.request.params = {}; | ||
if (req.hasOwnProperty("query")) { | ||
if (req.sws && req.sws.hasOwnProperty("query")) { | ||
rrr.http.request.query = {}; | ||
swsUtil.swsStringRecursive(rrr.http.request.query, req.query); | ||
swsUtil.swsStringRecursive(rrr.http.request.query, req.sws.query); | ||
} | ||
@@ -295,3 +302,3 @@ | ||
var remoteaddress = null; | ||
var xfwd = req.header('x-forwarded-for'); | ||
var xfwd = req.headers['x-forwarded-for']; | ||
if (xfwd) { | ||
@@ -310,3 +317,3 @@ var fwdaddrs = xfwd.split(','); // Could be "client IP, proxy 1 IP, proxy 2 IP" | ||
// Placeholder for sws-specific attributes | ||
req.sws = {}; | ||
req.sws = req.sws || {}; | ||
@@ -321,2 +328,3 @@ // Setup sws props and pass to stats processors | ||
req.sws.originalUrl = req.originalUrl || req.url; | ||
req.sws.track = true; | ||
@@ -351,3 +359,3 @@ req.sws.startts = ts; | ||
var req = res.req; | ||
var req = res._swsReq; | ||
@@ -363,5 +371,5 @@ // Capture route path for the request, if set by router | ||
// If request was not matched to Swagger API, set API path: | ||
// Use route_path, if exist; if not, use originalUrl | ||
// Use route_path, if exist; if not, use sws.originalUrl | ||
if(!('api_path' in req.sws)){ | ||
req.sws.api_path = (route_path!=''?route_path:req.originalUrl); | ||
req.sws.api_path = (route_path!=''?route_path:req.sws.originalUrl); | ||
} | ||
@@ -368,0 +376,0 @@ |
@@ -161,3 +161,3 @@ /** | ||
var req = res.req; | ||
var req = res._swsReq; | ||
@@ -164,0 +164,0 @@ // Update timeline stats |
@@ -97,2 +97,8 @@ /* | ||
// Username for Elasticsearch, if anonymous user is disbaled . Default is empty (disabled) | ||
elasticsearchUsername : "elasticsearchUsername", | ||
// Password for Elasticsearch, if anonymous user is disbaled . Default is empty (disabled) | ||
elasticsearchPassword : "elasticsearchPassword", | ||
// Set to true to track only requests defined in swagger spec. Default false. | ||
@@ -99,0 +105,0 @@ swaggerOnly : "swaggerOnly" |
{ | ||
"name": "swagger-stats", | ||
"version": "0.95.7", | ||
"version": "0.95.8", | ||
"description": "API Telemetry and APM. Trace API calls and Monitor API performance, health and usage statistics in Node.js Microservices, based on express routes and Swagger (Open API) specification", | ||
@@ -9,5 +9,8 @@ "main": "lib/index.js", | ||
"testonly": "mocha -S --delay", | ||
"hapitestapp": "nyc --reporter=lcov --reporter=html --reporter=json --reporter=text --report-dir=coverage/hapitestapp node examples/hapijstest/hapijstest.js", | ||
"hapitestappstop": "mocha --delay --exit test/stoptestapp.js", | ||
"test-old": "npm run coverage && npm run karma-ci", | ||
"coverage": "nyc --reporter=lcov --reporter=html --reporter=text --report-dir=coverage/mocha mocha -S --delay", | ||
"test": "npm run cov000 && npm run cov010 && npm run cov100 && npm run cov200 && npm run cov300 && npm run cov400 && npm run cov500 && npm run karma-ci && npm run coverage-report", | ||
"test": "npm run testExpress && npm run testHapi && npm run coverage-report", | ||
"testExpress": "npm run cov000 && npm run cov010 && npm run cov100 && npm run cov200 && npm run cov300 && npm run cov400 && npm run cov500 && npm run karma-ci", | ||
"cov000": "nyc --reporter=lcov --reporter=html --reporter=json --reporter=text --report-dir=coverage/000 mocha --delay --exit test/000_baseline.js", | ||
@@ -20,2 +23,10 @@ "cov010": "nyc --reporter=lcov --reporter=html --reporter=json --reporter=text --report-dir=coverage/010 mocha --delay --exit test/010_swsapistats.js", | ||
"cov500": "nyc --reporter=lcov --reporter=html --reporter=json --reporter=text --report-dir=coverage/500 mocha --delay --exit test/500_elastic.js", | ||
"testHapi": "concurrently -k --success first \"npm run hapitestapp\" \"npm run covHapi\"", | ||
"covHapi": "npm run cov000h && npm run cov100h && npm run cov200h && npm run cov300h && npm run cov500h && npm run hapitestappstop", | ||
"cov000h": "nyc --reporter=lcov --reporter=html --reporter=json --reporter=text --report-dir=coverage/000h mocha --delay --exit test/000_baseline.js", | ||
"cov100h": "nyc --reporter=lcov --reporter=html --reporter=json --reporter=text --report-dir=coverage/100h mocha --delay --exit test/100_method.js", | ||
"cov200h": "nyc --reporter=lcov --reporter=html --reporter=json --reporter=text --report-dir=coverage/200h mocha --delay --exit test/200_apicore.js", | ||
"cov300h": "nyc --reporter=lcov --reporter=html --reporter=json --reporter=text --report-dir=coverage/300h mocha --delay --exit test/300_timeline.js", | ||
"cov400h": "nyc --reporter=lcov --reporter=html --reporter=json --reporter=text --report-dir=coverage/400h mocha --delay --exit test/400_auth.js", | ||
"cov500h": "nyc --reporter=lcov --reporter=html --reporter=json --reporter=text --report-dir=coverage/500h mocha --delay --exit test/500_elastic.js", | ||
"coverage-report": "node_modules/.bin/istanbul report --root ./coverage --dir ./coverage-report lcov", | ||
@@ -57,3 +68,5 @@ "specstest": "mocha test/specstest/swaggerspecstest.js", | ||
"prom-client": "^11.0.0", | ||
"qs": "^6.7.0", | ||
"request": "^2.85.0", | ||
"send": "^0.17.1", | ||
"uuid": "^3.1.0" | ||
@@ -117,4 +130,7 @@ }, | ||
"swagger-jsdoc": "^1.9.1", | ||
"swagger-parser": "^3.4.1" | ||
"swagger-parser": "^3.4.1", | ||
"@hapi/hapi": "^18.3.2", | ||
"@hapi/inert": "^5.2.1", | ||
"concurrently": "^4.1.2" | ||
} | ||
} |
@@ -18,6 +18,8 @@ <p align="center"> | ||
## API Telemetry and APM | ||
## API Telemetry and APM | ||
> Trace API calls and Monitor API performance, health and usage statistics in Node.js Microservices | ||
### Supports Express, Koa and Hapi | ||
**swagger-stats** traces REST API requests and responses in Node.js Microservices, and collects statistics per API Operation. | ||
@@ -27,3 +29,2 @@ **swagger-stats** detects API operations based on express routes. You may also provide [Swagger (Open API) specification](https://swagger.io/specification/), | ||
**swagger-stats** exposes statistics and metrics per API Operation, such as `GET /myapi/:parameter`, or `GET /pet/{petId}` | ||
@@ -98,2 +99,4 @@ | ||
#### Express | ||
```javascript | ||
@@ -105,4 +108,40 @@ var swStats = require('swagger-stats'); | ||
See `/examples` for sample apps | ||
#### Koa | ||
[`express-to-koa`](https://github.com/kaelzhang/express-to-koa) can be used which is just a simple `Promise` wrapper. | ||
```javascript | ||
var swStats = require('swagger-stats'); | ||
var apiSpec = require('swagger.json'); | ||
var e2k = require('express-to-koa'); | ||
app.use(e2k(swStats.getMiddleware({ swaggerSpec:apiSpec }))); | ||
``` | ||
#### Hapi | ||
```javascript | ||
const swStats = require('swagger-stats'); | ||
const swaggerSpec = require('./petstore.json'); | ||
const init = async () => { | ||
server = Hapi.server({ | ||
port: 3040, | ||
host: 'localhost' | ||
}); | ||
await server.register({ | ||
plugin: swStats.getHapiPlugin, | ||
options: { | ||
swaggerSpec:swaggerSpec | ||
} | ||
}); | ||
await server.start(); | ||
console.log('Server running on %s', server.info.uri); | ||
}; | ||
```` | ||
See `/examples` for sample apps | ||
### Get Statistics with API | ||
@@ -207,2 +246,8 @@ | ||
#### v0.95.8 | ||
* [feature] Hapijs support [#75](https://github.com/slanatech/swagger-stats/issues/75) - [Example how to use](https://github.com/slanatech/swagger-stats/blob/master/examples/hapijstest/hapijstest.js) | ||
* [feature] Koa support [#70](https://github.com/slanatech/swagger-stats/pull/70), [#67](https://github.com/slanatech/swagger-stats/issues/67) - thank you @gombosg! | ||
#### v0.95.7 | ||
@@ -209,0 +254,0 @@ |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
5402328
68
12092
348
11
59
27
+ Addedqs@^6.7.0
+ Addedsend@^0.17.1
+ Addedcall-bind@1.0.7(transitive)
+ Addeddebug@2.6.9(transitive)
+ Addeddefine-data-property@1.1.4(transitive)
+ Addeddestroy@1.0.4(transitive)
+ Addedee-first@1.1.1(transitive)
+ Addedencodeurl@1.0.2(transitive)
+ Addedes-define-property@1.0.0(transitive)
+ Addedes-errors@1.3.0(transitive)
+ Addedescape-html@1.0.3(transitive)
+ Addedetag@1.8.1(transitive)
+ Addedfresh@0.5.2(transitive)
+ Addedfunction-bind@1.1.2(transitive)
+ Addedget-intrinsic@1.2.4(transitive)
+ Addedgopd@1.0.1(transitive)
+ Addedhas-property-descriptors@1.0.2(transitive)
+ Addedhas-proto@1.0.3(transitive)
+ Addedhas-symbols@1.0.3(transitive)
+ Addedhasown@2.0.2(transitive)
+ Addedhttp-errors@1.8.1(transitive)
+ Addedinherits@2.0.4(transitive)
+ Addedmime@1.6.0(transitive)
+ Addedms@2.0.0(transitive)
+ Addedobject-inspect@1.13.1(transitive)
+ Addedon-finished@2.3.0(transitive)
+ Addedqs@6.12.1(transitive)
+ Addedrange-parser@1.2.1(transitive)
+ Addedsend@0.17.2(transitive)
+ Addedset-function-length@1.2.2(transitive)
+ Addedsetprototypeof@1.2.0(transitive)
+ Addedside-channel@1.0.6(transitive)
+ Addedstatuses@1.5.0(transitive)
+ Addedtoidentifier@1.0.1(transitive)