@dxos/shell 0.6.13 → 0.6.14-main.69511f5

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.
Files changed (192) hide show
  1. package/dist/bundle/shell.mjs +16256 -15872
  2. package/dist/bundle/shell.mjs.map +1 -1
  3. package/dist/bundle/shell.umd.js +146 -108
  4. package/dist/bundle/shell.umd.js.map +1 -1
  5. package/dist/bundle/style.css +1 -1
  6. package/dist/lib/browser/meta.json +1 -1
  7. package/dist/lib/browser/react.mjs +428 -229
  8. package/dist/lib/browser/react.mjs.map +4 -4
  9. package/dist/lib/browser/testing/index.mjs +3 -46
  10. package/dist/lib/browser/testing/index.mjs.map +4 -4
  11. package/dist/lib/node/chunk-Q7SFCCGT.cjs +33 -0
  12. package/dist/lib/node/chunk-Q7SFCCGT.cjs.map +7 -0
  13. package/dist/lib/node/meta.json +1 -0
  14. package/dist/lib/node/react.cjs +4763 -0
  15. package/dist/lib/node/react.cjs.map +7 -0
  16. package/dist/lib/node/react.css +7 -0
  17. package/dist/lib/node/react.css.map +7 -0
  18. package/dist/lib/node/testing/index.cjs +181 -0
  19. package/dist/lib/node/testing/index.cjs.map +7 -0
  20. package/dist/lib/node-esm/chunk-HSLMI22Q.mjs +11 -0
  21. package/dist/lib/node-esm/chunk-HSLMI22Q.mjs.map +7 -0
  22. package/dist/lib/node-esm/meta.json +1 -0
  23. package/dist/lib/node-esm/react.css +7 -0
  24. package/dist/lib/node-esm/react.css.map +7 -0
  25. package/dist/lib/node-esm/react.mjs +4809 -0
  26. package/dist/lib/node-esm/react.mjs.map +7 -0
  27. package/dist/lib/node-esm/testing/index.mjs +161 -0
  28. package/dist/lib/node-esm/testing/index.mjs.map +7 -0
  29. package/dist/types/src/components/CompoundButton/CompoundButton.stories.js +1 -1
  30. package/dist/types/src/components/CompoundButton/CompoundButton.stories.js.map +1 -1
  31. package/dist/types/src/components/DeviceList/DeviceList.d.ts +1 -1
  32. package/dist/types/src/components/DeviceList/DeviceList.d.ts.map +1 -1
  33. package/dist/types/src/components/DeviceList/DeviceList.js +2 -2
  34. package/dist/types/src/components/DeviceList/DeviceList.js.map +1 -1
  35. package/dist/types/src/components/DeviceList/DeviceListItem.d.ts.map +1 -1
  36. package/dist/types/src/components/DeviceList/DeviceListItem.js +4 -1
  37. package/dist/types/src/components/DeviceList/DeviceListItem.js.map +1 -1
  38. package/dist/types/src/components/DeviceList/DeviceListProps.d.ts +2 -0
  39. package/dist/types/src/components/DeviceList/DeviceListProps.d.ts.map +1 -1
  40. package/dist/types/src/components/IdentityList/SpaceMemberList.stories.d.ts +0 -3
  41. package/dist/types/src/components/IdentityList/SpaceMemberList.stories.d.ts.map +1 -1
  42. package/dist/types/src/components/IdentityList/SpaceMemberList.stories.js +3 -4
  43. package/dist/types/src/components/IdentityList/SpaceMemberList.stories.js.map +1 -1
  44. package/dist/types/src/components/InvitationList/InvitationList.stories.d.ts +0 -3
  45. package/dist/types/src/components/InvitationList/InvitationList.stories.d.ts.map +1 -1
  46. package/dist/types/src/components/InvitationList/InvitationList.stories.js +11 -12
  47. package/dist/types/src/components/InvitationList/InvitationList.stories.js.map +1 -1
  48. package/dist/types/src/components/InvitationList/InvitationListContainer.d.ts.map +1 -1
  49. package/dist/types/src/components/InvitationList/InvitationListContainer.js +2 -2
  50. package/dist/types/src/components/InvitationList/InvitationListContainer.js.map +1 -1
  51. package/dist/types/src/components/Panel/Panel.stories.d.ts +0 -3
  52. package/dist/types/src/components/Panel/Panel.stories.d.ts.map +1 -1
  53. package/dist/types/src/components/Panel/Panel.stories.js +1 -2
  54. package/dist/types/src/components/Panel/Panel.stories.js.map +1 -1
  55. package/dist/types/src/components/Viewport/Viewport.stories.js +1 -1
  56. package/dist/types/src/components/Viewport/Viewport.stories.js.map +1 -1
  57. package/dist/types/src/composites/Shell/Shell.d.ts.map +1 -1
  58. package/dist/types/src/composites/Shell/Shell.js +14 -3
  59. package/dist/types/src/composites/Shell/Shell.js.map +1 -1
  60. package/dist/types/src/composites/Shell/run-shell.js +1 -1
  61. package/dist/types/src/composites/Shell/run-shell.js.map +1 -1
  62. package/dist/types/src/panels/IdentityPanel/IdentityPanel.d.ts.map +1 -1
  63. package/dist/types/src/panels/IdentityPanel/IdentityPanel.js +44 -25
  64. package/dist/types/src/panels/IdentityPanel/IdentityPanel.js.map +1 -1
  65. package/dist/types/src/panels/IdentityPanel/IdentityPanel.stories.d.ts.map +1 -1
  66. package/dist/types/src/panels/IdentityPanel/IdentityPanel.stories.js +7 -2
  67. package/dist/types/src/panels/IdentityPanel/IdentityPanel.stories.js.map +1 -1
  68. package/dist/types/src/panels/IdentityPanel/IdentityPanelProps.d.ts +6 -5
  69. package/dist/types/src/panels/IdentityPanel/IdentityPanelProps.d.ts.map +1 -1
  70. package/dist/types/src/panels/IdentityPanel/identityMachine.d.ts +1 -1
  71. package/dist/types/src/panels/IdentityPanel/identityMachine.d.ts.map +1 -1
  72. package/dist/types/src/panels/IdentityPanel/identityMachine.js +2 -0
  73. package/dist/types/src/panels/IdentityPanel/identityMachine.js.map +1 -1
  74. package/dist/types/src/panels/IdentityPanel/steps/IdentityActionChooser.d.ts +3 -5
  75. package/dist/types/src/panels/IdentityPanel/steps/IdentityActionChooser.d.ts.map +1 -1
  76. package/dist/types/src/panels/IdentityPanel/steps/IdentityActionChooser.js +5 -3
  77. package/dist/types/src/panels/IdentityPanel/steps/IdentityActionChooser.js.map +1 -1
  78. package/dist/types/src/panels/IdentityPanel/useAgentHandlers.d.ts +1 -1
  79. package/dist/types/src/panels/IdentityPanel/useAgentHandlers.d.ts.map +1 -1
  80. package/dist/types/src/panels/IdentityPanel/useAgentHandlers.js +10 -108
  81. package/dist/types/src/panels/IdentityPanel/useAgentHandlers.js.map +1 -1
  82. package/dist/types/src/panels/IdentityPanel/useEdgeAgentsHandlers.d.ts +8 -0
  83. package/dist/types/src/panels/IdentityPanel/useEdgeAgentsHandlers.d.ts.map +1 -0
  84. package/dist/types/src/panels/IdentityPanel/useEdgeAgentsHandlers.js +84 -0
  85. package/dist/types/src/panels/IdentityPanel/useEdgeAgentsHandlers.js.map +1 -0
  86. package/dist/types/src/panels/IdentityPanel/useKubeAgentHandlers.d.ts +8 -0
  87. package/dist/types/src/panels/IdentityPanel/useKubeAgentHandlers.d.ts.map +1 -0
  88. package/dist/types/src/panels/IdentityPanel/useKubeAgentHandlers.js +119 -0
  89. package/dist/types/src/panels/IdentityPanel/useKubeAgentHandlers.js.map +1 -0
  90. package/dist/types/src/panels/JoinPanel/JoinPanel.d.ts.map +1 -1
  91. package/dist/types/src/panels/JoinPanel/JoinPanel.js +24 -16
  92. package/dist/types/src/panels/JoinPanel/JoinPanel.js.map +1 -1
  93. package/dist/types/src/panels/JoinPanel/JoinPanel.stories.d.ts +3 -0
  94. package/dist/types/src/panels/JoinPanel/JoinPanel.stories.d.ts.map +1 -1
  95. package/dist/types/src/panels/JoinPanel/JoinPanel.stories.js +3 -2
  96. package/dist/types/src/panels/JoinPanel/JoinPanel.stories.js.map +1 -1
  97. package/dist/types/src/panels/JoinPanel/JoinPanelProps.d.ts +8 -4
  98. package/dist/types/src/panels/JoinPanel/JoinPanelProps.d.ts.map +1 -1
  99. package/dist/types/src/panels/JoinPanel/joinMachine.d.ts.map +1 -1
  100. package/dist/types/src/panels/JoinPanel/joinMachine.js +8 -0
  101. package/dist/types/src/panels/JoinPanel/joinMachine.js.map +1 -1
  102. package/dist/types/src/panels/JoinPanel/steps/AdditionMethodChooser.d.ts.map +1 -1
  103. package/dist/types/src/panels/JoinPanel/steps/AdditionMethodChooser.js +1 -3
  104. package/dist/types/src/panels/JoinPanel/steps/AdditionMethodChooser.js.map +1 -1
  105. package/dist/types/src/panels/JoinPanel/steps/IdentityInput.d.ts.map +1 -1
  106. package/dist/types/src/panels/JoinPanel/steps/IdentityInput.js +17 -7
  107. package/dist/types/src/panels/JoinPanel/steps/IdentityInput.js.map +1 -1
  108. package/dist/types/src/panels/JoinPanel/steps/InvitationAuthenticator.d.ts +3 -1
  109. package/dist/types/src/panels/JoinPanel/steps/InvitationAuthenticator.d.ts.map +1 -1
  110. package/dist/types/src/panels/JoinPanel/steps/InvitationAuthenticator.js +9 -6
  111. package/dist/types/src/panels/JoinPanel/steps/InvitationAuthenticator.js.map +1 -1
  112. package/dist/types/src/panels/JoinPanel/steps/InvitationInput.d.ts.map +1 -1
  113. package/dist/types/src/panels/JoinPanel/steps/InvitationInput.js +8 -2
  114. package/dist/types/src/panels/JoinPanel/steps/InvitationInput.js.map +1 -1
  115. package/dist/types/src/panels/Panels.stories.js +1 -1
  116. package/dist/types/src/panels/Panels.stories.js.map +1 -1
  117. package/dist/types/src/panels/SpacePanel/SpacePanel.js +1 -1
  118. package/dist/types/src/panels/SpacePanel/SpacePanel.js.map +1 -1
  119. package/dist/types/src/panels/SpacePanel/SpacePanel.stories.js +16 -16
  120. package/dist/types/src/panels/SpacePanel/SpacePanel.stories.js.map +1 -1
  121. package/dist/types/src/panels/SpacePanel/SpacePanelProps.d.ts +4 -4
  122. package/dist/types/src/panels/SpacePanel/SpacePanelProps.d.ts.map +1 -1
  123. package/dist/types/src/playwright/invitations-manager.d.ts +2 -3
  124. package/dist/types/src/playwright/invitations-manager.d.ts.map +1 -1
  125. package/dist/types/src/playwright/invitations-manager.js +8 -15
  126. package/dist/types/src/playwright/invitations-manager.js.map +1 -1
  127. package/dist/types/src/playwright/invitations.spec.js +40 -53
  128. package/dist/types/src/playwright/invitations.spec.js.map +1 -1
  129. package/dist/types/src/playwright/playwright.config.d.ts +2 -2
  130. package/dist/types/src/playwright/playwright.config.d.ts.map +1 -1
  131. package/dist/types/src/playwright/playwright.config.js +12 -2
  132. package/dist/types/src/playwright/playwright.config.js.map +1 -1
  133. package/dist/types/src/steps/ConfirmReset.d.ts +1 -1
  134. package/dist/types/src/steps/ConfirmReset.d.ts.map +1 -1
  135. package/dist/types/src/steps/ConfirmReset.js +6 -2
  136. package/dist/types/src/steps/ConfirmReset.js.map +1 -1
  137. package/dist/types/src/stories/Invitations.e2e-stories.d.ts +1 -3
  138. package/dist/types/src/stories/Invitations.e2e-stories.d.ts.map +1 -1
  139. package/dist/types/src/stories/Invitations.e2e-stories.js +14 -6
  140. package/dist/types/src/stories/Invitations.e2e-stories.js.map +1 -1
  141. package/dist/types/src/testing/index.d.ts +0 -1
  142. package/dist/types/src/testing/index.d.ts.map +1 -1
  143. package/dist/types/src/testing/index.js +1 -1
  144. package/dist/types/src/testing/index.js.map +1 -1
  145. package/dist/types/src/testing/shell-manager.d.ts +1 -0
  146. package/dist/types/src/testing/shell-manager.d.ts.map +1 -1
  147. package/dist/types/src/testing/shell-manager.js +2 -7
  148. package/dist/types/src/testing/shell-manager.js.map +1 -1
  149. package/dist/types/src/translations/locales/en-US.d.ts +3 -0
  150. package/dist/types/src/translations/locales/en-US.d.ts.map +1 -1
  151. package/dist/types/src/translations/locales/en-US.js +6 -3
  152. package/dist/types/src/translations/locales/en-US.js.map +1 -1
  153. package/package.json +42 -29
  154. package/src/components/CompoundButton/CompoundButton.stories.tsx +1 -1
  155. package/src/components/DeviceList/DeviceList.tsx +2 -1
  156. package/src/components/DeviceList/DeviceListItem.tsx +8 -0
  157. package/src/components/DeviceList/DeviceListProps.ts +2 -0
  158. package/src/components/IdentityList/SpaceMemberList.stories.tsx +2 -3
  159. package/src/components/InvitationList/InvitationList.stories.tsx +2 -3
  160. package/src/components/InvitationList/InvitationListContainer.tsx +6 -3
  161. package/src/components/Panel/Panel.stories.tsx +1 -2
  162. package/src/components/Viewport/Viewport.stories.tsx +1 -1
  163. package/src/composites/Shell/Shell.tsx +16 -4
  164. package/src/composites/Shell/run-shell.tsx +1 -1
  165. package/src/panels/IdentityPanel/IdentityPanel.stories.tsx +7 -2
  166. package/src/panels/IdentityPanel/IdentityPanel.tsx +81 -47
  167. package/src/panels/IdentityPanel/IdentityPanelProps.ts +6 -4
  168. package/src/panels/IdentityPanel/identityMachine.ts +3 -1
  169. package/src/panels/IdentityPanel/steps/IdentityActionChooser.tsx +21 -10
  170. package/src/panels/IdentityPanel/useAgentHandlers.ts +9 -114
  171. package/src/panels/IdentityPanel/useEdgeAgentsHandlers.ts +96 -0
  172. package/src/panels/IdentityPanel/useKubeAgentHandlers.ts +128 -0
  173. package/src/panels/JoinPanel/JoinPanel.stories.tsx +3 -2
  174. package/src/panels/JoinPanel/JoinPanel.tsx +31 -9
  175. package/src/panels/JoinPanel/JoinPanelProps.ts +8 -4
  176. package/src/panels/JoinPanel/joinMachine.ts +9 -0
  177. package/src/panels/JoinPanel/steps/AdditionMethodChooser.tsx +0 -2
  178. package/src/panels/JoinPanel/steps/IdentityInput.tsx +22 -10
  179. package/src/panels/JoinPanel/steps/InvitationAuthenticator.tsx +29 -16
  180. package/src/panels/JoinPanel/steps/InvitationInput.tsx +9 -17
  181. package/src/panels/Panels.stories.tsx +1 -1
  182. package/src/panels/SpacePanel/SpacePanel.stories.tsx +2 -2
  183. package/src/panels/SpacePanel/SpacePanel.tsx +3 -3
  184. package/src/panels/SpacePanel/SpacePanelProps.ts +4 -4
  185. package/src/playwright/invitations-manager.ts +9 -13
  186. package/src/playwright/invitations.spec.ts +42 -54
  187. package/src/playwright/playwright.config.ts +13 -2
  188. package/src/steps/ConfirmReset.tsx +13 -5
  189. package/src/stories/Invitations.e2e-stories.tsx +17 -10
  190. package/src/testing/index.ts +2 -1
  191. package/src/testing/shell-manager.ts +4 -9
  192. package/src/translations/locales/en-US.ts +6 -3
@@ -2,17 +2,18 @@
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';
12
13
  import { useInvitationStatus } from '@dxos/react-client/invitations';
13
14
  import { type CancellableInvitationObservable } from '@dxos/react-client/invitations';
14
15
  import { useNetworkStatus, ConnectionState } from '@dxos/react-client/mesh';
15
- import { Avatar, DensityProvider, Input, Toolbar, Tooltip, useId, useTranslation } from '@dxos/react-ui';
16
+ import { Avatar, Input, Toolbar, Tooltip, useId, useTranslation } from '@dxos/react-ui';
16
17
  import { getSize } from '@dxos/react-ui-theme';
17
18
  import { hexToEmoji, hexToHue, keyToFallback } from '@dxos/util';
18
19
 
@@ -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 copied = textValue === identityHex;
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 isConnected = connectionState === ConnectionState.ONLINE;
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 debug = true;
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
- {debug && (
107
- <>
108
- <Tooltip.Root>
109
- <Tooltip.Trigger asChild>
110
- <Toolbar.Button
111
- classNames='bs-[--rail-action]'
112
- data-testid='update-profile-form-copy-key'
113
- onClick={() => setTextValue(identityHex)}
114
- >
115
- <span className='sr-only'>{t(copied ? 'copy success label' : 'copy self public key label')}</span>
116
- <Key className={getSize(5)} />
117
- </Toolbar.Button>
118
- </Tooltip.Trigger>
119
- <Tooltip.Portal>
120
- <Tooltip.Content side='bottom' classNames='z-50'>
121
- {t(copied ? 'copy success label' : 'copy self public key label')}
122
- <Tooltip.Arrow />
123
- </Tooltip.Content>
124
- </Tooltip.Portal>
125
- <Tooltip.Root>
126
- <Tooltip.Trigger asChild>
127
- <Toolbar.Button
128
- classNames={['bs-[--rail-action]', !isConnected && 'text-error-500']}
129
- onClick={() =>
130
- onChangeConnectionState?.(isConnected ? ConnectionState.OFFLINE : ConnectionState.ONLINE)
131
- }
132
- >
133
- <span className='sr-only'>{t(isConnected ? 'disconnect label' : 'connect label')}</span>
134
- {isConnected ? <PlugsConnected className={getSize(5)} /> : <Plugs className={getSize(5)} />}
135
- </Toolbar.Button>
136
- </Tooltip.Trigger>
137
- <Tooltip.Portal>
138
- <Tooltip.Content side='bottom' classNames='z-50'>
139
- {t(isConnected ? 'disconnect label' : 'connect label')}
140
- <Tooltip.Arrow />
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,
@@ -178,7 +201,7 @@ export const IdentityPanelImpl = (props: IdentityPanelImplProps) => {
178
201
  const onCancelReset = () => rest.send?.('unchooseAction');
179
202
 
180
203
  return (
181
- <DensityProvider density='fine'>
204
+ <>
182
205
  <IdentityHeading
183
206
  {...{ identity, titleId, title, onDone, onUpdateProfile, connectionState, onChangeConnectionState }}
184
207
  />
@@ -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'}
@@ -218,7 +250,7 @@ export const IdentityPanelImpl = (props: IdentityPanelImplProps) => {
218
250
  </Viewport.View>
219
251
  </Viewport.Views>
220
252
  </Viewport.Root>
221
- </DensityProvider>
253
+ </>
222
254
  );
223
255
  };
224
256
 
@@ -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:
@@ -2,7 +2,7 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- import type { cloneElement } from 'react';
5
+ import type { FC, cloneElement } from 'react';
6
6
  import type { Event, SingleOrArray } from 'xstate';
7
7
 
8
8
  import type { Device, Identity } from '@dxos/react-client/halo';
@@ -10,7 +10,7 @@ import type { InvitationStatus } from '@dxos/react-client/invitations';
10
10
  import type { ConnectionState } from '@dxos/react-client/mesh';
11
11
 
12
12
  import type { IdentityEvent } from './identityMachine';
13
- import { type AgentFormProps } from '../../components';
13
+ import type { AgentFormProps } from '../../components';
14
14
  import type { InvitationManagerProps } from '../../steps';
15
15
 
16
16
  export type IdentityPanelInitialDisposition = 'default' | 'manage-device-invitation';
@@ -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,14 +30,15 @@ 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;
35
37
  onDone?: () => void;
36
38
  doneActionParent?: Parameters<typeof cloneElement>[0];
37
39
  invitationUrl?: string;
38
- IdentityActionChooser?: React.FC<IdentityPanelStepProps>;
39
- InvitationManager?: React.FC<InvitationManagerProps>;
40
+ IdentityActionChooser?: FC<IdentityPanelStepProps>;
41
+ InvitationManager?: FC<InvitationManagerProps>;
40
42
  } & Partial<InvitationStatus> &
41
43
  Partial<AgentFormProps>;
42
44
 
@@ -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
- return <IdentityActionChooserImpl {...props} onCreateInvitationClick={createInvitation} />;
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
- onCreateInvitationClick?: () => void;
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={onCreateInvitationClick}
59
- onClickJoinExisting={() => send?.({ type: 'chooseJoinNewIdentity' })}
60
- onClickReset={() => send?.({ type: 'chooseResetStorage' })}
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)} />}
@@ -2,127 +2,22 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import { useCallback, useEffect, useState } from 'react';
6
-
7
5
  import { type CancellableInvitation } from '@dxos/client-protocol';
8
- import { invariant } from '@dxos/invariant';
9
- import { log } from '@dxos/log';
10
- import { useClient, useAgentHostingClient } from '@dxos/react-client';
6
+ import { useClient } from '@dxos/react-client';
11
7
  import { type Identity } from '@dxos/react-client/halo';
12
- import { Invitation, InvitationEncoder } from '@dxos/react-client/invitations';
13
8
 
9
+ import { useEdgeAgentHandlers } from './useEdgeAgentsHandlers';
10
+ import { useKubeAgentHandlers } from './useKubeAgentHandlers';
14
11
  import { type AgentFormProps } from '../../components';
15
12
 
16
- export const useAgentHandlers = ({
17
- invitations,
18
- identity,
19
- }: {
13
+ export const useAgentHandlers = (params: {
20
14
  invitations: CancellableInvitation[];
21
15
  identity: Identity | null;
22
16
  }): AgentFormProps => {
23
- const [validationMessage, setValidationMessage] = useState('');
24
- const [agentStatus, setAgentStatus] = useState<AgentFormProps['agentStatus']>('getting');
25
- const agentHostingProviderClient = useAgentHostingClient();
26
17
  const client = useClient();
27
-
28
- const updateAgentStatus = async () => {
29
- // TODO(nf): if agent exists, subscribe to updates instead of oneshot status
30
- if (agentHostingProviderClient) {
31
- setAgentStatus('getting');
32
- try {
33
- const agentId = await agentHostingProviderClient?.getAgent(identity?.identityKey.truncate() ?? 'never');
34
- if (agentId) {
35
- setAgentStatus('created');
36
- } else {
37
- setAgentStatus('creatable');
38
- }
39
- } catch (err: any) {
40
- setAgentStatus('error');
41
- setValidationMessage(`Couldn’t check agent status: ${err.message}`);
42
- }
43
- } else {
44
- setAgentStatus('error');
45
- setValidationMessage('No agent provider configured.');
46
- }
47
- };
48
-
49
- const createAgent = async (invitationCode: string) => {
50
- invariant(identity, 'Identity not found');
51
- if (agentHostingProviderClient) {
52
- setAgentStatus('creating');
53
- setValidationMessage('');
54
- try {
55
- const agentId = await agentHostingProviderClient?.createAgent(invitationCode, identity.identityKey.truncate());
56
- if (agentId) {
57
- setAgentStatus('created');
58
- } else {
59
- setAgentStatus('creatable');
60
- setValidationMessage('Error creating agent.');
61
- }
62
- } catch (err: any) {
63
- setValidationMessage(`Error creating agent: ${err.message}`);
64
- return updateAgentStatus();
65
- }
66
- } else {
67
- setAgentStatus('error');
68
- setValidationMessage('No agent provider configured.');
69
- }
70
- };
71
-
72
- const destroyAgent = async () => {
73
- invariant(identity, 'Identity not found');
74
- if (agentHostingProviderClient) {
75
- setAgentStatus('destroying');
76
- setValidationMessage('');
77
- try {
78
- await agentHostingProviderClient?.destroyAgent(identity.identityKey.truncate());
79
- setAgentStatus('creatable');
80
- } catch (err: any) {
81
- setValidationMessage(`Error destroying agent: ${err.message}`);
82
- return updateAgentStatus();
83
- }
84
- } else {
85
- setAgentStatus('error');
86
- setValidationMessage('No agent provider configured.');
87
- }
88
- };
89
-
90
- useEffect(() => {
91
- void updateAgentStatus();
92
- }, []);
93
-
94
- const handleAgentCreate = useCallback(async (invitation: Invitation) => {
95
- const invitationCode = InvitationEncoder.encode(invitation);
96
- if (invitation.state === Invitation.State.CONNECTING) {
97
- log.info(JSON.stringify({ invitationCode, authCode: invitation.authCode }));
98
- return createAgent(invitationCode);
99
- }
100
- }, []);
101
-
102
- const onAgentCreate = async () => {
103
- invitations.forEach((invitation) => invitation.cancel());
104
-
105
- // TODO(nf): do this work in the hosting provider client?
106
- const invitation = client.halo.share({
107
- type: Invitation.Type.INTERACTIVE,
108
- authMethod: Invitation.AuthMethod.NONE,
109
- multiUse: true,
110
- });
111
-
112
- invitation.subscribe(handleAgentCreate);
113
- };
114
-
115
- const onAgentRefresh = async () => {
116
- setValidationMessage('');
117
- await updateAgentStatus();
118
- };
119
-
120
- return {
121
- onAgentCreate,
122
- onAgentDestroy: destroyAgent,
123
- onAgentRefresh,
124
- validationMessage,
125
- agentStatus,
126
- agentHostingEnabled: !!agentHostingProviderClient,
127
- };
18
+ if (client.config.values?.runtime?.client?.edgeFeatures?.agents) {
19
+ return useEdgeAgentHandlers(params);
20
+ } else {
21
+ return useKubeAgentHandlers(params);
22
+ }
128
23
  };
@@ -0,0 +1,96 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import { useEffect, useState } from 'react';
6
+
7
+ import { type CancellableInvitation } from '@dxos/client-protocol';
8
+ import { invariant } from '@dxos/invariant';
9
+ import { QueryAgentStatusResponse } from '@dxos/protocols/proto/dxos/client/services';
10
+ import { useClient } from '@dxos/react-client';
11
+ import { type Identity } from '@dxos/react-client/halo';
12
+
13
+ import { type AgentFormProps } from '../../components';
14
+
15
+ export const useEdgeAgentHandlers = ({
16
+ identity,
17
+ }: {
18
+ invitations: CancellableInvitation[];
19
+ identity: Identity | null;
20
+ }): AgentFormProps => {
21
+ const [validationMessage, setValidationMessage] = useState('');
22
+ const [lastReceivedStatus, setLastReceivedStatus] = useState<QueryAgentStatusResponse.AgentStatus>(
23
+ QueryAgentStatusResponse.AgentStatus.UNKNOWN,
24
+ );
25
+ const [agentStatus, setAgentStatus] = useState<AgentFormProps['agentStatus']>('getting');
26
+ const client = useClient();
27
+ const agentHostingEnabled = Boolean(client.config.values.runtime?.client?.edgeFeatures?.agents);
28
+
29
+ const handleStatus = (status: QueryAgentStatusResponse.AgentStatus) => {
30
+ switch (status) {
31
+ case QueryAgentStatusResponse.AgentStatus.ACTIVE:
32
+ case QueryAgentStatusResponse.AgentStatus.INACTIVE:
33
+ setAgentStatus('created');
34
+ break;
35
+ case QueryAgentStatusResponse.AgentStatus.NOT_FOUND:
36
+ setAgentStatus('creatable');
37
+ break;
38
+ case QueryAgentStatusResponse.AgentStatus.UNKNOWN:
39
+ setAgentStatus('getting');
40
+ break;
41
+ }
42
+ };
43
+
44
+ useEffect(() => {
45
+ const stream = client.services.services.EdgeAgentService?.queryAgentStatus();
46
+ stream?.subscribe(({ status }) => {
47
+ setLastReceivedStatus(status);
48
+ handleStatus(status);
49
+ });
50
+ return () => {
51
+ void stream?.close().catch(() => {});
52
+ };
53
+ }, []);
54
+
55
+ const onAgentCreate = async () => {
56
+ invariant(identity, 'Identity not found');
57
+ const service = client.services.services.EdgeAgentService;
58
+ if (!service) {
59
+ setAgentStatus('error');
60
+ setValidationMessage('Agent service not initialized.');
61
+ return;
62
+ }
63
+ if (!agentHostingEnabled) {
64
+ setAgentStatus('error');
65
+ setValidationMessage('Edge agents are disabled in config.');
66
+ return;
67
+ }
68
+ setAgentStatus('creating');
69
+ setValidationMessage('');
70
+ try {
71
+ await service.createAgent();
72
+ setAgentStatus('created');
73
+ } catch (err: any) {
74
+ setAgentStatus('error');
75
+ setValidationMessage(`Error creating an agent: ${err.message}`);
76
+ }
77
+ };
78
+
79
+ const onAgentDestroy = async () => {
80
+ setValidationMessage('Agents are non-destructible.');
81
+ };
82
+
83
+ const onAgentRefresh = async () => {
84
+ setValidationMessage('');
85
+ handleStatus(lastReceivedStatus);
86
+ };
87
+
88
+ return {
89
+ onAgentCreate,
90
+ onAgentDestroy,
91
+ onAgentRefresh,
92
+ validationMessage,
93
+ agentStatus,
94
+ agentHostingEnabled,
95
+ };
96
+ };