@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
@@ -5,7 +5,7 @@
5
5
  import expect from 'expect';
6
6
 
7
7
  import { CredentialGenerator, verifyCredential } from '@dxos/credentials';
8
- import { MOCK_AUTH_PROVIDER, MOCK_AUTH_VERIFIER, Space, SpaceProtocol, valueEncoding } from '@dxos/echo-db';
8
+ import { MOCK_AUTH_PROVIDER, MOCK_AUTH_VERIFIER, Space, SpaceProtocol, valueEncoding } from '@dxos/echo-pipeline';
9
9
  import { FeedFactory, FeedStore } from '@dxos/feed-store';
10
10
  import { Keyring } from '@dxos/keyring';
11
11
  import { PublicKey } from '@dxos/keys';
@@ -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,16 +14,17 @@ import {
14
14
  CredentialConsumer
15
15
  } from '@dxos/credentials';
16
16
  import { Signer } from '@dxos/crypto';
17
- import { Space } from '@dxos/echo-db';
17
+ import { failUndefined } from '@dxos/debug';
18
+ import { Space } from '@dxos/echo-pipeline';
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';
25
26
 
26
- import { HaloAuthVerifier } from './authenticator';
27
+ import { TrustedKeySetAuthVerifier } from './authenticator';
27
28
 
28
29
  /**
29
30
  * Timeout for the device to be added to the trusted set during auth.
@@ -45,7 +46,7 @@ export class Identity {
45
46
  private readonly _signer: Signer;
46
47
  private readonly _deviceStateMachine: CredentialConsumer<DeviceStateMachine>;
47
48
  private readonly _profileStateMachine: CredentialConsumer<ProfileStateMachine>;
48
- public readonly authVerifier: HaloAuthVerifier;
49
+ public readonly authVerifier: TrustedKeySetAuthVerifier;
49
50
 
50
51
  public readonly identityKey: PublicKey;
51
52
  public readonly deviceKey: PublicKey;
@@ -73,8 +74,8 @@ export class Identity {
73
74
  })
74
75
  );
75
76
 
76
- this.authVerifier = new HaloAuthVerifier({
77
- trustedDevicesProvider: () => this.authorizedDeviceKeys,
77
+ this.authVerifier = new TrustedKeySetAuthVerifier({
78
+ trustedKeysProvider: () => this.authorizedDeviceKeys,
78
79
  update: this.stateUpdate,
79
80
  authTimeout: AUTH_TIMEOUT
80
81
  });
@@ -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
@@ -7,14 +7,15 @@ import assert from 'node:assert';
7
7
 
8
8
  import { asyncChain, latch, Trigger } from '@dxos/async';
9
9
  import { raise } from '@dxos/debug';
10
+ import { testLocalDatabase } from '@dxos/echo-pipeline/testing';
10
11
  import { PublicKey } from '@dxos/keys';
11
- import { ObjectModel } from '@dxos/object-model';
12
12
  import { Invitation } from '@dxos/protocols/proto/dxos/client/services';
13
- import { describe, test, afterTest } from '@dxos/test';
13
+ import { afterTest, describe, test } from '@dxos/test';
14
14
  import { range } from '@dxos/util';
15
15
 
16
16
  import { ServiceContext } from '../services';
17
17
  import { createIdentity, createPeers, syncItems } from '../testing';
18
+ import { performInvitation } from '../testing/invitation-utils';
18
19
 
19
20
  const closeAfterTest = async (peer: ServiceContext) => {
20
21
  afterTest(() => peer.close());
@@ -26,7 +27,6 @@ describe('services/space-invitations-handler', () => {
26
27
  const [peer] = await asyncChain<ServiceContext>([createIdentity, closeAfterTest])(createPeers(1));
27
28
 
28
29
  const space = await peer.dataSpaceManager!.createSpace();
29
- expect(space.database).not.to.be.undefined;
30
30
  expect(peer.dataSpaceManager!.spaces.has(space.key)).to.be.true;
31
31
  await space.close();
32
32
  });
@@ -34,18 +34,30 @@ describe('services/space-invitations-handler', () => {
34
34
  test('genesis with database mutations', async () => {
35
35
  const [peer] = await asyncChain<ServiceContext>([createIdentity, closeAfterTest])(createPeers(1));
36
36
  const space = await peer.dataSpaceManager!.createSpace();
37
+ afterTest(() => space.close());
37
38
 
38
- {
39
- const item = await space.database!.createItem<ObjectModel>({ type: 'test' });
40
- await item.model.set('name', 'test');
41
- }
39
+ await testLocalDatabase(space.dataPipelineController);
40
+ });
41
+
42
+ test('invitation with no auth', async () => {
43
+ const [host, guest] = await asyncChain<ServiceContext>([createIdentity, closeAfterTest])(createPeers(2));
44
+
45
+ const space1 = await host.dataSpaceManager!.createSpace();
46
+ const spaceKey = space1.key;
47
+
48
+ await performInvitation(host.spaceInvitations!, guest.spaceInvitations!, space1);
42
49
 
43
50
  {
44
- const [item] = space.database!.select({ type: 'test' }).exec().entities;
45
- expect(item.model.get('name')).to.eq('test');
46
- }
51
+ const space1 = host.dataSpaceManager!.spaces.get(spaceKey)!;
52
+ const space2 = guest.dataSpaceManager!.spaces.get(spaceKey)!;
53
+ expect(space1).not.to.be.undefined;
54
+ expect(space2).not.to.be.undefined;
47
55
 
48
- await space.close();
56
+ await syncItems(space1.dataPipelineController, space2.dataPipelineController);
57
+
58
+ await space1.close();
59
+ await space2.close();
60
+ }
49
61
  });
50
62
 
51
63
  test('creates and accepts invitation with retry', async () => {
@@ -79,8 +91,8 @@ describe('services/space-invitations-handler', () => {
79
91
  complete2.wake(invitation.spaceKey!);
80
92
  },
81
93
  onCancelled: () => raise(new Error()),
82
- onTimeout: (err: Error) => raise(new Error(err.message)),
83
- onError: (err: Error) => raise(new Error(err.message))
94
+ onTimeout: (err: Error) => raise(err),
95
+ onError: (err: Error) => raise(err)
84
96
  });
85
97
  },
86
98
  onConnected: (invitation: Invitation) => {
@@ -91,8 +103,8 @@ describe('services/space-invitations-handler', () => {
91
103
  complete1.wake(invitation.spaceKey!);
92
104
  },
93
105
  onCancelled: () => raise(new Error()),
94
- onTimeout: (err: Error) => raise(new Error(err.message)),
95
- onError: (err: Error) => raise(new Error(err.message))
106
+ onTimeout: (err: Error) => raise(err),
107
+ onError: (err: Error) => raise(err)
96
108
  });
97
109
 
98
110
  const [spaceKey1, spaceKey2] = await Promise.all([complete1.wait(), complete2.wait()]);
@@ -104,11 +116,15 @@ describe('services/space-invitations-handler', () => {
104
116
  expect(space1).not.to.be.undefined;
105
117
  expect(space2).not.to.be.undefined;
106
118
 
107
- await syncItems(space1, space2);
119
+ await syncItems(space1.dataPipelineController, space2.dataPipelineController);
108
120
 
109
121
  await space1.close();
110
122
  await space2.close();
111
123
  }
124
+
125
+ expect(
126
+ guest.identityManager.identity?.space.spaceState.getCredentialsOfType('dxos.halo.credentials.SpaceMember').length
127
+ ).to.equal(2); // own halo + newly joined space.
112
128
  });
113
129
 
114
130
  test('cancels invitation', async () => {
@@ -133,8 +149,8 @@ describe('services/space-invitations-handler', () => {
133
149
  onConnected: async (invitation2: Invitation) => {},
134
150
  onSuccess: () => {},
135
151
  onCancelled: () => raise(new Error()),
136
- onTimeout: (err: Error) => raise(new Error(err.message)),
137
- onError: (err: Error) => raise(new Error(err.message))
152
+ onTimeout: (err: Error) => raise(err),
153
+ onError: (err: Error) => raise(err)
138
154
  });
139
155
  },
140
156
  onConnected: async (invitation1: Invitation) => {},
@@ -142,13 +158,13 @@ describe('services/space-invitations-handler', () => {
142
158
  cancelled.wake();
143
159
  },
144
160
  onSuccess: () => raise(new Error()),
145
- onTimeout: (err: Error) => raise(new Error(err.message)),
146
- onError: (err: Error) => raise(new Error(err.message))
161
+ onTimeout: (err: Error) => raise(err),
162
+ onError: (err: Error) => raise(err)
147
163
  });
148
164
 
149
165
  const invitation1 = await connecting1.wait();
150
166
  const invitation2 = await connecting2.wait();
151
- expect(invitation1.swarmKey).to.eq(invitation2.swarmKey);
167
+ expect(invitation1.swarmKey).to.eq(invitation2.swarmKey); // TODO(burdon): Normalize to use to.deep.eq.
152
168
 
153
169
  // TODO(burdon): Simulate network latency.
154
170
  setTimeout(async () => {
@@ -159,7 +175,8 @@ describe('services/space-invitations-handler', () => {
159
175
  await space1.close();
160
176
  });
161
177
 
162
- test('test multi-use invitation', async () => {
178
+ // TODO(burdon): Flaky.
179
+ test.skip('test multi-use invitation', async () => {
163
180
  const GUEST_COUNT = 3;
164
181
  const [host, ...guests] = await asyncChain<ServiceContext>([createIdentity, closeAfterTest])(
165
182
  createPeers(GUEST_COUNT + 1)
@@ -180,8 +197,8 @@ describe('services/space-invitations-handler', () => {
180
197
  count();
181
198
  },
182
199
  onCancelled: () => {},
183
- onTimeout: (err: Error) => raise(new Error(err.message)),
184
- onError: (err: Error) => raise(new Error(err.message))
200
+ onTimeout: (err: Error) => raise(err),
201
+ onError: (err: Error) => raise(err)
185
202
  });
186
203
 
187
204
  await Promise.all(
@@ -198,8 +215,8 @@ describe('services/space-invitations-handler', () => {
198
215
  success.wake();
199
216
  },
200
217
  onCancelled: () => raise(new Error()),
201
- onTimeout: (err: Error) => raise(new Error(err.message)),
202
- onError: (err: Error) => raise(new Error(err.message))
218
+ onTimeout: (err: Error) => raise(err),
219
+ onError: (err: Error) => raise(err)
203
220
  });
204
221
  await success.wait({ timeout: 300 });
205
222
  })
@@ -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 { createAdmissionCredentials, generatePasscode, getCredentialAssertion } from '@dxos/credentials';
10
- import { SigningContext } from '@dxos/echo-db';
10
+ import { SigningContext } from '@dxos/echo-pipeline';
11
11
  import { writeMessages } from '@dxos/feed-store';
12
12
  import { Keyring } from '@dxos/keyring';
13
13
  import { PublicKey } from '@dxos/keys';
@@ -15,6 +15,7 @@ import { log } from '@dxos/log';
15
15
  import { createTeleportProtocolFactory, NetworkManager, StarTopology, SwarmConnection } from '@dxos/network-manager';
16
16
  import { schema } from '@dxos/protocols';
17
17
  import { Invitation } from '@dxos/protocols/proto/dxos/client/services';
18
+ import { FeedMessage } from '@dxos/protocols/proto/dxos/echo/feed';
18
19
  import { ProfileDocument } from '@dxos/protocols/proto/dxos/halo/credentials';
19
20
  import {
20
21
  AuthenticationRequest,
@@ -137,20 +138,17 @@ export class SpaceInvitationsHandler extends AbstractInvitationsHandler<DataSpac
137
138
 
138
139
  log('writing guest credentials', { host: this._signingContext.deviceKey, guest: deviceKey });
139
140
  // TODO(burdon): Check if already admitted.
140
- const credentials = await createAdmissionCredentials(
141
+ const credentials: FeedMessage.Payload[] = await createAdmissionCredentials(
141
142
  this._signingContext.credentialSigner,
142
143
  identityKey,
143
- deviceKey,
144
144
  space.key,
145
- controlFeedKey,
146
- dataFeedKey,
147
145
  space.inner.genesisFeedKey,
148
146
  guestProfile
149
147
  );
150
148
 
151
149
  // TODO(dmaretskyi): Refactor.
152
- assert(credentials[0]['@type'] === 'dxos.echo.feed.CredentialsMessage');
153
- const spaceMemberCredential = credentials[0].credential;
150
+ assert(credentials[0].credential);
151
+ const spaceMemberCredential = credentials[0].credential.credential;
154
152
  assert(getCredentialAssertion(spaceMemberCredential)['@type'] === 'dxos.halo.credentials.SpaceMember');
155
153
 
156
154
  await writeMessages(space.inner.controlPipeline.writer, credentials);
@@ -295,8 +293,6 @@ export class SpaceInvitationsHandler extends AbstractInvitationsHandler<DataSpac
295
293
  dataFeedKey
296
294
  });
297
295
 
298
- // TODO(dmaretskyi): Record credential in our HALO.
299
-
300
296
  // 4. Create local space.
301
297
  const assertion = getCredentialAssertion(credential);
302
298
  assert(assertion['@type'] === 'dxos.halo.credentials.SpaceMember', 'Invalid credential');
@@ -304,11 +300,12 @@ export class SpaceInvitationsHandler extends AbstractInvitationsHandler<DataSpac
304
300
 
305
301
  const space = await this._spaceManager.acceptSpace({
306
302
  spaceKey: assertion.spaceKey,
307
- genesisFeedKey: assertion.genesisFeedKey,
308
- controlFeedKey,
309
- dataFeedKey
303
+ genesisFeedKey: assertion.genesisFeedKey
310
304
  });
311
305
 
306
+ // Record credential in our HALO.
307
+ await this._signingContext.recordCredential(credential);
308
+
312
309
  // 5. Success.
313
310
  log('admitted by host', { guest: this._signingContext.deviceKey, spaceKey: space.key });
314
311
  complete.wake();
@@ -0,0 +1,56 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ import { MemorySignalManagerContext } from '@dxos/messaging';
6
+ import { describe, test } from '@dxos/test';
7
+
8
+ import { createServiceContext, syncItems } from '../testing';
9
+ import { performInvitation } from '../testing/invitation-utils';
10
+
11
+ describe('services/ServiceContext', () => {
12
+ test('existing space is synchronized on device invitations', async () => {
13
+ const networkContext = new MemorySignalManagerContext();
14
+ const device1 = createServiceContext({ signalContext: networkContext });
15
+ await device1.createIdentity();
16
+ const space1 = await device1.dataSpaceManager!.createSpace();
17
+
18
+ const device2 = createServiceContext({ signalContext: networkContext });
19
+ await performInvitation(device1.haloInvitations, device2.haloInvitations, undefined);
20
+
21
+ const space2 = await device2.dataSpaceManager!.spaces.get(space1.key);
22
+ await syncItems(space1.dataPipelineController, space2!.dataPipelineController);
23
+ });
24
+
25
+ test('new space is synchronized on device invitations', async () => {
26
+ const networkContext = new MemorySignalManagerContext();
27
+ const device1 = createServiceContext({ signalContext: networkContext });
28
+ await device1.createIdentity();
29
+
30
+ const device2 = createServiceContext({ signalContext: networkContext });
31
+ await performInvitation(device1.haloInvitations, device2.haloInvitations, undefined);
32
+
33
+ const space1 = await device1.dataSpaceManager!.createSpace();
34
+ await device2.dataSpaceManager?.updated.waitForCondition(() => !!device2.dataSpaceManager!.spaces.get(space1.key));
35
+ const space2 = await device2.dataSpaceManager!.spaces.get(space1.key);
36
+ await syncItems(space1.dataPipelineController, space2!.dataPipelineController);
37
+ });
38
+
39
+ test('joined space is synchronized on device invitations', async () => {
40
+ const networkContext = new MemorySignalManagerContext();
41
+ const device1 = createServiceContext({ signalContext: networkContext });
42
+ await device1.createIdentity();
43
+
44
+ const device2 = createServiceContext({ signalContext: networkContext });
45
+ await performInvitation(device1.haloInvitations, device2.haloInvitations, undefined);
46
+
47
+ const identity2 = createServiceContext({ signalContext: networkContext });
48
+ await identity2.createIdentity();
49
+ const space1 = await identity2.dataSpaceManager!.createSpace();
50
+ await performInvitation(identity2.spaceInvitations!, device1.spaceInvitations!, space1);
51
+
52
+ await device2.dataSpaceManager?.updated.waitForCondition(() => !!device2.dataSpaceManager!.spaces.get(space1.key));
53
+ const space2 = await device2.dataSpaceManager!.spaces.get(space1.key);
54
+ await syncItems(space1.dataPipelineController, space2!.dataPipelineController);
55
+ });
56
+ });