@dxos/shell 0.6.13 → 0.6.14-main.1366248

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 +15995 -15601
  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 +432 -230
  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 +4766 -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 +4812 -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 +51 -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 +7 -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 +89 -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 +92 -48
  167. package/src/panels/IdentityPanel/IdentityPanelProps.ts +7 -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 +103 -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
 
@@ -46,11 +47,13 @@ const IdentityHeading = ({
46
47
  titleId,
47
48
  title,
48
49
  identity,
50
+ hideRecover,
49
51
  onDone,
50
52
  onUpdateProfile,
51
53
  connectionState,
52
54
  onChangeConnectionState,
53
55
  }: IdentityPanelHeadingProps) => {
56
+ const client = useClient();
54
57
  const fallbackValue = keyToFallback(identity.identityKey);
55
58
  const { t } = useTranslation('os');
56
59
  const [displayName, setDisplayName] = useState(identity.profile?.displayName ?? '');
@@ -58,7 +61,7 @@ const IdentityHeading = ({
58
61
  const [hue, setHueDirectly] = useState<string>(getHueValue(identity));
59
62
  const { textValue, setTextValue } = useClipboardContext();
60
63
  const identityHex = identity.identityKey.toHex();
61
- const copied = textValue === identityHex;
64
+ const publicKeyCopied = textValue === identityHex;
62
65
 
63
66
  const setEmoji = (nextEmoji: string) => {
64
67
  setEmojiDirectly(nextEmoji);
@@ -70,9 +73,14 @@ const IdentityHeading = ({
70
73
  void onUpdateProfile?.({ ...identity.profile, data: { ...identity.profile?.data, hue: nextHue } });
71
74
  };
72
75
 
73
- const isConnected = connectionState === ConnectionState.ONLINE;
76
+ const handleRecoveryCode = async () => {
77
+ invariant(client.services.services.IdentityService, 'IdentityService not available');
78
+ // TODO(wittjosiah): This needs a proper api.
79
+ const { seedphrase } = await client.services.services.IdentityService.createRecoveryPhrase();
80
+ void setTextValue(seedphrase);
81
+ };
74
82
 
75
- const debug = true;
83
+ const isConnected = connectionState === ConnectionState.ONLINE;
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
+ {!hideRecover && (
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,
@@ -176,11 +199,21 @@ export const IdentityPanelImpl = (props: IdentityPanelImplProps) => {
176
199
  }, [activeView, t]);
177
200
 
178
201
  const onCancelReset = () => rest.send?.('unchooseAction');
202
+ const hideRecover = rest.agentStatus !== 'created';
179
203
 
180
204
  return (
181
- <DensityProvider density='fine'>
205
+ <>
182
206
  <IdentityHeading
183
- {...{ identity, titleId, title, onDone, onUpdateProfile, connectionState, onChangeConnectionState }}
207
+ {...{
208
+ identity,
209
+ titleId,
210
+ title,
211
+ hideRecover,
212
+ onDone,
213
+ onUpdateProfile,
214
+ connectionState,
215
+ onChangeConnectionState,
216
+ }}
184
217
  />
185
218
  <Viewport.Root activeView={activeView}>
186
219
  <Viewport.Views>
@@ -207,6 +240,15 @@ export const IdentityPanelImpl = (props: IdentityPanelImplProps) => {
207
240
  onCancel={onCancelReset}
208
241
  />
209
242
  </Viewport.View>
243
+ <Viewport.View classNames={viewStyles} id='confirm recover'>
244
+ <ConfirmReset
245
+ active={activeView === 'confirm recover'}
246
+ {...rest}
247
+ mode='recover'
248
+ onConfirm={onRecover}
249
+ onCancel={onCancelReset}
250
+ />
251
+ </Viewport.View>
210
252
  <Viewport.View classNames={viewStyles} id='confirm reset storage'>
211
253
  <ConfirmReset
212
254
  active={activeView === 'confirm reset storage'}
@@ -218,7 +260,7 @@ export const IdentityPanelImpl = (props: IdentityPanelImplProps) => {
218
260
  </Viewport.View>
219
261
  </Viewport.Views>
220
262
  </Viewport.Root>
221
- </DensityProvider>
263
+ </>
222
264
  );
223
265
  };
224
266
 
@@ -270,6 +312,8 @@ export const IdentityPanel = ({
270
312
  return 'device invitation manager';
271
313
  case identityState.matches('confirmingResetStorage'):
272
314
  return 'confirm reset storage';
315
+ case identityState.matches('confirmingRecover'):
316
+ return 'confirm recover';
273
317
  case identityState.matches('confirmingJoinNewIdentity'):
274
318
  return 'confirm join new identity';
275
319
  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
 
@@ -47,6 +49,7 @@ export type IdentityPanelHeadingProps = Pick<
47
49
  'titleId' | 'identity' | 'onDone' | 'onUpdateProfile' | 'connectionState' | 'onChangeConnectionState'
48
50
  > & {
49
51
  title: string;
52
+ hideRecover?: boolean;
50
53
  };
51
54
 
52
55
  export type IdentityPanelStepProps = Pick<
@@ -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={agentProps.agentStatus === 'created' ? onClickRecover : undefined}
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,103 @@
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 { EdgeReplicationSetting } from '@dxos/protocols/proto/dxos/echo/metadata';
11
+ import { useClient } from '@dxos/react-client';
12
+ import { type Identity } from '@dxos/react-client/halo';
13
+
14
+ import { type AgentFormProps } from '../../components';
15
+
16
+ export const useEdgeAgentHandlers = ({
17
+ identity,
18
+ }: {
19
+ invitations: CancellableInvitation[];
20
+ identity: Identity | null;
21
+ }): AgentFormProps => {
22
+ const [validationMessage, setValidationMessage] = useState('');
23
+ const [lastReceivedStatus, setLastReceivedStatus] = useState<QueryAgentStatusResponse.AgentStatus>(
24
+ QueryAgentStatusResponse.AgentStatus.UNKNOWN,
25
+ );
26
+ const [agentStatus, setAgentStatus] = useState<AgentFormProps['agentStatus']>('getting');
27
+ const client = useClient();
28
+ // TODO(wittjosiah): This should be based on HALO credentials.
29
+ const agentHostingEnabled = Boolean(client.config.values.runtime?.client?.edgeFeatures?.agents);
30
+
31
+ const handleStatus = (status: QueryAgentStatusResponse.AgentStatus) => {
32
+ switch (status) {
33
+ case QueryAgentStatusResponse.AgentStatus.ACTIVE:
34
+ case QueryAgentStatusResponse.AgentStatus.INACTIVE:
35
+ setAgentStatus('created');
36
+ break;
37
+ case QueryAgentStatusResponse.AgentStatus.NOT_FOUND:
38
+ setAgentStatus('creatable');
39
+ break;
40
+ case QueryAgentStatusResponse.AgentStatus.UNKNOWN:
41
+ setAgentStatus('getting');
42
+ break;
43
+ }
44
+ };
45
+
46
+ useEffect(() => {
47
+ const stream = client.services.services.EdgeAgentService?.queryAgentStatus();
48
+ stream?.subscribe(({ status }) => {
49
+ setLastReceivedStatus(status);
50
+ handleStatus(status);
51
+ });
52
+ return () => {
53
+ void stream?.close().catch(() => {});
54
+ };
55
+ }, []);
56
+
57
+ const onAgentCreate = async () => {
58
+ invariant(identity, 'Identity not found');
59
+ const service = client.services.services.EdgeAgentService;
60
+ if (!service) {
61
+ setAgentStatus('error');
62
+ setValidationMessage('Agent service not initialized.');
63
+ return;
64
+ }
65
+ if (!agentHostingEnabled) {
66
+ setAgentStatus('error');
67
+ setValidationMessage('Edge agents are disabled in config.');
68
+ return;
69
+ }
70
+ setAgentStatus('creating');
71
+ setValidationMessage('');
72
+ try {
73
+ await service.createAgent();
74
+ await Promise.all(
75
+ client.spaces
76
+ .get()
77
+ .map((space) => space?.internal.setEdgeReplicationPreference(EdgeReplicationSetting.ENABLED)),
78
+ );
79
+ setAgentStatus('created');
80
+ } catch (err: any) {
81
+ setAgentStatus('error');
82
+ setValidationMessage(`Error creating an agent: ${err.message}`);
83
+ }
84
+ };
85
+
86
+ const onAgentDestroy = async () => {
87
+ setValidationMessage('Agents are non-destructible.');
88
+ };
89
+
90
+ const onAgentRefresh = async () => {
91
+ setValidationMessage('');
92
+ handleStatus(lastReceivedStatus);
93
+ };
94
+
95
+ return {
96
+ onAgentCreate,
97
+ onAgentDestroy,
98
+ onAgentRefresh,
99
+ validationMessage,
100
+ agentStatus,
101
+ agentHostingEnabled,
102
+ };
103
+ };