Skip to main content

Differences from real socket.io

TL;DR The short list of where smocket and real socket.io do not line up: the places smocket deliberately diverges (section A), the API smocket adds that socket.io has no equivalent for (section B), and the gaps that are nobody's decision and are waiting to be closed (section C). A section A entry links to the decision that explains it; the reasoning lives there, not here.

This page exists because of how much else matches. A mock that answers correctly almost everywhere gives a reader no reason to keep checking, and the reader who has stopped checking is the one a divergence reaches. The closer the fidelity gets, the more the remaining gaps depend on being written down. What to keep doubting is read here rather than discovered in a failing suite.

A. Where smocket deliberately differs

  • No retry when the server is absent. Real socket.io retries a failed connection forever; smocket reports the failure once and stops. This is the one intentional behavioural divergence. See 0005.
  • A console.error alongside connect_error. smocket logs a missing-server failure to the console; real socket.io does not. See 0005.
  • No delay before reporting an absent server. smocket fires connect_error on the next tick with no network wait, because a round-trip delay has no source in a mock. See 0005.
  • Server.close() always returns a promise. Socket.IO 4.7 returns void, while 4.8 returns Promise<void>. The shared contract accepts either result; smocket exposes the 4.8 promise so its in-memory teardown can be awaited. See 0020.
  • Handshake headers, address, xdomain, and secure are left unset. These describe a real transport a mock does not have, so smocket leaves them rather than invent values. See 0006.
  • handshake.query fidelity is scalar-only. smocket stringifies each query value the way a real querystring does ({ room: 1 } -> { room: '1' }), matching real socket.io for scalar values. Array or object query values are coerced with String(...) and are not guaranteed to match real socket.io's encoding: that edge has no measured reference, so smocket does not invent one. See 0006.
  • A captured socket.rooms Set is emptied on disconnect. Real socket.io returns a new empty Set after disconnect while a reference captured beforehand retains its old rooms. smocket clears the captured Set in place. See 0013 and 0025.

B. What smocket adds that socket.io has no equivalent for

  • server.connect(namespace, options) and server.nextConnection(namespace). Neither is a socket.io server API. Together they form Smocket's direct connection API. connect opens the client without an origin-registry lookup, and nextConnection resolves with its admitted server-side Socket. Once the namespace exists, the per-namespace queues accept either call first and preserve FIFO order. Named static namespaces are established through of() or an earlier nextConnection(), while connect() alone rejects an unregistered name. Closing the server discards unclaimed sockets and rejects pending or later observers. See 0030.
  • io.adapter(factory) registers a smocket adapter. socket.io has io.adapter(...) too, but its adapter also delivers and needs a transport smocket lacks, so the two are not signature-compatible: a custom adapter written for smocket does not run on real socket.io. A smocket adapter changes the routing decision (which sockets a broadcast targets); delivery stays in the core unless the adapter opts into the optional scheduleDelivery(sid, deliver) hook (see the delay affordance below and 0018). Registration is setup-only, every namespace gets a fresh instance, and the optional removeSocket(sid) hook observes whole-socket cleanup without adding upstream delAll. See adapter-registration.md and 0031.
  • DelayingAdapter delays what a socket's client receives, by sid. Not a socket.io API. It rides the adapter registration above to hold a socket's client-inbound stream (server -> client) by a per-sid amount, so a race-condition test can interleave events across sockets deterministically; the server side still receives its client's emits on the next tick. Order within the delayed stream is preserved, and scheduling runs through an injectable timer so a test drives it with fake timers rather than the wall clock. Actual socket removal drains queued delivery in order and clears the sid state. See 0018 and 0031.
  • TracingAdapter records final broadcast routing decisions. It stores one immutable, payload-free trace per successful concrete-namespace broadcast after exclusions and volatile filtering. Empty-recipient broadcasts are recorded, while direct Socket emits, reserved events, and encoding failures are not. It can wrap another Smocket adapter so tracing does not replace custom routing, scheduling, or cleanup. See 0032.
  • DroppingAdapter removes selected sids from broadcast delivery. The deterministic filter runs after normal routing and volatile selection and excludes dropped recipients from new acknowledgement collection. Direct Socket traffic and membership are unchanged. Real socket.io has no corresponding built-in adapter. See 0036.

C. Known gaps, not deliberate, recorded until corrected

Section A is where smocket chose to differ and section B is what it adds. This section is neither. These are places smocket does not match socket.io and no one decided that it should not, so they carry no decision record and are expected to disappear.

The distinction is not only editorial. Removing a section A entry is a major under 0019, because it withdraws a promise the project made on purpose. Closing one of these withdraws nothing. It is a correction toward measured real behaviour and takes that row instead, so the same fix does not change bump depending on which list it was written on.