@dxos/plugin-client 0.8.4-staging.60fe92afc8 → 0.9.1-main.c7dcc2e112
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/lib/neutral/{AccountContainer-FZ3IC6H4.mjs → AccountContainer-LIMGLO2Y.mjs} +3 -3
- package/dist/lib/neutral/AccountContainer-LIMGLO2Y.mjs.map +7 -0
- package/dist/lib/neutral/ClientPlugin.node.mjs +477 -370
- package/dist/lib/neutral/ClientPlugin.node.mjs.map +4 -4
- package/dist/lib/neutral/{DevicesContainer-6MWNUC3I.mjs → DevicesContainer-X26OGLFK.mjs} +12 -12
- package/dist/lib/neutral/DevicesContainer-X26OGLFK.mjs.map +7 -0
- package/dist/lib/neutral/{InvitationsContainer-ME722VKS.mjs → InvitationsContainer-OBBCJ7CJ.mjs} +2 -2
- package/dist/lib/neutral/InvitationsContainer-OBBCJ7CJ.mjs.map +7 -0
- package/dist/lib/neutral/{JoinDialog-2NXAKPLP.mjs → JoinDialog-4BWV4CYB.mjs} +2 -2
- package/dist/lib/neutral/JoinDialog-4BWV4CYB.mjs.map +7 -0
- package/dist/lib/neutral/{ProfileContainer-VPX3FPWG.mjs → ProfileContainer-FGS2MVWA.mjs} +2 -2
- package/dist/lib/neutral/ProfileContainer-FGS2MVWA.mjs.map +7 -0
- package/dist/lib/neutral/{RecoveryCodeDialog-EWHB6U5T.mjs → RecoveryCodeDialog-7EPNKCLB.mjs} +2 -2
- package/dist/lib/neutral/RecoveryCodeDialog-7EPNKCLB.mjs.map +7 -0
- package/dist/lib/neutral/{RecoveryCredentialsContainer-IPBGDMIA.mjs → RecoveryCredentialsContainer-SL4LYHBE.mjs} +3 -3
- package/dist/lib/neutral/RecoveryCredentialsContainer-SL4LYHBE.mjs.map +7 -0
- package/dist/lib/neutral/UsageContainer-HBPMLIK7.mjs +226 -0
- package/dist/lib/neutral/UsageContainer-HBPMLIK7.mjs.map +7 -0
- package/dist/lib/neutral/{app-graph-builder-A74W7C36.mjs → app-graph-builder-FTXJNX37.mjs} +28 -14
- package/dist/lib/neutral/app-graph-builder-FTXJNX37.mjs.map +7 -0
- package/dist/lib/neutral/capabilities/index.mjs +5 -5
- package/dist/lib/neutral/capabilities/node.mjs +3 -3
- package/dist/lib/neutral/chunk-54MDDWCV.mjs +8 -0
- package/dist/lib/neutral/{chunk-NTVDGDQ3.mjs.map → chunk-54MDDWCV.mjs.map} +2 -2
- package/dist/lib/neutral/chunk-6G4KZLBG.mjs +28 -0
- package/dist/lib/neutral/chunk-6G4KZLBG.mjs.map +7 -0
- package/dist/lib/neutral/{chunk-D6HPCLTS.mjs → chunk-7DEIKHB7.mjs} +16 -2
- package/dist/lib/neutral/chunk-7DEIKHB7.mjs.map +7 -0
- package/dist/lib/neutral/{chunk-TUK3ELUU.mjs → chunk-DA3LZQSD.mjs} +22 -21
- package/dist/lib/neutral/chunk-DA3LZQSD.mjs.map +7 -0
- package/dist/lib/neutral/chunk-GSKMAS3S.mjs +13 -0
- package/dist/lib/neutral/chunk-GSKMAS3S.mjs.map +7 -0
- package/dist/lib/neutral/containers/index.mjs +10 -8
- package/dist/lib/neutral/containers/index.mjs.map +3 -3
- package/dist/lib/neutral/{create-agent-6HLW4D3A.mjs → create-agent-U567RM7Z.mjs} +3 -3
- package/dist/lib/neutral/{create-identity-JIWAR5GW.mjs → create-identity-IKN2FJ5B.mjs} +3 -3
- package/dist/lib/neutral/{create-passkey-FOXEMXGF.mjs → create-passkey-JAD3ACY7.mjs} +7 -7
- package/dist/lib/neutral/create-passkey-JAD3ACY7.mjs.map +7 -0
- package/dist/lib/neutral/{create-recovery-code-ZWDKLPH4.mjs → create-recovery-code-JZYXR4BY.mjs} +4 -4
- package/dist/lib/neutral/index.mjs +4 -4
- package/dist/lib/neutral/{join-identity-QEQFAE2S.mjs → join-identity-PJCHDNLH.mjs} +3 -3
- package/dist/lib/neutral/{layer-specs-CQLXQBQJ.mjs → layer-specs-VQCJLV33.mjs} +2 -2
- package/dist/lib/neutral/{layer-specs-CQLXQBQJ.mjs.map → layer-specs-VQCJLV33.mjs.map} +2 -2
- package/dist/lib/neutral/meta.json +1 -1
- package/dist/lib/neutral/meta.mjs +1 -1
- package/dist/lib/neutral/{navigation-handler-LU26CDFR.mjs → navigation-handler-MPJAVHWY.mjs} +3 -3
- package/dist/lib/neutral/open-usage-24I7RAFL.mjs +27 -0
- package/dist/lib/neutral/open-usage-24I7RAFL.mjs.map +7 -0
- package/dist/lib/neutral/operations/index.mjs +2 -2
- package/dist/lib/neutral/plugin.mjs +3 -3
- package/dist/lib/neutral/{react-context-B5LXMHVM.mjs → react-context-V74T6JOJ.mjs} +2 -2
- package/dist/lib/neutral/{react-context-B5LXMHVM.mjs.map → react-context-V74T6JOJ.mjs.map} +3 -3
- package/dist/lib/neutral/{react-surface-DEKGWTCD.mjs → react-surface-HPXRA65U.mjs} +8 -3
- package/dist/lib/neutral/react-surface-HPXRA65U.mjs.map +7 -0
- package/dist/lib/neutral/{recover-identity-JNFKLCUD.mjs → recover-identity-B7YM2BR4.mjs} +3 -3
- package/dist/lib/neutral/{redeem-passkey-4SBAI4AG.mjs → redeem-passkey-4DR7GPMU.mjs} +10 -10
- package/dist/lib/neutral/redeem-passkey-4DR7GPMU.mjs.map +7 -0
- package/dist/lib/neutral/{redeem-token-POBD6TMN.mjs → redeem-token-N5MBQG3I.mjs} +3 -3
- package/dist/lib/neutral/{reset-storage-LQKUV5RA.mjs → reset-storage-K47HZTGP.mjs} +3 -3
- package/dist/lib/neutral/{share-identity-KNQ2XNXV.mjs → share-identity-YJIFDIPJ.mjs} +6 -6
- package/dist/lib/neutral/share-identity-YJIFDIPJ.mjs.map +7 -0
- package/dist/lib/neutral/testing/index.mjs +2 -2
- package/dist/lib/neutral/testing/index.mjs.map +3 -3
- package/dist/lib/neutral/translations.mjs +24 -1
- package/dist/lib/neutral/translations.mjs.map +3 -3
- package/dist/lib/neutral/types/index.mjs +1 -1
- package/dist/types/dx.config.d.ts +28 -0
- package/dist/types/dx.config.d.ts.map +1 -0
- package/dist/types/src/capabilities/index.d.ts +9 -43
- package/dist/types/src/capabilities/index.d.ts.map +1 -1
- package/dist/types/src/capabilities/navigation-handler/index.d.ts +1 -1
- package/dist/types/src/capabilities/navigation-handler/index.d.ts.map +1 -1
- package/dist/types/src/capabilities/node.d.ts +6 -6
- package/dist/types/src/capabilities/node.d.ts.map +1 -1
- package/dist/types/src/capabilities/react-surface.d.ts +2 -2
- package/dist/types/src/capabilities/react-surface.d.ts.map +1 -1
- package/dist/types/src/commands/account/index.d.ts +3 -0
- package/dist/types/src/commands/account/index.d.ts.map +1 -0
- package/dist/types/src/commands/account/login/index.d.ts +2 -0
- package/dist/types/src/commands/account/login/index.d.ts.map +1 -0
- package/dist/types/src/commands/account/login/login.d.ts +12 -0
- package/dist/types/src/commands/account/login/login.d.ts.map +1 -0
- package/dist/types/src/commands/account/logout/index.d.ts +2 -0
- package/dist/types/src/commands/account/logout/index.d.ts.map +1 -0
- package/dist/types/src/commands/account/logout/logout.d.ts +9 -0
- package/dist/types/src/commands/account/logout/logout.d.ts.map +1 -0
- package/dist/types/src/commands/halo/join/join.d.ts +1 -1
- package/dist/types/src/commands/halo/join/join.d.ts.map +1 -1
- package/dist/types/src/commands/halo/util.d.ts +4 -0
- package/dist/types/src/commands/halo/util.d.ts.map +1 -1
- package/dist/types/src/commands/index.d.ts +1 -0
- package/dist/types/src/commands/index.d.ts.map +1 -1
- package/dist/types/src/constants.d.ts.map +1 -1
- package/dist/types/src/containers/DevicesContainer/DevicesContainer.d.ts.map +1 -1
- package/dist/types/src/containers/RecoveryCredentialsContainer/RecoveryCredentialsContainer.d.ts.map +1 -1
- package/dist/types/src/containers/UsageContainer/UsageContainer.d.ts +7 -0
- package/dist/types/src/containers/UsageContainer/UsageContainer.d.ts.map +1 -0
- package/dist/types/src/containers/UsageContainer/UsageView.d.ts +19 -0
- package/dist/types/src/containers/UsageContainer/UsageView.d.ts.map +1 -0
- package/dist/types/src/containers/UsageContainer/UsageView.stories.d.ts +19 -0
- package/dist/types/src/containers/UsageContainer/UsageView.stories.d.ts.map +1 -0
- package/dist/types/src/containers/UsageContainer/index.d.ts +2 -0
- package/dist/types/src/containers/UsageContainer/index.d.ts.map +1 -0
- package/dist/types/src/containers/index.d.ts +1 -0
- package/dist/types/src/containers/index.d.ts.map +1 -1
- package/dist/types/src/meta.d.ts +28 -2
- package/dist/types/src/meta.d.ts.map +1 -1
- package/dist/types/src/operations/create-passkey.d.ts.map +1 -1
- package/dist/types/src/operations/definitions.d.ts +1 -0
- package/dist/types/src/operations/definitions.d.ts.map +1 -1
- package/dist/types/src/operations/index.d.ts.map +1 -1
- package/dist/types/src/operations/open-usage.d.ts +5 -0
- package/dist/types/src/operations/open-usage.d.ts.map +1 -0
- package/dist/types/src/testing/initializeIdentity.d.ts.map +1 -1
- package/dist/types/src/translations.d.ts.map +1 -1
- package/dist/types/src/types/capabilities.d.ts.map +1 -1
- package/dist/types/src/types/events.d.ts.map +1 -1
- package/dist/types/src/types/schema.d.ts +1 -0
- package/dist/types/src/types/schema.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/dx.config.ts +19 -0
- package/package.json +37 -35
- package/src/ClientPlugin.node.ts +2 -2
- package/src/ClientPlugin.test.ts +1 -1
- package/src/capabilities/app-graph-builder.ts +22 -13
- package/src/capabilities/layer-specs.ts +1 -1
- package/src/capabilities/react-context.tsx +1 -1
- package/src/capabilities/react-surface.tsx +6 -0
- package/src/commands/account/index.ts +13 -0
- package/src/commands/account/login/index.ts +5 -0
- package/src/commands/account/login/login.ts +147 -0
- package/src/commands/account/logout/index.ts +5 -0
- package/src/commands/account/logout/logout.ts +49 -0
- package/src/commands/halo/create/create.ts +2 -2
- package/src/commands/halo/index.ts +6 -6
- package/src/commands/halo/join/join.ts +2 -13
- package/src/commands/halo/util.ts +24 -0
- package/src/commands/index.ts +1 -0
- package/src/constants.ts +3 -3
- package/src/containers/AccountContainer/AccountContainer.tsx +1 -1
- package/src/containers/DevicesContainer/DevicesContainer.tsx +14 -11
- package/src/containers/InvitationsContainer/InvitationsContainer.tsx +1 -1
- package/src/containers/JoinDialog/JoinDialog.tsx +1 -1
- package/src/containers/ProfileContainer/ProfileContainer.tsx +1 -1
- package/src/containers/RecoveryCodeDialog/RecoveryCodeDialog.tsx +1 -1
- package/src/containers/RecoveryCredentialsContainer/RecoveryCredentialsContainer.tsx +2 -2
- package/src/containers/UsageContainer/UsageContainer.tsx +47 -0
- package/src/containers/UsageContainer/UsageView.stories.tsx +114 -0
- package/src/containers/UsageContainer/UsageView.tsx +238 -0
- package/src/containers/UsageContainer/index.ts +5 -0
- package/src/containers/index.ts +1 -0
- package/src/meta.ts +3 -12
- package/src/operations/create-passkey.ts +6 -4
- package/src/operations/definitions.ts +12 -1
- package/src/operations/index.ts +1 -0
- package/src/operations/open-usage.ts +24 -0
- package/src/operations/redeem-passkey.ts +7 -7
- package/src/operations/share-identity.ts +3 -3
- package/src/testing/initializeIdentity.ts +4 -2
- package/src/translations.ts +25 -1
- package/src/types/capabilities.ts +7 -5
- package/src/types/events.ts +5 -5
- package/src/types/schema.ts +23 -16
- package/dist/lib/neutral/AccountContainer-FZ3IC6H4.mjs.map +0 -7
- package/dist/lib/neutral/DevicesContainer-6MWNUC3I.mjs.map +0 -7
- package/dist/lib/neutral/InvitationsContainer-ME722VKS.mjs.map +0 -7
- package/dist/lib/neutral/JoinDialog-2NXAKPLP.mjs.map +0 -7
- package/dist/lib/neutral/ProfileContainer-VPX3FPWG.mjs.map +0 -7
- package/dist/lib/neutral/RecoveryCodeDialog-EWHB6U5T.mjs.map +0 -7
- package/dist/lib/neutral/RecoveryCredentialsContainer-IPBGDMIA.mjs.map +0 -7
- package/dist/lib/neutral/app-graph-builder-A74W7C36.mjs.map +0 -7
- package/dist/lib/neutral/chunk-D6HPCLTS.mjs.map +0 -7
- package/dist/lib/neutral/chunk-JOCCVODY.mjs +0 -21
- package/dist/lib/neutral/chunk-JOCCVODY.mjs.map +0 -7
- package/dist/lib/neutral/chunk-NTVDGDQ3.mjs +0 -8
- package/dist/lib/neutral/chunk-TUK3ELUU.mjs.map +0 -7
- package/dist/lib/neutral/chunk-WWCVZG3K.mjs +0 -13
- package/dist/lib/neutral/chunk-WWCVZG3K.mjs.map +0 -7
- package/dist/lib/neutral/create-passkey-FOXEMXGF.mjs.map +0 -7
- package/dist/lib/neutral/react-surface-DEKGWTCD.mjs.map +0 -7
- package/dist/lib/neutral/redeem-passkey-4SBAI4AG.mjs.map +0 -7
- package/dist/lib/neutral/share-identity-KNQ2XNXV.mjs.map +0 -7
- /package/dist/lib/neutral/{create-agent-6HLW4D3A.mjs.map → create-agent-U567RM7Z.mjs.map} +0 -0
- /package/dist/lib/neutral/{create-identity-JIWAR5GW.mjs.map → create-identity-IKN2FJ5B.mjs.map} +0 -0
- /package/dist/lib/neutral/{create-recovery-code-ZWDKLPH4.mjs.map → create-recovery-code-JZYXR4BY.mjs.map} +0 -0
- /package/dist/lib/neutral/{join-identity-QEQFAE2S.mjs.map → join-identity-PJCHDNLH.mjs.map} +0 -0
- /package/dist/lib/neutral/{navigation-handler-LU26CDFR.mjs.map → navigation-handler-MPJAVHWY.mjs.map} +0 -0
- /package/dist/lib/neutral/{recover-identity-JNFKLCUD.mjs.map → recover-identity-B7YM2BR4.mjs.map} +0 -0
- /package/dist/lib/neutral/{redeem-token-POBD6TMN.mjs.map → redeem-token-N5MBQG3I.mjs.map} +0 -0
- /package/dist/lib/neutral/{reset-storage-LQKUV5RA.mjs.map → reset-storage-K47HZTGP.mjs.map} +0 -0
|
@@ -25,7 +25,7 @@ export type DevicesContainerProps = {
|
|
|
25
25
|
};
|
|
26
26
|
|
|
27
27
|
export const DevicesContainer = ({ createInvitationUrl }: DevicesContainerProps) => {
|
|
28
|
-
const { t } = useTranslation(meta.
|
|
28
|
+
const { t } = useTranslation(meta.profile.key);
|
|
29
29
|
const { invokePromise } = useOperationInvoker();
|
|
30
30
|
const devices = useDevices();
|
|
31
31
|
const { swarm: connectionState } = useNetworkStatus();
|
|
@@ -46,12 +46,12 @@ export const DevicesContainer = ({ createInvitationUrl }: DevicesContainerProps)
|
|
|
46
46
|
<Clipboard.Provider>
|
|
47
47
|
<Settings.Viewport>
|
|
48
48
|
<Settings.Section
|
|
49
|
-
title={t('devices-verbose.label', { ns: meta.
|
|
50
|
-
description={t('devices.description', { ns: meta.
|
|
49
|
+
title={t('devices-verbose.label', { ns: meta.profile.key })}
|
|
50
|
+
description={t('devices.description', { ns: meta.profile.key })}
|
|
51
51
|
>
|
|
52
52
|
<Settings.Panel>
|
|
53
53
|
<div role='group' className='min-w-0'>
|
|
54
|
-
<h3 className='text-lg mb-2'>{t('devices.label', { ns: meta.
|
|
54
|
+
<h3 className='text-lg mb-2'>{t('devices.label', { ns: meta.profile.key })}</h3>
|
|
55
55
|
<List>
|
|
56
56
|
{devices.map((device: Device) => (
|
|
57
57
|
<DeviceListItem key={device.deviceKey.toHex()} device={device} connectionState={connectionState} />
|
|
@@ -67,17 +67,20 @@ export const DevicesContainer = ({ createInvitationUrl }: DevicesContainerProps)
|
|
|
67
67
|
</Settings.Panel>
|
|
68
68
|
</Settings.Section>
|
|
69
69
|
<Settings.Section
|
|
70
|
-
title={t('danger-zone.title', { ns: meta.
|
|
71
|
-
description={t('danger-zone.description', { ns: meta.
|
|
70
|
+
title={t('danger-zone.title', { ns: meta.profile.key })}
|
|
71
|
+
description={t('danger-zone.description', { ns: meta.profile.key })}
|
|
72
72
|
>
|
|
73
|
-
<Settings.Item
|
|
73
|
+
<Settings.Item
|
|
74
|
+
title={t('reset-device.label')}
|
|
75
|
+
description={t('reset-device.description', { ns: meta.profile.key })}
|
|
76
|
+
>
|
|
74
77
|
<Button variant='destructive' onClick={handleResetStorage} data-testid='devicesContainer.reset'>
|
|
75
78
|
{t('reset-device.label')}
|
|
76
79
|
</Button>
|
|
77
80
|
</Settings.Item>
|
|
78
81
|
<Settings.Item
|
|
79
82
|
title={t('recover-identity.label')}
|
|
80
|
-
description={t('recover-identity.description', { ns: meta.
|
|
83
|
+
description={t('recover-identity.description', { ns: meta.profile.key })}
|
|
81
84
|
>
|
|
82
85
|
<Button variant='destructive' onClick={handleRecover} data-testid='devicesContainer.recover'>
|
|
83
86
|
{t('recover-identity.label')}
|
|
@@ -85,7 +88,7 @@ export const DevicesContainer = ({ createInvitationUrl }: DevicesContainerProps)
|
|
|
85
88
|
</Settings.Item>
|
|
86
89
|
<Settings.Item
|
|
87
90
|
title={t('join-new-identity.label')}
|
|
88
|
-
description={t('join-new-identity.description', { ns: meta.
|
|
91
|
+
description={t('join-new-identity.description', { ns: meta.profile.key })}
|
|
89
92
|
>
|
|
90
93
|
<Button variant='destructive' onClick={handleJoinNewIdentity} data-testid='devicesContainer.joinExisting'>
|
|
91
94
|
{t('join-new-identity.label')}
|
|
@@ -167,7 +170,7 @@ const InvitationSection = ({
|
|
|
167
170
|
onInvitationDone = () => {},
|
|
168
171
|
onInvitationCreate = () => {},
|
|
169
172
|
}: InvitationComponentProps) => {
|
|
170
|
-
const { t } = useTranslation(meta.
|
|
173
|
+
const { t } = useTranslation(meta.profile.key);
|
|
171
174
|
const activeView =
|
|
172
175
|
state < 0
|
|
173
176
|
? 'init'
|
|
@@ -215,7 +218,7 @@ const InvitationQR = ({ id, url, onCancel }: { id: string; url: string; onCancel
|
|
|
215
218
|
const emoji = hexToEmoji(id);
|
|
216
219
|
return (
|
|
217
220
|
<>
|
|
218
|
-
<p className='text-description'>{t('qr-code.description', { ns: meta.
|
|
221
|
+
<p className='text-description'>{t('qr-code.description', { ns: meta.profile.key })}</p>
|
|
219
222
|
<div role='group' className='grid grid-cols-[1fr_min-content]'>
|
|
220
223
|
<div className='flex justify-center py-4'>
|
|
221
224
|
<div className='w-full md:max-w-80 aspect-square relative text-description'>
|
|
@@ -17,7 +17,7 @@ import { type AccountCacheInvitation } from '../../state/account-cache';
|
|
|
17
17
|
import { useHubHttpClient } from '../../state/use-hub-http';
|
|
18
18
|
|
|
19
19
|
export const InvitationsContainer = () => {
|
|
20
|
-
const { t } = useTranslation(meta.
|
|
20
|
+
const { t } = useTranslation(meta.profile.key);
|
|
21
21
|
const accountCacheAtom = useCapability(ClientCapabilities.AccountCache);
|
|
22
22
|
const [cache] = useAtom(accountCacheAtom);
|
|
23
23
|
const setCache = useAtomSet(accountCacheAtom);
|
|
@@ -17,7 +17,7 @@ import { ClientOperation } from '#operations';
|
|
|
17
17
|
|
|
18
18
|
export const JoinDialog = (props: JoinPanelProps) => {
|
|
19
19
|
const { invokePromise } = useOperationInvoker();
|
|
20
|
-
const { t } = useTranslation(meta.
|
|
20
|
+
const { t } = useTranslation(meta.profile.key);
|
|
21
21
|
|
|
22
22
|
const handleCancelResetStorage = useCallback(() => invokePromise(ClientOperation.ShareIdentity), [invokePromise]);
|
|
23
23
|
|
|
@@ -33,7 +33,7 @@ const getDefaultEmojiValue = (identity: Identity | null) => hexToEmoji(identity?
|
|
|
33
33
|
const getEmojiValue = (identity: Identity | null) => identity?.profile?.data?.emoji || getDefaultEmojiValue(identity);
|
|
34
34
|
|
|
35
35
|
export const ProfileContainer = () => {
|
|
36
|
-
const { t } = useTranslation(meta.
|
|
36
|
+
const { t } = useTranslation(meta.profile.key);
|
|
37
37
|
const client = useClient();
|
|
38
38
|
const identity = useIdentity();
|
|
39
39
|
const [displayName, setDisplayNameDirectly] = useState(identity?.profile?.displayName ?? '');
|
|
@@ -14,7 +14,7 @@ export type RecoveryCodeDialogProps = {
|
|
|
14
14
|
|
|
15
15
|
// TODO(burdon): Should have cancel button.
|
|
16
16
|
export const RecoveryCodeDialog = ({ code }: RecoveryCodeDialogProps) => {
|
|
17
|
-
const { t } = useTranslation(meta.
|
|
17
|
+
const { t } = useTranslation(meta.profile.key);
|
|
18
18
|
const [confirmation, setConfirmation] = useState(false);
|
|
19
19
|
|
|
20
20
|
const handleConfirmation = useCallback((checked: boolean) => setConfirmation(checked), []);
|
|
@@ -12,10 +12,10 @@ import { Settings } from '@dxos/react-ui-form';
|
|
|
12
12
|
import { meta } from '#meta';
|
|
13
13
|
import { ClientOperation } from '#operations';
|
|
14
14
|
|
|
15
|
-
export const MANAGE_CREDENTIALS_DIALOG = `${meta.
|
|
15
|
+
export const MANAGE_CREDENTIALS_DIALOG = `${meta.profile.key}.ManageCredentialsDialog`;
|
|
16
16
|
|
|
17
17
|
export const RecoveryCredentialsContainer = () => {
|
|
18
|
-
const { t } = useTranslation(meta.
|
|
18
|
+
const { t } = useTranslation(meta.profile.key);
|
|
19
19
|
const { invokePromise } = useOperationInvoker();
|
|
20
20
|
const credentials = useCredentials();
|
|
21
21
|
const recoveryCredentials = credentials.filter(
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2026 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import React, { useCallback, useState } from 'react';
|
|
6
|
+
|
|
7
|
+
import { Context } from '@dxos/context';
|
|
8
|
+
import { log } from '@dxos/log';
|
|
9
|
+
import { type GetProfileUsageResponse } from '@dxos/protocols';
|
|
10
|
+
import { useAsyncEffect } from '@dxos/react-ui';
|
|
11
|
+
|
|
12
|
+
import { useHubHttpClient } from '../../state/use-hub-http';
|
|
13
|
+
import { UsageView, type UsageViewState } from './UsageView';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Fetches rolling-window profile usage from the hub service and renders it.
|
|
17
|
+
* Connected wrapper around the presentational {@link UsageView}.
|
|
18
|
+
*/
|
|
19
|
+
export const UsageContainer = () => {
|
|
20
|
+
const hubHttp = useHubHttpClient();
|
|
21
|
+
const [state, setState] = useState<UsageViewState>('loading');
|
|
22
|
+
const [data, setData] = useState<GetProfileUsageResponse | undefined>();
|
|
23
|
+
const [lastUpdated, setLastUpdated] = useState<number | undefined>();
|
|
24
|
+
const [refreshCount, setRefreshCount] = useState(0);
|
|
25
|
+
|
|
26
|
+
useAsyncEffect(async () => {
|
|
27
|
+
if (!hubHttp) {
|
|
28
|
+
setState('unavailable');
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
// Keep prior data visible while refetching; only show the skeleton on first load.
|
|
32
|
+
setState((previous) => (previous === 'ready' ? previous : 'loading'));
|
|
33
|
+
try {
|
|
34
|
+
const result = await hubHttp.getProfileUsage(new Context());
|
|
35
|
+
setData(result);
|
|
36
|
+
setLastUpdated(Date.now());
|
|
37
|
+
setState('ready');
|
|
38
|
+
} catch (err) {
|
|
39
|
+
log.catch(err);
|
|
40
|
+
setState('error');
|
|
41
|
+
}
|
|
42
|
+
}, [hubHttp, refreshCount]);
|
|
43
|
+
|
|
44
|
+
const handleRefresh = useCallback(() => setRefreshCount((count) => count + 1), []);
|
|
45
|
+
|
|
46
|
+
return <UsageView state={state} data={data} lastUpdated={lastUpdated} onRefresh={handleRefresh} />;
|
|
47
|
+
};
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2026 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { type Meta, type StoryObj } from '@storybook/react-vite';
|
|
6
|
+
|
|
7
|
+
import { type GetProfileUsageResponse } from '@dxos/protocols';
|
|
8
|
+
import { withLayout, withTheme } from '@dxos/react-ui/testing';
|
|
9
|
+
|
|
10
|
+
import { translations } from '#translations';
|
|
11
|
+
|
|
12
|
+
import { UsageView } from './UsageView';
|
|
13
|
+
|
|
14
|
+
// Mock payload shaped like the `/api/metering/profile/usage` response.
|
|
15
|
+
const usageData: GetProfileUsageResponse = {
|
|
16
|
+
profileId: 'did:key:z6MkExampleProfileIdentity',
|
|
17
|
+
usage: [
|
|
18
|
+
{ eventType: 'ai', valueKey: 'inputTokens', subtypePattern: ['claude-opus-4'], amount: 1_284_500 },
|
|
19
|
+
{ eventType: 'ai', valueKey: 'outputTokens', subtypePattern: ['claude-opus-4'], amount: 238_900 },
|
|
20
|
+
{ eventType: 'ai', valueKey: 'inputTokens', subtypePattern: ['claude-sonnet-4'], amount: 92_300 },
|
|
21
|
+
{ eventType: 'ai', valueKey: 'outputTokens', subtypePattern: ['claude-sonnet-4'], amount: 18_400 },
|
|
22
|
+
],
|
|
23
|
+
limits: [
|
|
24
|
+
{
|
|
25
|
+
eventType: 'ai',
|
|
26
|
+
valueKey: 'inputTokens',
|
|
27
|
+
subtypePattern: ['claude-opus-4'],
|
|
28
|
+
limit: 5_000_000,
|
|
29
|
+
windowDuration: 24 * 7 * 60 * 60,
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
eventType: 'ai',
|
|
33
|
+
valueKey: 'outputTokens',
|
|
34
|
+
subtypePattern: ['*'],
|
|
35
|
+
limit: 1_000_000,
|
|
36
|
+
windowDuration: 24 * 30 * 60 * 60,
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
eventType: 'ai',
|
|
40
|
+
valueKey: 'inputTokens',
|
|
41
|
+
subtypePattern: ['*'],
|
|
42
|
+
limit: 20_000_000,
|
|
43
|
+
windowDuration: 24 * 30 * 60 * 60,
|
|
44
|
+
},
|
|
45
|
+
],
|
|
46
|
+
buckets: [],
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const meta = {
|
|
50
|
+
title: 'plugins/plugin-client/containers/UsageView',
|
|
51
|
+
component: UsageView,
|
|
52
|
+
decorators: [withTheme(), withLayout({ layout: 'fullscreen' })],
|
|
53
|
+
parameters: {
|
|
54
|
+
layout: 'fullscreen',
|
|
55
|
+
translations,
|
|
56
|
+
},
|
|
57
|
+
} satisfies Meta<typeof UsageView>;
|
|
58
|
+
|
|
59
|
+
export default meta;
|
|
60
|
+
|
|
61
|
+
type Story = StoryObj<typeof meta>;
|
|
62
|
+
|
|
63
|
+
export const Default: Story = {
|
|
64
|
+
args: {
|
|
65
|
+
state: 'ready',
|
|
66
|
+
data: usageData,
|
|
67
|
+
lastUpdated: new Date('2026-06-16T12:00:00Z').getTime(),
|
|
68
|
+
onRefresh: () => {},
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
// Internal plan: a single unlimited (`limit: null`) `ai` limit — renders without a meter.
|
|
73
|
+
export const Internal: Story = {
|
|
74
|
+
args: {
|
|
75
|
+
state: 'ready',
|
|
76
|
+
data: {
|
|
77
|
+
profileId: usageData.profileId,
|
|
78
|
+
usage: usageData.usage,
|
|
79
|
+
limits: [
|
|
80
|
+
{
|
|
81
|
+
eventType: 'ai',
|
|
82
|
+
valueKey: 'outputTokens',
|
|
83
|
+
subtypePattern: ['*'],
|
|
84
|
+
limit: null,
|
|
85
|
+
windowDuration: 24 * 30 * 60 * 60,
|
|
86
|
+
},
|
|
87
|
+
],
|
|
88
|
+
buckets: [],
|
|
89
|
+
},
|
|
90
|
+
lastUpdated: new Date('2026-06-16T12:00:00Z').getTime(),
|
|
91
|
+
onRefresh: () => {},
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
export const Loading: Story = {
|
|
96
|
+
args: { state: 'loading' },
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
export const Error: Story = {
|
|
100
|
+
args: { state: 'error' },
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
export const Empty: Story = {
|
|
104
|
+
args: {
|
|
105
|
+
state: 'ready',
|
|
106
|
+
data: { profileId: usageData.profileId, usage: [], limits: [], buckets: [] },
|
|
107
|
+
lastUpdated: new Date('2026-06-16T12:00:00Z').getTime(),
|
|
108
|
+
onRefresh: () => {},
|
|
109
|
+
},
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
export const Unavailable: Story = {
|
|
113
|
+
args: { state: 'unavailable' },
|
|
114
|
+
};
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2026 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import React, { useMemo, useState } from 'react';
|
|
6
|
+
|
|
7
|
+
import { type GetProfileUsageResponse, type MeteringLimit, type MeteringUsageItem } from '@dxos/protocols';
|
|
8
|
+
import { IconButton, Message, useTranslation } from '@dxos/react-ui';
|
|
9
|
+
import { Settings } from '@dxos/react-ui-form';
|
|
10
|
+
import { JsonHighlighter } from '@dxos/react-ui-syntax-highlighter';
|
|
11
|
+
|
|
12
|
+
import { meta } from '#meta';
|
|
13
|
+
|
|
14
|
+
export type UsageViewState = 'loading' | 'ready' | 'error' | 'unavailable';
|
|
15
|
+
|
|
16
|
+
export type UsageViewProps = {
|
|
17
|
+
/** Current fetch state; drives which content is shown. */
|
|
18
|
+
state: UsageViewState;
|
|
19
|
+
/** Profile usage payload; required when `state` is `'ready'`. */
|
|
20
|
+
data?: GetProfileUsageResponse;
|
|
21
|
+
/** Epoch milliseconds of the last successful fetch. */
|
|
22
|
+
lastUpdated?: number;
|
|
23
|
+
/** Invoked when the user requests a refresh. */
|
|
24
|
+
onRefresh?: () => void;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
type TFunction = (key: string, options?: Record<string, unknown>) => string;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Whether a usage row falls under a limit. Subtype segments are matched positionally against
|
|
31
|
+
* `subtypePattern` where `*` matches any segment.
|
|
32
|
+
*/
|
|
33
|
+
const usageMatchesLimit = (item: MeteringUsageItem, limit: MeteringLimit): boolean => {
|
|
34
|
+
if (item.eventType !== limit.eventType || item.valueKey !== limit.valueKey) {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
return limit.subtypePattern.every((pattern, index) => pattern === '*' || pattern === item.subtypePattern[index]);
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
/** Sum every usage item that falls under the limit. */
|
|
41
|
+
const sumUsageForLimit = (usage: GetProfileUsageResponse['usage'], limit: MeteringLimit): number =>
|
|
42
|
+
usage.reduce((total, item) => (usageMatchesLimit(item, limit) ? total + item.amount : total), 0);
|
|
43
|
+
|
|
44
|
+
/** Humanize a camelCase value key, e.g. `outputTokens` → `output tokens`. */
|
|
45
|
+
const formatValueKey = (valueKey: string): string =>
|
|
46
|
+
valueKey
|
|
47
|
+
.replace(/([A-Z])/g, ' $1')
|
|
48
|
+
.trim()
|
|
49
|
+
.toLowerCase();
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Human label for a limit: a scope followed by the value key, e.g.
|
|
53
|
+
* `{ eventType: 'ai', valueKey: 'outputTokens', subtypePattern: ['*'] }` → `All models · output tokens`,
|
|
54
|
+
* `{ eventType: 'ai', valueKey: 'inputTokens', subtypePattern: ['claude-opus-4'] }` → `claude-opus-4 · input tokens`.
|
|
55
|
+
*/
|
|
56
|
+
const formatLimitLabel = (limit: MeteringLimit, t: TFunction): string => {
|
|
57
|
+
const metric = formatValueKey(limit.valueKey);
|
|
58
|
+
const subtype = limit.subtypePattern.filter((segment) => segment && segment !== '*').join('/');
|
|
59
|
+
if (limit.eventType === 'ai') {
|
|
60
|
+
return `${subtype || t('usage-all-models.label')} · ${metric}`;
|
|
61
|
+
}
|
|
62
|
+
return `${subtype ? `${limit.eventType}/${subtype}` : limit.eventType} · ${metric}`;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const formatAmount = (amount: number): string => amount.toLocaleString();
|
|
66
|
+
|
|
67
|
+
const SECONDS_PER_HOUR = 60 * 60;
|
|
68
|
+
|
|
69
|
+
/** Render `windowHours` as a localized "N months / days / hours" string. */
|
|
70
|
+
const formatWindow = (windowHours: number, t: TFunction): string => {
|
|
71
|
+
if (windowHours % (24 * 30) === 0) {
|
|
72
|
+
return t('usage-window-months.label', { count: windowHours / (24 * 30) });
|
|
73
|
+
}
|
|
74
|
+
if (windowHours % 24 === 0) {
|
|
75
|
+
return t('usage-window-days.label', { count: windowHours / 24 });
|
|
76
|
+
}
|
|
77
|
+
return t('usage-window-hours.label', { count: windowHours });
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* A single usage row: label + caption on the left, a thin meter, and a value on the right.
|
|
82
|
+
* Omitting `percent` (an unlimited limit) renders no meter.
|
|
83
|
+
*/
|
|
84
|
+
const UsageRow = ({
|
|
85
|
+
label,
|
|
86
|
+
caption,
|
|
87
|
+
percent,
|
|
88
|
+
value,
|
|
89
|
+
}: {
|
|
90
|
+
label: string;
|
|
91
|
+
caption?: string;
|
|
92
|
+
percent?: number;
|
|
93
|
+
value: string;
|
|
94
|
+
}) => (
|
|
95
|
+
<div className='flex items-center gap-trim-lg py-trim-sm'>
|
|
96
|
+
<div className='flex flex-col min-w-0 w-1/3 shrink-0'>
|
|
97
|
+
<span className='truncate text-base-fg'>{label}</span>
|
|
98
|
+
{caption && <span className='text-sm text-description'>{caption}</span>}
|
|
99
|
+
</div>
|
|
100
|
+
{percent === undefined ? (
|
|
101
|
+
<div className='flex-1' />
|
|
102
|
+
) : (
|
|
103
|
+
<div
|
|
104
|
+
role='progressbar'
|
|
105
|
+
aria-label={label}
|
|
106
|
+
aria-valuenow={percent}
|
|
107
|
+
aria-valuemin={0}
|
|
108
|
+
aria-valuemax={100}
|
|
109
|
+
className='h-1.5 flex-1 rounded-full bg-separator overflow-hidden'
|
|
110
|
+
>
|
|
111
|
+
<div
|
|
112
|
+
className='h-full rounded-full bg-primary-500 transition-[width] duration-500'
|
|
113
|
+
style={{ width: `${percent}%` }}
|
|
114
|
+
/>
|
|
115
|
+
</div>
|
|
116
|
+
)}
|
|
117
|
+
<span className='shrink-0 w-24 text-end text-sm text-description tabular-nums'>{value}</span>
|
|
118
|
+
</div>
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
/** Placeholder rows shown while the first fetch is in flight. */
|
|
122
|
+
const UsageSkeleton = () => (
|
|
123
|
+
<div className='flex flex-col'>
|
|
124
|
+
{Array.from({ length: 3 }).map((_, index) => (
|
|
125
|
+
<div key={index} className='flex items-center gap-trim-lg py-trim-sm'>
|
|
126
|
+
<div className='flex flex-col gap-1 w-1/3 shrink-0'>
|
|
127
|
+
<div className='h-4 w-24 rounded bg-separator animate-pulse' />
|
|
128
|
+
<div className='h-3 w-32 rounded bg-separator animate-pulse' />
|
|
129
|
+
</div>
|
|
130
|
+
<div className='h-1.5 flex-1 rounded-full bg-separator animate-pulse' />
|
|
131
|
+
<div className='h-3 w-12 shrink-0 rounded bg-separator animate-pulse' />
|
|
132
|
+
</div>
|
|
133
|
+
))}
|
|
134
|
+
</div>
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Presentational view of profile usage: renders loading/error/unavailable/empty states, or one row
|
|
139
|
+
* per limit with a usage meter, plus a collapsible raw-response viewer. Pure — all data via props.
|
|
140
|
+
*/
|
|
141
|
+
export const UsageView = ({ state, data, lastUpdated, onRefresh }: UsageViewProps) => {
|
|
142
|
+
const { t } = useTranslation(meta.profile.key);
|
|
143
|
+
const [rawExpanded, setRawExpanded] = useState(false);
|
|
144
|
+
|
|
145
|
+
// One display row per limit, shorter windows first. A `null` amount means unlimited (rendered without a meter).
|
|
146
|
+
const limitRows = useMemo(() => {
|
|
147
|
+
if (!data) {
|
|
148
|
+
return [];
|
|
149
|
+
}
|
|
150
|
+
return data.limits
|
|
151
|
+
.map((limit) => {
|
|
152
|
+
const used = sumUsageForLimit(data.usage, limit);
|
|
153
|
+
const label = formatLimitLabel(limit, t);
|
|
154
|
+
const windowHours = limit.windowDuration / SECONDS_PER_HOUR;
|
|
155
|
+
const window = formatWindow(windowHours, t);
|
|
156
|
+
const key = `${limit.eventType}/${limit.subtypePattern.join(',')}/${limit.valueKey}/${limit.windowDuration}/${limit.limit ?? 'unlimited'}`;
|
|
157
|
+
if (limit.limit === null) {
|
|
158
|
+
return {
|
|
159
|
+
key,
|
|
160
|
+
label,
|
|
161
|
+
caption: t('usage-unlimited.description', { used: formatAmount(used), window }),
|
|
162
|
+
percent: undefined,
|
|
163
|
+
value: t('usage-unlimited.label'),
|
|
164
|
+
windowHours,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
const percent = limit.limit > 0 ? Math.min(100, Math.round((used / limit.limit) * 100)) : used > 0 ? 100 : 0;
|
|
168
|
+
return {
|
|
169
|
+
key,
|
|
170
|
+
label,
|
|
171
|
+
caption: t('usage-limit.description', { used: formatAmount(used), limit: formatAmount(limit.limit), window }),
|
|
172
|
+
percent,
|
|
173
|
+
value: t('usage-percent-used.label', { percent }),
|
|
174
|
+
windowHours,
|
|
175
|
+
};
|
|
176
|
+
})
|
|
177
|
+
.sort((a, b) => a.windowHours - b.windowHours);
|
|
178
|
+
}, [data, t]);
|
|
179
|
+
|
|
180
|
+
return (
|
|
181
|
+
<Settings.Viewport>
|
|
182
|
+
<Settings.Section title={t('usage-section.title')} description={t('usage-section.description')}>
|
|
183
|
+
{state === 'unavailable' ? (
|
|
184
|
+
<Message.Root valence='warning'>
|
|
185
|
+
<Message.Title icon='ph--cloud-slash--duotone'>{t('usage-unavailable.title')}</Message.Title>
|
|
186
|
+
<Message.Content>{t('usage-unavailable.description')}</Message.Content>
|
|
187
|
+
</Message.Root>
|
|
188
|
+
) : state === 'loading' ? (
|
|
189
|
+
<UsageSkeleton />
|
|
190
|
+
) : state === 'error' ? (
|
|
191
|
+
<Message.Root valence='error'>
|
|
192
|
+
<Message.Title icon='ph--cloud-x--duotone'>{t('usage-offline.title')}</Message.Title>
|
|
193
|
+
<Message.Content>{t('usage-offline.description')}</Message.Content>
|
|
194
|
+
</Message.Root>
|
|
195
|
+
) : limitRows.length === 0 ? (
|
|
196
|
+
<Message.Root valence='neutral'>
|
|
197
|
+
<Message.Title icon='ph--chart-bar--duotone'>{t('usage-empty.title')}</Message.Title>
|
|
198
|
+
<Message.Content>{t('usage-empty.description')}</Message.Content>
|
|
199
|
+
</Message.Root>
|
|
200
|
+
) : (
|
|
201
|
+
<div className='flex flex-col'>
|
|
202
|
+
{limitRows.map((row) => (
|
|
203
|
+
<UsageRow key={row.key} label={row.label} caption={row.caption} percent={row.percent} value={row.value} />
|
|
204
|
+
))}
|
|
205
|
+
|
|
206
|
+
<div className='flex items-center gap-1 pt-trim-md text-sm text-description'>
|
|
207
|
+
{lastUpdated !== undefined && (
|
|
208
|
+
<span>{t('usage-last-updated.label', { time: new Date(lastUpdated).toLocaleTimeString() })}</span>
|
|
209
|
+
)}
|
|
210
|
+
{onRefresh && (
|
|
211
|
+
<IconButton
|
|
212
|
+
iconOnly
|
|
213
|
+
variant='ghost'
|
|
214
|
+
icon='ph--arrow-clockwise--regular'
|
|
215
|
+
label={t('usage-refresh.label')}
|
|
216
|
+
onClick={onRefresh}
|
|
217
|
+
/>
|
|
218
|
+
)}
|
|
219
|
+
</div>
|
|
220
|
+
</div>
|
|
221
|
+
)}
|
|
222
|
+
{state === 'ready' && data && (
|
|
223
|
+
<div className='flex flex-col pt-trim-md'>
|
|
224
|
+
<IconButton
|
|
225
|
+
variant='ghost'
|
|
226
|
+
icon='ph--caret-right--regular'
|
|
227
|
+
iconClassNames={rawExpanded ? 'transition-transform rotate-90' : 'transition-transform'}
|
|
228
|
+
label={t('usage-raw-json.label')}
|
|
229
|
+
onClick={() => setRawExpanded((value) => !value)}
|
|
230
|
+
classNames='self-start'
|
|
231
|
+
/>
|
|
232
|
+
{rawExpanded && <JsonHighlighter data={data} classNames='mt-trim-sm text-xs overflow-auto' />}
|
|
233
|
+
</div>
|
|
234
|
+
)}
|
|
235
|
+
</Settings.Section>
|
|
236
|
+
</Settings.Viewport>
|
|
237
|
+
);
|
|
238
|
+
};
|
package/src/containers/index.ts
CHANGED
|
@@ -10,6 +10,7 @@ export { type ResetDialogProps } from './ResetDialog';
|
|
|
10
10
|
export const AccountContainer: ComponentType<any> = lazy(() => import('./AccountContainer'));
|
|
11
11
|
export const DevicesContainer: ComponentType<any> = lazy(() => import('./DevicesContainer'));
|
|
12
12
|
export const InvitationsContainer: ComponentType<any> = lazy(() => import('./InvitationsContainer'));
|
|
13
|
+
export const UsageContainer: ComponentType<any> = lazy(() => import('./UsageContainer'));
|
|
13
14
|
export const JoinDialog: ComponentType<any> = lazy(() => import('./JoinDialog'));
|
|
14
15
|
export const ProfileContainer: ComponentType<any> = lazy(() => import('./ProfileContainer'));
|
|
15
16
|
export const RecoveryCodeDialog: ComponentType<any> = lazy(() => import('./RecoveryCodeDialog'));
|
package/src/meta.ts
CHANGED
|
@@ -3,16 +3,7 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import { Plugin } from '@dxos/app-framework';
|
|
6
|
-
import { DXN } from '@dxos/keys';
|
|
7
|
-
import { trim } from '@dxos/util';
|
|
8
6
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
author: 'DXOS',
|
|
13
|
-
description: trim`
|
|
14
|
-
Core client connectivity and peer-to-peer networking infrastructure.
|
|
15
|
-
Manages identity, authentication, and real-time synchronization across devices.
|
|
16
|
-
`,
|
|
17
|
-
tags: ['system'],
|
|
18
|
-
});
|
|
7
|
+
import config from '../dx.config';
|
|
8
|
+
|
|
9
|
+
export const meta = Plugin.getMetaFromConfig(config);
|
|
@@ -6,7 +6,7 @@ import * as Effect from 'effect/Effect';
|
|
|
6
6
|
import * as Match from 'effect/Match';
|
|
7
7
|
|
|
8
8
|
import { Capability } from '@dxos/app-framework';
|
|
9
|
-
import {
|
|
9
|
+
import { NativePasskey } from '@dxos/app-toolkit';
|
|
10
10
|
import { PublicKey } from '@dxos/client';
|
|
11
11
|
import { Operation } from '@dxos/compute';
|
|
12
12
|
import { invariant } from '@dxos/invariant';
|
|
@@ -23,16 +23,18 @@ const handler: Operation.WithHandler<typeof CreatePasskey> = CreatePasskey.pipe(
|
|
|
23
23
|
|
|
24
24
|
const lookupKey = PublicKey.random();
|
|
25
25
|
|
|
26
|
-
const { recoveryKey, algorithm } = yield* Match.value(supportsNativePasskeys()).pipe(
|
|
26
|
+
const { recoveryKey, algorithm } = yield* Match.value(NativePasskey.supportsNativePasskeys()).pipe(
|
|
27
27
|
Match.when(true, () =>
|
|
28
28
|
Effect.gen(function* () {
|
|
29
29
|
const result = yield* Effect.promise(() =>
|
|
30
|
-
createNativePasskey({
|
|
30
|
+
NativePasskey.createNativePasskey({
|
|
31
31
|
username: identity.did,
|
|
32
32
|
userId: lookupKey.asUint8Array(),
|
|
33
33
|
}),
|
|
34
34
|
);
|
|
35
|
-
const { publicKey, algorithm: alg } = extractPublicKeyFromAttestation(
|
|
35
|
+
const { publicKey, algorithm: alg } = NativePasskey.extractPublicKeyFromAttestation(
|
|
36
|
+
result.attestation_object,
|
|
37
|
+
);
|
|
36
38
|
return {
|
|
37
39
|
recoveryKey: PublicKey.from(publicKey),
|
|
38
40
|
algorithm: alg === -7 ? 'ES256' : 'ED25519',
|
|
@@ -10,7 +10,7 @@ import { DXN, IdentityDid, SpaceId } from '@dxos/keys';
|
|
|
10
10
|
|
|
11
11
|
import { meta } from '#meta';
|
|
12
12
|
|
|
13
|
-
const makeKey = (name: string) => DXN.make(`${meta.
|
|
13
|
+
const makeKey = (name: string) => DXN.make(`${meta.profile.key}.operation.${name}`);
|
|
14
14
|
|
|
15
15
|
const IdentitySchema = Schema.Struct({
|
|
16
16
|
identityDid: IdentityDid,
|
|
@@ -57,6 +57,17 @@ export const ShareIdentity = Operation.make({
|
|
|
57
57
|
output: Schema.Void,
|
|
58
58
|
});
|
|
59
59
|
|
|
60
|
+
export const OpenUsage = Operation.make({
|
|
61
|
+
meta: {
|
|
62
|
+
key: makeKey('openUsage'),
|
|
63
|
+
name: 'Open Usage',
|
|
64
|
+
icon: 'ph--chart-bar--regular',
|
|
65
|
+
},
|
|
66
|
+
services: [Capability.Service],
|
|
67
|
+
input: Schema.Void,
|
|
68
|
+
output: Schema.Void,
|
|
69
|
+
});
|
|
70
|
+
|
|
60
71
|
export const RecoverIdentity = Operation.make({
|
|
61
72
|
meta: { key: makeKey('recoverIdentity'), name: 'Recover Identity', icon: 'ph--key--regular' },
|
|
62
73
|
services: [Capability.Service],
|
package/src/operations/index.ts
CHANGED
|
@@ -12,6 +12,7 @@ export const ClientOperationHandlerSet = OperationHandlerSet.lazy(
|
|
|
12
12
|
() => import('./create-passkey'),
|
|
13
13
|
() => import('./create-recovery-code'),
|
|
14
14
|
() => import('./join-identity'),
|
|
15
|
+
() => import('./open-usage'),
|
|
15
16
|
() => import('./recover-identity'),
|
|
16
17
|
() => import('./redeem-passkey'),
|
|
17
18
|
() => import('./redeem-token'),
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2026 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import * as Effect from 'effect/Effect';
|
|
6
|
+
|
|
7
|
+
import { LayoutOperation, Paths } from '@dxos/app-toolkit';
|
|
8
|
+
import { Operation } from '@dxos/compute';
|
|
9
|
+
|
|
10
|
+
import { Account } from '../types';
|
|
11
|
+
import { OpenUsage } from './definitions';
|
|
12
|
+
|
|
13
|
+
const handler: Operation.WithHandler<typeof OpenUsage> = OpenUsage.pipe(
|
|
14
|
+
Operation.withHandler(
|
|
15
|
+
Effect.fnUntraced(function* () {
|
|
16
|
+
yield* Operation.invoke(LayoutOperation.SwitchWorkspace, { subject: Paths.getSpacePath(Account.id) });
|
|
17
|
+
yield* Operation.invoke(LayoutOperation.Open, {
|
|
18
|
+
subject: [Paths.getSpacePath(Account.id, Account.Usage)],
|
|
19
|
+
});
|
|
20
|
+
}),
|
|
21
|
+
),
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
export default handler;
|