Skip to content
This repository was archived by the owner on Nov 9, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ module.exports = {
{
files: ['*.test.ts', '*.test.js'],
extends: ['@metamask/eslint-config-jest'],
rules: {
'import/no-nodejs-modules': 'off',
},
},
],

Expand Down
6 changes: 3 additions & 3 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@ module.exports = {
// An object that configures minimum threshold enforcement for coverage results
coverageThreshold: {
global: {
branches: 90.9,
branches: 91.66,
functions: 100,
lines: 98.56,
statements: 98.56,
lines: 99.51,
statements: 99.51,
},
},

Expand Down
29 changes: 15 additions & 14 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,27 +34,28 @@
"@lavamoat/allow-scripts": "^2.3.1",
"@lavamoat/preinstall-always-fail": "^1.0.0",
"@metamask/auto-changelog": "^3.0.0",
"@metamask/eslint-config": "^9.0.0",
"@metamask/eslint-config-jest": "^9.0.0",
"@metamask/eslint-config-nodejs": "^9.0.0",
"@metamask/eslint-config-typescript": "^9.0.1",
"@metamask/eslint-config": "^12.0.0",
"@metamask/eslint-config-jest": "^12.0.0",
"@metamask/eslint-config-nodejs": "^12.0.0",
"@metamask/eslint-config-typescript": "^12.0.0",
"@types/jest": "^26.0.13",
"@types/node": "^17.0.23",
"@types/readable-stream": "^2.3.9",
"@typescript-eslint/eslint-plugin": "^4.21.0",
"@typescript-eslint/parser": "^4.21.0",
"@typescript-eslint/eslint-plugin": "^5.30.7",
"@typescript-eslint/parser": "^5.30.7",
"depcheck": "^1.4.5",
"eslint": "^7.23.0",
"eslint-config-prettier": "^8.1.0",
"eslint-plugin-import": "^2.22.1",
"eslint-plugin-jest": "^24.3.4",
"eslint-plugin-jsdoc": "^36.1.0",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-prettier": "^3.3.1",
"eslint": "^8.44.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-jest": "^27.1.5",
"eslint-plugin-jsdoc": "^39.3.3",
"eslint-plugin-n": "^15.7.0",
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-promise": "^6.1.1",
"extension-port-stream": "^2.0.1",
"jest": "^27.5.1",
"jest-it-up": "^2.0.2",
"prettier": "^2.2.1",
"prettier": "^2.7.1",
"prettier-plugin-packagejson": "^2.2.17",
"rimraf": "^3.0.2",
"ts-jest": "^27.1.4",
Expand Down
18 changes: 9 additions & 9 deletions src/createEngineStream.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import type { JsonRpcEngine } from '@metamask/json-rpc-engine';
import type { JsonRpcRequest } from '@metamask/utils';
import { Duplex } from 'readable-stream';
import { JsonRpcEngine } from '@metamask/json-rpc-engine';
import { JsonRpcRequest, JsonRpcParams } from '@metamask/utils';

interface EngineStreamOptions {
type EngineStreamOptions = {
engine: JsonRpcEngine;
}
};

/**
* Takes a JsonRpcEngine and returns a Duplex stream wrapping it.
Expand All @@ -14,7 +14,7 @@ interface EngineStreamOptions {
* @returns The stream wrapping the engine.
*/
export default function createEngineStream(opts: EngineStreamOptions): Duplex {
if (!opts || !opts.engine) {
if (!opts?.engine) {
throw new Error('Missing engine parameter!');
}

Expand All @@ -33,16 +33,16 @@ export default function createEngineStream(opts: EngineStreamOptions): Duplex {
*
* @param req - The JSON-rpc request.
* @param _encoding - The stream encoding, not used.
* @param cb - The stream write callback.
* @param streamWriteCallback - The stream write callback.
*/
function write(
req: JsonRpcRequest<JsonRpcParams>,
req: JsonRpcRequest,
_encoding: unknown,
cb: (error?: Error | null) => void,
streamWriteCallback: (error?: Error | null) => void,
) {
engine.handle(req, (_err, res) => {
stream.push(res);
});
cb();
streamWriteCallback();
}
}
44 changes: 21 additions & 23 deletions src/createStreamMiddleware.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,32 @@
import SafeEventEmitter from '@metamask/safe-event-emitter';
import { Duplex } from 'readable-stream';
import type {
JsonRpcEngineNextCallback,
JsonRpcEngineEndCallback,
JsonRpcMiddleware,
} from '@metamask/json-rpc-engine';

import SafeEventEmitter from '@metamask/safe-event-emitter';
import type {
JsonRpcNotification,
JsonRpcParams,
JsonRpcRequest,
PendingJsonRpcResponse,
} from '@metamask/utils';
import { Duplex } from 'readable-stream';

interface IdMapValue {
req: JsonRpcRequest<JsonRpcParams>;
type IdMapValue = {
req: JsonRpcRequest;
res: PendingJsonRpcResponse<JsonRpcParams>;
next: JsonRpcEngineNextCallback;
end: JsonRpcEngineEndCallback;
retryCount?: number;
}
};

interface IdMap {
type IdMap = {
[requestId: string]: IdMapValue;
}
};

interface Options {
type Options = {
retryOnMessage?: string;
}
};

/**
* Creates a JsonRpcEngine middleware with an associated Duplex stream and
Expand Down Expand Up @@ -67,7 +66,7 @@ export default function createStreamMiddleware(options: Options = {}) {
*
* @param req - The JSON-RPC request object.
*/
function sendToStream(req: JsonRpcRequest<JsonRpcParams>) {
function sendToStream(req: JsonRpcRequest) {
// TODO: limiting retries could be implemented here
stream.push(req);
}
Expand All @@ -77,28 +76,26 @@ export default function createStreamMiddleware(options: Options = {}) {
*
* @param res - The JSON-RPC response object.
* @param _encoding - The stream encoding, not used.
* @param cb - The stream write callback.
* @param streamWriteCallback - The stream write callback.
*/
function processMessage(
res: PendingJsonRpcResponse<JsonRpcParams>,
_encoding: unknown,
cb: (error?: Error | null) => void,
streamWriteCallback: (error?: Error | null) => void,
) {
let err: Error | null = null;
let errorObj: Error | null = null;
try {
const isNotification = !res.id;
if (isNotification) {
processNotification(
res as unknown as JsonRpcNotification<JsonRpcParams>,
);
processNotification(res as unknown as JsonRpcNotification);
} else {
processResponse(res);
}
} catch (_err) {
err = _err as Error;
errorObj = _err as Error;
}
// continue processing stream
cb(err);
streamWriteCallback(errorObj);
}

/**
Expand All @@ -107,13 +104,14 @@ export default function createStreamMiddleware(options: Options = {}) {
* @param res - The response to process.
*/
function processResponse(res: PendingJsonRpcResponse<JsonRpcParams>) {
const context = idMap[res.id as unknown as string];
const responseId = res.id as unknown as string;
const context = idMap[responseId];
if (!context) {
console.warn(`StreamMiddleware - Unknown response id "${res.id}"`);
console.warn(`StreamMiddleware - Unknown response id "${responseId}"`);
return;
}

delete idMap[res.id as unknown as string];
delete idMap[responseId];
// copy whole res onto original res
Object.assign(context.res, res);
// run callback on empty stack,
Expand All @@ -126,7 +124,7 @@ export default function createStreamMiddleware(options: Options = {}) {
*
* @param notif - The notification to process.
*/
function processNotification(notif: JsonRpcNotification<JsonRpcParams>) {
function processNotification(notif: JsonRpcNotification) {
if (options?.retryOnMessage && notif.method === options.retryOnMessage) {
retryStuckRequests();
}
Expand Down
35 changes: 24 additions & 11 deletions src/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { Duplex } from 'stream';
import { JsonRpcEngine } from '@metamask/json-rpc-engine';
import PortStream from 'extension-port-stream';
import type { Duplex } from 'stream';
import type { Runtime } from 'webextension-polyfill-ts';

import { createStreamMiddleware, createEngineStream } from '.';

const artificialDelay = (t = 0) =>
new Promise((resolve) => setTimeout(resolve, t));
const artificialDelay = async (time = 0) =>
new Promise((resolve) => setTimeout(resolve, time));
// eslint-disable-next-line @typescript-eslint/no-empty-function
const noop = function (_a: any) {};

Expand Down Expand Up @@ -35,10 +36,10 @@ describe('createStreamMiddleware', () => {
() => {
reject(new Error('should not call next'));
},
(err) => {
(errorObj) => {
try {
// eslint-disable-next-line jest/no-restricted-matchers
expect(err).toBeFalsy();
expect(errorObj).toBeFalsy();
expect(initRes).toStrictEqual(res);
} catch (error) {
return reject(error);
Expand Down Expand Up @@ -73,13 +74,19 @@ describe('createEngineStream', () => {
return resolve();
});

stream.on('error', (err) => {
reject(err);
stream.on('error', (errorObj) => {
reject(errorObj);
});

stream.write(req);
});
});

it('throw error when engine stream options not available', async () => {
expect(() => {
createEngineStream({} as any);
}).toThrow('Missing engine parameter!');
});
});

describe('middleware and engine to stream', () => {
Expand Down Expand Up @@ -128,15 +135,15 @@ describe('retry logic in middleware connected to a port', () => {
messages = [];
const extensionPort = {
onMessage: {
addListener: (cb: any) => {
messageConsumer = cb;
addListener: (messageCallback: any) => {
messageConsumer = messageCallback;
},
},
onDisconnect: {
addListener: noop,
},
postMessage(m: any) {
messages.push(m);
postMessage(message: any) {
messages.push(message);
},
};

Expand All @@ -159,6 +166,8 @@ describe('retry logic in middleware connected to a port', () => {

// Initially sent once
const responsePromise1 = engineA?.handle(req1);
// intentionally not awaited
// eslint-disable-next-line @typescript-eslint/no-floating-promises
engineA?.handle(req2);
await artificialDelay();

Expand Down Expand Up @@ -193,6 +202,8 @@ describe('retry logic in middleware connected to a port', () => {
const req = { id: 1, jsonrpc, method: 'test' };

// Initially sent once, message count at 1
// intentionally not awaited
// eslint-disable-next-line @typescript-eslint/no-floating-promises
engineA?.handle(req);
await artificialDelay();
expect(messages).toHaveLength(1);
Expand Down Expand Up @@ -242,6 +253,8 @@ describe('retry logic in middleware connected to a port', () => {
const req = { id: undefined, jsonrpc, method: 'test' };

// Initially sent once, message count at 1
// intentionally not awaited
// eslint-disable-next-line @typescript-eslint/no-floating-promises
engineA?.handle(req);
await artificialDelay();
expect(messages).toHaveLength(1);
Expand Down
Loading