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

armlet

Package Overview
Dependencies
Maintainers
1
Versions
30
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

armlet - npm Package Compare versions

Comparing version 1.2.1 to 2.0.0

example/mythx-analysis

9

example/helper.js

@@ -1,10 +0,9 @@

if (!process.env.MYTHX_ETH_ADDRESS && !process.env.MYTHX_EMAIL) {
console.log('Please set either environment variable MYTHX_ETH_ADDRESS ' +
'or MYTHX_EMAIL')
if (!process.env.MYTHX_ETH_ADDRESS) {
console.log('Please set either environment variable MYTHX_ETH_ADDRESS ')
process.exit(2)
}
if (!process.env.MYTHX_PASSWORD && !process.env.MYTHX_API_KEY) {
console.log('Please set environment variable MYTHX_PASSWORD or MYTHX_API_KEY')
if (!process.env.MYTHX_PASSWORD) {
console.log('Please set environment variable MYTHX_PASSWORD')
process.exit(3)
}

@@ -1,3 +0,3 @@

Here we have some minimal command-line nodejs that can be run to show
how to use armlet and interact with the MythX API.
Here we have some command-line nodejs that can be run to show how to
use armlet and interact with the MythX API.

@@ -10,5 +10,5 @@ See the [openapi spec](https://api.mythx.io/v1/openapi) for details of the MythX API.

* [analysis](https://github.com/ConsenSys/armlet/blob/master/example/analysis): Submit a JSON with Solidity source and EVM bytecode information to the MythX and retrieve results.
* [analysis-status](https://github.com/ConsenSys/armlet/blob/master/example/analysis): Get status of a prior MythX analysis request.
* [analysis-issues](https://github.com/ConsenSys/armlet/blob/master/example/analysis): Get issues reported from a prior MythX analysis.
* [mythx-analysis](https://github.com/ConsenSys/armlet/blob/master/example/mythx-analysis): Submit a JSON with Solidity source and EVM bytecode information to the MythX and retrieve results.
* [analysis-status](https://github.com/ConsenSys/armlet/blob/master/example/analysis-status): Get status of a prior MythX analysis request.
* [analysis-issues](https://github.com/ConsenSys/armlet/blob/master/example/analysis-issues): Get issues reported from a prior MythX analysis.
* [list-analyses](https://github.com/ConsenSys/armlet/blob/master/example/list-analyses): Get issues reported from a prior MythX analysis.

@@ -15,0 +15,0 @@ * [api-version](https://github.com/ConsenSys/armlet/blob/master/example/api-version): Retrieve MythX API version information. JSON is output.

{
"contractName": "PublicStorageArray",
"contractName": "PublicStorageArray14",
"abi": [

@@ -4,0 +4,0 @@ {

@@ -5,10 +5,15 @@ const url = require('url')

const simpleRequester = require('./lib/simpleRequester')
const poller = require('./lib/poller')
const analysisPoller = require('./lib/analysisPoller')
const login = require('./lib/login')
const refresh = require('./lib/refresh')
const libUtil = require('./lib/util')
const defaultApiUrl = process.env['MYTHX_API_URL'] || 'https://api.mythx.io'
const defaultApiVersion = 'v1'
const trialUserId = '123456789012345678901234'
// No MythX job we've seen is faster than this value. So if an
// analysis request isn't cached, then the *first* poll for status
// will be delayed by this amount of time.
const defaultInitialDelay = 45000 // 45 seconds
class Client {

@@ -21,4 +26,3 @@ /**

* @param {auth} object - login or authentication information which contains
* (email | ethAddress) and a password or...
* apiKey
* an ethAddress and a password
* @param {inputApiUrl} string - Optional. A URL of a MythX API server we want to contect

@@ -29,17 +33,8 @@ * to.

constructor (auth, inputApiUrl = defaultApiUrl) {
const { email, ethAddress, apiKey, password } = auth || {}
const { ethAddress, password } = auth || {}
let userId
if (!password && !email && !ethAddress && !apiKey) {
userId = trialUserId
if (!password || !ethAddress) {
throw new TypeError('Please provide an Ethereum address and a password.')
}
if (password && !email && !ethAddress && !apiKey) {
throw new TypeError('Please provide an user id auth option.')
}
if (!apiKey && !userId && (!password && (email || ethAddress))) {
throw new TypeError('Please provide a password auth option.')
}
const apiUrl = new url.URL(inputApiUrl)

@@ -50,7 +45,4 @@ if (!apiUrl.hostname) {

this.userId = userId
this.email = email
this.ethAddress = ethAddress
this.password = password
this.accessToken = apiKey
this.apiUrl = apiUrl

@@ -63,6 +55,13 @@ }

*
* @param {options} object - structure which must contain
* {data} object - information containing Smart Contract information to be analyzed
* {timeout} number - optional timeout value in milliseconds
* @param {options} object - structure which must contain
* {data} object - information containing Smart Contract information to be analyzed
* {timeout} number - optional timeout value in milliseconds
* {clientToolName} string - optional; sets up for client tool usage tracking
* {initialDelay} number - optional; After submitting an analysis and seeing that it is
not cached, the first status API call will be delayed by this
number of milliseconds
minimum value for how long a non-cached analyses will take
* this must be larger than defaultInitialDelay which we believe to be
* the smallest reasonable value.
*

@@ -76,3 +75,3 @@ * @returns an array-like object of issues, and a uuid attribute which can

if (options === undefined || options.data === undefined) {
throw new TypeError('Please provide a data option.')
throw new TypeError('Please provide analysis request JSON in a "data" attribute.')
}

@@ -83,8 +82,6 @@

try {
tokens = await login.do(this.email, this.ethAddress, this.userId, this.password, this.apiUrl)
tokens = await login.do(this.ethAddress, this.password, this.apiUrl)
} catch (e) {
let authType = ''
if (this.email) {
authType = ` for email address ${this.email}`
} else if (this.ethAddress) {
if (this.ethAddress) {
authType = ` for ethereum address ${this.ethAddress}`

@@ -99,5 +96,5 @@ }

let uuid
let requestResponse
try {
uuid = await requester.do(options, this.accessToken, this.apiUrl)
requestResponse = await requester.do(options, this.accessToken, this.apiUrl)
} catch (e) {

@@ -111,20 +108,58 @@ if (e.statusCode !== 401) {

uuid = await requester.do(options, this.accessToken, this.apiUrl)
requestResponse = await requester.do(options, this.accessToken, this.apiUrl)
}
/*
Set "timeout" - the maximum amount of time we want to wait on
a request before giving up.
Unless a timeout has been explicitly given (and we recommend it should be),
we will use a value of 3 minutes for a "quick" analysis and
3 hours for a "full" analysis.
Note:
A "quick" analysis usually finishes within 90 seconds after the job starts.
A "full" analysis may run for 2 hours or more.
There is also average queuing delay as well which on average may be at
least 5 seconds.
*/
let timeout = options.timeout
if (!('timeout' in options)) {
options.timeout = (60 * 1000) * (options.data.analysisMode === 'full')
? 3 // 3 minutes
: (3 * 60) // 3 hours
}
if (options.debug) {
console.log(`now: ${Math.trunc(Date.now() / 1000)}`)
}
let result
try {
result = await poller.do(uuid, this.accessToken, this.apiUrl, undefined, options.timeout)
} catch (e) {
if (e.statusCode !== 401) {
throw e
if (requestResponse.status === 'Finished') {
result = await analysisPoller.getIssues(requestResponse.uuid, this.accessToken, this.apiUrl)
if (options.debug) {
const util = require('util')
let depth = (options.debug > 1) ? 10 : 2
console.log(`Cached Result:\n${util.inspect(result, { depth: depth })}\n------`)
}
const tokens = await refresh.do(this.accessToken, this.refreshToken, this.apiUrl)
this.accessToken = tokens.access
this.refreshToken = tokens.refresh
} else {
const initialDelay = Math.max(options.initialDelay || 0, defaultInitialDelay)
try {
result = await analysisPoller.do(requestResponse.uuid, this.accessToken, this.apiUrl, timeout, initialDelay, options.debug)
} catch (e) {
if (e.statusCode !== 401) {
throw e
}
const tokens = await refresh.do(this.accessToken, this.refreshToken, this.apiUrl)
this.accessToken = tokens.access
this.refreshToken = tokens.refresh
result = await analysisPoller.do(requestResponse.uuid, this.accessToken, this.apiUrl, timeout,
initialDelay, options.debug)
}
}
result = await poller.do(uuid, this.accessToken, this.apiUrl, undefined, options.timeout)
return {
issues: result,
uuid: requestResponse.uuid
}
result.uuid = uuid
return result
}

@@ -148,7 +183,7 @@

if (!this.accessToken) {
const tokens = await login.do(this.email, this.ethAddress, this.userId, this.password, this.apiUrl)
const tokens = await login.do(this.ethAddress, this.password, this.apiUrl)
this.accessToken = tokens.access
this.refreshToken = tokens.refresh
}
const url = `${this.apiUrl.href}${defaultApiVersion}/analyses?dateFrom=${options.dateFrom}&dateTo=${options.dateTo}&offset=${options.offset}`
const url = libUtil.joinUrl(this.apiUrl.href, `${defaultApiVersion}/analyses?dateFrom=${options.dateFrom}&dateTo=${options.dateTo}&offset=${options.offset}`)
let analyses

@@ -173,18 +208,20 @@ try {

*
* @param {options} object - structure which must contain:
* @param {object} options - structure which must contain:
* {data} object - information containing Smart Contract information to be analyzed
* {timeout} number - optional timeout value in milliseconds
* {clientToolName} string - optional; sets up for client tool usage tracking
* {Number} timeout - optional timeout value in milliseconds
* {String} clientToolName - optional; sets up for client tool usage tracking
*
* @returns object which contains:
* (issues} object - an like object which of issues is grouped by (file) input container.
* {status} object - status information as returned in each object of analyses().
* (Number} elsped - elaped milliseconds that we recorded
* (Object} issues - an like object which of issues is grouped by (file) input container.
* {Object} status - status information as returned in each object of analyses().
*
**/
async analyzeWithStatus (options) {
const issues = await this.analyze(options)
const uuid = issues.uuid
delete issues.uuid
const start = Date.now()
const { issues, uuid } = await this.analyze(options, true)
const status = await this.getStatus(uuid)
const elapsed = Date.now() - start
return {
elapsed,
issues,

@@ -198,3 +235,3 @@ status

if (!accessToken) {
const tokens = await login.do(this.email, this.ethAddress, this.userId, this.password, this.apiUrl)
const tokens = await login.do(this.ethAddress, this.password, this.apiUrl)
accessToken = tokens.access

@@ -220,3 +257,3 @@ }

async getStatus (uuid, inputApiUrl = defaultApiUrl) {
const url = `${inputApiUrl}/${defaultApiVersion}/analyses/${uuid}`
const url = libUtil.joinUrl(this.apiUrl.href, `${defaultApiVersion}/analyses/${uuid}`)
return this.getStatusOrIssues(uuid, url, inputApiUrl)

@@ -226,3 +263,3 @@ }

async getIssues (uuid, inputApiUrl = defaultApiUrl) {
const url = `${inputApiUrl}/${defaultApiVersion}/analyses/${uuid}/issues`
const url = libUtil.joinUrl(this.apiUrl.href, `${defaultApiVersion}/analyses/${uuid}/issues`)
return this.getStatusOrIssues(uuid, url, inputApiUrl)

@@ -234,7 +271,10 @@ }

if (!accessToken) {
const tokens = await login.do(this.email, this.ethAddress, this.userId, this.password, this.apiUrl)
const tokens = await login.do(this.ethAddress, this.password, this.apiUrl)
accessToken = tokens.access
}
const url = `${inputApiUrl}/${defaultApiVersion}/analyses`
return simpleRequester.do({ url, accessToken: accessToken, json: true })
const url = libUtil.joinUrl(inputApiUrl, `${defaultApiVersion}/analyses`)
return simpleRequester.do({ url,
accessToken: accessToken,
json: true,
ethAddress: this.ethAddress })
}

@@ -244,7 +284,9 @@ }

module.exports.ApiVersion = (inputApiUrl = defaultApiUrl) => {
return simpleRequester.do({ url: `${inputApiUrl}/${defaultApiVersion}/version`, json: true })
const url = libUtil.joinUrl(inputApiUrl, `${defaultApiVersion}/version`)
return simpleRequester.do({ url, json: true })
}
module.exports.OpenApiSpec = (inputApiUrl = defaultApiUrl) => {
return simpleRequester.do({ url: `${inputApiUrl}/${defaultApiVersion}/openapi.yaml` })
const url = libUtil.joinUrl(inputApiUrl, `${defaultApiVersion}/openapi.yaml`)
return simpleRequester.do({ url })
}

@@ -256,2 +298,2 @@

module.exports.defaultApiVersion = defaultApiVersion
module.exports.trialUserId = trialUserId
module.exports.defaultInitialDelay = defaultInitialDelay
const request = require('request')
const util = require('./util')
const basePath = 'v1/auth/login'
exports.do = (email, ethAddress, userId, password, apiUrl) => {
exports.do = (ethAddress, password, apiUrl) => {
return new Promise((resolve, reject) => {
const options = { form: { email, userId, ethAddress, password } }
const url = `${apiUrl.href}${basePath}`
const options = { form: { ethAddress, password } }
const url = util.joinUrl(apiUrl.href, basePath)

@@ -16,4 +17,22 @@ request.post(url, options, (error, res, body) => {

// Handle redirect
if (res.statusCode === 308 && apiUrl.protocol === 'http:') {
apiUrl.protocol = 'https:'
this.do(ethAddress, password, apiUrl).then(result => {
resolve(result)
}).catch(error => {
reject(error)
})
return
}
if (res.statusCode !== 200) {
reject(new Error(`Invalid status code ${res.statusCode}, ${body}`))
/* eslint-disable prefer-promise-reject-errors */
try {
body = JSON.parse(body)
reject(`${body.error} (HTTP status ${res.statusCode})`)
} catch (err) {
reject(`${body} (HTTP status ${res.statusCode})`)
}
/* eslint-enable prefer-promise-reject-errors */
return

@@ -20,0 +39,0 @@ }

const request = require('request')
const util = require('./util')

@@ -8,3 +9,3 @@ const basePath = 'v1/auth/refresh'

const options = { form: { refreshToken, accessToken } }
const url = `${apiUrl.href}${basePath}`
const url = util.joinUrl(apiUrl.href, basePath)

@@ -11,0 +12,0 @@ request.post(url, options, (error, res, body) => {

const request = require('request')
const util = require('./util')
const basePath = 'v1/analyses'

@@ -9,3 +10,3 @@

const options = {
url: `${apiUrl.href}${basePath}`,
url: util.joinUrl(apiUrl.href, basePath),
method: 'POST',

@@ -45,11 +46,13 @@ headers: {

}, [])
reject(new Error(`${errMsg}: ${msgs.join(', ')}`))
// eslint-disable-next-line prefer-promise-reject-errors
reject(`${errMsg}: ${msgs.join(', ')}`)
return
}
if (typeof data !== 'object') {
reject(new SyntaxError('Non JSON data returned'))
// eslint-disable-next-line prefer-promise-reject-errors
reject(`Non JSON data returned: ${data}`)
}
resolve(data.uuid)
resolve(data)
})
})
}

@@ -0,3 +1,12 @@

/*
This is somewhat generic but specific to the MythX API and handles
API requests which do not require more than a single request.
This is in contrast, say, to analysis where a request, needs
to be followed by polling.
*/
const request = require('request')
const HttpErrors = require('http-errors')
const trialEthAccount = '0x0000000000000000000000000000000000000000'

@@ -27,2 +36,8 @@ exports.do = options => {

return
} else if ((res.statusCode === 403) &&
(res.request.uri.path.endsWith('/analyses')) &&
(options.ethAddress === trialEthAccount)) {
// FIXME: Remove when API gives back a custom message like this:
// eslint-disable-next-line prefer-promise-reject-errors
reject('The trial user does not allow listing previous analyses. To enable this feature, please sign up for a free account on: https://mythx.io')
} else if (res.statusCode !== 200) {

@@ -29,0 +44,0 @@ try {

@@ -0,1 +1,84 @@

Release 2.0.0
=================
A lot has changed in the almost two weeks that have
elapsed since the last release.
Changes for Mythx API 1.4
-------------------------------
Perhaps the biggest change is that we now support version 1.4.0 of the MythX API.
This means various authentication options involving an API key or an email address
are no longer supported.
There were some smaller changes in the back end and the
acceptable way to interact with the back-end protocol has been adjusted.
Geometrically-increasing delays in polling
-----------------------------------------------------
We noticed that there was a lot of overhead created on the back end caused by
polling for analysis status. Taking a cue from how the Ethernet
handles congestion, successive polls are now spaced more widely.
For applications which use MythX through armlet, when they can predict
the likely time interval for the contract submitted, they will be
rewarded with a reduced delay in noticing that results are ready on the back-end.
Parameter `initial-delay` was added. This is the minimum amount of
time that this library waits before attempting its first status poll
when the results are not already cached.
You can read about improving polling response [here](https://github.com/ConsenSys/armlet/#improving-polling-response).
Introducing command-line utility "mythx-analysis"
-------------------------------------------------------------
The "example" program `analysis` is now called `mythx-analysis` and it
is installed as a standalone command-line utility.
It is more full featured:
* it supports more armlet library options,
`--version`, `--timeout`, `--delay`, and `--debug`
* it can accept Solidity source code and will run `solc` to compile the source before passing
on to MythX
Sample Solidity contracts now appear in `example/solidity-files`
Library changes not mentioned above
--------------------------------------------
An additional analysis option `debug` is available. With this, you
can get more information about what is going on in armlet. Setting
`debug` to a numeric value of 2 or more gives more-verbose
output.
Getting a list of past analyses is not allowed as a trial user, as
is now noted in the response. We suggest a suitable course of action
(registering) and supply a link to do so.
Some small URL canonicalization is now done. In particular you can add a trailing slash
to the HTTP host `https://api.mythix.io/` and that is the same things as `https://api.mythix.io`.
Similarly `http` will be turned into `https` when appropriate.
There is now proxy support via
[`omni-fetch`](https://www.npmjs.com/package/omni-fetch) which is a
wrapper to
[`isomorphic-fetch`](https://www.npmjs.com/package/isomorphic-fetch). This
work was kindly contributed by Teruhiro Tagomori at NRISecure.
Additional tests were added and test-code coverage has been
increased. This is the work of Daniyar Chambylov at Maddevs.
Some time units are shown in a more human-friendly way. There are numerous other small documentation and code improvements.
Older Releases
=================
v1.2.1 - 2019-02-06

@@ -2,0 +85,0 @@ -----------------------

{
"name": "armlet",
"version": "1.2.1",
"description": "A MythX API client.",
"version": "2.0.0",
"description": [
"Armlet is a thin wrapper around the MythX API written in Javascript.",
"It simplifies interaction with MythX. For example, the library",
"wraps API analysis requests into a promise. A MythX API client.",
"",
"A simple command-line tool, mythx-analysis, is provided to show how to",
"use the API. It can be used to run MythX analyses on a single Solidity",
"smart-contract text file."
],
"main": "index.js",
"bin": {
"mythx-analysis": "./example/mythx-analysis"
},
"directories": {
"example": "example",
"lib": "lib"
},
"scripts": {

@@ -17,3 +32,2 @@ "lint": "eslint .",

},
"author": "Federico Gimenez <federico.gimenez@gmail.com>",
"license": "MIT",

@@ -25,3 +39,2 @@ "bugs": {

"devDependencies": {
"chai": "^4.1.2",
"chai-as-promised": "^7.1.1",

@@ -43,5 +56,6 @@ "coveralls": "^3.0.2",

"humanize-duration": "^3.17.0",
"moment": "^2.24.0",
"isomorphic-fetch": "^2.2.1",
"omni-fetch": "^0.2.3",
"request": "^2.88.0"
}
}

@@ -6,14 +6,28 @@ [![CircleCI](https://circleci.com/gh/ConsenSys/armlet.svg?style=svg)](https://circleci.com/gh/ConsenSys/armlet)

Armlet is a thin wrapper around the MythX API written in Javascript
which simplifies interaction with MythX. For example, the library
wraps API analysis requests into a promise.
Armlet is a thin wrapper around the MythX API written in Javascript.
It simplifies interaction with MythX. For example, the library
wraps API analysis requests into a promise, merges status information
with analysis-result information, and judiciously polls for results.
A simple command-line tool, `mythx-analysis`, is provided to show how to use the API.
It can be used to run MythX analyses on a single Solidity smart-contract text file.",
# Installation
Just as with any nodejs package, install with:
To install the latest stable version from NPM:
```
$ npm install armlet
$ npm -g install armlet
```
If you're feeling adventurous, you can also install the from the master branch:
```
$ npm install -g git+https://git@github.com/ConsenSys/armlet.git
```
The `-g` or `--global` option above may not be needed depending on how
you work. It my ensuring `mythx-analysis` is in your path where it might not
otherwise be there.
# Example

@@ -40,3 +54,3 @@

{
password: process.env.MYTHX_PASSWORD, // adjust this
password: process.env.MYTHX_PASSWORD,
ethAddress: process.env.MYTHX_ETH_ADDRESS,

@@ -49,3 +63,8 @@ })

client.analyzeWithStatus({data})
client.analyzeWithStatus(
{
"data": data, // required
"timeout": 2 * 60 * 1000, // optional, but can improve response time
"debug": false, // optional: set to true if you want to see what's going on
})
.then(result => {

@@ -59,10 +78,12 @@ const util = require('util');

```
You can also specify the timeout in milliseconds to wait for the analysis to be
done (the default is 40 seconds). Also, for statistical tracking you can tag the type of tool making the request using `clientToolName`.
For statistical tracking you can tag the type of tool making the request using `clientToolName`.
For example, to log analysis request as a use of `armlet-readme`, run:
As an example, to wait up to 50 seconds, and log analysis request as as use of `armlet-readme`, run:
```javascript
client.analyzeWithStatus({data, timeout: 50000, clientToolName: 'armlet-readme'})
client.analyzeWithStatus(
{
"data": data,
"clientToolName": "armlet-readme"
})
.then(result => {

@@ -76,2 +97,82 @@ console.log(result.status, {depth: null})

# Improving Polling Response
There are two time parameters, given in milliseconds, that change how quickly a analysis result is reported back:
* initial delay
* maximum delay
The initial delay is the minimum amount of time that this library
waits before attempting its first status poll. Note however that if a
request has been cached, then results come back immediately and no
status polling is needed. (The server caches previous analysis runs;
it takes into account the data passed to it, the analysis mode, and the
back-end versions of components used to provide the analysis.)
The maximum delay is the maximum amount of time we will wait for an
analysis to complete. Note, however, that if the processing has not
finished when this timeout is reached, it may still be running on the
server side. Therefore when a timeout occurs, you will get back a
UUID which can subsequently be used to get status and results.
The closer these two parameters are to the actual time range that is
needed by analysis, the faster the response will get reported back
after completion on the server end. Below we explain
* why we have these two parameters,
* why giving good guesses helps response in reporting results,
* how you can get good guesses.
Until we have a websocket interface so the server can directly
pass back results without any additional action required on the server
side, your REST API requires the client to poll for status. We have
seen that this polling can cause a lot of overhead, if not done
judiciously. So, each request is allowed up to 10 status probes.
We have seen that _no_ analysis request will finish in less than a
certain period of time. Since the number of probe per analysis is
limited, it doesn't make sense to probe before the fastest
analysis-completion time.
The 10 status probes are done in geometrically increasing time
intervals. The first interval is the shortest and the last interval is
the longest. The response rate at the beginning is better than the
response rate at the end, in terms of how much additional time it
takes before the analysis completion is noticed.
However this progression is not fixed. Instead, it takes into account
the maximum amount of time you are willing to wait for a result.
In other words, the shorter the short period of time you give for the
maximum timeout, the shorter the geometric succession of the 10 probes
allotted to an analysis request will be.
To make this clear, if you only want to wait a; maximum of two minutes, then
the first delay will be 0.3 seconds, while the delay before last poll
will be about half a minute. If on the other hand you want to wait up
to 2 hours, then the first delay will be 9 seconds, and the last one will
be about 15 minutes.
Good guessing of these two parameters reduces the
unnecessary probe time while providing good response around the declared
period of time around that was declared.
So, how can you guess decent values? We have reasonable defaults built
in. But there are two factors that you can use to get better estimates.
The first is the kind of analysis mode used: a "quick" analysis will
usually be under two minutes, while a "full" analysis will usually be
under two hours.
When an analysis request finishes, we provide the amount of time used
broken into two components: the amount of time spent in analysis, and
the amount of time spent in queuing. The queuing time can vary
depending on what else is going on when the analysis request
was sent, so that's why it is separated out. In addition, the
library provides its own elapsed time in the response.
If you are making an analysis within an IDE which saves reports of
past runs, such as truffle or VSCode, the timings can be used for
estimates.
# See Also

@@ -78,0 +179,0 @@

@@ -12,7 +12,6 @@ const armlet = require('../index')

const simpleRequester = require('../lib/simpleRequester')
const poller = require('../lib/poller')
const poller = require('../lib/analysisPoller')
const login = require('../lib/login')
const refresh = require('../lib/refresh')
const email = 'user@example.com'
const ethAddress = '0x74B904af705Eb2D5a6CDc174c08147bED478a60d'

@@ -48,26 +47,16 @@ const password = 'my-password'

describe('should have a constructor which should', () => {
it('initialize with trial userId', () => {
const instance = new Client()
instance.userId.should.be.deep.equal(armlet.trialUserId)
it('throw error when initialize with no auth parameters', () => {
(() => new Client()).should.throw(TypeError, /Please provide/)
})
it('require a password auth option if email is provided', () => {
(() => new Client({ email })).should.throw(TypeError)
it('require an ethAddress and password ', () => {
(() => new Client({ ethAddress })).should.throw(TypeError, /Please provide/)
})
it('require a password auth option if ethAddress is provided', () => {
(() => new Client({ ethAddress })).should.throw(TypeError)
})
it('require an user id auth option', () => {
(() => new Client({ password })).should.throw(TypeError)
})
it('require a valid apiUrl if given', () => {
(() => new Client({ email, password }, 'not-a-valid-url')).should.throw(TypeError)
(() => new Client({ ethAddress, password }, 'not-a-valid-url')).should.throw(TypeError)
})
it('initialize apiUrl to a default value if not given', () => {
const instance = new Client({ email, password })
const instance = new Client({ ethAddress, password })

@@ -78,3 +67,3 @@ instance.apiUrl.should.be.deep.equal(armlet.defaultApiUrl)

it('initialize apiUrl to the given value', () => {
const instance = new Client({ email, password }, apiUrl)
const instance = new Client({ ethAddress, password }, apiUrl)

@@ -84,11 +73,5 @@ instance.apiUrl.should.be.deep.equal(new url.URL(apiUrl))

it('accept an apiKey auth and store it as accessToken', () => {
const instance = new Client({ apiKey: 'my-apikey' })
instance.accessToken.should.be.equal('my-apikey')
})
describe('instances should', () => {
beforeEach(() => {
this.instance = new Client({ email, password })
this.instance = new Client({ ethAddress, password })
})

@@ -125,14 +108,24 @@

const input = { data: 'content' }
const analyzeStub = sinon.stub(this.instance, 'analyze')
sinon.stub(Date, 'now')
.returns(1)
sinon.stub(this.instance, 'analyze')
.withArgs(input, true)
.resolves({ issues: 'issues', uuid: 'uuid' })
const getStatusStub = sinon.stub(this.instance, 'getStatus')
sinon.stub(this.instance, 'getStatus')
.withArgs('uuid')
.resolves('stubbed')
await this.instance.analyzeWithStatus(input)
.should.eventually.deep.equal({ issues: { issues: 'issues' }, status: 'stubbed' })
analyzeStub.calledWith(input).should.be.equal(true)
getStatusStub.calledWith('uuid').should.be.equal(true)
.should.eventually.deep.equal({
elapsed: 0,
issues: 'issues',
status: 'stubbed'
})
analyzeStub.restore()
getStatusStub.restore()
this.instance.analyze.restore()
this.instance.getStatus.restore()
Date.now.restore()
})

@@ -185,3 +178,3 @@ })

beforeEach(() => {
this.instance = new Client({ email, ethAddress, password }, apiUrl)
this.instance = new Client({ ethAddress, password }, apiUrl)
})

@@ -198,3 +191,3 @@

sinon.stub(login, 'do')
.withArgs(email, ethAddress, undefined, password, parsedApiUrl)
.withArgs(ethAddress, password, parsedApiUrl)
.returns(new Promise(resolve => {

@@ -208,3 +201,3 @@ resolve({ access: accessToken, refresh: refreshToken })

}))
// await this.instance.getStatus(uuid).should.eventually.equal('stubbed')
await this.instance.getStatus(uuid).should.eventually.equal('stubbed')
})

@@ -225,3 +218,3 @@ })

sinon.stub(login, 'do')
.withArgs(email, ethAddress, undefined, password, parsedApiUrl)
.withArgs(ethAddress, password, parsedApiUrl)
.returns(new Promise(resolve => {

@@ -233,3 +226,3 @@ resolve({ access: accessToken, refresh: refreshToken })

.returns(new Promise(resolve => {
resolve(uuid)
resolve({ uuid })
}))

@@ -242,9 +235,9 @@ sinon.stub(poller, 'do')

await this.instance.analyze({ data }).should.eventually.equal(issues)
await this.instance.analyze({ data }).should.eventually.deep.equal({ issues, uuid })
})
it('should reject with login failures', async () => {
const errorMsg = 'Invalid MythX credentials for email address user@example.com given.'
const errorMsg = 'Invalid MythX credentials for ethereum address 0x74B904af705Eb2D5a6CDc174c08147bED478a60d given.'
sinon.stub(login, 'do')
.withArgs(email, ethAddress, undefined, password, parsedApiUrl)
.withArgs(ethAddress, password, parsedApiUrl)
.returns(new Promise((resolve, reject) => {

@@ -256,3 +249,3 @@ reject(new Error(errorMsg))

.returns(new Promise(resolve => {
resolve(uuid)
resolve({ uuid })
}))

@@ -262,3 +255,3 @@ sinon.stub(poller, 'do')

.returns(new Promise(resolve => {
resolve(issues)
resolve({ issues })
}))

@@ -272,3 +265,3 @@

sinon.stub(login, 'do')
.withArgs(email, ethAddress, undefined, password, parsedApiUrl)
.withArgs(ethAddress, password, parsedApiUrl)
.returns(new Promise(resolve => {

@@ -285,3 +278,3 @@ resolve({ access: accessToken, refresh: refreshToken })

.returns(new Promise(resolve => {
resolve(issues)
resolve({ issues })
}))

@@ -295,3 +288,3 @@

sinon.stub(login, 'do')
.withArgs(email, ethAddress, undefined, password, parsedApiUrl)
.withArgs(ethAddress, password, parsedApiUrl)
.returns(new Promise(resolve => {

@@ -303,3 +296,3 @@ resolve({ access: accessToken, refresh: refreshToken })

.returns(new Promise(resolve => {
resolve(uuid)
resolve({ uuid })
}))

@@ -318,3 +311,3 @@ sinon.stub(poller, 'do')

sinon.stub(login, 'do')
.withArgs(email, ethAddress, undefined, password, parsedApiUrl)
.withArgs(ethAddress, password, parsedApiUrl)
.returns(new Promise(resolve => {

@@ -326,12 +319,54 @@ resolve({ access: accessToken, refresh: refreshToken })

.returns(new Promise(resolve => {
resolve(uuid)
resolve({ uuid, status: 'Finished' })
}))
sinon.stub(poller, 'getIssues')
.withArgs(uuid, accessToken, parsedApiUrl)
.returns(issues)
sinon.stub(poller, 'do')
.withArgs(uuid, accessToken, parsedApiUrl, undefined, timeout)
.withArgs(uuid, accessToken, parsedApiUrl, timeout)
.returns(new Promise(resolve => {
resolve(issues)
}))
await this.instance.analyze({ data, timeout }).should.eventually.deep.equal({ issues, uuid })
poller.getIssues.restore()
})
await this.instance.analyze({ data, timeout }).should.eventually.equal(issues)
it('should pass default initial delay option to poller', async () => {
const timeout = 40000
sinon.stub(login, 'do')
.withArgs(ethAddress, password, parsedApiUrl)
.returns(new Promise(resolve => {
resolve({ access: accessToken, refresh: refreshToken })
}))
sinon.stub(requester, 'do')
.withArgs({ data, timeout }, accessToken, parsedApiUrl)
.returns(new Promise(resolve => {
resolve({ uuid })
}))
sinon.stub(poller, 'do')
.withArgs(uuid, accessToken, parsedApiUrl, timeout, armlet.defaultInitialDelay, undefined)
.resolves(issues)
await this.instance.analyze({ data, timeout }).should.eventually.deep.equal({ issues, uuid })
})
it('should pass initial delay option to poller', async () => {
const timeout = 40000
const initialDelay = 50000
sinon.stub(login, 'do')
.withArgs(ethAddress, password, parsedApiUrl)
.returns(new Promise(resolve => {
resolve({ access: accessToken, refresh: refreshToken })
}))
sinon.stub(requester, 'do')
.withArgs({ data, timeout, initialDelay }, accessToken, parsedApiUrl)
.returns(new Promise(resolve => {
resolve({ uuid })
}))
sinon.stub(poller, 'do')
.withArgs(uuid, accessToken, parsedApiUrl, timeout, initialDelay, undefined)
.returns(new Promise(resolve => {
resolve(issues)
}))
await this.instance.analyze({ data, timeout, initialDelay }).should.eventually.deep.equal({ issues, uuid })
})
})

@@ -346,3 +381,3 @@

.returns(new Promise(resolve => {
resolve(uuid)
resolve({ uuid })
}))

@@ -355,3 +390,3 @@ sinon.stub(poller, 'do')

await this.instance.analyze({ data }).should.eventually.equal(issues)
await this.instance.analyze({ data }).should.eventually.deep.equal({ issues, uuid })
})

@@ -384,3 +419,3 @@ })

.returns(new Promise(resolve => {
resolve(uuid)
resolve({ uuid })
}))

@@ -400,3 +435,3 @@

await this.instance.analyze({ data }).should.eventually.equal(issues)
await this.instance.analyze({ data }).should.eventually.deep.equal({ issues, uuid })
})

@@ -418,3 +453,3 @@

.returns(new Promise(resolve => {
resolve(uuid)
resolve({ uuid })
}))

@@ -428,3 +463,3 @@

await this.instance.analyze({ data }).should.eventually.equal(issues)
await this.instance.analyze({ data }).should.eventually.deep.equal({ issues, uuid })
})

@@ -449,3 +484,3 @@ })

sinon.stub(login, 'do')
.withArgs(email, ethAddress, undefined, password, parsedApiUrl)
.withArgs(ethAddress, password, parsedApiUrl)
.returns(new Promise(resolve => {

@@ -466,3 +501,3 @@ resolve({ access: accessToken, refresh: refreshToken })

sinon.stub(login, 'do')
.withArgs(email, ethAddress, undefined, password, parsedApiUrl)
.withArgs(ethAddress, password, parsedApiUrl)
.returns(new Promise((resolve, reject) => {

@@ -483,3 +518,3 @@ reject(new Error(errorMsg))

sinon.stub(login, 'do')
.withArgs(email, ethAddress, undefined, password, parsedApiUrl)
.withArgs(ethAddress, password, parsedApiUrl)
.returns(new Promise(resolve => {

@@ -551,36 +586,2 @@ resolve({ access: accessToken, refresh: refreshToken })

})
describe('as anonymous user', () => {
beforeEach(() => {
this.instance = new Client({ }, apiUrl)
})
describe('analyze', () => {
it('should login and chain requester and poller', async () => {
sinon.stub(login, 'do')
.withArgs(undefined, undefined, armlet.trialUserId, undefined, parsedApiUrl)
.returns(new Promise(resolve => {
resolve({ access: accessToken, refresh: refreshToken })
}))
sinon.stub(requester, 'do')
.withArgs({ data }, accessToken, parsedApiUrl)
.returns(new Promise(resolve => {
resolve(uuid)
}))
sinon.stub(poller, 'do')
.withArgs(uuid, accessToken, parsedApiUrl)
.returns(new Promise(resolve => {
resolve(issues)
}))
await this.instance.analyze({ data }).should.eventually.equal(issues)
})
afterEach(() => {
requester.do.restore()
poller.do.restore()
login.do.restore()
})
})
})
})

@@ -587,0 +588,0 @@

@@ -13,7 +13,5 @@ const nock = require('nock')

const parsedApiUrl = new url.URL(apiUrl)
const email = 'content'
const ethAddress = '0x74B904af705Eb2D5a6CDc174c08147bED478a60d'
const userId = '123456'
const password = 'password'
const auth = { email, ethAddress, userId, password }
const auth = { ethAddress, password }
const loginPath = '/v1/auth/login'

@@ -29,9 +27,19 @@ const refresh = 'refresh-token'

await login.do(email, ethAddress, userId, password, parsedApiUrl).should.eventually.deep.equal(jsonTokens)
await login.do(ethAddress, password, parsedApiUrl).should.eventually.deep.equal(jsonTokens)
})
it('should redirect refresh and access tokens', async () => {
nock('http://localhost:3100')
.post(loginPath, auth)
.reply(200, jsonTokens)
const parsedApiUrlHttp = new url.URL('http://localhost:3100')
await login.do(ethAddress, password, parsedApiUrlHttp)
.should.eventually.deep.equal(jsonTokens)
})
it('should reject on api server connection failure', async () => {
const invalidUrlObject = 'not-an-url-object'
await login.do(email, ethAddress, userId, password, invalidUrlObject).should.be.rejectedWith(Error)
await login.do(ethAddress, password, invalidUrlObject).should.be.rejectedWith(Error)
})

@@ -44,3 +52,3 @@

await login.do(email, ethAddress, userId, password, parsedApiUrl).should.be.rejectedWith(Error, 'Invalid status code')
await login.do(ethAddress, password, parsedApiUrl).should.be.rejectedWith('HTTP status 500')
})

@@ -53,3 +61,3 @@

await login.do(email, ethAddress, userId, password, parsedApiUrl).should.be.rejectedWith(Error, 'JSON parse error')
await login.do(ethAddress, password, parsedApiUrl).should.be.rejectedWith(Error, 'JSON parse error')
})

@@ -62,3 +70,3 @@

await login.do(email, ethAddress, userId, password, parsedApiUrl).should.be.rejectedWith(Error, 'Refresh Token missing')
await login.do(ethAddress, password, parsedApiUrl).should.be.rejectedWith(Error, 'Refresh Token missing')
})

@@ -71,5 +79,5 @@

await login.do(email, ethAddress, userId, password, parsedApiUrl).should.be.rejectedWith(Error, 'Access Token missing')
await login.do(ethAddress, password, parsedApiUrl).should.be.rejectedWith(Error, 'Access Token missing')
})
})
})

@@ -32,3 +32,6 @@ const nock = require('nock')

await requester.do(data, validApiKey, httpApiUrl).should.eventually.equal(uuid)
await requester.do(data, validApiKey, httpApiUrl).should.eventually.deep.equal({
result: 'Queued',
uuid
})
})

@@ -48,3 +51,6 @@

await requester.do(data, validApiKey, httpsApiUrl).should.eventually.equal(uuid)
await requester.do(data, validApiKey, httpsApiUrl).should.eventually.deep.equal({
result: 'Queued',
uuid
})
})

@@ -64,3 +70,6 @@

await requester.do(data, validApiKey, defaultApiUrl).should.eventually.equal(uuid)
await requester.do(data, validApiKey, defaultApiUrl).should.eventually.deep.equal({
result: 'Queued',
uuid
})
})

@@ -154,3 +163,3 @@

await requester.do(data, validApiKey, defaultApiUrl).should.be.rejectedWith(SyntaxError)
await requester.do(data, validApiKey, defaultApiUrl).should.be.rejectedWith('Non JSON data returned: non-json-response')
})

@@ -157,0 +166,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

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