Skip to content

Commit 64f0445

Browse files
ggazzoclaude
andcommitted
fix(ddp-streamer): use graceful ws.close() on user.forceLogout
The microservices ddp-streamer was using ws.terminate() (TCP RST) for broadcast force-logouts. The monolith path uses session.connectionHandle.close() which is graceful and flushes the WS buffer first — letting the `notify-user/<uid>/force_logout` stream message (queued by apps/meteor/server/modules/listeners/listeners.module.ts:49) reach the client before the socket goes down. In EE that stream message races with the terminate, terminate wins, the client's useForceLogout hook never fires, and tests like e2e-encryption/e2ee-passphrase-management.spec.ts:87 are left with stale localStorage credentials and a Login button that never hides. Switch to ws.close() with a 5s setTimeout fallback to terminate() for unresponsive sockets — matches the graceful-close semantics the monolith already relies on without losing the safety net for zombies. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 5e5ec97 commit 64f0445

1 file changed

Lines changed: 17 additions & 1 deletion

File tree

ee/apps/ddp-streamer/src/DDPStreamer.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,23 @@ export class DDPStreamer extends ServiceClass {
6060
return;
6161
}
6262
if (client?.userId === uid) {
63-
ws.terminate();
63+
// Graceful close: lets the WS lib flush queued frames (including
64+
// the `notify-user/<uid>/force_logout` stream message that the
65+
// monolith listener at apps/meteor/server/modules/listeners/listeners.module.ts:49
66+
// just enqueued) before the socket goes down. Previously this was
67+
// `ws.terminate()`, which sends a TCP RST immediately and drops
68+
// the queued frames — clients depending on the stream message
69+
// (useForceLogout hook → Accounts._unstoreLoginToken + setUserId(null))
70+
// then never see the cleanup, leaving stale credentials in
71+
// localStorage. Falls back to terminate() after a short grace
72+
// period for unresponsive sockets.
73+
ws.close();
74+
const guard = setTimeout(() => {
75+
if (ws.readyState !== ws.CLOSED) {
76+
ws.terminate();
77+
}
78+
}, 5000);
79+
ws.once('close', () => clearTimeout(guard));
6480
}
6581
});
6682
});

0 commit comments

Comments
 (0)