nestjs / nest

A progressive Node.js framework for building efficient, scalable, and enterprise-grade server-side applications with TypeScript/JavaScript 🚀
https://nestjs.com
MIT License
67.81k stars 7.64k forks source link

Server-Sent Events support #4826

Closed soyuka closed 4 years ago

soyuka commented 4 years ago

Feature Request

Describe the solution you'd like

Considering that Nest supports Websocket out of the box, would you like to see Server-Sent Events available as well? There are lots of cases where using a simple EventSource client-side is sufficient over adding a whole new protocol to the stack (Websocket).

Teachability, Documentation, Adoption, Migration Strategy

On a first thought, and as it's available on top of HTTP, one could have an api like this:

// follows https://www.w3.org/TR/eventsource/#eventsource
interface MessageEvent {
    data: string;
    id: string;
    type: string;
    retry?: number;
}

@Controller('updates')
class Updates {
  @Sse()
  publishUpdates(): Observable<Partial<MessageEvent>> {
    return of({data: "Hello"}, {data: "world!"})
  }
}

On the client side:

const es = new EventSource('/updates')
// outputs
// - Hello
// - world!
es.onmessage = (message.data) => { console.log(message.data) }

Sse directive would take an optional prefix and handle the sse stream (could use https://github.com/EventSource/node-ssestream although the maintenance isn't my favorite I tend to use a custom transformer), it'd handle the id if needed.

I'd be glad to contribute such a feature if you guys are up for it!

kamilmysliwiec commented 4 years ago

Looks like a nice abstraction! Would you like to create a PR for this? However, I'd prefer not to use any additional library for this. If there's anything we must implement, let's do it as a part of the PR instead of bringing external packages.

underfisk commented 4 years ago

@soyuka do you plan to use the standard nodejs http? Because in case we have a fastify app or express we could have this SEE but I'm not sure if you need to receive an Adapter like Nestjs does

soyuka commented 4 years ago

Please see #4842, it's indeed based on nodejs http and it will be compatible with express or fastify adapters without changes. Indeed, we're working directly on the response stream which fastify and express are based on.

Sharique-Hasan commented 4 years ago

Hi, is there any update on this?

soyuka commented 4 years ago

PR is ready on my end, waiting on reviews from the nest team. Feel free to try this out: https://github.com/nestjs/nest/issues/4826 and give some feedbacks.

Sharique-Hasan commented 4 years ago

@kamilmysliwiec any idea how soon are we releasing this? Our product currently needs this as soon as possible

matfire commented 4 years ago

same here; anyone have any updates ?

kamilmysliwiec commented 4 years ago

Added in 7.5.0

Sharique-Hasan commented 4 years ago

@kamilmysliwiec @soyuka where can we see the documentation for this?

underfisk commented 4 years ago

@Sharique-Hasan Check the documentation repository but it might not be added yet

soyuka commented 4 years ago

Hi documentation is in https://github.com/nestjs/docs.nestjs.com/pull/1387

A small example script is available at https://github.com/nestjs/nest/pull/4842

Last but not least I created a sample application in https://github.com/nestjs/nest/tree/master/sample/28-sse

vh13294 commented 4 years ago

If we use pm2 to spawn multiple instance of app, will there be multiple open connection as well ?

Another question is can pass in params to identify unique user in url ?

soyuka commented 4 years ago

Another question is can pass in params to identify unique user in url ?

Probably but I'd suggest to use cookies for authentification or use the mercure protocol if things get complicated, there's an implementation in node https://github.com/Ilshidur/node-mercure also.

If we use pm2 to spawn multiple instance of app, will there be multiple open connection as well ?

The instance called will open a connection.

kuriel-trivu commented 3 years ago

How UseGuards can be combinated with Sse ?

import { AuthGuard } from '@nestjs/passport';
...
@UseGuards(AuthGuard())
@Sse('events')
methodHere() {
    ...
}

i got unexpected 401 error on client side:

const subscription = new EventSource(`${env.API}/events`);
subscription.onerror = (error) => {
    console.log({error});
};
kuriel-trivu commented 3 years ago

i fixed it on client request by adding headers like this:

const subscription = new EventSource(`${env.API}/events`, {
                headers: {
                    'Authorization': 'Bearer ' + token,
                }
 });

so

import { AuthGuard } from '@nestjs/passport';
...
@UseGuards(AuthGuard())

works like a charm!!

quezak commented 3 years ago

@kuriel-trivu can you post some sources about passing headers in the EventSource constructor? From what I see, there's only a withCredentials option for CORS, no info on headers.

soyuka commented 3 years ago

withCredentials makes use of cookies IIRC. Many polyfills (as https://www.npmjs.com/package/event-source-polyfill) add support t for headers, as SSE is plain HTTP it'll work :).

thematan commented 3 years ago

It seems that using sse with fastify disables CORS for that endpoint. Tried specifying @Header decorator - but it didn't work and is bad anyway (must specify only one domain).

I injected response object and set header manually. I guess there is a bug in the @Sse decorator that erases other headers

brianorwhatever commented 3 years ago

@thematan I am also running into a problem with SSE and fastify CORS however am not having luck inject response obj and setting manually. Can you show me how to do that?

thematan commented 3 years ago

@brianorwhatever

import { ServerResponse } from 'http';

@Sse("verify-payment")
    verifyPayment(@Res() response: { raw: ServerResponse }, @Query("tx") tx?: string): Observable<{ data: { status: DTOBookingStage | "close", msg?: string } }> {
        response.raw.setHeader('Access-Control-Allow-Origin', BBConfig.envVars.NODE_ENV === "production" ? "https://www.example.com" : "http://localhost:3000"); //FIXME: It won't be good when i'll have staging
        return this.bookService.verifyClientPayment({}, tx);
    }
brianorwhatever commented 3 years ago

sweet, thanks @thematan I'll give that a go