Revert ddp-server (3.1.3→3.1.2) and socket-stream-client (0.6.2→0.6.1)
version bumps per reviewer request. Add server-side tests covering both
SockJS and raw WebSocket transports: DDP connectivity, method calls,
HTTP rejection on /websocket, /sockjs/info availability, and connection
shape validation.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Context:
PR #12007 (Meteor 2.7.2) introduced the DISABLE_SOCKJS env var but only
implemented the client side: switching new SockJS() → new WebSocket().
The server continued to create a SockJS server, install HTTP handlers,
and serve /sockjs/info. The flag was effectively a half-measure.
This has been a recurring community pain point:
- forums.meteor.com/t/state-of-sockjs-in-meteor/51841 — SockJS is a
copy-fork of v0.3.4 from 2012, bundled unconditionally (57 KB)
- Previous attempts at conditional loading (PRs #9353, #9985) were
reverted due to dynamic-import issues
- PR #14190 proposes uWebSockets as alternative, but adds a new runtime
dependency and a separate port — our approach is simpler
What this commit does:
1. Server (ddp-server/stream_server.js):
- Extract _onConnection() into a shared method (was inline in constructor)
- Move SockJS setup into _setupSockJS() (code moved verbatim)
- Add _setupRawWebSocket() for DISABLE_SOCKJS=1 mode:
* Uses faye-websocket (already a transitive dep via SockJS) to handle
WebSocket upgrades on /websocket
* RawWebSocketConnection class wraps faye-websocket with the same
interface as SockJS connections (headers, send/write/close,
setWebsocketTimeout, _session.recv.connection, EventEmitter events)
* Takes over 'upgrade' listeners (same pattern as SockJS's
overshadowListeners) and delegates non-/websocket upgrades to
existing handlers (HMR, etc.)
* Rejects plain HTTP to /websocket with 400 "Not a valid websocket
request" (same behavior as SockJS mode)
- Guard setWebsocketTimeout and send with if(!socket.X) checks so
RawWebSocketConnection's own implementations aren't overwritten
2. Client (socket-stream-client/browser.js):
- Replace static `import SockJS from "./sockjs-1.6.1-min-.js"` with
dynamic `import()` inside _launchConnection()
- SockJS (57 KB) is now a lazy chunk, only fetched when needed
- When DISABLE_SOCKJS=1: native WebSocket, zero SockJS code loaded
- _launchConnection() becomes async (fire-and-forget from constructor
and _retryNow — safe, no callers await it)
3. Dependencies (ddp-server/package.js):
- Add faye-websocket 0.11.4 as explicit Npm dependency (was already
a transitive dep of sockjs; now needed directly for raw WS mode)
What is NOT changed:
- Default behavior (without DISABLE_SOCKJS) is 100% identical
- No code modernization on untouched lines (var style preserved)
- No test modifications — all 44 existing tinytests pass in both modes
- register(), all_sockets(), _redirectWebsocketEndpoint() untouched
Benchmarks (production build, Chrome, localhost):
DDP connection time:
- Hard refresh / first load: ~300ms WITH vs ~253ms WITHOUT
(modest difference — both must load and parse all JS)
- Warm refreshes: ~300ms WITH vs ~150ms WITHOUT — 2x faster
The 300ms floor WITH SockJS is structural: SockJS always makes
a /sockjs/info XHR before opening the WebSocket. This roundtrip
happens on every connection, even with cached assets. Without
SockJS, the WebSocket connects directly — no pre-flight, no
transport negotiation.
Bundle size:
- Main JS bundle: 152 KB raw (identical in both modes)
- SockJS dynamic chunk: 56 KB raw / ~16 KB gzip — only loaded
in default mode, completely absent with DISABLE_SOCKJS=1
- Net savings: 1 fewer JS chunk + 1 fewer XHR (/sockjs/info)
Network tab (WITH → WITHOUT):
- /sockjs/info?t=... XHR (0.4 KB) → gone
- /sockjs/010/<session>/websocket → /websocket (direct)
- dynamic-import fetch for SockJS chunk → gone
WITH SockJS (10 runs): 300, 300, 301, 300, 300, 300, 302, 300, 300, 300 ms
WITHOUT SockJS (11 runs): 253, 151, 150, 150, 145, 150, 153, 188, 163, 149, 164 ms
Tested:
- Tinytests: 44/44 pass WITH SockJS, WITHOUT SockJS,
and WITHOUT + ROOT_URL_PATH_PREFIX
- HMR: works correctly in DISABLE_SOCKJS mode
- Fresh Blaze apps: clean operation in both modes, clean Network tab
Note: package.js version bumps (ddp-server 3.0.3→3.1.3,
socket-stream-client 0.5.3→0.6.2) are provisional — core team
should verify and adjust for the target release.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>