diff --git a/__tests__/hooks/useMarkWaveNotificationsRead.test.tsx b/__tests__/hooks/useMarkWaveNotificationsRead.test.tsx index 46fc65438c..5e1568a6cf 100644 --- a/__tests__/hooks/useMarkWaveNotificationsRead.test.tsx +++ b/__tests__/hooks/useMarkWaveNotificationsRead.test.tsx @@ -124,6 +124,7 @@ const createWrapper = const setActiveIdentity = ({ address, jwt, + connectedProfileId, activeProfileProxyId, activeProfileProxyCreatorId, jwtAddress, @@ -133,6 +134,7 @@ const setActiveIdentity = ({ }: { readonly address?: string | undefined; readonly jwt?: string | null | undefined; + readonly connectedProfileId?: string | null | undefined; readonly activeProfileProxyId?: string | null | undefined; readonly activeProfileProxyCreatorId?: string | null | undefined; readonly jwtAddress?: string | undefined; @@ -156,6 +158,7 @@ const setActiveIdentity = ({ useSeizeConnectContextMock.mockReturnValue({ address } as any); useAuthMock.mockReturnValue({ + connectedProfile: connectedProfileId ? { id: connectedProfileId } : null, activeProfileProxy: activeProfileProxyId ? { id: activeProfileProxyId, created_by: { id: proxyCreatorId } } : null, @@ -233,6 +236,63 @@ describe("useMarkWaveNotificationsRead", () => { }); }); + it("treats the connected profile's own JWT role as primary auth when no proxy is active", async () => { + const invalidateNotifications = jest.fn(); + + setActiveIdentity({ + address: "0xAAA", + jwt: "jwt-own-role", + connectedProfileId: "profile-1", + jwtRole: "profile-1", + }); + + const { result } = renderHook(() => useWaveNotificationsReadMarkerState(), { + wrapper: createWrapper(invalidateNotifications), + }); + + expect(result.current.proxyRoleIdentityKey).toBeNull(); + + await expect( + result.current.markWaveNotificationsRead("wave-own-role") + ).resolves.toBe("sent"); + + expect(apiPostMock).toHaveBeenCalledTimes(1); + expect(apiPostMock).toHaveBeenCalledWith({ + endpoint: "notifications/wave/wave-own-role/read", + headers: { Authorization: "Bearer jwt-own-role" }, + }); + }); + + it("does not treat a different JWT role as primary auth when no proxy is active", async () => { + const invalidateNotifications = jest.fn(); + + setActiveIdentity({ + address: "0xAAA", + jwt: "jwt-other-role", + connectedProfileId: "profile-1", + jwtRole: "creator-1", + }); + + const { result } = renderHook(() => useWaveNotificationsReadMarkerState(), { + wrapper: createWrapper(invalidateNotifications), + }); + + expect(result.current.proxyRoleIdentityKey).toBe( + getWaveReadProxyRoleIdentityKey({ + addressKey: "0xaaa", + proxyCreatorId: "creator-1", + }) + ); + + await expect( + result.current.markWaveNotificationsRead("wave-other-role", { + queueIfBlocked: false, + }) + ).resolves.toBe("skipped"); + + expect(apiPostMock).not.toHaveBeenCalled(); + }); + it("does not queue a read before the wallet address is known", async () => { const invalidateNotifications = jest.fn(); diff --git a/hooks/useMarkWaveNotificationsRead.helpers.ts b/hooks/useMarkWaveNotificationsRead.helpers.ts index 28930ae9a8..485596dc99 100644 --- a/hooks/useMarkWaveNotificationsRead.helpers.ts +++ b/hooks/useMarkWaveNotificationsRead.helpers.ts @@ -23,6 +23,7 @@ export type { export const useWaveNotificationsReadMarkerState = ({ address, + connectedProfileId, activeProfileProxyId, activeProfileProxyCreatorId, walletAuth, @@ -30,6 +31,7 @@ export const useWaveNotificationsReadMarkerState = ({ }: WaveNotificationsReadMarkerConfig): WaveNotificationsReadMarkerState => { const identityState = useWaveReadIdentityState({ address, + connectedProfileId, activeProfileProxyId, activeProfileProxyCreatorId, walletAuth, diff --git a/hooks/useMarkWaveNotificationsRead.identity.ts b/hooks/useMarkWaveNotificationsRead.identity.ts index 13880bb055..9179924c78 100644 --- a/hooks/useMarkWaveNotificationsRead.identity.ts +++ b/hooks/useMarkWaveNotificationsRead.identity.ts @@ -32,6 +32,7 @@ export interface WaveReadTemporaryProxyRoleIdentity { export interface WaveReadIdentityConfig { readonly address: string | undefined; + readonly connectedProfileId: string | null; readonly activeProfileProxyId: string | null; readonly activeProfileProxyCreatorId: string | null; readonly walletAuth: string | null; @@ -95,8 +96,30 @@ const getAuthHeaders = (walletAuth: string): AuthHeaders => ({ export const isWaveReadJwtExpired = (jwtExpiresAt: number): boolean => jwtExpiresAt <= Math.floor(Date.now() / 1000); +const normalizeWaveReadJwtRole = ({ + role, + connectedProfileId, + activeProfileProxyId, +}: { + readonly role: string | null | undefined; + readonly connectedProfileId: string | null; + readonly activeProfileProxyId: string | null; +}): string | null => { + if (typeof role !== "string" || role.length === 0) { + return null; + } + + if (activeProfileProxyId === null && role === connectedProfileId) { + return null; + } + + return role; +}; + const decodeWaveReadJwtIdentity = ( - walletAuth: string | null + walletAuth: string | null, + connectedProfileId: string | null, + activeProfileProxyId: string | null ): WaveReadJwtIdentity | undefined => { if (!walletAuth) { return undefined; @@ -120,10 +143,11 @@ const decodeWaveReadJwtIdentity = ( return { addressKey, - proxyCreatorId: - typeof decodedJwt.role === "string" && decodedJwt.role.length > 0 - ? decodedJwt.role - : null, + proxyCreatorId: normalizeWaveReadJwtRole({ + role: decodedJwt.role, + connectedProfileId, + activeProfileProxyId, + }), jwtExpiresAt: expiresAt, }; } catch { @@ -247,14 +271,20 @@ const getProxyRoleIdentityKey = ({ export const useWaveReadIdentityState = ({ address, + connectedProfileId, activeProfileProxyId, activeProfileProxyCreatorId, walletAuth, }: WaveReadIdentityConfig): WaveReadIdentityState => { const addressKey = getAddressKey(address); const jwtIdentity = useMemo( - () => decodeWaveReadJwtIdentity(walletAuth), - [walletAuth] + () => + decodeWaveReadJwtIdentity( + walletAuth, + connectedProfileId, + activeProfileProxyId + ), + [activeProfileProxyId, connectedProfileId, walletAuth] ); const verifiedAuthHeaders = useMemo( () => diff --git a/hooks/useMarkWaveNotificationsRead.ts b/hooks/useMarkWaveNotificationsRead.ts index e1b271815d..29a663632f 100644 --- a/hooks/useMarkWaveNotificationsRead.ts +++ b/hooks/useMarkWaveNotificationsRead.ts @@ -15,14 +15,16 @@ import { useContext } from "react"; export function useWaveNotificationsReadMarkerState(): WaveNotificationsReadMarkerState { const { invalidateNotifications } = useContext(ReactQueryWrapperContext); const { address } = useSeizeConnectContext(); - const { activeProfileProxy } = useAuth(); + const { activeProfileProxy, connectedProfile } = useAuth(); const activeProfileProxyId = activeProfileProxy?.id ?? null; const activeProfileProxyCreatorId = activeProfileProxy ? activeProfileProxy.created_by.id : null; + const connectedProfileId = connectedProfile?.id ?? null; return useWaveNotificationsReadMarkerStateFromConfig({ address, + connectedProfileId, activeProfileProxyId, activeProfileProxyCreatorId, walletAuth: getAuthJwt(),