From fba1f0b87d459d3bd0c6cf3a45144e5866b6e5ad Mon Sep 17 00:00:00 2001 From: Jannis Braun <151788261+TheZwiss@users.noreply.github.com> Date: Sat, 25 Apr 2026 19:20:07 +0200 Subject: [PATCH] fix(social-client): lowercase parsed @domain in sendFriendRequest Hostnames are case-insensitive (RFC 4343), and both right-hand sides of the routing comparisons (window.location.host and URL.host) are already canonical lowercase. The user-typed domain substring was compared with strict ===, so ORBIT.ddns.net failed to match an existing connected peer and popped a spurious Connect Instance modal. Normalize at parse time. --- .../stores/socialStore.sendRequest.test.ts | 87 +++++++++++++++++++ packages/web/src/stores/socialStore.ts | 3 +- 2 files changed, 89 insertions(+), 1 deletion(-) create mode 100644 packages/web/src/stores/socialStore.sendRequest.test.ts diff --git a/packages/web/src/stores/socialStore.sendRequest.test.ts b/packages/web/src/stores/socialStore.sendRequest.test.ts new file mode 100644 index 00000000..4dc3ef7f --- /dev/null +++ b/packages/web/src/stores/socialStore.sendRequest.test.ts @@ -0,0 +1,87 @@ +import { describe, it, expect, beforeEach, vi } from 'vitest'; + +const homeSendRequest = vi.fn(async () => ({ success: true, requestId: 'req-home' })); +const remoteSendRequest = vi.fn(async () => ({ success: true, requestId: 'req-remote' })); +const homeRequests = vi.fn(async () => []); +const remoteRequests = vi.fn(async () => []); + +vi.mock('../api/client', () => ({ + api: { + social: { + sendRequest: (...args: unknown[]) => homeSendRequest(...args), + requests: () => homeRequests(), + }, + }, +})); + +const remoteApi = { + social: { + sendRequest: (...args: unknown[]) => remoteSendRequest(...args), + requests: () => remoteRequests(), + }, +}; + +vi.mock('./instanceStore', () => ({ + useInstanceStore: { + getState: () => ({ + instances: [ + { + origin: 'https://orbit.ddns.net', + status: 'connected', + api: remoteApi, + }, + ], + }), + }, +})); + +vi.mock('../utils/assetUrls', () => ({ + normalizeUserAssets: (u: unknown) => u, +})); + +import { useSocialStore } from './socialStore'; + +describe('socialStore.sendFriendRequest — case-insensitive domain routing', () => { + beforeEach(() => { + homeSendRequest.mockClear(); + remoteSendRequest.mockClear(); + homeRequests.mockClear(); + remoteRequests.mockClear(); + // window.location.host in jsdom defaults to 'localhost:3000' or similar. + // Override it for routing tests. + Object.defineProperty(window, 'location', { + configurable: true, + value: { ...window.location, host: 'local.test', hostname: 'local.test' }, + }); + }); + + it('sends to the home API when the typed domain matches window.location.host exactly', async () => { + await useSocialStore.getState().sendFriendRequest('bob@local.test'); + expect(homeSendRequest).toHaveBeenCalledWith('bob'); + expect(remoteSendRequest).not.toHaveBeenCalled(); + }); + + it('sends to the home API when the typed domain matches with mixed case', async () => { + await useSocialStore.getState().sendFriendRequest('bob@LOCAL.TEST'); + expect(homeSendRequest).toHaveBeenCalledWith('bob'); + expect(remoteSendRequest).not.toHaveBeenCalled(); + }); + + it('routes to a connected remote instance when the typed domain matches its origin host', async () => { + await useSocialStore.getState().sendFriendRequest('bob@orbit.ddns.net'); + expect(remoteSendRequest).toHaveBeenCalledWith('bob'); + expect(homeSendRequest).not.toHaveBeenCalled(); + }); + + it('routes to a connected remote instance when the typed domain has mixed case', async () => { + await useSocialStore.getState().sendFriendRequest('bob@ORBIT.ddns.net'); + expect(remoteSendRequest).toHaveBeenCalledWith('bob'); + expect(homeSendRequest).not.toHaveBeenCalled(); + }); + + it('sends bare handle (no @) directly to the home API', async () => { + await useSocialStore.getState().sendFriendRequest('bob'); + expect(homeSendRequest).toHaveBeenCalledWith('bob'); + expect(remoteSendRequest).not.toHaveBeenCalled(); + }); +}); diff --git a/packages/web/src/stores/socialStore.ts b/packages/web/src/stores/socialStore.ts index 419fc02a..d2bef9b8 100644 --- a/packages/web/src/stores/socialStore.ts +++ b/packages/web/src/stores/socialStore.ts @@ -218,7 +218,8 @@ export const useSocialStore = create((set, get) => ({ res = await api.social.sendRequest(username); } else { const baseName = username.slice(0, atIndex); - const domain = username.slice(atIndex + 1); + // Hostnames are case-insensitive; comparison RHS is canonical lowercase. + const domain = username.slice(atIndex + 1).toLowerCase(); // Check if domain matches home instance if (domain === window.location.host) {