@dxos/plugin-client 0.7.4-staging.f7e8224 → 0.7.5-feature-compute.4d9d99a

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 (138) hide show
  1. package/dist/lib/browser/app-graph-builder-6LOTDB4T.mjs +44 -0
  2. package/dist/lib/browser/app-graph-builder-6LOTDB4T.mjs.map +7 -0
  3. package/dist/lib/browser/chunk-6IF6PREG.mjs +158 -0
  4. package/dist/lib/browser/chunk-6IF6PREG.mjs.map +7 -0
  5. package/dist/lib/browser/chunk-E3OUNOEL.mjs +76 -0
  6. package/dist/lib/browser/chunk-E3OUNOEL.mjs.map +7 -0
  7. package/dist/lib/browser/chunk-NHIL4T3L.mjs +12 -0
  8. package/dist/lib/browser/chunk-NHIL4T3L.mjs.map +7 -0
  9. package/dist/lib/browser/chunk-R4Q6H7RD.mjs +18 -0
  10. package/dist/lib/browser/chunk-R4Q6H7RD.mjs.map +7 -0
  11. package/dist/lib/browser/chunk-SVBQRT7I.mjs +17 -0
  12. package/dist/lib/browser/chunk-SVBQRT7I.mjs.map +7 -0
  13. package/dist/lib/browser/client-25GYH6TN.mjs +45 -0
  14. package/dist/lib/browser/client-25GYH6TN.mjs.map +7 -0
  15. package/dist/lib/browser/index.mjs +74 -423
  16. package/dist/lib/browser/index.mjs.map +4 -4
  17. package/dist/lib/browser/intent-resolver-IWWLWOZD.mjs +132 -0
  18. package/dist/lib/browser/intent-resolver-IWWLWOZD.mjs.map +7 -0
  19. package/dist/lib/browser/meta.json +1 -1
  20. package/dist/lib/browser/react-context-M3RT6SSG.mjs +24 -0
  21. package/dist/lib/browser/react-context-M3RT6SSG.mjs.map +7 -0
  22. package/dist/lib/browser/react-surface-XXONPG5T.mjs +41 -0
  23. package/dist/lib/browser/react-surface-XXONPG5T.mjs.map +7 -0
  24. package/dist/lib/browser/types.mjs +8 -0
  25. package/dist/lib/node/app-graph-builder-JCND3P6G.cjs +58 -0
  26. package/dist/lib/node/app-graph-builder-JCND3P6G.cjs.map +7 -0
  27. package/dist/lib/node/chunk-3OB25QN2.cjs +36 -0
  28. package/dist/lib/node/chunk-3OB25QN2.cjs.map +7 -0
  29. package/dist/lib/node/chunk-IXVWJCYB.cjs +37 -0
  30. package/dist/lib/node/chunk-IXVWJCYB.cjs.map +7 -0
  31. package/dist/lib/node/chunk-N3U7KVO3.cjs +186 -0
  32. package/dist/lib/node/chunk-N3U7KVO3.cjs.map +7 -0
  33. package/dist/lib/node/chunk-NVYWMDF3.cjs +95 -0
  34. package/dist/lib/node/chunk-NVYWMDF3.cjs.map +7 -0
  35. package/dist/lib/node/{meta.cjs → chunk-OIFPM5ZW.cjs} +12 -11
  36. package/dist/lib/node/chunk-OIFPM5ZW.cjs.map +7 -0
  37. package/dist/lib/node/client-52L4TUYU.cjs +59 -0
  38. package/dist/lib/node/client-52L4TUYU.cjs.map +7 -0
  39. package/dist/lib/node/index.cjs +69 -411
  40. package/dist/lib/node/index.cjs.map +4 -4
  41. package/dist/lib/node/intent-resolver-H3Y64MIP.cjs +140 -0
  42. package/dist/lib/node/intent-resolver-H3Y64MIP.cjs.map +7 -0
  43. package/dist/lib/node/meta.json +1 -1
  44. package/dist/lib/node/react-context-B2DNULH4.cjs +48 -0
  45. package/dist/lib/node/react-context-B2DNULH4.cjs.map +7 -0
  46. package/dist/lib/node/react-surface-7USN5NOO.cjs +62 -0
  47. package/dist/lib/node/react-surface-7USN5NOO.cjs.map +7 -0
  48. package/dist/lib/node/types.cjs +30 -0
  49. package/dist/lib/node/types.cjs.map +7 -0
  50. package/dist/lib/node-esm/app-graph-builder-ZLGKFBLY.mjs +45 -0
  51. package/dist/lib/node-esm/app-graph-builder-ZLGKFBLY.mjs.map +7 -0
  52. package/dist/lib/node-esm/chunk-73M5MK5Q.mjs +19 -0
  53. package/dist/lib/node-esm/chunk-73M5MK5Q.mjs.map +7 -0
  54. package/dist/lib/node-esm/chunk-74T33KTB.mjs +77 -0
  55. package/dist/lib/node-esm/chunk-74T33KTB.mjs.map +7 -0
  56. package/dist/lib/node-esm/chunk-EHGD3UJH.mjs +159 -0
  57. package/dist/lib/node-esm/chunk-EHGD3UJH.mjs.map +7 -0
  58. package/dist/lib/node-esm/chunk-HO7WDE7C.mjs +14 -0
  59. package/dist/lib/node-esm/chunk-HO7WDE7C.mjs.map +7 -0
  60. package/dist/lib/node-esm/chunk-OBGRL3DF.mjs +18 -0
  61. package/dist/lib/node-esm/chunk-OBGRL3DF.mjs.map +7 -0
  62. package/dist/lib/node-esm/client-K52NWOUN.mjs +46 -0
  63. package/dist/lib/node-esm/client-K52NWOUN.mjs.map +7 -0
  64. package/dist/lib/node-esm/index.mjs +74 -423
  65. package/dist/lib/node-esm/index.mjs.map +4 -4
  66. package/dist/lib/node-esm/intent-resolver-JCKD6R6L.mjs +133 -0
  67. package/dist/lib/node-esm/intent-resolver-JCKD6R6L.mjs.map +7 -0
  68. package/dist/lib/node-esm/meta.json +1 -1
  69. package/dist/lib/node-esm/react-context-IGEHJQK5.mjs +25 -0
  70. package/dist/lib/node-esm/react-context-IGEHJQK5.mjs.map +7 -0
  71. package/dist/lib/node-esm/react-surface-CXLBCMPN.mjs +42 -0
  72. package/dist/lib/node-esm/react-surface-CXLBCMPN.mjs.map +7 -0
  73. package/dist/lib/node-esm/types.mjs +9 -0
  74. package/dist/types/src/ClientPlugin.d.ts +2 -36
  75. package/dist/types/src/ClientPlugin.d.ts.map +1 -1
  76. package/dist/types/src/capabilities/app-graph-builder.d.ts +181 -0
  77. package/dist/types/src/capabilities/app-graph-builder.d.ts.map +1 -0
  78. package/dist/types/src/capabilities/capabilities.d.ts +8 -0
  79. package/dist/types/src/capabilities/capabilities.d.ts.map +1 -0
  80. package/dist/types/src/capabilities/client.d.ts +9 -0
  81. package/dist/types/src/capabilities/client.d.ts.map +1 -0
  82. package/dist/types/src/capabilities/index.d.ts +193 -0
  83. package/dist/types/src/capabilities/index.d.ts.map +1 -0
  84. package/dist/types/src/capabilities/intent-resolver.d.ts +8 -0
  85. package/dist/types/src/capabilities/intent-resolver.d.ts.map +1 -0
  86. package/dist/types/src/capabilities/react-context.d.ts +8 -0
  87. package/dist/types/src/capabilities/react-context.d.ts.map +1 -0
  88. package/dist/types/src/capabilities/react-surface.d.ts +7 -0
  89. package/dist/types/src/capabilities/react-surface.d.ts.map +1 -0
  90. package/dist/types/src/components/IdentityDialog.d.ts +1 -0
  91. package/dist/types/src/components/IdentityDialog.d.ts.map +1 -1
  92. package/dist/types/src/components/JoinDialog.d.ts +1 -0
  93. package/dist/types/src/components/JoinDialog.d.ts.map +1 -1
  94. package/dist/types/src/components/RecoveryCodeDialog.d.ts +1 -0
  95. package/dist/types/src/components/RecoveryCodeDialog.d.ts.map +1 -1
  96. package/dist/types/src/components/RecoveryCodeDialog.stories.d.ts +8 -0
  97. package/dist/types/src/components/RecoveryCodeDialog.stories.d.ts.map +1 -0
  98. package/dist/types/src/events.d.ts +7 -0
  99. package/dist/types/src/events.d.ts.map +1 -0
  100. package/dist/types/src/index.d.ts +3 -2
  101. package/dist/types/src/index.d.ts.map +1 -1
  102. package/dist/types/src/meta.d.ts +1 -12
  103. package/dist/types/src/meta.d.ts.map +1 -1
  104. package/dist/types/src/translations.d.ts +2 -1
  105. package/dist/types/src/translations.d.ts.map +1 -1
  106. package/dist/types/src/types.d.ts +98 -0
  107. package/dist/types/src/types.d.ts.map +1 -0
  108. package/dist/types/tsconfig.tsbuildinfo +1 -0
  109. package/package.json +21 -19
  110. package/src/ClientPlugin.ts +59 -0
  111. package/src/capabilities/app-graph-builder.ts +39 -0
  112. package/src/capabilities/capabilities.ts +15 -0
  113. package/src/capabilities/client.ts +48 -0
  114. package/src/capabilities/index.ts +13 -0
  115. package/src/capabilities/intent-resolver.ts +101 -0
  116. package/src/capabilities/react-context.tsx +20 -0
  117. package/src/capabilities/react-surface.tsx +44 -0
  118. package/src/components/IdentityDialog.tsx +13 -11
  119. package/src/components/JoinDialog.tsx +13 -19
  120. package/src/components/RecoveryCodeDialog.stories.tsx +50 -0
  121. package/src/components/RecoveryCodeDialog.tsx +19 -7
  122. package/src/events.ts +14 -0
  123. package/src/index.ts +4 -4
  124. package/src/meta.ts +1 -15
  125. package/src/translations.ts +5 -4
  126. package/src/types.ts +89 -0
  127. package/dist/lib/browser/chunk-FGUXUNOK.mjs +0 -26
  128. package/dist/lib/browser/chunk-FGUXUNOK.mjs.map +0 -7
  129. package/dist/lib/browser/meta.mjs +0 -13
  130. package/dist/lib/node/chunk-XYLDQWFV.cjs +0 -51
  131. package/dist/lib/node/chunk-XYLDQWFV.cjs.map +0 -7
  132. package/dist/lib/node/meta.cjs.map +0 -7
  133. package/dist/lib/node-esm/chunk-C6X363FK.mjs +0 -28
  134. package/dist/lib/node-esm/chunk-C6X363FK.mjs.map +0 -7
  135. package/dist/lib/node-esm/meta.mjs +0 -14
  136. package/src/ClientPlugin.tsx +0 -316
  137. /package/dist/lib/browser/{meta.mjs.map → types.mjs.map} +0 -0
  138. /package/dist/lib/node-esm/{meta.mjs.map → types.mjs.map} +0 -0
@@ -0,0 +1,39 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { createIntent } from '@dxos/app-framework';
6
+ import { Capabilities, contributes, type PluginsContext } from '@dxos/app-framework';
7
+ import { createExtension, type Node } from '@dxos/plugin-graph';
8
+
9
+ import { CLIENT_PLUGIN } from '../meta';
10
+ import { ClientAction } from '../types';
11
+
12
+ export default (context: PluginsContext) =>
13
+ contributes(
14
+ Capabilities.AppGraphBuilder,
15
+ createExtension({
16
+ id: CLIENT_PLUGIN,
17
+ filter: (node): node is Node<null> => node.id === 'root',
18
+ actions: () => [
19
+ {
20
+ id: `${CLIENT_PLUGIN}/open-shell`,
21
+ data: async () => {
22
+ const { dispatchPromise: dispatch } = context.requestCapability(Capabilities.IntentDispatcher);
23
+ await dispatch(createIntent(ClientAction.ShareIdentity));
24
+ },
25
+ properties: {
26
+ label: ['open shell label', { ns: CLIENT_PLUGIN }],
27
+ icon: 'ph--address-book--regular',
28
+ keyBinding: {
29
+ macos: 'meta+shift+.',
30
+ // TODO(wittjosiah): Test on windows to see if it behaves the same as linux.
31
+ windows: 'alt+shift+.',
32
+ linux: 'alt+shift+>',
33
+ },
34
+ testId: 'clientPlugin.openShell',
35
+ },
36
+ },
37
+ ],
38
+ }),
39
+ );
@@ -0,0 +1,15 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { defineCapability } from '@dxos/app-framework';
6
+ import { type TypedObject } from '@dxos/echo-schema';
7
+ import { type Client } from '@dxos/react-client';
8
+
9
+ import { CLIENT_PLUGIN } from '../meta';
10
+
11
+ export namespace ClientCapabilities {
12
+ export const Client = defineCapability<Client>(`${CLIENT_PLUGIN}/capability/client`);
13
+ export const Schema = defineCapability<TypedObject[]>(`${CLIENT_PLUGIN}/capability/schema`);
14
+ export const SystemSchema = defineCapability<TypedObject[]>(`${CLIENT_PLUGIN}/capability/system-schema`);
15
+ }
@@ -0,0 +1,48 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { contributes, type PluginsContext } from '@dxos/app-framework';
6
+ import { Config, Defaults, Envs, Local, Storage } from '@dxos/config';
7
+ import { Client } from '@dxos/react-client';
8
+
9
+ import { ClientCapabilities } from './capabilities';
10
+ import { ClientEvents } from '../events';
11
+ import { type ClientPluginOptions } from '../types';
12
+
13
+ type ClientCapabilityOptions = Omit<ClientPluginOptions, 'appKey' | 'invitationUrl' | 'invitationParam' | 'onReset'> & {
14
+ context: PluginsContext;
15
+ };
16
+
17
+ export default async ({ context, onClientInitialized, ...options }: ClientCapabilityOptions) => {
18
+ const config = new Config(await Storage(), Envs(), Local(), Defaults());
19
+ const client = new Client({ config, ...options });
20
+
21
+ await client.initialize();
22
+ await onClientInitialized?.(context, client);
23
+
24
+ const systemSchemas = Array.from(new Set(context.requestCapabilities(ClientCapabilities.SystemSchema).flat()));
25
+ const schemas = Array.from(new Set(context.requestCapabilities(ClientCapabilities.Schema).flat()));
26
+ client.addTypes(systemSchemas);
27
+ client.addTypes(schemas);
28
+
29
+ // TODO(wittjosiah): Remove. This is a hack to get the app to boot with the new identity after a reset.
30
+ client.reloaded.on(() => {
31
+ client.halo.identity.subscribe(async (identity) => {
32
+ if (identity) {
33
+ window.location.href = window.location.origin;
34
+ }
35
+ });
36
+ });
37
+
38
+ const subscription = client.spaces.isReady.subscribe(async (ready) => {
39
+ if (ready) {
40
+ await context.activate(ClientEvents.SpacesReady);
41
+ }
42
+ });
43
+
44
+ return contributes(ClientCapabilities.Client, client, async () => {
45
+ subscription.unsubscribe();
46
+ await client.destroy();
47
+ });
48
+ };
@@ -0,0 +1,13 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { lazy } from '@dxos/app-framework';
6
+
7
+ export const AppGraphBuilder = lazy(async () => import('./app-graph-builder'));
8
+ export const Client = lazy(async () => import('./client'));
9
+ export const IntentResolver = lazy(async () => import('./intent-resolver'));
10
+ export const ReactContext = lazy(async () => import('./react-context'));
11
+ export const ReactSurface = lazy(async () => import('./react-surface'));
12
+
13
+ export * from './capabilities';
@@ -0,0 +1,101 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import {
6
+ Capabilities,
7
+ contributes,
8
+ createIntent,
9
+ createResolver,
10
+ LayoutAction,
11
+ type PluginsContext,
12
+ } from '@dxos/app-framework';
13
+ import { invariant } from '@dxos/invariant';
14
+ import { ObservabilityAction } from '@dxos/plugin-observability/types';
15
+ import { type JoinPanelProps } from '@dxos/shell/react';
16
+
17
+ import { ClientCapabilities } from './capabilities';
18
+ import { IDENTITY_DIALOG, JOIN_DIALOG, RECOVER_CODE_DIALOG } from '../components';
19
+ import { ClientEvents } from '../events';
20
+ import { ClientAction, type ClientPluginOptions } from '../types';
21
+
22
+ type IntentResolverOptions = Pick<ClientPluginOptions, 'onReset'> & {
23
+ context: PluginsContext;
24
+ };
25
+
26
+ export default ({ context, onReset }: IntentResolverOptions) =>
27
+ contributes(Capabilities.IntentResolver, [
28
+ createResolver(ClientAction.CreateIdentity, async () => {
29
+ const manager = context.requestCapability(Capabilities.PluginManager);
30
+ const client = context.requestCapability(ClientCapabilities.Client);
31
+ const data = await client.halo.createIdentity();
32
+ await manager.activate(ClientEvents.IdentityCreated);
33
+ return { data, intents: [createIntent(ObservabilityAction.SendEvent, { name: 'identity.create' })] };
34
+ }),
35
+ createResolver(ClientAction.JoinIdentity, async (data) => {
36
+ return {
37
+ intents: [
38
+ createIntent(LayoutAction.SetLayout, {
39
+ element: 'dialog',
40
+ component: JOIN_DIALOG,
41
+ dialogBlockAlign: 'start',
42
+ subject: {
43
+ initialInvitationCode: data.invitationCode,
44
+ initialDisposition: 'accept-halo-invitation',
45
+ } satisfies Partial<JoinPanelProps>,
46
+ }),
47
+ ],
48
+ };
49
+ }),
50
+ createResolver(ClientAction.ShareIdentity, async () => {
51
+ return {
52
+ intents: [
53
+ createIntent(LayoutAction.SetLayout, {
54
+ element: 'dialog',
55
+ component: IDENTITY_DIALOG,
56
+ dialogBlockAlign: 'start',
57
+ }),
58
+ createIntent(ObservabilityAction.SendEvent, { name: 'identity.share' }),
59
+ ],
60
+ };
61
+ }),
62
+ createResolver(ClientAction.RecoverIdentity, async () => {
63
+ return {
64
+ intents: [
65
+ createIntent(LayoutAction.SetLayout, {
66
+ element: 'dialog',
67
+ component: JOIN_DIALOG,
68
+ dialogBlockAlign: 'start',
69
+ subject: { initialDisposition: 'recover-identity' } satisfies Partial<JoinPanelProps>,
70
+ }),
71
+ ],
72
+ };
73
+ }),
74
+ createResolver(ClientAction.ResetStorage, async (data) => {
75
+ await onReset?.({ target: data.target });
76
+ return {};
77
+ }),
78
+ createResolver(ClientAction.CreateAgent, async () => {
79
+ const client = context.requestCapability(ClientCapabilities.Client);
80
+ invariant(client.services.services.EdgeAgentService, 'Missing EdgeAgentService');
81
+ await client.services.services.EdgeAgentService.createAgent(null as any, { timeout: 10_000 });
82
+ }),
83
+ createResolver(ClientAction.CreateRecoveryCode, async () => {
84
+ const client = context.requestCapability(ClientCapabilities.Client);
85
+ invariant(client.services.services.IdentityService, 'IdentityService not available');
86
+ // TODO(wittjosiah): This needs a proper api. Rename property.
87
+ const { seedphrase } = await client.services.services.IdentityService.createRecoveryPhrase();
88
+ return {
89
+ intents: [
90
+ createIntent(LayoutAction.SetLayout, {
91
+ element: 'dialog',
92
+ dialogBlockAlign: 'start',
93
+ dialogType: 'alert',
94
+ state: true,
95
+ component: RECOVER_CODE_DIALOG,
96
+ subject: { code: seedphrase },
97
+ }),
98
+ ],
99
+ };
100
+ }),
101
+ ]);
@@ -0,0 +1,20 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import React from 'react';
6
+
7
+ import { Capabilities, contributes, useCapability } from '@dxos/app-framework';
8
+ import { ClientProvider } from '@dxos/react-client';
9
+
10
+ import { ClientCapabilities } from './capabilities';
11
+ import { CLIENT_PLUGIN } from '../meta';
12
+
13
+ export default () =>
14
+ contributes(Capabilities.ReactContext, {
15
+ id: CLIENT_PLUGIN,
16
+ context: ({ children }) => {
17
+ const client = useCapability(ClientCapabilities.Client);
18
+ return <ClientProvider client={client}>{children}</ClientProvider>;
19
+ },
20
+ });
@@ -0,0 +1,44 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import React from 'react';
6
+
7
+ import { createSurface, Capabilities, contributes } from '@dxos/app-framework';
8
+ import { type IdentityPanelProps, type JoinPanelProps } from '@dxos/shell/react';
9
+
10
+ import {
11
+ IDENTITY_DIALOG,
12
+ IdentityDialog,
13
+ JOIN_DIALOG,
14
+ JoinDialog,
15
+ RECOVER_CODE_DIALOG,
16
+ RecoveryCodeDialog,
17
+ type RecoveryCodeDialogProps,
18
+ } from '../components';
19
+
20
+ type ReactSurfaceOptions = {
21
+ createInvitationUrl: (invitationCode: string) => string;
22
+ };
23
+
24
+ export default ({ createInvitationUrl }: ReactSurfaceOptions) =>
25
+ contributes(Capabilities.ReactSurface, [
26
+ createSurface({
27
+ id: IDENTITY_DIALOG,
28
+ role: 'dialog',
29
+ filter: (data): data is { subject: IdentityPanelProps } => data.component === IDENTITY_DIALOG,
30
+ component: ({ data }) => <IdentityDialog {...data.subject} createInvitationUrl={createInvitationUrl} />,
31
+ }),
32
+ createSurface({
33
+ id: JOIN_DIALOG,
34
+ role: 'dialog',
35
+ filter: (data): data is { subject: JoinPanelProps } => data.component === JOIN_DIALOG,
36
+ component: ({ data }) => <JoinDialog {...data.subject} />,
37
+ }),
38
+ createSurface({
39
+ id: RECOVER_CODE_DIALOG,
40
+ role: 'dialog',
41
+ filter: (data): data is { subject: RecoveryCodeDialogProps } => data.component === RECOVER_CODE_DIALOG,
42
+ component: ({ data }) => <RecoveryCodeDialog {...data.subject} />,
43
+ }),
44
+ ]);
@@ -4,42 +4,44 @@
4
4
 
5
5
  import React, { useCallback } from 'react';
6
6
 
7
- import { LayoutAction, useIntentDispatcher } from '@dxos/app-framework';
7
+ import { createIntent, LayoutAction, useIntentDispatcher } from '@dxos/app-framework';
8
8
  import { useClient } from '@dxos/react-client';
9
9
  import { Clipboard, Dialog } from '@dxos/react-ui';
10
10
  import { IdentityPanel, type IdentityPanelProps } from '@dxos/shell/react';
11
11
 
12
- import { ClientAction } from '../meta';
12
+ import { CLIENT_PLUGIN } from '../meta';
13
+ import { ClientAction } from '../types';
14
+
15
+ export const IDENTITY_DIALOG = `${CLIENT_PLUGIN}/IdentityDialog`;
13
16
 
14
17
  export const IdentityDialog = (props: IdentityPanelProps) => {
15
- const dispatch = useIntentDispatcher();
18
+ const { dispatchPromise: dispatch } = useIntentDispatcher();
16
19
  const client = useClient();
17
20
 
18
21
  const handleDone = useCallback(
19
22
  () =>
20
- dispatch({
21
- action: LayoutAction.SET_LAYOUT,
22
- data: {
23
+ dispatch(
24
+ createIntent(LayoutAction.SetLayout, {
23
25
  element: 'dialog',
24
26
  state: false,
25
- },
26
- }),
27
+ }),
28
+ ),
27
29
  [dispatch],
28
30
  );
29
31
 
30
32
  const handleResetStorage = useCallback(async () => {
31
33
  await client.reset();
32
- await dispatch({ action: ClientAction.RESET_STORAGE });
34
+ await dispatch(createIntent(ClientAction.ResetStorage));
33
35
  }, [dispatch]);
34
36
 
35
37
  const handleRecover = useCallback(async () => {
36
38
  await client.reset();
37
- await dispatch({ action: ClientAction.RESET_STORAGE, data: { target: 'recoverIdentity' } });
39
+ await dispatch(createIntent(ClientAction.ResetStorage, { target: 'recoverIdentity' }));
38
40
  }, [dispatch]);
39
41
 
40
42
  const handleJoinNewIdentity = useCallback(async () => {
41
43
  await client.reset();
42
- await dispatch({ action: ClientAction.RESET_STORAGE, data: { target: 'deviceInvitation' } });
44
+ await dispatch(createIntent(ClientAction.ResetStorage, { target: 'deviceInvitation' }));
43
45
  }, [dispatch]);
44
46
 
45
47
  return (
@@ -4,38 +4,32 @@
4
4
 
5
5
  import React, { useCallback } from 'react';
6
6
 
7
- import { LayoutAction, useIntentDispatcher } from '@dxos/app-framework';
7
+ import { createIntent, LayoutAction, useIntentDispatcher } from '@dxos/app-framework';
8
+ import { ObservabilityAction } from '@dxos/plugin-observability/types';
8
9
  import { type InvitationResult } from '@dxos/react-client/invitations';
9
10
  import { Dialog } from '@dxos/react-ui';
10
11
  import { JoinPanel, type JoinPanelProps } from '@dxos/shell/react';
11
12
 
12
- import { CLIENT_PLUGIN, ClientAction, OBSERVABILITY_ACTION } from '../meta';
13
+ import { CLIENT_PLUGIN } from '../meta';
14
+ import { ClientAction } from '../types';
15
+
16
+ export const JOIN_DIALOG = `${CLIENT_PLUGIN}/JoinDialog`;
13
17
 
14
18
  export const JoinDialog = (props: JoinPanelProps) => {
15
- const dispatch = useIntentDispatcher();
19
+ const { dispatchPromise: dispatch } = useIntentDispatcher();
16
20
 
17
- const handleCancelResetStorage = useCallback(
18
- () => dispatch({ plugin: CLIENT_PLUGIN, action: ClientAction.SHARE_IDENTITY }),
19
- [dispatch],
20
- );
21
+ const handleCancelResetStorage = useCallback(() => dispatch(createIntent(ClientAction.ShareIdentity)), [dispatch]);
21
22
 
22
23
  const handleDone = useCallback(
23
24
  async (result: InvitationResult | null) => {
24
25
  if (result?.identityKey) {
25
26
  await Promise.all([
26
- dispatch({
27
- action: LayoutAction.SET_LAYOUT,
28
- data: {
29
- element: 'dialog',
30
- state: false,
31
- },
32
- }),
33
- dispatch({
34
- action: OBSERVABILITY_ACTION,
35
- data: {
27
+ dispatch(createIntent(LayoutAction.SetLayout, { element: 'dialog', state: false })),
28
+ dispatch(
29
+ createIntent(ObservabilityAction.SendEvent, {
36
30
  name: props.initialDisposition === 'recover-identity' ? 'identity.recover' : 'identity.join',
37
- },
38
- }),
31
+ }),
32
+ ),
39
33
  ]);
40
34
  }
41
35
  },
@@ -0,0 +1,50 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ import '@dxos-theme';
6
+
7
+ import { type StoryObj, type Meta } from '@storybook/react';
8
+ import React, { useState } from 'react';
9
+
10
+ import { useClient } from '@dxos/react-client';
11
+ import { withClientProvider } from '@dxos/react-client/testing';
12
+ import { AlertDialog, useAsyncEffect } from '@dxos/react-ui';
13
+ import { withTheme, withLayout } from '@dxos/storybook-utils';
14
+
15
+ import { RecoveryCodeDialog, type RecoveryCodeDialogProps } from './RecoveryCodeDialog';
16
+ import translations from '../translations';
17
+
18
+ const Render = () => {
19
+ const client = useClient();
20
+ const [seedphrase, setSeedphrase] = useState<string>();
21
+ useAsyncEffect(async () => {
22
+ const { seedphrase } = (await client.services.services.IdentityService?.createRecoveryPhrase()) ?? {};
23
+ setSeedphrase(seedphrase);
24
+ }, [client]);
25
+
26
+ return (
27
+ <AlertDialog.Root open={!!seedphrase}>
28
+ <AlertDialog.Overlay>
29
+ <RecoveryCodeDialog code={seedphrase ?? ''} />
30
+ </AlertDialog.Overlay>
31
+ </AlertDialog.Root>
32
+ );
33
+ };
34
+
35
+ const meta: Meta<RecoveryCodeDialogProps> = {
36
+ title: 'plugins/plugin-client/RecoveryCodeDialog',
37
+ component: RecoveryCodeDialog,
38
+ render: Render,
39
+ decorators: [withClientProvider({ createIdentity: true }), withTheme, withLayout({ tooltips: true })],
40
+ parameters: {
41
+ layout: 'fullscreen',
42
+ translations,
43
+ },
44
+ };
45
+
46
+ export default meta;
47
+
48
+ type Story = StoryObj<RecoveryCodeDialogProps>;
49
+
50
+ export const Default: Story = {};
@@ -8,6 +8,8 @@ import { AlertDialog, Button, Clipboard, Input, useTranslation } from '@dxos/rea
8
8
 
9
9
  import { CLIENT_PLUGIN } from '../meta';
10
10
 
11
+ export const RECOVER_CODE_DIALOG = `${CLIENT_PLUGIN}/RecoveryCodeDialog`;
12
+
11
13
  export type RecoveryCodeDialogProps = {
12
14
  code: string;
13
15
  };
@@ -20,12 +22,15 @@ export const RecoveryCodeDialog = ({ code }: RecoveryCodeDialogProps) => {
20
22
 
21
23
  return (
22
24
  <AlertDialog.Content classNames='bs-content min-bs-[15rem] max-bs-full md:max-is-[40rem] overflow-hidden'>
23
- <AlertDialog.Title classNames=''>{t('recovery code dialog title')}</AlertDialog.Title>
25
+ <AlertDialog.Title>{t('recovery code dialog title')}</AlertDialog.Title>
24
26
  <p className='py-4'>{t('recovery code dialog description')}</p>
25
27
  <Clipboard.Provider>
26
- <Code value={code} />
28
+ <Code code={code} />
27
29
  </Clipboard.Provider>
28
- <p className='py-4'>{t('recovery code dialog warning')}</p>
30
+ <div className='flex flex-col py-4 gap-2'>
31
+ <p>{t('recovery code dialog warning 1')}</p>
32
+ <p>{t('recovery code dialog warning 2')}</p>
33
+ </div>
29
34
  <div className='flex items-center gap-2 pbe-4'>
30
35
  <Input.Root>
31
36
  <Input.Checkbox
@@ -47,12 +52,19 @@ export const RecoveryCodeDialog = ({ code }: RecoveryCodeDialogProps) => {
47
52
  );
48
53
  };
49
54
 
50
- // TODO(wittjosiah): Factor out.
51
- const Code = ({ value }: { value: string }) => {
55
+ const Code = ({ code }: { code: string }) => {
56
+ const words = code.split(' ');
52
57
  return (
53
58
  <div className='relative p-2 border border-separator rounded group'>
54
- <Clipboard.IconButton value={value} classNames='absolute top-2 right-2 invisible group-hover:visible' />
55
- <code className='whitespace-pre-wrap'>{value}</code>
59
+ <Clipboard.IconButton value={code} classNames='absolute top-2 right-2 invisible group-hover:visible' />
60
+ <div className='grid grid-cols-4'>
61
+ {words.map((word, i) => (
62
+ <div key={i} className='flex items-center p-2 gap-2 items-center'>
63
+ <div className='w-4 text-xs text-center text-subdued'>{i + 1}</div>
64
+ <div className='text-sm'>{word}</div>
65
+ </div>
66
+ ))}
67
+ </div>
56
68
  </div>
57
69
  );
58
70
  };
package/src/events.ts ADDED
@@ -0,0 +1,14 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { defineEvent } from '@dxos/app-framework';
6
+
7
+ import { CLIENT_PLUGIN } from './meta';
8
+
9
+ export namespace ClientEvents {
10
+ export const SetupClient = defineEvent(`${CLIENT_PLUGIN}/event/setup-client`);
11
+ export const ClientReady = defineEvent(`${CLIENT_PLUGIN}/event/client-ready`);
12
+ export const IdentityCreated = defineEvent(`${CLIENT_PLUGIN}/event/identity-created`);
13
+ export const SpacesReady = defineEvent(`${CLIENT_PLUGIN}/event/spaces-ready`);
14
+ }
package/src/index.ts CHANGED
@@ -2,8 +2,8 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- import { ClientPlugin } from './ClientPlugin';
6
-
7
- export default ClientPlugin;
8
-
9
5
  export * from './ClientPlugin';
6
+ export * from './meta';
7
+
8
+ export { ClientCapabilities } from './capabilities';
9
+ export { ClientEvents } from './events';
package/src/meta.ts CHANGED
@@ -6,21 +6,7 @@ import { type PluginMeta } from '@dxos/app-framework';
6
6
 
7
7
  export const CLIENT_PLUGIN = 'dxos.org/plugin/client';
8
8
 
9
- export default {
9
+ export const meta = {
10
10
  id: CLIENT_PLUGIN,
11
11
  name: 'Client',
12
12
  } satisfies PluginMeta;
13
-
14
- const CLIENT_ACTION = `${CLIENT_PLUGIN}/action`;
15
- export enum ClientAction {
16
- CREATE_IDENTITY = `${CLIENT_ACTION}/CREATE_IDENTITY`,
17
- JOIN_IDENTITY = `${CLIENT_ACTION}/JOIN_IDENTITY`,
18
- SHARE_IDENTITY = `${CLIENT_ACTION}/SHARE_IDENTITY`,
19
- RECOVER_IDENTITY = `${CLIENT_ACTION}/RECOVER_IDENTITY`,
20
- RESET_STORAGE = `${CLIENT_ACTION}/RESET_STORAGE`,
21
- CREATE_AGENT = `${CLIENT_ACTION}/CREATE_AGENT`,
22
- CREATE_RECOVERY_CODE = `${CLIENT_ACTION}/CREATE_RECOVERY_CODE`,
23
- }
24
-
25
- // NOTE: This action is hardcoded to avoid circular dependency with observability plugin.
26
- export const OBSERVABILITY_ACTION = 'dxos.org/plugin/observability/send-event';
@@ -9,12 +9,13 @@ export default [
9
9
  'en-US': {
10
10
  [CLIENT_PLUGIN]: {
11
11
  'open shell label': 'Open HALO',
12
- 'recovery code dialog title': 'Recovery Code',
12
+ 'recovery code dialog title': 'Account Recovery',
13
13
  'recovery code dialog description':
14
14
  'This is your identity recovery code, store it in a safe place. You can use it to recover your identity if you ever lose access to your devices.',
15
- 'recovery code dialog warning':
16
- 'Please note: This code will not be displayed again and cannot be replaced. It is your private key for recovering DXOS data. Anyone with this key will also be able to gain access to your account.',
17
- 'recovery code confirmation label': 'Confirm the code has been saved',
15
+ 'recovery code dialog warning 1': 'NOTE: This code will not be displayed again.',
16
+ 'recovery code dialog warning 2':
17
+ 'It is your private key for recovering DXOS data. Anyone with this key will be able to gain access to your account.',
18
+ 'recovery code confirmation label': 'Please confirm you have saved the code.',
18
19
  'continue label': 'Continue',
19
20
  },
20
21
  },
package/src/types.ts ADDED
@@ -0,0 +1,89 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import { type PluginsContext } from '@dxos/app-framework';
6
+ import { S } from '@dxos/echo-schema';
7
+ import { type Client, PublicKey, type ClientOptions } from '@dxos/react-client';
8
+ import { type MaybePromise } from '@dxos/util';
9
+
10
+ import { CLIENT_PLUGIN } from './meta';
11
+
12
+ // TODO(wittjosiah): Factor out. Generate?
13
+ const IdentitySchema = S.Struct({
14
+ identityKey: S.instanceOf(PublicKey),
15
+ spaceKey: S.optional(S.instanceOf(PublicKey)),
16
+ profile: S.optional(
17
+ S.Struct({
18
+ displayName: S.optional(S.String),
19
+ avatarCid: S.optional(S.String),
20
+ data: S.optional(S.Record({ key: S.String, value: S.Any })),
21
+ }),
22
+ ),
23
+ });
24
+
25
+ const CLIENT_ACTION = `${CLIENT_PLUGIN}/action`;
26
+ export namespace ClientAction {
27
+ export class CreateIdentity extends S.TaggedClass<CreateIdentity>()(`${CLIENT_ACTION}/create-identity`, {
28
+ input: S.Struct({
29
+ displayName: S.optional(S.String),
30
+ }),
31
+ output: IdentitySchema,
32
+ }) {}
33
+
34
+ export class JoinIdentity extends S.TaggedClass<JoinIdentity>()(`${CLIENT_ACTION}/join-identity`, {
35
+ input: S.Struct({
36
+ invitationCode: S.String,
37
+ }),
38
+ output: S.Void,
39
+ }) {}
40
+
41
+ export class ShareIdentity extends S.TaggedClass<ShareIdentity>()(`${CLIENT_ACTION}/share-identity`, {
42
+ input: S.Void,
43
+ output: S.Void,
44
+ }) {}
45
+
46
+ export class RecoverIdentity extends S.TaggedClass<RecoverIdentity>()(`${CLIENT_ACTION}/recover-identity`, {
47
+ input: S.Void,
48
+ output: S.Void,
49
+ }) {}
50
+
51
+ export class ResetStorage extends S.TaggedClass<ResetStorage>()(`${CLIENT_ACTION}/reset-storage`, {
52
+ input: S.Struct({
53
+ target: S.optional(S.String),
54
+ }),
55
+ output: S.Void,
56
+ }) {}
57
+
58
+ export class CreateAgent extends S.TaggedClass<CreateAgent>()(`${CLIENT_ACTION}/create-agent`, {
59
+ input: S.Void,
60
+ output: S.Void,
61
+ }) {}
62
+
63
+ export class CreateRecoveryCode extends S.TaggedClass<CreateRecoveryCode>()(`${CLIENT_ACTION}/create-recovery-code`, {
64
+ input: S.Void,
65
+ output: S.Void,
66
+ }) {}
67
+ }
68
+
69
+ export type ClientPluginOptions = ClientOptions & {
70
+ /**
71
+ * Base URL for the invitation link.
72
+ */
73
+ invitationUrl?: string;
74
+
75
+ /**
76
+ * Query parameter for the invitation code.
77
+ */
78
+ invitationParam?: string;
79
+
80
+ /**
81
+ * Run after the client has been initialized.
82
+ */
83
+ onClientInitialized?: (context: PluginsContext, client: Client) => MaybePromise<void>;
84
+
85
+ /**
86
+ * Called when the client is reset.
87
+ */
88
+ onReset?: (params: { target?: string }) => MaybePromise<void>;
89
+ };