From 65ace80871c3c6a4c7c765e3b465cd6fa55e3e87 Mon Sep 17 00:00:00 2001 From: Travis Vachon Date: Tue, 28 Nov 2023 14:48:39 -0800 Subject: [PATCH 1/5] fix: use ReactNode instead of JSX.Element in a couple places I've found this is more flexible and more idiomatic for components intended to be used in React applications (vs generic JSX applications, which imho this package should not strive to support) --- packages/react/src/providers/Provider.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/react/src/providers/Provider.tsx b/packages/react/src/providers/Provider.tsx index bc2b409d..8b91ab91 100644 --- a/packages/react/src/providers/Provider.tsx +++ b/packages/react/src/providers/Provider.tsx @@ -7,7 +7,7 @@ import type { Account } from '@w3ui/core' -import React, { createContext, useState, useContext, useEffect } from 'react' +import React, { createContext, useState, useContext, useEffect, ReactNode } from 'react' import { createClient } from '@w3ui/core' export { ContextState, ContextActions } @@ -31,7 +31,7 @@ export const Context = createContext( ) export interface ProviderProps extends ServiceConfig { - children?: JSX.Element + children?: ReactNode } /** @@ -41,7 +41,7 @@ export function Provider ({ children, servicePrincipal, connection -}: ProviderProps): JSX.Element { +}: ProviderProps): ReactNode { const [client, setClient] = useState() const [events, setEvents] = useState() const [accounts, setAccounts] = useState([]) From 82e4ccda584e6309634695cfc35bd5462d28c36b Mon Sep 17 00:00:00 2001 From: Travis Vachon Date: Tue, 28 Nov 2023 18:01:56 -0800 Subject: [PATCH 2/5] feat: add a logout function this resets the store, which isn't exposed in the client's types. I think it's a little better to manage the store manually anyway since we're going to be calling a method on it directly, so I'm happy with this but could be dissuaded. --- packages/core/src/index.ts | 13 ++++++-- packages/react/src/providers/Provider.tsx | 37 +++++++++++++++-------- 2 files changed, 35 insertions(+), 15 deletions(-) diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 4dc3aa1e..d7e304af 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -6,9 +6,11 @@ import { Client, create as createW3UPClient } from '@web3-storage/w3up-client' import { Account } from '@web3-storage/w3up-client/account' import { Space } from '@web3-storage/w3up-client/space' import { createServiceConf } from './service' +import { Driver } from '@web3-storage/access/drivers/types' export * from '@web3-storage/w3up-client/types' export { Client, Account, Space, ServiceConfig } +export type Store = Driver const DB_NAME = '@w3ui' const DB_STORE_NAME = 'core' @@ -29,7 +31,12 @@ export interface ContextState { } // eslint-disable-next-line @typescript-eslint/no-empty-interface -export interface ContextActions {} +export interface ContextActions { + /** + * Reset local store (deleting existing agent), logging the user out. + */ + logout: () => Promise +} export interface CreateClientOptions extends ServiceConfig { events?: EventTarget @@ -59,11 +66,11 @@ class IndexedDBEventDispatcherStore extends StoreIndexedDB { */ export async function createClient ( options?: CreateClientOptions -): Promise<{ client: Client, events: EventTarget }> { +): Promise<{ client: Client, events: EventTarget, store: Store }> { const dbName = `${DB_NAME}${options?.servicePrincipal != null ? '@' + options?.servicePrincipal.did() : ''}` const events = options?.events ?? new EventTarget() const store = new IndexedDBEventDispatcherStore(dbName, events) const serviceConf = createServiceConf(options) const client = await createW3UPClient({ store, serviceConf }) - return { client, events } + return { client, events, store } } diff --git a/packages/react/src/providers/Provider.tsx b/packages/react/src/providers/Provider.tsx index 8b91ab91..0aaa8cf5 100644 --- a/packages/react/src/providers/Provider.tsx +++ b/packages/react/src/providers/Provider.tsx @@ -4,7 +4,8 @@ import type { ContextActions, ServiceConfig, Space, - Account + Account, + Store } from '@w3ui/core' import React, { createContext, useState, useContext, useEffect, ReactNode } from 'react' @@ -46,6 +47,7 @@ export function Provider ({ const [events, setEvents] = useState() const [accounts, setAccounts] = useState([]) const [spaces, setSpaces] = useState([]) + const [store, setStore] = useState() useEffect(() => { if ((client === undefined) || (events === undefined)) return @@ -61,22 +63,33 @@ export function Provider ({ } }, [client, events]) - const getClient = async (): Promise => { - if (client == null) { - const { client, events } = await createClient({ servicePrincipal, connection }) - setClient(client) - setEvents(events) - setAccounts(Object.values(client.accounts())) - setSpaces(client.spaces()) - return client + const setupClient = async (): Promise => { + const { client, events, store } = await createClient({ servicePrincipal, connection }) + setClient(client) + setEvents(events) + setAccounts(Object.values(client.accounts())) + setSpaces(client.spaces()) + setStore(store) + } + + const logout = async (): Promise => { + if (store) { + await store.reset() + // set state back to defaults + setClient(undefined) + setEvents(undefined) + setAccounts([]) + setSpaces([]) + setStore(undefined) + // try to set state up again + await setupClient() } - return client } - useEffect(() => { void getClient() }, []) // load client - once. + useEffect(() => { setupClient() }, []) // load client - once. return ( - + {children} ) From 453e21cff757aaa6c9642ae25d63f9db7737c30b Mon Sep 17 00:00:00 2001 From: Travis Vachon Date: Tue, 28 Nov 2023 18:08:10 -0800 Subject: [PATCH 3/5] fix: add default value for logout --- packages/react/src/providers/Provider.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react/src/providers/Provider.tsx b/packages/react/src/providers/Provider.tsx index 0aaa8cf5..53f58212 100644 --- a/packages/react/src/providers/Provider.tsx +++ b/packages/react/src/providers/Provider.tsx @@ -24,7 +24,7 @@ export const ContextDefaultValue: ContextValue = [ accounts: [], spaces: [] }, - {} + { logout: async () => {} } ] export const Context = createContext( From 23904e48f3da756e1ea56d8103e18314205d6287 Mon Sep 17 00:00:00 2001 From: Travis Vachon Date: Tue, 28 Nov 2023 18:11:35 -0800 Subject: [PATCH 4/5] fix: add defaults to make tsc happy --- packages/react/src/Authenticator.tsx | 4 ++++ packages/react/src/providers/Provider.tsx | 10 +++++++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/packages/react/src/Authenticator.tsx b/packages/react/src/Authenticator.tsx index ff2c0674..f61f5af7 100644 --- a/packages/react/src/Authenticator.tsx +++ b/packages/react/src/Authenticator.tsx @@ -60,6 +60,9 @@ export const AuthenticatorContextDefaultValue: AuthenticatorContextValue = [ }, cancelLogin: () => { throw new Error('missing cancel login function') + }, + logout: () => { + throw new Error('missing logout function') } } ] @@ -112,6 +115,7 @@ export const AuthenticatorRoot: Component = () => [ { ...state, email, submitted, handleRegisterSubmit }, { + ...actions, setEmail, cancelLogin: () => { loginAbortController?.abort() diff --git a/packages/react/src/providers/Provider.tsx b/packages/react/src/providers/Provider.tsx index 53f58212..db8a9e89 100644 --- a/packages/react/src/providers/Provider.tsx +++ b/packages/react/src/providers/Provider.tsx @@ -24,7 +24,11 @@ export const ContextDefaultValue: ContextValue = [ accounts: [], spaces: [] }, - { logout: async () => {} } + { + logout: async () => { + throw new Error('missing logout function') + } + } ] export const Context = createContext( @@ -73,7 +77,7 @@ export function Provider ({ } const logout = async (): Promise => { - if (store) { + if (store !== undefined) { await store.reset() // set state back to defaults setClient(undefined) @@ -86,7 +90,7 @@ export function Provider ({ } } - useEffect(() => { setupClient() }, []) // load client - once. + useEffect(() => { void setupClient() }, []) // load client - once. return ( From eb52faf2af12d2f3a7e3d6bf297636cee03a0501 Mon Sep 17 00:00:00 2001 From: Travis Vachon Date: Wed, 29 Nov 2023 14:35:11 -0800 Subject: [PATCH 5/5] fix: make sure the store is loaded when logging out Previously it was possible (and common in the case where the user hit a page to log out) for store to be undefined, because setupClient had not yet run. This actively loads the store before calling reset, which is much more reliable. --- packages/core/package.json | 2 +- packages/react/src/providers/Provider.tsx | 29 ++++++++++------------- pnpm-lock.yaml | 27 ++------------------- 3 files changed, 16 insertions(+), 42 deletions(-) diff --git a/packages/core/package.json b/packages/core/package.json index 5e14d467..a73aa059 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -35,7 +35,7 @@ "@ucanto/interface": "^9.0.0", "@ucanto/principal": "^9.0.0", "@ucanto/transport": "^9.0.0", - "@web3-storage/access": "^18.0.3", + "@web3-storage/access": "^18.0.5", "@web3-storage/did-mailto": "^2.0.2", "@web3-storage/w3up-client": "^11.2.0" }, diff --git a/packages/react/src/providers/Provider.tsx b/packages/react/src/providers/Provider.tsx index db8a9e89..a2e210bf 100644 --- a/packages/react/src/providers/Provider.tsx +++ b/packages/react/src/providers/Provider.tsx @@ -4,8 +4,7 @@ import type { ContextActions, ServiceConfig, Space, - Account, - Store + Account } from '@w3ui/core' import React, { createContext, useState, useContext, useEffect, ReactNode } from 'react' @@ -51,7 +50,6 @@ export function Provider ({ const [events, setEvents] = useState() const [accounts, setAccounts] = useState([]) const [spaces, setSpaces] = useState([]) - const [store, setStore] = useState() useEffect(() => { if ((client === undefined) || (events === undefined)) return @@ -68,26 +66,25 @@ export function Provider ({ }, [client, events]) const setupClient = async (): Promise => { - const { client, events, store } = await createClient({ servicePrincipal, connection }) + const { client, events } = await createClient({ servicePrincipal, connection }) setClient(client) setEvents(events) setAccounts(Object.values(client.accounts())) setSpaces(client.spaces()) - setStore(store) } const logout = async (): Promise => { - if (store !== undefined) { - await store.reset() - // set state back to defaults - setClient(undefined) - setEvents(undefined) - setAccounts([]) - setSpaces([]) - setStore(undefined) - // try to set state up again - await setupClient() - } + // it's possible that setupClient hasn't been run yet - run createClient here + // to get a reliable handle on the latest store + const { store } = await createClient({ servicePrincipal, connection }) + await store.reset() + // set state back to defaults + setClient(undefined) + setEvents(undefined) + setAccounts([]) + setSpaces([]) + // set state up again + await setupClient() } useEffect(() => { void setupClient() }, []) // load client - once. diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6292cd8f..0c317bb3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -550,8 +550,8 @@ importers: specifier: ^9.0.0 version: 9.0.0 '@web3-storage/access': - specifier: ^18.0.3 - version: 18.0.3 + specifier: ^18.0.5 + version: 18.0.5 '@web3-storage/did-mailto': specifier: ^2.0.2 version: 2.1.0 @@ -3459,29 +3459,6 @@ packages: uint8arrays: 4.0.9 dev: false - /@web3-storage/access@18.0.3: - resolution: {integrity: sha512-cKxOimmUtKjkwsw+naa7sB9U+e67+bHJb+/KC4Q/d8b+rAmYY0UwlcPCC207w9c7+Ne/cdOuuJd7BH6LfcdX1w==} - dependencies: - '@ipld/car': 5.2.4 - '@ipld/dag-ucan': 3.4.0 - '@scure/bip39': 1.2.1 - '@ucanto/client': 9.0.0 - '@ucanto/core': 9.0.1 - '@ucanto/interface': 9.0.0 - '@ucanto/principal': 9.0.0 - '@ucanto/transport': 9.0.0 - '@ucanto/validator': 9.0.1 - '@web3-storage/capabilities': 12.0.3 - '@web3-storage/did-mailto': 2.1.0 - bigint-mod-arith: 3.3.1 - conf: 11.0.2 - multiformats: 12.1.3 - one-webcrypto: github.com/web3-storage/one-webcrypto/5148cd14d5489a8ac4cd38223870e02db15a2382 - p-defer: 4.0.0 - type-fest: 3.13.1 - uint8arrays: 4.0.9 - dev: false - /@web3-storage/access@18.0.5: resolution: {integrity: sha512-dTojMu7UWb7esbnX3F18eTC+hwlYkDhxBZAogmHX7legRGRK5MwwoRMpk8qz6zI6ImUmkApWFYrF2U8OaGC0bQ==} dependencies: