Skip to content

Hooks & Middleware

WebMQ provides Express-style middleware hooks on both the server and client for intercepting and transforming messages.

Server-side hooks

Hooks run at specific points in the message lifecycle. Each hook receives the message header, a context object, and (for RMQ messages) the raw AMQP message.

Hook types

Hook Runs when
pre Before every action
wsMessage After pre, before action-specific hook
identify Client identifies itself
publish Client publishes a message
listen Client subscribes to a topic
unlisten Client unsubscribes
rmqMessage RabbitMQ delivers a message to a client
post After action-specific hook

Hook signature

type HookFunction = (
  header: MessageHeader,
  context: HookContext,
  rmqMessage?: amqplib.ConsumeMessage
) => Promise<MessageHeader>;

Return the (possibly modified) header. Throw to abort the action.

Adding hooks

import WebMQServer from 'webmq-backend';

const server = new WebMQServer({
  rmqUrl: 'amqp://localhost',
  exchange: 'secure_app',
  port: 8080
});

// Authentication — reject unauthenticated requests
server.addHook('wsMessage', async (header, context) => {
  if (header.action === 'identify') {
    const { token } = header;
    if (!token) throw new Error('Authentication required');
    context.user = await verifyToken(token);
  }
  return header;
});

// Authorization — restrict listen to user-specific topics
server.addHook('listen', async (header, context) => {
  if (!header.bindingKey.endsWith(context.user.id)) {
    throw new Error('Cannot listen to other users events');
  }
  return header;
});

// Payload enrichment — add metadata to every publish
server.addHook('publish', async (header, context) => {
  header.payload = {
    ...header.payload,
    userId: context.user.id,
    timestamp: Date.now()
  };
  return header;
});

await server.start();

Auth example

A complete auth flow using hooks on both server and client:

Server:

server.addHook('identify', async (header, context) => {
  const { token } = header;
  if (!token) throw new Error('Token required');
  context.user = await jwt.verify(token, SECRET);
  return header;
});

server.addHook('publish', async (header, context) => {
  if (!context.user) throw new Error('Not authenticated');
  header.payload.userId = context.user.id;
  return header;
});

Client:

client.addHook('identify', (header) => {
  header.token = localStorage.getItem('jwt');
  return header;
});

Client-side hooks

Client hooks are synchronous header transformers — modify and return the header.

Hook types

Hook Runs when
pre Before every action
identify Before sending identify message
publish Before publishing
listen Before subscribing
unlisten Before unsubscribing
message When receiving a message from the server
post After action-specific hook

Hook signature

type HookFunction<T extends MessageHeader> = (header: T) => T;

Return the modified header. Returning undefined or throwing will abort the action.

Adding hooks

import WebMQClient from 'webmq-frontend';

const client = new WebMQClient({
  url: 'ws://localhost:8080',
  sessionId: crypto.randomUUID()
});

// Logging
client.addHook('pre', (header) => {
  console.log('Processing:', header.action);
  return header;
});

// Decrypt incoming messages
client.addHook('message', (header) => {
  if (header.payload?.encrypted) {
    header.payload.data = decrypt(header.payload.encrypted);
    delete header.payload.encrypted;
  }
  return header;
});

// Add auth token to identify
client.addHook('identify', (header) => {
  header.token = sessionStorage.getItem('authToken');
  return header;
});

client.connect();

Removing hooks

const logHook = (header) => {
  console.log('Processing:', header.action);
  return header;
};

client.addHook('pre', logHook);
// ...
client.removeHook('pre', logHook);

Context object

The context object on the server persists across all messages from the same WebSocket connection:

const sessionHook = async (header, context) => {
  if (!context.sessionStartTime) {
    context.sessionStartTime = Date.now();
  }
  // Available in all subsequent hook calls for this connection
  return header;
};

server.addHook('pre', sessionHook);

On the client, you can use closures or module-level variables instead — the context parameter does not exist in client hooks since they are synchronous transformers.