npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@leparadoxhd/angular-hermes

v1.0.6

Published

Angular 20+ Socket.IO client for Hermes — auto-connect, channels, events$

Readme

@leparadoxhd/angular-hermes

Cliente Socket.IO para aplicaciones Angular 20+ (standalone) que se conectan a Hermes Server.

Hermes expone namespaces dinámicos (/${project}). Este paquete:

  • Conecta automáticamente al arrancar la app.
  • Gestiona join / leave de rooms con prefijos user: y group:.
  • Re-une la sesión de usuario tras reconexión.
  • Expone streams RxJS (connected$, events$, on<T>()).

Para encolar notificaciones desde el backend (BullMQ), usa @leparadoxhd/nestjs-hermes o @leparadoxhd/hermes-client. Este paquete es solo el cliente en el navegador.


Requisitos

| Dependencia | Versión | |-------------|---------| | @angular/core | ^20 o ^21 | | @angular/common | ^20 o ^21 | | rxjs | ^7.8 | | socket.io-client | ^4.8 | | Hermes Server | Socket.IO en el mismo project que configuras |


Instalación

npm install @leparadoxhd/angular-hermes socket.io-client

socket.io-client es peer obligatorio (no se empaqueta dentro de la librería).


Configuración rápida

1. Registrar el provider

En app.config.ts (sin NgModule):

import { ApplicationConfig } from "@angular/core";
import { provideHermesSocket } from "@leparadoxhd/angular-hermes";

export const appConfig: ApplicationConfig = {
  providers: [
    provideHermesSocket({
      project: "mi-app",
      socketUrl: "https://hermes.example.com",
      authToken: "secreto-en-claro", // opcional; el servidor guarda bcrypt en JSON
    }),
  ],
};

| Opción | Descripción | |--------|-------------| | project | Nombre del namespace. Debe coincidir con el project de Hermes / NestJS (forRoot({ project })). | | socketUrl | Origen del servidor Hermes (protocolo + host + puerto). Ej. http://localhost:3000 en dev. | | authToken? | Secreto en claro para namespaces protegidos (auth.token en handshake). No envíes el hash bcrypt. | | autoConnect? | Conectar al instanciar el servicio. Por defecto true. Usa false si conectarás tras login con connect(token). |

Fijos en la librería (no configurables):

  • Ruta Socket.IO: path: "/" (mismo default que el servidor).
  • Transporte: solo websocket.

URL efectiva de conexión:

{socketUrl}/{project}

Ejemplo: https://hermes.example.com/mi-app → namespace /mi-app.

2. Inyectar el servicio

import { inject, Injectable } from "@angular/core";
import { HermesSocketService } from "@leparadoxhd/angular-hermes";

@Injectable({ providedIn: "root" })
export class NotificacionesService {
  private readonly hermes = inject(HermesSocketService);
}

El servicio se instancia al resolver el injector (normalmente al primer inject). Por defecto conecta al namespace en el constructor; no hace join hasta que llames a joinUserChannel o joinGroupChannel.

Para namespaces protegidos cuando el token solo existe tras login:

provideHermesSocket({
  project: "mi-app",
  socketUrl: environment.hermesUrl,
  autoConnect: false,
});

// auth.service.ts — tras login
await this.hermes.connect(authToken);
await this.hermes.joinUserChannel(userHash);

Si el token ya está en el config, connect() sin argumentos reconecta con ese valor. connect(nuevoToken) tiene prioridad y fuerza reconexión si cambia el secreto.


Flujo recomendado

sequenceDiagram
  participant App as App_Angular
  participant HS as HermesSocketService
  participant Srv as Hermes_Server

  App->>HS: provideHermesSocket (bootstrap)
  HS->>Srv: connect /project
  Note over App,Srv: Usuario anónimo — solo conectado al namespace

  App->>HS: joinUserChannel(userHash)
  HS->>Srv: join user:hash + group:authenticated

  Srv-->>HS: emit notification (vía cola BullMQ)
  HS-->>App: on notification

  App->>HS: leaveUserChannel (logout)
  HS->>Srv: leave user + authenticated
  1. Bootstrap: conexión al namespace (sin rooms).
  2. Tras login: await joinUserChannel(userHash).
  3. Escuchar: on<T>('nombre-evento') o events$.
  4. Logout: await leaveUserChannel(userHash).
  5. Grupos opcionales: joinGroupChannel / leaveGroupChannel cuando haga falta.

Protocolo de rooms (Hermes)

Hermes solo acepta suscripciones con rooms user:<id> o group:<id> (id: letras, números, _, -, ., máx. 128 caracteres).

| Acción cliente | Evento Socket.IO | Payload | |----------------|------------------|---------| | Confirmación namespace | connected (servidor → cliente) | { success: true, namespace, socketId } | | Suscribirse | join | "user:abc" o ["user:abc", "group:ventas"] → ack { success, ok, rooms } | | Desuscribirse | leave | Igual formato |

Este paquete construye esos nombres por ti en los métodos públicos; no envíes el prefijo en joinUserChannel (solo el hash).

Canal de usuario autenticado

joinUserChannel(hash) une dos rooms en un solo join:

| Room | Uso | |------|-----| | user:<hash> | Mensajes dirigidos a un usuario (backend: user: hash en la cola). | | group:authenticated | Broadcast a todos los usuarios logueados (backend: group: "authenticated"). |

Constante exportada: AUTHENTICATED_GROUP_ID === "authenticated".

No hay auto-join a group:public. Si lo necesitas, llama explícitamente joinGroupChannel("public").


HermesSocketService — API

Conexión

connect(authToken?: string): Promise<void>;
disconnect(): void;

Conecta o reconecta al namespace. authToken opcional; si se omite, usa authToken del config. La Promise se resuelve cuando el WebSocket conecta; se rechaza en connect_error. Idempotente si ya está conectado con el mismo token.

disconnect() cierra la conexión y limpia la sesión activa (no re-join automático al reconectar hasta que vuelvas a llamar joinUserChannel).

Estado de conexión

readonly connected$: Observable<boolean>;
  • true tras connect.
  • false en disconnect o connect_error.
  • Emite el valor actual al suscribirse (BehaviorSubject interno).

Eventos entrantes

readonly events$: Observable<HermesSocketMessage>;
// HermesSocketMessage = { event: string; payload: unknown }

on<T>(eventName: string): Observable<T>;

Todos los eventos que envía el servidor (vía worker websocket) llegan a events$. on<T>() filtra por nombre y tipa el payload.

// Preferido: tipado
hermes.on<{ msg: string }>("notification").subscribe((data) => {
  console.log(data.msg);
});

// Todos los eventos
hermes.events$.subscribe(({ event, payload }) => {
  console.log(event, payload);
});

Recuerda desuscribirte (takeUntilDestroyed, async pipe, etc.) para evitar fugas.

Canales de usuario

await hermes.joinUserChannel(userHash);   // Promise<void>
await hermes.leaveUserChannel(userHash);
  • Guarda userHash como sesión activa (activeUserHash).
  • Valida el id con ID_PATTERN; si no es válido, lanza Error.
  • Si el socket no está conectado: Error: Hermes socket is not connected.
  • Si el servidor rechaza el join: Error con el mensaje del ack (invalid room, etc.).

leaveUserChannel hace leave de user:<hash> y group:authenticated. Si el hash coincide con la sesión activa, limpia activeUserHash.

Canales de grupo

await hermes.joinGroupChannel(groupHash);
await hermes.leaveGroupChannel(groupHash);

Solo une group:<hash>. No se re-unen automáticamente tras reconexión (a diferencia de la sesión de usuario).

Reconexión

Si el socket se cae y vuelve a conectar:

  • Si había joinUserChannel previo → reintenta join de user:<hash> + group:authenticated (silencioso si falla; puedes volver a llamar joinUserChannel).
  • Los grupos unidos con joinGroupChannel no se restauran solos.

Ejemplo completo (auth + notificaciones)

// app.config.ts
export const appConfig: ApplicationConfig = {
  providers: [
    provideHermesSocket({
      project: environment.hermesProject,
      socketUrl: environment.hermesUrl,
    }),
  ],
};

// auth.service.ts
@Injectable({ providedIn: "root" })
export class AuthService {
  private readonly hermes = inject(HermesSocketService);

  async onLogin(userHash: string, authToken: string): Promise<void> {
    await this.hermes.connect(authToken);
    await this.hermes.joinUserChannel(userHash);
  }

  async onLogout(userHash: string): Promise<void> {
    await this.hermes.leaveUserChannel(userHash);
    this.hermes.disconnect();
  }
}

// notificaciones.service.ts
@Injectable({ providedIn: "root" })
export class NotificacionesService {
  private readonly hermes = inject(HermesSocketService);
  private readonly destroyRef = inject(DestroyRef);

  iniciarEscucha(): void {
    this.hermes
      .on<{ title: string; body: string }>("notification")
      .pipe(takeUntilDestroyed(this.destroyRef))
      .subscribe((n) => {
        // mostrar toast, etc.
      });
  }
}

Enviar eventos desde el backend (NestJS)

El frontend escucha el nombre de evento que encolas en el servidor. El targeting usa hash sin prefijo en el job; Hermes añade user: / group: al emitir.

// NestJS — tras login del usuario
await this.hermesWs.add({
  event: "notification",
  payload: { title: "Hola", body: "Mundo" },
  user: userHash, // → sockets en room user:<hash>
});

// Solo usuarios autenticados (todos los que hicieron joinUserChannel)
await this.hermesWs.add({
  event: "system-announcement",
  payload: { msg: "Mantenimiento en 5 min" },
  group: "authenticated",
});

// Varios grupos
await this.hermesWs.add({
  event: "team-update",
  payload: { teamId: 1 },
  group: ["ventas", "admins"],
});

// Todo el namespace (sin user ni group)
await this.hermesWs.add({
  event: "global-ping",
  payload: { t: Date.now() },
});

Instalación Nest: @leparadoxhd/nestjs-hermes.


Variables de entorno (referencia)

En la app Angular (build-time):

# environment.ts
hermesUrl: 'http://localhost:3000',
hermesProject: 'mi-app',

En Hermes Server (debe coincidir el project):

PORT=3000
# SOCKETIO_PATH=/socket.io  # opcional; el servidor ya usa el estándar

Detalle del servidor: README del monorepo.


API exportada

| Símbolo | Tipo | Descripción | |---------|------|-------------| | provideHermesSocket | función | Registra config + HermesSocketService | | HermesSocketService | clase | Cliente Socket.IO inyectable | | HermesSocketConfig | interface | { project, socketUrl, authToken?, autoConnect? } | | HermesSocketMessage | interface | { event, payload } | | InboundAck | type | Respuesta ack de join/leave (uso interno) | | HERMES_SOCKET_OPTIONS | InjectionToken | Token de configuración | | AUTHENTICATED_GROUP_ID | string | "authenticated" | | userRoom(id) | función | "user:" + id | | groupRoom(id) | función | "group:" + id |


Errores frecuentes

| Síntoma | Causa probable | |---------|----------------| | No llegan eventos | No llamaste joinUserChannel o el backend usa otro user / namespace. | | Hermes socket is not connected | join* antes de connected$ === true. Espera conexión o reintenta. | | Invalid user hash | Caracteres no permitidos en el hash (espacios, : en el id, etc.). | | Conecta pero 404 / CORS | socketUrl incorrecta o proxy que no deja pasar WebSocket. | | Namespace vacío | project distinto entre Angular y NestJS / Hermes. |


Desarrollo en el monorepo

bun run build:packages          # incluye angular-hermes (ng-packagr)
bun test packages/angular-hermes/tests

Publicación: ver PUBLISHING.md.


Relación con otros paquetes

| Paquete | Rol | |---------|-----| | angular-hermes (este) | Cliente Socket.IO en el navegador | | nestjs-hermes | Encolar jobs desde API Nest | | hermes-client | Cliente BullMQ sin framework | | hermes-server | Workers + Socket.IO |