Skip to content

Reconnect and Heartbeat

Problem

A realtime session must recover from network loss without opening duplicate sockets or replaying application messages before its channel and room state exists.

Solution

Enable reconnect and heartbeat, create scopes before connecting, and subscribe to the status external store.

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

type Schema = {
  channels: {
    chat: {
      client: { send: { text: string } };
      server: { message: { text: string } };
    };
  };
  rooms: {
    lobby: { presence: { name: string } };
  };
};

const pulse = createPulse<Schema>('wss://api.example.com/ws', {
  heartbeat: { interval: 20_000, timeout: 8_000 },
  reconnect: { delay: (attempt) => Math.min(500 * 2 ** attempt, 30_000), maxAttempts: 8 },
});
pulse.tap((event) => {
  if (event.type === 'error') console.error(event.error);
  if (event.type === 'status-change') console.log('status:', event.status);
});
const chat = pulse.channel('chat');
const lobby = pulse.room('lobby');

const stopStatus = pulse.status.subscribe(() => {
  console.log('Pulse status:', pulse.status.getSnapshot());
});

try {
  await pulse.connect();
  chat.on('message', ({ text }) => console.log(text));
  await lobby.joined;
  lobby.updatePresence({ name: 'Ada' });
} catch (error) {
  console.error('Pulse connection failed:', error);
} finally {
  stopStatus();
  lobby.dispose();
  chat.dispose();
  pulse.dispose();
}

Pitfalls

  • disconnect() cancels retry immediately. It never leaves a scheduled reconnect behind.
  • Restoration is ordered. Pulse resubscribes channels, rejoins rooms, then restores the last successfully published local presence state.
  • status is transport state. Wait for the server's own restored-room data before acting on room membership.