@dxos/client-services 0.1.23 → 0.1.24

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 (115) hide show
  1. package/dist/lib/browser/chunk-R2FEBB66.mjs +3865 -0
  2. package/dist/lib/browser/chunk-R2FEBB66.mjs.map +7 -0
  3. package/dist/lib/browser/index.mjs +56 -3066
  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 +1401 -645
  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 +3688 -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 +1 -1
  25. package/dist/types/src/packlets/identity/authenticator.d.ts.map +1 -1
  26. package/dist/types/src/packlets/identity/identity-manager.d.ts +7 -1
  27. package/dist/types/src/packlets/identity/identity-manager.d.ts.map +1 -1
  28. package/dist/types/src/packlets/identity/identity.d.ts +1 -0
  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.map +1 -1
  35. package/dist/types/src/packlets/services/service-context.d.ts +2 -0
  36. package/dist/types/src/packlets/services/service-context.d.ts.map +1 -1
  37. package/dist/types/src/packlets/services/service-context.test.d.ts +2 -0
  38. package/dist/types/src/packlets/services/service-context.test.d.ts.map +1 -0
  39. package/dist/types/src/packlets/services/service-definitions.d.ts +1 -1
  40. package/dist/types/src/packlets/services/service-definitions.d.ts.map +1 -1
  41. package/dist/types/src/packlets/services/service-host.d.ts +9 -6
  42. package/dist/types/src/packlets/services/service-host.d.ts.map +1 -1
  43. package/dist/types/src/packlets/services/service-registry.d.ts +4 -3
  44. package/dist/types/src/packlets/services/service-registry.d.ts.map +1 -1
  45. package/dist/types/src/packlets/spaces/data-space-manager.d.ts +9 -2
  46. package/dist/types/src/packlets/spaces/data-space-manager.d.ts.map +1 -1
  47. package/dist/types/src/packlets/spaces/data-space.d.ts +18 -4
  48. package/dist/types/src/packlets/spaces/data-space.d.ts.map +1 -1
  49. package/dist/types/src/packlets/spaces/notarization-plugin.d.ts +50 -0
  50. package/dist/types/src/packlets/spaces/notarization-plugin.d.ts.map +1 -0
  51. package/dist/types/src/packlets/spaces/notarization-plugin.test.d.ts +2 -0
  52. package/dist/types/src/packlets/spaces/notarization-plugin.test.d.ts.map +1 -0
  53. package/dist/types/src/packlets/spaces/spaces-service.d.ts +2 -1
  54. package/dist/types/src/packlets/spaces/spaces-service.d.ts.map +1 -1
  55. package/dist/types/src/packlets/testing/credential-utils.d.ts +8 -0
  56. package/dist/types/src/packlets/testing/credential-utils.d.ts.map +1 -0
  57. package/dist/types/src/packlets/testing/index.d.ts +2 -0
  58. package/dist/types/src/packlets/testing/index.d.ts.map +1 -1
  59. package/dist/types/src/packlets/testing/invitation-utils.d.ts +3 -0
  60. package/dist/types/src/packlets/testing/invitation-utils.d.ts.map +1 -0
  61. package/dist/types/src/packlets/testing/test-builder.d.ts +2 -2
  62. package/dist/types/src/packlets/testing/test-builder.d.ts.map +1 -1
  63. package/dist/types/src/packlets/vault/iframe-host-runtime.d.ts +28 -0
  64. package/dist/types/src/packlets/vault/iframe-host-runtime.d.ts.map +1 -0
  65. package/dist/types/src/packlets/vault/{iframe-runtime.d.ts → iframe-proxy-runtime.d.ts} +4 -4
  66. package/dist/types/src/packlets/vault/iframe-proxy-runtime.d.ts.map +1 -0
  67. package/dist/types/src/packlets/vault/index.d.ts +4 -1
  68. package/dist/types/src/packlets/vault/index.d.ts.map +1 -1
  69. package/dist/types/src/packlets/vault/vault-resource-lock.d.ts +19 -0
  70. package/dist/types/src/packlets/vault/vault-resource-lock.d.ts.map +1 -0
  71. package/dist/types/src/packlets/vault/worker-runtime.d.ts.map +1 -1
  72. package/dist/types/src/packlets/vault/worker-session.d.ts +4 -4
  73. package/dist/types/src/packlets/vault/worker-session.d.ts.map +1 -1
  74. package/package.json +29 -29
  75. package/src/packlets/deprecated/profile.ts +20 -2
  76. package/src/packlets/deprecated/space.ts +41 -37
  77. package/src/packlets/deprecated/system.ts +26 -13
  78. package/src/packlets/devtools/devtools.ts +4 -4
  79. package/src/packlets/devtools/feeds.ts +11 -6
  80. package/src/packlets/devtools/network.ts +20 -9
  81. package/src/packlets/devtools/spaces.ts +7 -6
  82. package/src/packlets/identity/authenticator.ts +11 -6
  83. package/src/packlets/identity/identity-manager.test.ts +24 -15
  84. package/src/packlets/identity/identity-manager.ts +22 -8
  85. package/src/packlets/identity/identity.test.ts +21 -22
  86. package/src/packlets/identity/identity.ts +10 -5
  87. package/src/packlets/invitations/halo-invitations-handler.test.ts +4 -45
  88. package/src/packlets/invitations/halo-invitations-handler.ts +33 -17
  89. package/src/packlets/invitations/invitations.ts +2 -1
  90. package/src/packlets/invitations/space-invitations-handler.test.ts +43 -26
  91. package/src/packlets/invitations/space-invitations-handler.ts +8 -11
  92. package/src/packlets/services/service-context.test.ts +56 -0
  93. package/src/packlets/services/service-context.ts +60 -4
  94. package/src/packlets/services/service-definitions.ts +1 -1
  95. package/src/packlets/services/service-host.test.ts +74 -5
  96. package/src/packlets/services/service-host.ts +85 -23
  97. package/src/packlets/services/service-registry.test.ts +2 -1
  98. package/src/packlets/services/service-registry.ts +5 -1
  99. package/src/packlets/spaces/data-space-manager.test.ts +71 -14
  100. package/src/packlets/spaces/data-space-manager.ts +49 -8
  101. package/src/packlets/spaces/data-space.ts +113 -14
  102. package/src/packlets/spaces/notarization-plugin.test.ts +81 -0
  103. package/src/packlets/spaces/notarization-plugin.ts +211 -0
  104. package/src/packlets/spaces/spaces-service.ts +9 -1
  105. package/src/packlets/testing/credential-utils.ts +28 -0
  106. package/src/packlets/testing/index.ts +2 -0
  107. package/src/packlets/testing/invitation-utils.ts +44 -0
  108. package/src/packlets/testing/test-builder.ts +14 -18
  109. package/src/packlets/vault/iframe-host-runtime.ts +112 -0
  110. package/src/packlets/vault/{iframe-runtime.ts → iframe-proxy-runtime.ts} +3 -3
  111. package/src/packlets/vault/index.ts +4 -1
  112. package/src/packlets/vault/vault-resource-lock.ts +76 -0
  113. package/src/packlets/vault/worker-runtime.ts +13 -3
  114. package/src/packlets/vault/worker-session.ts +17 -17
  115. package/dist/types/src/packlets/vault/iframe-runtime.d.ts.map +0 -1
@@ -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 }) =>
@@ -9,8 +9,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
+ };
@@ -42,17 +42,15 @@ export type TrustedKeySetAuthVerifierParams = {
42
42
  export class TrustedKeySetAuthVerifier {
43
43
  private _ctx = new Context();
44
44
 
45
- constructor(private readonly _params: TrustedKeySetAuthVerifierParams) {}
45
+ // prettier-ignore
46
+ constructor(
47
+ private readonly _params: TrustedKeySetAuthVerifierParams
48
+ ) {}
46
49
 
47
50
  async close() {
48
51
  await this._ctx.dispose();
49
52
  }
50
53
 
51
- private _isTrustedKey(deviceKey: PublicKey) {
52
- const deviceSet = this._params.trustedKeysProvider();
53
- return deviceSet.has(deviceKey);
54
- }
55
-
56
54
  get verifier(): AuthVerifier {
57
55
  return async (nonce, auth) => {
58
56
  const credential = schema.getCodecForType('dxos.halo.credentials.Credential').decode(auth);
@@ -78,6 +76,7 @@ export class TrustedKeySetAuthVerifier {
78
76
  this._ctx.onDispose(() => {
79
77
  trigger.wake(false);
80
78
  });
79
+
81
80
  const clear = this._params.update.on(this._ctx, () => {
82
81
  if (this._isTrustedKey(credential.issuer)) {
83
82
  log('auth success', { key: credential.issuer });
@@ -86,6 +85,7 @@ export class TrustedKeySetAuthVerifier {
86
85
  log('key is not currently in trusted set, waiting...', { key: credential.issuer });
87
86
  }
88
87
  });
88
+
89
89
  try {
90
90
  return await trigger.wait({ timeout: this._params.authTimeout });
91
91
  } catch {
@@ -95,4 +95,9 @@ export class TrustedKeySetAuthVerifier {
95
95
  }
96
96
  };
97
97
  }
98
+
99
+ private _isTrustedKey(deviceKey: PublicKey) {
100
+ const deviceSet = this._params.trustedKeysProvider();
101
+ return deviceSet.has(deviceKey);
102
+ }
98
103
  }
@@ -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.
@@ -7,9 +7,11 @@ import assert from 'node:assert';
7
7
  import { Event } from '@dxos/async';
8
8
  import { createCredentialSignerWithKey, CredentialGenerator } from '@dxos/credentials';
9
9
  import { MetadataStore, SpaceManager, SwarmIdentity } from '@dxos/echo-db';
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
 
@@ -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,6 +169,12 @@ 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: {
@@ -171,6 +184,9 @@ export class IdentityManager {
171
184
  },
172
185
  identityKey: identityRecord.identityKey
173
186
  });
187
+ space.setControlFeed(controlFeed);
188
+ space.setDataFeed(dataFeed);
189
+
174
190
  const identity: Identity = new Identity({
175
191
  space,
176
192
  signer: this._keyring,
@@ -186,9 +202,7 @@ export class IdentityManager {
186
202
  return this._spaceManager.constructSpace({
187
203
  metadata: {
188
204
  key: spaceRecord.spaceKey,
189
- genesisFeedKey: spaceRecord.genesisFeedKey,
190
- controlFeedKey: spaceRecord.writeControlFeedKey,
191
- dataFeedKey: spaceRecord.writeDataFeedKey
205
+ genesisFeedKey: spaceRecord.genesisFeedKey
192
206
  },
193
207
  swarmIdentity,
194
208
  onNetworkConnection: () => {}
@@ -60,10 +60,10 @@ describe('identity/identity', () => {
60
60
  spaceKey,
61
61
  protocol,
62
62
  genesisFeed: controlFeed,
63
- controlFeed,
64
- dataFeed,
65
63
  feedProvider: (feedKey) => feedStore.openFeed(feedKey)
66
- });
64
+ })
65
+ .setControlFeed(controlFeed)
66
+ .setDataFeed(dataFeed);
67
67
 
68
68
  const identity = new Identity({
69
69
  signer: keyring,
@@ -88,8 +88,7 @@ describe('identity/identity', () => {
88
88
 
89
89
  for (const credential of credentials) {
90
90
  await identity.controlPipeline.writer.write({
91
- '@type': 'dxos.echo.feed.CredentialsMessage',
92
- credential
91
+ credential: { credential }
93
92
  });
94
93
  }
95
94
  }
@@ -166,10 +165,10 @@ describe('identity/identity', () => {
166
165
  spaceKey,
167
166
  protocol,
168
167
  genesisFeed: controlFeed,
169
- controlFeed,
170
- dataFeed,
171
168
  feedProvider: (feedKey) => feedStore.openFeed(feedKey)
172
- });
169
+ })
170
+ .setControlFeed(controlFeed)
171
+ .setDataFeed(dataFeed);
173
172
 
174
173
  const identity = (identity1 = new Identity({
175
174
  signer: keyring,
@@ -194,8 +193,7 @@ describe('identity/identity', () => {
194
193
 
195
194
  for (const credential of credentials) {
196
195
  await identity.controlPipeline.writer.write({
197
- '@type': 'dxos.echo.feed.CredentialsMessage',
198
- credential
196
+ credential: { credential }
199
197
  });
200
198
  }
201
199
  }
@@ -246,10 +244,10 @@ describe('identity/identity', () => {
246
244
  spaceKey,
247
245
  protocol,
248
246
  genesisFeed: await feedStore.openFeed(genesisFeedKey),
249
- controlFeed,
250
- dataFeed,
251
247
  feedProvider: (feedKey) => feedStore.openFeed(feedKey)
252
- });
248
+ })
249
+ .setControlFeed(controlFeed)
250
+ .setDataFeed(dataFeed);
253
251
 
254
252
  const identity = (identity2 = new Identity({
255
253
  signer: keyring,
@@ -268,15 +266,16 @@ describe('identity/identity', () => {
268
266
  {
269
267
  const signer = identity1.getIdentityCredentialSigner();
270
268
  void identity1.controlPipeline.writer.write({
271
- '@type': 'dxos.echo.feed.CredentialsMessage',
272
- credential: await signer.createCredential({
273
- subject: identity2.deviceKey,
274
- assertion: {
275
- '@type': 'dxos.halo.credentials.AuthorizedDevice',
276
- identityKey: identity1.identityKey,
277
- deviceKey: identity2.deviceKey
278
- }
279
- })
269
+ credential: {
270
+ credential: await signer.createCredential({
271
+ subject: identity2.deviceKey,
272
+ assertion: {
273
+ '@type': 'dxos.halo.credentials.AuthorizedDevice',
274
+ identityKey: identity1.identityKey,
275
+ deviceKey: identity2.deviceKey
276
+ }
277
+ })
278
+ }
280
279
  });
281
280
 
282
281
  await identity2.ready();
@@ -14,11 +14,12 @@ import {
14
14
  CredentialConsumer
15
15
  } from '@dxos/credentials';
16
16
  import { Signer } from '@dxos/crypto';
17
+ import { failUndefined } from '@dxos/debug';
17
18
  import { Space } from '@dxos/echo-db';
18
19
  import { writeMessages } from '@dxos/feed-store';
19
20
  import { PublicKey } from '@dxos/keys';
20
21
  import { log } from '@dxos/log';
21
- import { TypedMessage } from '@dxos/protocols';
22
+ import { FeedMessage } from '@dxos/protocols/proto/dxos/echo/feed';
22
23
  import { AdmittedFeed, ProfileDocument } from '@dxos/protocols/proto/dxos/halo/credentials';
23
24
  import { HaloAdmissionCredentials } from '@dxos/protocols/proto/dxos/halo/invitations';
24
25
  import { ComplexSet } from '@dxos/util';
@@ -101,7 +102,7 @@ export class Identity {
101
102
  async ready() {
102
103
  await this._deviceStateMachine.processor.deviceChainReady.wait();
103
104
 
104
- // TODO(dmaretskyi): Should we also wait for our feeds to be admitted?
105
+ await this.controlPipeline.state.waitUntilReachedTargetTimeframe({ timeout: 3_000 });
105
106
  }
106
107
 
107
108
  get profileDocument(): ProfileDocument | undefined {
@@ -123,11 +124,15 @@ export class Identity {
123
124
  return this.space.genesisFeedKey;
124
125
  }
125
126
 
127
+ get deviceCredentialChain() {
128
+ return this._deviceStateMachine.processor.deviceCredentialChain;
129
+ }
130
+
126
131
  getAdmissionCredentials(): HaloAdmissionCredentials {
127
132
  return {
128
133
  deviceKey: this.deviceKey,
129
- controlFeedKey: this.space.controlFeedKey,
130
- dataFeedKey: this.space.dataFeedKey
134
+ controlFeedKey: this.space.controlFeedKey ?? failUndefined(),
135
+ dataFeedKey: this.space.dataFeedKey ?? failUndefined()
131
136
  };
132
137
  }
133
138
 
@@ -191,7 +196,7 @@ export class Identity {
191
196
  designation: AdmittedFeed.Designation.DATA
192
197
  }
193
198
  })
194
- ].map((credential): TypedMessage => ({ '@type': 'dxos.echo.feed.CredentialsMessage', credential }))
199
+ ].map((credential): FeedMessage.Payload => ({ credential: { credential } }))
195
200
  );
196
201
  }
197
202
  }
@@ -3,16 +3,13 @@
3
3
  //
4
4
 
5
5
  import { expect } from 'chai';
6
- import assert from 'node:assert';
7
6
 
8
- import { asyncChain, Trigger } from '@dxos/async';
9
- import { raise } from '@dxos/debug';
10
- import { PublicKey } from '@dxos/keys';
11
- import { Invitation } from '@dxos/protocols/proto/dxos/client/services';
7
+ import { asyncChain } from '@dxos/async';
12
8
  import { describe, test, afterTest } from '@dxos/test';
13
9
 
14
10
  import { ServiceContext } from '../services';
15
11
  import { createPeers, createServiceContext } from '../testing';
12
+ import { performInvitation } from '../testing/invitation-utils';
16
13
 
17
14
  const closeAfterTest = async (peer: ServiceContext) => {
18
15
  afterTest(() => peer.close());
@@ -35,45 +32,7 @@ describe('services/halo', () => {
35
32
  const identity1 = await host.createIdentity();
36
33
  expect(host.identityManager.identity).to.eq(identity1);
37
34
 
38
- const observable1 = await host.haloInvitations.createInvitation();
39
-
40
- const complete1 = new Trigger<PublicKey>();
41
- const complete2 = new Trigger<PublicKey>();
42
-
43
- const authenticationCode = new Trigger<string>();
44
-
45
- observable1.subscribe({
46
- onConnecting: async (invitation1: Invitation) => {
47
- const observable2 = guest.haloInvitations!.acceptInvitation(invitation1);
48
- observable2.subscribe({
49
- onConnecting: async () => {},
50
- onConnected: async (invitation2: Invitation) => {
51
- expect(invitation1.swarmKey).to.eq(invitation2.swarmKey);
52
- },
53
- onAuthenticating: async () => {
54
- await observable2.authenticate(await authenticationCode.wait());
55
- },
56
- onSuccess: (invitation: Invitation) => {
57
- complete2.wake(invitation.identityKey!);
58
- },
59
- onCancelled: () => raise(new Error()),
60
- onTimeout: (err: Error) => raise(new Error(err.message)),
61
- onError: (err: Error) => raise(new Error(err.message))
62
- });
63
- },
64
- onConnected: (invitation: Invitation) => {
65
- assert(invitation.authenticationCode);
66
- authenticationCode.wake(invitation.authenticationCode);
67
- },
68
- onSuccess: (invitation: Invitation) => {
69
- complete1.wake(host.identityManager.identity!.identityKey);
70
- },
71
- onCancelled: () => raise(new Error()),
72
- onTimeout: (err: Error) => raise(new Error(err.message)),
73
- onError: (err: Error) => raise(new Error(err.message))
74
- });
75
-
76
- const [identityKey1, identityKey2] = await Promise.all([complete1.wait(), complete2.wait()]);
77
- expect(identityKey1).to.deep.eq(identityKey2);
35
+ await performInvitation(host.haloInvitations, guest.haloInvitations, undefined);
36
+ expect(guest.identityManager.identity?.identityKey).to.deep.eq(identity1.identityKey);
78
37
  });
79
38
  });
@@ -7,7 +7,7 @@ import assert from 'node:assert';
7
7
  import { scheduleTask, sleep, Trigger } from '@dxos/async';
8
8
  import { Context } from '@dxos/context';
9
9
  import { generatePasscode } from '@dxos/credentials';
10
- import { failUndefined } from '@dxos/debug';
10
+ import { Keyring } from '@dxos/keyring';
11
11
  import { PublicKey } from '@dxos/keys';
12
12
  import { log } from '@dxos/log';
13
13
  import { createTeleportProtocolFactory, NetworkManager, StarTopology } from '@dxos/network-manager';
@@ -22,7 +22,7 @@ import {
22
22
  } from '@dxos/protocols/proto/dxos/halo/invitations';
23
23
  import { ExtensionContext, RpcExtension } from '@dxos/teleport';
24
24
 
25
- import { IdentityManager } from '../identity';
25
+ import { Identity, JoinIdentityParams } from '../identity';
26
26
  import {
27
27
  AuthenticatingInvitationProvider,
28
28
  AUTHENTICATION_CODE_LENGTH,
@@ -33,6 +33,14 @@ import {
33
33
  } from './invitations';
34
34
  import { AbstractInvitationsHandler, InvitationsOptions } from './invitations-handler';
35
35
 
36
+ type HaloInvitationsHandlerParams = {
37
+ networkManager: NetworkManager;
38
+ keyring: Keyring;
39
+
40
+ getIdentity: () => Identity;
41
+ acceptIdentity: (identity: JoinIdentityParams) => Promise<Identity>;
42
+ };
43
+
36
44
  /**
37
45
  * Handles the life-cycle of Halo invitations between peers.
38
46
  */
@@ -40,10 +48,9 @@ import { AbstractInvitationsHandler, InvitationsOptions } from './invitations-ha
40
48
  export class HaloInvitationsHandler extends AbstractInvitationsHandler {
41
49
  // prettier-ignore
42
50
  constructor(
43
- networkManager: NetworkManager,
44
- private readonly _identityManager: IdentityManager
51
+ private readonly _params: HaloInvitationsHandlerParams
45
52
  ) {
46
- super(networkManager);
53
+ super(_params.networkManager);
47
54
  }
48
55
 
49
56
  /**
@@ -52,7 +59,7 @@ export class HaloInvitationsHandler extends AbstractInvitationsHandler {
52
59
  createInvitation(context: void, options?: InvitationsOptions): CancellableInvitationObservable {
53
60
  const { type, timeout = INVITATION_TIMEOUT, swarmKey } = options ?? {};
54
61
  assert(type !== Invitation.Type.OFFLINE);
55
- const identity = this._identityManager.identity ?? failUndefined();
62
+ const identity = this._params.getIdentity();
56
63
 
57
64
  // TODO(dmaretskyi): Add invitation kind: halo/space.
58
65
  const invitation: Invitation = {
@@ -78,8 +85,8 @@ export class HaloInvitationsHandler extends AbstractInvitationsHandler {
78
85
  const complete = new Trigger<PublicKey>();
79
86
 
80
87
  // Called for every connecting peer.
81
- const createExtension = (): HostHaloInvitationExtension => {
82
- const hostInvitationExtension = new HostHaloInvitationExtension({
88
+ const createExtension = () =>
89
+ new HostHaloInvitationExtension({
83
90
  requestAdmission: async () => {
84
91
  log('responding with admission offer', {
85
92
  host: identity.deviceKey
@@ -145,8 +152,6 @@ export class HaloInvitationsHandler extends AbstractInvitationsHandler {
145
152
  });
146
153
  }
147
154
  });
148
- return hostInvitationExtension;
149
- };
150
155
 
151
156
  scheduleTask(ctx, async () => {
152
157
  const topic = invitation.swarmKey!;
@@ -224,18 +229,29 @@ export class HaloInvitationsHandler extends AbstractInvitationsHandler {
224
229
  await extension.rpc.HaloHostService.authenticate({ authenticationCode });
225
230
  }
226
231
 
227
- // 3. Create local identity.
232
+ const deviceKey = await this._params.keyring.createKey();
233
+ const controlFeedKey = await this._params.keyring.createKey();
234
+ const dataFeedKey = await this._params.keyring.createKey();
235
+
236
+ // 3. Send admission credentials to host (with local identity keys).
237
+ log('presenting admission credentials', { identityKey, deviceKey, controlFeedKey, dataFeedKey });
238
+ await extension.rpc.HaloHostService.presentAdmissionCredentials({
239
+ deviceKey,
240
+ controlFeedKey,
241
+ dataFeedKey
242
+ });
243
+
244
+ // 4. Create local identity.
228
245
  // TODO(burdon): Abandon if does not complete (otherwise retry will fail).
229
- const identity = await this._identityManager.acceptIdentity({
246
+ const identity = await this._params.acceptIdentity({
230
247
  identityKey,
248
+ deviceKey,
231
249
  haloSpaceKey,
232
- haloGenesisFeedKey: genesisFeedKey
250
+ haloGenesisFeedKey: genesisFeedKey,
251
+ controlFeedKey,
252
+ dataFeedKey
233
253
  });
234
254
 
235
- // 4. Send admission credentials to host (with local identity keys).
236
- log('presenting admission credentials', { guest: identity.deviceKey, identityKey });
237
- await extension.rpc.HaloHostService.presentAdmissionCredentials(identity.getAdmissionCredentials());
238
-
239
255
  // 5. Success.
240
256
  log('admitted by host', { guest: identity.deviceKey, identityKey });
241
257
  complete.wake(identityKey);
@@ -18,7 +18,7 @@ export const AUTHENTICATION_CODE_LENGTH = 6;
18
18
  export const INVITATION_TIMEOUT = 3 * 60_000; // 3 mins.
19
19
 
20
20
  // TODO(burdon): Don't close until RPC has complete (bug).
21
- export const ON_CLOSE_DELAY = 500;
21
+ export const ON_CLOSE_DELAY = 1000;
22
22
 
23
23
  export interface InvitationsService {
24
24
  createInvitation(invitation: Invitation): Stream<Invitation>;
@@ -46,6 +46,7 @@ export interface CancellableInvitationObservable extends CancellableObservable<I
46
46
  get invitation(): Invitation | undefined;
47
47
  }
48
48
 
49
+ // TODO(wittjosiah): Update with Observable.value.
49
50
  export class InvitationObservableProvider
50
51
  extends CancellableObservableProvider<InvitationEvents>
51
52
  implements CancellableInvitationObservable