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.
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
};
// 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)
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.JSON.stringify({ channel, event, payload }), and JSON.parse each inbound frame's data. The channel id is what routing keys on.channel(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.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.on returns an unsubscribe function, and off removes a single handler.leave() 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.