diff --git a/src/app/page.tsx b/src/app/page.tsx index bd7d703..e2ceae7 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,12 +1,14 @@ 'use client' -import { useW3, Space } from '@w3ui/react' +import { useW3, Space, Delegation, DIDKey, Abilities } from '@w3ui/react' import { DidIcon } from '@/components/DidIcon' +import { ShieldCheckIcon, ShieldExclamationIcon } from '@heroicons/react/24/outline' import Link from 'next/link' import { SpacesNav } from './space/layout' import { H2 } from '@/components/Text' import SidebarLayout from '@/components/SidebarLayout' import { ReactNode } from 'react' +import { useClaims } from '@/hooks' export default function HomePage () { return ( @@ -16,26 +18,57 @@ export default function HomePage () { ) } +type RecoverableCapabilities = { [key: DIDKey]: Abilities } + +/** + * A heuristic for determining the "recoverable capabilities" of + * spaces represented by a set of Delegations. + * + * First, finds any delegations that delegation * on ucan:* + * Next, searches through all proofs of these delegations, + * creating a map from resource names (assumed to be spaces) to a list + * of capabilities, like: + * + * { + * 'did:key:zkfirstspace': ['*'] + * 'did:key:zksecondSpace': ['upload/add', 'store/add'] + * } + */ +function guessRecoverableCapabilities(delegations: Delegation[]): RecoverableCapabilities { + return delegations.filter(d => ((d.capabilities[0].can === '*') && (d.capabilities[0].with === 'ucan:*'))) + .map(d => d.proofs as Delegation[]) + .reduce((m: any, proofs: Delegation[]) => [...m, ...proofs], []) + .reduce((m: any, proof: Delegation) => { + for (const capability of proof.capabilities) { + m[capability.with] = [...(m[capability.with] || []), capability.can] + } + return m + }, {}) +} + function SpacePage (): ReactNode { - const [{ spaces }] = useW3() + const [{ spaces, client }] = useW3() + const { data: delegations } = useClaims(client) if (spaces.length === 0) { return
} + const recoverableCapabilities = delegations && guessRecoverableCapabilities(delegations) + return ( <>

Pick a Space

- { spaces.map(s => ) } + {spaces.map(s => )}
) } -function Item ({space}: {space: Space}) { - return ( +function Item ({ space, recoverableAbilities }: { space: Space, recoverableAbilities?: Abilities }) { + return (
@@ -46,6 +79,12 @@ function Item ({space}: {space: Space}) { {space.did()}
+ {(typeof recoverableAbilities !== 'undefined') && ( + (recoverableAbilities.length == 0) ? ( + + ) : ( + + ))} ) } diff --git a/src/hooks.tsx b/src/hooks.tsx index 02a184b..8cf0071 100644 --- a/src/hooks.tsx +++ b/src/hooks.tsx @@ -1,5 +1,7 @@ -import { Account, PlanGetSuccess } from '@w3ui/react' +import { Account, Capabilities, Client, Delegation, PlanGetSuccess, Tuple } from '@w3ui/react' import useSWR from 'swr' +import { claimAccess } from '@web3-storage/access/agent' + export const usePlan = (account: Account) => useSWR(`/plan/${account?.did() ?? ''}`, { @@ -10,4 +12,13 @@ export const usePlan = (account: Account) => return result.ok }, onError: err => console.error(err.message, err.cause) + }) + +export const useClaims = (client: Client | undefined) => + useSWR> | undefined>(client && `/claims/${client.agent.did()}`, { + fetcher: async () => { + if (!client) return + return await client.capability.access.claim() + }, + onError: err => console.error(err.message, err.cause) }) \ No newline at end of file