@dxos/react-client 0.8.4-main.5ea62a8 → 0.8.4-main.60689f5b1c
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/.storybook/main.mts +9 -0
- package/.storybook/preview.mts +8 -0
- package/LICENSE +102 -5
- package/README.md +1 -1
- package/dist/lib/browser/chunk-3LVU7RNK.mjs +16 -0
- package/dist/lib/browser/chunk-3LVU7RNK.mjs.map +7 -0
- package/dist/lib/browser/chunk-CZMIVAGI.mjs +8 -0
- package/dist/lib/browser/chunk-CZMIVAGI.mjs.map +7 -0
- package/dist/lib/browser/chunk-XT352YI6.mjs +134 -0
- package/dist/lib/browser/chunk-XT352YI6.mjs.map +7 -0
- package/dist/lib/browser/{chunk-BXOLJYAY.mjs → chunk-YHE6WD44.mjs} +4 -14
- package/dist/lib/browser/chunk-YHE6WD44.mjs.map +7 -0
- package/dist/lib/browser/devtools/index.mjs +3 -7
- package/dist/lib/browser/devtools/index.mjs.map +2 -2
- package/dist/lib/browser/echo/index.mjs +55 -173
- package/dist/lib/browser/echo/index.mjs.map +4 -4
- package/dist/lib/browser/halo/index.mjs +8 -21
- package/dist/lib/browser/halo/index.mjs.map +2 -2
- package/dist/lib/browser/index.mjs +99 -12
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/invitations/index.mjs +1 -1
- package/dist/lib/browser/mesh/index.mjs +2 -1
- package/dist/lib/browser/mesh/index.mjs.map +1 -1
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/testing/index.mjs +86 -127
- package/dist/lib/browser/testing/index.mjs.map +3 -3
- package/dist/lib/node-esm/chunk-CUDKNAMV.mjs +17 -0
- package/dist/lib/node-esm/chunk-CUDKNAMV.mjs.map +7 -0
- package/dist/lib/node-esm/{chunk-256IHY6F.mjs → chunk-RQJPEHDY.mjs} +4 -14
- package/dist/lib/node-esm/chunk-RQJPEHDY.mjs.map +7 -0
- package/dist/lib/node-esm/chunk-WL3Z5HOH.mjs +135 -0
- package/dist/lib/node-esm/chunk-WL3Z5HOH.mjs.map +7 -0
- package/dist/lib/node-esm/chunk-WUTK2IJF.mjs +10 -0
- package/dist/lib/node-esm/chunk-WUTK2IJF.mjs.map +7 -0
- package/dist/lib/node-esm/devtools/index.mjs +3 -7
- package/dist/lib/node-esm/devtools/index.mjs.map +2 -2
- package/dist/lib/node-esm/echo/index.mjs +55 -173
- package/dist/lib/node-esm/echo/index.mjs.map +4 -4
- package/dist/lib/node-esm/halo/index.mjs +8 -21
- package/dist/lib/node-esm/halo/index.mjs.map +2 -2
- package/dist/lib/node-esm/index.mjs +99 -12
- package/dist/lib/node-esm/index.mjs.map +4 -4
- package/dist/lib/node-esm/invitations/index.mjs +1 -1
- package/dist/lib/node-esm/mesh/index.mjs +2 -1
- package/dist/lib/node-esm/mesh/index.mjs.map +1 -1
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/lib/node-esm/testing/index.mjs +86 -127
- package/dist/lib/node-esm/testing/index.mjs.map +3 -3
- package/dist/types/src/banner.d.ts.map +1 -1
- package/dist/types/src/client/AgentHostingProvider.d.ts.map +1 -1
- package/dist/types/src/client/ClientProvider.d.ts +1 -11
- package/dist/types/src/client/ClientProvider.d.ts.map +1 -1
- package/dist/types/src/client/useClientStatus.d.ts.map +1 -1
- package/dist/types/src/devtools/useStream.d.ts.map +1 -1
- package/dist/types/src/echo/index.d.ts +2 -3
- package/dist/types/src/echo/index.d.ts.map +1 -1
- package/dist/types/src/echo/useMembers.d.ts +2 -2
- package/dist/types/src/echo/useMembers.d.ts.map +1 -1
- package/dist/types/src/echo/useSpaceInvitations.d.ts +3 -2
- package/dist/types/src/echo/useSpaceInvitations.d.ts.map +1 -1
- package/dist/types/src/echo/useSpaceProperties.d.ts +13 -0
- package/dist/types/src/echo/useSpaceProperties.d.ts.map +1 -0
- package/dist/types/src/echo/useSpaces.d.ts +19 -8
- package/dist/types/src/echo/useSpaces.d.ts.map +1 -1
- package/dist/types/src/echo/useSubscription.d.ts +1 -1
- package/dist/types/src/echo/useSubscription.d.ts.map +1 -1
- package/dist/types/src/echo/useSyncState.d.ts.map +1 -1
- package/dist/types/src/halo/Passkey.stories.d.ts +1 -1
- package/dist/types/src/halo/Passkey.stories.d.ts.map +1 -1
- package/dist/types/src/halo/useHaloInvitations.d.ts.map +1 -1
- package/dist/types/src/halo/useKeyStore.d.ts.map +1 -1
- package/dist/types/src/invitations/useInvitationStatus.d.ts +1 -1
- package/dist/types/src/invitations/useInvitationStatus.d.ts.map +1 -1
- package/dist/types/src/testing/ClientRepeater.d.ts +6 -5
- package/dist/types/src/testing/ClientRepeater.d.ts.map +1 -1
- package/dist/types/src/testing/ClientRepeater.stories.d.ts +0 -1
- package/dist/types/src/testing/ClientRepeater.stories.d.ts.map +1 -1
- package/dist/types/src/testing/context.d.ts +4 -1
- package/dist/types/src/testing/context.d.ts.map +1 -1
- package/dist/types/src/testing/util.d.ts.map +1 -1
- package/dist/types/src/testing/withClientProvider.d.ts +5 -9
- package/dist/types/src/testing/withClientProvider.d.ts.map +1 -1
- package/dist/types/src/testing/withClientProvider.stories.d.ts +3 -2
- package/dist/types/src/testing/withClientProvider.stories.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/docs/README.md +38 -38
- package/moon.yml +3 -2
- package/package.json +38 -59
- package/src/client/ClientProvider.test.tsx +0 -1
- package/src/client/ClientProvider.tsx +12 -27
- package/src/client/useConfig.test.tsx +0 -1
- package/src/echo/index.ts +2 -3
- package/src/echo/useMembers.ts +5 -3
- package/src/echo/useSpaceInvitations.ts +9 -5
- package/src/echo/useSpaceProperties.ts +28 -0
- package/src/echo/useSpaces.test.tsx +7 -14
- package/src/echo/useSpaces.ts +33 -26
- package/src/echo/useSubscription.ts +1 -1
- package/src/echo/useSyncState.ts +3 -7
- package/src/halo/Passkey.stories.tsx +16 -22
- package/src/halo/useDevices.test.tsx +0 -1
- package/src/invitations/useInvitationStatus.ts +9 -8
- package/src/testing/ClientRepeater.stories.tsx +12 -12
- package/src/testing/ClientRepeater.tsx +14 -18
- package/src/testing/context.tsx +4 -2
- package/src/testing/util.tsx +0 -1
- package/src/testing/withClientProvider.stories.tsx +23 -15
- package/src/testing/withClientProvider.tsx +74 -91
- package/tsconfig.json +7 -4
- package/typedoc.json +1 -1
- package/vitest.config.ts +10 -16
- package/.swc/plugins/linux_x86_64_19.0.0/727453fb3a62f7f1d952a41e051ca8a6f88cadc45cee43c6a4d1aa45f9b75665.wasmer-v7 +0 -0
- package/dist/lib/browser/chunk-BXOLJYAY.mjs.map +0 -7
- package/dist/lib/browser/chunk-TA2HGRAF.mjs +0 -309
- package/dist/lib/browser/chunk-TA2HGRAF.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-256IHY6F.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-ZJMNF7GC.mjs +0 -311
- package/dist/lib/node-esm/chunk-ZJMNF7GC.mjs.map +0 -7
- package/dist/types/src/echo/useQuery.d.ts +0 -11
- package/dist/types/src/echo/useQuery.d.ts.map +0 -1
- package/dist/types/src/echo/useQuery.test.d.ts +0 -2
- package/dist/types/src/echo/useQuery.test.d.ts.map +0 -1
- package/dist/types/src/echo/useQueue.d.ts +0 -11
- package/dist/types/src/echo/useQueue.d.ts.map +0 -1
- package/dist/types/src/echo/useSchema.d.ts +0 -8
- package/dist/types/src/echo/useSchema.d.ts.map +0 -1
- package/dist/types/src/playwright/playwright.config.d.ts +0 -3
- package/dist/types/src/playwright/playwright.config.d.ts.map +0 -1
- package/dist/types/src/playwright/smoke.spec.d.ts +0 -2
- package/dist/types/src/playwright/smoke.spec.d.ts.map +0 -1
- package/src/echo/useQuery.test.tsx +0 -129
- package/src/echo/useQuery.ts +0 -56
- package/src/echo/useQueue.ts +0 -74
- package/src/echo/useSchema.ts +0 -54
- package/src/playwright/playwright.config.ts +0 -17
- package/src/playwright/smoke.spec.ts +0 -24
|
@@ -2,8 +2,6 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import '@dxos-theme';
|
|
6
|
-
|
|
7
5
|
import { type Meta, type StoryObj } from '@storybook/react-vite';
|
|
8
6
|
import React, { useCallback } from 'react';
|
|
9
7
|
|
|
@@ -11,12 +9,11 @@ import { Config, PublicKey } from '@dxos/client';
|
|
|
11
9
|
import { invariant } from '@dxos/invariant';
|
|
12
10
|
import { log } from '@dxos/log';
|
|
13
11
|
import { Button } from '@dxos/react-ui';
|
|
14
|
-
import {
|
|
15
|
-
import { withTheme } from '@dxos/
|
|
12
|
+
import { JsonHighlighter } from '@dxos/react-ui-syntax-highlighter';
|
|
13
|
+
import { withTheme } from '@dxos/react-ui/testing';
|
|
16
14
|
|
|
17
15
|
import { useClient } from '../client';
|
|
18
16
|
import { withClientProvider } from '../testing';
|
|
19
|
-
|
|
20
17
|
import { useCredentials } from './useCredentials';
|
|
21
18
|
import { useIdentity } from './useIdentity';
|
|
22
19
|
|
|
@@ -107,7 +104,7 @@ const Test = () => {
|
|
|
107
104
|
|
|
108
105
|
return (
|
|
109
106
|
<>
|
|
110
|
-
<div className='
|
|
107
|
+
<div className='mb-4 flex gap-2'>
|
|
111
108
|
<Button disabled={!!identity} onClick={handleCreateIdentity}>
|
|
112
109
|
Create Identity
|
|
113
110
|
</Button>
|
|
@@ -118,24 +115,13 @@ const Test = () => {
|
|
|
118
115
|
Authenticate with Passkey
|
|
119
116
|
</Button>
|
|
120
117
|
</div>
|
|
121
|
-
<div className='flex flex-col min-w-[28rem] divide-y divide-separator border border-separator rounded'>
|
|
122
|
-
<
|
|
123
|
-
{JSON.stringify({ identity, credentials: credentials.length }, null, 2)}
|
|
124
|
-
</SyntaxHighlighter>
|
|
118
|
+
<div className='flex flex-col min-w-[28rem] divide-y divide-separator border border-separator rounded-sm'>
|
|
119
|
+
<JsonHighlighter data={{ identity, credentials: credentials.length }} />
|
|
125
120
|
</div>
|
|
126
121
|
</>
|
|
127
122
|
);
|
|
128
123
|
};
|
|
129
124
|
|
|
130
|
-
const meta = {
|
|
131
|
-
title: 'sdk/react-client/Passkeys',
|
|
132
|
-
render: Test,
|
|
133
|
-
} satisfies Meta<typeof Config>;
|
|
134
|
-
|
|
135
|
-
export default meta;
|
|
136
|
-
|
|
137
|
-
type Story = StoryObj<typeof meta>;
|
|
138
|
-
|
|
139
125
|
const config = new Config({
|
|
140
126
|
runtime: {
|
|
141
127
|
client: {
|
|
@@ -156,6 +142,14 @@ const config = new Config({
|
|
|
156
142
|
},
|
|
157
143
|
});
|
|
158
144
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
145
|
+
const meta = {
|
|
146
|
+
title: 'sdk/react-client/Passkeys',
|
|
147
|
+
render: Test,
|
|
148
|
+
decorators: [withClientProvider({ config }), withTheme()],
|
|
149
|
+
} satisfies Meta;
|
|
150
|
+
|
|
151
|
+
export default meta;
|
|
152
|
+
|
|
153
|
+
type Story = StoryObj<typeof meta>;
|
|
154
|
+
|
|
155
|
+
export const Default: Story = {};
|
|
@@ -6,7 +6,6 @@ import { renderHook } from '@testing-library/react';
|
|
|
6
6
|
import { describe, expect, test } from 'vitest';
|
|
7
7
|
|
|
8
8
|
import { createClient, createClientContextProvider } from '../testing/util';
|
|
9
|
-
|
|
10
9
|
import { useDevices } from './useDevices';
|
|
11
10
|
|
|
12
11
|
describe('useDevices', () => {
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// Copyright 2022 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { useCallback, useEffect, useMemo, useReducer } from 'react';
|
|
6
6
|
|
|
7
7
|
import { type PublicKey } from '@dxos/client';
|
|
8
8
|
import {
|
|
@@ -24,9 +24,10 @@ interface InvitationReducerState {
|
|
|
24
24
|
status: Invitation.State; // TODO(burdon): Rename state.
|
|
25
25
|
haltedAt?: Invitation.State;
|
|
26
26
|
result: InvitationResult;
|
|
27
|
-
error?:
|
|
27
|
+
error?: Error;
|
|
28
28
|
id?: string;
|
|
29
29
|
multiUse?: boolean;
|
|
30
|
+
shareable?: boolean;
|
|
30
31
|
invitationCode?: string;
|
|
31
32
|
authCode?: string;
|
|
32
33
|
authMethod?: Invitation.AuthMethod;
|
|
@@ -72,7 +73,7 @@ export type InvitationStatus = {
|
|
|
72
73
|
multiUse?: boolean;
|
|
73
74
|
shareable?: boolean;
|
|
74
75
|
result: InvitationResult;
|
|
75
|
-
error?:
|
|
76
|
+
error?: Error;
|
|
76
77
|
cancel(): void;
|
|
77
78
|
// TODO(wittjosiah): Remove?
|
|
78
79
|
connect(observable: CancellableInvitationObservable): void;
|
|
@@ -82,11 +83,11 @@ export type InvitationStatus = {
|
|
|
82
83
|
// Without private key, the invitation code cannot be created.
|
|
83
84
|
// These invitations are only available to be accepted but not shared.
|
|
84
85
|
const isShareableInvitation = (invitation: Invitation) =>
|
|
85
|
-
invitation.authMethod !== Invitation.AuthMethod.KNOWN_PUBLIC_KEY || invitation.guestKeypair?.privateKey;
|
|
86
|
+
invitation.authMethod !== Invitation.AuthMethod.KNOWN_PUBLIC_KEY || !!invitation.guestKeypair?.privateKey;
|
|
86
87
|
|
|
87
88
|
export const useInvitationStatus = (observable?: CancellableInvitationObservable): InvitationStatus => {
|
|
88
|
-
const [state, dispatch] = useReducer
|
|
89
|
-
(prev, action) => {
|
|
89
|
+
const [state, dispatch] = useReducer(
|
|
90
|
+
(prev: InvitationReducerState, action: InvitationAction): InvitationReducerState => {
|
|
90
91
|
log('useInvitationStatus', { action });
|
|
91
92
|
const invitationProps =
|
|
92
93
|
'invitation' in action
|
|
@@ -115,10 +116,10 @@ export const useInvitationStatus = (observable?: CancellableInvitationObservable
|
|
|
115
116
|
action.status === Invitation.State.TIMEOUT) && {
|
|
116
117
|
haltedAt: typeof prev.haltedAt === 'undefined' ? action.haltedAt : prev.haltedAt,
|
|
117
118
|
}),
|
|
118
|
-
}
|
|
119
|
+
};
|
|
119
120
|
},
|
|
120
121
|
null,
|
|
121
|
-
(_arg: null) => {
|
|
122
|
+
(_arg: null): InvitationReducerState => {
|
|
122
123
|
const invitation = observable?.get();
|
|
123
124
|
return {
|
|
124
125
|
status: Invitation.State.INIT,
|
|
@@ -2,41 +2,37 @@
|
|
|
2
2
|
// Copyright 2023 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import '@dxos-theme';
|
|
6
|
-
|
|
7
5
|
import { type Meta, type StoryObj } from '@storybook/react-vite';
|
|
8
6
|
import React from 'react';
|
|
9
7
|
|
|
8
|
+
import { Obj } from '@dxos/echo';
|
|
10
9
|
import { Input } from '@dxos/react-ui';
|
|
11
|
-
import {
|
|
12
|
-
import { withTheme } from '@dxos/
|
|
10
|
+
import { JsonHighlighter } from '@dxos/react-ui-syntax-highlighter';
|
|
11
|
+
import { withTheme } from '@dxos/react-ui/testing';
|
|
13
12
|
|
|
14
13
|
import { useClient } from '../client';
|
|
15
14
|
import { useSpace } from '../echo';
|
|
16
|
-
|
|
17
15
|
import { type ClientRepeatedComponentProps, ClientRepeater } from './ClientRepeater';
|
|
18
16
|
|
|
19
17
|
const meta = {
|
|
20
18
|
title: 'sdk/react-client/ClientRepeater',
|
|
21
|
-
decorators: [withTheme],
|
|
19
|
+
decorators: [withTheme()],
|
|
22
20
|
} satisfies Meta<typeof Input>;
|
|
23
21
|
|
|
24
22
|
export default meta;
|
|
25
23
|
|
|
26
24
|
type Story = StoryObj<typeof meta>;
|
|
27
25
|
|
|
28
|
-
const JsonPanel = ({ value }: { value: any }) =>
|
|
29
|
-
<SyntaxHighlighter language='json'>{JSON.stringify(value, undefined, 2)}</SyntaxHighlighter>
|
|
30
|
-
);
|
|
26
|
+
const JsonPanel = ({ value }: { value: any }) => <JsonHighlighter data={value} />;
|
|
31
27
|
|
|
32
28
|
const ClientStory = () => {
|
|
33
29
|
const client = useClient();
|
|
34
30
|
return <JsonPanel value={client.toJSON()} />;
|
|
35
31
|
};
|
|
36
32
|
|
|
37
|
-
const ClientSpace = ({
|
|
33
|
+
const ClientSpace = ({ spaceId }: ClientRepeatedComponentProps) => {
|
|
38
34
|
const client = useClient();
|
|
39
|
-
const space = useSpace(
|
|
35
|
+
const space = useSpace(spaceId);
|
|
40
36
|
if (!space?.isOpen) {
|
|
41
37
|
return null;
|
|
42
38
|
}
|
|
@@ -47,7 +43,11 @@ const ClientSpace = ({ spaceKey }: ClientRepeatedComponentProps) => {
|
|
|
47
43
|
<Input.TextInput
|
|
48
44
|
placeholder='Name'
|
|
49
45
|
value={space.properties.name}
|
|
50
|
-
onChange={(event) =>
|
|
46
|
+
onChange={(event) =>
|
|
47
|
+
Obj.update(space.properties, (obj) => {
|
|
48
|
+
obj.name = event.target.value;
|
|
49
|
+
})
|
|
50
|
+
}
|
|
51
51
|
/>
|
|
52
52
|
</Input.Root>
|
|
53
53
|
<JsonPanel value={client.toJSON()} />
|
|
@@ -2,20 +2,19 @@
|
|
|
2
2
|
// Copyright 2022 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import React, { type FC,
|
|
5
|
+
import React, { type FC, useRef, useState } from 'react';
|
|
6
6
|
|
|
7
|
-
import { Client
|
|
7
|
+
import { Client } from '@dxos/client';
|
|
8
8
|
import { TestBuilder, performInvitation } from '@dxos/client/testing';
|
|
9
|
-
import { type
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
9
|
+
import { type Type } from '@dxos/echo';
|
|
10
|
+
import { type SpaceId } from '@dxos/keys';
|
|
11
|
+
import { random } from '@dxos/random';
|
|
12
12
|
import { useAsyncEffect } from '@dxos/react-hooks';
|
|
13
13
|
|
|
14
14
|
import { ClientProvider } from '../client';
|
|
15
|
-
|
|
16
15
|
import { type WithClientProviderProps } from './withClientProvider';
|
|
17
16
|
|
|
18
|
-
export type ClientRepeatedComponentProps = { id: number; count: number;
|
|
17
|
+
export type ClientRepeatedComponentProps = { id: number; count: number; spaceId?: SpaceId };
|
|
19
18
|
|
|
20
19
|
export type ClientRepeaterControlsProps = { clients: Client[] };
|
|
21
20
|
|
|
@@ -26,9 +25,9 @@ export type ClientRepeaterProps<P extends ClientRepeatedComponentProps> = {
|
|
|
26
25
|
controls?: FC<ClientRepeaterControlsProps>;
|
|
27
26
|
count?: number;
|
|
28
27
|
clients?: Client[];
|
|
29
|
-
types?:
|
|
28
|
+
types?: Type.AnyObj[];
|
|
30
29
|
args?: Omit<P, 'id' | 'count'>;
|
|
31
|
-
} & Pick<WithClientProviderProps, 'createIdentity' | 'createSpace' | '
|
|
30
|
+
} & Pick<WithClientProviderProps, 'createIdentity' | 'createSpace' | 'onCreateSpace'>;
|
|
32
31
|
|
|
33
32
|
/**
|
|
34
33
|
* Utility component for Storybook stories which sets up clients for n peers.
|
|
@@ -48,14 +47,11 @@ export const ClientRepeater = <P extends ClientRepeatedComponentProps>(props: Cl
|
|
|
48
47
|
types,
|
|
49
48
|
createIdentity,
|
|
50
49
|
createSpace,
|
|
51
|
-
|
|
50
|
+
onCreateSpace,
|
|
52
51
|
} = props;
|
|
53
|
-
useEffect(() => {
|
|
54
|
-
registerSignalsRuntime();
|
|
55
|
-
}, []);
|
|
56
52
|
|
|
57
53
|
const [clients, setClients] = useState(props.clients ?? []);
|
|
58
|
-
const [
|
|
54
|
+
const [spaceId, setSpaceId] = useState<SpaceId>();
|
|
59
55
|
|
|
60
56
|
const testBuilder = useRef(new TestBuilder());
|
|
61
57
|
useAsyncEffect(async () => {
|
|
@@ -70,9 +66,9 @@ export const ClientRepeater = <P extends ClientRepeatedComponentProps>(props: Cl
|
|
|
70
66
|
|
|
71
67
|
if (createSpace) {
|
|
72
68
|
const client = clients[0];
|
|
73
|
-
const space = await client.spaces.create({ name:
|
|
74
|
-
|
|
75
|
-
await
|
|
69
|
+
const space = await client.spaces.create({ name: random.commerce.productName() });
|
|
70
|
+
setSpaceId(space.id);
|
|
71
|
+
await onCreateSpace?.({ client, space }, {});
|
|
76
72
|
await Promise.all(clients.slice(1).flatMap((client) => performInvitation({ host: space, guest: client.spaces })));
|
|
77
73
|
}
|
|
78
74
|
|
|
@@ -88,7 +84,7 @@ export const ClientRepeater = <P extends ClientRepeatedComponentProps>(props: Cl
|
|
|
88
84
|
{Controls && <Controls clients={clients} />}
|
|
89
85
|
{clients.map((client, index) => (
|
|
90
86
|
<ClientProvider key={index} client={client}>
|
|
91
|
-
<Component id={index} count={clients.length}
|
|
87
|
+
<Component id={index} count={clients.length} spaceId={spaceId} {...{ ...props.args }} />
|
|
92
88
|
</ClientProvider>
|
|
93
89
|
))}
|
|
94
90
|
</>
|
package/src/testing/context.tsx
CHANGED
|
@@ -6,14 +6,16 @@ import { createContext, useContext } from 'react';
|
|
|
6
6
|
|
|
7
7
|
import { type Space } from '@dxos/client/echo';
|
|
8
8
|
import { raise } from '@dxos/debug';
|
|
9
|
+
import { type SpaceId } from '@dxos/keys';
|
|
9
10
|
|
|
10
11
|
export type ClientStory = {
|
|
12
|
+
index?: number;
|
|
13
|
+
spaceId?: SpaceId;
|
|
11
14
|
space?: Space;
|
|
12
15
|
};
|
|
13
16
|
|
|
14
|
-
// TODO(wittjosiah): Add to multi-client as well.
|
|
15
17
|
export const ClientStory = createContext<ClientStory | undefined>(undefined);
|
|
16
18
|
|
|
17
|
-
export const
|
|
19
|
+
export const useClientStory = (): ClientStory => {
|
|
18
20
|
return useContext(ClientStory) ?? raise(new Error('Missing ClientStory'));
|
|
19
21
|
};
|
package/src/testing/util.tsx
CHANGED
|
@@ -2,35 +2,33 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import '@dxos-theme';
|
|
6
|
-
|
|
7
5
|
import { type Meta, type StoryObj } from '@storybook/react-vite';
|
|
8
6
|
import React from 'react';
|
|
7
|
+
import { expect, within } from 'storybook/test';
|
|
9
8
|
|
|
10
9
|
import { log } from '@dxos/log';
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
10
|
+
import { JsonHighlighter } from '@dxos/react-ui-syntax-highlighter';
|
|
11
|
+
import { withLayout, withTheme, Loading } from '@dxos/react-ui/testing';
|
|
13
12
|
|
|
14
13
|
import { useClient } from '../client';
|
|
15
14
|
import { type Space, useSpaces } from '../echo';
|
|
16
|
-
|
|
17
15
|
import { type WithClientProviderProps, withClientProvider, withMultiClientProvider } from './withClientProvider';
|
|
18
16
|
|
|
19
17
|
const SpaceInfo = ({ space }: { space: Space }) => {
|
|
20
18
|
const name = space.isOpen ? space.properties.name : '';
|
|
21
|
-
return <
|
|
19
|
+
return <JsonHighlighter data={{ id: space.id, name }} />;
|
|
22
20
|
};
|
|
23
21
|
|
|
24
22
|
const DefaultStory = () => {
|
|
25
23
|
const client = useClient();
|
|
26
24
|
const spaces = useSpaces();
|
|
27
25
|
if (!client) {
|
|
28
|
-
return
|
|
26
|
+
return <Loading data={{ client: !!client }} />;
|
|
29
27
|
}
|
|
30
28
|
|
|
31
29
|
return (
|
|
32
|
-
<div className='flex flex-col
|
|
33
|
-
<
|
|
30
|
+
<div className='flex flex-col divide-y divide-separator border border-separator'>
|
|
31
|
+
<JsonHighlighter data={client.toJSON()} />
|
|
34
32
|
{spaces.map((space) => (
|
|
35
33
|
<SpaceInfo key={space.id} space={space} />
|
|
36
34
|
))}
|
|
@@ -40,7 +38,9 @@ const DefaultStory = () => {
|
|
|
40
38
|
|
|
41
39
|
const meta = {
|
|
42
40
|
title: 'sdk/react-client/withClientProvider',
|
|
43
|
-
render:
|
|
41
|
+
render: DefaultStory,
|
|
42
|
+
decorators: [withTheme()],
|
|
43
|
+
tags: ['test'],
|
|
44
44
|
} satisfies Meta<typeof DefaultStory>;
|
|
45
45
|
|
|
46
46
|
export default meta;
|
|
@@ -53,19 +53,27 @@ const clientProps: WithClientProviderProps = {
|
|
|
53
53
|
onInitialized: async (client) => {
|
|
54
54
|
log.info('onInitialized', { client });
|
|
55
55
|
},
|
|
56
|
-
|
|
57
|
-
log.info('
|
|
56
|
+
onCreateSpace: async ({ client, space }) => {
|
|
57
|
+
log.info('onCreateSpace', { client, space });
|
|
58
58
|
},
|
|
59
59
|
};
|
|
60
60
|
|
|
61
61
|
export const Default: Story = {
|
|
62
|
-
decorators: [withClientProvider(clientProps)
|
|
62
|
+
decorators: [withClientProvider(clientProps)],
|
|
63
|
+
parameters: {
|
|
64
|
+
layout: 'centered',
|
|
65
|
+
},
|
|
66
|
+
play: async ({ canvasElement }) => {
|
|
67
|
+
const canvas = within(canvasElement);
|
|
68
|
+
const initializedText = await canvas.findByText(/"initialized": true/i, {}, { timeout: 30_000 });
|
|
69
|
+
await expect(initializedText).toBeVisible();
|
|
70
|
+
},
|
|
63
71
|
};
|
|
64
72
|
|
|
65
73
|
export const Multiple: Story = {
|
|
66
74
|
decorators: [
|
|
75
|
+
withTheme(),
|
|
76
|
+
withLayout({ classNames: 'grid grid-cols-3' }),
|
|
67
77
|
withMultiClientProvider({ ...clientProps, numClients: 3 }),
|
|
68
|
-
withTheme,
|
|
69
|
-
withLayout({ classNames: ['flex gap-2'] }),
|
|
70
78
|
],
|
|
71
79
|
};
|
|
@@ -3,27 +3,27 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import { type Decorator, type StoryContext } from '@storybook/react';
|
|
6
|
-
import React, {
|
|
7
|
-
import { type FallbackProps, ErrorBoundary as NativeErrorBoundary } from 'react-error-boundary';
|
|
6
|
+
import React, { useCallback, useMemo, useRef, useState } from 'react';
|
|
8
7
|
|
|
9
8
|
import { Trigger } from '@dxos/async';
|
|
10
9
|
import { type Client } from '@dxos/client';
|
|
11
10
|
import { type Space } from '@dxos/client/echo';
|
|
12
11
|
import { TestBuilder, performInvitation } from '@dxos/client/testing';
|
|
12
|
+
import { type SpaceId } from '@dxos/keys';
|
|
13
13
|
import { log } from '@dxos/log';
|
|
14
|
+
import { ErrorBoundary } from '@dxos/react-ui';
|
|
14
15
|
import { type MaybePromise } from '@dxos/util';
|
|
15
16
|
|
|
16
17
|
import { ClientProvider, type ClientProviderProps } from '../client';
|
|
17
|
-
|
|
18
18
|
import { ClientStory } from './context';
|
|
19
19
|
|
|
20
20
|
type InitializeProps = {
|
|
21
21
|
createIdentity?: boolean;
|
|
22
22
|
createSpace?: boolean;
|
|
23
23
|
onInitialized?: (client: Client) => MaybePromise<void>;
|
|
24
|
-
|
|
24
|
+
onCreateIdentity?: (props: { client: Client }, context: StoryContext | any) => MaybePromise<void>;
|
|
25
25
|
// NOTE: context must be untyped until ClientRepeater is removed.
|
|
26
|
-
|
|
26
|
+
onCreateSpace?: (props: { client: Client; space: Space }, context: StoryContext | any) => MaybePromise<void>;
|
|
27
27
|
};
|
|
28
28
|
|
|
29
29
|
/**
|
|
@@ -31,7 +31,7 @@ type InitializeProps = {
|
|
|
31
31
|
*/
|
|
32
32
|
const initializeClient = async (
|
|
33
33
|
client: Client,
|
|
34
|
-
{ createIdentity, createSpace,
|
|
34
|
+
{ createIdentity, createSpace, onCreateIdentity, onCreateSpace, onInitialized }: InitializeProps,
|
|
35
35
|
context: StoryContext,
|
|
36
36
|
): Promise<ClientStory> => {
|
|
37
37
|
await onInitialized?.(client);
|
|
@@ -39,17 +39,18 @@ const initializeClient = async (
|
|
|
39
39
|
if (createIdentity || createSpace) {
|
|
40
40
|
if (!client.halo.identity.get()) {
|
|
41
41
|
await client.halo.createIdentity();
|
|
42
|
-
await
|
|
42
|
+
await onCreateIdentity?.({ client }, context);
|
|
43
43
|
}
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
let space: Space | undefined;
|
|
47
47
|
if (createSpace) {
|
|
48
48
|
space = await client.spaces.create({ name: 'Test Space' });
|
|
49
|
-
await
|
|
49
|
+
await space.waitUntilReady(); // Is this required?
|
|
50
|
+
await onCreateSpace?.({ client, space }, context);
|
|
50
51
|
}
|
|
51
52
|
|
|
52
|
-
return { space };
|
|
53
|
+
return { space, spaceId: space?.id };
|
|
53
54
|
};
|
|
54
55
|
|
|
55
56
|
export type WithClientProviderProps = InitializeProps & Omit<ClientProviderProps, 'onInitialized'>;
|
|
@@ -60,8 +61,8 @@ export type WithClientProviderProps = InitializeProps & Omit<ClientProviderProps
|
|
|
60
61
|
export const withClientProvider = ({
|
|
61
62
|
createIdentity,
|
|
62
63
|
createSpace,
|
|
63
|
-
|
|
64
|
-
|
|
64
|
+
onCreateSpace,
|
|
65
|
+
onCreateIdentity,
|
|
65
66
|
onInitialized,
|
|
66
67
|
...props
|
|
67
68
|
}: WithClientProviderProps = {}): Decorator => {
|
|
@@ -73,8 +74,8 @@ export const withClientProvider = ({
|
|
|
73
74
|
{
|
|
74
75
|
createIdentity,
|
|
75
76
|
createSpace,
|
|
76
|
-
|
|
77
|
-
|
|
77
|
+
onCreateSpace,
|
|
78
|
+
onCreateIdentity,
|
|
78
79
|
onInitialized,
|
|
79
80
|
},
|
|
80
81
|
context,
|
|
@@ -84,7 +85,7 @@ export const withClientProvider = ({
|
|
|
84
85
|
};
|
|
85
86
|
|
|
86
87
|
return (
|
|
87
|
-
<ErrorBoundary>
|
|
88
|
+
<ErrorBoundary name='client-provider'>
|
|
88
89
|
<ClientProvider onInitialized={handleInitialized} {...props}>
|
|
89
90
|
<ClientStory.Provider value={data}>
|
|
90
91
|
<Story />
|
|
@@ -101,105 +102,87 @@ export const withClientProvider = ({
|
|
|
101
102
|
export type WithMultiClientProviderProps = InitializeProps &
|
|
102
103
|
Omit<ClientProviderProps, 'onInitialized'> & { numClients?: number };
|
|
103
104
|
|
|
104
|
-
const MultiClientContext = createContext<{ id: number }>({ id: 0 });
|
|
105
|
-
|
|
106
|
-
export const useMultiClient = () => useContext(MultiClientContext);
|
|
107
|
-
|
|
108
105
|
/**
|
|
109
106
|
* Decorator that creates a scaffold for multiple clients.
|
|
110
107
|
* Orchestrates invitations between a randomly selected host and the remaining clients.
|
|
111
|
-
* NOTE: Should come before withLayout.
|
|
112
108
|
*/
|
|
113
109
|
export const withMultiClientProvider = ({
|
|
114
110
|
numClients = 2,
|
|
115
111
|
createIdentity,
|
|
116
112
|
createSpace,
|
|
117
|
-
|
|
118
|
-
|
|
113
|
+
onCreateSpace,
|
|
114
|
+
onCreateIdentity,
|
|
119
115
|
onInitialized,
|
|
120
116
|
...props
|
|
121
117
|
}: WithMultiClientProviderProps): Decorator => {
|
|
122
118
|
return (Story, context) => {
|
|
123
|
-
const builder = useRef(new TestBuilder());
|
|
124
|
-
const hostRef = useRef<Client>();
|
|
125
119
|
const spaceReady = useRef(new Trigger<Space | undefined>());
|
|
120
|
+
const [spaceId, setSpaceId] = useState<SpaceId>();
|
|
121
|
+
|
|
122
|
+
const clients = useMemo(() => {
|
|
123
|
+
const buidler = new TestBuilder();
|
|
124
|
+
return Array.from({ length: numClients }).map(() => {
|
|
125
|
+
return {
|
|
126
|
+
services: buidler.createLocalClientServices(),
|
|
127
|
+
};
|
|
128
|
+
});
|
|
129
|
+
}, [numClients]);
|
|
126
130
|
|
|
127
131
|
// Handle invitations.
|
|
128
132
|
// NOTE: The zeroth client isn't necessarily the first to be initialized.
|
|
129
|
-
const handleInitialized = async (client: Client, index: number) => {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
133
|
+
const handleInitialized = useCallback(async (client: Client, index: number) => {
|
|
134
|
+
if (index === 0) {
|
|
135
|
+
// Initialize host.
|
|
136
|
+
const { space } = await initializeClient(
|
|
137
|
+
client,
|
|
138
|
+
{
|
|
139
|
+
createIdentity,
|
|
140
|
+
createSpace,
|
|
141
|
+
onCreateSpace,
|
|
142
|
+
onCreateIdentity,
|
|
143
|
+
onInitialized,
|
|
144
|
+
},
|
|
145
|
+
context,
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
log.info('inviting', { index, spaceId: space?.id });
|
|
149
|
+
setSpaceId(space?.id);
|
|
150
|
+
spaceReady.current.wake(space);
|
|
151
|
+
} else {
|
|
152
|
+
// Initialize guest.
|
|
153
|
+
await initializeClient(client, { createIdentity, onInitialized }, context);
|
|
154
|
+
|
|
155
|
+
log.info('waiting', { index });
|
|
156
|
+
const space = await spaceReady.current.wait();
|
|
157
|
+
if (space) {
|
|
158
|
+
log.info('joining', { index, spaceId: space.id });
|
|
159
|
+
await Promise.all(
|
|
160
|
+
performInvitation({
|
|
161
|
+
host: space,
|
|
162
|
+
guest: client.spaces,
|
|
163
|
+
}),
|
|
144
164
|
);
|
|
145
|
-
|
|
146
|
-
spaceReady.current.wake(space);
|
|
147
|
-
log.info('inviting', { index });
|
|
148
|
-
} else {
|
|
149
|
-
await initializeClient(client, { createIdentity, onInitialized }, context);
|
|
150
|
-
const space = await spaceReady.current.wait();
|
|
151
|
-
if (space) {
|
|
152
|
-
log.info('joining', { index });
|
|
153
|
-
await Promise.all(performInvitation({ host: space, guest: client.spaces }));
|
|
154
|
-
}
|
|
155
165
|
}
|
|
156
166
|
}
|
|
157
|
-
};
|
|
167
|
+
}, []);
|
|
158
168
|
|
|
159
169
|
return (
|
|
160
|
-
<
|
|
161
|
-
{
|
|
162
|
-
<
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
170
|
+
<div className='absolute inset-0 grid grid-flow-col auto-cols-fr gap-4 overflow-hidden'>
|
|
171
|
+
{clients.map((client, index) => (
|
|
172
|
+
<ClientProvider
|
|
173
|
+
key={index}
|
|
174
|
+
services={client.services}
|
|
175
|
+
onInitialized={(client) => handleInitialized(client, index)}
|
|
176
|
+
{...props}
|
|
177
|
+
>
|
|
178
|
+
<ClientStory.Provider value={{ index, spaceId }}>
|
|
179
|
+
<ErrorBoundary name='client-provider'>
|
|
180
|
+
<Story />
|
|
181
|
+
</ErrorBoundary>
|
|
182
|
+
</ClientStory.Provider>
|
|
183
|
+
</ClientProvider>
|
|
171
184
|
))}
|
|
172
|
-
</
|
|
185
|
+
</div>
|
|
173
186
|
);
|
|
174
187
|
};
|
|
175
188
|
};
|
|
176
|
-
|
|
177
|
-
const ErrorBoundary = ({ children }: PropsWithChildren) => {
|
|
178
|
-
const [error, setError] = useState<Error>();
|
|
179
|
-
useEffect(() => {
|
|
180
|
-
const handleError = (event: PromiseRejectionEvent) => {
|
|
181
|
-
setError(event.reason);
|
|
182
|
-
};
|
|
183
|
-
|
|
184
|
-
window.addEventListener('unhandledrejection', handleError);
|
|
185
|
-
return () => window.removeEventListener('unhandledrejection', handleError);
|
|
186
|
-
}, []);
|
|
187
|
-
|
|
188
|
-
if (error) {
|
|
189
|
-
return <ErrorFallback error={error} resetErrorBoundary={() => setError(undefined)} />;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
return <NativeErrorBoundary FallbackComponent={ErrorFallback}>{children}</NativeErrorBoundary>;
|
|
193
|
-
};
|
|
194
|
-
|
|
195
|
-
const ErrorFallback = ({ error }: FallbackProps) => {
|
|
196
|
-
const { name, message, stack } =
|
|
197
|
-
error instanceof Error ? error : { name: 'Error', message: String(error), stack: undefined };
|
|
198
|
-
return (
|
|
199
|
-
<div role='alert' className='flex flex-col p-4 gap-4 overflow-auto'>
|
|
200
|
-
<h1 className='text-xl text-red-500'>{name}</h1>
|
|
201
|
-
<div className='text-lg'>{message}</div>
|
|
202
|
-
{stack && <pre className='whitespace-pre-wrap text-sm text-subdued'>{stack}</pre>}
|
|
203
|
-
</div>
|
|
204
|
-
);
|
|
205
|
-
};
|