Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unable to access base Event properties from CloseEvent, ErrorEvent & MessageEvent for WebSocket #3524

Closed
eXhumer opened this issue Aug 29, 2024 · 9 comments · Fixed by #3533
Labels
bug Something isn't working Types Changes related to the TypeScript definitions

Comments

@eXhumer
Copy link
Contributor

eXhumer commented Aug 29, 2024

Bug Description

MessageEvent inherits Event and is supposed to provide the properties of Event such as Event.type and Event.timeStamp to MessageEvent. However, any class inheriting Event (CloseEvent, ErrorEvent & MessageEvent) fails to make any of the base properties accessible and causes the typescript compiler to fail unless MessageEvent type gets replaced by any when trying to access the base event properties.

Reproducible By

The following code is successfully compiled by tsc compiler.

const ws = new WebSocket('ws://localhost:8080');

ws.onmessage = (ev) => {
  const e = ev as any;
  console.log(e.type);
  console.log(e.defaultPrevented);
  console.log(e.cancelable);
  console.log(e.timeStamp);
};

The following code will throw an error while trying to compile with tsc due to Property '' does not exist on type MessageEvent<any>.

const ws = new WebSocket('ws://localhost:8080');

ws.onmessage = (e) => {
  console.log(e.type);
  console.log(e.defaultPrevented);
  console.log(e.cancelable);
  console.log(e.timeStamp);
};

Expected Behavior

Base properties of Event class accessible from inherited classes.

Logs & Screenshots

None

Environment

macOS 15.1 Beta (24B5035e), node v22.6.0, undici v6.19.8

Additional context

@eXhumer eXhumer added the bug Something isn't working label Aug 29, 2024
@KhafraDev KhafraDev added the Types Changes related to the TypeScript definitions label Aug 30, 2024
@mcollina
Copy link
Member

@KhafraDev @Uzlopak @panva wdyt?

@panva
Copy link
Member

panva commented Aug 30, 2024

I don't follow, the types are set up to extend Event all the same.

interface CloseEvent extends Event {
readonly code: number
readonly reason: string
readonly wasClean: boolean
}

undici/types/websocket.d.ts

Lines 135 to 141 in cab6011

interface ErrorEvent extends Event {
readonly message: string
readonly filename: string
readonly lineno: number
readonly colno: number
readonly error: any
}

undici/types/websocket.d.ts

Lines 104 to 120 in cab6011

interface MessageEvent<T = any> extends Event {
readonly data: T
readonly lastEventId: string
readonly origin: string
readonly ports: ReadonlyArray<typeof MessagePort>
readonly source: typeof MessagePort | null
initMessageEvent(
type: string,
bubbles?: boolean,
cancelable?: boolean,
data?: any,
origin?: string,
lastEventId?: string,
source?: typeof MessagePort | null,
ports?: (typeof MessagePort)[]
): void;
}

@eXhumer
Copy link
Contributor Author

eXhumer commented Aug 30, 2024

I think the problem lies in undici/types/patch.d.ts, where the Event type is defined with the following.

undici/types/patch.d.ts

Lines 25 to 26 in 5f81898

export type Event = typeof globalThis extends { Event: infer T }
? T

The { Event: infer T } infers the type as any.

@KhafraDev
Copy link
Member

Some history of why it was added: #1740

Is there an easy workaround? Is this even needed anymore? @Ethan-Arrowood @andrewbranch

@eXhumer
Copy link
Contributor Author

eXhumer commented Sep 1, 2024

I did a bit more digging, I think I figured out what the exact issue is. With the type inference being used, typescript infers the following Event type from node_modules/typescript/lib/lib.dom.d.ts instead of the correct type from the same file.

declare var Event: {
    prototype: Event;
    new(type: string, eventInitDict?: EventInit): Event;
    readonly NONE: 0;
    readonly CAPTURING_PHASE: 1;
    readonly AT_TARGET: 2;
    readonly BUBBLING_PHASE: 3;
};

Even if you use @types/node package to provide types, unless you disable the default DOM types provided by typescript, typescript infers the type from typescript package rather than @types/node package.

If you try accessing the readonly values, typescript will incorrectly think they are actually there and not complain when trying to access a non-existent property.

import { WebSocket } from 'undici';
import { WebSocketServer } from 'ws';

const wss = new WebSocketServer({ port: 8080 });

wss.on('connection', function connection(ws) {
  ws.send('something');
});

const ws = new WebSocket('ws://localhost:8080');

ws.onmessage = e => {
  console.log('e.NONE', e.NONE);
  console.log('e.CAPTURING_PHASE', e.CAPTURING_PHASE);
  console.log('e.AT_TARGET', e.AT_TARGET);
  console.log('e.BUBBLING_PHASE', e.BUBBLING_PHASE);
  console.log('e.data', e.data);
};

process.on('SIGINT', () => {
  console.log('Closing WebSocket server');
  wss.close();
  process.exit();
});

Console output

➜  node-network-app git:(main) ✗ yarn tsc
yarn run v1.22.22
$ /Users/exhumer/Projects/node-network-app/node_modules/.bin/tsc
✨  Done in 0.75s.
➜  node-network-app git:(main) ✗ yarn ts-node src/index.ts
yarn run v1.22.22
$ /Users/exhumer/Projects/node-network-app/node_modules/.bin/ts-node src/index.ts
e.NONE undefined
e.CAPTURING_PHASE undefined
e.AT_TARGET undefined
e.BUBBLING_PHASE undefined
e.data something
^CClosing WebSocket server

@eXhumer
Copy link
Contributor Author

eXhumer commented Sep 1, 2024

I have found a fix by getting rid of the Event type provided in patch.d.ts. typescript now correctly imports the DOM Event type from typescript/lib/lib.dom.d.ts. If you want to use the types provided by @types/node package instead for whatever reason, you can disable the default DOM imports from typescript config and then getting rid of the Event type provided in patch.d.ts.

@eXhumer
Copy link
Contributor Author

eXhumer commented Sep 1, 2024

undici is currently at node@>=18.17 after support for node@<18.17 was dropped with the following commit. Based on the comments from @KhafraDev here, this patched Event type seems to have been added for support for node@<=16, which is no longer supported. I am not sure if there is any point in the patched Event type at the very least.

patch.d.ts was added in as a part of this commit when the supported engine was node@>=12.18.

@eXhumer
Copy link
Contributor Author

eXhumer commented Sep 1, 2024

Looking into PR #1690, there was a lengthy discussion with SimenB and other members about the DOMException type and it's availability.

After which a new PR (#1762) was later opened which added the patch.d.ts as part of a type fix.

@eXhumer
Copy link
Contributor Author

eXhumer commented Sep 1, 2024

Confirmed issue to be fixed with the Event & EventTarget removed from patch.d.ts and removing all references to related imports.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working Types Changes related to the TypeScript definitions
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants