From 2d858c30459ac39007bea904ac9775d9af2b5a8c Mon Sep 17 00:00:00 2001 From: Travis Vachon Date: Fri, 8 Dec 2023 15:38:41 -0800 Subject: [PATCH 1/2] wip: tell users when they have unrecoverable spaces Use a heuristic to look at available claims and determine whether a space is recoverable. Currently considers a space recoverable if any abilities are delegated and should probably validate a minimum set. Unfortunately we don't have any way for users to recover an "unrecoverable" space at the moment, since we still discard Space keys after creation - I'm not convinced this feature is particularly helpful without some way to fix unrecoverable spaces, but it is probably better than not having any way to determine that at all! --- src/app/page.tsx | 49 +++++++++++++++++++++++++++++++++++++++++++----- src/hooks.tsx | 13 ++++++++++++- 2 files changed, 56 insertions(+), 6 deletions(-) diff --git a/src/app/page.tsx b/src/app/page.tsx index bd7d703..2358af8 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() if (spaces.length === 0) { return
} + const { data: delegations } = useClaims(client) + 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 From afba598c2cff520189c56a1340f1d6c2e0e44a6c Mon Sep 17 00:00:00 2001 From: Travis Vachon Date: Mon, 11 Dec 2023 17:14:42 -0800 Subject: [PATCH 2/2] fix: appease rules of hooks --- src/app/page.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/page.tsx b/src/app/page.tsx index 2358af8..e2ceae7 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -48,12 +48,12 @@ function guessRecoverableCapabilities(delegations: Delegation[]): RecoverableCap function SpacePage (): ReactNode { const [{ spaces, client }] = useW3() + const { data: delegations } = useClaims(client) if (spaces.length === 0) { return
} - const { data: delegations } = useClaims(client) const recoverableCapabilities = delegations && guessRecoverableCapabilities(delegations) return (