@dxos/shell 0.6.14-main.2b6a0f3 → 0.6.14-main.f49f251
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/dist/bundle/shell.mjs +15116 -11401
- package/dist/bundle/shell.mjs.map +1 -1
- package/dist/bundle/shell.umd.js +134 -89
- package/dist/bundle/shell.umd.js.map +1 -1
- package/dist/bundle/style.css +1 -1
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/react.mjs +465 -364
- package/dist/lib/browser/react.mjs.map +4 -4
- package/dist/types/src/components/CompoundButton/CompoundButton.stories.js +1 -1
- package/dist/types/src/components/CompoundButton/CompoundButton.stories.js.map +1 -1
- package/dist/types/src/components/DeviceList/DeviceList.d.ts +1 -1
- package/dist/types/src/components/DeviceList/DeviceList.d.ts.map +1 -1
- package/dist/types/src/components/DeviceList/DeviceList.js +2 -2
- package/dist/types/src/components/DeviceList/DeviceList.js.map +1 -1
- package/dist/types/src/components/DeviceList/DeviceListItem.d.ts.map +1 -1
- package/dist/types/src/components/DeviceList/DeviceListItem.js +4 -1
- package/dist/types/src/components/DeviceList/DeviceListItem.js.map +1 -1
- package/dist/types/src/components/DeviceList/DeviceListProps.d.ts +2 -0
- package/dist/types/src/components/DeviceList/DeviceListProps.d.ts.map +1 -1
- package/dist/types/src/components/IdentityList/SpaceMemberList.stories.js +1 -1
- package/dist/types/src/components/IdentityList/SpaceMemberList.stories.js.map +1 -1
- package/dist/types/src/components/InvitationList/InvitationList.stories.js +1 -1
- package/dist/types/src/components/InvitationList/InvitationList.stories.js.map +1 -1
- package/dist/types/src/components/InvitationList/InvitationListContainer.d.ts.map +1 -1
- package/dist/types/src/components/InvitationList/InvitationListContainer.js +2 -2
- package/dist/types/src/components/InvitationList/InvitationListContainer.js.map +1 -1
- package/dist/types/src/components/Panel/Panel.stories.js +1 -1
- package/dist/types/src/components/Panel/Panel.stories.js.map +1 -1
- package/dist/types/src/components/Viewport/Viewport.stories.js +1 -1
- package/dist/types/src/components/Viewport/Viewport.stories.js.map +1 -1
- package/dist/types/src/composites/Shell/Shell.d.ts.map +1 -1
- package/dist/types/src/composites/Shell/Shell.js +11 -1
- package/dist/types/src/composites/Shell/Shell.js.map +1 -1
- package/dist/types/src/panels/IdentityPanel/IdentityPanel.d.ts.map +1 -1
- package/dist/types/src/panels/IdentityPanel/IdentityPanel.js +43 -24
- package/dist/types/src/panels/IdentityPanel/IdentityPanel.js.map +1 -1
- package/dist/types/src/panels/IdentityPanel/IdentityPanel.stories.js +1 -1
- package/dist/types/src/panels/IdentityPanel/IdentityPanel.stories.js.map +1 -1
- package/dist/types/src/panels/IdentityPanel/IdentityPanelProps.d.ts +2 -1
- package/dist/types/src/panels/IdentityPanel/IdentityPanelProps.d.ts.map +1 -1
- package/dist/types/src/panels/IdentityPanel/identityMachine.d.ts +1 -1
- package/dist/types/src/panels/IdentityPanel/identityMachine.d.ts.map +1 -1
- package/dist/types/src/panels/IdentityPanel/identityMachine.js +2 -0
- package/dist/types/src/panels/IdentityPanel/identityMachine.js.map +1 -1
- package/dist/types/src/panels/IdentityPanel/steps/IdentityActionChooser.d.ts +3 -5
- package/dist/types/src/panels/IdentityPanel/steps/IdentityActionChooser.d.ts.map +1 -1
- package/dist/types/src/panels/IdentityPanel/steps/IdentityActionChooser.js +5 -3
- package/dist/types/src/panels/IdentityPanel/steps/IdentityActionChooser.js.map +1 -1
- package/dist/types/src/panels/JoinPanel/JoinPanel.stories.js +1 -1
- package/dist/types/src/panels/JoinPanel/JoinPanel.stories.js.map +1 -1
- package/dist/types/src/panels/JoinPanel/JoinPanelProps.d.ts +1 -1
- package/dist/types/src/panels/JoinPanel/JoinPanelProps.d.ts.map +1 -1
- package/dist/types/src/panels/JoinPanel/joinMachine.d.ts.map +1 -1
- package/dist/types/src/panels/JoinPanel/joinMachine.js +6 -0
- package/dist/types/src/panels/JoinPanel/joinMachine.js.map +1 -1
- package/dist/types/src/panels/JoinPanel/steps/AdditionMethodChooser.d.ts.map +1 -1
- package/dist/types/src/panels/JoinPanel/steps/AdditionMethodChooser.js +1 -3
- package/dist/types/src/panels/JoinPanel/steps/AdditionMethodChooser.js.map +1 -1
- package/dist/types/src/panels/JoinPanel/steps/IdentityInput.d.ts.map +1 -1
- package/dist/types/src/panels/JoinPanel/steps/IdentityInput.js +17 -7
- package/dist/types/src/panels/JoinPanel/steps/IdentityInput.js.map +1 -1
- package/dist/types/src/panels/Panels.stories.js +1 -1
- package/dist/types/src/panels/Panels.stories.js.map +1 -1
- package/dist/types/src/panels/SpacePanel/SpacePanel.stories.js +1 -1
- package/dist/types/src/panels/SpacePanel/SpacePanel.stories.js.map +1 -1
- package/dist/types/src/playwright/invitations-manager.d.ts.map +1 -1
- package/dist/types/src/playwright/invitations-manager.js +2 -2
- package/dist/types/src/playwright/invitations-manager.js.map +1 -1
- package/dist/types/src/playwright/invitations.spec.js +0 -16
- package/dist/types/src/playwright/invitations.spec.js.map +1 -1
- package/dist/types/src/steps/ConfirmReset.d.ts +1 -1
- package/dist/types/src/steps/ConfirmReset.d.ts.map +1 -1
- package/dist/types/src/steps/ConfirmReset.js +6 -2
- package/dist/types/src/steps/ConfirmReset.js.map +1 -1
- package/dist/types/src/stories/Invitations.e2e-stories.d.ts +1 -3
- package/dist/types/src/stories/Invitations.e2e-stories.d.ts.map +1 -1
- package/dist/types/src/stories/Invitations.e2e-stories.js +14 -6
- package/dist/types/src/stories/Invitations.e2e-stories.js.map +1 -1
- package/dist/types/src/translations/locales/en-US.d.ts +2 -0
- package/dist/types/src/translations/locales/en-US.d.ts.map +1 -1
- package/dist/types/src/translations/locales/en-US.js +2 -0
- package/dist/types/src/translations/locales/en-US.js.map +1 -1
- package/package.json +26 -26
- package/src/components/CompoundButton/CompoundButton.stories.tsx +1 -1
- package/src/components/DeviceList/DeviceList.tsx +2 -1
- package/src/components/DeviceList/DeviceListItem.tsx +8 -0
- package/src/components/DeviceList/DeviceListProps.ts +2 -0
- package/src/components/IdentityList/SpaceMemberList.stories.tsx +1 -1
- package/src/components/InvitationList/InvitationList.stories.tsx +1 -1
- package/src/components/InvitationList/InvitationListContainer.tsx +6 -3
- package/src/components/Panel/Panel.stories.tsx +1 -1
- package/src/components/Viewport/Viewport.stories.tsx +1 -1
- package/src/composites/Shell/Shell.tsx +12 -1
- package/src/panels/IdentityPanel/IdentityPanel.stories.tsx +1 -1
- package/src/panels/IdentityPanel/IdentityPanel.tsx +78 -44
- package/src/panels/IdentityPanel/IdentityPanelProps.ts +2 -0
- package/src/panels/IdentityPanel/identityMachine.ts +3 -1
- package/src/panels/IdentityPanel/steps/IdentityActionChooser.tsx +21 -10
- package/src/panels/JoinPanel/JoinPanel.stories.tsx +1 -1
- package/src/panels/JoinPanel/JoinPanelProps.ts +1 -1
- package/src/panels/JoinPanel/joinMachine.ts +7 -0
- package/src/panels/JoinPanel/steps/AdditionMethodChooser.tsx +0 -2
- package/src/panels/JoinPanel/steps/IdentityInput.tsx +22 -10
- package/src/panels/Panels.stories.tsx +1 -1
- package/src/panels/SpacePanel/SpacePanel.stories.tsx +1 -1
- package/src/playwright/invitations-manager.ts +3 -2
- package/src/playwright/invitations.spec.ts +0 -16
- package/src/steps/ConfirmReset.tsx +13 -5
- package/src/stories/Invitations.e2e-stories.tsx +17 -10
- package/src/translations/locales/en-US.ts +2 -0
|
@@ -2,10 +2,11 @@
|
|
|
2
2
|
// Copyright 2023 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import { Key, Plugs, PlugsConnected } from '@phosphor-icons/react';
|
|
5
|
+
import { FirstAidKit, Key, Plugs, PlugsConnected } from '@phosphor-icons/react';
|
|
6
6
|
import React, { useEffect, useMemo, useState } from 'react';
|
|
7
7
|
|
|
8
8
|
import { generateName } from '@dxos/display-name';
|
|
9
|
+
import { invariant } from '@dxos/invariant';
|
|
9
10
|
import { log } from '@dxos/log';
|
|
10
11
|
import { useClient } from '@dxos/react-client';
|
|
11
12
|
import { type Identity, useIdentity, useDevices, useHaloInvitations } from '@dxos/react-client/halo';
|
|
@@ -51,6 +52,7 @@ const IdentityHeading = ({
|
|
|
51
52
|
connectionState,
|
|
52
53
|
onChangeConnectionState,
|
|
53
54
|
}: IdentityPanelHeadingProps) => {
|
|
55
|
+
const client = useClient();
|
|
54
56
|
const fallbackValue = keyToFallback(identity.identityKey);
|
|
55
57
|
const { t } = useTranslation('os');
|
|
56
58
|
const [displayName, setDisplayName] = useState(identity.profile?.displayName ?? '');
|
|
@@ -58,7 +60,7 @@ const IdentityHeading = ({
|
|
|
58
60
|
const [hue, setHueDirectly] = useState<string>(getHueValue(identity));
|
|
59
61
|
const { textValue, setTextValue } = useClipboardContext();
|
|
60
62
|
const identityHex = identity.identityKey.toHex();
|
|
61
|
-
const
|
|
63
|
+
const publicKeyCopied = textValue === identityHex;
|
|
62
64
|
|
|
63
65
|
const setEmoji = (nextEmoji: string) => {
|
|
64
66
|
setEmojiDirectly(nextEmoji);
|
|
@@ -70,9 +72,15 @@ const IdentityHeading = ({
|
|
|
70
72
|
void onUpdateProfile?.({ ...identity.profile, data: { ...identity.profile?.data, hue: nextHue } });
|
|
71
73
|
};
|
|
72
74
|
|
|
73
|
-
const
|
|
75
|
+
const handleRecoveryCode = async () => {
|
|
76
|
+
invariant(client.services.services.IdentityService, 'IdentityService not available');
|
|
77
|
+
// TODO(wittjosiah): This needs a proper api.
|
|
78
|
+
const { seedphrase } = await client.services.services.IdentityService.createRecoveryPhrase();
|
|
79
|
+
void setTextValue(seedphrase);
|
|
80
|
+
};
|
|
74
81
|
|
|
75
|
-
const
|
|
82
|
+
const isConnected = connectionState === ConnectionState.ONLINE;
|
|
83
|
+
const isProduction = client.config.values.runtime?.app?.env?.DX_ENVIRONMENT === 'production';
|
|
76
84
|
|
|
77
85
|
return (
|
|
78
86
|
<Heading titleId={titleId} title={title} corner={<CloseButton onDone={onDone} />}>
|
|
@@ -103,47 +111,61 @@ const IdentityHeading = ({
|
|
|
103
111
|
<Toolbar.Root classNames='flex justify-center items-center gap-1 pt-3'>
|
|
104
112
|
<EmojiPickerToolbarButton emoji={emoji} onChangeEmoji={setEmoji} classNames='bs-[--rail-action]' />
|
|
105
113
|
<HuePickerToolbarButton hue={hue} onChangeHue={setHue} classNames='bs-[--rail-action]' />
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
<
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
<Tooltip.
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
</Tooltip.Content>
|
|
142
|
-
</Tooltip.Portal>
|
|
143
|
-
</Tooltip.Root>
|
|
144
|
-
</Tooltip.Root>
|
|
145
|
-
</>
|
|
114
|
+
<Tooltip.Root>
|
|
115
|
+
<Tooltip.Trigger asChild>
|
|
116
|
+
<Toolbar.Button
|
|
117
|
+
classNames='bs-[--rail-action]'
|
|
118
|
+
data-testid='update-profile-form-copy-key'
|
|
119
|
+
onClick={() => setTextValue(identityHex)}
|
|
120
|
+
>
|
|
121
|
+
<span className='sr-only'>
|
|
122
|
+
{t(publicKeyCopied ? 'copy success label' : 'copy self public key label')}
|
|
123
|
+
</span>
|
|
124
|
+
<Key className={getSize(5)} />
|
|
125
|
+
</Toolbar.Button>
|
|
126
|
+
</Tooltip.Trigger>
|
|
127
|
+
<Tooltip.Portal>
|
|
128
|
+
<Tooltip.Content side='bottom' classNames='z-50'>
|
|
129
|
+
{t(publicKeyCopied ? 'copy success label' : 'copy self public key label')}
|
|
130
|
+
<Tooltip.Arrow />
|
|
131
|
+
</Tooltip.Content>
|
|
132
|
+
</Tooltip.Portal>
|
|
133
|
+
</Tooltip.Root>
|
|
134
|
+
{/* TODO(wittjosiah): Remove. This should have its own flow. */}
|
|
135
|
+
{!isProduction && (
|
|
136
|
+
<Tooltip.Root>
|
|
137
|
+
<Tooltip.Trigger asChild>
|
|
138
|
+
<Toolbar.Button classNames='bs-[--rail-action]' onClick={handleRecoveryCode}>
|
|
139
|
+
<FirstAidKit className={getSize(5)} />
|
|
140
|
+
</Toolbar.Button>
|
|
141
|
+
</Tooltip.Trigger>
|
|
142
|
+
<Tooltip.Portal>
|
|
143
|
+
<Tooltip.Content side='bottom' classNames='z-50'>
|
|
144
|
+
{t('create recovery code label')}
|
|
145
|
+
<Tooltip.Arrow />
|
|
146
|
+
</Tooltip.Content>
|
|
147
|
+
</Tooltip.Portal>
|
|
148
|
+
</Tooltip.Root>
|
|
146
149
|
)}
|
|
150
|
+
<Tooltip.Root>
|
|
151
|
+
<Tooltip.Trigger asChild>
|
|
152
|
+
<Toolbar.Button
|
|
153
|
+
classNames={['bs-[--rail-action]', !isConnected && 'text-error-500']}
|
|
154
|
+
onClick={() =>
|
|
155
|
+
onChangeConnectionState?.(isConnected ? ConnectionState.OFFLINE : ConnectionState.ONLINE)
|
|
156
|
+
}
|
|
157
|
+
>
|
|
158
|
+
<span className='sr-only'>{t(isConnected ? 'disconnect label' : 'connect label')}</span>
|
|
159
|
+
{isConnected ? <PlugsConnected className={getSize(5)} /> : <Plugs className={getSize(5)} />}
|
|
160
|
+
</Toolbar.Button>
|
|
161
|
+
</Tooltip.Trigger>
|
|
162
|
+
<Tooltip.Portal>
|
|
163
|
+
<Tooltip.Content side='bottom' classNames='z-50'>
|
|
164
|
+
{t(isConnected ? 'disconnect label' : 'connect label')}
|
|
165
|
+
<Tooltip.Arrow />
|
|
166
|
+
</Tooltip.Content>
|
|
167
|
+
</Tooltip.Portal>
|
|
168
|
+
</Tooltip.Root>
|
|
147
169
|
</Toolbar.Root>
|
|
148
170
|
</Avatar.Root>
|
|
149
171
|
</Heading>
|
|
@@ -157,6 +179,7 @@ export const IdentityPanelImpl = (props: IdentityPanelImplProps) => {
|
|
|
157
179
|
activeView,
|
|
158
180
|
onUpdateProfile,
|
|
159
181
|
onResetStorage,
|
|
182
|
+
onRecover,
|
|
160
183
|
onJoinNewIdentity,
|
|
161
184
|
IdentityActionChooser: IdentityActionChooserComponent = IdentityActionChooser,
|
|
162
185
|
InvitationManager: InvitationManagerComponent = InvitationManager,
|
|
@@ -207,6 +230,15 @@ export const IdentityPanelImpl = (props: IdentityPanelImplProps) => {
|
|
|
207
230
|
onCancel={onCancelReset}
|
|
208
231
|
/>
|
|
209
232
|
</Viewport.View>
|
|
233
|
+
<Viewport.View classNames={viewStyles} id='confirm recover'>
|
|
234
|
+
<ConfirmReset
|
|
235
|
+
active={activeView === 'confirm recover'}
|
|
236
|
+
{...rest}
|
|
237
|
+
mode='recover'
|
|
238
|
+
onConfirm={onRecover}
|
|
239
|
+
onCancel={onCancelReset}
|
|
240
|
+
/>
|
|
241
|
+
</Viewport.View>
|
|
210
242
|
<Viewport.View classNames={viewStyles} id='confirm reset storage'>
|
|
211
243
|
<ConfirmReset
|
|
212
244
|
active={activeView === 'confirm reset storage'}
|
|
@@ -270,6 +302,8 @@ export const IdentityPanel = ({
|
|
|
270
302
|
return 'device invitation manager';
|
|
271
303
|
case identityState.matches('confirmingResetStorage'):
|
|
272
304
|
return 'confirm reset storage';
|
|
305
|
+
case identityState.matches('confirmingRecover'):
|
|
306
|
+
return 'confirm recover';
|
|
273
307
|
case identityState.matches('confirmingJoinNewIdentity'):
|
|
274
308
|
return 'confirm join new identity';
|
|
275
309
|
default:
|
|
@@ -21,6 +21,7 @@ export type IdentityPanelImplProps = {
|
|
|
21
21
|
| 'device invitation manager'
|
|
22
22
|
| 'identity action chooser'
|
|
23
23
|
| 'confirm join new identity'
|
|
24
|
+
| 'confirm recover'
|
|
24
25
|
| 'confirm reset storage';
|
|
25
26
|
initialDisposition?: IdentityPanelInitialDisposition;
|
|
26
27
|
identity: Identity;
|
|
@@ -29,6 +30,7 @@ export type IdentityPanelImplProps = {
|
|
|
29
30
|
onChangeConnectionState?: (nextState: ConnectionState) => Promise<void>;
|
|
30
31
|
onUpdateProfile?: (profile: NonNullable<Identity['profile']>) => Promise<void>;
|
|
31
32
|
onResetStorage?: () => Promise<void>;
|
|
33
|
+
onRecover?: () => Promise<void>;
|
|
32
34
|
onJoinNewIdentity?: () => Promise<void>;
|
|
33
35
|
createInvitationUrl: (invitationCode: string) => string;
|
|
34
36
|
send?: (event: SingleOrArray<Event<IdentityEvent>>) => void;
|
|
@@ -21,7 +21,7 @@ type IdentityMachineContext = {
|
|
|
21
21
|
};
|
|
22
22
|
|
|
23
23
|
type IdentityChooseActionEvent = {
|
|
24
|
-
type: 'chooseResetStorage' | 'chooseJoinNewIdentity' | 'unchooseAction';
|
|
24
|
+
type: 'chooseResetStorage' | 'chooseRecoverIdentity' | 'chooseJoinNewIdentity' | 'unchooseAction';
|
|
25
25
|
};
|
|
26
26
|
|
|
27
27
|
type IdentitySelectDeviceInvitationEvent = {
|
|
@@ -67,6 +67,7 @@ const identityMachine = createMachine<IdentityMachineContext, IdentityEvent>(
|
|
|
67
67
|
choosingAction: {},
|
|
68
68
|
managingDeviceInvitation: {},
|
|
69
69
|
confirmingResetStorage: {},
|
|
70
|
+
confirmingRecover: {},
|
|
70
71
|
confirmingJoinNewIdentity: {},
|
|
71
72
|
},
|
|
72
73
|
on: {
|
|
@@ -74,6 +75,7 @@ const identityMachine = createMachine<IdentityMachineContext, IdentityEvent>(
|
|
|
74
75
|
deselectInvitation: { target: '.choosingAction', actions: ['unsetInvitation', 'log'] },
|
|
75
76
|
selectInvitation: { target: '.managingDeviceInvitation', actions: ['setInvitation', 'log'] },
|
|
76
77
|
chooseJoinNewIdentity: { target: '.confirmingJoinNewIdentity', actions: 'log' },
|
|
78
|
+
chooseRecoverIdentity: { target: '.confirmingRecover', actions: 'log' },
|
|
77
79
|
chooseResetStorage: { target: '.confirmingResetStorage', actions: 'log' },
|
|
78
80
|
},
|
|
79
81
|
},
|
|
@@ -9,7 +9,7 @@ import { useClient } from '@dxos/react-client';
|
|
|
9
9
|
import { useHaloInvitations, DeviceType } from '@dxos/react-client/halo';
|
|
10
10
|
import { Invitation, InvitationEncoder } from '@dxos/react-client/invitations';
|
|
11
11
|
|
|
12
|
-
import { AgentConfig, type AgentFormProps, DeviceList } from '../../../components';
|
|
12
|
+
import { AgentConfig, type AgentFormProps, DeviceList, type DeviceListProps } from '../../../components';
|
|
13
13
|
import { type IdentityPanelStepProps } from '../IdentityPanelProps';
|
|
14
14
|
|
|
15
15
|
export type IdentityActionChooserProps = IdentityPanelStepProps;
|
|
@@ -33,20 +33,30 @@ export const IdentityActionChooser = (props: IdentityPanelStepProps) => {
|
|
|
33
33
|
}
|
|
34
34
|
send?.({ type: 'selectInvitation', invitation });
|
|
35
35
|
};
|
|
36
|
-
|
|
36
|
+
const isProduction = client.config.values.runtime?.app?.env?.DX_ENVIRONMENT === 'production';
|
|
37
|
+
return (
|
|
38
|
+
<IdentityActionChooserImpl
|
|
39
|
+
{...props}
|
|
40
|
+
onClickAdd={createInvitation}
|
|
41
|
+
onClickJoinExisting={() => send?.({ type: 'chooseJoinNewIdentity' })}
|
|
42
|
+
onClickRecover={isProduction ? undefined : () => send?.({ type: 'chooseRecoverIdentity' })}
|
|
43
|
+
onClickReset={() => send?.({ type: 'chooseResetStorage' })}
|
|
44
|
+
/>
|
|
45
|
+
);
|
|
37
46
|
};
|
|
38
47
|
|
|
39
48
|
export type IdentityActionChooserImplProps = IdentityActionChooserProps &
|
|
40
|
-
Partial<AgentFormProps> &
|
|
41
|
-
|
|
42
|
-
};
|
|
49
|
+
Partial<AgentFormProps> &
|
|
50
|
+
Pick<DeviceListProps, 'onClickAdd' | 'onClickJoinExisting' | 'onClickRecover' | 'onClickReset'>;
|
|
43
51
|
|
|
44
52
|
export const IdentityActionChooserImpl = ({
|
|
45
|
-
send,
|
|
46
|
-
onCreateInvitationClick,
|
|
47
53
|
devices,
|
|
48
54
|
connectionState,
|
|
49
55
|
agentHostingEnabled,
|
|
56
|
+
onClickAdd,
|
|
57
|
+
onClickJoinExisting,
|
|
58
|
+
onClickRecover,
|
|
59
|
+
onClickReset,
|
|
50
60
|
...agentProps
|
|
51
61
|
}: IdentityActionChooserImplProps) => {
|
|
52
62
|
const managedDeviceAvailable = devices.find((device) => device.profile?.type === DeviceType.AGENT_MANAGED);
|
|
@@ -55,9 +65,10 @@ export const IdentityActionChooserImpl = ({
|
|
|
55
65
|
<DeviceList
|
|
56
66
|
devices={devices}
|
|
57
67
|
connectionState={connectionState}
|
|
58
|
-
onClickAdd={
|
|
59
|
-
onClickJoinExisting={
|
|
60
|
-
|
|
68
|
+
onClickAdd={onClickAdd}
|
|
69
|
+
onClickJoinExisting={onClickJoinExisting}
|
|
70
|
+
onClickRecover={onClickRecover}
|
|
71
|
+
onClickReset={onClickReset}
|
|
61
72
|
onAgentDestroy={agentProps.onAgentDestroy!}
|
|
62
73
|
/>
|
|
63
74
|
{!managedDeviceAvailable && agentHostingEnabled && <AgentConfig {...(agentProps as AgentFormProps)} />}
|
|
@@ -30,7 +30,7 @@ const JoinDialog = (args: Partial<JoinPanelImplProps>) => (
|
|
|
30
30
|
);
|
|
31
31
|
|
|
32
32
|
export default {
|
|
33
|
-
title: 'sdk/
|
|
33
|
+
title: 'sdk/shell/JoinPanel',
|
|
34
34
|
component: JoinDialog,
|
|
35
35
|
decorators: [withTheme],
|
|
36
36
|
parameters: { translations, chromatic: { disableSnapshot: false } },
|
|
@@ -14,7 +14,7 @@ import { type FailReason } from '../../types';
|
|
|
14
14
|
|
|
15
15
|
export type JoinPanelMode = 'default' | 'halo-only';
|
|
16
16
|
|
|
17
|
-
export type JoinPanelInitialDisposition = 'default' | 'accept-halo-invitation';
|
|
17
|
+
export type JoinPanelInitialDisposition = 'default' | 'accept-halo-invitation' | 'recover-identity';
|
|
18
18
|
|
|
19
19
|
export type JoinStepProps = Omit<StepProps, 'send' | 'onDone'> & {
|
|
20
20
|
send: JoinSend;
|
|
@@ -312,6 +312,11 @@ const joinMachine = createMachine<JoinMachineContext, JoinEvent>(
|
|
|
312
312
|
target: '#join.choosingIdentity.acceptingHaloInvitation',
|
|
313
313
|
actions: 'log',
|
|
314
314
|
},
|
|
315
|
+
{
|
|
316
|
+
cond: 'initiallyRecoveringIdentity',
|
|
317
|
+
target: '#join.choosingIdentity.recoveringIdentity',
|
|
318
|
+
actions: 'log',
|
|
319
|
+
},
|
|
315
320
|
{ cond: 'noSelectedIdentity', target: 'choosingIdentity', actions: 'log' },
|
|
316
321
|
{ target: 'acceptingSpaceInvitation', actions: 'log' },
|
|
317
322
|
],
|
|
@@ -364,6 +369,8 @@ const joinMachine = createMachine<JoinMachineContext, JoinEvent>(
|
|
|
364
369
|
noSelectedIdentity: ({ identity }, _event) => !identity,
|
|
365
370
|
initiallyAcceptingHaloInvitation: ({ identity, initialDisposition }, _event) =>
|
|
366
371
|
!identity && initialDisposition === 'accept-halo-invitation',
|
|
372
|
+
initiallyRecoveringIdentity: ({ identity, initialDisposition }, _event) =>
|
|
373
|
+
!identity && initialDisposition === 'recover-identity',
|
|
367
374
|
hasHaloUnredeemedCode: ({ halo }, _event) => !!halo.unredeemedCode,
|
|
368
375
|
noSpaceInvitation: ({ space }, _event) => !space.invitation && !space.unredeemedCode,
|
|
369
376
|
},
|
|
@@ -50,8 +50,6 @@ export const AdditionMethodChooser = (viewStateProps: AdditionMethodChooserProps
|
|
|
50
50
|
</CompoundButton>
|
|
51
51
|
<CompoundButton
|
|
52
52
|
{...sharedButtonProps}
|
|
53
|
-
// TODO(mykola): Implement recover.
|
|
54
|
-
disabled={true}
|
|
55
53
|
description={t('recover identity description')}
|
|
56
54
|
before={<Textbox className={getSize(6)} />}
|
|
57
55
|
onClick={() => send({ type: 'recoverIdentity' })}
|
|
@@ -28,16 +28,28 @@ export const IdentityInput = (props: IdentityInputProps) => {
|
|
|
28
28
|
const client = useClient();
|
|
29
29
|
const { t } = useTranslation('os');
|
|
30
30
|
const [validationMessage, setValidationMessage] = useState('');
|
|
31
|
-
const createIdentity = (
|
|
32
|
-
|
|
33
|
-
(
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
31
|
+
const createIdentity = (value: string) => {
|
|
32
|
+
if (isRecover) {
|
|
33
|
+
void client.halo.recoverIdentity({ seedphrase: value }).then(
|
|
34
|
+
(identity) => {
|
|
35
|
+
send?.({ type: 'selectIdentity' as const, identity });
|
|
36
|
+
},
|
|
37
|
+
(error) => {
|
|
38
|
+
log.catch(error);
|
|
39
|
+
setValidationMessage(t('failed to recover identity message'));
|
|
40
|
+
},
|
|
41
|
+
);
|
|
42
|
+
} else {
|
|
43
|
+
void client.halo.createIdentity({ displayName: value }).then(
|
|
44
|
+
(identity) => {
|
|
45
|
+
send?.({ type: 'selectIdentity' as const, identity });
|
|
46
|
+
},
|
|
47
|
+
(error) => {
|
|
48
|
+
log.catch(error);
|
|
49
|
+
setValidationMessage(t('failed to create identity message'));
|
|
50
|
+
},
|
|
51
|
+
);
|
|
52
|
+
}
|
|
41
53
|
};
|
|
42
54
|
return <IdentityInputImpl {...props} onDisplayNameConfirm={createIdentity} validationMessage={validationMessage} />;
|
|
43
55
|
};
|
|
@@ -53,7 +53,7 @@ export const Default = (props: any) => {
|
|
|
53
53
|
Default.parameters = { layout: 'fullscreen' };
|
|
54
54
|
|
|
55
55
|
export default {
|
|
56
|
-
title: 'sdk/
|
|
56
|
+
title: 'sdk/shell/All',
|
|
57
57
|
decorators: [withTheme],
|
|
58
58
|
component: StoryRow,
|
|
59
59
|
parameters: { chromatic: { disableSnapshot: false }, translations: [osTranslations] },
|
|
@@ -32,7 +32,7 @@ export class InvitationsManager extends ScopedShellManager {
|
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
const { page } = await setupPage(this._browser, {
|
|
35
|
-
url: storybookUrl('invitations--default'),
|
|
35
|
+
url: storybookUrl('sdk-shell-invitations--default'),
|
|
36
36
|
});
|
|
37
37
|
|
|
38
38
|
this.page = page;
|
|
@@ -144,7 +144,8 @@ export class InvitationsManager extends ScopedShellManager {
|
|
|
144
144
|
},
|
|
145
145
|
{ id, type, options },
|
|
146
146
|
);
|
|
147
|
-
|
|
147
|
+
|
|
148
|
+
return this._invitationCode.wait({ timeout: 1_000 });
|
|
148
149
|
}
|
|
149
150
|
|
|
150
151
|
async acceptInvitation(id: number, type: 'device' | 'space', invitation: string) {
|
|
@@ -44,7 +44,6 @@ test.describe('Invitations', () => {
|
|
|
44
44
|
await manager.acceptInvitation(1, 'device', invitation);
|
|
45
45
|
await manager.readyToAuthenticate('device', manager.peer(1));
|
|
46
46
|
await manager.authenticateInvitation('device', authCode, manager.peer(1));
|
|
47
|
-
await manager.doneInvitation('device', manager.peer(1));
|
|
48
47
|
|
|
49
48
|
expect(await manager.getDisplayName(0)).toEqual(await manager.getDisplayName(1));
|
|
50
49
|
});
|
|
@@ -56,7 +55,6 @@ test.describe('Invitations', () => {
|
|
|
56
55
|
|
|
57
56
|
await manager.openPanel(1, 'identity');
|
|
58
57
|
await manager.acceptInvitation(1, 'device', invitation);
|
|
59
|
-
await manager.doneInvitation('device', manager.peer(1));
|
|
60
58
|
|
|
61
59
|
expect(await manager.getDisplayName(0)).toEqual(await manager.getDisplayName(1));
|
|
62
60
|
});
|
|
@@ -72,7 +70,6 @@ test.describe('Invitations', () => {
|
|
|
72
70
|
await manager.authenticateInvitation('device', '000000', manager.peer(1));
|
|
73
71
|
await manager.clearAuthCode('device', manager.peer(1));
|
|
74
72
|
await manager.authenticateInvitation('device', authCode, manager.peer(1));
|
|
75
|
-
await manager.doneInvitation('device', manager.peer(1));
|
|
76
73
|
|
|
77
74
|
expect(await manager.getDisplayName(0)).toEqual(await manager.getDisplayName(1));
|
|
78
75
|
});
|
|
@@ -99,7 +96,6 @@ test.describe('Invitations', () => {
|
|
|
99
96
|
await manager.invitationInputContinue('device', manager.peer(1));
|
|
100
97
|
await manager.clearAuthCode('device', manager.peer(1));
|
|
101
98
|
await manager.authenticateInvitation('device', authCode, manager.peer(1));
|
|
102
|
-
await manager.doneInvitation('device', manager.peer(1));
|
|
103
99
|
|
|
104
100
|
expect(await manager.getDisplayName(0)).toEqual(await manager.getDisplayName(1));
|
|
105
101
|
});
|
|
@@ -144,7 +140,6 @@ test.describe('Invitations', () => {
|
|
|
144
140
|
await manager.invitationInputContinue('device', manager.peer(1));
|
|
145
141
|
await manager.clearAuthCode('device', manager.peer(1));
|
|
146
142
|
await manager.authenticateInvitation('device', authCode, manager.peer(1));
|
|
147
|
-
await manager.doneInvitation('device', manager.peer(1));
|
|
148
143
|
|
|
149
144
|
expect(await manager.getDisplayName(0)).toEqual(await manager.getDisplayName(1));
|
|
150
145
|
});
|
|
@@ -167,7 +162,6 @@ test.describe('Invitations', () => {
|
|
|
167
162
|
await manager.invitationInputContinue('device', manager.peer(1));
|
|
168
163
|
await manager.clearAuthCode('device', manager.peer(1));
|
|
169
164
|
await manager.authenticateInvitation('device', authCode, manager.peer(1));
|
|
170
|
-
await manager.doneInvitation('device', manager.peer(1));
|
|
171
165
|
|
|
172
166
|
expect(await manager.getDisplayName(0)).toEqual(await manager.getDisplayName(1));
|
|
173
167
|
});
|
|
@@ -186,7 +180,6 @@ test.describe('Invitations', () => {
|
|
|
186
180
|
await manager.acceptInvitation(1, 'space', invitation);
|
|
187
181
|
await manager.readyToAuthenticate('space', manager.peer(1));
|
|
188
182
|
await manager.authenticateInvitation('space', authCode, manager.peer(1));
|
|
189
|
-
await manager.doneInvitation('space', manager.peer(1));
|
|
190
183
|
|
|
191
184
|
await manager.openPanel(0, 'spaces');
|
|
192
185
|
expect(await manager.getSpaceName(0, 1)).toEqual(await manager.getSpaceName(1, 1));
|
|
@@ -202,11 +195,9 @@ test.describe('Invitations', () => {
|
|
|
202
195
|
await manager.createIdentity(1);
|
|
203
196
|
await manager.openPanel(1, 'join');
|
|
204
197
|
await manager.acceptInvitation(1, 'space', invitation1);
|
|
205
|
-
await manager.doneInvitation('space', manager.peer(1));
|
|
206
198
|
|
|
207
199
|
await manager.openPanel(1, 'join');
|
|
208
200
|
await manager.acceptInvitation(1, 'space', invitation2);
|
|
209
|
-
await manager.doneInvitation('space', manager.peer(1));
|
|
210
201
|
|
|
211
202
|
expect(await manager.getSpaceMembersCount(0)).toEqual(2);
|
|
212
203
|
});
|
|
@@ -220,7 +211,6 @@ test.describe('Invitations', () => {
|
|
|
220
211
|
await manager.createIdentity(1);
|
|
221
212
|
await manager.openPanel(1, 'join');
|
|
222
213
|
await manager.acceptInvitation(1, 'space', invitation);
|
|
223
|
-
await manager.doneInvitation('space', manager.peer(1));
|
|
224
214
|
|
|
225
215
|
await manager.openPanel(0, 'spaces');
|
|
226
216
|
expect(await manager.getSpaceName(0, 1)).toEqual(await manager.getSpaceName(1, 1));
|
|
@@ -242,7 +232,6 @@ test.describe('Invitations', () => {
|
|
|
242
232
|
|
|
243
233
|
await manager.clearAuthCode('space', manager.peer(1));
|
|
244
234
|
await manager.authenticateInvitation('space', authCode, manager.peer(1));
|
|
245
|
-
await manager.doneInvitation('space', manager.peer(1));
|
|
246
235
|
|
|
247
236
|
await manager.openPanel(0, 'spaces');
|
|
248
237
|
expect(await manager.getSpaceName(0, 1)).toEqual(await manager.getSpaceName(1, 1));
|
|
@@ -272,7 +261,6 @@ test.describe('Invitations', () => {
|
|
|
272
261
|
await manager.invitationInputContinue('space', manager.peer(1));
|
|
273
262
|
await manager.clearAuthCode('space', manager.peer(1));
|
|
274
263
|
await manager.authenticateInvitation('space', authCode, manager.peer(1));
|
|
275
|
-
await manager.doneInvitation('space', manager.peer(1));
|
|
276
264
|
|
|
277
265
|
await manager.openPanel(0, 'spaces');
|
|
278
266
|
expect(await manager.getSpaceName(0, 1)).toEqual(await manager.getSpaceName(1, 1));
|
|
@@ -323,7 +311,6 @@ test.describe('Invitations', () => {
|
|
|
323
311
|
await manager.invitationInputContinue('space', manager.peer(1));
|
|
324
312
|
await manager.clearAuthCode('space', manager.peer(1));
|
|
325
313
|
await manager.authenticateInvitation('space', authCode, manager.peer(1));
|
|
326
|
-
await manager.doneInvitation('space', manager.peer(1));
|
|
327
314
|
|
|
328
315
|
await manager.openPanel(0, 'spaces');
|
|
329
316
|
expect(await manager.getSpaceName(0, 1)).toEqual(await manager.getSpaceName(1, 1));
|
|
@@ -349,7 +336,6 @@ test.describe('Invitations', () => {
|
|
|
349
336
|
await manager.invitationInputContinue('space', manager.peer(1));
|
|
350
337
|
await manager.clearAuthCode('space', manager.peer(1));
|
|
351
338
|
await manager.authenticateInvitation('space', authCode, manager.peer(1));
|
|
352
|
-
await manager.doneInvitation('space', manager.peer(1));
|
|
353
339
|
|
|
354
340
|
await manager.openPanel(0, 'spaces');
|
|
355
341
|
expect(await manager.getSpaceName(0, 1)).toEqual(await manager.getSpaceName(1, 1));
|
|
@@ -380,8 +366,6 @@ test.describe('Invitations', () => {
|
|
|
380
366
|
// Helps to ensure both auth codes are fully input (especially in webkit).
|
|
381
367
|
await sleep(100);
|
|
382
368
|
await manager.authenticateInvitation('space', authCode2, manager.peer(2));
|
|
383
|
-
await manager.doneInvitation('space', manager.peer(1));
|
|
384
|
-
await manager.doneInvitation('space', manager.peer(2));
|
|
385
369
|
|
|
386
370
|
await manager.openPanel(0, 'spaces');
|
|
387
371
|
expect(await manager.getSpaceName(0, 1)).toEqual(await manager.getSpaceName(1, 1));
|
|
@@ -15,7 +15,7 @@ import { Action, Actions, StepHeading, Input } from '../components';
|
|
|
15
15
|
type ConfirmResetOptions = Partial<{
|
|
16
16
|
onCancel: () => void;
|
|
17
17
|
onConfirm: () => Promise<void>;
|
|
18
|
-
mode: 'join new identity' | 'reset storage';
|
|
18
|
+
mode: 'join new identity' | 'recover' | 'reset storage';
|
|
19
19
|
}>;
|
|
20
20
|
|
|
21
21
|
export type ConfirmResetProps = StepProps & ConfirmResetOptions;
|
|
@@ -64,7 +64,8 @@ export const ConfirmResetImpl = ({
|
|
|
64
64
|
const { t } = useTranslation('os');
|
|
65
65
|
const confirmationValue = t('confirmation value');
|
|
66
66
|
const [inputValue, setInputValue] = useState('');
|
|
67
|
-
const testIdAffix =
|
|
67
|
+
const testIdAffix =
|
|
68
|
+
mode === 'join new identity' ? 'join-new-identity' : mode === 'recover' ? 'recover' : 'reset-storage';
|
|
68
69
|
return (
|
|
69
70
|
<>
|
|
70
71
|
<div role='none' className='grow flex flex-col gap-2 justify-center'>
|
|
@@ -79,9 +80,16 @@ export const ConfirmResetImpl = ({
|
|
|
79
80
|
{...{ validationMessage }}
|
|
80
81
|
label={
|
|
81
82
|
<StepHeading className='text-start mlb-2'>
|
|
82
|
-
{t(
|
|
83
|
-
|
|
84
|
-
|
|
83
|
+
{t(
|
|
84
|
+
mode === 'join new identity'
|
|
85
|
+
? 'join new identity input label'
|
|
86
|
+
: mode === 'recover'
|
|
87
|
+
? 'recover reset input label'
|
|
88
|
+
: 'reset storage input label',
|
|
89
|
+
{
|
|
90
|
+
confirmationValue,
|
|
91
|
+
},
|
|
92
|
+
)}
|
|
85
93
|
</StepHeading>
|
|
86
94
|
}
|
|
87
95
|
disabled={disabled}
|
|
@@ -14,7 +14,7 @@ import { type Space, type SpaceMember, useSpaces } from '@dxos/react-client/echo
|
|
|
14
14
|
import { useIdentity } from '@dxos/react-client/halo';
|
|
15
15
|
import { Invitation, InvitationEncoder } from '@dxos/react-client/invitations';
|
|
16
16
|
import { ConnectionState, useNetworkStatus } from '@dxos/react-client/mesh';
|
|
17
|
-
import {
|
|
17
|
+
import { useMultiClient, withMultiClientProvider } from '@dxos/react-client/testing';
|
|
18
18
|
import { Button, ButtonGroup, List } from '@dxos/react-ui';
|
|
19
19
|
import { getSize, groupSurface } from '@dxos/react-ui-theme';
|
|
20
20
|
import { withLayout, withTheme } from '@dxos/storybook-utils';
|
|
@@ -24,7 +24,7 @@ import { IdentityPanel, JoinPanel, SpacePanel } from '../panels';
|
|
|
24
24
|
import { osTranslations } from '../translations';
|
|
25
25
|
|
|
26
26
|
export default {
|
|
27
|
-
title: 'Invitations',
|
|
27
|
+
title: 'sdk/shell/Invitations',
|
|
28
28
|
};
|
|
29
29
|
|
|
30
30
|
export type PanelType = Space | 'identity' | 'devices' | 'join';
|
|
@@ -113,8 +113,8 @@ const Panel = ({ id, panel, setPanel }: { id: number; panel?: PanelType; setPane
|
|
|
113
113
|
}
|
|
114
114
|
};
|
|
115
115
|
|
|
116
|
-
const Invitations = (
|
|
117
|
-
const { id }
|
|
116
|
+
const Invitations = () => {
|
|
117
|
+
const { id } = useMultiClient();
|
|
118
118
|
const client = useClient();
|
|
119
119
|
const networkStatus = useNetworkStatus().swarm;
|
|
120
120
|
const identity = useIdentity();
|
|
@@ -220,12 +220,19 @@ const Invitations = (args: { id: number; count: number }) => {
|
|
|
220
220
|
// The issue appears to be related to dynamic imports during client initialization.
|
|
221
221
|
// This does not seem to be a problem in other browsers nor in Safari in the app.
|
|
222
222
|
export const Default = {
|
|
223
|
-
render: (
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
223
|
+
render: () => {
|
|
224
|
+
return (
|
|
225
|
+
// TODO(wittjosiah): Factor out ClipboardProvider to react-ui and include in layout decorator.
|
|
226
|
+
<ClipboardProvider>
|
|
227
|
+
<Invitations />
|
|
228
|
+
</ClipboardProvider>
|
|
229
|
+
);
|
|
230
|
+
},
|
|
231
|
+
decorators: [
|
|
232
|
+
withMultiClientProvider({ numClients: 3 }),
|
|
233
|
+
withLayout({ tooltips: true, classNames: 'grid grid-cols-3' }),
|
|
234
|
+
withTheme,
|
|
235
|
+
],
|
|
229
236
|
parameters: {
|
|
230
237
|
chromatic: { disableSnapshot: true },
|
|
231
238
|
translations: [osTranslations],
|
|
@@ -153,4 +153,6 @@ export const os = {
|
|
|
153
153
|
'agent requested description':
|
|
154
154
|
'The agent service is creating an agent for you. Once the agent connects, it will display in the devices list.',
|
|
155
155
|
'authenticating label': 'Authenticating…',
|
|
156
|
+
'create recovery code label': 'Create recovery code',
|
|
157
|
+
'choose recover identity label': 'Recover identity',
|
|
156
158
|
};
|