Skip to content

Commit 841c0d2

Browse files
committed
fix(sdk): replace broken Accounts.loggingIn gate with sdk.account poll
The previous `Accounts.loggingIn?.()` gate threw at runtime ('Cannot read properties of undefined reading _loggingIn') because it was called as a free reference and lost its `this` binding. The throw was caught silently by runUserDataSync's try/catch, which meant the gate never actually ran — boot timing happened to work because the microtask between the throw and synchronizeUserData's stream subscribe gave adopt enough time to populate sdk.account. Replace it with an explicit ~500ms poll on sdk.account.uid. If the Meteor-routed resume login (via stubMeteorStream → SDK socket → adopt) completes within the window, we short-circuit and avoid issuing a duplicate loginWithToken on the same socket — the duplicate causes a second Presence connection, the aggregate stays online instead of flipping to away, and the omnichannel idle/away tests fail. If adopt doesn't fire in time, fall back to our own loginWithToken (existing inflightLogin gate keeps it idempotent). Verified: login, account-login, e2ee-key-reset and the full omnichannel-rooms-forward suite pass locally.
1 parent 26cebbe commit 841c0d2

1 file changed

Lines changed: 11 additions & 19 deletions

File tree

apps/meteor/client/lib/sdk/ddpSdk.ts

Lines changed: 11 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -85,26 +85,18 @@ export const ensureConnectedAndAuthenticated = async (): Promise<void> => {
8585
return;
8686
}
8787

88-
// Wait for Meteor's own login (resume) flow to settle before issuing our
89-
// own loginWithToken. Meteor's resume goes through Connection._send →
90-
// stubMeteorStream → SDK socket and the response triggers
91-
// adoptAccountFromMeteorLoginResult which sets sdk.account.uid. If we
92-
// race ahead and call sdk.account.loginWithToken here, the SDK socket
93-
// receives TWO `login` method frames; ddp-streamer's Account.login
94-
// has no dedup, so each fires Accounts.onLogin → Presence.newConnection
95-
// → a duplicate connection in usersSessions. The duplicate stays
96-
// 'online' while the active one flips to 'away' on idle, and
97-
// processConnectionStatus prefers ONLINE over AWAY in the aggregate —
98-
// so auto-away never propagates and the navbar badge stays online.
99-
const accountsLoggingIn = (Accounts as unknown as { loggingIn?: () => boolean }).loggingIn;
100-
const start = Date.now();
101-
while (accountsLoggingIn?.() && Date.now() - start < 2000) {
102-
await new Promise<void>((resolve) => setTimeout(resolve, 50));
103-
if (sdk.account.uid) return;
88+
// Give Meteor's own login flow (resume routed through stubMeteorStream
89+
// + adoptAccountFromMeteorLoginResult) time to populate sdk.account
90+
// before we issue our own loginWithToken. If adopt fires first, we can
91+
// short-circuit and avoid sending a second login frame on the SDK
92+
// socket — which would otherwise create a duplicate Presence
93+
// connection (processConnectionStatus prefers ONLINE over AWAY in the
94+
// aggregate, breaking the auto-away flow). 500ms covers a single
95+
// server roundtrip in CI; if the stub-routed login hasn't completed by
96+
// then, fall back to issuing our own loginWithToken below.
97+
for (let i = 0; i < 20 && !sdk.account.uid; i++) {
98+
await new Promise<void>((resolve) => setTimeout(resolve, 25));
10499
}
105-
// One more microtask so the adopt callback (registered as ddp.onResult
106-
// in the stub) has a chance to fire ahead of us.
107-
await new Promise<void>((resolve) => setTimeout(resolve, 0));
108100
if (sdk.account.uid) {
109101
return;
110102
}

0 commit comments

Comments
 (0)