socket.io-client
socket.io-client copied to clipboard
4.x is no more working with RxJS - not assignable to parameter of type FromEventTarget
Using RxJS instead of writing:
socket.on('connection', () => { ... })
it was possible to create an observable like so:
const connection$ = fromEvent(socket, 'connection');
This was working both with 2.x and 3.x, but since 4.x it throws an error:
Argument of type 'Socket<DefaultEventsMap, DefaultEventsMap>' is not assignable to parameter of type 'FromEventTarget
'.
Specifically it says:
Type 'Socket<DefaultEventsMap, DefaultEventsMap>' is not assignable to type 'JQueryStyleEventEmitter'
Resources:
The interface for FromEventTarget is defined here: https://github.com/ReactiveX/rxjs/blob/5d0552b15c8645fe285e63abdcfaae922174bd21/src/internal/observable/fromEvent.ts#L58
Description of fromEvent:
Creates an Observable from DOM events, or Node.js EventEmitter events or others.
So I think socket.io's .on() is belonging to the interface JQueryStyleEventEmitter, which is no more matching: https://github.com/ReactiveX/rxjs/blob/5d0552b15c8645fe285e63abdcfaae922174bd21/src/internal/observable/fromEvent.ts#L30
Workaround
Adding // @ts-ignore suppresses the error
@darrachequesne Could you take a look at this yet? Still the same for 4.1.2
I think it might be linked to https://github.com/socketio/socket.io/commit/0107510ba8a0f148c78029d8be8919b350feb633.
I'm not sure how to fix this though. @MaximeKjaer any thoughts?
I think the easiest workaround here would be to up-cast the socket to an EventEmitter, using as EventEmitter.
For the RxJS typings, I think a Socket fits best with NodeCompatibleEventEmitter, not JQueryStyleEventEmitter (because the event handlers don't have to have at least 2 parameters, as in JQueryStyleEventEmitter). After all, the Socket extends EventEmitter.
My understanding is that RxJS doesn't statically type events when using fromEvent, so I feel like it's fair enough to have to put in a type cast here, to say "I'm okay losing the strong static typing, just consider this as a very generic thing".
Small rectification: you would probably need to cast to the Node.js EventEmitter on the server-side, and to Emitter from the component-emitter dependency on the client-side.
I think the RxJS type is very generic and also Typescript interfaces are kind of generic. So it does not need to be casted to the specific type, it should just have what one of the types requires then the interface "fits". E.g. before 4.0 it fitted to JQueryStyleEventEmitter
What version of TypeScript are you using? I tried replicating the behavior in a small toy example based on the RxJS source code you linked to, but it seems to pass type checking for me with TypeScript 4.2.4.
Just want to make sure we eliminate this as a possible source of the bug before I dig deeper.
Toy example / replication
function fromEvent<T>(target: FromEventTarget<T>, eventName: string): void {}
type FromEventTarget<T> = EventTargetLike<T> | ArrayLike<EventTargetLike<T>>
type EventTargetLike<T> =
| HasEventTargetAddRemove<T>
| NodeStyleEventEmitter
| NodeCompatibleEventEmitter
| JQueryStyleEventEmitter<any, T>;
interface HasEventTargetAddRemove<E> {
addEventListener(
type: string,
listener: ((evt: E) => void) | EventListenerObject<E> | null,
options?: boolean | AddEventListenerOptions
): void;
removeEventListener(
type: string,
listener: ((evt: E) => void) | EventListenerObject<E> | null,
options?: EventListenerOptions | boolean
): void;
}
interface NodeStyleEventEmitter {
addListener(eventName: string | symbol, handler: NodeEventHandler): this;
removeListener(eventName: string | symbol, handler: NodeEventHandler): this;
}
type NodeEventHandler = (...args: any[]) => void;
interface NodeCompatibleEventEmitter {
addListener(eventName: string, handler: NodeEventHandler): void | {};
removeListener(eventName: string, handler: NodeEventHandler): void | {};
}
interface JQueryStyleEventEmitter<TContext, T> {
on(eventName: string, handler: (this: TContext, t: T, ...args: any[]) => any): void;
off(eventName: string, handler: (this: TContext, t: T, ...args: any[]) => any): void;
}
interface EventListenerObject<E> {
handleEvent(evt: E): void;
}
//////////////////
const socket: Socket<ClientToServerEvents, ServerToClientEvents> = null;
fromEvent(socket, "test") // works?
I made a reproduction repository where the error is reproducable. It occurs when setting strict to true in tsconfig.json.
https://github.com/MickL/socketio-rxjs-fromevent-error
Any news on this? Just started a new Angular project (npx @angular/cli new) and ran into this issue again. I dont want to disable strict :(
I dont have this issue with RxJS 7.8.0 and Angular 16