Estudio Sustenta broker for handling real-time notifications
Made before I knew about MQTT and then adopting some of it 😅 so maybe just use MQTT
- Add example with whisper
- Add example with binding key
- Set own server for examples with node
- Create NPM scripts for start and test
- Refactor basic example without PHP involved...
- FIX noPollSubscriber_NODE it goeas crazy when lost connection (it seems it won't wait 5 seconds to reconnect and it creates a new http client to connect... so it multiplies the enxt connection?)
- Detect immediately when a client disconnects -> solved? is not really possible without the client sending a ping and the server doing a timeout (this is how websockets socket.io works)
- Migrate keys to external file -> moved to config.js
- Fix all the mixed up using of "event point", "channel" and "topic"
- Organize code
- Add pushing quee when multiple event are emmited to the same endpoint. Messages colliding? -> this was not fault of the server, is a problem in Svelte Controll
- Add verify the server sending event
- Migrate to stateless JWT auth with access and refresh token workflow
- Add redis integration for handling stateful auth
- Add websockets API
- Add raw TCP API
- Connect to a database exposing
connectDb()
in amodule.exports
.- If your database is MariaDB just fill the already set module in
/mdb
folder:- Edit server/mdb/index.js and replace
DATABASE_USER
andDATABASE_PASSWORD
with your credentials (or use node ENV) - Run
npm install
in the/mdb
folder
- Edit server/mdb/index.js and replace
- If your database is MariaDB just fill the already set module in
- Edit server/options.js
- Point to your .key and .cert files for creating an HTTPS secure server. (In my case the Synology NAS certificates)
- Create user-binding credentials modifying the
privateKey
andpublicKey
variables (line 56 and 60). I think this should be the same as the user-binding server but I'm not sure... need to test.
- DONE! Run
node server/index.js
ornohup node server/index.js > "server/[$(date +%F)]stdout.txt" 2> "server/[$(date +%F)]stderr.txt" &
to run permanently. Runkillall -9 node
to kill the proces (And every node instance running 😂 oops)
- Copy client folder to laragon/xaamp server and go to
http://localhost/client/example/index.html
/
Handles client subscription
/event
Handles event sent by a server
/connections
List of active clients and number of instances for each client_id
(and @TOTAL@
).
If no client_id
was passed to the subscription it will default to @ANON_CONNECTION@
/connections?clid=<client_id>
Number of active instances of given client_id
/connections?clid=<client_id>&details=true
Insight of full details of active instances of given client_id
/state
Overview of the server state (connections)
/memory
Overview of the server current memory consumption
/alive
Keeps a connection alive. By hitting this endpoint before the connection times out with the correct connection_id
and connection_secret
it will reset the given connection_id
timeout. A maximum of resets can be configured in the server. It returns the connection_id
, a new connection_secret
and the connection_timeout
set by the server.
Petición POST
a NOTIF. SERVER
en donde se especifiquen los EVENT POINTS
que se quieren escuchar (a los que se va a suscribir)
// SUB CONFIG
{
bind_key: BindKey, // Optional
clid: String, // Optional, helps identify client for debugging
ep: [
/* Setup as string or object with properties */
TopicString,
{
topic: TopicString,
binded: true // Optional. Only works if a valid bind_key is passed
}
]
}
Al pasar una BindKey
válida, se habilitan los Event Points
permitidos para el usuario identificado.
Ej.
Usuario_1
yUsuario_2
se suscriben aCASA/NIVEL_AGUA
y cada uno pasa suBinKey
, pero soloUsuario_1
tiene permitido recibir esa información, por lo queUsuario_2
nunca recibe la notificación.
BindKey
= RSA_ENCRYPTED({iat, session_id})
Las BindKey tienen una expiración corta (~3seg) configurada en broker
Flujo:
- cliente web pide a servidor PHP el
BindKey
pasando suPHP_SESION_ID
en una cookie (HTTPS ONLY) - cliente web se suscribe usando el
BindKey
recibido NOTIF. SERVER
enviaBindKey
a servidor PHP para que este devuelva alNOTIF. SERVER
elUSER_ID
asociado a la sesiónNOTIF. SERVER
bindea usuario con conexion y tópicos
(esto se podría evitar si ambos servidores compartiran storage de session_id o se hiciera uso de JWT)
SOLO LO PUEDE HACER UN BACKEND! EL MISMO QUE MODIFICA BASES DE DATOS
peticion POST
a NOTIF. SERVER
en donde se especifiquen los EVENT POINTS
a los que se notificara y el EVENTO
que contendrá el TIPO DE EVENTO
y opcionalmente los DATOS DE EVENTO
(String JSON encoded si es necesario)
// EVENT
{
bind_key: BindKey, // Optional
ep: [
/* Setup as string or object with properties */
TopicString, // Unbinded, shout mode event
{
topic: TopicString,
whisper: ConnId, // Optional. Connection Id to whisper event
binded: true, // Optional. Only works if a valid bind_key is passed
password: String // Optional. Only if endpoint requires password
}
],
e: {
type: String,
detail: Any // Optional
}
}
Al pasar una BindKey
válida, los EVENT POINTS
serán dirigidos sólo a los usuarios que los pueden escuchar. De igual manera, seran evaluados los permisos para emitir el evento a los EVENT POINTS
pasados.
BindKey
= RSA_ENCRYPTED({iat, user_id})
Las BindKey tienen una expiración corta (~3seg) configurada en broker
Flujo:
- cliente envía evento a servidor PHP destinado. Pasa su
PHP_SESION_ID
en una cookie (HTTPS ONLY) - servidor PHP evalúa si cliente tiene acceso a este endpoint (en base a sesión)
- servidor PHP extrae
USER_ID
dePHP_SESION_ID
, genera elBindKey
y emite evento aNOTIF. SERVER
NOTIF. SERVER
desencriptaBindKey
y si eltimestamp
no ha expirado (es menor al tiempo actual, pero mayor a el tiempo actual menos X segundos), revisa permisos deUSER_ID
recibido para publicar a cadaEVENT POINT
(esto se podría evitar si ambos servidores compartiran storage de session_id o se hiciera uso de JWT) (AMBOS SERVIDORES DEBEN ESTAR PERFECTAMENTE SINCRONIZADOS EN SU RELOJ)
Al momento de publicar o suscribirse de forma bindeada, sólo entran en juego los endpoints registrados en esta base de datos.
// BINDED EVENT POINT
{
ep: String, // Atomic EventPoint
owner: UserId,
pub_password: HashString, // Empty = no password required
pub: [ // Who can publish to this topic?
UserId // Includes owner. WILDCARDS: "[][][]" = all unidentified (binded) users, "+" = all binded users, "*" = all users
],
sub: [ // Who can subscribe to this topic?
UserId // Includes owner. WILDCARDS: "[][][]" = all unidentified (binded) users, "+" = all binded users, "*" = all users
]
}
!!! EVITA REGISTRAR
EVENT POINTS
CONWILDCARDS
(# o +) EN LA BASE DE DATOS
Emitido al realizar la conexión exitosamente. Contiene metadatos de la conexión asignada al cliente.
Escucha las conexiones y desconexiones del Id de cliente suministrado.
Al suscribirse emite un evento con el número de conexiones del clid actualmente conectadas.
Distintos clientes pueden estar suscritos a un mismo EVENT POINT
, por lo tanto cada evento que se emita a dicho EVENT POINT
será recibido por los clientes suscritos a este. Esto se conocerá como un evento en modo SHOUT
.
Sin embargo el emisor del evento puede decidir emitirlo como modo WHISPER
en donde el evento será recibido sólo por un cliente (especificado por el emisor). Esto hace que se tenga una fuincionalidad prácticamente de un tradicional HTTP donde hay peticiones y respuestas a quien hizo la petición.
Si no se recibe instrucción de WHISPER
, se asume modo SHOUT
.
- Definido en peticion de cliente
- Significa que el hecho de estar en esta pagina, te suscribe a sus eventos
- Al salir de la pagina te des-suscribes de sus eventos
- Se recibe cualquier usuario en la pagina
- ej. Un espectador de una partida en especifico de un juego online: recibe las actualizaciones de los jugadores y del chat publico. Fuera de la sala no recibe notificaciones de la partida.
- Definido en BASE DE DATOS de cada evento
- Un evento X revisa en BASE DE DATOS quienes son los usuarios suscritos (a quienes se les notificará el evento) y manda una señal a cada uno de sus EVENT POINTS
- Se recibe independiente de la pagina actual (recibira notificacion en cualquier sitio)
- Para anular la subscripcion se debe modificar la BASE DE DATOS del evento correspondiente
- ej. Se actualiza un post al que estas suscrito: recibes la notificacion de que ocurrio sin importar que estes en ese post
- Definido en peticion de cliente acompañado de TOKEN / SESSID como autenticacion
- Solo se recibe si el usuario esta en la pagina
- Para dejar de escuchar los eventos se puede modificar la BASE DE DATOS del evento (des-suscribirse) o abandonar la pagina actual
- ej. Un jugador de un juego online: recibe las actualizaciones de sus rivales y su chat personal. Fuera de la sala no recibe notificaciones de la partida.