fix(presence): reset stale users.status on boot; drop REST-login online write
users.status was only flipped back to offline by the WebSocket disconnect path (5s grace timer in ConnectionManager). Process exits (deploy/crash/OOM) lose those in-memory timers, freezing any non-offline row at its last value and making the user appear permanently online to friends and space co-members. Confirmed in production on the Pi instance: a user appeared online for ~3 days with no live socket. Add resetStalePresenceOnBoot() in utils/presenceBoot.ts and call it from index.ts after getDb()/seedDatabase() and before WebSocket route registration. The reset is federation-safe: it only updates rows where home_instance IS NULL (replicated stubs are projections of remote presence and must not be stomped) and is_deleted = 0 (tombstoned users are excluded from broadcasts). Also remove the redundant status='online' write from POST /api/auth/login. A successful REST login does not imply a live socket; the WS auth handshake is the single source of truth. Login alone could otherwise produce the same stuck-online row when a client logs in and never establishes a WS. Tests cover: locally-homed online/idle/dnd reset, replicated rows untouched, tombstoned rows untouched, idempotence, mixed populations. Updates docs/systems/activity-presence.md (Connect/Disconnect Flow, new Boot Reset section) and docs/systems/auth.md (login no longer mutates status).
This commit is contained in:
@@ -308,13 +308,18 @@ export async function authRoutes(app: FastifyInstance): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
db.update(schema.users).set({ status: 'online' }).where(eq(schema.users.id, user.id)).run();
|
||||
|
||||
// Note: status='online' is set exclusively by the WebSocket auth path
|
||||
// (ws/handler.ts). A successful REST /login does not by itself imply a
|
||||
// live connection — the client may never establish a WS (transient
|
||||
// network failure, mobile background, error path), which would otherwise
|
||||
// produce a permanently stuck-online row that no disconnect timer can
|
||||
// clean up. The user's reported status remains whatever it was; the WS
|
||||
// handshake will flip it to 'online' once a real socket attaches.
|
||||
const token = signJwt({ userId: user.id, username: user.username });
|
||||
|
||||
const response: AuthResponse = {
|
||||
token,
|
||||
user: sanitizeUser({ ...user, status: 'online' }, true),
|
||||
user: sanitizeUser(user, true),
|
||||
};
|
||||
|
||||
return reply.code(200).send(response);
|
||||
|
||||
Reference in New Issue
Block a user