Improve module template compliance (#100)

This PR updates various repository features for module template compliance. This repository should be fully compliant as of this PR, except for tests, which will be addressed in a follow-up. Notable changes include:

- Updates the ESLint config and related dependencies
- Adds `@lavamoat/allow-scripts` and the `setup` package script
- Migrates from CircleCI to GitHub Actions
- Bumps the minimum Node version to 14
This commit is contained in:
Erik Marks
2022-04-08 11:16:22 -07:00
committed by GitHub
parent d38b71aaa8
commit 75d2d37c7a
29 changed files with 1561 additions and 610 deletions

View File

@@ -1,86 +0,0 @@
version: 2.1
workflows:
build-test:
jobs:
- prep-deps
- prep-build:
requires:
- prep-deps
- test-lint:
requires:
- prep-deps
- prep-build
- test-unit:
requires:
- prep-deps
- prep-build
- all-tests-pass:
requires:
- test-lint
- test-unit
jobs:
prep-deps:
docker:
- image: circleci/node:12
steps:
- checkout
- run:
name: Install deps
command: |
.circleci/scripts/deps-install.sh
- run:
name: Collect yarn install HAR logs
command: |
.circleci/scripts/collect-har-artifact.sh
- persist_to_workspace:
root: .
paths:
- node_modules
- build-artifacts
test-lint:
docker:
- image: circleci/node:12
steps:
- checkout
- attach_workspace:
at: .
- run:
name: Lint
command: yarn lint
prep-build:
docker:
- image: circleci/node:12
steps:
- checkout
- attach_workspace:
at: .
- run:
name: Build project
command: yarn build
- persist_to_workspace:
root: .
paths:
- dist
test-unit:
docker:
- image: circleci/node:12
steps:
- checkout
- attach_workspace:
at: .
- run:
name: Unit tests
command: yarn coverage
all-tests-pass:
docker:
- image: circleci/node:12
steps:
- run:
name: All tests passed
command: echo 'Great success'

View File

@@ -1,9 +0,0 @@
#!/usr/bin/env bash
set -x
set -e
set -u
set -o pipefail
mkdir -p build-artifacts/yarn-install-har
mv ./*.har build-artifacts/yarn-install-har/

View File

@@ -1,8 +0,0 @@
#!/usr/bin/env bash
set -x
set -e
set -u
set -o pipefail
yarn --frozen-lockfile --ignore-scripts --har

View File

@@ -1,13 +1,9 @@
# http://editorconfig.org
root = true
[*]
charset = utf-8
end_of_line = lf
indent_size = 2
indent_style = space
insert_final_newline = true
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
[*.md]
indent_size = 4
insert_final_newline = true

View File

@@ -1,22 +1,28 @@
module.exports = {
root: true,
plugins: ['import'],
extends: ['@metamask/eslint-config', '@metamask/eslint-config-nodejs'],
rules: {
'prefer-object-spread': 'off',
},
extends: ['@metamask/eslint-config'],
overrides: [
{
files: ['*.ts'],
extends: ['@metamask/eslint-config-typescript'],
},
{
files: ['*.js'],
parserOptions: {
sourceType: 'script',
},
extends: ['@metamask/eslint-config-nodejs'],
},
{
files: ['test/*'],
extends: ['@metamask/eslint-config-mocha'],
parserOptions: {
ecmaVersion: 2020,
},
},
],

15
.github/dependabot.yml vendored Normal file
View File

@@ -0,0 +1,15 @@
# Please see the documentation for all configuration options:
# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
version: 2
updates:
- package-ecosystem: 'npm'
directory: '/'
schedule:
interval: 'daily'
time: '06:00'
allow:
- dependency-name: '@metamask/*'
target-branch: 'main'
versioning-strategy: 'increase-if-necessary'
open-pull-requests-limit: 10

56
.github/workflows/build-test.yml vendored Normal file
View File

@@ -0,0 +1,56 @@
name: Build, Lint, and Test
on:
push:
branches: [main]
pull_request:
jobs:
build-lint-test:
name: Build, Lint, and Test
runs-on: ubuntu-20.04
strategy:
matrix:
node-version: [14.x, 16.x]
steps:
- uses: actions/checkout@v2
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v2
with:
node-version: ${{ matrix.node-version }}
- name: Get Yarn cache directory
run: echo "::set-output name=YARN_CACHE_DIR::$(yarn cache dir)"
id: yarn-cache-dir
- name: Get Yarn version
run: echo "::set-output name=YARN_VERSION::$(yarn --version)"
id: yarn-version
- name: Cache yarn dependencies
uses: actions/cache@v2
with:
path: ${{ steps.yarn-cache-dir.outputs.YARN_CACHE_DIR }}
key: yarn-cache-${{ runner.os }}-${{ steps.yarn-version.outputs.YARN_VERSION }}-${{ hashFiles('yarn.lock') }}
- run: yarn --frozen-lockfile
- run: yarn allow-scripts
- run: yarn build
- run: yarn lint
- run: yarn test
- name: Validate RC changelog
if: ${{ startsWith(github.head_ref, 'release/') }}
run: yarn auto-changelog validate --rc
- name: Validate changelog
if: ${{ !startsWith(github.head_ref, 'release/') }}
run: yarn auto-changelog validate
- name: Require clean working directory
shell: bash
run: |
if ! git diff --exit-code; then
echo "Working tree dirty after building"
exit 1
fi
all-jobs-pass:
name: All jobs pass
runs-on: ubuntu-20.04
needs:
- build-lint-test
steps:
- run: echo "Great success!"

50
.github/workflows/create-release-pr.yml vendored Normal file
View File

@@ -0,0 +1,50 @@
name: Create Release Pull Request
on:
workflow_dispatch:
inputs:
base-branch:
description: 'The base branch for git operations and the pull request.'
default: 'main'
required: true
release-type:
description: 'A SemVer version diff, i.e. major, minor, patch, prerelease etc. Mutually exclusive with "release-version".'
required: false
release-version:
description: 'A specific version to bump to. Mutually exclusive with "release-type".'
required: false
jobs:
create-release-pr:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- uses: actions/checkout@v2
with:
# This is to guarantee that the most recent tag is fetched.
# This can be configured to a more reasonable value by consumers.
fetch-depth: 0
# We check out the specified branch, which will be used as the base
# branch for all git operations and the release PR.
ref: ${{ github.event.inputs.base-branch }}
- name: Get Node.js version
id: nvm
run: echo ::set-output name=NODE_VERSION::$(cat .nvmrc)
- uses: actions/setup-node@v2
with:
node-version: ${{ steps.nvm.outputs.NODE_VERSION }}
- uses: MetaMask/action-create-release-pr@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
release-type: ${{ github.event.inputs.release-type }}
release-version: ${{ github.event.inputs.release-version }}
artifacts-path: gh-action__release-authors
# Upload the release author artifact for use in subsequent workflows
- uses: actions/upload-artifact@v2
with:
name: release-authors
path: gh-action__release-authors
if-no-files-found: error

29
.github/workflows/publish-release.yml vendored Normal file
View File

@@ -0,0 +1,29 @@
name: Publish Release
on:
pull_request:
types: [closed]
jobs:
publish-release:
permissions:
contents: write
if: |
github.event.pull_request.merged == true &&
startsWith(github.event.pull_request.head.ref, 'release/')
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
# We check out the release pull request's base branch, which will be
# used as the base branch for all git operations.
ref: ${{ github.event.pull_request.base.ref }}
- name: Get Node.js version
id: nvm
run: echo ::set-output name=NODE_VERSION::$(cat .nvmrc)
- uses: actions/setup-node@v2
with:
node-version: ${{ steps.nvm.outputs.NODE_VERSION }}
- uses: MetaMask/action-publish-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -0,0 +1,29 @@
name: Require Additional Reviewer for Releases
on:
pull_request:
pull_request_review:
jobs:
require-additional-reviewer:
permissions:
actions: read
contents: read
pull-requests: read
statuses: write
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
# If the base branch has been merged into the release branch, we
# need to find the earliest common ancestor commit of the base and
# release branches.
fetch-depth: 0
# We want the head / feature branch to be checked out, and we will
# compare it to the base branch in the action.
ref: ${{ github.event.pull_request.head.ref }}
- uses: MetaMask/action-require-additional-reviewer@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
read-org-token: ${{ secrets.ORG_READER }}

2
.nvmrc
View File

@@ -1 +1 @@
v12
v14

8
.prettierrc.js Normal file
View File

@@ -0,0 +1,8 @@
// All of these are defaults except singleQuote, but we specify them
// for explicitness
module.exports = {
quoteProps: 'as-needed',
singleQuote: true,
tabWidth: 2,
trailingComma: 'all',
};

View File

@@ -1,4 +0,0 @@
quoteProps: consistent
singleQuote: true
trailingComma: all
tabWidth: 2

1
.yarnrc Normal file
View File

@@ -0,0 +1 @@
ignore-scripts true

View File

@@ -1,41 +1,31 @@
# Changelog
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]
### Added
- `isJsonRpcSuccess` and `isJsonRpcFailure` type guard utilities ([#91](https://github.com/MetaMask/json-rpc-engine/pull/91))
- JSON-RPC ID validation utility and type guard, via `getJsonRpcIdValidator` ([#91](https://github.com/MetaMask/json-rpc-engine/pull/91))
### Changed
- **(BREAKING)** Return a `null` instead of `undefined` response `id` for malformed request objects ([#91](https://github.com/MetaMask/json-rpc-engine/pull/91))
- This is very unlikely to be breaking in practice, but the behavior could have been relied on.
## [6.1.0] - 2020-11-20
### Added
- Add `PendingJsonRpcResponse` interface for use in middleware ([#75](https://github.com/MetaMask/json-rpc-engine/pull/75))
### Changed
- Use `async`/`await` and `try`/`catch` instead of Promise methods everywhere ([#74](https://github.com/MetaMask/json-rpc-engine/pull/74))
- Consumers may notice improved stack traces on certain platforms.
## [6.0.0] - 2020-11-19
### Added
- Add docstrings for public `JsonRpcEngine` methods ([#70](https://github.com/MetaMask/json-rpc-engine/pull/70))
### Changed
- **(BREAKING)** Refactor exports ([#69](https://github.com/MetaMask/json-rpc-engine/pull/69))
- All exports are now named, and available via the package entry point.
- All default exports have been removed.
@@ -50,43 +40,28 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Make some internal `JsonRpcEngine` methods `static` ([#71](https://github.com/MetaMask/json-rpc-engine/pull/71))
## [5.4.0] - 2020-11-07
### Changed
- Make the TypeScript types not terrible ([#66](https://github.com/MetaMask/json-rpc-engine/pull/66), [#67](https://github.com/MetaMask/json-rpc-engine/pull/67))
## [5.3.0] - 2020-07-30
### Changed
- Response object errors no longer include a `stack` property
## [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 middleware flow control.
They have been replaced with Promises and native `async`/`await`, which means that some operations are _no longer_ eagerly executed.
This change may affect consumers that depend on the eager execution of middleware _during_ request processing, _outside of_ middleware functions and request handlers.
- In general, it is a bad practice to work with state that depends on middleware execution, while the middleware are executing.
[unreleased]: https://github.com/MetaMask/json-rpc-engine/compare/v6.1.0...HEAD
[Unreleased]: https://github.com/MetaMask/json-rpc-engine/compare/v6.1.0...HEAD
[6.1.0]: https://github.com/MetaMask/json-rpc-engine/compare/v6.0.0...v6.1.0
[6.0.0]: https://github.com/MetaMask/json-rpc-engine/compare/v5.4.0...v6.0.0
[5.4.0]: https://github.com/MetaMask/json-rpc-engine/compare/v5.3.0...v5.4.0
[5.3.0]: https://github.com/MetaMask/json-rpc-engine/compare/v5.2.0...v5.3.0
[5.2.0]: https://github.com/MetaMask/json-rpc-engine/compare/v5.1.8...v5.2.0
[5.1.8]: https://github.com/MetaMask/json-rpc-engine/compare/v5.1.6...v5.1.8
[5.1.6]: https://github.com/MetaMask/json-rpc-engine/compare/v5.1.5...v5.1.6
[5.1.5]: https://github.com/MetaMask/json-rpc-engine/compare/v5.1.4...v5.1.5
[5.1.4]: https://github.com/MetaMask/json-rpc-engine/compare/v5.1.3...v5.1.4
[5.1.3]: https://github.com/MetaMask/json-rpc-engine/compare/v5.1.1...v5.1.3
[5.1.1]: https://github.com/MetaMask/json-rpc-engine/compare/v5.1.0...v5.1.1
[5.1.0]: https://github.com/MetaMask/json-rpc-engine/compare/v5.0.0...v5.1.0
[5.0.0]: https://github.com/MetaMask/json-rpc-engine/compare/v4.0.0...v5.0.0
[5.2.0]: https://github.com/MetaMask/json-rpc-engine/releases/tag/v5.2.0

View File

@@ -1,6 +1,6 @@
ISC License
Copyright (c) 2020 MetaMask
Copyright (c) 2022 MetaMask
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above

View File

@@ -2,62 +2,69 @@
"name": "json-rpc-engine",
"version": "6.1.0",
"description": "A tool for processing JSON-RPC messages.",
"homepage": "https://github.com/MetaMask/json-rpc-engine#readme",
"bugs": {
"url": "https://github.com/MetaMask/json-rpc-engine/issues"
},
"repository": {
"type": "git",
"url": "https://github.com/MetaMask/json-rpc-engine.git"
},
"license": "ISC",
"author": "kumavis",
"main": "dist/index.js",
"engines": {
"node": ">=12.0.0"
"directories": {
"test": "test"
},
"files": [
"dist"
],
"scripts": {
"build": "tsc --project .",
"lint:eslint": "eslint . --cache --ext js,ts",
"lint:misc": "prettier '**/*.json' '**/*.md' '**/*.yml' --single-quote --ignore-path .gitignore",
"build:clean": "rimraf dist && yarn build",
"lint": "yarn lint:eslint && yarn lint:misc --check",
"lint:eslint": "eslint . --cache --ext js,ts",
"lint:fix": "yarn lint:eslint --fix && yarn lint:misc --write",
"lint:misc": "prettier '**/*.json' '**/*.md' '!CHANGELOG.md' '**/*.yml' --ignore-path .gitignore --no-error-on-unmatched-pattern",
"prepublishOnly": "yarn build:clean && yarn lint && yarn test:coverage",
"setup": "yarn install && yarn allow-scripts",
"test": "mocha ./test",
"coverage": "nyc --check-coverage yarn test",
"prepublishOnly": "yarn && yarn lint && yarn build && yarn coverage"
"test:coverage": "nyc --check-coverage yarn test"
},
"dependencies": {
"@metamask/safe-event-emitter": "^2.0.0",
"eth-rpc-errors": "^4.0.2"
},
"devDependencies": {
"@metamask/eslint-config": "^6.0.0",
"@lavamoat/allow-scripts": "^1.0.5",
"@metamask/auto-changelog": "^2.3.0",
"@metamask/eslint-config": "^9.0.0",
"@metamask/eslint-config-mocha": "^6.0.0",
"@metamask/eslint-config-nodejs": "^6.0.0",
"@metamask/eslint-config-typescript": "^6.0.0",
"@types/node": "^14.14.7",
"@typescript-eslint/eslint-plugin": "^4.24.0",
"@typescript-eslint/parser": "^4.24.0",
"eslint": "^7.26.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-import": "^2.23.2",
"@metamask/eslint-config-nodejs": "^9.0.0",
"@metamask/eslint-config-typescript": "^9.0.1",
"@types/node": "^17.0.23",
"@typescript-eslint/eslint-plugin": "^4.21.0",
"@typescript-eslint/parser": "^4.21.0",
"eslint": "^7.23.0",
"eslint-config-prettier": "^8.1.0",
"eslint-plugin-import": "^2.22.1",
"eslint-plugin-jsdoc": "^36.1.0",
"eslint-plugin-mocha": "^8.1.0",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-prettier": "^3.4.0",
"eslint-plugin-prettier": "^3.3.1",
"mocha": "^7.1.1",
"nyc": "^15.1.0",
"prettier": "^2.3.0",
"prettier": "^2.2.1",
"prettier-plugin-packagejson": "^2.2.11",
"rimraf": "^3.0.2",
"sinon": "^9.0.2",
"typescript": "^4.2.4"
},
"repository": {
"type": "git",
"url": "git+https://github.com/MetaMask/json-rpc-engine.git"
"engines": {
"node": ">=14.0.0"
},
"bugs": {
"url": "https://github.com/MetaMask/json-rpc-engine/issues"
},
"homepage": "https://github.com/MetaMask/json-rpc-engine#readme",
"directories": {
"test": "test"
},
"contributors": [
"kumavis <aaron@kumavis.me>",
"Erik Marks <rekmarks@protonmail.com>"
]
"lavamoat": {
"allowScripts": {
"@lavamoat/preinstall-always-fail": false
}
}
}

View File

@@ -133,35 +133,33 @@ export class JsonRpcEngine extends SafeEventEmitter {
/**
* Handle a JSON-RPC request, and return a response.
*
* @param request - The request to handle.
* @returns A promise that resolves with the response, or rejects with an
* error.
* @param request - The JSON-RPC request to handle.
* @returns The JSON-RPC response.
*/
handle<T, U>(request: JsonRpcRequest<T>): Promise<JsonRpcResponse<U>>;
/**
* Handle an array of JSON-RPC requests, and return an array of responses.
*
* @param request - The requests to handle.
* @returns A promise that resolves with the array of responses, or rejects
* with an error.
* @param request - The JSON-RPC requests to handle.
* @returns An array of JSON-RPC responses.
*/
handle<T, U>(requests: JsonRpcRequest<T>[]): Promise<JsonRpcResponse<U>[]>;
handle(req: unknown, cb?: any) {
if (cb && typeof cb !== 'function') {
handle(req: unknown, callback?: any) {
if (callback && typeof callback !== 'function') {
throw new Error('"callback" must be a function if provided.');
}
if (Array.isArray(req)) {
if (cb) {
return this._handleBatch(req, cb);
if (callback) {
return this._handleBatch(req, callback);
}
return this._handleBatch(req);
}
if (cb) {
return this._handle(req as JsonRpcRequest<unknown>, cb);
if (callback) {
return this._handle(req as JsonRpcRequest<unknown>, callback);
}
return this._promiseHandle(req as JsonRpcRequest<unknown>);
}
@@ -209,12 +207,20 @@ export class JsonRpcEngine extends SafeEventEmitter {
*/
private _handleBatch(
reqs: JsonRpcRequest<unknown>[],
cb: (error: unknown, responses?: JsonRpcResponse<unknown>[]) => void,
callback: (error: unknown, responses?: JsonRpcResponse<unknown>[]) => void,
): Promise<void>;
/**
* Handles a batch of JSON-RPC requests, either in `async` or callback
* fashion.
*
* @param reqs - The request objects to process.
* @param callback - The completion callback.
* @returns The array of responses, or nothing if a callback was specified.
*/
private async _handleBatch(
reqs: JsonRpcRequest<unknown>[],
cb?: (error: unknown, responses?: JsonRpcResponse<unknown>[]) => void,
callback?: (error: unknown, responses?: JsonRpcResponse<unknown>[]) => void,
): Promise<JsonRpcResponse<unknown>[] | void> {
// The order here is important
try {
@@ -226,13 +232,13 @@ export class JsonRpcEngine extends SafeEventEmitter {
);
// 3. Return batch response
if (cb) {
return cb(null, responses);
if (callback) {
return callback(null, responses);
}
return responses;
} catch (error) {
if (cb) {
return cb(error);
if (callback) {
return callback(error);
}
throw error;
@@ -241,6 +247,9 @@ export class JsonRpcEngine extends SafeEventEmitter {
/**
* A promise-wrapped _handle.
*
* @param req - The JSON-RPC request.
* @returns The JSON-RPC response.
*/
private _promiseHandle(
req: JsonRpcRequest<unknown>,
@@ -259,10 +268,14 @@ export class JsonRpcEngine extends SafeEventEmitter {
* error and the response object to the given callback.
*
* Does not reject.
*
* @param callerReq - The request object from the caller.
* @param callback - The callback function.
* @returns Nothing.
*/
private async _handle(
callerReq: JsonRpcRequest<unknown>,
cb: (error: unknown, response: JsonRpcResponse<unknown>) => void,
callback: (error: unknown, response: JsonRpcResponse<unknown>) => void,
): Promise<void> {
if (
!callerReq ||
@@ -274,7 +287,7 @@ export class JsonRpcEngine extends SafeEventEmitter {
`Requests must be plain objects. Received: ${typeof callerReq}`,
{ request: callerReq },
);
return cb(error, { id: null, jsonrpc: '2.0', error });
return callback(error, { id: null, jsonrpc: '2.0', error });
}
if (typeof callerReq.method !== 'string') {
@@ -283,7 +296,7 @@ export class JsonRpcEngine extends SafeEventEmitter {
`Must specify a string method. Received: ${typeof callerReq.method}`,
{ request: callerReq },
);
return cb(error, { id: callerReq.id, jsonrpc: '2.0', error });
return callback(error, { id: callerReq.id, jsonrpc: '2.0', error });
}
const req: JsonRpcRequest<unknown> = { ...callerReq };
@@ -309,13 +322,16 @@ export class JsonRpcEngine extends SafeEventEmitter {
}
}
return cb(error, res as JsonRpcResponse<unknown>);
return callback(error, res as JsonRpcResponse<unknown>);
}
/**
* For the given request and response, runs all middleware and their return
* handlers, if any, and ensures that internal request processing semantics
* are satisfied.
*
* @param req - The request object.
* @param res - The response object.
*/
private async _processRequest(
req: JsonRpcRequest<unknown>,
@@ -342,6 +358,9 @@ export class JsonRpcEngine extends SafeEventEmitter {
/**
* Serially executes the given stack of middleware.
*
* @param req - The request object.
* @param res - The response object.
* @param middlewareStack - The stack of middleware functions to execute.
* @returns An array of any error encountered during middleware execution,
* a boolean indicating whether the request was completed, and an array of
* middleware-defined return handlers.
@@ -369,6 +388,7 @@ export class JsonRpcEngine extends SafeEventEmitter {
middleware,
returnHandlers,
);
if (isComplete) {
break;
}
@@ -377,8 +397,12 @@ export class JsonRpcEngine extends SafeEventEmitter {
}
/**
* Runs an individual middleware.
* Runs an individual middleware function.
*
* @param req - The request object.
* @param res - The response object.
* @param middleware - The middleware function to execute.
* @param returnHandlers - The return handlers array for the current request.
* @returns An array of any error encountered during middleware exection,
* and a boolean indicating whether the request should end.
*/
@@ -436,6 +460,8 @@ export class JsonRpcEngine extends SafeEventEmitter {
/**
* Serially executes array of return handlers. The request and response are
* assumed to be in their scope.
*
* @param handlers - The return handlers to execute.
*/
private static async _runReturnHandlers(
handlers: JsonRpcEngineReturnHandler[],
@@ -450,6 +476,11 @@ export class JsonRpcEngine extends SafeEventEmitter {
/**
* Throws an error if the response has neither a result nor an error, or if
* the "isComplete" flag is falsy.
*
* @param req - The request object.
* @param res - The response object.
* @param isComplete - Boolean from {@link JsonRpcEngine._runAllMiddleware}
* indicating whether a middleware ended the request.
*/
private static _checkForCompletion(
req: JsonRpcRequest<unknown>,
@@ -465,6 +496,7 @@ export class JsonRpcEngine extends SafeEventEmitter {
{ request: req },
);
}
if (!isComplete) {
throw new EthereumRpcError(
errorCodes.rpc.internal,
@@ -475,6 +507,12 @@ export class JsonRpcEngine extends SafeEventEmitter {
}
}
/**
* JSON-stringifies a request object.
*
* @param request - The request object to JSON-stringify.
* @returns The JSON-stringified request object.
*/
function jsonify(request: JsonRpcRequest<unknown>): string {
return JSON.stringify(request, null, 2);
}

View File

@@ -30,6 +30,10 @@ type ReturnHandlerCallback = (error: null | Error) => void;
*
* The return handler will always be called. Its resolution of the promise
* enables the control flow described above.
*
* @param asyncMiddleware - The asynchronous middleware function to wrap.
* @returns The wrapped asynchronous middleware function, ready to be consumed
* by JsonRpcEngine.
*/
export function createAsyncMiddleware<T, U>(
asyncMiddleware: AsyncJsonrpcMiddleware<T, U>,

View File

@@ -2,6 +2,15 @@ import { Json, JsonRpcMiddleware, JsonRpcSuccess } from './JsonRpcEngine';
type ScaffoldMiddlewareHandler<T, U> = JsonRpcMiddleware<T, U> | Json;
/**
* Creates a middleware function from an object of RPC method handler functions,
* keyed to particular method names. If a method corresponding to a key of this
* object is requested, this middleware will pass it to the corresponding
* handler and return the result.
*
* @param handlers - The RPC method handler functions.
* @returns The scaffold middleware function.
*/
export function createScaffoldMiddleware(handlers: {
[methodName: string]: ScaffoldMiddlewareHandler<unknown, unknown>;
}): JsonRpcMiddleware<unknown, unknown> {
@@ -11,6 +20,7 @@ export function createScaffoldMiddleware(handlers: {
if (handler === undefined) {
return next();
}
// if handler is fn, call as middleware
if (typeof handler === 'function') {
return handler(req, res, next, end);

View File

@@ -1,8 +1,15 @@
// uint32 (two's complement) max
// more conservative than Number.MAX_SAFE_INTEGER
const MAX = 4294967295;
const MAX = 4_294_967_295;
let idCounter = Math.floor(Math.random() * MAX);
/**
* Gets an ID that is guaranteed to be unique so long as no more than
* 4_294_967_295 (uint32 max) IDs are created, or the IDs are rapidly turned
* over.
*
* @returns The unique ID.
*/
export function getUniqueId(): number {
idCounter = (idCounter + 1) % MAX;
return idCounter;

View File

@@ -1,6 +1,15 @@
import { getUniqueId } from './getUniqueId';
import { JsonRpcMiddleware } from './JsonRpcEngine';
/**
* Returns a middleware function that overwrites the `id` property of each
* request with an ID that is guaranteed to be unique, and restores the original
* ID in a return handler.
*
* If used, should be the first middleware in the stack.
*
* @returns The ID remap middleware function.
*/
export function createIdRemapMiddleware(): JsonRpcMiddleware<unknown, unknown> {
return (req, res, next, _end) => {
const originalId = req.id;

View File

@@ -1,5 +1,11 @@
import { JsonRpcEngine, JsonRpcMiddleware } from './JsonRpcEngine';
/**
* Takes a stack of middleware and joins them into a single middleware function.
*
* @param middlewareStack - The middleware stack to merge.
* @returns The merged middleware function.
*/
export function mergeMiddleware(
middlewareStack: JsonRpcMiddleware<unknown, unknown>[],
) {

View File

@@ -6,7 +6,7 @@ import type {
} from './JsonRpcEngine';
/**
* **ATTN:** Assumes that only one of the `result` and `error` properties is
* ATTN: Assumes that only one of the `result` and `error` properties is
* present on the `response`, as guaranteed by e.g. `JsonRpcEngine.handle`.
*
* Type guard to narrow a JsonRpcResponse object to a success (or failure).
@@ -22,7 +22,7 @@ export function isJsonRpcSuccess<T>(
}
/**
* **ATTN:** Assumes that only one of the `result` and `error` properties is
* ATTN: Assumes that only one of the `result` and `error` properties is
* present on the `response`, as guaranteed by e.g. `JsonRpcEngine.handle`.
*
* Type guard to narrow a JsonRpcResponse object to a failure (or success).
@@ -79,6 +79,8 @@ export function getJsonRpcIdValidator(options?: JsonRpcValidatorOptions) {
};
/**
* Type guard for {@link JsonRpcId}.
*
* @param id - The JSON-RPC ID value to check.
* @returns Whether the given ID is valid per the options given to the
* factory.

View File

@@ -40,6 +40,7 @@ describe('createScaffoldMiddleware', function () {
'method3',
'should have expected error',
);
assert.equal(
response4.result,
'passthrough',

View File

@@ -28,7 +28,7 @@ describe('idRemapMiddleware', function () {
});
const payload = { id: 1, jsonrpc: '2.0', method: 'hello' };
const payloadCopy = Object.assign({}, payload);
const payloadCopy = { ...payload };
engine.handle(payload, function (err, res) {
assert.ifError(err, 'did not error');
@@ -47,6 +47,7 @@ describe('idRemapMiddleware', function () {
observedIds.after.req,
'ids are different',
);
assert.equal(
observedIds.before.req,
res.id,

View File

@@ -148,6 +148,7 @@ describe('mergeMiddleware', function () {
},
]),
);
engine.push((_req, res, _next, end) => {
res.result = true;
end();

View File

@@ -12,5 +12,6 @@
"strict": true,
"target": "ES2017"
},
"include": ["./src/*.ts"]
"exclude": ["./test"],
"include": ["./src/**/*.ts"]
}

1604
yarn.lock

File diff suppressed because it is too large Load Diff