Skip to main content

BasePushRoute

BasePushRoute is the concrete delivery mechanism for push notifications: a WebSocket endpoint that subscribes a connected client to Redis pub/sub channels named after their own uid (and any other channel they have READ permission for), and forwards whatever's published on those channels straight to the socket.

import { BasePushRoute, RouteDecorators } from "@rapidrest/service-core";
const { Route } = RouteDecorators;

@Route("/push")
export class PushRoute extends BasePushRoute {}

The CLI scaffold generates exactly this at src/routes/PushRoute.ts, mounted at /push.

Endpoints

FunctionHTTP Method + PathWhat it does
connectUPGRADE /push/connectOpens a WebSocket connection and subscribes it to the authenticated user's own channel.
sendPOST /push/:idPublishes a message to the channel :id, subject to an ACL check.

Both endpoints require @Auth(["jwt"]).

How connect works

On connect, the socket is automatically subscribed to a channel named after the caller's uid. From there, the client can send SUBSCRIBE/ UNSUBSCRIBE messages to add or remove additional channels:

{"id": 1, "type": "SUBSCRIBE", "data": ["room-42"]}

Each requested channel is checked with ACLUtils.hasPermission(user, channel, ACLAction.READ) before the subscription is added. A client can't subscribe to a channel it doesn't have read access to. The server acknowledges with a SUBSCRIBED/UNSUBSCRIBED message echoing the same id.

Every message published on a channel the socket is subscribed to is forwarded to the client verbatim, as the raw JSON string that was published.

Publishing to a channel

There are two ways to get a message onto a channel that connect is listening on:

From server-side code, publish through NotificationUtils (see Notifications). This is the usual path when a backend service wants to notify a user as a side effect of something else happening (an order shipped, a document was updated):

const notifications = await this.objectFactory?.newInstance(NotificationUtils, { args: [this.redis] });
notifications?.sendMessage(ownerUid, 'pet', 'updated', pet);

Over HTTP, via POST /push/:id. This is the endpoint to reach for when the publisher isn't backend code with direct Redis access, for example a webhook handler or another service that only speaks REST. The caller needs CREATE permission on :id:

await fetch('/push/room-42', {
method: 'POST',
headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
body: JSON.stringify({ hello: 'world' }),
});

Note that send wraps the payload as {type: "MESSAGE", channel: id, data: msg} before publishing, which differs slightly from NotificationUtils.sendMessage's {type, action, data} shape. Clients listening for both should handle either envelope.

Configuration

conf.defaults({
datastores: {
events: {type: 'redis', host: 'localhost', port: 6379},
},
});

BasePushRoute publishes and subscribes over the events datastore connection. If it isn't configured, connect still accepts sockets but they'll never receive anything, and send fails with a 500.