feat: add user_updated broadcast to federation identity delete, use shared helper
Switch from manual space-ID collection to collectDeletionBroadcastTargets and add user_updated broadcast so clients patch their caches when a federated user is deleted via S2S. Force-disconnect moved after broadcasts so other tabs receive events before the connection is torn down.
This commit is contained in:
@@ -10,7 +10,7 @@ import { connectionManager } from '../ws/handler.js';
|
|||||||
import type { FederatedCallEntry, DmRoomMeta } from '../ws/handler.js';
|
import type { FederatedCallEntry, DmRoomMeta } from '../ws/handler.js';
|
||||||
import { sanitizeUser } from '../utils/sanitize.js';
|
import { sanitizeUser } from '../utils/sanitize.js';
|
||||||
import { deleteAttachmentFiles } from '../utils/fileCleanup.js';
|
import { deleteAttachmentFiles } from '../utils/fileCleanup.js';
|
||||||
import { tombstoneUser } from '../utils/userDeletion.js';
|
import { tombstoneUser, collectDeletionBroadcastTargets } from '../utils/userDeletion.js';
|
||||||
import { computeFederatedId, getDmParticipants, sendCallRelay } from '../utils/federationOutbox.js';
|
import { computeFederatedId, getDmParticipants, sendCallRelay } from '../utils/federationOutbox.js';
|
||||||
import { getDmMessageWithUser } from './dm.js';
|
import { getDmMessageWithUser } from './dm.js';
|
||||||
import type { FederationRelayRequest, FederationRelayResponse, FederationRelayEvent, FederationRelayAttachment, FederationSyncRequest, FederationSyncResponse, DmMessageWithUser, FederationRelayProfileSnapshot, FederationIdentityDeleteS2SRequest } from '@backspace/shared';
|
import type { FederationRelayRequest, FederationRelayResponse, FederationRelayEvent, FederationRelayAttachment, FederationSyncRequest, FederationSyncResponse, DmMessageWithUser, FederationRelayProfileSnapshot, FederationIdentityDeleteS2SRequest } from '@backspace/shared';
|
||||||
@@ -740,12 +740,8 @@ export async function federationRoutes(app: FastifyInstance): Promise<void> {
|
|||||||
return reply.code(409).send({ error: 'owns_spaces', ownedSpaces, statusCode: 409 });
|
return reply.code(409).send({ error: 'owns_spaces', ownedSpaces, statusCode: 409 });
|
||||||
}
|
}
|
||||||
|
|
||||||
// 6. Collect spaces for broadcast BEFORE deletion removes memberships
|
// 6. Collect broadcast targets BEFORE deletion removes memberships
|
||||||
const memberSpaceIds = db.select({ spaceId: schema.spaceMembers.spaceId })
|
const { memberSpaceIds, targetUserIds } = collectDeletionBroadcastTargets(user.id);
|
||||||
.from(schema.spaceMembers)
|
|
||||||
.where(eq(schema.spaceMembers.userId, user.id))
|
|
||||||
.all()
|
|
||||||
.map(m => m.spaceId);
|
|
||||||
|
|
||||||
// 7. Execute deletion
|
// 7. Execute deletion
|
||||||
const filesToDelete = tombstoneUser(user.id, { purgeContent: mode === 'full' });
|
const filesToDelete = tombstoneUser(user.id, { purgeContent: mode === 'full' });
|
||||||
@@ -753,10 +749,7 @@ export async function federationRoutes(app: FastifyInstance): Promise<void> {
|
|||||||
// 8. Clean up files from disk
|
// 8. Clean up files from disk
|
||||||
deleteAttachmentFiles(filesToDelete.map(f => ({ filename: f })));
|
deleteAttachmentFiles(filesToDelete.map(f => ({ filename: f })));
|
||||||
|
|
||||||
// 9. Force-disconnect WS if somehow still connected (unlikely but safe)
|
// 9. Broadcast member_left to other connected clients for each space
|
||||||
connectionManager.forceDisconnectUser(user.id);
|
|
||||||
|
|
||||||
// 10. Broadcast member_left to other connected clients for each space
|
|
||||||
for (const spaceId of memberSpaceIds) {
|
for (const spaceId of memberSpaceIds) {
|
||||||
connectionManager.sendToSpace(spaceId, {
|
connectionManager.sendToSpace(spaceId, {
|
||||||
type: 'member_left',
|
type: 'member_left',
|
||||||
@@ -765,6 +758,19 @@ export async function federationRoutes(app: FastifyInstance): Promise<void> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 10. Broadcast user_updated with sanitized deleted user data
|
||||||
|
const deletedRow = db.select().from(schema.users).where(eq(schema.users.id, user.id)).get();
|
||||||
|
if (deletedRow) {
|
||||||
|
const deletedUser = sanitizeUser(deletedRow);
|
||||||
|
const userUpdatedEvent = { type: 'user_updated' as const, user: deletedUser };
|
||||||
|
for (const uid of targetUserIds) {
|
||||||
|
connectionManager.sendToUser(uid, userUpdatedEvent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 11. Force-disconnect WS if somehow still connected (unlikely but safe)
|
||||||
|
connectionManager.forceDisconnectUser(user.id);
|
||||||
|
|
||||||
console.log(`[federation] Identity deleted for user ${user.id} (${user.username}) via S2S from ${fedHeaders.origin}, mode=${mode}`);
|
console.log(`[federation] Identity deleted for user ${user.id} (${user.username}) via S2S from ${fedHeaders.origin}, mode=${mode}`);
|
||||||
|
|
||||||
return reply.code(200).send({ success: true });
|
return reply.code(200).send({ success: true });
|
||||||
|
|||||||
Reference in New Issue
Block a user