Socket
Socket
Sign inDemoInstall

json-rpc-engine

Package Overview
Dependencies
Maintainers
5
Versions
36
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

json-rpc-engine - npm Package Compare versions

Comparing version 5.1.8 to 5.2.0

LICENSE

22

CHANGELOG.md

@@ -1,8 +0,22 @@

# [5.0.0]
upgraded to babelify@10
# Changelog
<a name="3.0.0"></a>
# [3.0.0](https://github.com/kumavis/json-rpc-engine/compare/v2.2.3...v3.0.0) (2017-04-04)
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
## [5.2.0] - 2020-07-24
### Added
- Promise signatures for `engine.handle` ([#55](https://github.com/MetaMask/json-rpc-engine/pull/55))
- So, in addition to `engine.handle(request, callback)`, you can do e.g. `await engine.handle(request)`.
### Changed
- Remove `async` and `promise-to-callback` dependencies
- These dependencies were used internally for asynchronous control flow.
They have been replaced with Promises and native `async`/`await`.
This has made middleware execution faster, and may affect consumers that rely on middleware timing, advertently or not.

42

package.json
{
"name": "json-rpc-engine",
"version": "5.1.8",
"version": "5.2.0",
"description": "a tool for processing JSON RPC",

@@ -9,35 +9,33 @@ "license": "ISC",

"types": "src/index.d.ts",
"files": [
"src"
],
"scripts": {
"lint": "aegir lint",
"build": "aegir build && cp ./src/index.d.ts ./dist",
"test": "npm run test:node",
"test:node": "aegir test --target node",
"test:browser": "aegir test --target browser",
"release": "aegir release",
"release-minor": "aegir release --type minor",
"release-major": "aegir release --type major",
"coverage": "aegir coverage",
"coverage-publish": "aegir coverage --provider coveralls"
"lint": "eslint . --ext js,json",
"lint:fix": "eslint . --ext js,json --fix",
"test": "mocha ./test",
"coverage": "nyc --check-coverage yarn test"
},
"dependencies": {
"async": "^2.0.1",
"eth-json-rpc-errors": "^2.0.1",
"promise-to-callback": "^1.0.0",
"eth-rpc-errors": "^2.1.1",
"safe-event-emitter": "^1.0.1"
},
"devDependencies": {
"@babel/core": "^7.5.5",
"@babel/preset-env": "^7.5.5",
"aegir": "^20.2.0",
"babelify": "^10.0.0",
"browserify": "^16.2.3"
"@metamask/eslint-config": "^2.1.0",
"eslint": "^6.8.0",
"eslint-plugin-import": "^2.20.1",
"eslint-plugin-json": "^2.1.0",
"eslint-plugin-mocha": "^6.3.0",
"mocha": "^7.1.1",
"nyc": "^15.0.0",
"sinon": "^9.0.2"
},
"repository": {
"type": "git",
"url": "git+https://github.com/kumavis/json-rpc-engine.git"
"url": "git+https://github.com/MetaMask/json-rpc-engine.git"
},
"bugs": {
"url": "https://github.com/kumavis/json-rpc-engine/issues"
"url": "https://github.com/MetaMask/json-rpc-engine/issues"
},
"homepage": "https://github.com/kumavis/json-rpc-engine#readme",
"homepage": "https://github.com/MetaMask/json-rpc-engine#readme",
"directories": {

@@ -44,0 +42,0 @@ "test": "test"

@@ -1,6 +0,6 @@

# RpcEngine
# json-rpc-engine
a tool for processing JSON RPC
A tool for processing JSON-RPC requests and responses.
### usage
## Usage

@@ -13,3 +13,3 @@ ```js

Build a stack of json rpc processors by pushing in RpcEngine middleware.
Build a stack of JSON-RPC processors by pushing middleware to the engine.

@@ -23,3 +23,3 @@ ```js

JSON RPC are handled asynchronously, stepping down the stack until complete.
Requests are handled asynchronously, stepping down the stack until complete.

@@ -29,9 +29,12 @@ ```js

engine.handle(request, function(err, res){
// do something with res.result
engine.handle(request, function(err, response){
// do something with response.result
})
// there is also a Promise signature
const response = await engine.handle(request)
```
RpcEngine middleware has direct access to the request and response objects.
It can let processing continue down the stack with `next()` or complete the request with `end()`.
Middleware have direct access to the request and response objects.
They can let processing continue down the stack with `next()`, or complete the request with `end()`.

@@ -46,3 +49,3 @@ ```js

By passing a 'return handler' to the `next` function, you can get a peek at the result before it returns.
By passing a _return handler_ to the `next` function, you can get a peek at the result before it returns.

@@ -57,6 +60,6 @@ ```js

RpcEngines can be nested by converting them to middleware `asMiddleware(engine)`
RpcEngines can be nested by converting them to middleware using `asMiddleware(engine)`:
```js
const asMiddleware = require('json-rpc-engine/lib/asMiddleware')
const asMiddleware = require('json-rpc-engine/src/asMiddleware')

@@ -68,4 +71,61 @@ let engine = new RpcEngine()

### gotchas
### `async` Middleware
If you require your middleware function to be `async`, use `createAsyncMiddleware`:
```js
const createAsyncMiddleware = require('json-rpc-engine/src/createAsyncMiddleware')
let engine = new RpcEngine()
engine.push(createAsyncMiddleware(async (req, res, next) => {
res.result = 42
next()
}))
```
`async` middleware do not take an `end` callback.
Instead, the request ends if the middleware returns without calling `next()`:
```js
engine.push(createAsyncMiddleware(async (req, res, next) => {
res.result = 42
/* The request will end when this returns */
}))
```
The `next` callback of `async` middleware also don't take return handlers.
Instead, you can `await next()`.
When the execution of the middleware resumes, you can work with the response again.
```js
engine.push(createAsyncMiddleware(async (req, res, next) => {
res.result = 42
await next()
/* Your return handler logic goes here */
addToMetrics(res)
}))
```
You can freely mix callback-based and `async` middleware:
```js
engine.push(function(req, res, next, end){
if (!isCached(req)) {
return next((cb) => {
insertIntoCache(res, cb)
})
}
res.result = getResultFromCache(req)
end()
})
engine.push(createAsyncMiddleware(async (req, res, next) => {
res.result = 42
await next()
addToMetrics(res)
}))
```
### Gotchas
Handle errors via `end(err)`, *NOT* `next(err)`.

@@ -85,3 +145,10 @@

That said, `next()` will detect errors on the RPC response, and cause
However, `next()` will detect errors on the response object, and cause
`end(res.error)` to be called.
```js
engine.push(function(req, res, next, end){
res.error = new Error()
next() /* This will cause end(res.error) to be called. */
})
```
'use strict'
module.exports = asMiddleware
module.exports = function asMiddleware (engine) {
return function engineAsMiddleware (req, res, next, end) {
engine._runAllMiddleware(req, res)
.then(async ({ isComplete, returnHandlers }) => {
function asMiddleware (engine) {
return function engineAsMiddleware (req, res, next, end) {
engine._runMiddlewareDown(req, res, function (err, { isComplete, returnHandlers }) {
if (err) return end(err)
if (isComplete) {
engine._runReturnHandlersUp(returnHandlers, end)
} else {
next((cb) => {
engine._runReturnHandlersUp(returnHandlers, cb)
if (isComplete) {
await engine._runReturnHandlers(returnHandlers)
return end()
}
return next(async (handlerCallback) => {
try {
await engine._runReturnHandlers(returnHandlers)
} catch (err) {
return handlerCallback(err)
}
return handlerCallback()
})
}
})
})
.catch((error) => {
end(error)
})
}
}

@@ -1,40 +0,60 @@

const promiseToCallback = require('promise-to-callback')
/**
* JsonRpcEngine only accepts callback-based middleware directly.
* createAsyncMiddleware exists to enable consumers to pass in async middleware
* functions.
*
* Async middleware have no "end" function. Instead, they "end" if they return
* without calling "next". Rather than passing in explicit return handlers,
* async middleware can simply await "next", and perform operations on the
* response object when execution resumes.
*
* To accomplish this, createAsyncMiddleware passes the async middleware a
* wrapped "next" function. That function calls the internal JsonRpcEngine
* "next" function with a return handler that resolves a promise when called.
*
* The return handler will always be called. Its resolution of the promise
* enables the control flow described above.
*/
module.exports = createAsyncMiddleware
module.exports = function createAsyncMiddleware (asyncMiddleware) {
return (req, res, next, end) => {
function createAsyncMiddleware(asyncMiddleware) {
return (req, res, next, end) => {
let nextDonePromise = null
const finishedPromise = asyncMiddleware(req, res, getNextPromise)
promiseToCallback(finishedPromise)((err) => {
// async middleware ended
if (nextDonePromise) {
// next handler was called - complete nextHandler
promiseToCallback(nextDonePromise)((nextErr, nextHandlerSignalDone) => {
// nextErr is only present if something went really wrong
// if an error is thrown after `await next()` it appears as `err` and not `nextErr`
if (nextErr) {
console.error(nextErr)
return end(nextErr)
}
nextHandlerSignalDone(err)
})
} else {
// next handler was not called - complete middleware
end(err)
}
// nextPromise is the key to the implementation
// it is resolved by the return handler passed to the
// "next" function
let resolveNextPromise
const nextPromise = new Promise((resolve) => {
resolveNextPromise = resolve
})
async function getNextPromise() {
nextDonePromise = getNextDoneCallback()
await nextDonePromise
return undefined
let returnHandlerCallback, nextWasCalled
const asyncNext = async () => {
nextWasCalled = true
next((callback) => { // eslint-disable-line callback-return
returnHandlerCallback = callback
resolveNextPromise()
})
await nextPromise
}
function getNextDoneCallback() {
return new Promise((resolve) => {
next((cb) => resolve(cb))
asyncMiddleware(req, res, asyncNext)
.then(async () => {
if (nextWasCalled) {
await nextPromise // we must wait until the return handler is called
returnHandlerCallback(null)
} else {
end(null)
}
})
}
.catch((error) => {
if (returnHandlerCallback) {
returnHandlerCallback(error)
} else {
end(error)
}
})
}
}

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

module.exports = createScaffoldMiddleware
function createScaffoldMiddleware (handlers) {
module.exports = function createScaffoldMiddleware (handlers) {
return (req, res, next, end) => {

@@ -16,4 +14,4 @@ const handler = handlers[req.method]

res.result = handler
end()
return end()
}
}

@@ -6,7 +6,5 @@ // uint32 (two's complement) max

module.exports = getUniqueId
function getUniqueId () {
module.exports = function getUniqueId () {
idCounter = (idCounter + 1) % MAX
return idCounter
}
}
const getUniqueId = require('./getUniqueId')
module.exports = createIdRemapMiddleware
function createIdRemapMiddleware() {
return (req, res, next, end) => {
module.exports = function createIdRemapMiddleware () {
return (req, res, next, _end) => {
const originalId = req.id

@@ -8,0 +6,0 @@ const newId = getUniqueId()

import { IEthereumRpcError } from 'eth-json-rpc-errors/@types'
import { IEthereumRpcError } from 'eth-rpc-errors/@types'

@@ -4,0 +4,0 @@ /** A String specifying the version of the JSON-RPC protocol. MUST be exactly "2.0". */

'use strict'
const async = require('async')
const SafeEventEmitter = require('safe-event-emitter')
const {
serializeError, EthereumRpcError, ERROR_CODES
} = require('eth-json-rpc-errors')
serializeError,
EthereumRpcError,
ERROR_CODES,
} = require('eth-rpc-errors')
class RpcEngine extends SafeEventEmitter {
module.exports = class RpcEngine extends SafeEventEmitter {
constructor () {

@@ -23,8 +25,17 @@ super()

handle (req, cb) {
// batch request support
if (Array.isArray(req)) {
this._handleBatch(req, cb)
} else {
this._handle(req, cb)
if (cb) {
this._handleBatch(req)
.then((res) => cb(null, res))
.catch((err) => cb(err)) // fatal error
return undefined
}
return this._handleBatch(req)
}
if (!cb) {
return this._promiseHandle(req)
}
return this._handle(req, cb)
}

@@ -36,27 +47,17 @@

async _handleBatch (reqs, cb) {
async _handleBatch (reqs) {
// The order here is important
try {
const batchRes = await Promise.all( // 2. Wait for all requests to finish
// 1. Begin executing each request in the order received
reqs.map(this._promiseHandle.bind(this))
)
cb(null, batchRes) // 3a. Return batch response
} catch (err) {
cb(err) // 3b. Some kind of fatal error; all requests are lost
}
// 3. Return batch response, or reject on some kind of fatal error
return await Promise.all( // 2. Wait for all requests to finish
// 1. Begin executing each request in the order received
reqs.map(this._promiseHandle.bind(this)),
)
}
_promiseHandle (req) {
return new Promise((resolve, reject) => {
this._handle(req, (err, res) => {
if (!res) { // defensive programming
reject(err || new EthereumRpcError(
ERROR_CODES.rpc.internal,
'JsonRpcEngine: Request handler returned neither error nor response.'
))
} else {
resolve(res)
}
return new Promise((resolve) => {
this._handle(req, (_err, res) => {
// there will always be a response, and it will always have any error
// that is caught and propagated
resolve(res)
})

@@ -66,118 +67,114 @@ })

_handle (_req, cb) {
// shallow clone request object
const req = Object.assign({}, _req)
// create response obj
_handle (callerReq, cb) {
const req = Object.assign({}, callerReq)
const res = {
id: req.id,
jsonrpc: req.jsonrpc
jsonrpc: req.jsonrpc,
}
// process all middleware
this._runMiddleware(req, res, (err) => {
// take a clear any responseError
const responseError = res._originalError
delete res._originalError
if (responseError) {
// ensure no result is present on an errored response
delete res.result
// return originalError and response
return cb(responseError, res)
}
// return response
cb(err, res)
})
let processingError
this._processRequest(req, res)
.catch((error) => {
// either from return handlers or something unexpected
processingError = error
})
.finally(() => {
// preserve unserialized error, if any, for use in callback
const responseError = res._originalError
delete res._originalError
const error = responseError || processingError || null
if (error) {
// ensure no result is present on an errored response
delete res.result
if (!res.error) {
res.error = serializeError(error)
}
}
cb(error, res)
})
}
_runMiddleware (req, res, onDone) {
// flow
async.waterfall([
(cb) => this._runMiddlewareDown(req, res, cb),
checkForCompletion,
(returnHandlers, cb) => this._runReturnHandlersUp(returnHandlers, cb),
], onDone)
async _processRequest (req, res) {
const { isComplete, returnHandlers } = await this._runAllMiddleware(req, res)
this._checkForCompletion(req, res, isComplete)
await this._runReturnHandlers(returnHandlers)
}
function checkForCompletion({ isComplete, returnHandlers }, cb) {
// fail if not completed
if (!('result' in res) && !('error' in res)) {
const requestBody = JSON.stringify(req, null, 2)
const message = 'JsonRpcEngine: Response has no error or result for request:\n' + requestBody
return cb(new EthereumRpcError(
ERROR_CODES.rpc.internal, message, req
))
}
if (!isComplete) {
const requestBody = JSON.stringify(req, null, 2)
const message = 'JsonRpcEngine: Nothing ended request:\n' + requestBody
return cb(new EthereumRpcError(
ERROR_CODES.rpc.internal, message, req
))
}
// continue
return cb(null, returnHandlers)
async _runReturnHandlers (handlers) {
for (const handler of handlers) {
await new Promise((resolve, reject) => {
handler((err) => (err ? reject(err) : resolve()))
})
}
}
_checkForCompletion (req, res, isComplete) {
if (!('result' in res) && !('error' in res)) {
const requestBody = JSON.stringify(req, null, 2)
const message = `JsonRpcEngine: Response has no error or result for request:\n${requestBody}`
throw new EthereumRpcError(ERROR_CODES.rpc.internal, message, req)
}
if (!isComplete) {
const requestBody = JSON.stringify(req, null, 2)
const message = `JsonRpcEngine: Nothing ended request:\n${requestBody}`
throw new EthereumRpcError(ERROR_CODES.rpc.internal, message, req)
}
}
// walks down stack of middleware
_runMiddlewareDown (req, res, onDone) {
// for climbing back up the stack
let allReturnHandlers = []
// flag for stack return
async _runAllMiddleware (req, res) {
const returnHandlers = []
// flag for early return
let isComplete = false
// down stack of middleware, call and collect optional allReturnHandlers
async.mapSeries(this._middleware, eachMiddleware, completeRequest)
// go down stack of middleware, call and collect optional returnHandlers
for (const middleware of this._middleware) {
isComplete = await RpcEngine._runMiddleware(
req, res, middleware, returnHandlers,
)
if (isComplete) {
break
}
}
return { isComplete, returnHandlers: returnHandlers.reverse() }
}
// runs an individual middleware
function eachMiddleware (middleware, cb) {
// skip middleware if completed
if (isComplete) return cb()
// run individual middleware
middleware(req, res, next, end)
// runs an individual middleware
static _runMiddleware (req, res, middleware, returnHandlers) {
return new Promise((resolve) => {
function next (returnHandler) {
const end = (err) => {
const error = err || (res && res.error)
if (error) {
res.error = serializeError(error)
res._originalError = error
}
resolve(true) // true indicates the request should end
}
const next = (returnHandler) => {
if (res.error) {
end(res.error)
} else {
// add return handler
allReturnHandlers.push(returnHandler)
cb()
if (returnHandler) {
returnHandlers.push(returnHandler)
}
resolve(false) // false indicates the request should not end
}
}
function end (err) {
// if errored, set the error but dont pass to callback
const _err = err || (res && res.error)
// const _err = err
if (_err) {
res.error = serializeError(_err)
res._originalError = _err
}
// mark as completed
isComplete = true
cb()
try {
middleware(req, res, next, end)
} catch (error) {
end(error)
}
}
// returns, indicating whether or not it ended
function completeRequest (err) {
// this is an internal catastrophic error, not an error from middleware
if (err) {
// prepare error message
res.error = serializeError(err)
// remove result if present
delete res.result
// return error-first and res with err
return onDone(err, res)
}
const returnHandlers = allReturnHandlers.filter(Boolean).reverse()
onDone(null, { isComplete, returnHandlers })
}
})
}
// climbs the stack calling return handlers
_runReturnHandlersUp (returnHandlers, cb) {
async.eachSeries(returnHandlers, (handler, next) => handler(next), cb)
}
}
module.exports = RpcEngine

@@ -1,10 +0,8 @@

const JsonRpcEngine = require('./index')
const asMiddleware = require('./asMiddleware')
const JsonRpcEngine = require('.')
module.exports = mergeMiddleware
function mergeMiddleware(middlewareStack) {
module.exports = function mergeMiddleware (middlewareStack) {
const engine = new JsonRpcEngine()
middlewareStack.forEach(middleware => engine.push(middleware))
middlewareStack.forEach((middleware) => engine.push(middleware))
return asMiddleware(engine)
}
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