fix(presence): broadcast presence_update to friends + DM members + space members
Six WS sites that previously broadcast presence_update to spaces only now use collectProfileBroadcastTargetIds (the same recipient set as user_updated): - ws/handler.ts finalizeDisconnect (offline) - ws/handler.ts auth path (online) - ws/events.ts handlePresenceUpdate (manual idle/dnd/online) - ws/events.ts handleActivityUpdate (rich activity changes) - routes/users.ts showActivity-toggle clear - routes/users.ts status PATCH Friends with no shared space + DM-only co-members now see each other's online/offline transitions live, matching user_updated semantics. Federated stub presence broadcasts (Task B3) use the same helper, so cross-instance recipients are uniform. Updates one assertion in social.federated.test.ts that asserted the old snowflake-style stub username (now realname-based per A1).
This commit is contained in:
@@ -21,6 +21,7 @@ import type {
|
||||
Activity,
|
||||
} from '@backspace/shared';
|
||||
import { sanitizeUser } from '../utils/sanitize.js';
|
||||
import { collectProfileBroadcastTargetIds } from '../utils/userDeletion.js';
|
||||
|
||||
// ─── Heartbeat State ──────────────────────────────────────────────────────────
|
||||
const wsIsAlive: WeakMap<WebSocket, boolean> = new WeakMap();
|
||||
@@ -296,16 +297,18 @@ class ConnectionManager {
|
||||
this.userStatuses.delete(userId);
|
||||
this.lastActivityUpdate.delete(userId);
|
||||
|
||||
// Broadcast offline to all spaces
|
||||
const userSpaces = this.getUserSpaces(userId);
|
||||
for (const spaceId of userSpaces) {
|
||||
this.sendToSpace(spaceId, {
|
||||
type: 'presence_update',
|
||||
userId: userId,
|
||||
status: 'offline',
|
||||
activities: [] as Activity[],
|
||||
});
|
||||
}
|
||||
// Broadcast offline to friends + DM co-members + space co-members.
|
||||
// Mirrors collectProfileBroadcastTargetIds (the recipient set used by
|
||||
// user_updated). Two locally-friended users with no shared space now see
|
||||
// each other's offline transitions live, instead of being space-only.
|
||||
const offlinePayload = {
|
||||
type: 'presence_update' as const,
|
||||
userId,
|
||||
status: 'offline' as const,
|
||||
activities: [] as Activity[],
|
||||
};
|
||||
const offlineTargets = collectProfileBroadcastTargetIds(userId);
|
||||
for (const uid of offlineTargets) this.sendToUser(uid, offlinePayload);
|
||||
|
||||
// S2S: project offline to all active peers (mirrors profile_update fanout).
|
||||
// Imported lazily to avoid circular import (federationPresence → db → ws/handler).
|
||||
@@ -1669,15 +1672,10 @@ export async function registerWebSocket(app: FastifyInstance): Promise<void> {
|
||||
...readyData,
|
||||
}));
|
||||
|
||||
// Broadcast presence update to all spaces
|
||||
const userSpaces = connectionManager.getUserSpaces(userId);
|
||||
for (const spaceId of userSpaces) {
|
||||
connectionManager.sendToSpace(spaceId, {
|
||||
type: 'presence_update',
|
||||
userId,
|
||||
status: 'online',
|
||||
}, userId);
|
||||
}
|
||||
// Broadcast online to friends + DM co-members + space co-members.
|
||||
const onlinePayload = { type: 'presence_update' as const, userId, status: 'online' as const };
|
||||
const onlineTargets = collectProfileBroadcastTargetIds(userId);
|
||||
for (const uid of onlineTargets) connectionManager.sendToUser(uid, onlinePayload);
|
||||
|
||||
// S2S: project online to all active peers (mirrors profile_update fanout).
|
||||
const _uid = userId;
|
||||
|
||||
Reference in New Issue
Block a user