@dxos/client-services 0.1.22 → 0.1.24-next.6b1f434

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 (129) hide show
  1. package/dist/lib/browser/chunk-ZPZBLOQW.mjs +4086 -0
  2. package/dist/lib/browser/chunk-ZPZBLOQW.mjs.map +7 -0
  3. package/dist/lib/browser/index.mjs +68 -3027
  4. package/dist/lib/browser/index.mjs.map +4 -4
  5. package/dist/lib/browser/meta.json +1 -1
  6. package/dist/lib/browser/packlets/testing/index.mjs +198 -0
  7. package/dist/lib/browser/packlets/testing/index.mjs.map +7 -0
  8. package/dist/lib/node/index.cjs +1694 -667
  9. package/dist/lib/node/index.cjs.map +4 -4
  10. package/dist/lib/node/meta.json +1 -1
  11. package/dist/lib/node/packlets/testing/index.cjs +3806 -0
  12. package/dist/lib/node/packlets/testing/index.cjs.map +7 -0
  13. package/dist/types/src/packlets/deprecated/profile.d.ts +3 -1
  14. package/dist/types/src/packlets/deprecated/profile.d.ts.map +1 -1
  15. package/dist/types/src/packlets/deprecated/space.d.ts.map +1 -1
  16. package/dist/types/src/packlets/deprecated/system.d.ts +15 -9
  17. package/dist/types/src/packlets/deprecated/system.d.ts.map +1 -1
  18. package/dist/types/src/packlets/devtools/devtools.d.ts +2 -2
  19. package/dist/types/src/packlets/devtools/devtools.d.ts.map +1 -1
  20. package/dist/types/src/packlets/devtools/feeds.d.ts.map +1 -1
  21. package/dist/types/src/packlets/devtools/network.d.ts +3 -3
  22. package/dist/types/src/packlets/devtools/network.d.ts.map +1 -1
  23. package/dist/types/src/packlets/devtools/spaces.d.ts.map +1 -1
  24. package/dist/types/src/packlets/identity/authenticator.d.ts +9 -9
  25. package/dist/types/src/packlets/identity/authenticator.d.ts.map +1 -1
  26. package/dist/types/src/packlets/identity/identity-manager.d.ts +8 -2
  27. package/dist/types/src/packlets/identity/identity-manager.d.ts.map +1 -1
  28. package/dist/types/src/packlets/identity/identity.d.ts +5 -4
  29. package/dist/types/src/packlets/identity/identity.d.ts.map +1 -1
  30. package/dist/types/src/packlets/invitations/halo-invitations-handler.d.ts +11 -3
  31. package/dist/types/src/packlets/invitations/halo-invitations-handler.d.ts.map +1 -1
  32. package/dist/types/src/packlets/invitations/invitations.d.ts +1 -1
  33. package/dist/types/src/packlets/invitations/invitations.d.ts.map +1 -1
  34. package/dist/types/src/packlets/invitations/space-invitations-handler.d.ts +1 -1
  35. package/dist/types/src/packlets/invitations/space-invitations-handler.d.ts.map +1 -1
  36. package/dist/types/src/packlets/services/service-context.d.ts +3 -1
  37. package/dist/types/src/packlets/services/service-context.d.ts.map +1 -1
  38. package/dist/types/src/packlets/services/service-context.test.d.ts +2 -0
  39. package/dist/types/src/packlets/services/service-context.test.d.ts.map +1 -0
  40. package/dist/types/src/packlets/services/service-definitions.d.ts +1 -1
  41. package/dist/types/src/packlets/services/service-definitions.d.ts.map +1 -1
  42. package/dist/types/src/packlets/services/service-host.d.ts +9 -6
  43. package/dist/types/src/packlets/services/service-host.d.ts.map +1 -1
  44. package/dist/types/src/packlets/services/service-registry.d.ts +4 -3
  45. package/dist/types/src/packlets/services/service-registry.d.ts.map +1 -1
  46. package/dist/types/src/packlets/spaces/data-space-manager.d.ts +9 -2
  47. package/dist/types/src/packlets/spaces/data-space-manager.d.ts.map +1 -1
  48. package/dist/types/src/packlets/spaces/data-space-manager.test.d.ts +2 -0
  49. package/dist/types/src/packlets/spaces/data-space-manager.test.d.ts.map +1 -0
  50. package/dist/types/src/packlets/spaces/data-space.d.ts +20 -4
  51. package/dist/types/src/packlets/spaces/data-space.d.ts.map +1 -1
  52. package/dist/types/src/packlets/spaces/notarization-plugin.d.ts +50 -0
  53. package/dist/types/src/packlets/spaces/notarization-plugin.d.ts.map +1 -0
  54. package/dist/types/src/packlets/spaces/notarization-plugin.test.d.ts +2 -0
  55. package/dist/types/src/packlets/spaces/notarization-plugin.test.d.ts.map +1 -0
  56. package/dist/types/src/packlets/spaces/spaces-service.d.ts +3 -2
  57. package/dist/types/src/packlets/spaces/spaces-service.d.ts.map +1 -1
  58. package/dist/types/src/packlets/testing/credential-utils.d.ts +8 -0
  59. package/dist/types/src/packlets/testing/credential-utils.d.ts.map +1 -0
  60. package/dist/types/src/packlets/testing/index.d.ts +2 -0
  61. package/dist/types/src/packlets/testing/index.d.ts.map +1 -1
  62. package/dist/types/src/packlets/testing/invitation-utils.d.ts +3 -0
  63. package/dist/types/src/packlets/testing/invitation-utils.d.ts.map +1 -0
  64. package/dist/types/src/packlets/testing/test-builder.d.ts +31 -2
  65. package/dist/types/src/packlets/testing/test-builder.d.ts.map +1 -1
  66. package/dist/types/src/packlets/vault/iframe-host-runtime.d.ts +35 -0
  67. package/dist/types/src/packlets/vault/iframe-host-runtime.d.ts.map +1 -0
  68. package/dist/types/src/packlets/vault/{iframe-runtime.d.ts → iframe-proxy-runtime.d.ts} +9 -4
  69. package/dist/types/src/packlets/vault/iframe-proxy-runtime.d.ts.map +1 -0
  70. package/dist/types/src/packlets/vault/index.d.ts +6 -1
  71. package/dist/types/src/packlets/vault/index.d.ts.map +1 -1
  72. package/dist/types/src/packlets/vault/services.d.ts +9 -1
  73. package/dist/types/src/packlets/vault/services.d.ts.map +1 -1
  74. package/dist/types/src/packlets/vault/shell-controller.d.ts +24 -0
  75. package/dist/types/src/packlets/vault/shell-controller.d.ts.map +1 -0
  76. package/dist/types/src/packlets/vault/shell-runtime.d.ts +45 -0
  77. package/dist/types/src/packlets/vault/shell-runtime.d.ts.map +1 -0
  78. package/dist/types/src/packlets/vault/vault-resource-lock.d.ts +19 -0
  79. package/dist/types/src/packlets/vault/vault-resource-lock.d.ts.map +1 -0
  80. package/dist/types/src/packlets/vault/worker-runtime.d.ts +2 -1
  81. package/dist/types/src/packlets/vault/worker-runtime.d.ts.map +1 -1
  82. package/dist/types/src/packlets/vault/worker-session.d.ts +7 -4
  83. package/dist/types/src/packlets/vault/worker-session.d.ts.map +1 -1
  84. package/package.json +30 -29
  85. package/src/packlets/deprecated/profile.ts +20 -2
  86. package/src/packlets/deprecated/space.ts +41 -37
  87. package/src/packlets/deprecated/system.ts +26 -13
  88. package/src/packlets/devtools/devtools.ts +4 -4
  89. package/src/packlets/devtools/feeds.ts +11 -6
  90. package/src/packlets/devtools/network.ts +20 -9
  91. package/src/packlets/devtools/spaces.ts +8 -7
  92. package/src/packlets/identity/authenticator.test.ts +4 -4
  93. package/src/packlets/identity/authenticator.ts +27 -17
  94. package/src/packlets/identity/identity-manager.test.ts +25 -16
  95. package/src/packlets/identity/identity-manager.ts +25 -13
  96. package/src/packlets/identity/identity.test.ts +22 -23
  97. package/src/packlets/identity/identity.ts +15 -10
  98. package/src/packlets/invitations/halo-invitations-handler.test.ts +4 -45
  99. package/src/packlets/invitations/halo-invitations-handler.ts +33 -17
  100. package/src/packlets/invitations/invitations.ts +2 -1
  101. package/src/packlets/invitations/space-invitations-handler.test.ts +43 -26
  102. package/src/packlets/invitations/space-invitations-handler.ts +9 -12
  103. package/src/packlets/services/service-context.test.ts +56 -0
  104. package/src/packlets/services/service-context.ts +62 -10
  105. package/src/packlets/services/service-definitions.ts +1 -1
  106. package/src/packlets/services/service-host.test.ts +74 -5
  107. package/src/packlets/services/service-host.ts +86 -24
  108. package/src/packlets/services/service-registry.test.ts +2 -1
  109. package/src/packlets/services/service-registry.ts +5 -1
  110. package/src/packlets/spaces/data-space-manager.test.ts +131 -0
  111. package/src/packlets/spaces/data-space-manager.ts +61 -15
  112. package/src/packlets/spaces/data-space.ts +125 -14
  113. package/src/packlets/spaces/notarization-plugin.test.ts +81 -0
  114. package/src/packlets/spaces/notarization-plugin.ts +211 -0
  115. package/src/packlets/spaces/spaces-service.ts +10 -2
  116. package/src/packlets/testing/credential-utils.ts +28 -0
  117. package/src/packlets/testing/index.ts +2 -0
  118. package/src/packlets/testing/invitation-utils.ts +44 -0
  119. package/src/packlets/testing/test-builder.ts +96 -14
  120. package/src/packlets/vault/iframe-host-runtime.ts +131 -0
  121. package/src/packlets/vault/{iframe-runtime.ts → iframe-proxy-runtime.ts} +18 -3
  122. package/src/packlets/vault/index.ts +6 -1
  123. package/src/packlets/vault/services.ts +17 -1
  124. package/src/packlets/vault/shell-controller.ts +77 -0
  125. package/src/packlets/vault/shell-runtime.ts +133 -0
  126. package/src/packlets/vault/vault-resource-lock.ts +76 -0
  127. package/src/packlets/vault/worker-runtime.ts +16 -4
  128. package/src/packlets/vault/worker-session.ts +36 -26
  129. package/dist/types/src/packlets/vault/iframe-runtime.d.ts.map +0 -1
@@ -4,7 +4,6 @@
4
4
 
5
5
  import assert from 'node:assert';
6
6
 
7
- import { EventSubscriptions } from '@dxos/async';
8
7
  import { Stream } from '@dxos/codec-protobuf';
9
8
  import { todo } from '@dxos/debug';
10
9
  import { log } from '@dxos/log';
@@ -94,29 +93,29 @@ export class SpaceServiceImpl implements SpaceService {
94
93
  }
95
94
 
96
95
  subscribeSpaces() {
97
- return new Stream<SubscribeSpacesResponse>(({ next }) => {
98
- const subscriptions = new EventSubscriptions();
99
-
96
+ return new Stream<SubscribeSpacesResponse>(({ next, ctx }) => {
100
97
  const onUpdate = () => {
101
- const spaces = Array.from(this.serviceContext.dataSpaceManager!.spaces.values()).map(
102
- (space): Space => ({
103
- publicKey: space.key,
104
- isOpen: true,
105
- isActive: true,
106
- members: Array.from(space.inner.spaceState.members.values()).map((member) => ({
107
- identityKey: member.key,
108
- profile: {
98
+ const spaces = Array.from(this.serviceContext.dataSpaceManager!.spaces.values())
99
+ .filter((space) => this.serviceContext.dataServiceSubscriptions.getDataService(space.key)) // Skip spaces without data service available.
100
+ .map(
101
+ (space): Space => ({
102
+ publicKey: space.key,
103
+ isOpen: true,
104
+ isActive: true,
105
+ members: Array.from(space.inner.spaceState.members.values()).map((member) => ({
109
106
  identityKey: member.key,
110
- displayName: member.assertion.profile?.displayName ?? humanize(member.key)
111
- },
112
- presence:
113
- this.serviceContext.identityManager.identity?.identityKey.equals(member.key) ||
114
- space.presence.getPeersOnline().filter(({ identityKey }) => identityKey.equals(member.key)).length > 0
115
- ? SpaceMember.PresenceState.ONLINE
116
- : SpaceMember.PresenceState.OFFLINE
117
- }))
118
- })
119
- );
107
+ profile: {
108
+ identityKey: member.key,
109
+ displayName: member.assertion.profile?.displayName ?? humanize(member.key)
110
+ },
111
+ presence:
112
+ this.serviceContext.identityManager.identity?.identityKey.equals(member.key) ||
113
+ space.presence.getPeersOnline().filter(({ identityKey }) => identityKey.equals(member.key)).length > 0
114
+ ? SpaceMember.PresenceState.ONLINE
115
+ : SpaceMember.PresenceState.OFFLINE
116
+ }))
117
+ })
118
+ );
120
119
  log('update', { spaces });
121
120
  next({ spaces });
122
121
  };
@@ -128,25 +127,27 @@ export class SpaceServiceImpl implements SpaceService {
128
127
 
129
128
  await this.serviceContext.initialized.wait();
130
129
 
131
- subscriptions.add(
132
- this.serviceContext.dataSpaceManager!.updated.on(() => {
133
- this.serviceContext.dataSpaceManager!.spaces.forEach((space) => {
134
- subscriptions.add(space.stateUpdate.on(onUpdate));
135
- subscriptions.add(space.presence.updated.on(onUpdate));
136
- });
137
- onUpdate();
138
- })
139
- );
140
-
141
- this.serviceContext.dataSpaceManager!.spaces.forEach((space) => {
142
- subscriptions.add(space.stateUpdate.on(onUpdate));
143
- subscriptions.add(space.presence.updated.on(onUpdate));
130
+ // TODO(dmaretskyi): Create a pattern for subscribing to a set of objects.
131
+ const subscribeSpaces = () => {
132
+ for (const space of this.serviceContext.dataSpaceManager!.spaces.values()) {
133
+ if (!this.serviceContext.dataServiceSubscriptions.getDataService(space.key)) {
134
+ // Skip spaces without data service available.
135
+ continue;
136
+ }
137
+
138
+ space.stateUpdate.on(ctx, onUpdate);
139
+ space.presence.updated.on(ctx, onUpdate);
140
+ }
141
+ };
142
+
143
+ this.serviceContext.dataSpaceManager!.updated.on(ctx, () => {
144
+ subscribeSpaces();
145
+ onUpdate();
144
146
  });
145
147
 
148
+ subscribeSpaces();
146
149
  onUpdate();
147
150
  });
148
-
149
- return () => subscriptions.clear();
150
151
  });
151
152
  }
152
153
 
@@ -159,6 +160,9 @@ export class SpaceServiceImpl implements SpaceService {
159
160
  }
160
161
 
161
162
  async createSpace(): Promise<Space> {
163
+ if (!this.serviceContext.identityManager.identity) {
164
+ throw new Error('This device has no HALO identity available. See https://docs.dxos.org/guide/halo');
165
+ }
162
166
  await this.serviceContext.initialized.wait();
163
167
  const space = await this.serviceContext.dataSpaceManager!.createSpace();
164
168
  return {
@@ -3,33 +3,46 @@
3
3
  //
4
4
 
5
5
  import { Config } from '@dxos/config';
6
- import { SystemService } from '@dxos/protocols/proto/dxos/client';
6
+ import { StatusResponse, SystemService } from '@dxos/protocols/proto/dxos/client';
7
+ import { MaybePromise } from '@dxos/util';
7
8
 
8
- import { ServiceContext } from '../services';
9
+ export type SystemServiceOptions = {
10
+ config: Config;
11
+ onInit: () => MaybePromise<void>;
12
+ onStatus: () => MaybePromise<StatusResponse>;
13
+ onReset: () => MaybePromise<void>;
14
+ };
9
15
 
10
16
  /**
11
17
  * @deprecated
12
18
  */
13
19
  export class SystemServiceImpl implements SystemService {
14
- constructor(private readonly _config: Config, private readonly _serviceContext: ServiceContext) {}
20
+ private readonly _config: Config;
21
+ private readonly _onInit: () => MaybePromise<void>;
22
+ private readonly _onStatus: () => MaybePromise<StatusResponse>;
23
+ private readonly _onReset: () => MaybePromise<void>;
15
24
 
16
- // TODO(burdon): Remove.
17
- async initSession() {
18
- // Ok.
25
+ constructor({ config, onInit, onStatus, onReset }: SystemServiceOptions) {
26
+ this._config = config;
27
+ this._onInit = onInit;
28
+ this._onStatus = onStatus;
29
+ this._onReset = onReset;
19
30
  }
20
31
 
21
- async getConfig(request: void) {
32
+ async getConfig() {
22
33
  return this._config.values;
23
34
  }
24
35
 
25
36
  // TODO(burdon): Connect to iframe RPC heartbeat for network status?
26
- async getStatus(request: void) {
27
- return {
28
- message: `ok: ${Date.now()}`
29
- };
37
+ async getStatus() {
38
+ return this._onStatus();
39
+ }
40
+
41
+ async initSession() {
42
+ await this._onInit();
30
43
  }
31
44
 
32
- async reset(request: any) {
33
- await this._serviceContext.reset();
45
+ async reset() {
46
+ await this._onReset();
34
47
  }
35
48
  }
@@ -33,14 +33,14 @@ import {
33
33
  SubscribeToSpacesRequest,
34
34
  SubscribeToSpacesResponse,
35
35
  SubscribeToSignalStatusResponse,
36
- SubscribeToSignalTraceResponse,
36
+ SignalResponse,
37
37
  SubscribeToSwarmInfoResponse
38
38
  } from '@dxos/protocols/proto/dxos/devtools/host';
39
39
 
40
40
  import { ServiceContext } from '../services';
41
41
  import { subscribeToFeedBlocks, subscribeToFeeds } from './feeds';
42
42
  import { subscribeToKeyringKeys } from './keys';
43
- import { subscribeToNetworkStatus, subscribeToSignalTrace, subscribeToSwarmInfo } from './network';
43
+ import { subscribeToNetworkStatus, subscribeToSignal, subscribeToSwarmInfo } from './network';
44
44
  import { subscribeToSpaces } from './spaces';
45
45
 
46
46
  export class DevtoolsHostEvents {
@@ -133,8 +133,8 @@ export class DevtoolsServiceImpl implements DevtoolsHost {
133
133
  return subscribeToNetworkStatus({ networkManager: this.params.context.networkManager });
134
134
  }
135
135
 
136
- subscribeToSignalTrace(): Stream<SubscribeToSignalTraceResponse> {
137
- return subscribeToSignalTrace({ networkManager: this.params.context.networkManager });
136
+ subscribeToSignal(): Stream<SignalResponse> {
137
+ return subscribeToSignal({ networkManager: this.params.context.networkManager });
138
138
  }
139
139
 
140
140
  subscribeToSwarmInfo(): Stream<SubscribeToSwarmInfoResponse> {
@@ -18,17 +18,17 @@ import { ComplexMap } from '@dxos/util';
18
18
  export const subscribeToFeeds = (
19
19
  { feedStore }: { feedStore: FeedStore<FeedMessage> },
20
20
  { feedKeys }: SubscribeToFeedsRequest
21
- ) =>
22
- new Stream<SubscribeToFeedsResponse>(({ next }) => {
21
+ ) => {
22
+ return new Stream<SubscribeToFeedsResponse>(({ next }) => {
23
23
  if (feedKeys?.length === 0) {
24
24
  return;
25
25
  }
26
26
 
27
- const feedMap = new ComplexMap<PublicKey, FeedWrapper<FeedMessage>>(PublicKey.hash);
28
27
  const subscriptions = new EventSubscriptions();
28
+ const feedMap = new ComplexMap<PublicKey, FeedWrapper<FeedMessage>>(PublicKey.hash);
29
29
 
30
30
  const update = () => {
31
- const feeds = feedStore.feeds;
31
+ const { feeds } = feedStore;
32
32
  feeds
33
33
  .filter((feed) => !feedKeys?.length || feedKeys.some((feedKey) => feedKey.equals(feed.key)))
34
34
  .forEach((feed) => {
@@ -38,6 +38,7 @@ export const subscribeToFeeds = (
38
38
  subscriptions.add(feed.off('close', update));
39
39
  }
40
40
  });
41
+
41
42
  next({
42
43
  feeds: Array.from(feedMap.values()).map((feed) => ({ feedKey: feed.key, length: feed.properties.length }))
43
44
  });
@@ -50,12 +51,13 @@ export const subscribeToFeeds = (
50
51
  subscriptions.clear();
51
52
  };
52
53
  });
54
+ };
53
55
 
54
56
  export const subscribeToFeedBlocks = (
55
57
  { feedStore }: { feedStore: FeedStore<FeedMessage> },
56
58
  { feedKey, maxBlocks = 10 }: SubscribeToFeedBlocksRequest
57
- ) =>
58
- new Stream<SubscribeToFeedBlocksResponse>(({ next }) => {
59
+ ) => {
60
+ return new Stream<SubscribeToFeedBlocksResponse>(({ next }) => {
59
61
  if (!feedKey) {
60
62
  return;
61
63
  }
@@ -77,9 +79,11 @@ export const subscribeToFeedBlocks = (
77
79
  break;
78
80
  }
79
81
  }
82
+
80
83
  next({
81
84
  blocks: blocks.slice(-maxBlocks)
82
85
  });
86
+
83
87
  await iterator.close();
84
88
  };
85
89
 
@@ -96,3 +100,4 @@ export const subscribeToFeedBlocks = (
96
100
  clearTimeout(timeout);
97
101
  };
98
102
  });
103
+ };
@@ -3,15 +3,15 @@
3
3
  //
4
4
 
5
5
  import { Stream } from '@dxos/codec-protobuf';
6
+ import { Context } from '@dxos/context';
6
7
  import { PublicKey } from '@dxos/keys';
7
- import { CommandTrace } from '@dxos/messaging';
8
8
  import { NetworkManager } from '@dxos/network-manager';
9
9
  import {
10
10
  GetNetworkPeersRequest,
11
11
  GetNetworkPeersResponse,
12
12
  SubscribeToNetworkTopicsResponse,
13
13
  SubscribeToSignalStatusResponse,
14
- SubscribeToSignalTraceResponse,
14
+ SignalResponse,
15
15
  SubscribeToSwarmInfoResponse
16
16
  } from '@dxos/protocols/proto/dxos/devtools/host';
17
17
 
@@ -30,14 +30,25 @@ export const subscribeToNetworkStatus = ({ networkManager }: { networkManager: N
30
30
  update();
31
31
  });
32
32
 
33
- export const subscribeToSignalTrace = ({ networkManager }: { networkManager: NetworkManager }) =>
34
- new Stream<SubscribeToSignalTraceResponse>(({ next }) => {
35
- next({ events: [] });
36
- const trace: CommandTrace[] = [];
37
- networkManager.signalManager.commandTrace.on((msg) => {
38
- trace.push(msg);
39
- next({ events: trace.map((msg) => JSON.stringify(msg)) });
33
+ export const subscribeToSignal = ({ networkManager }: { networkManager: NetworkManager }) =>
34
+ new Stream<SignalResponse>(({ next }) => {
35
+ const ctx = new Context();
36
+ networkManager.signalManager.onMessage.on(ctx, (message) => {
37
+ next({
38
+ message: {
39
+ author: message.author.asUint8Array(),
40
+ recipient: message.recipient.asUint8Array(),
41
+ payload: message.payload
42
+ },
43
+ receivedAt: new Date()
44
+ });
40
45
  });
46
+ networkManager.signalManager.swarmEvent.on(ctx, (swarmEvent) => {
47
+ next({ swarmEvent: swarmEvent.swarmEvent, receivedAt: new Date() });
48
+ });
49
+ return () => {
50
+ return ctx.dispose();
51
+ };
41
52
  });
42
53
 
43
54
  export const subscribeToNetworkTopics = ({ networkManager }: { networkManager: NetworkManager }) =>
@@ -3,14 +3,14 @@
3
3
  //
4
4
 
5
5
  import { Stream } from '@dxos/codec-protobuf';
6
- import { Space } from '@dxos/echo-db';
6
+ import { Space } from '@dxos/echo-pipeline';
7
7
  import { SubscribeToSpacesRequest, SubscribeToSpacesResponse } from '@dxos/protocols/proto/dxos/devtools/host';
8
8
  import { SpaceMetadata } from '@dxos/protocols/proto/dxos/echo/metadata';
9
9
 
10
10
  import { ServiceContext } from '../services';
11
11
 
12
- export const subscribeToSpaces = (context: ServiceContext, { spaceKeys = [] }: SubscribeToSpacesRequest) =>
13
- new Stream<SubscribeToSpacesResponse>(({ next }) => {
12
+ export const subscribeToSpaces = (context: ServiceContext, { spaceKeys = [] }: SubscribeToSpacesRequest) => {
13
+ return new Stream<SubscribeToSpacesResponse>(({ next }) => {
14
14
  let unsubscribe: () => void;
15
15
 
16
16
  const update = async () => {
@@ -20,7 +20,7 @@ export const subscribeToSpaces = (context: ServiceContext, { spaceKeys = [] }: S
20
20
  );
21
21
 
22
22
  next({
23
- spaces: filteredSpaces.map((space) => {
23
+ spaces: filteredSpaces.map((space): SubscribeToSpacesResponse.SpaceInfo => {
24
24
  const spaceMetadata = context.metadataStore.spaces.find((spaceMetadata: SpaceMetadata) =>
25
25
  spaceMetadata.key.equals(space.key)
26
26
  );
@@ -28,10 +28,10 @@ export const subscribeToSpaces = (context: ServiceContext, { spaceKeys = [] }: S
28
28
  return {
29
29
  key: space.key,
30
30
  isOpen: space.isOpen,
31
- timeframe: spaceMetadata?.latestTimeframe,
31
+ timeframe: spaceMetadata?.dataTimeframe,
32
32
  genesisFeed: space.genesisFeedKey,
33
- controlFeed: space.controlFeedKey,
34
- dataFeed: space.dataFeedKey
33
+ controlFeed: space.controlFeedKey!, // TODO(dmaretskyi): Those keys may be missing.
34
+ dataFeed: space.dataFeedKey!
35
35
  };
36
36
  })
37
37
  });
@@ -50,3 +50,4 @@ export const subscribeToSpaces = (context: ServiceContext, { spaceKeys = [] }: S
50
50
  clearTimeout(timeout);
51
51
  };
52
52
  });
53
+ };
@@ -12,16 +12,16 @@ import { PublicKey } from '@dxos/keys';
12
12
  import { describe, test } from '@dxos/test';
13
13
  import { ComplexSet } from '@dxos/util';
14
14
 
15
- import { createHaloAuthProvider, HaloAuthVerifier } from './authenticator';
15
+ import { createAuthProvider, TrustedKeySetAuthVerifier } from './authenticator';
16
16
 
17
17
  describe('identity/authenticator', () => {
18
18
  test('verifies credentials', async () => {
19
19
  const keyring = new Keyring();
20
20
  const deviceKey = await keyring.createKey();
21
21
  const signer = createCredentialSignerWithKey(keyring, deviceKey);
22
- const authProvider = createHaloAuthProvider(signer);
23
- const authVerifier = new HaloAuthVerifier({
24
- trustedDevicesProvider: () => new ComplexSet(PublicKey.hash, [deviceKey]),
22
+ const authProvider = createAuthProvider(signer);
23
+ const authVerifier = new TrustedKeySetAuthVerifier({
24
+ trustedKeysProvider: () => new ComplexSet(PublicKey.hash, [deviceKey]),
25
25
  update: new Event(),
26
26
  authTimeout: 10
27
27
  });
@@ -5,13 +5,13 @@
5
5
  import { Event, Trigger } from '@dxos/async';
6
6
  import { Context } from '@dxos/context';
7
7
  import { verifyCredential, CredentialSigner } from '@dxos/credentials';
8
- import { AuthProvider, AuthVerifier } from '@dxos/echo-db';
8
+ import { AuthProvider, AuthVerifier } from '@dxos/echo-pipeline';
9
9
  import { PublicKey } from '@dxos/keys';
10
10
  import { log } from '@dxos/log';
11
11
  import { schema } from '@dxos/protocols';
12
12
  import { ComplexSet } from '@dxos/util';
13
13
 
14
- export const createHaloAuthProvider =
14
+ export const createAuthProvider =
15
15
  (signer: CredentialSigner): AuthProvider =>
16
16
  async (nonce) => {
17
17
  const credential = await signer.createCredential({
@@ -25,8 +25,9 @@ export const createHaloAuthProvider =
25
25
  return schema.getCodecForType('dxos.halo.credentials.Credential').encode(credential);
26
26
  };
27
27
 
28
- export type HaloAuthVerifierParams = {
29
- trustedDevicesProvider: () => ComplexSet<PublicKey>;
28
+ export type TrustedKeySetAuthVerifierParams = {
29
+ // TODO(dmaretskyi): Change to `isTrusted: (key) => bool`.
30
+ trustedKeysProvider: () => ComplexSet<PublicKey>;
30
31
  update: Event<void>;
31
32
  /**
32
33
  * Timeout to wait for the device key to be added to the trusted set.
@@ -35,26 +36,25 @@ export type HaloAuthVerifierParams = {
35
36
  };
36
37
 
37
38
  /**
38
- * Verifies credentials of another device of the same identity in the HALO space.
39
- * Will wait up to `authTimeout` for the device key to be added to the trusted set.
39
+ * Verifies credentials of another member in the space based on a set of trusted key.
40
+ * Will wait up to `authTimeout` for the key to be added to the trusted set.
40
41
  */
41
- export class HaloAuthVerifier {
42
+ export class TrustedKeySetAuthVerifier {
42
43
  private _ctx = new Context();
43
44
 
44
- constructor(private readonly _params: HaloAuthVerifierParams) {}
45
+ // prettier-ignore
46
+ constructor(
47
+ private readonly _params: TrustedKeySetAuthVerifierParams
48
+ ) {}
45
49
 
46
50
  async close() {
47
51
  await this._ctx.dispose();
48
52
  }
49
53
 
50
- private _isTrustedDevice(deviceKey: PublicKey) {
51
- const deviceSet = this._params.trustedDevicesProvider();
52
- return deviceSet.has(deviceKey);
53
- }
54
-
55
54
  get verifier(): AuthVerifier {
56
55
  return async (nonce, auth) => {
57
56
  const credential = schema.getCodecForType('dxos.halo.credentials.Credential').decode(auth);
57
+ log('authenticating...', { credential });
58
58
 
59
59
  const result = await verifyCredential(credential);
60
60
  if (result.kind === 'fail') {
@@ -67,7 +67,8 @@ export class HaloAuthVerifier {
67
67
  return false;
68
68
  }
69
69
 
70
- if (this._isTrustedDevice(credential.issuer)) {
70
+ if (this._isTrustedKey(credential.issuer)) {
71
+ log('key is not currently in trusted set, waiting...', { key: credential.issuer });
71
72
  return true;
72
73
  }
73
74
 
@@ -75,14 +76,18 @@ export class HaloAuthVerifier {
75
76
  this._ctx.onDispose(() => {
76
77
  trigger.wake(false);
77
78
  });
79
+
78
80
  const clear = this._params.update.on(this._ctx, () => {
79
- if (this._isTrustedDevice(credential.issuer)) {
81
+ if (this._isTrustedKey(credential.issuer)) {
82
+ log('auth success', { key: credential.issuer });
80
83
  trigger.wake(true);
84
+ } else {
85
+ log('key is not currently in trusted set, waiting...', { key: credential.issuer });
81
86
  }
82
87
  });
88
+
83
89
  try {
84
- await trigger.wait({ timeout: this._params.authTimeout });
85
- return true;
90
+ return await trigger.wait({ timeout: this._params.authTimeout });
86
91
  } catch {
87
92
  return false;
88
93
  } finally {
@@ -90,4 +95,9 @@ export class HaloAuthVerifier {
90
95
  }
91
96
  };
92
97
  }
98
+
99
+ private _isTrustedKey(deviceKey: PublicKey) {
100
+ const deviceSet = this._params.trustedKeysProvider();
101
+ return deviceSet.has(deviceKey);
102
+ }
93
103
  }
@@ -4,7 +4,7 @@
4
4
 
5
5
  import { expect } from 'chai';
6
6
 
7
- import { valueEncoding, MetadataStore, SpaceManager, AuthStatus } from '@dxos/echo-db';
7
+ import { valueEncoding, MetadataStore, SpaceManager, AuthStatus } from '@dxos/echo-pipeline';
8
8
  import { FeedFactory, FeedStore } from '@dxos/feed-store';
9
9
  import { Keyring } from '@dxos/keyring';
10
10
  import { MemorySignalManager, MemorySignalManagerContext } from '@dxos/messaging';
@@ -46,11 +46,12 @@ describe('identity/identity-manager', () => {
46
46
  feedStore,
47
47
  networkManager
48
48
  });
49
- const identityManager = new IdentityManager(metadataStore, keyring, spaceManager);
49
+ const identityManager = new IdentityManager(metadataStore, keyring, feedStore, spaceManager);
50
50
 
51
51
  return {
52
52
  identityManager,
53
- feedStore
53
+ feedStore,
54
+ keyring
54
55
  };
55
56
  };
56
57
 
@@ -90,23 +91,31 @@ describe('identity/identity-manager', () => {
90
91
 
91
92
  const peer2 = await setupPeer({ signalContext });
92
93
 
94
+ const deviceKey = await peer2.keyring.createKey();
95
+ const controlFeedKey = await peer2.keyring.createKey();
96
+ const dataFeedKey = await peer2.keyring.createKey();
97
+
98
+ await identity1.controlPipeline.writer.write({
99
+ credential: {
100
+ credential: await identity1.getIdentityCredentialSigner().createCredential({
101
+ subject: deviceKey,
102
+ assertion: {
103
+ '@type': 'dxos.halo.credentials.AuthorizedDevice',
104
+ identityKey: identity1.identityKey,
105
+ deviceKey
106
+ }
107
+ })
108
+ }
109
+ });
110
+
93
111
  // Identity2 is not yet ready at this point. Peer1 needs to admit peer2 device key and feed keys.
94
112
  const identity2 = await peer2.identityManager.acceptIdentity({
95
113
  identityKey: identity1.identityKey,
114
+ deviceKey,
96
115
  haloSpaceKey: identity1.haloSpaceKey,
97
- haloGenesisFeedKey: identity1.haloGenesisFeedKey
98
- });
99
-
100
- await identity1.controlPipeline.writer.write({
101
- '@type': 'dxos.echo.feed.CredentialsMessage',
102
- credential: await identity1.getIdentityCredentialSigner().createCredential({
103
- subject: identity2.deviceKey,
104
- assertion: {
105
- '@type': 'dxos.halo.credentials.AuthorizedDevice',
106
- identityKey: identity2.identityKey,
107
- deviceKey: identity2.deviceKey
108
- }
109
- })
116
+ haloGenesisFeedKey: identity1.haloGenesisFeedKey,
117
+ controlFeedKey,
118
+ dataFeedKey
110
119
  });
111
120
 
112
121
  // TODO(dmaretskyi): We'd also need to admit device2's feeds otherwise messages from them won't be processed by the pipeline.
@@ -6,15 +6,17 @@ import assert from 'node:assert';
6
6
 
7
7
  import { Event } from '@dxos/async';
8
8
  import { createCredentialSignerWithKey, CredentialGenerator } from '@dxos/credentials';
9
- import { MetadataStore, SpaceManager, SwarmIdentity } from '@dxos/echo-db';
9
+ import { MetadataStore, SpaceManager, SwarmIdentity } from '@dxos/echo-pipeline';
10
+ import { FeedStore } from '@dxos/feed-store';
10
11
  import { Keyring } from '@dxos/keyring';
11
12
  import { PublicKey } from '@dxos/keys';
12
13
  import { log } from '@dxos/log';
14
+ import { FeedMessage } from '@dxos/protocols/proto/dxos/echo/feed';
13
15
  import { AdmittedFeed, IdentityRecord, SpaceRecord } from '@dxos/protocols/proto/dxos/halo/credentials';
14
16
  import { deferFunction } from '@dxos/util';
15
17
 
16
18
  import { Identity } from '../identity';
17
- import { createHaloAuthProvider } from './authenticator';
19
+ import { createAuthProvider } from './authenticator';
18
20
 
19
21
  interface ConstructSpaceParams {
20
22
  spaceRecord: SpaceRecord;
@@ -24,8 +26,11 @@ interface ConstructSpaceParams {
24
26
 
25
27
  export type JoinIdentityParams = {
26
28
  identityKey: PublicKey;
29
+ deviceKey: PublicKey;
27
30
  haloSpaceKey: PublicKey;
28
31
  haloGenesisFeedKey: PublicKey;
32
+ controlFeedKey: PublicKey;
33
+ dataFeedKey: PublicKey;
29
34
  };
30
35
 
31
36
  export type CreateIdentityOptions = {
@@ -43,6 +48,7 @@ export class IdentityManager {
43
48
  constructor(
44
49
  private readonly _metadataStore: MetadataStore,
45
50
  private readonly _keyring: Keyring,
51
+ private readonly _feedStore: FeedStore<FeedMessage>,
46
52
  private readonly _spaceManager: SpaceManager
47
53
  ) {}
48
54
 
@@ -113,8 +119,7 @@ export class IdentityManager {
113
119
 
114
120
  for (const credential of credentials) {
115
121
  await identity.controlPipeline.writer.write({
116
- '@type': 'dxos.echo.feed.CredentialsMessage',
117
- credential
122
+ credential: { credential }
118
123
  });
119
124
  }
120
125
  }
@@ -141,12 +146,12 @@ export class IdentityManager {
141
146
 
142
147
  const identityRecord: IdentityRecord = {
143
148
  identityKey: params.identityKey,
144
- deviceKey: await this._keyring.createKey(),
149
+ deviceKey: params.deviceKey,
145
150
  haloSpace: {
146
151
  spaceKey: params.haloSpaceKey,
147
152
  genesisFeedKey: params.haloGenesisFeedKey,
148
- writeControlFeedKey: await this._keyring.createKey(),
149
- writeDataFeedKey: await this._keyring.createKey()
153
+ writeControlFeedKey: params.controlFeedKey,
154
+ writeDataFeedKey: params.dataFeedKey
150
155
  }
151
156
  };
152
157
  const identity = await this._constructIdentity(identityRecord);
@@ -154,7 +159,9 @@ export class IdentityManager {
154
159
  await identity.open();
155
160
  this._identity = identity;
156
161
  await this._metadataStore.setIdentityRecord(identityRecord);
162
+ await this._identity.ready();
157
163
  this.stateUpdate.emit();
164
+ log('accepted identity', { identityKey: identity.identityKey, deviceKey: identity.deviceKey });
158
165
  return identity;
159
166
  }
160
167
 
@@ -162,17 +169,24 @@ export class IdentityManager {
162
169
  assert(!this._identity);
163
170
  log('constructing identity', { identityRecord });
164
171
 
172
+ // Must be created before the space so the feeds are writable.
173
+ const controlFeed = await this._feedStore.openFeed(identityRecord.haloSpace.writeControlFeedKey, {
174
+ writable: true
175
+ });
176
+ const dataFeed = await this._feedStore.openFeed(identityRecord.haloSpace.writeDataFeedKey, { writable: true });
177
+
165
178
  const space = await this._constructSpace({
166
179
  spaceRecord: identityRecord.haloSpace,
167
180
  swarmIdentity: {
168
181
  peerKey: identityRecord.deviceKey,
169
- credentialProvider: createHaloAuthProvider(
170
- createCredentialSignerWithKey(this._keyring, identityRecord.deviceKey)
171
- ),
182
+ credentialProvider: createAuthProvider(createCredentialSignerWithKey(this._keyring, identityRecord.deviceKey)),
172
183
  credentialAuthenticator: deferFunction(() => identity.authVerifier.verifier)
173
184
  },
174
185
  identityKey: identityRecord.identityKey
175
186
  });
187
+ space.setControlFeed(controlFeed);
188
+ space.setDataFeed(dataFeed);
189
+
176
190
  const identity: Identity = new Identity({
177
191
  space,
178
192
  signer: this._keyring,
@@ -188,9 +202,7 @@ export class IdentityManager {
188
202
  return this._spaceManager.constructSpace({
189
203
  metadata: {
190
204
  key: spaceRecord.spaceKey,
191
- genesisFeedKey: spaceRecord.genesisFeedKey,
192
- controlFeedKey: spaceRecord.writeControlFeedKey,
193
- dataFeedKey: spaceRecord.writeDataFeedKey
205
+ genesisFeedKey: spaceRecord.genesisFeedKey
194
206
  },
195
207
  swarmIdentity,
196
208
  onNetworkConnection: () => {}