From d88d369a82893cf2c7ace38f12f4b0e50126d60d Mon Sep 17 00:00:00 2001 From: Jannis Braun <151788261+TheZwiss@users.noreply.github.com> Date: Sat, 25 Apr 2026 22:18:47 +0200 Subject: [PATCH] fix(social): friend_request_sent local broadcast must carry target profile MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The local branch was reusing friendRequestPayload (built for friend_request_received with user=sender) for the sender-side friend_request_sent broadcast. Tab B on the sender would render the sender's own avatar where the target's should appear. Match the federated branch — sent broadcast carries target. --- .../server/src/routes/social.federated.test.ts | 17 ++++++++++------- packages/server/src/routes/social.test.ts | 4 ++++ packages/server/src/routes/social.ts | 18 ++++++++++++++---- 3 files changed, 28 insertions(+), 11 deletions(-) diff --git a/packages/server/src/routes/social.federated.test.ts b/packages/server/src/routes/social.federated.test.ts index f9fe3ad4..82643963 100644 --- a/packages/server/src/routes/social.federated.test.ts +++ b/packages/server/src/routes/social.federated.test.ts @@ -161,14 +161,17 @@ describe('POST /api/social/requests — federated branch (happy path)', () => { .get(); expect(mutationRow).toBeTruthy(); - // WS broadcast to sender - expect(sendToUser).toHaveBeenCalledWith( - CALLER_ID, - expect.objectContaining({ - type: 'friend_request_sent', - request: expect.objectContaining({ id: body.requestId }), - }), + // WS broadcast to sender — the 'user' field must carry the TARGET's profile (alice), + // not the sender. This is the federated analogue of the local-branch invariant. + const sentEvent = sendToUser.mock.calls.find( + (c: unknown[]) => (c[1] as { type?: string })?.type === 'friend_request_sent', ); + expect(sentEvent).toBeDefined(); + expect(sentEvent![0]).toBe(CALLER_ID); + expect(sentEvent![1].request.id).toBe(body.requestId); + // homeUserId identifies the target; username is the canonical stub form (@). + expect(sentEvent![1].request.user.homeUserId).toBe('remote-alice'); + expect(sentEvent![1].request.user.username).toBe('remote-alice@orbit.test'); }); }); diff --git a/packages/server/src/routes/social.test.ts b/packages/server/src/routes/social.test.ts index 4a8e1c0b..d123c2e7 100644 --- a/packages/server/src/routes/social.test.ts +++ b/packages/server/src/routes/social.test.ts @@ -236,5 +236,9 @@ describe('POST /api/social/requests — case-insensitive username lookup', () => const sent = sendToUser.mock.calls.find(c => c[1]?.type === 'friend_request_sent'); expect(sent).toBeDefined(); expect(sent![0]).toBe(CALLER_ID); + // The 'user' field on the sent payload must be the TARGET (alice), + // not the sender — symmetric with how the federated branch builds it. + expect(sent![1].request.user.id).toBe('u1'); + expect(sent![1].request.user.username).toBe('alice'); }); }); diff --git a/packages/server/src/routes/social.ts b/packages/server/src/routes/social.ts index 61f62f80..2db95d7a 100644 --- a/packages/server/src/routes/social.ts +++ b/packages/server/src/routes/social.ts @@ -88,8 +88,8 @@ async function handleLocalFriendRequest( createdAt: now, }).run(); - // Broadcast friend_request_received to the target user - const friendRequestPayload: FriendRequest = { + // Broadcast to the target: they need to know who sent the request (sender profile). + const receivedPayload: FriendRequest = { id, fromId: request.userId, toId: targetUser.id, @@ -98,14 +98,24 @@ async function handleLocalFriendRequest( user: sanitizeUser(sender), }; + // Broadcast to the sender's other tabs: they need to know who they added (target profile). + const sentPayload: FriendRequest = { + id, + fromId: request.userId, + toId: targetUser.id, + status: 'pending', + createdAt: now, + user: sanitizeUser(targetUser), + }; + connectionManager.sendToUser(targetUser.id, { type: 'friend_request_received', - request: friendRequestPayload, + request: receivedPayload, }); connectionManager.sendToUser(request.userId, { type: 'friend_request_sent', - request: friendRequestPayload, + request: sentPayload, }); // Federation relay: notify the target user's home instance