From b47e5a42fcef9e781eb9d8e88671c5d7ce7405f6 Mon Sep 17 00:00:00 2001 From: Archit Date: Tue, 20 Aug 2024 12:48:18 +0530 Subject: [PATCH 1/3] Replace event emitter with typed emitter --- package-lock.json | 16 +++++++++++- package.json | 3 ++- src/core/PopupHandler.ts | 13 ++++++---- src/jrpc/jrpc.ts | 4 +-- src/jrpc/jrpcEngine.ts | 14 +++++------ src/jrpc/safeEventEmitter.ts | 48 ++++-------------------------------- 6 files changed, 39 insertions(+), 59 deletions(-) diff --git a/package-lock.json b/package-lock.json index b2790e54..753600f0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,7 +26,7 @@ "elliptic": "^6.5.6", "enc-utils": "^3.0.0", "end-of-stream": "^1.4.4", - "eventemitter3": "^5.0.1", + "events": "^3.3.0", "fast-safe-stringify": "^2.1.1", "json-stable-stringify": "^1.1.1", "loglevel": "^1.9.1", @@ -64,6 +64,7 @@ "tsconfig-paths-webpack-plugin": "^4.1.0", "tslib": "^2.6.3", "tsx": "^4.16.3", + "typed-emitter": "^2.1.0", "typescript": "^5.5.4" }, "engines": { @@ -6931,10 +6932,13 @@ }, "node_modules/eventemitter3": { "version": "5.0.1", + "dev": true, "license": "MIT" }, "node_modules/events": { "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", "license": "MIT", "engines": { "node": ">=0.8.x" @@ -13193,6 +13197,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/typed-emitter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/typed-emitter/-/typed-emitter-2.1.0.tgz", + "integrity": "sha512-g/KzbYKbH5C2vPkaXGu8DJlHrGKHLsM25Zg9WuC9pMGfuvT+X25tZQWo5fK1BjBm8+UrVE9LDCvaY0CQk+fXDA==", + "dev": true, + "license": "MIT", + "optionalDependencies": { + "rxjs": "*" + } + }, "node_modules/typedarray-to-buffer": { "version": "3.1.5", "license": "MIT", diff --git a/package.json b/package.json index 81abf18a..60df5865 100644 --- a/package.json +++ b/package.json @@ -91,6 +91,7 @@ "tsconfig-paths-webpack-plugin": "^4.1.0", "tslib": "^2.6.3", "tsx": "^4.16.3", + "typed-emitter": "^2.1.0", "typescript": "^5.5.4" }, "peerDependencies": { @@ -133,7 +134,7 @@ "elliptic": "^6.5.6", "enc-utils": "^3.0.0", "end-of-stream": "^1.4.4", - "eventemitter3": "^5.0.1", + "events": "^3.3.0", "fast-safe-stringify": "^2.1.1", "json-stable-stringify": "^1.1.1", "loglevel": "^1.9.1", diff --git a/src/core/PopupHandler.ts b/src/core/PopupHandler.ts index 4be8237a..126af37b 100644 --- a/src/core/PopupHandler.ts +++ b/src/core/PopupHandler.ts @@ -1,5 +1,6 @@ import { SecurePubSub } from "@toruslabs/secure-pub-sub"; -import { EventEmitter } from "eventemitter3"; +import EventEmitter from "events"; +import type { default as TypedEmitter } from "typed-emitter"; import { LoginError } from "./errors"; import { getPopupFeatures } from "./utils"; @@ -11,11 +12,11 @@ export interface PopupResponse { error?: string; } -export interface PopupHandlerEvents { - close: []; -} +export type PopupHandlerEvents = { + close: () => void; +}; -class PopupHandler extends EventEmitter { +class PopupHandler extends (EventEmitter as new () => TypedEmitter) { url: string; target: string; @@ -31,6 +32,8 @@ class PopupHandler extends EventEmitter { timeout: number; constructor({ url, target, features, timeout = 30000 }: { url: string; target?: string; features?: string; timeout?: number }) { + // Disabling the rule here, as it is a false positive. + // eslint-disable-next-line constructor-super super(); this.url = url; this.target = target || "_blank"; diff --git a/src/jrpc/jrpc.ts b/src/jrpc/jrpc.ts index e4d957f9..08b95096 100644 --- a/src/jrpc/jrpc.ts +++ b/src/jrpc/jrpc.ts @@ -41,9 +41,9 @@ export function createErrorMiddleware(log: ConsoleLike): JRPCMiddleware) => boolean; -} +}; export function createStreamMiddleware(): { events: SafeEventEmitter; middleware: JRPCMiddleware; stream: Duplex } { const idMap: IdMap = {}; diff --git a/src/jrpc/jrpcEngine.ts b/src/jrpc/jrpcEngine.ts index 892f8629..90fcaf6d 100644 --- a/src/jrpc/jrpcEngine.ts +++ b/src/jrpc/jrpcEngine.ts @@ -17,9 +17,9 @@ import { import { SafeEventEmitter } from "./safeEventEmitter"; import { SerializableError } from "./serializableError"; -export interface JrpcEngineEvents { - notification: (arg1: string) => void; -} +export type JrpcEngineEvents = { + notification: (...args: unknown[]) => void; +}; /** * A JSON-RPC request and response processor. @@ -411,9 +411,9 @@ export function createEngineStream(opts: EngineStreamOptions): Duplex { return stream; } -export interface ProviderEvents { - data: (error: unknown, message: string) => void; -} +export type ProviderEvents = { + data: (error: unknown, message: unknown) => void; +}; export interface SafeEventEmitterProvider extends SafeEventEmitter { sendAsync: (req: JRPCRequest) => Promise; @@ -452,7 +452,7 @@ export function providerFromEngine(engine: JRPCEngine): SafeEventEmitterProvider }; // forward notifications if (engine.on) { - engine.on("notification", (message: string) => { + engine.on("notification", (message) => { provider.emit("data", null, message); }); } diff --git a/src/jrpc/safeEventEmitter.ts b/src/jrpc/safeEventEmitter.ts index fca5dea2..1d95b97f 100644 --- a/src/jrpc/safeEventEmitter.ts +++ b/src/jrpc/safeEventEmitter.ts @@ -1,33 +1,9 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { EventEmitter } from "eventemitter3"; +import EventEmitter from "events"; +import type { default as TypedEmitter, EventMap } from "typed-emitter"; -type Handler = (...args: any[]) => void; -interface EventMap { - [k: string]: Handler | Handler[] | undefined; -} - -function safeApply(handler: (this: T, ...handlerArgs: A) => void, context: T, args: A): void { - try { - Reflect.apply(handler, context, args); - } catch (err) { - // Throw error after timeout so as not to interrupt the stack - setTimeout(() => { - throw err; - }); - } -} - -function arrayClone(arr: T[]): T[] { - const n = arr.length; - const copy = new Array(n); - for (let i = 0; i < n; i += 1) { - copy[i] = arr[i]; - } - return copy; -} - -export class SafeEventEmitter extends EventEmitter { - emit(type: EventEmitter.EventNames, ...args: any[]): boolean { +export class SafeEventEmitter extends (EventEmitter as { new (): TypedEmitter }) { + emit(type: E, ...args: Parameters): boolean { let doError = type === "error"; const events: EventMap = (this as any)._events; @@ -54,21 +30,7 @@ export class SafeEventEmitter extends Ev throw err; // Unhandled 'error' event } - const handler = events[type as string]; - - if (handler === undefined) { - return false; - } - - if (typeof handler === "function") { - safeApply(handler, this, args); - } else { - const len = handler.length; - const listeners = arrayClone(handler); - for (let i = 0; i < len; i += 1) { - safeApply(listeners[i], this, args); - } - } + super.emit(type, ...args); return true; } From f717504a89aee1841f709a935e7825aa597db3ae Mon Sep 17 00:00:00 2001 From: Archit Date: Wed, 21 Aug 2024 09:17:24 +0530 Subject: [PATCH 2/3] change imports --- src/core/PopupHandler.ts | 2 +- src/jrpc/safeEventEmitter.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/PopupHandler.ts b/src/core/PopupHandler.ts index 126af37b..810ffda5 100644 --- a/src/core/PopupHandler.ts +++ b/src/core/PopupHandler.ts @@ -1,5 +1,5 @@ import { SecurePubSub } from "@toruslabs/secure-pub-sub"; -import EventEmitter from "events"; +import { EventEmitter } from "events"; import type { default as TypedEmitter } from "typed-emitter"; import { LoginError } from "./errors"; diff --git a/src/jrpc/safeEventEmitter.ts b/src/jrpc/safeEventEmitter.ts index 1d95b97f..86250546 100644 --- a/src/jrpc/safeEventEmitter.ts +++ b/src/jrpc/safeEventEmitter.ts @@ -1,5 +1,5 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import EventEmitter from "events"; +import { EventEmitter } from "events"; import type { default as TypedEmitter, EventMap } from "typed-emitter"; export class SafeEventEmitter extends (EventEmitter as { new (): TypedEmitter }) { From 61b5e9353df4fbf9c125a3b25a00d17f01a0d7e9 Mon Sep 17 00:00:00 2001 From: Archit Date: Wed, 21 Aug 2024 09:36:53 +0530 Subject: [PATCH 3/3] revert safeApply --- src/jrpc/safeEventEmitter.ts | 36 +++++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/src/jrpc/safeEventEmitter.ts b/src/jrpc/safeEventEmitter.ts index 86250546..03baaa49 100644 --- a/src/jrpc/safeEventEmitter.ts +++ b/src/jrpc/safeEventEmitter.ts @@ -2,6 +2,26 @@ import { EventEmitter } from "events"; import type { default as TypedEmitter, EventMap } from "typed-emitter"; +function safeApply(handler: (this: T, ...handlerArgs: A) => void, context: T, args: A): void { + try { + Reflect.apply(handler, context, args); + } catch (err) { + // Throw error after timeout so as not to interrupt the stack + setTimeout(() => { + throw err; + }); + } +} + +function arrayClone(arr: T[]): T[] { + const n = arr.length; + const copy = new Array(n); + for (let i = 0; i < n; i += 1) { + copy[i] = arr[i]; + } + return copy; +} + export class SafeEventEmitter extends (EventEmitter as { new (): TypedEmitter }) { emit(type: E, ...args: Parameters): boolean { let doError = type === "error"; @@ -30,7 +50,21 @@ export class SafeEventEmitter extends (EventEmitt throw err; // Unhandled 'error' event } - super.emit(type, ...args); + const handler = events[type as string]; + + if (handler === undefined) { + return false; + } + + if (typeof handler === "function") { + safeApply(handler, this, args); + } else { + const len = (handler as any[]).length; + const listeners = arrayClone(handler as any[]); + for (let i = 0; i < len; i += 1) { + safeApply(listeners[i], this, args); + } + } return true; }