@dxos/plugin-client 0.8.1-main.ae460ac → 0.8.1-staging.31c3ee1

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 (178) hide show
  1. package/dist/lib/browser/app-graph-builder-WAS4YZNA.mjs +119 -0
  2. package/dist/lib/browser/app-graph-builder-WAS4YZNA.mjs.map +7 -0
  3. package/dist/lib/browser/{chunk-OLUST5AO.mjs → chunk-4PLF3BEB.mjs} +2 -2
  4. package/dist/lib/browser/{chunk-7DPLXOMO.mjs → chunk-AN7FJKIJ.mjs} +21 -4
  5. package/dist/lib/browser/{chunk-7DPLXOMO.mjs.map → chunk-AN7FJKIJ.mjs.map} +3 -3
  6. package/dist/lib/browser/{chunk-XK4HM5M7.mjs → chunk-BGY6BSUC.mjs} +3 -2
  7. package/dist/lib/browser/{chunk-XK4HM5M7.mjs.map → chunk-BGY6BSUC.mjs.map} +3 -3
  8. package/dist/lib/browser/chunk-CAM6IWZ2.mjs +619 -0
  9. package/dist/lib/browser/chunk-CAM6IWZ2.mjs.map +7 -0
  10. package/dist/lib/browser/{chunk-NHIL4T3L.mjs → chunk-MCEXD5XH.mjs} +1 -1
  11. package/dist/lib/browser/{chunk-NHIL4T3L.mjs.map → chunk-MCEXD5XH.mjs.map} +2 -2
  12. package/dist/lib/browser/{client-UUGITHCW.mjs → client-LV67Q3R7.mjs} +5 -5
  13. package/dist/lib/browser/{client-UUGITHCW.mjs.map → client-LV67Q3R7.mjs.map} +3 -3
  14. package/dist/lib/browser/index.mjs +51 -22
  15. package/dist/lib/browser/index.mjs.map +2 -2
  16. package/dist/lib/browser/{intent-resolver-AIUHL34E.mjs → intent-resolver-JAQCJLKQ.mjs} +65 -27
  17. package/dist/lib/browser/intent-resolver-JAQCJLKQ.mjs.map +7 -0
  18. package/dist/lib/browser/meta.json +1 -1
  19. package/dist/lib/browser/{migrations-XODC625X.mjs → migrations-3DWFOL5Q.mjs} +3 -3
  20. package/dist/lib/browser/{react-context-JBD7Y3SN.mjs → react-context-WEH7SL4I.mjs} +3 -3
  21. package/dist/lib/browser/react-surface-M6Q6RT3Q.mjs +66 -0
  22. package/dist/lib/browser/react-surface-M6Q6RT3Q.mjs.map +7 -0
  23. package/dist/lib/browser/{schema-Y7ZNXYSL.mjs → schema-JQAT6Q7S.mjs} +3 -3
  24. package/dist/lib/browser/types.mjs +4 -2
  25. package/dist/lib/node/app-graph-builder-PXOJZJO5.cjs +130 -0
  26. package/dist/lib/node/app-graph-builder-PXOJZJO5.cjs.map +7 -0
  27. package/dist/lib/node/chunk-ARZVO2AR.cjs +644 -0
  28. package/dist/lib/node/chunk-ARZVO2AR.cjs.map +7 -0
  29. package/dist/lib/node/{chunk-OIFPM5ZW.cjs → chunk-DJKWB5L6.cjs} +4 -4
  30. package/dist/lib/node/{chunk-OIFPM5ZW.cjs.map → chunk-DJKWB5L6.cjs.map} +2 -2
  31. package/dist/lib/node/{chunk-TREVKZCJ.cjs → chunk-FROKBEVH.cjs} +25 -7
  32. package/dist/lib/node/chunk-FROKBEVH.cjs.map +7 -0
  33. package/dist/lib/node/{chunk-FR2K56EV.cjs → chunk-W64EOF2B.cjs} +10 -10
  34. package/dist/lib/node/{chunk-T4K27K2F.cjs → chunk-ZVQIICEN.cjs} +9 -8
  35. package/dist/lib/node/{chunk-T4K27K2F.cjs.map → chunk-ZVQIICEN.cjs.map} +3 -3
  36. package/dist/lib/node/{client-5BHSU35T.cjs → client-BKTGRBYM.cjs} +9 -9
  37. package/dist/lib/node/{client-5BHSU35T.cjs.map → client-BKTGRBYM.cjs.map} +3 -3
  38. package/dist/lib/node/index.cjs +70 -41
  39. package/dist/lib/node/index.cjs.map +2 -2
  40. package/dist/lib/node/{intent-resolver-PWCHHNV3.cjs → intent-resolver-Y73DGA7F.cjs} +81 -44
  41. package/dist/lib/node/intent-resolver-Y73DGA7F.cjs.map +7 -0
  42. package/dist/lib/node/meta.json +1 -1
  43. package/dist/lib/node/{migrations-YCBZUPWO.cjs → migrations-TGNI4FVL.cjs} +8 -8
  44. package/dist/lib/node/{react-context-MPHNGZHT.cjs → react-context-SF3M3YLR.cjs} +8 -8
  45. package/dist/lib/node/{react-surface-TPISDIHR.cjs → react-surface-KWTTP7ZX.cjs} +34 -26
  46. package/dist/lib/node/react-surface-KWTTP7ZX.cjs.map +7 -0
  47. package/dist/lib/node/{schema-NMMCMK64.cjs → schema-INQ72F54.cjs} +8 -8
  48. package/dist/lib/node/types.cjs +5 -3
  49. package/dist/lib/node/types.cjs.map +2 -2
  50. package/dist/lib/node-esm/app-graph-builder-KIN6NWFI.mjs +120 -0
  51. package/dist/lib/node-esm/app-graph-builder-KIN6NWFI.mjs.map +7 -0
  52. package/dist/lib/node-esm/chunk-6WHEPWQD.mjs +620 -0
  53. package/dist/lib/node-esm/chunk-6WHEPWQD.mjs.map +7 -0
  54. package/dist/lib/node-esm/{chunk-QJMF2MWP.mjs → chunk-AD5W5QCQ.mjs} +21 -4
  55. package/dist/lib/node-esm/{chunk-QJMF2MWP.mjs.map → chunk-AD5W5QCQ.mjs.map} +3 -3
  56. package/dist/lib/node-esm/{chunk-XLLNVLWU.mjs → chunk-BP56BGZR.mjs} +2 -2
  57. package/dist/lib/node-esm/{chunk-HO7WDE7C.mjs → chunk-HM7VB4ZV.mjs} +1 -1
  58. package/dist/lib/node-esm/{chunk-HO7WDE7C.mjs.map → chunk-HM7VB4ZV.mjs.map} +2 -2
  59. package/dist/lib/node-esm/{chunk-3OU2ZXLK.mjs → chunk-K3KGGSJZ.mjs} +3 -2
  60. package/dist/lib/node-esm/{chunk-3OU2ZXLK.mjs.map → chunk-K3KGGSJZ.mjs.map} +3 -3
  61. package/dist/lib/node-esm/{client-ZDGFGLX4.mjs → client-QQD6WFOB.mjs} +5 -5
  62. package/dist/lib/node-esm/{client-ZDGFGLX4.mjs.map → client-QQD6WFOB.mjs.map} +3 -3
  63. package/dist/lib/node-esm/index.mjs +51 -22
  64. package/dist/lib/node-esm/index.mjs.map +2 -2
  65. package/dist/lib/node-esm/{intent-resolver-FIXGPH7C.mjs → intent-resolver-K3CA4JRC.mjs} +65 -27
  66. package/dist/lib/node-esm/intent-resolver-K3CA4JRC.mjs.map +7 -0
  67. package/dist/lib/node-esm/meta.json +1 -1
  68. package/dist/lib/node-esm/{migrations-VIEIPYJW.mjs → migrations-IVGATGZR.mjs} +3 -3
  69. package/dist/lib/node-esm/{react-context-KUEOXWWK.mjs → react-context-EAKKTE5A.mjs} +3 -3
  70. package/dist/lib/node-esm/react-surface-ORRNXP2X.mjs +67 -0
  71. package/dist/lib/node-esm/react-surface-ORRNXP2X.mjs.map +7 -0
  72. package/dist/lib/node-esm/{schema-NE5IYLMD.mjs → schema-OK7HY3JJ.mjs} +3 -3
  73. package/dist/lib/node-esm/types.mjs +4 -2
  74. package/dist/types/src/capabilities/app-graph-builder.d.ts.map +1 -1
  75. package/dist/types/src/capabilities/capabilities.d.ts +1 -0
  76. package/dist/types/src/capabilities/capabilities.d.ts.map +1 -1
  77. package/dist/types/src/capabilities/index.d.ts +4 -4
  78. package/dist/types/src/capabilities/index.d.ts.map +1 -1
  79. package/dist/types/src/capabilities/intent-resolver.d.ts +2 -3
  80. package/dist/types/src/capabilities/intent-resolver.d.ts.map +1 -1
  81. package/dist/types/src/capabilities/react-surface.d.ts +3 -2
  82. package/dist/types/src/capabilities/react-surface.d.ts.map +1 -1
  83. package/dist/types/src/components/DevicesContainer.d.ts +5 -0
  84. package/dist/types/src/components/DevicesContainer.d.ts.map +1 -0
  85. package/dist/types/src/components/DevicesContainer.stories.d.ts +8 -0
  86. package/dist/types/src/components/DevicesContainer.stories.d.ts.map +1 -0
  87. package/dist/types/src/components/JoinDialog.d.ts +2 -1
  88. package/dist/types/src/components/JoinDialog.d.ts.map +1 -1
  89. package/dist/types/src/components/ProfileContainer.d.ts +3 -0
  90. package/dist/types/src/components/ProfileContainer.d.ts.map +1 -0
  91. package/dist/types/src/components/ProfileContainer.stories.d.ts +8 -0
  92. package/dist/types/src/components/ProfileContainer.stories.d.ts.map +1 -0
  93. package/dist/types/src/components/RecoveryCodeDialog.d.ts +3 -2
  94. package/dist/types/src/components/RecoveryCodeDialog.d.ts.map +1 -1
  95. package/dist/types/src/components/RecoveryCredentialsContainer.d.ts +4 -0
  96. package/dist/types/src/components/RecoveryCredentialsContainer.d.ts.map +1 -0
  97. package/dist/types/src/components/RecoveryCredentialsContainer.stories.d.ts +8 -0
  98. package/dist/types/src/components/RecoveryCredentialsContainer.stories.d.ts.map +1 -0
  99. package/dist/types/src/components/ResetDialog.d.ts +7 -0
  100. package/dist/types/src/components/ResetDialog.d.ts.map +1 -0
  101. package/dist/types/src/components/ResetDialog.stories.d.ts +10 -0
  102. package/dist/types/src/components/ResetDialog.stories.d.ts.map +1 -0
  103. package/dist/types/src/components/index.d.ts +4 -2
  104. package/dist/types/src/components/index.d.ts.map +1 -1
  105. package/dist/types/src/meta.d.ts +2 -4
  106. package/dist/types/src/meta.d.ts.map +1 -1
  107. package/dist/types/src/translations.d.ts +28 -1
  108. package/dist/types/src/translations.d.ts.map +1 -1
  109. package/dist/types/src/types.d.ts +17 -1
  110. package/dist/types/src/types.d.ts.map +1 -1
  111. package/package.json +26 -19
  112. package/src/ClientPlugin.ts +2 -2
  113. package/src/capabilities/app-graph-builder.ts +62 -6
  114. package/src/capabilities/capabilities.ts +1 -0
  115. package/src/capabilities/client.ts +1 -1
  116. package/src/capabilities/intent-resolver.ts +68 -23
  117. package/src/capabilities/react-surface.tsx +32 -24
  118. package/src/components/DevicesContainer.stories.tsx +45 -0
  119. package/src/components/DevicesContainer.tsx +268 -0
  120. package/src/components/ProfileContainer.stories.tsx +29 -0
  121. package/src/components/ProfileContainer.tsx +163 -0
  122. package/src/components/RecoveryCodeDialog.stories.tsx +2 -2
  123. package/src/components/RecoveryCodeDialog.tsx +1 -2
  124. package/src/components/{ManageCredentialsDialog.stories.tsx → RecoveryCredentialsContainer.stories.tsx} +4 -17
  125. package/src/components/RecoveryCredentialsContainer.tsx +74 -0
  126. package/src/components/ResetDialog.stories.tsx +47 -0
  127. package/src/components/ResetDialog.tsx +49 -0
  128. package/src/components/index.ts +4 -2
  129. package/src/meta.ts +2 -2
  130. package/src/translations.ts +45 -9
  131. package/src/types.ts +20 -1
  132. package/dist/lib/browser/app-graph-builder-ON64R7SO.mjs +0 -44
  133. package/dist/lib/browser/app-graph-builder-ON64R7SO.mjs.map +0 -7
  134. package/dist/lib/browser/chunk-HNJZ74DZ.mjs +0 -281
  135. package/dist/lib/browser/chunk-HNJZ74DZ.mjs.map +0 -7
  136. package/dist/lib/browser/intent-resolver-AIUHL34E.mjs.map +0 -7
  137. package/dist/lib/browser/react-surface-QGPWIUFR.mjs +0 -57
  138. package/dist/lib/browser/react-surface-QGPWIUFR.mjs.map +0 -7
  139. package/dist/lib/node/app-graph-builder-RW7ESEX3.cjs +0 -58
  140. package/dist/lib/node/app-graph-builder-RW7ESEX3.cjs.map +0 -7
  141. package/dist/lib/node/chunk-TREVKZCJ.cjs.map +0 -7
  142. package/dist/lib/node/chunk-UTG3UR3N.cjs +0 -309
  143. package/dist/lib/node/chunk-UTG3UR3N.cjs.map +0 -7
  144. package/dist/lib/node/intent-resolver-PWCHHNV3.cjs.map +0 -7
  145. package/dist/lib/node/react-surface-TPISDIHR.cjs.map +0 -7
  146. package/dist/lib/node-esm/app-graph-builder-TDYRWNI7.mjs +0 -45
  147. package/dist/lib/node-esm/app-graph-builder-TDYRWNI7.mjs.map +0 -7
  148. package/dist/lib/node-esm/chunk-SHJBU4QR.mjs +0 -282
  149. package/dist/lib/node-esm/chunk-SHJBU4QR.mjs.map +0 -7
  150. package/dist/lib/node-esm/intent-resolver-FIXGPH7C.mjs.map +0 -7
  151. package/dist/lib/node-esm/react-surface-KBROL4VH.mjs +0 -58
  152. package/dist/lib/node-esm/react-surface-KBROL4VH.mjs.map +0 -7
  153. package/dist/types/src/components/IdentityDialog.d.ts +0 -4
  154. package/dist/types/src/components/IdentityDialog.d.ts.map +0 -1
  155. package/dist/types/src/components/ManageCredentialsDialog.d.ts +0 -3
  156. package/dist/types/src/components/ManageCredentialsDialog.d.ts.map +0 -1
  157. package/dist/types/src/components/ManageCredentialsDialog.stories.d.ts +0 -8
  158. package/dist/types/src/components/ManageCredentialsDialog.stories.d.ts.map +0 -1
  159. package/dist/types/src/components/RecoverySetupDialog.d.ts +0 -3
  160. package/dist/types/src/components/RecoverySetupDialog.d.ts.map +0 -1
  161. package/dist/types/src/components/RecoverySetupDialog.stories.d.ts +0 -8
  162. package/dist/types/src/components/RecoverySetupDialog.stories.d.ts.map +0 -1
  163. package/src/components/IdentityDialog.tsx +0 -75
  164. package/src/components/ManageCredentialsDialog.tsx +0 -64
  165. package/src/components/RecoverySetupDialog.stories.tsx +0 -77
  166. package/src/components/RecoverySetupDialog.tsx +0 -52
  167. /package/dist/lib/browser/{chunk-OLUST5AO.mjs.map → chunk-4PLF3BEB.mjs.map} +0 -0
  168. /package/dist/lib/browser/{migrations-XODC625X.mjs.map → migrations-3DWFOL5Q.mjs.map} +0 -0
  169. /package/dist/lib/browser/{react-context-JBD7Y3SN.mjs.map → react-context-WEH7SL4I.mjs.map} +0 -0
  170. /package/dist/lib/browser/{schema-Y7ZNXYSL.mjs.map → schema-JQAT6Q7S.mjs.map} +0 -0
  171. /package/dist/lib/node/{chunk-FR2K56EV.cjs.map → chunk-W64EOF2B.cjs.map} +0 -0
  172. /package/dist/lib/node/{migrations-YCBZUPWO.cjs.map → migrations-TGNI4FVL.cjs.map} +0 -0
  173. /package/dist/lib/node/{react-context-MPHNGZHT.cjs.map → react-context-SF3M3YLR.cjs.map} +0 -0
  174. /package/dist/lib/node/{schema-NMMCMK64.cjs.map → schema-INQ72F54.cjs.map} +0 -0
  175. /package/dist/lib/node-esm/{chunk-XLLNVLWU.mjs.map → chunk-BP56BGZR.mjs.map} +0 -0
  176. /package/dist/lib/node-esm/{migrations-VIEIPYJW.mjs.map → migrations-IVGATGZR.mjs.map} +0 -0
  177. /package/dist/lib/node-esm/{react-context-KUEOXWWK.mjs.map → react-context-EAKKTE5A.mjs.map} +0 -0
  178. /package/dist/lib/node-esm/{schema-NE5IYLMD.mjs.map → schema-OK7HY3JJ.mjs.map} +0 -0
@@ -2,8 +2,11 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
+ import { pipe } from 'effect';
6
+
5
7
  import {
6
8
  Capabilities,
9
+ chain,
7
10
  contributes,
8
11
  createIntent,
9
12
  createResolver,
@@ -16,16 +19,18 @@ import { PublicKey } from '@dxos/react-client';
16
19
  import { type JoinPanelProps } from '@dxos/shell/react';
17
20
 
18
21
  import { ClientCapabilities } from './capabilities';
19
- import { IDENTITY_DIALOG, JOIN_DIALOG, RECOVER_CODE_DIALOG } from '../components';
22
+ import { JOIN_DIALOG, RECOVERY_CODE_DIALOG, RESET_DIALOG } from '../components';
20
23
  import { ClientEvents } from '../events';
21
- import { ClientAction, type ClientPluginOptions } from '../types';
24
+ import { Account, ClientAction } from '../types';
22
25
 
23
- type IntentResolverOptions = Pick<ClientPluginOptions, 'onReset'> & {
26
+ type IntentResolverOptions = {
24
27
  context: PluginsContext;
25
28
  appName?: string;
26
29
  };
27
30
 
28
- export default ({ context, appName = 'Composer', onReset }: IntentResolverOptions) =>
31
+ const RECOVER_IDENTITY_RPC_TIMEOUT = 20_000;
32
+
33
+ export default ({ context, appName = 'Composer' }: IntentResolverOptions) =>
29
34
  contributes(Capabilities.IntentResolver, [
30
35
  createResolver({
31
36
  intent: ClientAction.CreateIdentity,
@@ -34,7 +39,14 @@ export default ({ context, appName = 'Composer', onReset }: IntentResolverOption
34
39
  const client = context.requestCapability(ClientCapabilities.Client);
35
40
  const data = await client.halo.createIdentity();
36
41
  await manager.activate(ClientEvents.IdentityCreated);
37
- return { data, intents: [createIntent(ObservabilityAction.SendEvent, { name: 'identity.create' })] };
42
+ return {
43
+ data,
44
+ intents: [
45
+ createIntent(ObservabilityAction.SendEvent, {
46
+ name: 'identity.create',
47
+ }),
48
+ ],
49
+ };
38
50
  },
39
51
  }),
40
52
  createResolver({
@@ -62,14 +74,19 @@ export default ({ context, appName = 'Composer', onReset }: IntentResolverOption
62
74
  resolve: async () => {
63
75
  return {
64
76
  intents: [
65
- createIntent(LayoutAction.UpdateDialog, {
66
- part: 'dialog',
67
- subject: IDENTITY_DIALOG,
68
- options: {
69
- blockAlign: 'start',
70
- },
77
+ pipe(
78
+ createIntent(LayoutAction.SwitchWorkspace, {
79
+ part: 'workspace',
80
+ subject: Account.id,
81
+ }),
82
+ chain(LayoutAction.Open, {
83
+ part: 'main',
84
+ subject: [Account.Profile],
85
+ }),
86
+ ),
87
+ createIntent(ObservabilityAction.SendEvent, {
88
+ name: 'identity.share',
71
89
  }),
72
- createIntent(ObservabilityAction.SendEvent, { name: 'identity.share' }),
73
90
  ],
74
91
  };
75
92
  },
@@ -96,7 +113,20 @@ export default ({ context, appName = 'Composer', onReset }: IntentResolverOption
96
113
  createResolver({
97
114
  intent: ClientAction.ResetStorage,
98
115
  resolve: async (data) => {
99
- await onReset?.({ target: data.target });
116
+ return {
117
+ intents: [
118
+ createIntent(LayoutAction.UpdateDialog, {
119
+ part: 'dialog',
120
+ subject: RESET_DIALOG,
121
+ options: {
122
+ blockAlign: 'start',
123
+ props: {
124
+ mode: data.mode ?? 'reset storage',
125
+ },
126
+ },
127
+ }),
128
+ ],
129
+ };
100
130
  },
101
131
  }),
102
132
  createResolver({
@@ -118,7 +148,7 @@ export default ({ context, appName = 'Composer', onReset }: IntentResolverOption
118
148
  intents: [
119
149
  createIntent(LayoutAction.UpdateDialog, {
120
150
  part: 'dialog',
121
- subject: RECOVER_CODE_DIALOG,
151
+ subject: RECOVERY_CODE_DIALOG,
122
152
  options: {
123
153
  blockAlign: 'start',
124
154
  type: 'alert',
@@ -190,16 +220,31 @@ export default ({ context, appName = 'Composer', onReset }: IntentResolverOption
190
220
  },
191
221
  });
192
222
  const lookupKey = PublicKey.from(new Uint8Array((credential as any).response.userHandle));
193
- await client.services.services.IdentityService.recoverIdentity({
194
- external: {
195
- lookupKey,
196
- deviceKey,
197
- controlFeedKey,
198
- signature: Buffer.from((credential as any).response.signature),
199
- clientDataJson: Buffer.from((credential as any).response.clientDataJSON),
200
- authenticatorData: Buffer.from((credential as any).response.authenticatorData),
223
+ await client.services.services.IdentityService.recoverIdentity(
224
+ {
225
+ external: {
226
+ lookupKey,
227
+ deviceKey,
228
+ controlFeedKey,
229
+ signature: Buffer.from((credential as any).response.signature),
230
+ clientDataJson: Buffer.from((credential as any).response.clientDataJSON),
231
+ authenticatorData: Buffer.from((credential as any).response.authenticatorData),
232
+ },
201
233
  },
202
- });
234
+ { timeout: RECOVER_IDENTITY_RPC_TIMEOUT },
235
+ );
236
+ },
237
+ }),
238
+ createResolver({
239
+ intent: ClientAction.RedeemToken,
240
+ resolve: async (data) => {
241
+ const client = context.requestCapability(ClientCapabilities.Client);
242
+ // TODO(wittjosiah): This needs a proper api.
243
+ invariant(client.services.services.IdentityService, 'IdentityService not available');
244
+ await client.services.services.IdentityService.recoverIdentity(
245
+ { token: data.token },
246
+ { timeout: RECOVER_IDENTITY_RPC_TIMEOUT },
247
+ );
203
248
  },
204
249
  }),
205
250
  ]);
@@ -5,32 +5,46 @@
5
5
  import React from 'react';
6
6
 
7
7
  import { createSurface, Capabilities, contributes } from '@dxos/app-framework';
8
- import { type IdentityPanelProps, type JoinPanelProps } from '@dxos/shell/react';
8
+ import { type JoinPanelProps } from '@dxos/shell/react';
9
9
 
10
10
  import {
11
- IDENTITY_DIALOG,
12
- IdentityDialog,
11
+ DevicesContainer,
13
12
  JOIN_DIALOG,
14
13
  JoinDialog,
15
- RECOVER_CODE_DIALOG,
16
- RECOVER_SETUP_DIALOG,
14
+ ProfileContainer,
15
+ RECOVERY_CODE_DIALOG,
17
16
  RecoveryCodeDialog,
18
- RecoverySetupDialog,
17
+ RecoveryCredentialsContainer,
18
+ RESET_DIALOG,
19
+ ResetDialog,
20
+ type ResetDialogProps,
19
21
  type RecoveryCodeDialogProps,
20
22
  } from '../components';
21
- import { MANAGE_CREDENTIALS_DIALOG, ManageCredentialsDialog } from '../components/ManageCredentialsDialog';
23
+ import { Account, type ClientPluginOptions } from '../types';
22
24
 
23
- type ReactSurfaceOptions = {
25
+ type ReactSurfaceOptions = Pick<ClientPluginOptions, 'onReset'> & {
24
26
  createInvitationUrl: (invitationCode: string) => string;
25
27
  };
26
28
 
27
- export default ({ createInvitationUrl }: ReactSurfaceOptions) =>
29
+ export default ({ createInvitationUrl, onReset }: ReactSurfaceOptions) =>
28
30
  contributes(Capabilities.ReactSurface, [
29
31
  createSurface({
30
- id: IDENTITY_DIALOG,
31
- role: 'dialog',
32
- filter: (data): data is { props: IdentityPanelProps } => data.component === IDENTITY_DIALOG,
33
- component: ({ data }) => <IdentityDialog {...data.props} createInvitationUrl={createInvitationUrl} />,
32
+ id: Account.Profile,
33
+ role: 'article',
34
+ filter: (data): data is any => data.subject === Account.Profile,
35
+ component: () => <ProfileContainer />,
36
+ }),
37
+ createSurface({
38
+ id: Account.Devices,
39
+ role: 'article',
40
+ filter: (data): data is any => data.subject === Account.Devices,
41
+ component: ({ data }) => <DevicesContainer createInvitationUrl={createInvitationUrl} />,
42
+ }),
43
+ createSurface({
44
+ id: Account.Security,
45
+ role: 'article',
46
+ filter: (data): data is any => data.subject === Account.Security,
47
+ component: () => <RecoveryCredentialsContainer />,
34
48
  }),
35
49
  createSurface({
36
50
  id: JOIN_DIALOG,
@@ -39,21 +53,15 @@ export default ({ createInvitationUrl }: ReactSurfaceOptions) =>
39
53
  component: ({ data }) => <JoinDialog {...data.props} />,
40
54
  }),
41
55
  createSurface({
42
- id: RECOVER_CODE_DIALOG,
56
+ id: RECOVERY_CODE_DIALOG,
43
57
  role: 'dialog',
44
- filter: (data): data is { props: RecoveryCodeDialogProps } => data.component === RECOVER_CODE_DIALOG,
58
+ filter: (data): data is { props: RecoveryCodeDialogProps } => data.component === RECOVERY_CODE_DIALOG,
45
59
  component: ({ data }) => <RecoveryCodeDialog {...data.props} />,
46
60
  }),
47
61
  createSurface({
48
- id: RECOVER_SETUP_DIALOG,
49
- role: 'dialog',
50
- filter: (data): data is any => data.component === RECOVER_SETUP_DIALOG,
51
- component: () => <RecoverySetupDialog />,
52
- }),
53
- createSurface({
54
- id: MANAGE_CREDENTIALS_DIALOG,
62
+ id: RESET_DIALOG,
55
63
  role: 'dialog',
56
- filter: (data): data is any => data.component === MANAGE_CREDENTIALS_DIALOG,
57
- component: () => <ManageCredentialsDialog />,
64
+ filter: (data): data is { props: ResetDialogProps } => data.component === RESET_DIALOG,
65
+ component: ({ data }) => <ResetDialog {...data.props} onReset={onReset} />,
58
66
  }),
59
67
  ]);
@@ -0,0 +1,45 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ import '@dxos-theme';
6
+
7
+ import { type StoryObj, type Meta } from '@storybook/react';
8
+
9
+ import { IntentPlugin } from '@dxos/app-framework';
10
+ import { withPluginManager } from '@dxos/app-framework/testing';
11
+ import { osTranslations } from '@dxos/shell/react';
12
+ import { withTheme, withLayout } from '@dxos/storybook-utils';
13
+
14
+ import { DevicesContainer } from './DevicesContainer';
15
+ import { ClientPlugin } from '../ClientPlugin';
16
+ import translations from '../translations';
17
+
18
+ const meta: Meta = {
19
+ title: 'plugins/plugin-client/DevicesContainer',
20
+ component: DevicesContainer,
21
+ decorators: [
22
+ withPluginManager({
23
+ plugins: [
24
+ IntentPlugin(),
25
+ ClientPlugin({
26
+ onClientInitialized: async (_, client) => {
27
+ await client.halo.createIdentity();
28
+ },
29
+ }),
30
+ ],
31
+ }),
32
+ withTheme,
33
+ withLayout({ tooltips: true }),
34
+ ],
35
+ parameters: {
36
+ layout: 'fullscreen',
37
+ translations: [...translations, osTranslations],
38
+ },
39
+ };
40
+
41
+ export default meta;
42
+
43
+ type Story = StoryObj<typeof DevicesContainer>;
44
+
45
+ export const Default: Story = {};
@@ -0,0 +1,268 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { Check, X } from '@phosphor-icons/react';
6
+ import React, { useCallback, useEffect, useState } from 'react';
7
+ import { QR } from 'react-qr-rounded';
8
+
9
+ import { createIntent, useIntentDispatcher } from '@dxos/app-framework';
10
+ import { log } from '@dxos/log';
11
+ import { useClient, useMulticastObservable } from '@dxos/react-client';
12
+ import { type Device, useDevices } from '@dxos/react-client/halo';
13
+ import { type CancellableInvitationObservable, Invitation, InvitationEncoder } from '@dxos/react-client/invitations';
14
+ import { useNetworkStatus } from '@dxos/react-client/mesh';
15
+ import { Button, Clipboard, IconButton, List, useId, useTranslation } from '@dxos/react-ui';
16
+ import { ControlFrame, ControlFrameItem, ControlGroup, ControlItem, ControlSection } from '@dxos/react-ui-form';
17
+ import { StackItem } from '@dxos/react-ui-stack';
18
+ import { getSize, mx } from '@dxos/react-ui-theme';
19
+ import { AuthCode, Centered, DeviceListItem, Emoji, Viewport } from '@dxos/shell/react';
20
+ import { hexToEmoji } from '@dxos/util';
21
+
22
+ import { CLIENT_PLUGIN } from '../meta';
23
+ import { ClientAction } from '../types';
24
+
25
+ export const DevicesContainer = ({
26
+ createInvitationUrl,
27
+ }: {
28
+ createInvitationUrl: (invitationCode: string) => string;
29
+ }) => {
30
+ const { t } = useTranslation('os');
31
+ const { dispatchPromise: dispatch } = useIntentDispatcher();
32
+ const devices = useDevices();
33
+ const { swarm: connectionState } = useNetworkStatus();
34
+
35
+ const handleResetStorage = useCallback(() => dispatch(createIntent(ClientAction.ResetStorage)), [dispatch]);
36
+
37
+ const handleRecover = useCallback(
38
+ () => dispatch(createIntent(ClientAction.ResetStorage, { mode: 'recover' })),
39
+ [dispatch],
40
+ );
41
+
42
+ const handleJoinNewIdentity = useCallback(
43
+ () => dispatch(createIntent(ClientAction.ResetStorage, { mode: 'join new identity' })),
44
+ [dispatch],
45
+ );
46
+
47
+ return (
48
+ <StackItem.Content classNames='p-2 block overflow-y-auto'>
49
+ <ControlSection
50
+ title={t('devices verbose label', { ns: CLIENT_PLUGIN })}
51
+ description={t('devices description', { ns: CLIENT_PLUGIN })}
52
+ >
53
+ <ControlFrame>
54
+ <ControlFrameItem title={t('devices label', { ns: CLIENT_PLUGIN })}>
55
+ <List>
56
+ {devices.map((device: Device) => {
57
+ return (
58
+ <DeviceListItem key={device.deviceKey.toHex()} device={device} connectionState={connectionState} />
59
+ );
60
+ })}
61
+ </List>
62
+ </ControlFrameItem>
63
+ <ControlFrameItem title='Add device'>
64
+ <DeviceInvitation createInvitationUrl={createInvitationUrl} />
65
+ </ControlFrameItem>
66
+ </ControlFrame>
67
+ </ControlSection>
68
+ <ControlSection
69
+ title={t('danger zone title', { ns: CLIENT_PLUGIN })}
70
+ description={t('danger zone description', { ns: CLIENT_PLUGIN })}
71
+ >
72
+ <ControlGroup>
73
+ <ControlItem
74
+ title={t('reset device label')}
75
+ description={t('reset device description', { ns: CLIENT_PLUGIN })}
76
+ >
77
+ <Button variant='destructive' onClick={handleResetStorage} data-testid='devicesContainer.reset'>
78
+ {t('reset device label')}
79
+ </Button>
80
+ </ControlItem>
81
+ <ControlItem
82
+ title={t('recover identity label')}
83
+ description={t('recover identity description', { ns: CLIENT_PLUGIN })}
84
+ >
85
+ <Button variant='destructive' onClick={handleRecover} data-testid='devicesContainer.recover'>
86
+ {t('recover identity label')}
87
+ </Button>
88
+ </ControlItem>
89
+ <ControlItem
90
+ title={t('join new identity label')}
91
+ description={t('join new identity description', { ns: CLIENT_PLUGIN })}
92
+ >
93
+ <Button variant='destructive' onClick={handleJoinNewIdentity} data-testid='devicesContainer.joinExisting'>
94
+ {t('join new identity label')}
95
+ </Button>
96
+ </ControlItem>
97
+ </ControlGroup>
98
+ </ControlSection>
99
+ </StackItem.Content>
100
+ );
101
+ };
102
+
103
+ type DeviceInvitationProps = {
104
+ invitation?: CancellableInvitationObservable;
105
+ createInvitationUrl: (invitationCode: string) => string;
106
+ onInvitationDone: () => void;
107
+ onInvitationCreate: () => void;
108
+ };
109
+
110
+ const DeviceInvitation = (props: Pick<DeviceInvitationProps, 'createInvitationUrl'>) => {
111
+ const client = useClient();
112
+ const [invitation, setInvitation] = useState<CancellableInvitationObservable>();
113
+
114
+ const onInvitationCreate = useCallback(() => {
115
+ const invitation = client.halo.share();
116
+ if (client.config.values.runtime?.app?.env?.DX_ENVIRONMENT !== 'production') {
117
+ const subscription = invitation.subscribe((invitation: Invitation) => {
118
+ const invitationCode = InvitationEncoder.encode(invitation);
119
+ if (invitation.state === Invitation.State.CONNECTING) {
120
+ log.info(JSON.stringify({ invitationCode, authCode: invitation.authCode }));
121
+ subscription.unsubscribe();
122
+ }
123
+ });
124
+ }
125
+ setInvitation(invitation);
126
+ }, [client]);
127
+
128
+ const onInvitationDone = useCallback(() => {
129
+ setInvitation(undefined);
130
+ }, []);
131
+
132
+ if (invitation) {
133
+ return <DeviceInvitationImpl {...props} {...{ invitation, onInvitationCreate, onInvitationDone }} />;
134
+ } else {
135
+ return <InvitationSection {...props} {...{ onInvitationCreate, onInvitationDone }} />;
136
+ }
137
+ };
138
+
139
+ const DeviceInvitationImpl = ({
140
+ invitation: invitationObservable,
141
+ createInvitationUrl,
142
+ onInvitationDone,
143
+ onInvitationCreate,
144
+ }: DeviceInvitationProps) => {
145
+ const invitation = useMulticastObservable(invitationObservable!);
146
+ const url = createInvitationUrl(InvitationEncoder.encode(invitation));
147
+
148
+ useEffect(() => {
149
+ if (invitation.state >= Invitation.State.SUCCESS) {
150
+ onInvitationDone();
151
+ }
152
+ }, [invitation.state]);
153
+
154
+ return <InvitationSection {...invitation} {...{ url, onInvitationDone, onInvitationCreate }} />;
155
+ };
156
+
157
+ type InvitationComponentProps = Partial<
158
+ Pick<Invitation, 'authCode' | 'invitationId'> &
159
+ Pick<DeviceInvitationProps, 'onInvitationDone' | 'onInvitationCreate'> & {
160
+ state: number;
161
+ url: string;
162
+ }
163
+ >;
164
+
165
+ const InvitationSection = ({
166
+ state = -1,
167
+ authCode,
168
+ invitationId = 'never',
169
+ url = 'never',
170
+ onInvitationDone = () => {},
171
+ onInvitationCreate = () => {},
172
+ }: InvitationComponentProps) => {
173
+ const { t } = useTranslation(CLIENT_PLUGIN);
174
+ const activeView =
175
+ state < 0
176
+ ? 'init'
177
+ : state >= Invitation.State.CANCELLED
178
+ ? 'complete'
179
+ : state >= Invitation.State.READY_FOR_AUTHENTICATION && authCode
180
+ ? 'auth-code'
181
+ : 'qr-code';
182
+ return activeView === 'init' ? (
183
+ <>
184
+ <p className='text-description mbe-2'>{t('add device description')}</p>
185
+ <IconButton
186
+ icon='ph--plus--regular'
187
+ label={t('create device invitation label')}
188
+ disabled={state >= 0}
189
+ classNames='is-full'
190
+ data-testid='devicesContainer.createInvitation'
191
+ onClick={onInvitationCreate}
192
+ />
193
+ </>
194
+ ) : (
195
+ <Viewport.Root activeView={activeView}>
196
+ <Viewport.Views>
197
+ <Viewport.View id='init'>
198
+ {/* This view intentionally left blank while conditionally rendering the viewport. */}
199
+ </Viewport.View>
200
+ <Viewport.View id='complete'>
201
+ <InvitationComplete statusValue={state} />
202
+ </Viewport.View>
203
+ <Viewport.View id='auth-code'>
204
+ <InvitationAuthCode id={invitationId} code={authCode ?? 'never'} onCancel={onInvitationDone} />
205
+ </Viewport.View>
206
+ <Viewport.View id='qr-code'>
207
+ <InvitationQR id={invitationId} url={url} onCancel={onInvitationDone} />
208
+ </Viewport.View>
209
+ </Viewport.Views>
210
+ </Viewport.Root>
211
+ );
212
+ };
213
+
214
+ const InvitationQR = ({ id, url, onCancel }: { id: string; url: string; onCancel: () => void }) => {
215
+ const { t } = useTranslation('os');
216
+ const qrLabel = useId('devices-container__qr-code');
217
+ const emoji = hexToEmoji(id);
218
+ return (
219
+ <Clipboard.Provider>
220
+ <p className='text-description'>{t('qr code description', { ns: CLIENT_PLUGIN })}</p>
221
+ <div role='group' className='grid grid-cols-[1fr_min-content] mlb-2 gap-2'>
222
+ <div role='none' className='is-full aspect-square relative text-description'>
223
+ <QR
224
+ rounding={100}
225
+ backgroundColor='transparent'
226
+ color='currentColor'
227
+ aria-labelledby={qrLabel}
228
+ errorCorrectionLevel='Q'
229
+ cutout={true}
230
+ >
231
+ {url ?? 'never'}
232
+ </QR>
233
+ <Centered>
234
+ <Emoji text={emoji} />
235
+ </Centered>
236
+ </div>
237
+ <span id={qrLabel} className='sr-only'>
238
+ {t('qr label')}
239
+ </span>
240
+ <Clipboard.Button value={url ?? 'never'} />
241
+ </div>
242
+ <Button variant='ghost' onClick={onCancel}>
243
+ {t('cancel label')}
244
+ </Button>
245
+ </Clipboard.Provider>
246
+ );
247
+ };
248
+
249
+ const InvitationAuthCode = ({ id, code, onCancel }: { id: string; code: string; onCancel: () => void }) => {
250
+ const { t } = useTranslation('os');
251
+ const emoji = hexToEmoji(id);
252
+
253
+ return (
254
+ <>
255
+ <p className='text-description'>{t('auth other device emoji message')}</p>
256
+ {emoji && <Emoji text={emoji} className='mli-auto mlb-2 text-center' />}
257
+ <p className='text-description'>{t('auth code message')}</p>
258
+ <AuthCode code={code} large classNames='mli-auto mlb-2 text-center grow' />
259
+ <Button variant='ghost' onClick={onCancel}>
260
+ {t('cancel label')}
261
+ </Button>
262
+ </>
263
+ );
264
+ };
265
+
266
+ const InvitationComplete = ({ statusValue }: { statusValue: number }) => {
267
+ return statusValue > 0 ? <Check className={mx('m-1.5', getSize(6))} /> : <X className={mx('m-1.5', getSize(6))} />;
268
+ };
@@ -0,0 +1,29 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ import '@dxos-theme';
6
+
7
+ import { type StoryObj, type Meta } from '@storybook/react';
8
+
9
+ import { withClientProvider } from '@dxos/react-client/testing';
10
+ import { withTheme, withLayout } from '@dxos/storybook-utils';
11
+
12
+ import { ProfileContainer } from './ProfileContainer';
13
+ import translations from '../translations';
14
+
15
+ const meta: Meta = {
16
+ title: 'plugins/plugin-client/ProfileContainer',
17
+ component: ProfileContainer,
18
+ decorators: [withClientProvider({ createIdentity: true }), withTheme, withLayout({ tooltips: true })],
19
+ parameters: {
20
+ layout: 'fullscreen',
21
+ translations,
22
+ },
23
+ };
24
+
25
+ export default meta;
26
+
27
+ type Story = StoryObj<typeof ProfileContainer>;
28
+
29
+ export const Default: Story = {};