joshmossas/event-source-plus

onMessage listener doesn't work with React

Closed this issue ยท 15 comments

I would like to use this lib in React SPA, unfortunately the onMessage event listener is not working. I can't do any state changes or see console logs when SSE messages arrive (I can see them in the browser's network tab).
I see the logs from onResponse listener, eventSource is definitely connects to server with HTTP200.

I use this code in a React component:

useEffect(() => {
    const eventSource = new EventSourcePlus(
      "http://localhost:8000/process", 
      {
        method: "get",             
      }
    );

    const controller = eventSource.listen({
      onMessage(msg) {
        console.log(msg.event, msg.data);        
      },
      async onResponse({ request, response, options }) {
        console.log(`Received status code: ${response.status}`);
      },
      async onResponseError({ request, response, options }) {
        console.log(
          `[response error]`,
          request,
          response.status,
          response.body
        );
        controller.abort();
      },
    });

    return () => {      
      controller.abort();
    };
}, []);

NodeJS: 20.15.1
React: 18.3.1
Vite: 5.3.4

Do you have any idea what could be wrong? The Microsoft lib has the same issue at me.

P.S.: I have tried out with your h3 test server and it worked.
I don`t know the difference between the ordinary EventSource browser Api and EventSourcePlus related to the server side.

Can you confirm that the server is setting the keep-alive header properly. You'll still get a 200 response even if the keep-alive header isn't set.

Here is an example of my code for a connection event in the server. I have to explicitly set the keep-alive header.

/**
 * @param {string} conn_id combination of user id & agent id
 * @param {connection} conn
 */
static connect(conn_id: string, conn: connection): void {
  conn.conn.writeHead(200, {
    'Content-Type': 'text/event-stream',
    'Connection': 'keep-alive',
    'Cache-Control': 'no-cache'
  })
  const msg = EventController.composeEvent({
    id: conn_id,
    event: 'connect',
    data: { 'connected': true }
  })
  conn.conn.write(msg)
}

I'm storing each connection in a linked list so you can ignore a lot of this, but you can see when a connection is created, I have to ensure these headers are set.

Can you confirm you're setting the headers and possibly send the log you're getting back in the client.

@teddybee could you provide an example of what your server code looks like? It could be that your server is sending a malformed response. If you are experiencing the same issue in the Microsoft library it does make me think there might be an issue with how your server endpoint is set up.

Can you confirm that the server is setting the keep-alive header properly. You'll still get a 200 response even if the keep-alive header isn't set.

Request header:

GET /process HTTP/1.1
Accept-Encoding: gzip, deflate, br, zstd
Accept-Language: en-US,en;q=0.9,hu;q=0.8
Connection: keep-alive
Host: localhost:8000
Origin: http://localhost:5173
Referer: http://localhost:5173/
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-site
User-Agent: ...
accept: text/event-stream

The response header that I can see in the browser (there is a keep-live connection):

HTTP/1.1 200 OK
date: Mon, 22 Jul 2024 08:12:40 GMT
server: uvicorn
cache-control: no-cache
connection: keep-alive
x-accel-buffering: no
content-type: text/event-stream; charset=utf-8
access-control-allow-origin: *
access-control-allow-headers: *
access-control-allow-methods: *
transfer-encoding: chunked

The response itself:

event: progress
data: 1

event: progress
data: 2

event: progress
data: 3

event: progress
data: 4

event: progress
data: 5

event: result
data: rtert res

It looks OK, and it is working with standard EventSource.

I look at your H3 server`s response header:

HTTP/1.1 200 OK
access-control-allow-origin: *
Content-Type: text/event-stream
Cache-Control: private, no-cache, no-store, no-transform, must-revalidate, max-age=0
X-Accel-Buffering: no
Connection: keep-alive
Date: Mon, 22 Jul 2024 09:01:09 GMT
Transfer-Encoding: chunked

The only difference with my server is the charset in content type
content-type: text/event-stream; charset=utf-8

Unfortunately even if I set the response headers on my server almost the same, it is not working (the client app is the same).

My server sends the headers with full of small cases (even if I set it PascalCase), like:

connection: keep-alive
cache-control: no-cache,

This could be the root of the problem?
In theory it shouldn`t, headers are case insensitives by specification.

Hmm yeah that all looks correct. Let me poke around and see if I can reproduce.

Should I send/copy here the basic server code of my backend (python) or put it up somewhere to host/run?

@teddybee, yes, if you could provide a repo with a minimal reproduction of what your server is doing, that that would be a huge help

@joshmossas I pushed the backend code in this repo
https://github.com/teddybee/litestar-sse-test
I used Poetry as a package manager.

@teddybee Thanks for the reproduction. I was able to determine the source of the issue.

It turns out the python implementation is using \r\n to delimit lines instead of \n. I reread the spec over at https://html.spec.whatwg.org/multipage/server-sent-events.html and the following are all valid line delimiters for SSE

  • /n
  • /r/n
  • /r

So I needed to rewrite to parser to account for all of these cases.

I'll ping when the patch is ready. Plan on getting it out later today.

@teddybee Thanks for the reproduction. I was able to determine the source of the issue.

It turns out the python implementation is using \r\n to delimit lines instead of \n

The standard, browser inbuilt EventSource is working with this. Thanks for the investigation, I am informing the backend developers too.

@teddybee The fix is live in v0.1.2. Please let me know if you run into any other problems

@joshmossas Thank you very much. The new version is working like a charm.

@joshmossas I was going to post the same issue but luckily updated my dependencies first and I can confirm it's working great now. Thank you!