Skip to content

Commit

Permalink
add eventemitter3 (#6823)
Browse files Browse the repository at this point in the history
* update utils

* replace web3 eventemitter for eventemitter3

* update tests

* update snapshot

* remove connection error test

---------

Co-authored-by: Junaid <86780488+jdevcs@users.noreply.github.com>
  • Loading branch information
luu-alex and jdevcs committed Mar 7, 2024
1 parent 040d3a3 commit edf3164
Show file tree
Hide file tree
Showing 15 changed files with 90 additions and 152 deletions.
Expand Up @@ -42,12 +42,27 @@ exports[`Web3Context getContextObject should return correct context object 1`] =
"requestManager": Web3RequestManager {
"_emitter": {
"_events": {
"BEFORE_PROVIDER_CHANGE": [Function],
"PROVIDER_CHANGED": [Function],
"BEFORE_PROVIDER_CHANGE": EE {
"context": EventEmitter {
"_events": [Circular],
"_eventsCount": 2,
"maxListeners": 9007199254740991,
},
"fn": [Function],
"once": false,
},
"PROVIDER_CHANGED": EE {
"context": EventEmitter {
"_events": [Circular],
"_eventsCount": 2,
"maxListeners": 9007199254740991,
},
"fn": [Function],
"once": false,
},
},
"_eventsCount": 2,
"_maxListeners": undefined,
Symbol(kCapture): false,
"maxListeners": 9007199254740991,
Symbol(shapeMode): false,
},
"_provider": HttpProvider {
Expand All @@ -62,12 +77,27 @@ exports[`Web3Context getContextObject should return correct context object 1`] =
"requestManager": Web3RequestManager {
"_emitter": {
"_events": {
"BEFORE_PROVIDER_CHANGE": [Function],
"PROVIDER_CHANGED": [Function],
"BEFORE_PROVIDER_CHANGE": EE {
"context": EventEmitter {
"_events": [Circular],
"_eventsCount": 2,
"maxListeners": 9007199254740991,
},
"fn": [Function],
"once": false,
},
"PROVIDER_CHANGED": EE {
"context": EventEmitter {
"_events": [Circular],
"_eventsCount": 2,
"maxListeners": 9007199254740991,
},
"fn": [Function],
"once": false,
},
},
"_eventsCount": 2,
"_maxListeners": undefined,
Symbol(kCapture): false,
"maxListeners": 9007199254740991,
Symbol(shapeMode): false,
},
"_provider": HttpProvider {
Expand Down
1 change: 1 addition & 0 deletions packages/web3-eth-contract/package.json
Expand Up @@ -62,6 +62,7 @@
"eslint-config-base-web3": "0.1.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-import": "^2.26.0",
"flatted": "^3.3.1",
"jest": "^29.7.0",
"jest-extended": "^3.0.1",
"prettier": "^2.7.1",
Expand Down
5 changes: 3 additions & 2 deletions packages/web3-eth-contract/test/unit/contract.test.ts
Expand Up @@ -21,6 +21,7 @@ import { Web3ContractError } from 'web3-errors';
import { Web3Context , Web3ConfigEvent } from 'web3-core';
import { Web3ValidatorError } from 'web3-validator';
import { AbiItem } from 'web3-utils';
import { stringify } from 'flatted';
import {Abi} from '../fixtures/AbiItem'
import { Contract } from '../../src';
import { sampleStorageContractABI } from '../fixtures/storage';
Expand Down Expand Up @@ -755,7 +756,7 @@ describe('Contract', () => {

const clonnedContract = contract.clone();

expect(JSON.stringify(contract)).toStrictEqual(JSON.stringify(clonnedContract));
expect(stringify(contract)).toStrictEqual(stringify(clonnedContract));

contract.options.jsonInterface = GreeterAbi;
});
Expand All @@ -764,7 +765,7 @@ describe('Contract', () => {
const contract = new Contract(sampleStorageContractABI);

const clonnedContract = contract.clone();
expect(JSON.stringify(contract)).toStrictEqual(JSON.stringify(clonnedContract));
expect(stringify(contract)).toStrictEqual(stringify(clonnedContract));
});

it('should be able to update the jsonInterface', () => {
Expand Down
17 changes: 0 additions & 17 deletions packages/web3-providers-ipc/test/unit/check_implementation.test.ts
Expand Up @@ -97,23 +97,6 @@ describe('IPCProvider', () => {
expect(end).toHaveBeenCalled();
});

it('connection error', async () => {
const ipc = new IpcProvider(socketPath);
// @ts-expect-error mock method
ipc._socketConnection.connecting = false;
// @ts-expect-error mock method
ipc._connectionStatus = 'disconnected';
ipc.connect = jest.fn();

await expect(
ipc.request({
jsonrpc: '2.0',
id: 42,
method: 'eth_getBalance',
params: ['0x407d73d8a49eeb85d32cf465507dd71d507100c1', 'latest'],
}),
).rejects.toThrow('Connection not open');
});
it('_onCloseHandler autoReconnect=false', () => {
const ipc = new IpcProvider(socketPath, {}, { autoReconnect: false });
const _clearQueues = jest.fn();
Expand Down
Expand Up @@ -137,6 +137,21 @@ describeIf(isWs)('WebSocketProvider - implemented methods', () => {
webSocketProvider.disconnect(code);
await closePromise;
});

it('should error when no connection is established', async () => {
const wsProvider = new WebSocketProvider("ws://localhost:999",{}, { autoReconnect: false });
let errored = false;
try{
await wsProvider.request(jsonRpcPayload);
// should not be able to reach here

}catch(e){
// eslint-disable-next-line jest/no-conditional-expect
expect((e as any).message).toBe('Connection not open')
errored = true;
}
expect(errored).toBe(true);
});
});

describe('disconnect and reset test', () => {
Expand Down
16 changes: 7 additions & 9 deletions packages/web3-providers-ws/test/unit/__mocks__/isomorphic-ws.ts
Expand Up @@ -23,10 +23,9 @@ export default class WebSocket extends EventEmitter {
public CONNECTING = 0;
public OPEN = 1;

public constructor(...args: any[]) {
public constructor() {
super()
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
super(...args);

// Connected state
this.readyState = 1;
}
Expand All @@ -43,13 +42,12 @@ export default class WebSocket extends EventEmitter {
}, 100);
}

public addEventListener(event: any, cb: () => void) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
this.prependListener(event, cb);
public addEventListener(_event: any, _cb: () => void) {
this.on(_event, _cb)

}

public removeEventListener(event: any, cb: () => void) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
this.removeListener(event, cb);
public removeEventListener(_event: any, _cb: () => void) {
this.off(_event, _cb)
}
}
20 changes: 3 additions & 17 deletions packages/web3-providers-ws/test/unit/check_implementation.test.ts
Expand Up @@ -85,28 +85,14 @@ describe('WebSocketProvider', () => {
ws.disconnect(code, data);
expect(close).toHaveBeenCalledWith(code, data);
});
it('connection error', async () => {
const ws = new WebSocketProvider('ws://localhost:8545');
// @ts-expect-error mock method
ws._socketConnection.readyState = 2;
ws.connect = jest.fn();

await expect(
ws.request({
jsonrpc: '2.0',
id: 42,
method: 'eth_getBalance',
params: ['0x407d73d8a49eeb85d32cf465507dd71d507100c1', 'latest'],
}),
).rejects.toThrow('Connection not open');
});
it('onCloseEvent autoReconnect=false', () => {
const ws = new WebSocketProvider('ws://localhost:8545', {}, { autoReconnect: false });
const _clearQueues = jest.fn();
const _removeSocketListeners = jest.fn();
const _onDisconnect = jest.fn();
// @ts-expect-error mock method
ws._socketConnection.close = jest.fn();

// @ts-expect-error mock method
ws._clearQueues = _clearQueues;
// @ts-expect-error mock method
Expand Down Expand Up @@ -140,7 +126,7 @@ describe('WebSocketProvider', () => {
const ws = new WebSocketProvider('ws://localhost:8545');
// @ts-expect-error mock method
ws._socketConnection.listeners = () => {
throw new Error('error');
throw new Error('error');
};
const addEventListener = jest.fn();
// @ts-expect-error mock method
Expand All @@ -161,6 +147,6 @@ describe('WebSocketProvider', () => {
expect(removeEventListener).toHaveBeenCalledWith('open', ws._onOpenHandler);
// @ts-expect-error mock method
expect(removeEventListener).toHaveBeenCalledWith('close', ws._onCloseHandler);
});
});
});
});
6 changes: 5 additions & 1 deletion packages/web3-utils/CHANGELOG.md
Expand Up @@ -193,4 +193,8 @@ Documentation:

- Adds missing exported type `AbiItem` from 1.x to v4 for compatabiltiy (#6678)

## [Unreleased]
## [Unreleased]

### Fixed

- replaced our eventEmitter to EventEmitter3 to support react native builds (#6253)
1 change: 1 addition & 0 deletions packages/web3-utils/package.json
Expand Up @@ -64,6 +64,7 @@
},
"dependencies": {
"ethereum-cryptography": "^2.0.0",
"eventemitter3": "^5.0.1",
"web3-errors": "^1.1.4",
"web3-types": "^1.4.0",
"web3-validator": "^2.0.4"
Expand Down
2 changes: 1 addition & 1 deletion packages/web3-utils/src/chunk_response_parser.ts
Expand Up @@ -16,7 +16,7 @@ along with web3.js. If not, see <http://www.gnu.org/licenses/>.
*/
import { JsonRpcResponse } from 'web3-types';
import { InvalidResponseError } from 'web3-errors';
import { EventEmitter } from 'events';
import { EventEmitter } from 'eventemitter3';
import { Timeout } from './promise_helpers.js';

export class ChunkResponseParser {
Expand Down
97 changes: 4 additions & 93 deletions packages/web3-utils/src/event_emitter.ts
Expand Up @@ -16,75 +16,17 @@ along with web3.js. If not, see <http://www.gnu.org/licenses/>.
*/
/* eslint-disable max-classes-per-file */

import { EventEmitter as EventEmitterAtNode } from 'events';
import EventEmitter3 from 'eventemitter3';

// eslint-disable-next-line @typescript-eslint/no-explicit-any
type Callback = (params: any) => void | Promise<void>;

type EventTargetCallback = (params: CustomEvent) => void;

const wrapFunction =
(fn: Callback): EventTargetCallback =>
(params: CustomEvent) =>
fn(params.detail);

/**
* This class copy the behavior of Node.js EventEmitter class.
* It is used to provide the same interface for the browser environment.
*/
class EventEmitterAtBrowser extends EventTarget {
private _listeners: Record<string, [key: Callback, value: EventTargetCallback][]> = {};
export class EventEmitter extends EventEmitter3 {
// must be defined for backwards compatibility
private maxListeners = Number.MAX_SAFE_INTEGER;

public on(eventName: string, fn: Callback) {
this.addEventListener(eventName, fn);
return this;
}

public once(eventName: string, fn: Callback) {
const onceCallback = async (params: Callback) => {
this.off(eventName, onceCallback);
await fn(params);
};
return this.on(eventName, onceCallback);
}

public off(eventName: string, fn: Callback) {
this.removeEventListener(eventName, fn);
return this;
}

public emit(eventName: string, params: unknown) {
const event = new CustomEvent(eventName, { detail: params });
return super.dispatchEvent(event);
}

public listenerCount(eventName: string): number {
const eventListeners = this._listeners[eventName];
return eventListeners ? eventListeners.length : 0;
}

public listeners(eventName: string): Callback[] {
return this._listeners[eventName].map(value => value[0]) || [];
}

public eventNames(): string[] {
return Object.keys(this._listeners);
}

public removeAllListeners() {
Object.keys(this._listeners).forEach(event => {
this._listeners[event].forEach(
(listener: [key: Callback, value: EventTargetCallback]) => {
super.removeEventListener(event, listener[1] as EventListener);
},
);
});

this._listeners = {};
return this;
}

public setMaxListeners(maxListeners: number) {
this.maxListeners = maxListeners;
return this;
Expand All @@ -94,35 +36,4 @@ class EventEmitterAtBrowser extends EventTarget {
return this.maxListeners;
}

public addEventListener(eventName: string, fn: Callback) {
const wrappedFn = wrapFunction(fn);
super.addEventListener(eventName, wrappedFn as EventListener);
if (!this._listeners[eventName]) {
this._listeners[eventName] = [];
}
this._listeners[eventName].push([fn, wrappedFn]);
}

public removeEventListener(eventName: string, fn: Callback) {
const eventListeners = this._listeners[eventName];
if (eventListeners) {
const index = eventListeners.findIndex(item => item[0] === fn);
if (index !== -1) {
super.removeEventListener(eventName, eventListeners[index][1] as EventListener);
eventListeners.splice(index, 1);
}
}
}
}

// eslint-disable-next-line import/no-mutable-exports
let EventEmitterType: typeof EventEmitterAtNode;
// Check if the code is running in a Node.js environment
if (typeof window === 'undefined') {
EventEmitterType = EventEmitterAtNode;
} else {
// Fallback for the browser environment
EventEmitterType = EventEmitterAtBrowser as unknown as typeof EventEmitterAtNode;
}

export class EventEmitter extends EventEmitterType {}
}
2 changes: 0 additions & 2 deletions packages/web3-utils/src/socket_provider.ts
Expand Up @@ -118,7 +118,6 @@ export abstract class SocketProvider<

this._socketPath = socketPath;
this._socketOptions = socketOptions;

this._reconnectOptions = {
...DEFAULT_RECONNECTION_OPTIONS,
...(reconnectOptions ?? {}),
Expand Down Expand Up @@ -465,7 +464,6 @@ export abstract class SocketProvider<
if (this._sentRequestsQueue.has(requestId)) {
throw new RequestAlreadySentError(requestId);
}

const deferredPromise = new Web3DeferredPromise<JsonRpcResponseWithResult<ResultType>>();
deferredPromise.catch(error => {
this._eventEmitter.emit('error', error);
Expand Down
2 changes: 1 addition & 1 deletion packages/web3-utils/src/web3_eip1193_provider.ts
Expand Up @@ -23,7 +23,7 @@ import {
Web3APISpec,
Web3BaseProvider,
} from 'web3-types';
import { EventEmitter } from 'events';
import { EventEmitter } from 'eventemitter3';
import { EIP1193ProviderRpcError } from 'web3-errors';
import { toPayload } from './json_rpc.js';

Expand Down

1 comment on commit edf3164

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Benchmark

Benchmark suite Current: edf3164 Previous: 6c075db Ratio
processingTx 9587 ops/sec (±3.73%) 9301 ops/sec (±4.81%) 0.97
processingContractDeploy 41652 ops/sec (±7.59%) 39129 ops/sec (±7.62%) 0.94
processingContractMethodSend 21041 ops/sec (±4.36%) 19443 ops/sec (±5.19%) 0.92
processingContractMethodCall 40567 ops/sec (±3.99%) 38971 ops/sec (±6.34%) 0.96
abiEncode 46438 ops/sec (±7.50%) 44252 ops/sec (±6.92%) 0.95
abiDecode 33324 ops/sec (±6.50%) 30419 ops/sec (±8.89%) 0.91
sign 1683 ops/sec (±3.23%) 1656 ops/sec (±4.08%) 0.98
verify 379 ops/sec (±0.43%) 373 ops/sec (±0.78%) 0.98

This comment was automatically generated by workflow using github-action-benchmark.

Please sign in to comment.