import { type Grant, type GrantId, type PartitionId, type SharePermission, grantId as toGrantId, partitionId as toPartitionId, userId as toUserId, type UserId, } from "@suaveplan/drive-sharing"; import { useCallback, useEffect, useRef, useState } from "react"; import { DRIVE_REACT_SHARING_CODES, DriveReactSharingError, } from "../../error-codes.js"; import { getDriveReactSharingInstruments } from "../../otel/instruments.js"; import type { ShareRecipient, UseSharingOptions, UseSharingResult, } from "../../types/sharing.types.js"; /** Permission tier ordering for `primaryPermission` resolution. */ const TIER_RANK: Record = { read: 1, write: 2, delete: 3, share: 4, admin: 5, }; function primaryOf(permissions: readonly SharePermission[]): SharePermission { let best: SharePermission = "read"; let bestRank = 0; for (const p of permissions) { const rank = TIER_RANK[p]; if (rank > bestRank) { best = p; bestRank = rank; } } return best; } function projectGrant(grant: Grant): ShareRecipient { return { grantId: grant.id, recipientId: grant.granteeId, permissions: grant.permissions, primaryPermission: primaryOf(grant.permissions), createdAt: grant.createdAt, ...(grant.expiresAt !== undefined ? { expiresAt: grant.expiresAt } : {}), revoked: grant.revoked, }; } function loadRecipients(options: UseSharingOptions): readonly ShareRecipient[] { const pid = toPartitionId(options.partitionId) as PartitionId; return options.grants .list({ partitionId: pid, active: true }) .map(projectGrant); } function wrap( code: | typeof DRIVE_REACT_SHARING_CODES.ADD_FAILED | typeof DRIVE_REACT_SHARING_CODES.REVOKE_FAILED | typeof DRIVE_REACT_SHARING_CODES.UPDATE_FAILED | typeof DRIVE_REACT_SHARING_CODES.REFRESH_FAILED, messageKey: string, cause: unknown ): DriveReactSharingError { return new DriveReactSharingError(code, messageKey, { cause }); } function checkAborted(signal: AbortSignal | undefined): void { if (signal?.aborted === true) { throw new DriveReactSharingError( DRIVE_REACT_SHARING_CODES.ADD_FAILED, "drive-react-sharing.aborted" ); } } /** * React hook binding a partition's share state to the live engine. * * Holds an in-memory `ShareRecipient[]` that mirrors active grants for the * supplied `partitionId`, plus async action callbacks (`addRecipient`, * `removeRecipient`, `updatePermission`, `refresh`) that delegate to * `@suaveplan/drive-sharing` and re-sync state on success. Every action * accepts an optional `AbortSignal`. Errors are surfaced both via * `result.error` and by re-throwing so callers can render inline. */ export function useSharing(options: UseSharingOptions): UseSharingResult { const optionsRef = useRef(options); optionsRef.current = options; const [recipients, setRecipients] = useState( () => options.initialRecipients ?? loadRecipients(options) ); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); // Track mount status so async tasks do not setState after unmount. const mountedRef = useRef(true); useEffect(() => { mountedRef.current = true; return () => { mountedRef.current = false; }; }, []); const safeSetRecipients = useCallback( (next: readonly ShareRecipient[]): void => { if (mountedRef.current) setRecipients(next); }, [] ); const safeSetLoading = useCallback((next: boolean): void => { if (mountedRef.current) setLoading(next); }, []); const safeSetError = useCallback( (next: DriveReactSharingError | null): void => { if (mountedRef.current) setError(next); }, [] ); const refresh = useCallback( async (signal?: AbortSignal): Promise => { safeSetLoading(true); try { checkAborted(signal); const next = loadRecipients(optionsRef.current); safeSetRecipients(next); safeSetError(null); } catch (err) { const wrapped = wrap( DRIVE_REACT_SHARING_CODES.REFRESH_FAILED, "drive-react-sharing.refresh-failed", err ); safeSetError(wrapped); throw wrapped; } finally { safeSetLoading(false); } }, [safeSetError, safeSetLoading, safeSetRecipients] ); const addRecipient = useCallback( async ( recipientId: string, permission: SharePermission, signal?: AbortSignal ): Promise => { if (recipientId.trim() === "") { const e = new DriveReactSharingError( DRIVE_REACT_SHARING_CODES.INVALID_RECIPIENT, "drive-react-sharing.invalid-recipient" ); safeSetError(e); throw e; } safeSetLoading(true); try { checkAborted(signal); const opts = optionsRef.current; opts.grants.create({ grantorId: opts.grantorId, granteeId: toUserId(recipientId) as UserId, partitionId: toPartitionId(opts.partitionId) as PartitionId, permissions: [permission], }); getDriveReactSharingInstruments().recipientsAddedTotal.add(1, { permission, }); safeSetRecipients(loadRecipients(opts)); safeSetError(null); } catch (err) { const wrapped = wrap( DRIVE_REACT_SHARING_CODES.ADD_FAILED, "drive-react-sharing.add-failed", err ); safeSetError(wrapped); throw wrapped; } finally { safeSetLoading(false); } }, [safeSetError, safeSetLoading, safeSetRecipients] ); const removeRecipient = useCallback( async (grantId: string, signal?: AbortSignal): Promise => { const opts = optionsRef.current; const existing = opts.grants.get(toGrantId(grantId) as GrantId); if (existing === undefined) { const e = new DriveReactSharingError( DRIVE_REACT_SHARING_CODES.GRANT_NOT_FOUND, "drive-react-sharing.grant-not-found" ); safeSetError(e); throw e; } safeSetLoading(true); try { checkAborted(signal); opts.grants.revoke(toGrantId(grantId) as GrantId); getDriveReactSharingInstruments().recipientsRemovedTotal.add(1); safeSetRecipients(loadRecipients(opts)); safeSetError(null); } catch (err) { const wrapped = wrap( DRIVE_REACT_SHARING_CODES.REVOKE_FAILED, "drive-react-sharing.revoke-failed", err ); safeSetError(wrapped); throw wrapped; } finally { safeSetLoading(false); } }, [safeSetError, safeSetLoading, safeSetRecipients] ); const updatePermission = useCallback( async ( grantId: string, permission: SharePermission, signal?: AbortSignal ): Promise => { const opts = optionsRef.current; const existing = opts.grants.get(toGrantId(grantId) as GrantId); if (existing === undefined) { const e = new DriveReactSharingError( DRIVE_REACT_SHARING_CODES.GRANT_NOT_FOUND, "drive-react-sharing.grant-not-found" ); safeSetError(e); throw e; } safeSetLoading(true); try { checkAborted(signal); // Revoke + recreate is the engine-supported way to swap // permissions atomically; GrantManager has no mutate API. opts.grants.revoke(existing.id); opts.grants.create({ grantorId: existing.grantorId, granteeId: existing.granteeId, partitionId: existing.partitionId, permissions: [permission], ...(existing.expiresAt !== undefined ? { expiresAt: existing.expiresAt } : {}), }); getDriveReactSharingInstruments().permissionsUpdatedTotal.add( 1, { permission } ); safeSetRecipients(loadRecipients(opts)); safeSetError(null); } catch (err) { const wrapped = wrap( DRIVE_REACT_SHARING_CODES.UPDATE_FAILED, "drive-react-sharing.update-failed", err ); safeSetError(wrapped); throw wrapped; } finally { safeSetLoading(false); } }, [safeSetError, safeSetLoading, safeSetRecipients] ); return { recipients, loading, error, addRecipient, removeRecipient, updatePermission, refresh, }; }