Socket Channel MultiplexingLoading saved progress…

Socket Channel Multiplexing

Multiplexing runs many independent logical channels — rooms, topics, feeds — over a single physical connection by tagging every message with a channel id and routing each one to the subscribers of just that channel. It is the model behind Phoenix Channels, Rails Action Cable, and Socket.IO namespaces: instead of opening a WebSocket per room, you open one and let a multiplexer fan messages in and out by channel.

You will implement socketChannelMultiplexing(socket). So it can be tested without a real server, it never calls new WebSocket — it takes a socket-like object you pass in, with a send(data) method and an onmessage slot the multiplexer assigns its single listener to. In production you hand it a real WebSocket; in a test you hand it a fake you can drive by hand.

Signature

type SocketLike = {
  send(data: string): void; // write one frame to the wire
  onmessage: ((e: { data: string }) => void) | null; // the mux installs ONE listener here
};

type Channel = {
  on(event: string, handler: (payload: any) => void): () => void; // returns an unsubscribe fn
  off(event: string, handler: (payload: any) => void): void;
  send(event: string, payload?: any): void; // writes an envelope tagged with this channel
  leave(): void; // stop routing here and send a leave frame
};

function socketChannelMultiplexing(socket: SocketLike): {
  channel(name: string): Channel; // the SAME name returns the SAME channel
};

Examples

// Frames on the wire are JSON envelopes: { channel, event, payload }.
const mux = socketChannelMultiplexing(socket);

const room = mux.channel('room:1');
room.on('msg', (payload) => render(payload)); // subscribe to room:1 / msg
room.send('msg', { text: 'hello' });
// wrote: {"channel":"room:1","event":"msg","payload":{"text":"hello"}}

mux.channel('room:1') === room; // true — same name, same channel
// Test: inject a fake socket and drive both directions by hand — no real network.
const socket = { sent: [], onmessage: null, send(d) { this.sent.push(d); } };
const mux = socketChannelMultiplexing(socket);

const seen = [];
mux.channel('room:2').on('msg', (p) => seen.push(p));

// simulate the server delivering a frame for room:2
socket.onmessage({ data: JSON.stringify({ channel: 'room:2', event: 'msg', payload: 7 }) });
// seen is now [7]; a frame tagged room:1 would have left `seen` empty (isolation)

Notes

  • Injected socket — build everything around the passed-in socket. Read inbound frames by assigning socket.onmessage, and write outbound frames with socket.send. Never construct a real socket yourself; that is what keeps the multiplexer testable.
  • Envelope — serialize each outbound message as JSON.stringify({ channel, event, payload }), and JSON.parse each inbound frame's data. The channel id is what routing keys on.
  • Same name, same channelchannel(name) is idempotent: repeated calls with one name return the same handle, so different parts of the app share a channel instead of each getting a private copy.
  • Isolation — an inbound frame is delivered only to handlers on the matching channel and event. A room:1 frame must never fire a room:2 handler, and a msg frame must never fire a typing handler. A frame for an unknown channel, or a non-JSON payload, is dropped silently — never thrown.
  • Many listeners — several handlers can share one channel and event and all fire; on returns an unsubscribe function, and off removes a single handler.
  • Leaveleave() removes the channel from the routing table so it receives nothing more, and sends a leave control frame. Afterward, channel(name) returns a fresh subscription.

FAQ

Why does the multiplexer take an injected socket instead of creating its own WebSocket?
Passing in a socket-like object with send() and onmessage lets tests drive it with a fake — asserting the exact frame written and simulating inbound frames by hand — with no real network. In production you hand it a real WebSocket; the routing logic never changes.
How are two channels kept isolated over a single connection?
Every frame on the wire carries a channel id in its envelope. The multiplexer keeps a routing table keyed by channel, so an inbound frame is dispatched only to the handlers of the channel it names. A message on room:1 can never reach a room:2 handler.
What happens to a frame for a channel nobody has joined?
It is dropped silently. The router looks the channel up in its table, finds nothing, and returns without throwing — so a stray or late frame for a left channel is a no-op rather than a crash.
Loading editor…