0

I was hoping to use SSE to dynamically send messages to the browser. Ideally I would like a minimal application where the browser does nothing until a function or method (which takes the message as it's argument) is called and the browser receives this message an logs it only once. I have tried to illustrate this with the below:

const http = require("http");
const server = http.createServer(<not sure what code goes here>);
server.listen(8000);

// this can be called at any time after creating the server so the browser 
// can receive the message and log it to the console.
sendMessageToBrowser(`data: this is a dynamic message\n\n`) 

However, the below basic SSE application below simply logs "hello world" to the browser console every 3 seconds (the default). I don't understand how this is any different to serving data via a regular route and using something like:

setInterval(fetch("/events").then(res => res.text()).then(data => console.log(data)));

Is my request possible with SSE or have I misunderstood how it works? I know my request is possible with websockets/socket.io but was hoping to use SSE as I don't want to use a library and SSE simpler to understand and implement.

Minimal example which logs hello world every 3 seconds:

const http = require("http");

const server = http.createServer((req, res) => {
  // Server-sent events endpoint
  if (req.url === "/events") {
    res.writeHead(200, {
      "Content-Type": "text/event-stream",
      "Cache-Control": "no-cache",
      Connection: "keep-alive",
    });
    res.end(`data: hello world\n\n`);
    return;
  }

  // Client side logs message data to console
  res.writeHead(200, { "Content-Type": "text/html" });
  res.end(`
      <script>
        var eventSource = new EventSource('/events');
        eventSource.onmessage = function(event) {
          console.log(event.data);
        };
      </script>
  `);
});

server.listen(8000);
Max888
  • 3,089
  • 24
  • 55
  • whats happening is its just reconnecting every 3 seconds, check your network tab, the reason SSE should be looping server side so the connection is hanging until data is outputted, your `res.end` will end the connection, ignoring any keep-alive.. Ive not implemented SSE in nodejs, socketio is just too easy, but if your into porting, maybe a php example will help. https://stackoverflow.com/a/49081040/661872 – Lawrence Cherone Nov 25 '19 at 13:18
  • you can use [Socket.io](https://www.npmjs.com/package/socket.io) for backend to listen the events on frontend you can use [Socket.io-client](https://www.npmjs.com/package/socket.io-client) – Sonu Bamniya Nov 25 '19 at 13:38
  • @LawrenceCherone thanks for the info. I've change res.end() to res.write() so the message is only sent to the browser once. However I still don't see how I can then send additional messages from the server. I thought the whole point of SSE is that upon some server side event, I can send a new message to the browser? – Max888 Nov 25 '19 at 16:54
  • You can ex: `setInterval()`, maybe eventemitter, or proxied through a database row and a loop inside which polls for event id change, there is no concept of emitting like with socketio – Lawrence Cherone Nov 25 '19 at 16:58
  • "there is no concept of emitting like with socketio" this is the key thing. In retrospect my question should simply have been: "what is the equivalent of socket.io's socket.emit() for SSE?" to which it seems the answer is that there isn't one. I find this strange though because everything I have read describes SSE as a unidirectional equivalent of websockets, and the name suggests that a server side event can be used to emit a message. – Max888 Nov 25 '19 at 17:13

2 Answers2

1

I don't know if I really understand what you need. I changed the code and now if you go to /sendEvent url you see a new log in the root page. You can use resEvents var inside any function to log a new message in the root page.

const http = require("http");
var resEvents;
var count = 0;

var myInterval = setInterval(() => {
  if (count < 50) sendMessageToBrowser(count);
  else clearInterval(myInterval);
}, 1000);

function sendMessageToBrowser(msg) {
  if (resEvents) {
    resEvents.write(`data: ${msg}\n\n`);
    count++;
  }
}

const server = http.createServer((req, res) => {
  // Server-sent events endpoint
  if (req.url === "/events") {
    resEvents = res;
    res.writeHead(200, {
      "Content-Type": "text/event-stream",
      "Cache-Control": "no-cache",
      Connection: "keep-alive"
    });
    res.write(
      `data: Go to http://localhost:8000/sendEvent to log a new Messages \n\n`
    );
    return;
  }

  if (req.url === "/sendEvent") {
    if (!resEvents) {
      res.writeHead(200, { "Content-Type": "text/html" });
      res.end(`Logged a new Message<br>Refresh the page to log a new Message`);
      resEvents.write(
        `data: From Url: ${Math.floor(Math.random() * 5000) + 1}\n\n`
      );
    } else {
      res.writeHead(200, { "Content-Type": "text/html" });
      res.end(`First go to http://localhost:8000/`);
    }
    return;
  }

  // Client side logs message data to console
  res.writeHead(200, { "Content-Type": "text/html" });
  res.end(`
      <script>
        var eventSource = new EventSource('/events');
        eventSource.onmessage = function(event) {
          console.log(event.data);
        };
      </script>
  `);
});

server.listen(8000);

Update

I added sendMessageToBrowser function. It's inside a setInterval so you can see the server is sending max 50 messages to the browser one every second.

Mattia
  • 130
  • 2
  • 11
  • Thanks, this is interesting but doesn't seem to do what I am asking. Refreshing the browser means it is the client side that is generating the event, not the server. And I can't specify the message that the server sends to the browser. – Max888 Nov 25 '19 at 16:46
  • @Max888 refreshing the browser is just to show you how to call a function on the server. You can send messages to the browser from the server you just need to use `resEvents` – Mattia Nov 25 '19 at 17:46
  • thanks - this is exactly what I was hoping to do. I've added a simplified version of your answer. – Max888 Nov 27 '19 at 01:12
0

This is a minimal version of @Mattia's answer.

const http = require("http");
var resEvents;

const server = http.createServer((req, res) => {
  if (req.url === "/events") {
    resEvents = res;
    res.writeHead(200, {
      "Content-Type": "text/event-stream",
      "Cache-Control": "no-cache",
      Connection: "keep-alive",
    });
    return;
  }

  res.writeHead(200, { "Content-Type": "text/html" });
  res.end(`
      <script>
        var eventSource = new EventSource('/events');
        eventSource.onmessage = function(event) {
          console.log(event.data);
        };
      </script>
  `);
});

server.listen(8000);

// this can only be run after the browser has connected - either run code
// sequentially in a REPL or put this in a setTimeout() to give you time 
// to go to localhost:8000
resEvents.write(`data: hello\n\n`); 
Max888
  • 3,089
  • 24
  • 55
  • resEvents is undefined when you launch the server on your version – Mattia Nov 27 '19 at 01:18
  • Good point, I have running each line the the REPL, which gives me a chance to go to localhost:8000 before running resEvents.write(), in which case it works. – Max888 Nov 27 '19 at 02:30