@dxos/react-client 0.14.0 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +52 -0
- package/dist/lib/invitations.mjs.map +1 -1
- package/dist/lib/testing.mjs.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +24 -24
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,57 @@
|
|
|
1
1
|
# @dxos/react-client
|
|
2
2
|
|
|
3
|
+
## 0.16.0
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Updated dependencies [4007fa2]
|
|
8
|
+
- @dxos/echo-protocol@0.16.0
|
|
9
|
+
- @dxos/echo@0.16.0
|
|
10
|
+
- @dxos/client@0.16.0
|
|
11
|
+
- @dxos/echo-react@0.16.0
|
|
12
|
+
- @dxos/config@0.16.0
|
|
13
|
+
- @dxos/async@0.16.0
|
|
14
|
+
- @dxos/context@0.16.0
|
|
15
|
+
- @dxos/debug@0.16.0
|
|
16
|
+
- @dxos/keys@0.16.0
|
|
17
|
+
- @dxos/log@0.16.0
|
|
18
|
+
- @dxos/node-std@0.16.0
|
|
19
|
+
- @dxos/util@0.16.0
|
|
20
|
+
- @dxos/react-hooks@0.16.0
|
|
21
|
+
|
|
22
|
+
## 0.15.0
|
|
23
|
+
|
|
24
|
+
### Patch Changes
|
|
25
|
+
|
|
26
|
+
- Updated dependencies [9b2a2c0]
|
|
27
|
+
- Updated dependencies [419bf9a]
|
|
28
|
+
- Updated dependencies [3aed53a]
|
|
29
|
+
- Updated dependencies [ed8df23]
|
|
30
|
+
- Updated dependencies [0f6457d]
|
|
31
|
+
- Updated dependencies [94d4f85]
|
|
32
|
+
- Updated dependencies [b6ae19d]
|
|
33
|
+
- Updated dependencies [d0728d8]
|
|
34
|
+
- Updated dependencies [1e9c017]
|
|
35
|
+
- Updated dependencies [16bde33]
|
|
36
|
+
- Updated dependencies [ce8f092]
|
|
37
|
+
- Updated dependencies [9b9a4ca]
|
|
38
|
+
- Updated dependencies [37edd53]
|
|
39
|
+
- Updated dependencies [7b47951]
|
|
40
|
+
- Updated dependencies [2b6a93a]
|
|
41
|
+
- @dxos/echo@0.15.0
|
|
42
|
+
- @dxos/echo-protocol@0.15.0
|
|
43
|
+
- @dxos/async@0.15.0
|
|
44
|
+
- @dxos/echo-react@0.15.0
|
|
45
|
+
- @dxos/client@0.15.0
|
|
46
|
+
- @dxos/config@0.15.0
|
|
47
|
+
- @dxos/react-hooks@0.15.0
|
|
48
|
+
- @dxos/util@0.15.0
|
|
49
|
+
- @dxos/context@0.15.0
|
|
50
|
+
- @dxos/debug@0.15.0
|
|
51
|
+
- @dxos/keys@0.15.0
|
|
52
|
+
- @dxos/log@0.15.0
|
|
53
|
+
- @dxos/node-std@0.15.0
|
|
54
|
+
|
|
3
55
|
## 0.14.0
|
|
4
56
|
|
|
5
57
|
### Patch Changes
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"invitations.mjs","names":[],"sources":["../../src/invitations/useInvitationStatus.ts"],"sourcesContent":["//\n// Copyright 2022 DXOS.org\n//\n\nimport { useCallback, useEffect, useMemo, useReducer } from 'react';\n\nimport { PublicKey } from '@dxos/client';\nimport {\n type AuthenticatingInvitationObservable,\n type CancellableInvitationObservable,\n type Invitation,\n Invitation_AuthMethod,\n Invitation_State,\n Invitation_Type,\n InvitationEncoder,\n} from '@dxos/client/invitations';\nimport { log } from '@dxos/log';\n\nexport type InvitationResult = {\n spaceKey: PublicKey | null;\n identityKey: PublicKey | null;\n swarmKey: PublicKey | null;\n target: string | null;\n};\n\ninterface InvitationReducerState {\n status: Invitation_State; // TODO(burdon): Rename state.\n haltedAt?: Invitation_State;\n result: InvitationResult;\n error?: Error;\n id?: string;\n multiUse?: boolean;\n shareable?: boolean;\n invitationCode?: string;\n authCode?: string;\n authMethod?: Invitation_AuthMethod;\n type?: Invitation_Type;\n}\n\ntype InvitationPayload = { invitation: Invitation };\n\nexport type InvitationAction =\n | ({\n status:\n | Invitation_State.INIT\n | Invitation_State.CONNECTED\n | Invitation_State.READY_FOR_AUTHENTICATION\n | Invitation_State.AUTHENTICATING;\n } & InvitationPayload)\n | ({\n status: Invitation_State.CONNECTING;\n observable: CancellableInvitationObservable;\n } & InvitationPayload)\n | ({\n status: Invitation_State.SUCCESS;\n result: InvitationResult;\n } & InvitationPayload)\n | ({\n status: Invitation_State.CANCELLED | Invitation_State.TIMEOUT;\n haltedAt: Invitation_State;\n } & InvitationPayload)\n | {\n status: Invitation_State.ERROR;\n error?: Error;\n haltedAt: Invitation_State;\n };\n\nexport type InvitationStatus = {\n id?: string;\n invitationCode?: string;\n authCode?: string;\n authMethod?: Invitation_AuthMethod;\n type?: Invitation_Type;\n status: Invitation_State;\n haltedAt?: Invitation_State;\n multiUse?: boolean;\n shareable?: boolean;\n result: InvitationResult;\n error?: Error;\n cancel(): void;\n // TODO(wittjosiah): Remove?\n connect(observable: CancellableInvitationObservable): void;\n authenticate(authCode: string): Promise<void>;\n};\n\n// Without private key, the invitation code cannot be created.\n// These invitations are only available to be accepted but not shared.\nconst isShareableInvitation = (invitation: Invitation) =>\n invitation.authMethod !== Invitation_AuthMethod.KNOWN_PUBLIC_KEY || !!invitation.guestKeypair?.privateKey;\n\nexport const useInvitationStatus = (observable?: CancellableInvitationObservable): InvitationStatus => {\n const [state, dispatch] = useReducer(\n (prev: InvitationReducerState, action: InvitationAction): InvitationReducerState => {\n log('useInvitationStatus', { action });\n const invitationProps =\n 'invitation' in action\n ? {\n id: action.invitation.invitationId,\n multiUse: action.invitation.multiUse,\n invitationCode: InvitationEncoder.encode(action.invitation),\n authCode: action.invitation.authCode,\n authMethod: action.invitation.authMethod,\n shareable: isShareableInvitation(action.invitation),\n type: action.invitation.type,\n }\n : {};\n return {\n ...prev,\n ...invitationProps,\n // TODO(burdon): State.\n status: action.status,\n // `invitationObservable`, `secret`, and `result` is persisted between the status-actions that set them.\n result: action.status === Invitation_State.SUCCESS ? action.result : prev.result,\n // `error` gets set each time we enter the error state\n ...(action.status === Invitation_State.ERROR && { error: action.error }),\n // `haltedAt` gets set on only the first error/cancelled/timeout action and reset on any others.\n ...((action.status === Invitation_State.ERROR ||\n action.status === Invitation_State.CANCELLED ||\n action.status === Invitation_State.TIMEOUT) && {\n haltedAt: typeof prev.haltedAt === 'undefined' ? action.haltedAt : prev.haltedAt,\n }),\n };\n },\n null,\n (_arg: null): InvitationReducerState => {\n const invitation = observable?.get();\n return {\n status: Invitation_State.INIT,\n result: { spaceKey: null, identityKey: null, swarmKey: null, target: null },\n id: invitation?.invitationId,\n multiUse: invitation?.multiUse,\n invitationCode: invitation ? InvitationEncoder.encode(invitation) : undefined,\n authCode: invitation?.authCode,\n authMethod: invitation?.authMethod,\n shareable: invitation ? isShareableInvitation(invitation) : false,\n type: invitation?.type,\n };\n },\n );\n\n // Handle unmount.\n\n useEffect(() => {\n const update = (invitation: Invitation) => {\n switch (invitation.state) {\n case Invitation_State.INIT:\n case Invitation_State.CONNECTED:\n case Invitation_State.READY_FOR_AUTHENTICATION:\n case Invitation_State.AUTHENTICATING: {\n dispatch({\n invitation,\n status: invitation.state,\n });\n break;\n }\n\n case Invitation_State.SUCCESS: {\n dispatch({\n invitation,\n status: invitation.state,\n result: {\n spaceKey: invitation.spaceKey ? PublicKey.from(invitation.spaceKey.data) : null,\n identityKey: invitation.identityKey ? PublicKey.from(invitation.identityKey.data) : null,\n swarmKey: invitation.swarmKey ? PublicKey.from(invitation.swarmKey.data) : null,\n target: invitation.target || null,\n },\n });\n break;\n }\n\n case Invitation_State.CANCELLED:\n case Invitation_State.TIMEOUT: {\n dispatch({ invitation, status: invitation.state, haltedAt: state.status });\n break;\n }\n }\n };\n\n const subscription = observable?.subscribe(update, (err: Error) => {\n dispatch({ status: Invitation_State.ERROR, error: err, haltedAt: state.status });\n });\n\n const currentState = observable?.get();\n if (currentState) {\n update(currentState);\n }\n\n return () => subscription?.unsubscribe();\n }, [observable, state.status]);\n\n // Return memoized callbacks & values.\n\n const connect = useCallback((observable: CancellableInvitationObservable) => {\n dispatch({ invitation: observable.get(), status: Invitation_State.CONNECTING, observable });\n }, []);\n\n const authenticate = useCallback(\n (authCode: string) => {\n log('authenticating...', { authCode });\n return (observable as AuthenticatingInvitationObservable).authenticate(authCode);\n },\n [observable],\n );\n\n const cancel = useCallback(async () => observable?.cancel(), [observable]);\n\n return useMemo(() => {\n return {\n ...state,\n cancel,\n connect,\n authenticate,\n };\n }, [state, cancel, connect, authenticate]);\n};\n"],"mappings":";;;;;;AAuFA,IAAM,yBAAyB,eAC7B,WAAW,
|
|
1
|
+
{"version":3,"file":"invitations.mjs","names":["Invitation_AuthMethod","InvitationEncoder","Invitation_State"],"sources":["../../src/invitations/useInvitationStatus.ts"],"sourcesContent":["//\n// Copyright 2022 DXOS.org\n//\n\nimport { useCallback, useEffect, useMemo, useReducer } from 'react';\n\nimport { PublicKey } from '@dxos/client';\nimport {\n type AuthenticatingInvitationObservable,\n type CancellableInvitationObservable,\n type Invitation,\n Invitation_AuthMethod,\n Invitation_State,\n Invitation_Type,\n InvitationEncoder,\n} from '@dxos/client/invitations';\nimport { log } from '@dxos/log';\n\nexport type InvitationResult = {\n spaceKey: PublicKey | null;\n identityKey: PublicKey | null;\n swarmKey: PublicKey | null;\n target: string | null;\n};\n\ninterface InvitationReducerState {\n status: Invitation_State; // TODO(burdon): Rename state.\n haltedAt?: Invitation_State;\n result: InvitationResult;\n error?: Error;\n id?: string;\n multiUse?: boolean;\n shareable?: boolean;\n invitationCode?: string;\n authCode?: string;\n authMethod?: Invitation_AuthMethod;\n type?: Invitation_Type;\n}\n\ntype InvitationPayload = { invitation: Invitation };\n\nexport type InvitationAction =\n | ({\n status:\n | Invitation_State.INIT\n | Invitation_State.CONNECTED\n | Invitation_State.READY_FOR_AUTHENTICATION\n | Invitation_State.AUTHENTICATING;\n } & InvitationPayload)\n | ({\n status: Invitation_State.CONNECTING;\n observable: CancellableInvitationObservable;\n } & InvitationPayload)\n | ({\n status: Invitation_State.SUCCESS;\n result: InvitationResult;\n } & InvitationPayload)\n | ({\n status: Invitation_State.CANCELLED | Invitation_State.TIMEOUT;\n haltedAt: Invitation_State;\n } & InvitationPayload)\n | {\n status: Invitation_State.ERROR;\n error?: Error;\n haltedAt: Invitation_State;\n };\n\nexport type InvitationStatus = {\n id?: string;\n invitationCode?: string;\n authCode?: string;\n authMethod?: Invitation_AuthMethod;\n type?: Invitation_Type;\n status: Invitation_State;\n haltedAt?: Invitation_State;\n multiUse?: boolean;\n shareable?: boolean;\n result: InvitationResult;\n error?: Error;\n cancel(): void;\n // TODO(wittjosiah): Remove?\n connect(observable: CancellableInvitationObservable): void;\n authenticate(authCode: string): Promise<void>;\n};\n\n// Without private key, the invitation code cannot be created.\n// These invitations are only available to be accepted but not shared.\nconst isShareableInvitation = (invitation: Invitation) =>\n invitation.authMethod !== Invitation_AuthMethod.KNOWN_PUBLIC_KEY || !!invitation.guestKeypair?.privateKey;\n\nexport const useInvitationStatus = (observable?: CancellableInvitationObservable): InvitationStatus => {\n const [state, dispatch] = useReducer(\n (prev: InvitationReducerState, action: InvitationAction): InvitationReducerState => {\n log('useInvitationStatus', { action });\n const invitationProps =\n 'invitation' in action\n ? {\n id: action.invitation.invitationId,\n multiUse: action.invitation.multiUse,\n invitationCode: InvitationEncoder.encode(action.invitation),\n authCode: action.invitation.authCode,\n authMethod: action.invitation.authMethod,\n shareable: isShareableInvitation(action.invitation),\n type: action.invitation.type,\n }\n : {};\n return {\n ...prev,\n ...invitationProps,\n // TODO(burdon): State.\n status: action.status,\n // `invitationObservable`, `secret`, and `result` is persisted between the status-actions that set them.\n result: action.status === Invitation_State.SUCCESS ? action.result : prev.result,\n // `error` gets set each time we enter the error state\n ...(action.status === Invitation_State.ERROR && { error: action.error }),\n // `haltedAt` gets set on only the first error/cancelled/timeout action and reset on any others.\n ...((action.status === Invitation_State.ERROR ||\n action.status === Invitation_State.CANCELLED ||\n action.status === Invitation_State.TIMEOUT) && {\n haltedAt: typeof prev.haltedAt === 'undefined' ? action.haltedAt : prev.haltedAt,\n }),\n };\n },\n null,\n (_arg: null): InvitationReducerState => {\n const invitation = observable?.get();\n return {\n status: Invitation_State.INIT,\n result: { spaceKey: null, identityKey: null, swarmKey: null, target: null },\n id: invitation?.invitationId,\n multiUse: invitation?.multiUse,\n invitationCode: invitation ? InvitationEncoder.encode(invitation) : undefined,\n authCode: invitation?.authCode,\n authMethod: invitation?.authMethod,\n shareable: invitation ? isShareableInvitation(invitation) : false,\n type: invitation?.type,\n };\n },\n );\n\n // Handle unmount.\n\n useEffect(() => {\n const update = (invitation: Invitation) => {\n switch (invitation.state) {\n case Invitation_State.INIT:\n case Invitation_State.CONNECTED:\n case Invitation_State.READY_FOR_AUTHENTICATION:\n case Invitation_State.AUTHENTICATING: {\n dispatch({\n invitation,\n status: invitation.state,\n });\n break;\n }\n\n case Invitation_State.SUCCESS: {\n dispatch({\n invitation,\n status: invitation.state,\n result: {\n spaceKey: invitation.spaceKey ? PublicKey.from(invitation.spaceKey.data) : null,\n identityKey: invitation.identityKey ? PublicKey.from(invitation.identityKey.data) : null,\n swarmKey: invitation.swarmKey ? PublicKey.from(invitation.swarmKey.data) : null,\n target: invitation.target || null,\n },\n });\n break;\n }\n\n case Invitation_State.CANCELLED:\n case Invitation_State.TIMEOUT: {\n dispatch({ invitation, status: invitation.state, haltedAt: state.status });\n break;\n }\n }\n };\n\n const subscription = observable?.subscribe(update, (err: Error) => {\n dispatch({ status: Invitation_State.ERROR, error: err, haltedAt: state.status });\n });\n\n const currentState = observable?.get();\n if (currentState) {\n update(currentState);\n }\n\n return () => subscription?.unsubscribe();\n }, [observable, state.status]);\n\n // Return memoized callbacks & values.\n\n const connect = useCallback((observable: CancellableInvitationObservable) => {\n dispatch({ invitation: observable.get(), status: Invitation_State.CONNECTING, observable });\n }, []);\n\n const authenticate = useCallback(\n (authCode: string) => {\n log('authenticating...', { authCode });\n return (observable as AuthenticatingInvitationObservable).authenticate(authCode);\n },\n [observable],\n );\n\n const cancel = useCallback(async () => observable?.cancel(), [observable]);\n\n return useMemo(() => {\n return {\n ...state,\n cancel,\n connect,\n authenticate,\n };\n }, [state, cancel, connect, authenticate]);\n};\n"],"mappings":"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"}
|
package/dist/lib/testing.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"testing.mjs","names":[],"sources":["../../src/testing/context.tsx","../../src/testing/ClientRepeater.tsx","../../src/testing/withClientProvider.tsx"],"sourcesContent":["//\n// Copyright 2024 DXOS.org\n//\n\nimport { createContext, useContext } from 'react';\n\nimport { type Space } from '@dxos/client/echo';\nimport { raise } from '@dxos/debug';\nimport { type SpaceId } from '@dxos/keys';\n\nexport type ClientStory = {\n index?: number;\n spaceId?: SpaceId;\n space?: Space;\n};\n\nexport const ClientStory = createContext<ClientStory | undefined>(undefined);\n\nexport const useClientStory = (): ClientStory => {\n return useContext(ClientStory) ?? raise(new Error('Missing ClientStory'));\n};\n","//\n// Copyright 2022 DXOS.org\n//\n\nimport React, { type FC, useRef, useState } from 'react';\n\nimport { Client } from '@dxos/client';\nimport { TestBuilder, performInvitation } from '@dxos/client/testing';\nimport { type Type } from '@dxos/echo';\nimport { type SpaceId } from '@dxos/keys';\nimport { random } from '@dxos/random';\nimport { useAsyncEffect } from '@dxos/react-hooks';\n\nimport { ClientProvider } from '../client/index.ts';\nimport { type WithClientProviderProps } from './withClientProvider.tsx';\n\nexport type ClientRepeatedComponentProps = { id: number; count: number; spaceId?: SpaceId };\n\nexport type ClientRepeaterControlsProps = { clients: Client[] };\n\n// TODO(burdon): Reconcile with ClientProviderProps.\nexport type ClientRepeaterProps<P extends ClientRepeatedComponentProps> = {\n className?: string;\n component: FC<ClientRepeatedComponentProps>;\n controls?: FC<ClientRepeaterControlsProps>;\n count?: number;\n clients?: Client[];\n types?: Type.AnyObj[];\n args?: Omit<P, 'id' | 'count'>;\n} & Pick<WithClientProviderProps, 'createIdentity' | 'createSpace' | 'onCreateSpace'>;\n\n/**\n * Utility component for Storybook stories which sets up clients for n peers.\n * The `Component` property is rendered n times, once for each peer.\n * @deprecated use `withClientProvider`.\n */\n// TODO(burdon): To discuss: evolve ClientRepeater with optional decorator that uses it.\n// NOTE: This is specifically not a storybook decorator because it broke stories as a decorator.\n// This seems primarily due to the fact that it required top-level await for the clients to initialize.\n// Storybook seemed to handle it alright, but Chromatic had a lot of trouble with it.\n// There was also a question of whether or not calling the story function multiple times was a good idea.\nexport const ClientRepeater = <P extends ClientRepeatedComponentProps>(props: ClientRepeaterProps<P>) => {\n const {\n component: Component,\n controls: Controls,\n count = 1,\n types,\n createIdentity,\n createSpace,\n onCreateSpace,\n } = props;\n\n const [clients, setClients] = useState(props.clients ?? []);\n const [spaceId, setSpaceId] = useState<SpaceId>();\n\n const testBuilder = useRef(new TestBuilder());\n useAsyncEffect(async () => {\n const clients = [...Array(count)].map(\n (_) => new Client({ services: testBuilder.current.createLocalClientServices(), types }),\n );\n\n await Promise.all(clients.map((client) => client.initialize()));\n if (createIdentity || createSpace) {\n await Promise.all(clients.map((client) => client.halo.createIdentity()));\n }\n\n if (createSpace) {\n const client = clients[0];\n const space = await client.spaces.create({ name: random.commerce.productName() });\n setSpaceId(space.id);\n await onCreateSpace?.({ client, space }, {});\n await Promise.all(clients.slice(1).flatMap((client) => performInvitation({ host: space, guest: client.spaces })));\n }\n\n setClients(clients);\n }, []);\n\n if (clients.length === 0) {\n return null;\n }\n\n return (\n <>\n {Controls && <Controls clients={clients} />}\n {clients.map((client, index) => (\n <ClientProvider key={index} client={client}>\n <Component id={index} count={clients.length} spaceId={spaceId} {...{ ...props.args }} />\n </ClientProvider>\n ))}\n </>\n );\n};\n","//\n// Copyright 2024 DXOS.org\n//\n\nimport { type Decorator, type StoryContext } from '@storybook/react-vite';\nimport React, { type ComponentType, type PropsWithChildren, useCallback, useMemo, useRef, useState } from 'react';\n\nimport { Trigger } from '@dxos/async';\nimport { type Client } from '@dxos/client';\nimport { type Space } from '@dxos/client/echo';\nimport { TestBuilder, performInvitation } from '@dxos/client/testing';\nimport { type SpaceId } from '@dxos/keys';\nimport { log } from '@dxos/log';\nimport * as Status from '@dxos/react-ui/Status';\nimport { type MaybePromise } from '@dxos/util';\n\nimport { ClientProvider, type ClientProviderProps } from '../client/index.ts';\nimport { ClientStory } from './context.tsx';\n\ntype InitializeProps = {\n createIdentity?: boolean;\n createSpace?: boolean;\n onInitialized?: (client: Client) => MaybePromise<void>;\n onCreateIdentity?: (props: { client: Client }, context: StoryContext | any) => MaybePromise<void>;\n // NOTE: context must be untyped until ClientRepeater is removed.\n onCreateSpace?: (props: { client: Client; space: Space }, context: StoryContext | any) => MaybePromise<void>;\n};\n\n/**\n * Initialize client, identity and first space.\n */\nconst initializeClient = async (\n client: Client,\n { createIdentity, createSpace, onCreateIdentity, onCreateSpace, onInitialized }: InitializeProps,\n context: StoryContext,\n): Promise<ClientStory> => {\n await onInitialized?.(client);\n\n if (createIdentity || createSpace) {\n if (!client.halo.identity.get()) {\n await client.halo.createIdentity();\n await onCreateIdentity?.({ client }, context);\n }\n }\n\n let space: Space | undefined;\n if (createSpace) {\n space = await client.spaces.create({ name: 'Test Space' });\n await space.waitUntilReady(); // Is this required?\n await onCreateSpace?.({ client, space }, context);\n }\n\n return { space, spaceId: space?.id };\n};\n\nexport type WithClientProviderProps = InitializeProps & Omit<ClientProviderProps, 'onInitialized'>;\n\n/**\n * Decorator that provides the client context.\n */\nexport const withClientProvider = ({\n createIdentity,\n createSpace,\n onCreateSpace,\n onCreateIdentity,\n onInitialized,\n ...props\n}: WithClientProviderProps = {}): Decorator => {\n return (Story, context) => {\n const [data, setData] = useState<ClientStory>({});\n const handleInitialized = async (client: Client) => {\n const data = await initializeClient(\n client,\n {\n createIdentity,\n createSpace,\n onCreateSpace,\n onCreateIdentity,\n onInitialized,\n },\n context,\n );\n\n setData(data);\n };\n\n return (\n <Status.ErrorBoundary name='client-provider'>\n <ClientProvider onInitialized={handleInitialized} {...props}>\n <ClientStory.Provider value={data}>\n <Story />\n </ClientStory.Provider>\n </ClientProvider>\n </Status.ErrorBoundary>\n );\n };\n};\n\ntype LocalServicesOptions = NonNullable<Parameters<TestBuilder['createLocalClientServices']>[0]>;\n\n// TODO(burdon): Implement context per client for context.\n// TODO(burdon): Callback once all invitations have completed.\n// TODO(burdon): Delay/jitter for creation of other clients.\nexport type WithMultiClientProviderProps = InitializeProps &\n Omit<ClientProviderProps, 'onInitialized'> & {\n numClients?: number;\n /** One relay (e.g. `MemoryEdgeInbox`) shared by every client, so they can message each other without EDGE. */\n inboxRelay?: LocalServicesOptions['inboxRelay'];\n /**\n * Wraps each client's story inside its `ClientProvider`, so the client can host what the story\n * needs per client — e.g. a plugin manager adopting it (see plugin-client's `ClientPluginManager`).\n */\n wrapper?: ComponentType<PropsWithChildren<{ index: number }>>;\n };\n\n/**\n * Decorator that creates a scaffold for multiple clients.\n * Orchestrates invitations between a randomly selected host and the remaining clients.\n */\nexport const withMultiClientProvider = ({\n numClients = 2,\n createIdentity,\n createSpace,\n onCreateSpace,\n onCreateIdentity,\n onInitialized,\n inboxRelay,\n wrapper: Wrapper,\n ...props\n}: WithMultiClientProviderProps): Decorator => {\n return (Story, context) => {\n const spaceReady = useRef(new Trigger<Space | undefined>());\n const [spaceId, setSpaceId] = useState<SpaceId>();\n\n const clients = useMemo(() => {\n const buidler = new TestBuilder();\n return Array.from({ length: numClients }).map(() => {\n return {\n services: buidler.createLocalClientServices({ inboxRelay }),\n };\n });\n }, [numClients]);\n\n // Handle invitations.\n // NOTE: The zeroth client isn't necessarily the first to be initialized.\n const handleInitialized = useCallback(async (client: Client, index: number) => {\n if (index === 0) {\n // Initialize host.\n const { space } = await initializeClient(\n client,\n {\n createIdentity,\n createSpace,\n onCreateSpace,\n onCreateIdentity,\n onInitialized,\n },\n context,\n );\n\n log.info('inviting', { index, spaceId: space?.id });\n setSpaceId(space?.id);\n spaceReady.current.wake(space);\n } else {\n // Initialize guest.\n await initializeClient(client, { createIdentity, onInitialized }, context);\n\n log.info('waiting', { index });\n const space = await spaceReady.current.wait();\n if (space) {\n log.info('joining', { index, spaceId: space.id });\n await Promise.all(\n performInvitation({\n host: space,\n guest: client.spaces,\n }),\n );\n }\n }\n }, []);\n\n return (\n <div className='dx-cover grid grid-flow-col auto-cols-fr gap-4 overflow-hidden'>\n {clients.map((client, index) => (\n <ClientProvider\n key={index}\n services={client.services}\n onInitialized={(client) => handleInitialized(client, index)}\n {...props}\n >\n <ClientStory.Provider value={{ index, spaceId }}>\n <Status.ErrorBoundary name='client-provider'>\n {Wrapper ? (\n <Wrapper index={index}>\n <Story />\n </Wrapper>\n ) : (\n <Story />\n )}\n </Status.ErrorBoundary>\n </ClientStory.Provider>\n </ClientProvider>\n ))}\n </div>\n );\n };\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"testing.mjs","names":["_Fragment"],"sources":["../../src/testing/context.tsx","../../src/testing/ClientRepeater.tsx","../../src/testing/withClientProvider.tsx"],"sourcesContent":["//\n// Copyright 2024 DXOS.org\n//\n\nimport { createContext, useContext } from 'react';\n\nimport { type Space } from '@dxos/client/echo';\nimport { raise } from '@dxos/debug';\nimport { type SpaceId } from '@dxos/keys';\n\nexport type ClientStory = {\n index?: number;\n spaceId?: SpaceId;\n space?: Space;\n};\n\nexport const ClientStory = createContext<ClientStory | undefined>(undefined);\n\nexport const useClientStory = (): ClientStory => {\n return useContext(ClientStory) ?? raise(new Error('Missing ClientStory'));\n};\n","//\n// Copyright 2022 DXOS.org\n//\n\nimport React, { type FC, useRef, useState } from 'react';\n\nimport { Client } from '@dxos/client';\nimport { TestBuilder, performInvitation } from '@dxos/client/testing';\nimport { type Type } from '@dxos/echo';\nimport { type SpaceId } from '@dxos/keys';\nimport { random } from '@dxos/random';\nimport { useAsyncEffect } from '@dxos/react-hooks';\n\nimport { ClientProvider } from '../client/index.ts';\nimport { type WithClientProviderProps } from './withClientProvider.tsx';\n\nexport type ClientRepeatedComponentProps = { id: number; count: number; spaceId?: SpaceId };\n\nexport type ClientRepeaterControlsProps = { clients: Client[] };\n\n// TODO(burdon): Reconcile with ClientProviderProps.\nexport type ClientRepeaterProps<P extends ClientRepeatedComponentProps> = {\n className?: string;\n component: FC<ClientRepeatedComponentProps>;\n controls?: FC<ClientRepeaterControlsProps>;\n count?: number;\n clients?: Client[];\n types?: Type.AnyObj[];\n args?: Omit<P, 'id' | 'count'>;\n} & Pick<WithClientProviderProps, 'createIdentity' | 'createSpace' | 'onCreateSpace'>;\n\n/**\n * Utility component for Storybook stories which sets up clients for n peers.\n * The `Component` property is rendered n times, once for each peer.\n * @deprecated use `withClientProvider`.\n */\n// TODO(burdon): To discuss: evolve ClientRepeater with optional decorator that uses it.\n// NOTE: This is specifically not a storybook decorator because it broke stories as a decorator.\n// This seems primarily due to the fact that it required top-level await for the clients to initialize.\n// Storybook seemed to handle it alright, but Chromatic had a lot of trouble with it.\n// There was also a question of whether or not calling the story function multiple times was a good idea.\nexport const ClientRepeater = <P extends ClientRepeatedComponentProps>(props: ClientRepeaterProps<P>) => {\n const {\n component: Component,\n controls: Controls,\n count = 1,\n types,\n createIdentity,\n createSpace,\n onCreateSpace,\n } = props;\n\n const [clients, setClients] = useState(props.clients ?? []);\n const [spaceId, setSpaceId] = useState<SpaceId>();\n\n const testBuilder = useRef(new TestBuilder());\n useAsyncEffect(async () => {\n const clients = [...Array(count)].map(\n (_) => new Client({ services: testBuilder.current.createLocalClientServices(), types }),\n );\n\n await Promise.all(clients.map((client) => client.initialize()));\n if (createIdentity || createSpace) {\n await Promise.all(clients.map((client) => client.halo.createIdentity()));\n }\n\n if (createSpace) {\n const client = clients[0];\n const space = await client.spaces.create({ name: random.commerce.productName() });\n setSpaceId(space.id);\n await onCreateSpace?.({ client, space }, {});\n await Promise.all(clients.slice(1).flatMap((client) => performInvitation({ host: space, guest: client.spaces })));\n }\n\n setClients(clients);\n }, []);\n\n if (clients.length === 0) {\n return null;\n }\n\n return (\n <>\n {Controls && <Controls clients={clients} />}\n {clients.map((client, index) => (\n <ClientProvider key={index} client={client}>\n <Component id={index} count={clients.length} spaceId={spaceId} {...{ ...props.args }} />\n </ClientProvider>\n ))}\n </>\n );\n};\n","//\n// Copyright 2024 DXOS.org\n//\n\nimport { type Decorator, type StoryContext } from '@storybook/react-vite';\nimport React, { type ComponentType, type PropsWithChildren, useCallback, useMemo, useRef, useState } from 'react';\n\nimport { Trigger } from '@dxos/async';\nimport { type Client } from '@dxos/client';\nimport { type Space } from '@dxos/client/echo';\nimport { TestBuilder, performInvitation } from '@dxos/client/testing';\nimport { type SpaceId } from '@dxos/keys';\nimport { log } from '@dxos/log';\nimport * as Status from '@dxos/react-ui/Status';\nimport { type MaybePromise } from '@dxos/util';\n\nimport { ClientProvider, type ClientProviderProps } from '../client/index.ts';\nimport { ClientStory } from './context.tsx';\n\ntype InitializeProps = {\n createIdentity?: boolean;\n createSpace?: boolean;\n onInitialized?: (client: Client) => MaybePromise<void>;\n onCreateIdentity?: (props: { client: Client }, context: StoryContext | any) => MaybePromise<void>;\n // NOTE: context must be untyped until ClientRepeater is removed.\n onCreateSpace?: (props: { client: Client; space: Space }, context: StoryContext | any) => MaybePromise<void>;\n};\n\n/**\n * Initialize client, identity and first space.\n */\nconst initializeClient = async (\n client: Client,\n { createIdentity, createSpace, onCreateIdentity, onCreateSpace, onInitialized }: InitializeProps,\n context: StoryContext,\n): Promise<ClientStory> => {\n await onInitialized?.(client);\n\n if (createIdentity || createSpace) {\n if (!client.halo.identity.get()) {\n await client.halo.createIdentity();\n await onCreateIdentity?.({ client }, context);\n }\n }\n\n let space: Space | undefined;\n if (createSpace) {\n space = await client.spaces.create({ name: 'Test Space' });\n await space.waitUntilReady(); // Is this required?\n await onCreateSpace?.({ client, space }, context);\n }\n\n return { space, spaceId: space?.id };\n};\n\nexport type WithClientProviderProps = InitializeProps & Omit<ClientProviderProps, 'onInitialized'>;\n\n/**\n * Decorator that provides the client context.\n */\nexport const withClientProvider = ({\n createIdentity,\n createSpace,\n onCreateSpace,\n onCreateIdentity,\n onInitialized,\n ...props\n}: WithClientProviderProps = {}): Decorator => {\n return (Story, context) => {\n const [data, setData] = useState<ClientStory>({});\n const handleInitialized = async (client: Client) => {\n const data = await initializeClient(\n client,\n {\n createIdentity,\n createSpace,\n onCreateSpace,\n onCreateIdentity,\n onInitialized,\n },\n context,\n );\n\n setData(data);\n };\n\n return (\n <Status.ErrorBoundary name='client-provider'>\n <ClientProvider onInitialized={handleInitialized} {...props}>\n <ClientStory.Provider value={data}>\n <Story />\n </ClientStory.Provider>\n </ClientProvider>\n </Status.ErrorBoundary>\n );\n };\n};\n\ntype LocalServicesOptions = NonNullable<Parameters<TestBuilder['createLocalClientServices']>[0]>;\n\n// TODO(burdon): Implement context per client for context.\n// TODO(burdon): Callback once all invitations have completed.\n// TODO(burdon): Delay/jitter for creation of other clients.\nexport type WithMultiClientProviderProps = InitializeProps &\n Omit<ClientProviderProps, 'onInitialized'> & {\n numClients?: number;\n /** One relay (e.g. `MemoryEdgeInbox`) shared by every client, so they can message each other without EDGE. */\n inboxRelay?: LocalServicesOptions['inboxRelay'];\n /**\n * Wraps each client's story inside its `ClientProvider`, so the client can host what the story\n * needs per client — e.g. a plugin manager adopting it (see plugin-client's `ClientPluginManager`).\n */\n wrapper?: ComponentType<PropsWithChildren<{ index: number }>>;\n };\n\n/**\n * Decorator that creates a scaffold for multiple clients.\n * Orchestrates invitations between a randomly selected host and the remaining clients.\n */\nexport const withMultiClientProvider = ({\n numClients = 2,\n createIdentity,\n createSpace,\n onCreateSpace,\n onCreateIdentity,\n onInitialized,\n inboxRelay,\n wrapper: Wrapper,\n ...props\n}: WithMultiClientProviderProps): Decorator => {\n return (Story, context) => {\n const spaceReady = useRef(new Trigger<Space | undefined>());\n const [spaceId, setSpaceId] = useState<SpaceId>();\n\n const clients = useMemo(() => {\n const buidler = new TestBuilder();\n return Array.from({ length: numClients }).map(() => {\n return {\n services: buidler.createLocalClientServices({ inboxRelay }),\n };\n });\n }, [numClients]);\n\n // Handle invitations.\n // NOTE: The zeroth client isn't necessarily the first to be initialized.\n const handleInitialized = useCallback(async (client: Client, index: number) => {\n if (index === 0) {\n // Initialize host.\n const { space } = await initializeClient(\n client,\n {\n createIdentity,\n createSpace,\n onCreateSpace,\n onCreateIdentity,\n onInitialized,\n },\n context,\n );\n\n log.info('inviting', { index, spaceId: space?.id });\n setSpaceId(space?.id);\n spaceReady.current.wake(space);\n } else {\n // Initialize guest.\n await initializeClient(client, { createIdentity, onInitialized }, context);\n\n log.info('waiting', { index });\n const space = await spaceReady.current.wait();\n if (space) {\n log.info('joining', { index, spaceId: space.id });\n await Promise.all(\n performInvitation({\n host: space,\n guest: client.spaces,\n }),\n );\n }\n }\n }, []);\n\n return (\n <div className='dx-cover grid grid-flow-col auto-cols-fr gap-4 overflow-hidden'>\n {clients.map((client, index) => (\n <ClientProvider\n key={index}\n services={client.services}\n onInitialized={(client) => handleInitialized(client, index)}\n {...props}\n >\n <ClientStory.Provider value={{ index, spaceId }}>\n <Status.ErrorBoundary name='client-provider'>\n {Wrapper ? (\n <Wrapper index={index}>\n <Story />\n </Wrapper>\n ) : (\n <Story />\n )}\n </Status.ErrorBoundary>\n </ClientStory.Provider>\n </ClientProvider>\n ))}\n </div>\n );\n };\n};\n"],"mappings":"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"}
|