Skip to content

Outgoing Middleware

Problem

You need to intercept every outgoing send() call to add cross-cutting behaviour — logging, auth token injection, rate limiting, or message filtering — without scattering that logic across call sites.

Solution

Pass a middleware array to createPulse(). Each function receives (event, payload, next). Call next() to allow the send; omit it to suppress.

ts
import { createPulse, type Middleware } from '@vielzeug/pulse';

type ClientEvents = {
  'chat:send': { text: string };
  'action:execute': { type: string; data: unknown };
};

// Logging middleware
const logger: Middleware = (event, payload, next) => {
  console.debug('[ws out]', event, payload);
  next();
};

// Auth token injection middleware
const injectAuth: Middleware = (event, payload, next) => {
  // Note: middleware operates on event/payload metadata, not the raw frame.
  // Use onMessage for incoming auth — this is for outgoing send filtering.
  console.debug('[ws] sending as', getCurrentUser().id);
  next();
};

// Rate-limiting middleware
const rateLimiter = createRateLimiter(10, 1_000); // 10 messages per second

const limit: Middleware = (event, _payload, next) => {
  if (rateLimiter.allow(event)) {
    next();
  }
  // omit next() to drop the message silently
};

const pulse = createPulse<{}, ClientEvents>('wss://api.example.com/ws', {
  middleware: [logger, injectAuth, limit],
  reconnect: true,
});

// All three middleware functions run on every send()
pulse.send('chat:send', { text: 'Hello!' });
// → [ws out] chat:send { text: 'Hello!' }
// → [ws] sending as user-42
// → message sent (if rate limiter allows)

Suppressing specific events

ts
const redactSensitive: Middleware = (event, payload, next) => {
  // Drop internal debug events in production
  if (import.meta.env.PROD && event.startsWith('debug:')) return;
  next();
};

const pulse = createPulse<{}, ClientEvents>('wss://api.example.com/ws', {
  middleware: [redactSensitive],
});

Pitfalls

  • Middleware only applies to send() calls from application code. Internal frames (ping, join, subscribe, presence) bypass the middleware pipeline.
  • Order matters. Middleware runs in array order. A middleware that omits next() prevents all subsequent middleware and the actual send from running.
  • Middleware is synchronous. There is no async support in the pipeline. For async guards, resolve the decision before calling send().