MatthewWid / better-sse

⬆ Dead simple, dependency-less, spec-compliant server-sent events implementation for Node, written in TypeScript.
MIT License
485 stars 14 forks source link

Documentation: How to send a message on specific stream in "different controller"? #56

Closed Sun233333 closed 6 months ago

Sun233333 commented 10 months ago

Hey,

sorry to ask this question, but i am trying to get it done for over 10 hours alone :D So i am just asking here (if its wrong and i should delete it, i will):

How can i push a message on a specific in another controller (and even other route) ? I imagine something like that:

res.sse.push("stream/:userId", "hello User with the userId", "ping")

Or have a controller on my route "api/stream/:userId" sendMessagePerSSE, that i can import it anywhere in on my server and have something like:

sendMessagePerSSE("stream/:userId", "hello User with the userId", "ping")

Background: I want to make a real chat in my webapp(MERN without Typescript). I want that if Max (with userId: 4343) send a message to John (with userId: 8787), that in my controller sendMessage with the post.route "api/message/" is the following:

const sendMessage = async (req, res) => {

// logic to send the message to server and save it in the database (this is already obv. working)

and THEN!!!!

to push a message to the stream of John (with userId 8787) and the event "ping", so that John refetch() his messages from the database.

}

John is obv. listening on his stream with id 8787 and the route "api/stream/8787" and to the event "ping" on the frontend. This is also secured by JSON WebToken. That only if he has the right token he is able to connect to this stream. Max with the userId 4343 cant connect to "api/stream/8787" because his token has only the permission to connect to "api/stream/4343".

How do you design it? Or it is not possible? Yeah i just could just connect all user to one stream and let listen every to another event with unique Ids (this what i see in a few tutorials), but this seems very bad practice, because if someone can guess the unique id of another user, he can listen then his SSE-Messages.

Thanks for the help and if you document this simple example i could imagine, this would help a lot of people. Because in other packages with SSE i found the same question, but nobody helped.

MatthewWid commented 10 months ago

A fairly common approach I've seen others do for this has been to create a map from a user ID to a channel and then register new sessions to their corresponding channel. For example,

const userIdToChannel = {};

app.get("/stream/:userId", async (req, res) => {
  const { userId } = req.params;
  const session = await createSession(req, res);

  if (!userIdToChannel[userId]) {
    userIdToChannel[userId] = createChannel();
  }

  userIdToChannel[userId].register(session);
});

Now if you want to send a message to another user you can look up their user ID and broadcast messages to them:

userIdToChannel[johnsUserId]?.broadcast("Hello there", "ping");

This has the added benefit that the same user could open multiple sessions and they would all be registered with the same channel and all of their sessions would receive the same message.

In regards to the user guessing the user ID you would have to implement some mechanism that stops the session being registered with the channel if they are not authenticated, but that is out of the scope of this library.