@dxos/shell 0.3.11-main.a8d7a97 → 0.3.11-main.afa73e0
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 +6989 -6763
- package/dist/bundle/shell.mjs.map +1 -1
- package/dist/bundle/shell.umd.js +64 -64
- 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 +497 -411
- package/dist/lib/browser/react.mjs.map +4 -4
- package/dist/lib/browser/testing/index.mjs +35 -14
- package/dist/lib/browser/testing/index.mjs.map +3 -3
- package/dist/types/src/components/IdentityList/SpaceMemberList.js +1 -1
- package/dist/types/src/components/IdentityList/SpaceMemberList.js.map +1 -1
- package/dist/types/src/components/Panel/Action.d.ts.map +1 -1
- package/dist/types/src/components/Panel/Action.js +3 -18
- package/dist/types/src/components/Panel/Action.js.map +1 -1
- package/dist/types/src/composites/Shell/Shell.d.ts.map +1 -1
- package/dist/types/src/composites/Shell/Shell.js +5 -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 +11 -7
- package/dist/types/src/panels/IdentityPanel/IdentityPanel.js.map +1 -1
- package/dist/types/src/panels/IdentityPanel/IdentityPanelProps.d.ts +12 -2
- 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 -2
- package/dist/types/src/panels/IdentityPanel/identityMachine.js.map +1 -1
- package/dist/types/src/panels/IdentityPanel/steps/AgentForm.d.ts +3 -0
- package/dist/types/src/panels/IdentityPanel/steps/AgentForm.d.ts.map +1 -0
- package/dist/types/src/panels/IdentityPanel/steps/AgentForm.js +26 -0
- package/dist/types/src/panels/IdentityPanel/steps/AgentForm.js.map +1 -0
- package/dist/types/src/panels/IdentityPanel/steps/IdentityActionChooser.d.ts.map +1 -1
- package/dist/types/src/panels/IdentityPanel/steps/IdentityActionChooser.js +1 -1
- package/dist/types/src/panels/IdentityPanel/steps/IdentityActionChooser.js.map +1 -1
- package/dist/types/src/panels/IdentityPanel/steps/SignOutChooser.d.ts +16 -0
- package/dist/types/src/panels/IdentityPanel/steps/SignOutChooser.d.ts.map +1 -0
- package/dist/types/src/panels/IdentityPanel/steps/SignOutChooser.js +40 -0
- package/dist/types/src/panels/IdentityPanel/steps/SignOutChooser.js.map +1 -0
- package/dist/types/src/panels/IdentityPanel/steps/index.d.ts +2 -1
- package/dist/types/src/panels/IdentityPanel/steps/index.d.ts.map +1 -1
- package/dist/types/src/panels/IdentityPanel/steps/index.js +2 -1
- package/dist/types/src/panels/IdentityPanel/steps/index.js.map +1 -1
- package/dist/types/src/panels/IdentityPanel/useAgentHandlers.d.ts +15 -0
- package/dist/types/src/panels/IdentityPanel/useAgentHandlers.d.ts.map +1 -0
- package/dist/types/src/panels/IdentityPanel/useAgentHandlers.js +78 -0
- package/dist/types/src/panels/IdentityPanel/useAgentHandlers.js.map +1 -0
- package/dist/types/src/panels/JoinPanel/joinMachine.d.ts +1 -1
- package/dist/types/src/panels/JoinPanel/joinMachine.d.ts.map +1 -1
- package/dist/types/src/panels/JoinPanel/joinMachine.js +14 -17
- package/dist/types/src/panels/JoinPanel/joinMachine.js.map +1 -1
- package/dist/types/src/panels/JoinPanel/steps/AdditionMethodChooser.js +3 -3
- package/dist/types/src/panels/JoinPanel/steps/AdditionMethodChooser.js.map +1 -1
- package/dist/types/src/panels/JoinPanel/steps/InvitationInput.d.ts.map +1 -1
- package/dist/types/src/panels/JoinPanel/steps/InvitationInput.js +13 -6
- package/dist/types/src/panels/JoinPanel/steps/InvitationInput.js.map +1 -1
- package/dist/types/src/panels/JoinPanel/steps/ResetIdentity.js +1 -1
- package/dist/types/src/panels/JoinPanel/steps/ResetIdentity.js.map +1 -1
- package/dist/types/src/playwright/invitations-manager.d.ts +1 -0
- package/dist/types/src/playwright/invitations-manager.d.ts.map +1 -1
- package/dist/types/src/playwright/invitations-manager.js +4 -1
- package/dist/types/src/playwright/invitations-manager.js.map +1 -1
- package/dist/types/src/playwright/invitations.spec.js +15 -0
- package/dist/types/src/playwright/invitations.spec.js.map +1 -1
- package/dist/types/src/testing/shell-manager.d.ts +5 -4
- package/dist/types/src/testing/shell-manager.d.ts.map +1 -1
- package/dist/types/src/testing/shell-manager.js +36 -12
- package/dist/types/src/testing/shell-manager.js.map +1 -1
- package/dist/types/src/translations/locales/en-US.d.ts +5 -0
- package/dist/types/src/translations/locales/en-US.d.ts.map +1 -1
- package/dist/types/src/translations/locales/en-US.js +5 -0
- package/dist/types/src/translations/locales/en-US.js.map +1 -1
- package/package.json +21 -21
- package/src/components/IdentityList/SpaceMemberList.tsx +1 -1
- package/src/components/Panel/Action.tsx +4 -19
- package/src/composites/Shell/Shell.tsx +7 -1
- package/src/panels/IdentityPanel/IdentityPanel.tsx +21 -10
- package/src/panels/IdentityPanel/IdentityPanelProps.ts +15 -2
- package/src/panels/IdentityPanel/identityMachine.ts +3 -3
- package/src/panels/IdentityPanel/steps/AgentForm.tsx +61 -0
- package/src/panels/IdentityPanel/steps/IdentityActionChooser.tsx +2 -3
- package/src/panels/IdentityPanel/steps/SignOutChooser.tsx +84 -0
- package/src/panels/IdentityPanel/steps/index.ts +2 -1
- package/src/panels/IdentityPanel/useAgentHandlers.ts +88 -0
- package/src/panels/JoinPanel/joinMachine.ts +15 -18
- package/src/panels/JoinPanel/steps/AdditionMethodChooser.tsx +3 -3
- package/src/panels/JoinPanel/steps/InvitationInput.tsx +7 -1
- package/src/panels/JoinPanel/steps/ResetIdentity.tsx +1 -1
- package/src/playwright/invitations-manager.ts +5 -1
- package/src/playwright/invitations.spec.ts +19 -0
- package/src/testing/shell-manager.ts +40 -13
- package/src/translations/locales/en-US.ts +5 -0
- package/dist/types/src/panels/IdentityPanel/steps/AgentPanel.d.ts +0 -19
- package/dist/types/src/panels/IdentityPanel/steps/AgentPanel.d.ts.map +0 -1
- package/dist/types/src/panels/IdentityPanel/steps/AgentPanel.js +0 -122
- package/dist/types/src/panels/IdentityPanel/steps/AgentPanel.js.map +0 -1
- package/src/panels/IdentityPanel/steps/AgentPanel.tsx +0 -165
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import React from 'react';
|
|
6
|
+
|
|
7
|
+
import { useTranslation } from '@dxos/react-ui';
|
|
8
|
+
|
|
9
|
+
import { Action, Actions, StepHeading, Input } from '../../../components';
|
|
10
|
+
import { type AgentFormProps, type IdentityPanelStepProps } from '../IdentityPanelProps';
|
|
11
|
+
|
|
12
|
+
export const AgentForm = (props: IdentityPanelStepProps & AgentFormProps) => {
|
|
13
|
+
const {
|
|
14
|
+
active,
|
|
15
|
+
onAgentCreate,
|
|
16
|
+
onAgentDestroy,
|
|
17
|
+
send,
|
|
18
|
+
validationMessage,
|
|
19
|
+
agentStatus,
|
|
20
|
+
agentActive,
|
|
21
|
+
agentProviderDisabled,
|
|
22
|
+
} = props;
|
|
23
|
+
const disabled = !active;
|
|
24
|
+
const { t } = useTranslation('os');
|
|
25
|
+
return (
|
|
26
|
+
<>
|
|
27
|
+
<div role='none' className='grow flex flex-col justify-center'>
|
|
28
|
+
<Input
|
|
29
|
+
{...{ validationMessage }}
|
|
30
|
+
label={<StepHeading>Agent Status</StepHeading>}
|
|
31
|
+
disabled={disabled}
|
|
32
|
+
data-testid='agent-status'
|
|
33
|
+
value={agentStatus}
|
|
34
|
+
/>
|
|
35
|
+
</div>
|
|
36
|
+
<Actions>
|
|
37
|
+
<Action
|
|
38
|
+
disabled={agentActive || agentProviderDisabled}
|
|
39
|
+
onClick={() => onAgentCreate?.()}
|
|
40
|
+
data-testid={'create-agent'}
|
|
41
|
+
>
|
|
42
|
+
Create Agent
|
|
43
|
+
</Action>
|
|
44
|
+
<Action
|
|
45
|
+
disabled={!agentActive || agentProviderDisabled}
|
|
46
|
+
onClick={() => onAgentDestroy?.()}
|
|
47
|
+
data-testid={'destroy-agent'}
|
|
48
|
+
>
|
|
49
|
+
Destroy Agent
|
|
50
|
+
</Action>
|
|
51
|
+
<Action
|
|
52
|
+
disabled={disabled}
|
|
53
|
+
onClick={() => send?.({ type: 'unchooseAction' })}
|
|
54
|
+
data-testid={'update-profile-form-back'}
|
|
55
|
+
>
|
|
56
|
+
{t('back label')}
|
|
57
|
+
</Action>
|
|
58
|
+
</Actions>
|
|
59
|
+
</>
|
|
60
|
+
);
|
|
61
|
+
};
|
|
@@ -89,9 +89,8 @@ export const IdentityActionChooserImpl = ({
|
|
|
89
89
|
<CaretRight weight='bold' className={getSize(4)} />
|
|
90
90
|
</Action>
|
|
91
91
|
<Action
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
onClick={() => {} /* send({ type: 'chooseSignOut' }) */}
|
|
92
|
+
data-testid='devices-panel.sign-out'
|
|
93
|
+
onClick={() => send?.({ type: 'chooseSignOut' })}
|
|
95
94
|
classNames='plb-4'
|
|
96
95
|
>
|
|
97
96
|
<Power className={getSize(6)} />
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { Warning } from '@phosphor-icons/react';
|
|
6
|
+
import React from 'react';
|
|
7
|
+
import { type Event, type SingleOrArray } from 'xstate';
|
|
8
|
+
|
|
9
|
+
import { Message, useTranslation } from '@dxos/react-ui';
|
|
10
|
+
import { getSize, mx } from '@dxos/react-ui-theme';
|
|
11
|
+
|
|
12
|
+
import { Action, Actions } from '../../../components';
|
|
13
|
+
import { type IdentityPanelStepProps } from '../IdentityPanelProps';
|
|
14
|
+
import { type IdentityEvent } from '../identityMachine';
|
|
15
|
+
|
|
16
|
+
export interface SignOutChooserProps extends Omit<IdentityPanelStepProps, 'send'> {
|
|
17
|
+
send?: (event: SingleOrArray<Event<IdentityEvent>>) => void;
|
|
18
|
+
onResetDevice?: () => Promise<void>;
|
|
19
|
+
onJoinNewIdentity?: () => void;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const SignOutChooser = (props: SignOutChooserProps) => {
|
|
23
|
+
const { t } = useTranslation('os');
|
|
24
|
+
const onResetDevice = () => {
|
|
25
|
+
if (window.confirm(t('reset device confirm message'))) {
|
|
26
|
+
void props.onResetDevice?.();
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
return (
|
|
30
|
+
<SignOutChooserImpl
|
|
31
|
+
disabled={!props.active}
|
|
32
|
+
onBack={() => props.send?.({ type: 'unchooseAction' })}
|
|
33
|
+
onResetDevice={onResetDevice}
|
|
34
|
+
onJoinNewIdentity={props.onJoinNewIdentity}
|
|
35
|
+
/>
|
|
36
|
+
);
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export type SignOutChooserImplProps = {
|
|
40
|
+
disabled?: boolean;
|
|
41
|
+
onResetDevice?: () => void;
|
|
42
|
+
onJoinNewIdentity?: () => void;
|
|
43
|
+
onBack?: () => void;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const SignOutChooserImpl = ({ disabled, onResetDevice, onJoinNewIdentity, onBack }: SignOutChooserImplProps) => {
|
|
47
|
+
const { t } = useTranslation('os');
|
|
48
|
+
return (
|
|
49
|
+
<>
|
|
50
|
+
<div role='none' className='grow flex flex-col justify-center'>
|
|
51
|
+
<Message.Root valence='error'>
|
|
52
|
+
<Message.Title>
|
|
53
|
+
<Warning className={mx(getSize(6), 'inline mie-2')} />
|
|
54
|
+
{t('sign out chooser title')}
|
|
55
|
+
</Message.Title>
|
|
56
|
+
<Message.Body>{t('sign out chooser message')}</Message.Body>
|
|
57
|
+
</Message.Root>
|
|
58
|
+
</div>
|
|
59
|
+
<Actions>
|
|
60
|
+
<Action disabled={disabled} onClick={onBack}>
|
|
61
|
+
{t('back label')}
|
|
62
|
+
</Action>
|
|
63
|
+
<Action
|
|
64
|
+
variant='primary'
|
|
65
|
+
data-testid='sign-out.join-new-identity'
|
|
66
|
+
classNames='!bg-error-500 hover:!bg-error-550'
|
|
67
|
+
disabled={disabled}
|
|
68
|
+
onClick={onJoinNewIdentity}
|
|
69
|
+
>
|
|
70
|
+
{t('join new identity label')}
|
|
71
|
+
</Action>
|
|
72
|
+
<Action
|
|
73
|
+
variant='primary'
|
|
74
|
+
data-testid='sign-out.reset-device'
|
|
75
|
+
classNames='!bg-error-500 hover:!bg-error-550'
|
|
76
|
+
disabled={disabled}
|
|
77
|
+
onClick={onResetDevice}
|
|
78
|
+
>
|
|
79
|
+
{t('reset device label')}
|
|
80
|
+
</Action>
|
|
81
|
+
</Actions>
|
|
82
|
+
</>
|
|
83
|
+
);
|
|
84
|
+
};
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { useCallback, useEffect, useState } from 'react';
|
|
6
|
+
|
|
7
|
+
import { type CancellableInvitation } from '@dxos/client-protocol';
|
|
8
|
+
import { invariant } from '@dxos/invariant';
|
|
9
|
+
import { useAgentHostingProviderClient, type Client } from '@dxos/react-client';
|
|
10
|
+
import { type Identity } from '@dxos/react-client/halo';
|
|
11
|
+
import { Invitation, InvitationEncoder } from '@dxos/react-client/invitations';
|
|
12
|
+
|
|
13
|
+
export const useAgentHandlers = ({
|
|
14
|
+
client,
|
|
15
|
+
invitations,
|
|
16
|
+
identity,
|
|
17
|
+
}: {
|
|
18
|
+
client: Client;
|
|
19
|
+
invitations: CancellableInvitation[];
|
|
20
|
+
identity: Identity | null;
|
|
21
|
+
}) => {
|
|
22
|
+
const [validationMessage, setValidationMessage] = useState('');
|
|
23
|
+
const [agentStatus, setAgentStatus] = useState('');
|
|
24
|
+
const [agentActive, setAgentActive] = useState(false);
|
|
25
|
+
const agentHostingProviderClient = useAgentHostingProviderClient(client.config);
|
|
26
|
+
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
const fetchAgentStatus = async () => {
|
|
29
|
+
// TODO(nf): if agent exists, subscribe to updates instead of oneshot status
|
|
30
|
+
if (agentHostingProviderClient) {
|
|
31
|
+
const agentStatus = await agentHostingProviderClient?.getAgent(identity?.identityKey.truncate() ?? 'foo');
|
|
32
|
+
if (agentStatus) {
|
|
33
|
+
setAgentActive(true);
|
|
34
|
+
setAgentStatus(agentStatus);
|
|
35
|
+
} else {
|
|
36
|
+
setAgentActive(false);
|
|
37
|
+
setValidationMessage('No agent deployed via provider.');
|
|
38
|
+
}
|
|
39
|
+
} else {
|
|
40
|
+
setValidationMessage('No agent provider configured.');
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
void fetchAgentStatus();
|
|
44
|
+
}, []);
|
|
45
|
+
|
|
46
|
+
const handleAgentCreate = useCallback(async (invitation: Invitation) => {
|
|
47
|
+
const invitationCode = InvitationEncoder.encode(invitation);
|
|
48
|
+
if (invitation.state === Invitation.State.CONNECTING) {
|
|
49
|
+
console.log(JSON.stringify({ invitationCode, authCode: invitation.authCode }));
|
|
50
|
+
invariant(identity, 'Identity not found');
|
|
51
|
+
if (agentHostingProviderClient) {
|
|
52
|
+
try {
|
|
53
|
+
const res = await agentHostingProviderClient?.createAgent(invitationCode, identity.identityKey.truncate());
|
|
54
|
+
// TODO(nf): human-consumable response from API
|
|
55
|
+
setAgentStatus(res);
|
|
56
|
+
setAgentActive(true);
|
|
57
|
+
setValidationMessage('');
|
|
58
|
+
} catch (err: any) {
|
|
59
|
+
setValidationMessage(`error creating agent: ${err.message}`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}, []);
|
|
64
|
+
|
|
65
|
+
const onAgentCreate = async () => {
|
|
66
|
+
invitations.forEach((invitation) => invitation.cancel());
|
|
67
|
+
|
|
68
|
+
// TODO(nf): do this work in the hosting provider client?
|
|
69
|
+
const invitation = client.halo.share({ type: Invitation.Type.MULTIUSE, authMethod: Invitation.AuthMethod.NONE });
|
|
70
|
+
|
|
71
|
+
invitation.subscribe(handleAgentCreate);
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const onAgentDestroy = async () => {
|
|
75
|
+
const identity = client.halo.identity.get();
|
|
76
|
+
invariant(identity, 'Identity not found');
|
|
77
|
+
try {
|
|
78
|
+
await agentHostingProviderClient?.destroyAgent(identity.identityKey.truncate());
|
|
79
|
+
setValidationMessage('No agent deployed via provider.');
|
|
80
|
+
setAgentStatus('');
|
|
81
|
+
setAgentActive(false);
|
|
82
|
+
} catch (err: any) {
|
|
83
|
+
setValidationMessage(`error destroying agent: ${err.message}`);
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
return { onAgentCreate, onAgentDestroy, validationMessage, agentStatus, agentActive };
|
|
88
|
+
};
|
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
} from 'xstate';
|
|
16
16
|
|
|
17
17
|
import { log } from '@dxos/log';
|
|
18
|
-
import type
|
|
18
|
+
import { type Client, AlreadyJoinedError } from '@dxos/react-client';
|
|
19
19
|
import { type Identity } from '@dxos/react-client/halo';
|
|
20
20
|
import { type AuthenticatingInvitationObservable, Invitation, InvitationEncoder } from '@dxos/react-client/invitations';
|
|
21
21
|
|
|
@@ -133,6 +133,12 @@ const getInvitationSubscribable = (
|
|
|
133
133
|
}
|
|
134
134
|
},
|
|
135
135
|
(error: any) => {
|
|
136
|
+
if (error instanceof AlreadyJoinedError) {
|
|
137
|
+
log('[already joined]', { Kind, error });
|
|
138
|
+
next({ type: `success${Kind}Invitation`, invitation: invitation.get() });
|
|
139
|
+
return complete?.();
|
|
140
|
+
}
|
|
141
|
+
|
|
136
142
|
log.error('[invitation errored]', { Kind, error });
|
|
137
143
|
next({ type: `fail${Kind}Invitation`, reason: 'error' });
|
|
138
144
|
return onError?.(error);
|
|
@@ -437,9 +443,8 @@ const useJoinMachine = (
|
|
|
437
443
|
({ halo }: JoinMachineContext) => {
|
|
438
444
|
if (halo.unredeemedCode) {
|
|
439
445
|
try {
|
|
440
|
-
const
|
|
441
|
-
|
|
442
|
-
);
|
|
446
|
+
const invitation = InvitationEncoder.decode(defaultCodeFromUrl('halo', halo.unredeemedCode));
|
|
447
|
+
const invitationObservable = client.halo.join(invitation);
|
|
443
448
|
return {
|
|
444
449
|
...halo,
|
|
445
450
|
invitationObservable,
|
|
@@ -461,20 +466,12 @@ const useJoinMachine = (
|
|
|
461
466
|
if (space.unredeemedCode) {
|
|
462
467
|
try {
|
|
463
468
|
const invitation = InvitationEncoder.decode(defaultCodeFromUrl('space', space.unredeemedCode));
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
} else {
|
|
471
|
-
const invitationObservable = client.spaces.join(invitation);
|
|
472
|
-
return {
|
|
473
|
-
...space,
|
|
474
|
-
invitationObservable,
|
|
475
|
-
invitationSubscribable: getInvitationSubscribable('Space', invitationObservable),
|
|
476
|
-
};
|
|
477
|
-
}
|
|
469
|
+
const invitationObservable = client.spaces.join(invitation);
|
|
470
|
+
return {
|
|
471
|
+
...space,
|
|
472
|
+
invitationObservable,
|
|
473
|
+
invitationSubscribable: getInvitationSubscribable('Space', invitationObservable),
|
|
474
|
+
};
|
|
478
475
|
} catch (err) {
|
|
479
476
|
log.error('Could not redeem space invitation code', err);
|
|
480
477
|
return space;
|
|
@@ -35,7 +35,7 @@ export const AdditionMethodChooser = (viewStateProps: AdditionMethodChooserProps
|
|
|
35
35
|
before={<Plus className={getSize(6)} />}
|
|
36
36
|
onClick={() => send({ type: 'createIdentity' })}
|
|
37
37
|
data-autofocus='choosingAuthMethod'
|
|
38
|
-
data-testid='create-identity'
|
|
38
|
+
data-testid='identity-chooser.create-identity'
|
|
39
39
|
>
|
|
40
40
|
{t('create identity label')}
|
|
41
41
|
</CompoundButton>
|
|
@@ -44,7 +44,7 @@ export const AdditionMethodChooser = (viewStateProps: AdditionMethodChooserProps
|
|
|
44
44
|
description={t('join identity description')}
|
|
45
45
|
before={<QrCode className={getSize(6)} />}
|
|
46
46
|
onClick={() => send({ type: 'acceptHaloInvitation' })}
|
|
47
|
-
data-testid='join-identity'
|
|
47
|
+
data-testid='identity-chooser.join-identity'
|
|
48
48
|
>
|
|
49
49
|
{t('join identity label')}
|
|
50
50
|
</CompoundButton>
|
|
@@ -55,7 +55,7 @@ export const AdditionMethodChooser = (viewStateProps: AdditionMethodChooserProps
|
|
|
55
55
|
description={t('recover identity description')}
|
|
56
56
|
before={<Textbox className={getSize(6)} />}
|
|
57
57
|
onClick={() => send({ type: 'recoverIdentity' })}
|
|
58
|
-
data-testid='recover-identity'
|
|
58
|
+
data-testid='identity-chooser.recover-identity'
|
|
59
59
|
>
|
|
60
60
|
{t('recover identity label')}
|
|
61
61
|
</CompoundButton>
|
|
@@ -39,12 +39,17 @@ export const InvitationInput = (props: InvitationInputProps) => {
|
|
|
39
39
|
unredeemedCode && setInputValue(unredeemedCode ?? '');
|
|
40
40
|
}, [unredeemedCode]);
|
|
41
41
|
|
|
42
|
-
const handleNext = () =>
|
|
42
|
+
const handleNext = () => {
|
|
43
|
+
if (inputValue.trim().length === 0) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
43
47
|
send({
|
|
44
48
|
type: `set${Kind}InvitationCode`,
|
|
45
49
|
code: invitationCodeFromUrl(inputValue),
|
|
46
50
|
...(Kind === 'Space' && { succeededKeys }),
|
|
47
51
|
});
|
|
52
|
+
};
|
|
48
53
|
|
|
49
54
|
const exitAction = (
|
|
50
55
|
<Action
|
|
@@ -62,6 +67,7 @@ export const InvitationInput = (props: InvitationInputProps) => {
|
|
|
62
67
|
<div role='none' className='grow flex flex-col justify-center'>
|
|
63
68
|
<Input
|
|
64
69
|
label={<StepHeading>{t('invitation input label')}</StepHeading>}
|
|
70
|
+
// TODO(burdon): Translation.
|
|
65
71
|
placeholder='Type an invitation code'
|
|
66
72
|
disabled={disabled}
|
|
67
73
|
value={inputValue}
|
|
@@ -50,7 +50,7 @@ export const ResetIdentityImpl = ({ disabled, validationMessage, onConfirm }: Re
|
|
|
50
50
|
{...{ validationMessage }}
|
|
51
51
|
label={<StepHeading>{t('reset identity input label', { confirmationValue })}</StepHeading>}
|
|
52
52
|
disabled={disabled}
|
|
53
|
-
data-testid='identity-input'
|
|
53
|
+
data-testid='reset-identity-input'
|
|
54
54
|
placeholder={confirmationValue}
|
|
55
55
|
onChange={({ target: { value } }) => setInputValue(value)}
|
|
56
56
|
/>
|
|
@@ -70,6 +70,10 @@ export class InvitationsManager extends ScopedShellManager {
|
|
|
70
70
|
return this.peer(id).getByTestId('space-list-item').nth(nth).textContent();
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
+
async getSpaceMembersCount(id: number) {
|
|
74
|
+
return this.peer(id).getByTestId('space-members-list').locator('li').count();
|
|
75
|
+
}
|
|
76
|
+
|
|
73
77
|
async getAuthCode(): Promise<string> {
|
|
74
78
|
return await this._authCode.wait();
|
|
75
79
|
}
|
|
@@ -147,7 +151,7 @@ export class InvitationsManager extends ScopedShellManager {
|
|
|
147
151
|
const peer = this.peer(id);
|
|
148
152
|
// TODO(wittjosiah): Update ids.
|
|
149
153
|
if (type === 'device') {
|
|
150
|
-
await peer.getByTestId('join-identity').click();
|
|
154
|
+
await peer.getByTestId('identity-chooser.join-identity').click();
|
|
151
155
|
}
|
|
152
156
|
await peer.getByTestId(`${type === 'device' ? 'halo' : 'space'}-invitation-input`).waitFor({ timeout: 500 });
|
|
153
157
|
await peer.getByTestId(`${type === 'device' ? 'halo' : 'space'}-invitation-input`).fill(invitation);
|
|
@@ -185,6 +185,25 @@ test.describe('Invitations', () => {
|
|
|
185
185
|
expect(await manager.getSpaceName(0, 1)).to.equal(await manager.getSpaceName(1, 1));
|
|
186
186
|
});
|
|
187
187
|
|
|
188
|
+
test('already joined', async () => {
|
|
189
|
+
await manager.createIdentity(0);
|
|
190
|
+
await manager.createSpace(0);
|
|
191
|
+
await manager.openPanel(0, 1);
|
|
192
|
+
const invitation1 = await manager.createInvitation(0, 'space', { authMethod: Invitation.AuthMethod.NONE });
|
|
193
|
+
const invitation2 = await manager.createInvitation(0, 'space');
|
|
194
|
+
|
|
195
|
+
await manager.createIdentity(1);
|
|
196
|
+
await manager.openPanel(1, 'join');
|
|
197
|
+
await manager.acceptInvitation(1, 'space', invitation1);
|
|
198
|
+
await manager.doneInvitation('space', manager.peer(1));
|
|
199
|
+
|
|
200
|
+
await manager.openPanel(1, 'join');
|
|
201
|
+
await manager.acceptInvitation(1, 'space', invitation2);
|
|
202
|
+
await manager.doneInvitation('space', manager.peer(1));
|
|
203
|
+
|
|
204
|
+
expect(await manager.getSpaceMembersCount(0)).to.equal(2);
|
|
205
|
+
});
|
|
206
|
+
|
|
188
207
|
test('no auth method', async () => {
|
|
189
208
|
await manager.createIdentity(0);
|
|
190
209
|
await manager.createSpace(0);
|
|
@@ -2,12 +2,14 @@
|
|
|
2
2
|
// Copyright 2023 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import type { ConsoleMessage, Page } from '@playwright/test';
|
|
5
|
+
import type { ConsoleMessage, Dialog, Page } from '@playwright/test';
|
|
6
6
|
|
|
7
7
|
import { sleep, Trigger } from '@dxos/async';
|
|
8
8
|
|
|
9
9
|
import { ScopedShellManager } from './scoped-shell-manager';
|
|
10
10
|
|
|
11
|
+
// TODO(wittjosiah): Normalize data-testids between snake and camel case.
|
|
12
|
+
|
|
11
13
|
export class ShellManager extends ScopedShellManager {
|
|
12
14
|
private _invitationCode = new Trigger<string>();
|
|
13
15
|
private _authCode = new Trigger<string>();
|
|
@@ -27,6 +29,10 @@ export class ShellManager extends ScopedShellManager {
|
|
|
27
29
|
|
|
28
30
|
// Getters
|
|
29
31
|
|
|
32
|
+
async getDisplayName(): Promise<string | null> {
|
|
33
|
+
return this.shell.getByTestId('identityHeading.displayName').textContent();
|
|
34
|
+
}
|
|
35
|
+
|
|
30
36
|
async isCurrentSpaceViewVisible(): Promise<boolean> {
|
|
31
37
|
return await this.shell.getByTestId('current-space-view').isVisible();
|
|
32
38
|
}
|
|
@@ -44,29 +50,50 @@ export class ShellManager extends ScopedShellManager {
|
|
|
44
50
|
await this.shell.getByTestId('halo-invitation-accepted-done').click();
|
|
45
51
|
}
|
|
46
52
|
|
|
47
|
-
async
|
|
48
|
-
|
|
53
|
+
async createDeviceInvitation(): Promise<string> {
|
|
54
|
+
this._invitationCode = new Trigger<string>();
|
|
55
|
+
this._authCode = new Trigger<string>();
|
|
56
|
+
await this.shell.getByTestId('devices-panel.create-invitation').click();
|
|
57
|
+
return await this._invitationCode.wait();
|
|
49
58
|
}
|
|
50
59
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
60
|
+
async resetDevice() {
|
|
61
|
+
const handleDialog = async (dialog: Dialog) => {
|
|
62
|
+
if (dialog.type() === 'confirm') {
|
|
63
|
+
await dialog.accept();
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
this.page.on('dialog', handleDialog);
|
|
68
|
+
await this.shell.getByTestId('devices-panel.sign-out').click();
|
|
69
|
+
await this.shell.getByTestId('sign-out.reset-device').click();
|
|
70
|
+
this.page.off('dialog', handleDialog);
|
|
54
71
|
}
|
|
55
72
|
|
|
56
|
-
async
|
|
57
|
-
await this.
|
|
73
|
+
async joinNewIdentity(invitationCode: string) {
|
|
74
|
+
await this.shell.getByTestId('devices-panel.sign-out').click();
|
|
75
|
+
await this.shell.getByTestId('sign-out.join-new-identity').click();
|
|
76
|
+
await this.shell.getByTestId('reset-identity-input').fill('CONFIRM');
|
|
77
|
+
await this.shell.getByTestId('reset-identity-input-confirm').click();
|
|
78
|
+
await this.shell.getByTestId('identity-chooser.join-identity').click();
|
|
79
|
+
await this.inputInvitation('device', invitationCode, this.shell);
|
|
58
80
|
}
|
|
59
81
|
|
|
60
|
-
async
|
|
61
|
-
|
|
82
|
+
async authenticateDevice(authCode: string, skipDone = true) {
|
|
83
|
+
// Wait for focus to shift before typing.
|
|
84
|
+
await sleep(1500);
|
|
85
|
+
await this.authenticateInvitation('device', authCode, this.shell);
|
|
86
|
+
// TODO(wittjosiah): When "signing out" and joinging a new identity, the done step seems to be skipped.
|
|
87
|
+
if (!skipDone) {
|
|
88
|
+
await this.doneInvitation('device', this.shell);
|
|
89
|
+
}
|
|
62
90
|
}
|
|
63
91
|
|
|
64
|
-
async
|
|
65
|
-
await this.
|
|
92
|
+
async closeShell() {
|
|
93
|
+
await this.page.keyboard.press('Escape');
|
|
66
94
|
}
|
|
67
95
|
|
|
68
96
|
async createSpaceInvitation(): Promise<string> {
|
|
69
|
-
await this.openCurrentSpace();
|
|
70
97
|
this._invitationCode = new Trigger<string>();
|
|
71
98
|
this._authCode = new Trigger<string>();
|
|
72
99
|
await this.shell.getByTestId('spaces-panel.create-invitation').click();
|
|
@@ -99,4 +99,9 @@ export const os = {
|
|
|
99
99
|
'This will clear this device and all associated data. Type {{confirmationValue}} and confirm to continue.',
|
|
100
100
|
'failed to reset identity message': 'Failed to reset identity.',
|
|
101
101
|
'confirmation value': 'CONFIRM',
|
|
102
|
+
'sign out chooser title': 'DANGER ZONE',
|
|
103
|
+
'sign out chooser message': 'Proceeding with either action below will erase all data on this device.',
|
|
104
|
+
'join new identity label': 'Join a new identity',
|
|
105
|
+
'reset device label': 'Reset device',
|
|
106
|
+
'reset device confirm message': 'Are you sure you want to reset your device? All data will be lost.',
|
|
102
107
|
};
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { type Event, type SingleOrArray } from 'xstate';
|
|
2
|
-
import { type Identity } from '@dxos/react-client/halo';
|
|
3
|
-
import { type IdentityPanelStepProps } from '../IdentityPanelProps';
|
|
4
|
-
import { type IdentityEvent } from '../identityMachine';
|
|
5
|
-
export interface AgentFormProps extends Omit<IdentityPanelStepProps, 'send'> {
|
|
6
|
-
send?: (event: SingleOrArray<Event<IdentityEvent>>) => void;
|
|
7
|
-
onAgentCreate?: () => Promise<void>;
|
|
8
|
-
onAgentDestroy?: () => Promise<void>;
|
|
9
|
-
identity?: Identity;
|
|
10
|
-
}
|
|
11
|
-
export type AgentFormImplProps = AgentFormProps & {
|
|
12
|
-
agentStatus?: string;
|
|
13
|
-
agentActive?: boolean;
|
|
14
|
-
agentProviderDisabled?: boolean;
|
|
15
|
-
validationMessage?: string;
|
|
16
|
-
};
|
|
17
|
-
export declare const AgentForm: (props: AgentFormProps) => JSX.Element;
|
|
18
|
-
export declare const AgentFormImpl: (props: AgentFormImplProps) => JSX.Element;
|
|
19
|
-
//# sourceMappingURL=AgentPanel.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"AgentPanel.d.ts","sourceRoot":"","sources":["../../../../../../src/panels/IdentityPanel/steps/AgentPanel.tsx"],"names":[],"mappings":"AAKA,OAAO,EAAE,KAAK,KAAK,EAAE,KAAK,aAAa,EAAE,MAAM,QAAQ,CAAC;AAIxD,OAAO,EAAE,KAAK,QAAQ,EAAsB,MAAM,yBAAyB,CAAC;AAK5E,OAAO,EAAE,KAAK,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AACpE,OAAO,EAAE,KAAK,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAExD,MAAM,WAAW,cAAe,SAAQ,IAAI,CAAC,sBAAsB,EAAE,MAAM,CAAC;IAC1E,IAAI,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC,KAAK,IAAI,CAAC;IAC5D,aAAa,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACpC,cAAc,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACrC,QAAQ,CAAC,EAAE,QAAQ,CAAC;CACrB;AAED,MAAM,MAAM,kBAAkB,GAAG,cAAc,GAAG;IAChD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B,CAAC;AAEF,eAAO,MAAM,SAAS,UAAW,cAAc,gBAiF9C,CAAC;AAGF,eAAO,MAAM,aAAa,UAAW,kBAAkB,gBAiDtD,CAAC"}
|