diff --git a/packages/dht/src/dht/DhtNode.ts b/packages/dht/src/dht/DhtNode.ts index a9e16e2878..ecc42ea7a9 100644 --- a/packages/dht/src/dht/DhtNode.ts +++ b/packages/dht/src/dht/DhtNode.ts @@ -54,7 +54,7 @@ import { StoreManager } from './store/StoreManager' import { StoreRpcRemote } from './store/StoreRpcRemote' import { getLocalRegionByCoordinates, getLocalRegionWithCache } from '@streamr/cdn-location' -export interface DhtNodeEvents { +export interface DhtNodeEvents extends TransportEvents { nearbyContactAdded: (peerDescriptor: PeerDescriptor) => void nearbyContactRemoved: (peerDescriptor: PeerDescriptor) => void randomContactAdded: (peerDescriptor: PeerDescriptor) => void @@ -133,9 +133,7 @@ const PERIODICAL_PING_INTERVAL = 60 * 1000 // TODO move this to trackerless-network package and change serviceId to be a required paramater export const CONTROL_LAYER_NODE_SERVICE_ID = 'layer0' -export type Events = TransportEvents & DhtNodeEvents - -export class DhtNode extends EventEmitter implements ITransport { +export class DhtNode extends EventEmitter implements ITransport { private readonly options: StrictDhtNodeOptions private rpcCommunicator?: RoutingRpcCommunicator diff --git a/packages/dht/src/transport/ITransport.ts b/packages/dht/src/transport/ITransport.ts index c9a3208fe9..c6d991e058 100644 --- a/packages/dht/src/transport/ITransport.ts +++ b/packages/dht/src/transport/ITransport.ts @@ -1,9 +1,10 @@ import { Message, PeerDescriptor } from '../../generated/packages/dht/protos/DhtRpc' +import { EventEmitterType } from '@streamr/utils' export interface TransportEvents { + connected: (peerDescriptor: PeerDescriptor) => void disconnected: (peerDescriptor: PeerDescriptor, gracefulLeave: boolean) => void message: (message: Message) => void - connected: (peerDescriptor: PeerDescriptor) => void } export interface SendOptions { @@ -18,20 +19,7 @@ export const DEFAULT_SEND_OPTIONS = { doNotBufferWhileConnecting: false } -export interface ITransport { - on(eventName: T, listener: (message: Message) => void): void - on(eventName: T, listener: (peerDescriptor: PeerDescriptor) => void): void - on(eventName: T, listener: (peerDescriptor: PeerDescriptor, gracefulLeave: boolean) => void): void - - once(eventName: T, listener: (message: Message) => void): void - once(eventName: T, listener: (peerDescriptor: PeerDescriptor) => void): void - once(eventName: T, listener: (peerDescriptor: PeerDescriptor, - gracefulLeave: boolean) => void): void - - off(eventName: T, listener: (message: Message) => void): void - off(eventName: T, listener: (peerDescriptor: PeerDescriptor) => void): void - off(eventName: T, listener: (peerDescriptor: PeerDescriptor, gracefulLeave: boolean) => void): void - +export interface ITransport extends EventEmitterType { send(msg: Message, opts?: SendOptions): Promise getLocalPeerDescriptor(): PeerDescriptor stop(): void | Promise diff --git a/packages/dht/test/integration/RouteMessage.test.ts b/packages/dht/test/integration/RouteMessage.test.ts index a2084f7165..7d3b7381e2 100644 --- a/packages/dht/test/integration/RouteMessage.test.ts +++ b/packages/dht/test/integration/RouteMessage.test.ts @@ -1,4 +1,4 @@ -import { DhtNode, Events as DhtNodeEvents } from '../../src/dht/DhtNode' +import { DhtNode, DhtNodeEvents } from '../../src/dht/DhtNode' import { Message, PeerDescriptor, RouteMessageWrapper } from '../../generated/packages/dht/protos/DhtRpc' import { RpcMessage } from '../../generated/packages/proto-rpc/protos/ProtoRpc' import { Logger, runAndWaitForEvents3, until } from '@streamr/utils' diff --git a/packages/trackerless-network/src/logic/ContentDeliveryLayerNode.ts b/packages/trackerless-network/src/logic/ContentDeliveryLayerNode.ts index 3e1ebf04aa..931cb93754 100644 --- a/packages/trackerless-network/src/logic/ContentDeliveryLayerNode.ts +++ b/packages/trackerless-network/src/logic/ContentDeliveryLayerNode.ts @@ -117,46 +117,48 @@ export class ContentDeliveryLayerNode extends EventEmitter { }) } + /* eslint-disable indent */ + // Linting for indentation is broken in addManagedEventListener for some reason. async start(): Promise { this.started = true this.registerDefaultServerMethods() - addManagedEventListener( + addManagedEventListener<'nearbyContactAdded', (node: PeerDescriptor) => void>( this.options.discoveryLayerNode, 'nearbyContactAdded', () => this.onNearbyContactAdded(), this.abortController.signal ) - addManagedEventListener( + addManagedEventListener<'nearbyContactRemoved', (node: PeerDescriptor) => void>( this.options.discoveryLayerNode, 'nearbyContactRemoved', () => this.onNearbyContactRemoved(), this.abortController.signal ) - addManagedEventListener( + addManagedEventListener<'randomContactAdded', (node: PeerDescriptor) => void>( this.options.discoveryLayerNode, 'randomContactAdded', () => this.onRandomContactAdded(), this.abortController.signal ) - addManagedEventListener( + addManagedEventListener<'randomContactRemoved', (node: PeerDescriptor) => void>( this.options.discoveryLayerNode, 'randomContactRemoved', () => this.onRandomContactRemoved(), this.abortController.signal ) - addManagedEventListener( + addManagedEventListener<'ringContactAdded', (node: PeerDescriptor) => void>( this.options.discoveryLayerNode, 'ringContactAdded', () => this.onRingContactsUpdated(), this.abortController.signal ) - addManagedEventListener( + addManagedEventListener<'ringContactRemoved', (node: PeerDescriptor) => void>( this.options.discoveryLayerNode, 'ringContactRemoved', () => this.onRingContactsUpdated(), this.abortController.signal ) - addManagedEventListener( + addManagedEventListener<'disconnected', (node: PeerDescriptor) => void>( this.options.transport, 'disconnected', (peerDescriptor: PeerDescriptor) => this.onNodeDisconnected(peerDescriptor), diff --git a/packages/trackerless-network/src/logic/DiscoveryLayerNode.ts b/packages/trackerless-network/src/logic/DiscoveryLayerNode.ts index 0ab8a29af6..49a44b3936 100644 --- a/packages/trackerless-network/src/logic/DiscoveryLayerNode.ts +++ b/packages/trackerless-network/src/logic/DiscoveryLayerNode.ts @@ -1,4 +1,5 @@ import { DhtAddress, PeerDescriptor, RingContacts } from '@streamr/dht' +import { EventEmitterType } from '@streamr/utils' export interface DiscoveryLayerNodeEvents { manualRejoinRequired: () => void @@ -9,14 +10,7 @@ export interface DiscoveryLayerNodeEvents { ringContactAdded: (peerDescriptor: PeerDescriptor) => void ringContactRemoved: (peerDescriptor: PeerDescriptor) => void } - -export interface DiscoveryLayerNode { - on(eventName: T, listener: (peerDescriptor: PeerDescriptor) => void): void - on(eventName: T, listener: () => void): void - off(eventName: T, listener: (peerDescriptor: PeerDescriptor) => void): void - off(eventName: T, listener: () => void): void - once(eventName: T, listener: (peerDescriptor: PeerDescriptor) => void): void - once(eventName: T, listener: () => void): void +export interface DiscoveryLayerNode extends EventEmitterType { removeContact: (nodeId: DhtAddress) => void getClosestContacts: (maxCount?: number) => PeerDescriptor[] getRandomContacts: (maxCount?: number) => PeerDescriptor[] diff --git a/packages/trackerless-network/src/logic/proxy/ProxyClient.ts b/packages/trackerless-network/src/logic/proxy/ProxyClient.ts index 145082fbb5..fc3550202b 100644 --- a/packages/trackerless-network/src/logic/proxy/ProxyClient.ts +++ b/packages/trackerless-network/src/logic/proxy/ProxyClient.ts @@ -258,9 +258,11 @@ export class ProxyClient extends EventEmitter { } } + /* eslint-disable indent */ + // Linting for indentation is broken in addManagedEventListener for some reason. async start(): Promise { this.registerDefaultServerMethods() - addManagedEventListener( + addManagedEventListener<'disconnected', (node: PeerDescriptor) => void>( this.options.transport, 'disconnected', // TODO should we catch possible promise rejection? diff --git a/packages/utils/src/EventEmitterType.ts b/packages/utils/src/EventEmitterType.ts new file mode 100644 index 0000000000..9bd34241dd --- /dev/null +++ b/packages/utils/src/EventEmitterType.ts @@ -0,0 +1,35 @@ +// Utility type EmitterOf. By deriving an interface from this type, +// you declare that the interface has the standard EventEmitter3 +// listener setter functions 'on', 'off', 'once' for each event type +// defined in T. + +// Convert EventEmitter3 event types to corresponding listener +// setter function types (eg. types of 'on', 'off', 'once'). +// For example, +// message: (payload: Payload) => void +// -> message: (event: 'message', listener: (payload: Payload) => void) => void + +type ListenerSetterTypes = { + [K in keyof T]: (event: K, listener: T[K]) => void +} + +// In typescript, type of on overloaded function is +// the intersection of the overloaded function types +// (https://stackoverflow.com/a/54887669). +// The following code builds the intersection type +// of the given listener setter function types +// (https://stackoverflow.com/a/66445507). + +type Intersection = { + [K in keyof T]: (x: T[K]) => void + }[keyof T] extends + (x: infer I) => void ? I : never + +type OverloadedListenerSetter = Intersection> + +// eslint-disable-next-line @typescript-eslint/consistent-type-definitions +export type EventEmitterType = { + on: OverloadedListenerSetter + off: OverloadedListenerSetter + once: OverloadedListenerSetter +} diff --git a/packages/utils/src/exports.ts b/packages/utils/src/exports.ts index a0740dd3df..903b2f9880 100644 --- a/packages/utils/src/exports.ts +++ b/packages/utils/src/exports.ts @@ -46,6 +46,7 @@ import { toLengthPrefixedFrame, LengthPrefixedFrameDecoder } from './lengthPrefi import { verifySignature, createSignature, recoverSignerUserId, hash } from './signingUtils' import { ipv4ToNumber, numberToIpv4 } from './ipv4ToNumber' import { MapWithTtl } from './MapWithTtl' +import { EventEmitterType } from './EventEmitterType' export { BrandedString, @@ -108,6 +109,7 @@ export { numberToIpv4, hash, MapWithTtl, + EventEmitterType, Cache }