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

Use own interface for Socket request to allow extending the request interface independently of Express. #4787

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

alesmenzel
Copy link

Use own interface for Socket request to allow extending the request interface with custom properties without polluting the IncomingMessage type which is also used by Express/Passport.

Using socket.io with passport middlewares causes issues when using the same 'userProperty' key is used for both - for example by extending IncomingMessage { user: Express.User } we break passport's isAuthenticated() function, because now typescript thinks that the request is always of the type AuthenticatedRequest as IncomingMessage always has a user property on itself. To fix this, I believe we should be able to extend only the request type inside socket.io -> SocketRequest.

The kind of change this PR does introduce

  • a bug fix
  • a new feature
  • an update to the documentation
  • a code change that improves performance
  • other - Type change

… with custom properties without polluting the IncomingMessage type which is also used by Express/Passport

Using socket.io with passport middlewares causes issues when using the same 'userProperty' key for both - by extending IncomingMessage { user: Express.user } we break passport's isAuthenticated() function which returns never in the else case since the IncomingMessage always has a user property on itself. To fix this, I believe we should be able to extend only the request type inside socket.io -> SocketRequest.
@alesmenzel
Copy link
Author

Hi @darrachequesne, sorry to bother you, would you please review this change.

@alesmenzel
Copy link
Author

References #4795

darrachequesne added a commit that referenced this pull request Sep 13, 2023
The example is now available with different syntaxes:

- CommonJS
- ES modules
- TypeScript

Related: #4787
@darrachequesne
Copy link
Member

Hi! I'm not sure this is needed actually, you should be able to reuse the same SessionData:

import { type Request } from "express";

declare module "express-session" {
  interface SessionData {
    count: number;
  }
}

io.on("connection", (socket) => {
  const req = socket.request as Request;

  req.session.count++;
});

I've updated the example here: https://socket.io/how-to/use-with-express-session

@alesmenzel
Copy link
Author

Hi @darrachequesne , thanks for looking at this, but I don't think it solves the issue. You are hard-casting the request to be something it is not, an express Request.

  • e.g. removing session/passport middleware would still show as if the request has session and user properties
  • you have to cast every handler where you want to access session and user properties

whereas with the change suggested in this PR, we can extend the SocketRequest interface to include the fields we need - it is not the cleanest solution either, because we are still lying to TS, but in user code we don't need to cast the request everytime we want to use it.

Probably the best solution would be to add a generic to the Socket for the type of Request

class Socket<..., SocketRequest extends IncomingRequest = IncomingRequest> {

 public get request(): SocketRequest {

@alesmenzel
Copy link
Author

Hi, sorry to bother you @darrachequesne, I still think there is something to fix. Please see my comment above.

@darrachequesne
Copy link
Member

You are hard-casting the request to be something it is not, an express Request.

Fair enough! That being said, with your solution one would need to declare the SessionData twice, isn't it?

declare module "express-session" {
  interface SessionData {
    count: number;
  }
}

declare global {
  namespace Express {
    interface User {
      username: string;
    }
  }
}

const io = new Server<any, any, any, any, IncomingMessage & {
  session: Session & {
    count: number;
  },
  user: Express.User
}>(httpServer);

@alesmenzel
Copy link
Author

Yes, but you can reuse the SessionData type, but it is just because express-session is done this way.
https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/express-session/index.d.ts#L29

interface SocketRequest extends IncomingMessage {
  session: Session & Partial<SessionData>
  user: Express.User
}

const io = new Server<any, any, any, any, SocketRequest >

@darrachequesne
Copy link
Member

@alesmenzel something like this then?

@alesmenzel
Copy link
Author

@darrachequesne Yes, but I don´t understand using merging IncomingMessage & AdditionalRequestData over allowing to specify own "IncomingMessage" type.

With custom "IncomingMessage", it should be possible to just return

public get request(): SocketRequest {
    return this.client.request;
  }

without typecasting.

@darrachequesne
Copy link
Member

If I understand correctly, you are suggesting something like this:

export class Socket<
  ListenEvents extends EventsMap = DefaultEventsMap,
  EmitEvents extends EventsMap = ListenEvents,
  ServerSideEvents extends EventsMap = DefaultEventsMap,
  SocketData = any,
  SocketRequest extends IncomingMessage = IncomingMessage
> extends StrictEventEmitter<
  ListenEvents,
  EmitEvents,
  SocketReservedEventsMap
> {

  // [...]

  public get request(): SocketRequest {
    return this.client.request;
  }

}

But in that case, TypeScript complains:

lib/socket.ts:1010:5 - error TS2322: Type 'IncomingMessage' is not assignable to type 'SocketRequest'.
  'IncomingMessage' is assignable to the constraint of type 'SocketRequest', but 'SocketRequest' could be instantiated with a different subtype of constraint 'IncomingMessage'.

1010     return this.client.request;
         ~~~~~~~~~~~~~~~~~~~~~~~~~~~

Or am I missing something? Could you please open a pull request with what you had in mind?

vanquisher0411 added a commit to vanquisher0411/socketio_project that referenced this pull request Feb 29, 2024
The example is now available with different syntaxes:

- CommonJS
- ES modules
- TypeScript

Related: socketio/socket.io#4787
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants