Skip to content
File

Blob: src/client/lib/collab-identity.ts

typescript41 lines
1import { useMemo } from "react";
2import { useAuthStore } from "@/client/stores/auth-store";
3import { useWorkspaceMembers } from "@/client/stores/workspace-replica";
4import { useActivePageState } from "@/client/components/active-page/use-active-page";
5import { friendlyName } from "@/client/lib/friendly-name";
6import type { ResolveIdentity } from "@/client/lib/presence-identity";
7 
8export interface CollabIdentity {
9 userId: string | null;
10 resolveIdentity: ResolveIdentity;
11}
12 
13/**
14 * Derives collaboration identity for the active page. Returns the signed-in
15 * user's id (null when anonymous / share-token) and a memoized `resolveIdentity`
16 * that maps awareness userId+clientId to a display identity. Workspace members
17 * are the source of truth for names and avatars; share surfaces (where the
18 * viewer isn't a workspace member) fall back to a generated friendlyName.
19 *
20 * Memoization is keyed on the members array so `CanvasSurface`'s depsRef
21 * rebuild-avoidance stays intact — `resolveIdentity` only changes when members
22 * add or remove.
23 */
24export function useCollabIdentity(): CollabIdentity {
25 const userId = useAuthStore((s) => s.user?.id ?? null);
26 const state = useActivePageState();
27 const workspaceId = state.kind === "ready" ? state.snapshot.workspaceId : null;
28 const members = useWorkspaceMembers(workspaceId);
29 
30 const resolveIdentity = useMemo<ResolveIdentity>(() => {
31 const byId = new Map(members.map((m) => [m.user_id, m.user]));
32 return (lookupUserId, clientId) => {
33 const real = lookupUserId ? byId.get(lookupUserId) : undefined;
34 if (real) return { name: real.name, avatar_url: real.avatar_url };
35 return { name: friendlyName(lookupUserId ?? String(clientId)), avatar_url: null };
36 };
37 }, [members]);
38 
39 return { userId, resolveIdentity };
40}