添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
相关文章推荐
飘逸的荔枝  ·  WebSockets support in ...·  1 月前    · 
逆袭的鸵鸟  ·  Progress Customer ...·  9 月前    · 
开心的啤酒  ·  Generate SHA-256 ...·  1 年前    · 
留胡子的扁豆  ·  The Vamps_百度百科·  1 年前    · 

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

Description

I try to extend an interface of a library but without success
I have posted a question to stackoverflow but no one answered

I need to add some custom properties to WebSocket instance
Help me please describe it with typescript

Reproducible in:

  • version: any
  • Node.js version(s): any
  • OS version(s): any
  • Steps to reproduce:

    socket.on('connection', (ws: WebSocket, req: http.IncomingMessage) => {
        ws.online = true; // for heartbit but typescript throws a error
    

    Expected result:

    typescript is happy

    Actual result:

    typescript is angry

    Attachments:

    I think you have to extend the existing classes

    declare class MyWebSocket extends WebSocket {
      online: boolean;
    

    and similarly for WebSocket.Server. I don't think it's possible to modify existing interfaces but I'm a TypeScript noob so I may be wrong.

    Actually it's possible in typescript and works well with other DefinatelyTyped typings also. It just doesn't work with this library..

    user.middleware.ts

    import { NextFunction, Request, Response } from "express";
    export const userMiddleware = (req: Request, res: Response, next: NextFunction) => {
        req.userId = 1; // NO error here after using below declaration merging
        next();
    

    express-augmented.d.ts

    import express from "express";
    declare module "express" {
        export interface Request {
            userId: number;
    

    See last two sections: https://www.typescriptlang.org/docs/handbook/declaration-merging.html

    But when i try to do the same with this library, it shows errors. I am not 100% sure but i think it's because of all the interfaces inside namespace using the exported class(exported outside namespace)

    I am sorry i don't know how DefinatelyTyped typings work exactly but Is there any specific reason why class WebSocket is declared outside namespace WebSocket ?

    Leaving this here since it took too long to figure this out in typescript (i'm still relatively new to it):

    All you have to do is declare this in your file:

    interface ExtWebSocket extends WebSocket {
        isAlive: boolean;
    

    then wherever you're defining that, for example:

    setInterval(() => {
        wss.clients.forEach((ws: WebSocket) => {
            const extWs = ws as ExtWebSocket;
            if (!extWs.isAlive) return ws.terminate();
            extWs.isAlive = false;
            ws.ping(null, undefined);
    }, 10000);
    

    You cannot augment it via a module, as this particular library implements using a class and not an interface. That's what causes the type errors when trying to add new fields to the websocket object. That's what I gathered after researching.

    This uses type assertion: https://basarat.gitbooks.io/typescript/docs/types/type-assertion.html

    dafi87, abumalick, ferares, Badestrand, and AlexStormwood reacted with thumbs up emoji abumalick reacted with rocket emoji All reactions

    I know that this issue is closed but still want to share it here, maybe someone will find this useful, as it worked for me.

    declare module "ws" {
      class _WS extends WebSocket { }
      export interface WebSocket extends _WS {
          online: boolean;
    

    though this will not work (since WebSocket here refers to the class, not the declared interface):

    socket.on('connection', (ws: WebSocket, req: http.IncomingMessage) => {
        ws.online = true;
    

    while these do:

  • without type definition:
  • socket.on('connection', (ws, req: http.IncomingMessage) => {
        ws.online = true;
    
  • with type definition (WebSocket.WebSocket):
  • socket.on('connection', (ws: WebSocket.WebSocket, req: http.IncomingMessage) => {
        ws.online = true;
      Buntelrus, alanxp, DevDengChao, Aliph0th, and marcuspuchalla reacted with thumbs up emoji
      vkumov, beiguancyc, anotherminh, Buntelrus, TheMachineHerald, j8jacobs, matbind, and Ammaraly reacted with heart emoji
      TheMachineHerald and tuanpq1998 reacted with rocket emoji
        All reactions
              

    Kudos to @yuriyyakym!

    This is only solution I've found for accessing the method signatures of class WebSocket in VSCode.
    (instead of getting namespace ws.WebSock, (or the typescriptlib browser-side WebSocket)

    import * as ws from "ws"
    declare module 'ws' {
      export interface WebSocket extends ws { }
    

    I can now declare a ws: ws.WebSocket and ws.send(...) shows all the right arg types!

    I know that this issue is closed but still want to share it here, maybe someone will find this useful, as it worked for me.

    declare module "ws" {
      class _WS extends WebSocket { }
      export interface WebSocket extends _WS {
          online: boolean;
    

    though this will not work (since WebSocket here refers to the class, not the declared interface):

    socket.on('connection', (ws: WebSocket, req: http.IncomingMessage) => {
        ws.online = true;
    

    while these do:

    1. without type definition:
    
    socket.on('connection', (ws, req: http.IncomingMessage) => {
        ws.online = true;
    
    1. with type definition (`WebSocket.WebSocket`):
    
    socket.on('connection', (ws: WebSocket.WebSocket, req: http.IncomingMessage) => {
        ws.online = true;
    

    This was very handy and a slick way of bypassing this restriction.

    I know this issue is old, but it keeps getting referenced, and none of the above are actually correct. Making a type like that is effectively just lying to the type system to create a type like that, as the actual type of the socket object will still be the default WebSocket.

    // online will be `undefined`
    wss.on('connection', (ws) => console.log(ws.online.toString())

    Take note of the type parameter for the server:

        class Server<
            T extends typeof WebSocket.WebSocket = typeof WebSocket.WebSocket,
            U extends typeof IncomingMessage = typeof IncomingMessage,
        > extends EventEmitter {

    It is NOT T extends WebSocket.WebSocket, but T extends typeof WebSocket.WebSocket. typeof SomeClass gives you the type of the class object (constructor function), not the type of an instance of the class. Thus, after extending the original WebSocket class, the generic type parameter should be Server<typeof CustomWebSocket>.

    Also note the constructor parameter WebSocket in the options for Server. Here, you pass the custom class, which is then created here on connection:
    ws/lib/websocket-server.js Line 382 a57e963

    import { Server, WebSocket } from 'ws'
    export class CustomWebSocket extends WebSocket {
      userId!: string
      sendStuff() {
        this.send('stuff')
    export class CustomWebsocketServer extends Server<typeof CustomWebSocket> {
      constructor() {
        super({
          WebSocket: CustomWebSocket,
      socketForUser(userId: string): CustomWebSocket | undefined {
        // this.clients is of type Set<CustomWebSocket>
        for (const socket of this.clients) {
          if (socket.userId === userId) {
            return socket
    const wss = new CustomWebsocketServer()
    wss.on('connection', (socket) => {
      // socket is of type CustomWebSocket
      socket.sendStuff()
    

    Final note: You don't need one, but if you add a constructor to your WebSocket subclass, it has to be compatible with that of the original WebSocket class, which is techically:

    constructor(...params: ConstructorParameters<typeof WebSocket>) {}

    However, it will only ever be called with null, so you may as well leave it as a no-args constructor()

    I found a simple solution to this problem. I use express-ws as a basis.

    Supporting files

    package.json

    "name": "websocket-example", "version": "1.0.0", "description": "Websocket example", "main": "src/index.ts", "scripts": { "dev:server": "nodemon src/index.ts" "author": "Mykhailenko Serhii", "dependencies": { "config": "^3.3.8", "express": "^4.18.1", "express-ws": "^5.0.2", "uuid": "^9.0.1" "devDependencies": { "@types/config": "^3.3.0", "@types/express": "^4.17.14", "@types/express-ws": "^3.0.4", "@types/node": "^18.18.5", "@types/uuid": "^9.0.7", "@types/ws": "^8.5.10", "nodemon": "^2.0.20", "ts-node": "^10.9.2", "typescript": "^4.8.3"

    tsconfig.json

    "compilerOptions": { "target": "es2020", "module": "commonjs", "allowJs": true, "outDir": "./dist", "esModuleInterop": true, "forceConsistentCasingInFileNames": true, "strict": true, "skipLibCheck": true

    Solution

  • Create custom types:
    For example, an id field will be added to the ws object.
  • types.ts

    import {Request, NextFunction} from 'express'
    import * as ws from 'ws'
    export type MyWs = ws & {id: string}
    export type MyWsRequestHandler = (ws: MyWs, req: Request, next: NextFunction) => void
  • Create router, middleware and controller:
  • router.ts

    import {Router} from 'express'
    import {WebsocketRequestHandler} from 'express-ws'
    import {MyWsRequestHandler} from './types'
    import {v4} from 'uuid'
    const generateWsIdMiddleware: MyWsRequestHandler = (ws, _, next) => {
        ws.id = v4()
        next()
    type TController = {
        getUser: MyWsRequestHandler
        activateUser: MyWsRequestHandler
    const controller: TController = {
        getUser(ws, req) {
            const clientId = ws.id
            const {userId} = req.params
            console.log(`[ws] + connection | ${clientId}`)
            ws.on('close', () => {
                console.log(`[ws] - close | ${clientId}`)
            ws.on('message', (message) => {
                console.log('[ws] message', message)
                // your code
                // services.onWsMessage(message)
                // services.getUser(userId)
        activateUser(ws, req) {
            const clientId = ws.id
            const {link} = req.params
            console.log(`[ws] + connection | ${clientId}`)
            ws.on('close', () => {
                console.log(`[ws] - close | ${clientId}`)
            ws.on('message', (message) => {
                console.log('[ws] message', message)
                // your code
                // services.onWsMessage(message)
                // services.activateUser(link)
    export const wsRouter = Router()
    wsRouter.ws(
        '/users/:userId',
        generateWsIdMiddleware as WebsocketRequestHandler,
        controller.getUser as WebsocketRequestHandler
    wsRouter.ws(
        '/users/activate/:link',
        generateWsIdMiddleware as WebsocketRequestHandler,
        controller.activateUser as WebsocketRequestHandler
    
  • Create a server:
  • index.ts

    import express from 'express'
    import expressWs from 'express-ws'
    const expressWsServer = expressWs(express())
    const app = expressWsServer.app
    process.env['NODE_CONFIG_DIR'] = __dirname + '/config/'
    import config from 'config'             // necessarily after creating the server
    import {wsRouter} from './router'
    const PORT = config.get('port') || 5000
    app.use(express.json({inflate: true}))
    app.use('/ws', wsRouter)
    async function start() {
        try {
            app.listen(PORT, () => console.log(`Server listening on ${PORT} ...\n`))
        } catch (err) {
            console.log(err)
    start()

    In conclusion

    I placed the entire finished test project in the archive, link: websocket.zip

    If it helped someone - mark it

    import { Server, WebSocket } from 'ws'
    export class CustomWebSocket extends WebSocket {
      userId!: string
      sendStuff() {
        this.send('stuff')
    export class CustomWebsocketServer extends Server<typeof CustomWebSocket> {
      constructor() {
        super({
          WebSocket: CustomWebSocket,
      socketForUser(userId: string): CustomWebSocket | undefined {
        // this.clients is of type Set<CustomWebSocket>
        for (const socket of this.clients) {
          if (socket.userId === userId) {
            return socket
    const wss = new CustomWebsocketServer()
    wss.on('connection', (socket) => {
      // socket is of type CustomWebSocket
      socket.sendStuff()
    

    Am I missing something here? Having CustomWebSocket in the CustomWebsocketServer constructor creates a circular import where in ends up passing either port, server, or noServer in the options more than once. When you create a new CustomWebsocketServer instance, even though you haven't passed any options arguments, it's passing the default WebsocketServer arguments more than once.

    This error goes away when when you remove the Websocket option and add either port, server, noServer into the constructor.

    it's passing the default WebsocketServer arguments more than once.

    I don't know what that means. How can you pass arguments more than once? Either way, this has nothing to do with those properties. It's just referenced in a single place here:

    ws/lib/websocket-server.js Line 385 d343a0c

    This is what your full example is throwing when I run it:

     /app/node_modules/ws/lib/websocket-server.js:86
     throw new TypeError(
    TypeError: One and only one of the "port", "server", or "noServer" options must be specified
         at new WebSocketServer (/app/node_modules/ws/lib/websocket-server.js:86:13)
         at new CustomWebSocketServer (/app/dist/src/typings/ws.js:21:9)
    

    That error goes away with this for example:

    export class CustomWebSocketServer extends Server<typeof CustomWebSocket> {
      constructor(...params: ConstructorParameters<typeof Server>) {
        super({
          noServer: true
              

    Well sure, you still have to configure the rest of the options as appropriate for your use case, but none of those are related to the custom socket type. E.g.

        super({
          WebSocket: CustomWebSocket,
          noServer: true,