chore(@rocket.chat/ddp-client): introduce experimental Meteor independent DDP client
[!NOTE]
This is an intermediate step towards completely removing the use of the meteor client (auth, oauth, ddp, tracker, etc.), all this code must be removed at once after we have migrated everything to the SDK, so even if not everything is perfect or makes complete sense, it should only last a short time. That's why it's under a flag, if the original behavior is affected we must revert everything and adjust before reinserting
Summary
Migrates the frontend DDP transport from Meteor's WebSocket to our own
@rocket.chat/ddp-client SDK, gated behind a runtime flag for staged
rollout. The migration ships dormant by default — legacy Meteor DDP
stays the active transport — and can be opted into per-tab, per-user,
or per-workspace. Meteor Accounts stays as the auth anchor.
Runtime flag — Use_RC_SDK
Three sources resolve the flag in order; default off if none match:
- URL parameter
?sdk_transport=on|off— per-tab override. rc-config-sdk_transportin localStorage — persisted per-user.<meta name="rc-sdk-transport-enabled">injected from the new
Use_RC_SDKadmin setting (General → Use Rocket.Chat SDK) — global
opt-in / kill-switch.
apps/meteor/client/lib/sdk/sdkTransportEnabled.ts is the single
source of truth; the rest of the codebase reads it once at module init
and dispatches accordingly.
What the flag gates
When the flag is off, getDdpSdk() returns a Meteor-backed
pass-through proxy (apps/meteor/client/lib/sdk/meteorBackedSdk.ts)
with the same DDPSDK shape — client.subscribe ↦
Meteor.connection.subscribe, client.callAsync ↦
Meteor.callAsync, account.uid ↦ Meteor.userId(),
connection.status derived from Meteor.status() via Tracker autorun.
Consumers (ServerProvider, presence.ts, SDKClient, the override
modules) keep calling getDdpSdk() unconditionally and behave like
develop without any per-call-site flag checks. No second WebSocket is
opened, no auth lifecycle runs, no Presence session is duplicated
server-side.
When the flag is on, getDdpSdk() returns the real DDPSDK
instance and the five transport overrides install:
ddpSdkCollectionBridge— re-feeds DDPSDK collection frames into
Meteor's_streamHandlers.onMessage.subscribeViaSDK— routesMeteor.connection.subscribethrough
sdk.client.subscribe.stubMeteorStream— replacesMeteor.connection._streamwith a
stub that forwards outbound frames through the SDK socket.killMeteorStream— drains revival/quiescence bookkeeping and
outstanding-method blocks on logout.ddpOverREST— wrapsMeteor.connection._sendso non-bypassed
methods route via REST. Always on (matches develop's behavior),
not gated by the flag — the resume-onlyloginhandler in
ddp-streamer-service403s every other login shape, and routing via
REST is the only way the form login flow works in microservices.
Other always-on changes
These are server-side or package-internal fixes that apply regardless
of the flag:
ee/apps/ddp-streamer/src/DDPStreamer.ts—ws.close()(graceful)
onuser.forceLogoutinstead ofws.terminate(), with a 5s fallback
to terminate. Lets thenotify-user/<uid>/force_logoutstream
message reach the client before the socket goes down.packages/ddp-client/src/Connection.ts— idempotent
connect()/reconnect(),retryCountreset on successful connection,
stalews.oncloseignored when a newer ws has taken over, retry
timer re-checks status before firing.packages/ddp-client/src/DDPDispatcher.ts— non-method frames
bypass the wait queue soconnect/sub/pingaren't queued behind
a waitloginblock.
Boot-time flag-gated bits
ddpSdk.tsside-effect block (loginWithToken auth-error wrap,
userIdStore subscriber,ensureConnectedAndAuthenticatedboot path).app/notifications/client/lib/Presence.ts— only registers the
user-presence streamer on the SDK socket when the flag is on. With
flag off, onlyMeteor.connection's streamer runs.loggedIn.ts—whenLoggedIn/onLoggedInuse both
Accounts.onLoginand auserIdStorebridge when flag on (REST-
routed login can't fillMeteor.users); with flag off they match
develop'sAccounts.onLogin-only behavior.useForceLogout.ts— only clears local credentials manually when
flag on; with flag off the legacy WS-reconnect-then-relogin chain
handles cleanup.startup.ts—runUserDataSyncawaits SDK auth before
synchronizeUserDataonly when flag on.
Test plan
- TypeScript passes across the meteor workspace.
- Local: flag on (?sdk_transport=on) — login, logout,
account-manage-devices, admin-room, account-profile all pass on
monolith. - Local: flag off (default) — same suites pass on monolith
(bit-for-bit develop behavior). - CI: full E2E + API + Unit green.
- Manual (flag on): inspect DevTools → Network → WS. Two sockets
should be open after login — Meteor's stays present as the
Accounts anchor, DDPSDK carries every method/subscription. - Manual (flag on): login with password, logout, SAML login, LDAP
login still work end-to-end. Session resume on reload goes
straight into the app. - Manual (flag on): user-presence updates from another tab appear
in near-realtime. - Manual (flag on): ConnectionStatusBar reflects DDPSDK outages —
disable the DDPSDK socket via DevTools and verify the offline
banner appears. - Manual (flag off): the workspace behaves exactly like develop —
no SDK socket opens, no proxy WebSocket activity.
Task: ARCH-2127
Summary by CodeRabbit
-
New Features
- Added experimental "Use Rocket.Chat SDK" setting in workspace administration. Browser restart required after toggling this feature.
-
Bug Fixes
- Improved graceful shutdown handling during forced user logout.
-
Tests
- Enhanced transport-agnostic message delivery validation in E2E tests.