@dxos/client-services 0.6.11 → 0.6.12-main.568932b

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 (104) hide show
  1. package/dist/lib/browser/{chunk-QYVLLBAA.mjs → chunk-AHZ7ASHC.mjs} +5619 -5143
  2. package/dist/lib/browser/chunk-AHZ7ASHC.mjs.map +7 -0
  3. package/dist/lib/browser/index.mjs +3 -3
  4. package/dist/lib/browser/index.mjs.map +3 -3
  5. package/dist/lib/browser/meta.json +1 -1
  6. package/dist/lib/browser/testing/index.mjs +5 -6
  7. package/dist/lib/browser/testing/index.mjs.map +2 -2
  8. package/dist/lib/node/{chunk-F7G2TXVG.cjs → chunk-YJWFC43Y.cjs} +5391 -4915
  9. package/dist/lib/node/chunk-YJWFC43Y.cjs.map +7 -0
  10. package/dist/lib/node/index.cjs +46 -46
  11. package/dist/lib/node/index.cjs.map +3 -3
  12. package/dist/lib/node/meta.json +1 -1
  13. package/dist/lib/node/testing/index.cjs +11 -12
  14. package/dist/lib/node/testing/index.cjs.map +2 -2
  15. package/dist/lib/node-esm/chunk-7ADUGZEP.mjs +8192 -0
  16. package/dist/lib/node-esm/chunk-7ADUGZEP.mjs.map +7 -0
  17. package/dist/lib/node-esm/index.mjs +416 -0
  18. package/dist/lib/node-esm/index.mjs.map +7 -0
  19. package/dist/lib/node-esm/meta.json +1 -0
  20. package/dist/lib/node-esm/testing/index.mjs +418 -0
  21. package/dist/lib/node-esm/testing/index.mjs.map +7 -0
  22. package/dist/types/src/packlets/diagnostics/diagnostics-broadcast.d.ts.map +1 -1
  23. package/dist/types/src/packlets/identity/authenticator.d.ts.map +1 -1
  24. package/dist/types/src/packlets/identity/authenticator.node.test.d.ts +2 -0
  25. package/dist/types/src/packlets/identity/authenticator.node.test.d.ts.map +1 -0
  26. package/dist/types/src/packlets/identity/contacts-service.d.ts +1 -1
  27. package/dist/types/src/packlets/identity/contacts-service.d.ts.map +1 -1
  28. package/dist/types/src/packlets/identity/identity-manager.d.ts +19 -7
  29. package/dist/types/src/packlets/identity/identity-manager.d.ts.map +1 -1
  30. package/dist/types/src/packlets/identity/identity.d.ts +8 -1
  31. package/dist/types/src/packlets/identity/identity.d.ts.map +1 -1
  32. package/dist/types/src/packlets/invitations/invitation-host-extension.d.ts.map +1 -1
  33. package/dist/types/src/packlets/services/service-context.d.ts +7 -6
  34. package/dist/types/src/packlets/services/service-context.d.ts.map +1 -1
  35. package/dist/types/src/packlets/services/service-host.d.ts +1 -0
  36. package/dist/types/src/packlets/services/service-host.d.ts.map +1 -1
  37. package/dist/types/src/packlets/spaces/data-space-manager.d.ts +6 -3
  38. package/dist/types/src/packlets/spaces/data-space-manager.d.ts.map +1 -1
  39. package/dist/types/src/packlets/spaces/data-space.d.ts +4 -3
  40. package/dist/types/src/packlets/spaces/data-space.d.ts.map +1 -1
  41. package/dist/types/src/packlets/spaces/edge-feed-replicator.d.ts +3 -0
  42. package/dist/types/src/packlets/spaces/edge-feed-replicator.d.ts.map +1 -1
  43. package/dist/types/src/packlets/spaces/edge-feed-replicator.test.d.ts +2 -0
  44. package/dist/types/src/packlets/spaces/edge-feed-replicator.test.d.ts.map +1 -0
  45. package/dist/types/src/packlets/spaces/epoch-migrations.d.ts +1 -1
  46. package/dist/types/src/packlets/spaces/epoch-migrations.d.ts.map +1 -1
  47. package/dist/types/src/packlets/spaces/notarization-plugin.d.ts +31 -6
  48. package/dist/types/src/packlets/spaces/notarization-plugin.d.ts.map +1 -1
  49. package/dist/types/src/packlets/spaces/spaces-service.d.ts +1 -1
  50. package/dist/types/src/packlets/spaces/spaces-service.d.ts.map +1 -1
  51. package/dist/types/src/packlets/storage/storage.d.ts.map +1 -1
  52. package/dist/types/src/packlets/testing/test-builder.d.ts +1 -2
  53. package/dist/types/src/packlets/testing/test-builder.d.ts.map +1 -1
  54. package/dist/types/src/packlets/worker/worker-runtime.d.ts.map +1 -1
  55. package/dist/types/src/testing/setup.d.ts +3 -0
  56. package/dist/types/src/testing/setup.d.ts.map +1 -0
  57. package/dist/types/src/version.d.ts +1 -1
  58. package/dist/types/src/version.d.ts.map +1 -1
  59. package/package.json +43 -39
  60. package/src/packlets/devices/devices-service.test.ts +4 -5
  61. package/src/packlets/diagnostics/diagnostics-broadcast.ts +1 -0
  62. package/src/packlets/identity/{authenticator.test.ts → authenticator.node.test.ts} +2 -3
  63. package/src/packlets/identity/authenticator.ts +5 -2
  64. package/src/packlets/identity/contacts-service.ts +1 -1
  65. package/src/packlets/identity/identity-manager.test.ts +5 -6
  66. package/src/packlets/identity/identity-manager.ts +35 -19
  67. package/src/packlets/identity/identity-service.test.ts +4 -8
  68. package/src/packlets/identity/identity.test.ts +128 -239
  69. package/src/packlets/identity/identity.ts +42 -8
  70. package/src/packlets/invitations/device-invitation-protocol.test.ts +7 -4
  71. package/src/packlets/invitations/invitation-host-extension.ts +0 -3
  72. package/src/packlets/invitations/invitations-handler.test.ts +14 -7
  73. package/src/packlets/invitations/invitations-handler.ts +1 -1
  74. package/src/packlets/invitations/space-invitation-protocol.test.ts +4 -3
  75. package/src/packlets/logging/logging.test.ts +1 -2
  76. package/src/packlets/network/network-service.test.ts +2 -3
  77. package/src/packlets/services/service-context.test.ts +3 -1
  78. package/src/packlets/services/service-context.ts +64 -28
  79. package/src/packlets/services/service-host.test.ts +8 -12
  80. package/src/packlets/services/service-host.ts +8 -6
  81. package/src/packlets/services/service-registry.test.ts +1 -2
  82. package/src/packlets/spaces/data-space-manager.test.ts +2 -2
  83. package/src/packlets/spaces/data-space-manager.ts +38 -5
  84. package/src/packlets/spaces/data-space.ts +34 -6
  85. package/src/packlets/spaces/edge-feed-replicator.test.ts +251 -0
  86. package/src/packlets/spaces/edge-feed-replicator.ts +80 -22
  87. package/src/packlets/spaces/epoch-migrations.ts +2 -2
  88. package/src/packlets/spaces/notarization-plugin.test.ts +10 -7
  89. package/src/packlets/spaces/notarization-plugin.ts +169 -29
  90. package/src/packlets/spaces/spaces-service.test.ts +5 -9
  91. package/src/packlets/spaces/spaces-service.ts +6 -1
  92. package/src/packlets/storage/storage.ts +0 -1
  93. package/src/packlets/system/system-service.test.ts +1 -2
  94. package/src/packlets/testing/test-builder.ts +2 -3
  95. package/src/packlets/worker/worker-runtime.ts +2 -2
  96. package/src/testing/setup.ts +11 -0
  97. package/src/version.ts +1 -5
  98. package/dist/lib/browser/chunk-QYVLLBAA.mjs.map +0 -7
  99. package/dist/lib/node/chunk-F7G2TXVG.cjs.map +0 -7
  100. package/dist/types/src/packlets/identity/authenticator.test.d.ts +0 -2
  101. package/dist/types/src/packlets/identity/authenticator.test.d.ts.map +0 -1
  102. package/dist/types/src/packlets/services/automerge-host.test.d.ts +0 -2
  103. package/dist/types/src/packlets/services/automerge-host.test.d.ts.map +0 -1
  104. package/src/packlets/services/automerge-host.test.ts +0 -60
@@ -14,10 +14,12 @@ import {
14
14
  } from '@dxos/credentials';
15
15
  import { type Signer } from '@dxos/crypto';
16
16
  import { type Space } from '@dxos/echo-pipeline';
17
- import { writeMessages } from '@dxos/feed-store';
17
+ import { type EdgeConnection } from '@dxos/edge-client';
18
+ import { writeMessages, type FeedWrapper } from '@dxos/feed-store';
18
19
  import { invariant } from '@dxos/invariant';
19
20
  import { PublicKey, type SpaceId } from '@dxos/keys';
20
21
  import { log } from '@dxos/log';
22
+ import { type Runtime } from '@dxos/protocols/proto/dxos/config';
21
23
  import { type FeedMessage } from '@dxos/protocols/proto/dxos/echo/feed';
22
24
  import {
23
25
  AdmittedFeed,
@@ -32,6 +34,7 @@ import { type ComplexMap, ComplexSet } from '@dxos/util';
32
34
 
33
35
  import { TrustedKeySetAuthVerifier } from './authenticator';
34
36
  import { DefaultSpaceStateMachine } from './default-space-state-machine';
37
+ import { EdgeFeedReplicator } from '../spaces';
35
38
 
36
39
  export type IdentityParams = {
37
40
  identityKey: PublicKey;
@@ -39,6 +42,9 @@ export type IdentityParams = {
39
42
  signer: Signer;
40
43
  space: Space;
41
44
  presence?: Presence;
45
+
46
+ edgeConnection?: EdgeConnection;
47
+ edgeFeatures?: Runtime.Client.EdgeFeatures;
42
48
  };
43
49
 
44
50
  /**
@@ -52,6 +58,8 @@ export class Identity {
52
58
  private readonly _deviceStateMachine: DeviceStateMachine;
53
59
  private readonly _profileStateMachine: ProfileStateMachine;
54
60
  private readonly _defaultSpaceStateMachine: DefaultSpaceStateMachine;
61
+ private readonly _edgeFeedReplicator?: EdgeFeedReplicator = undefined;
62
+
55
63
  public readonly authVerifier: TrustedKeySetAuthVerifier;
56
64
 
57
65
  public readonly identityKey: PublicKey;
@@ -59,15 +67,15 @@ export class Identity {
59
67
 
60
68
  public readonly stateUpdate = new Event();
61
69
 
62
- constructor({ space, signer, identityKey, deviceKey, presence }: IdentityParams) {
63
- this.space = space;
64
- this._signer = signer;
65
- this._presence = presence;
70
+ constructor(params: IdentityParams) {
71
+ this.space = params.space;
72
+ this._signer = params.signer;
73
+ this._presence = params.presence;
66
74
 
67
- this.identityKey = identityKey;
68
- this.deviceKey = deviceKey;
75
+ this.identityKey = params.identityKey;
76
+ this.deviceKey = params.deviceKey;
69
77
 
70
- log.trace('dxos.halo.device', { deviceKey });
78
+ log.trace('dxos.halo.device', { deviceKey: params.deviceKey });
71
79
 
72
80
  this._deviceStateMachine = new DeviceStateMachine({
73
81
  identityKey: this.identityKey,
@@ -88,6 +96,10 @@ export class Identity {
88
96
  update: this.stateUpdate,
89
97
  authTimeout: AUTH_TIMEOUT,
90
98
  });
99
+
100
+ if (params.edgeConnection && params.edgeFeatures?.feedReplicator) {
101
+ this._edgeFeedReplicator = new EdgeFeedReplicator({ messenger: params.edgeConnection, spaceId: this.space.id });
102
+ }
91
103
  }
92
104
 
93
105
  // TODO(burdon): Expose state object?
@@ -105,7 +117,14 @@ export class Identity {
105
117
  await this.space.spaceState.addCredentialProcessor(this._deviceStateMachine);
106
118
  await this.space.spaceState.addCredentialProcessor(this._profileStateMachine);
107
119
  await this.space.spaceState.addCredentialProcessor(this._defaultSpaceStateMachine);
120
+
121
+ if (this._edgeFeedReplicator) {
122
+ this.space.protocol.feedAdded.append(this._onFeedAdded);
123
+ }
124
+
108
125
  await this.space.open(ctx);
126
+
127
+ await this._edgeFeedReplicator?.open();
109
128
  }
110
129
 
111
130
  @trace.span()
@@ -115,6 +134,13 @@ export class Identity {
115
134
  await this.space.spaceState.removeCredentialProcessor(this._defaultSpaceStateMachine);
116
135
  await this.space.spaceState.removeCredentialProcessor(this._profileStateMachine);
117
136
  await this.space.spaceState.removeCredentialProcessor(this._deviceStateMachine);
137
+
138
+ if (this._edgeFeedReplicator) {
139
+ this.space.protocol.feedAdded.remove(this._onFeedAdded);
140
+ }
141
+
142
+ await this._edgeFeedReplicator?.close();
143
+
118
144
  await this.space.close();
119
145
  }
120
146
 
@@ -151,6 +177,10 @@ export class Identity {
151
177
  return this._presence;
152
178
  }
153
179
 
180
+ get signer() {
181
+ return this._signer;
182
+ }
183
+
154
184
  /**
155
185
  * Issues credentials as identity.
156
186
  * Requires identity to be ready.
@@ -223,4 +253,8 @@ export class Identity {
223
253
  ].map((credential): FeedMessage.Payload => ({ credential: { credential } })),
224
254
  );
225
255
  }
256
+
257
+ private _onFeedAdded = async (feed: FeedWrapper<any>) => {
258
+ await this._edgeFeedReplicator!.addFeed(feed);
259
+ };
226
260
  }
@@ -2,19 +2,20 @@
2
2
  // Copyright 2022 DXOS.org
3
3
  //
4
4
 
5
- import { expect } from 'chai';
5
+ import { describe, expect, test, onTestFinished } from 'vitest';
6
6
 
7
7
  import { asyncChain } from '@dxos/async';
8
8
  import { Context } from '@dxos/context';
9
9
  import { AlreadyJoinedError } from '@dxos/protocols';
10
10
  import { Invitation } from '@dxos/protocols/proto/dxos/client/services';
11
- import { describe, test, afterTest } from '@dxos/test';
12
11
 
13
12
  import { type ServiceContext } from '../services';
14
13
  import { createPeers, createServiceContext, performInvitation } from '../testing';
15
14
 
16
15
  const closeAfterTest = async (peer: ServiceContext) => {
17
- afterTest(() => peer.close());
16
+ onTestFinished(async () => {
17
+ await peer.close();
18
+ });
18
19
  return peer;
19
20
  };
20
21
 
@@ -22,7 +23,9 @@ describe('services/device', () => {
22
23
  test('creates identity', async () => {
23
24
  const peer = await createServiceContext();
24
25
  await peer.open(new Context());
25
- afterTest(() => peer.close());
26
+ onTestFinished(async () => {
27
+ await peer.close();
28
+ });
26
29
 
27
30
  const identity = await peer.createIdentity();
28
31
  expect(identity).not.to.be.undefined;
@@ -106,13 +106,11 @@ export class InvitationHostExtension extends RpcExtension<
106
106
 
107
107
  introduce: async (request) => {
108
108
  const { profile, invitationId } = request;
109
-
110
109
  const traceId = PublicKey.random().toHex();
111
110
  log.trace('dxos.sdk.invitation-handler.host.introduce', trace.begin({ id: traceId }));
112
111
 
113
112
  const invitation = this._requireActiveInvitation();
114
113
  this._assertInvitationState(Invitation.State.CONNECTED);
115
-
116
114
  if (invitationId !== invitation?.invitationId) {
117
115
  log.warn('incorrect invitationId', { expected: invitation.invitationId, actual: invitationId });
118
116
  this._callbacks.onError(new Error('Incorrect invitationId.'));
@@ -126,7 +124,6 @@ export class InvitationHostExtension extends RpcExtension<
126
124
  log('guest introduced themselves', { guestProfile: profile });
127
125
  this.guestProfile = profile;
128
126
  this._callbacks.onStateUpdate(Invitation.State.READY_FOR_AUTHENTICATION);
129
-
130
127
  this._challenge =
131
128
  invitation.authMethod === Invitation.AuthMethod.KNOWN_PUBLIC_KEY ? randomBytes(32) : undefined;
132
129
 
@@ -2,13 +2,13 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import { expect } from 'chai';
5
+ import { beforeEach, onTestFinished, describe, expect, test } from 'vitest';
6
6
 
7
7
  import { type PushStream, sleep, Trigger, waitForCondition } from '@dxos/async';
8
8
  import { Context } from '@dxos/context';
9
9
  import { PublicKey } from '@dxos/keys';
10
10
  import { Invitation } from '@dxos/protocols/proto/dxos/client/services';
11
- import { afterTest, describe, openAndClose, test } from '@dxos/test';
11
+ import { openAndClose } from '@dxos/test-utils';
12
12
  import { range } from '@dxos/util';
13
13
 
14
14
  import { type InvitationProtocol } from './invitation-protocol';
@@ -34,6 +34,7 @@ type StateUpdateSink = PushStream<Invitation> & {
34
34
 
35
35
  describe('InvitationHandler', () => {
36
36
  let testBuilder: TestBuilder;
37
+
37
38
  beforeEach(() => {
38
39
  testBuilder = new TestBuilder();
39
40
  });
@@ -163,7 +164,7 @@ describe('InvitationHandler', () => {
163
164
  expect(guest.ctx.disposed).to.be.true;
164
165
  });
165
166
 
166
- test('guest gives up after trying with three hosts', async () => {
167
+ test('guest gives up after trying with three hosts', { timeout: 20_000 }, async () => {
167
168
  const hosts: PeerSetup[] = [await createPeer()];
168
169
  const [host] = hosts;
169
170
  const invitation = await createInvitation(host, { multiUse: true });
@@ -181,7 +182,7 @@ describe('InvitationHandler', () => {
181
182
 
182
183
  await sleep(10);
183
184
  expect(guest.sink.lastState).to.eq(Invitation.State.ERROR);
184
- }).timeout(20_000);
185
+ });
185
186
 
186
187
  test('single host - many guests', async () => {
187
188
  const hosts: PeerSetup[] = [await createPeer()];
@@ -261,7 +262,9 @@ describe('InvitationHandler', () => {
261
262
  });
262
263
  const protocol = new SpaceInvitationProtocol(peer.dataSpaceManager, peer.identity, peer.keyring, spaceKey);
263
264
  const ctx = new Context();
264
- afterTest(() => ctx.dispose());
265
+ onTestFinished(async () => {
266
+ await ctx.dispose();
267
+ });
265
268
  const sink = newStateUpdateSink();
266
269
  return { ctx, sink, peer, protocol, handler: invitationHandler, spaceKey };
267
270
  };
@@ -269,14 +272,18 @@ describe('InvitationHandler', () => {
269
272
  const hostInvitation = async (setup: PeerSetup, invitation: Invitation) => {
270
273
  await setup.ctx.dispose();
271
274
  setup.ctx = new Context();
272
- afterTest(() => setup.ctx.dispose());
275
+ onTestFinished(async () => {
276
+ await setup.ctx.dispose();
277
+ });
273
278
  setup.handler.handleInvitationFlow(setup.ctx, setup.sink, setup.protocol, invitation);
274
279
  };
275
280
 
276
281
  const acceptInvitation = async (setup: PeerSetup, invitation: Invitation): Promise<Trigger<string>> => {
277
282
  await setup.ctx.dispose();
278
283
  setup.ctx = new Context();
279
- afterTest(() => setup.ctx.dispose());
284
+ onTestFinished(async () => {
285
+ await setup.ctx.dispose();
286
+ });
280
287
  const authCodeInput = new Trigger<string>();
281
288
  setup.handler.acceptInvitation(setup.ctx, setup.sink, setup.protocol, invitation, authCodeInput);
282
289
  return authCodeInput;
@@ -463,7 +463,7 @@ export class InvitationsHandler {
463
463
  oldState: stateToString(invitation.state),
464
464
  });
465
465
  } else {
466
- log.info('invitation state update', {
466
+ log('invitation state update', {
467
467
  actor: actor?.constructor.name,
468
468
  newState: stateToString(newState),
469
469
  oldState: stateToString(invitation.state),
@@ -2,20 +2,21 @@
2
2
  // Copyright 2022 DXOS.org
3
3
  //
4
4
 
5
- import { expect } from 'chai';
5
+ import { onTestFinished, describe, expect, test } from 'vitest';
6
6
 
7
7
  import { asyncChain, Trigger } from '@dxos/async';
8
8
  import { raise } from '@dxos/debug';
9
9
  import { AlreadyJoinedError } from '@dxos/protocols';
10
10
  import { Invitation } from '@dxos/protocols/proto/dxos/client/services';
11
- import { afterTest, describe, test } from '@dxos/test';
12
11
 
13
12
  import { type ServiceContext } from '../services';
14
13
  import { createIdentity, createPeers } from '../testing';
15
14
  import { acceptInvitation, createInvitation, performInvitation } from '../testing/invitation-utils';
16
15
 
17
16
  const closeAfterTest = async (peer: ServiceContext) => {
18
- afterTest(() => peer.close());
17
+ onTestFinished(async () => {
18
+ await peer.close();
19
+ });
19
20
  return peer;
20
21
  };
21
22
 
@@ -2,12 +2,11 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- import { expect } from 'chai';
5
+ import { afterEach, beforeEach, describe, expect, test } from 'vitest';
6
6
 
7
7
  import { Trigger } from '@dxos/async';
8
8
  import { log, LogLevel } from '@dxos/log';
9
9
  import { type LogEntry } from '@dxos/protocols/proto/dxos/client/services';
10
- import { beforeEach, describe, test } from '@dxos/test';
11
10
 
12
11
  import { LoggingServiceImpl } from './logging-service';
13
12
 
@@ -2,12 +2,11 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- import { expect } from 'chai';
5
+ import { afterEach, onTestFinished, beforeEach, describe, expect, test } from 'vitest';
6
6
 
7
7
  import { Trigger } from '@dxos/async';
8
8
  import { Context } from '@dxos/context';
9
9
  import { type NetworkService, ConnectionState } from '@dxos/protocols/proto/dxos/client/services';
10
- import { afterEach, afterTest, beforeEach, describe, test } from '@dxos/test';
11
10
 
12
11
  import { NetworkServiceImpl } from './network-service';
13
12
  import { type ServiceContext } from '../services';
@@ -41,7 +40,7 @@ describe('NetworkService', () => {
41
40
  query.subscribe(({ swarm }) => {
42
41
  result.wake(swarm);
43
42
  });
44
- afterTest(() => query.close());
43
+ onTestFinished(() => query.close());
45
44
  expect(await result.wait()).to.equal(ConnectionState.ONLINE);
46
45
 
47
46
  result = new Trigger<ConnectionState | undefined>();
@@ -2,9 +2,11 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
+ import { describe, test } from 'vitest';
6
+
5
7
  import { MemorySignalManagerContext, MemorySignalManager } from '@dxos/messaging';
6
8
  import { Invitation } from '@dxos/protocols/proto/dxos/client/services';
7
- import { describe, openAndClose, test } from '@dxos/test';
9
+ import { openAndClose } from '@dxos/test-utils';
8
10
 
9
11
  import { createServiceContext, performInvitation } from '../testing';
10
12
 
@@ -2,13 +2,20 @@
2
2
  // Copyright 2022 DXOS.org
3
3
  //
4
4
 
5
- import { Trigger } from '@dxos/async';
5
+ import { Mutex, scheduleMicroTask, Trigger } from '@dxos/async';
6
6
  import { Context, Resource } from '@dxos/context';
7
7
  import { getCredentialAssertion, type CredentialProcessor } from '@dxos/credentials';
8
- import { failUndefined } from '@dxos/debug';
9
- import { EchoEdgeReplicator, EchoHost } from '@dxos/echo-db';
10
- import { MeshEchoReplicator, MetadataStore, SpaceManager, valueEncoding } from '@dxos/echo-pipeline';
11
- import type { EdgeConnection } from '@dxos/edge-client';
8
+ import { failUndefined, warnAfterTimeout } from '@dxos/debug';
9
+ import {
10
+ EchoEdgeReplicator,
11
+ EchoHost,
12
+ MeshEchoReplicator,
13
+ MetadataStore,
14
+ SpaceManager,
15
+ valueEncoding,
16
+ } from '@dxos/echo-pipeline';
17
+ import { createChainEdgeIdentity, createEphemeralEdgeIdentity } from '@dxos/edge-client';
18
+ import type { EdgeHttpClient, EdgeConnection } from '@dxos/edge-client';
12
19
  import { FeedFactory, FeedStore } from '@dxos/feed-store';
13
20
  import { invariant } from '@dxos/invariant';
14
21
  import { Keyring } from '@dxos/keyring';
@@ -31,7 +38,7 @@ import { safeInstanceof } from '@dxos/util';
31
38
  import {
32
39
  IdentityManager,
33
40
  type CreateIdentityOptions,
34
- type IdentityManagerRuntimeParams,
41
+ type IdentityManagerParams,
35
42
  type JoinIdentityParams,
36
43
  } from '../identity';
37
44
  import {
@@ -43,7 +50,10 @@ import {
43
50
  } from '../invitations';
44
51
  import { DataSpaceManager, type DataSpaceManagerRuntimeParams, type SigningContext } from '../spaces';
45
52
 
46
- export type ServiceContextRuntimeParams = IdentityManagerRuntimeParams &
53
+ export type ServiceContextRuntimeParams = Pick<
54
+ IdentityManagerParams,
55
+ 'devicePresenceOfflineTimeout' | 'devicePresenceAnnounceInterval'
56
+ > &
47
57
  DataSpaceManagerRuntimeParams & {
48
58
  invitationConnectionDefaultParams?: Partial<TeleportParams>;
49
59
  disableP2pReplication?: boolean;
@@ -56,6 +66,8 @@ export type ServiceContextRuntimeParams = IdentityManagerRuntimeParams &
56
66
  @safeInstanceof('dxos.client-services.ServiceContext')
57
67
  @Trace.resource()
58
68
  export class ServiceContext extends Resource {
69
+ private readonly _edgeIdentityUpdateMutex = new Mutex();
70
+
59
71
  public readonly initialized = new Trigger();
60
72
  public readonly metadataStore: MetadataStore;
61
73
  public readonly blobStore: BlobStore;
@@ -87,6 +99,7 @@ export class ServiceContext extends Resource {
87
99
  public readonly networkManager: SwarmNetworkManager,
88
100
  public readonly signalManager: SignalManager,
89
101
  private readonly _edgeConnection: EdgeConnection | undefined,
102
+ private readonly _edgeHttpClient: EdgeHttpClient | undefined,
90
103
  public readonly _runtimeParams?: ServiceContextRuntimeParams,
91
104
  private readonly _edgeFeatures?: Runtime.Client.EdgeFeatures,
92
105
  ) {
@@ -116,32 +129,50 @@ export class ServiceContext extends Resource {
116
129
  disableP2pReplication: this._runtimeParams?.disableP2pReplication,
117
130
  });
118
131
 
119
- this.identityManager = new IdentityManager(
120
- this.metadataStore,
121
- this.keyring,
122
- this.feedStore,
123
- this.spaceManager,
124
- this._runtimeParams as IdentityManagerRuntimeParams,
125
- {
132
+ this.identityManager = new IdentityManager({
133
+ metadataStore: this.metadataStore,
134
+ keyring: this.keyring,
135
+ feedStore: this.feedStore,
136
+ spaceManager: this.spaceManager,
137
+ devicePresenceOfflineTimeout: this._runtimeParams?.devicePresenceOfflineTimeout,
138
+ devicePresenceAnnounceInterval: this._runtimeParams?.devicePresenceAnnounceInterval,
139
+ edgeConnection: this._edgeConnection,
140
+ edgeFeatures: this._edgeFeatures,
141
+ callbacks: {
126
142
  onIdentityConstruction: (identity) => {
127
143
  if (this._edgeConnection) {
128
- log.info('Setting identity on edge connection', {
129
- identity: identity.identityKey.toHex(),
130
- oldIdentity: this._edgeConnection.identityKey,
131
- swarms: this.networkManager.topics,
132
- });
133
- this._edgeConnection.setIdentity({
134
- peerKey: identity.deviceKey.toHex(),
135
- identityKey: identity.identityKey.toHex(),
136
- });
137
- this.networkManager.setPeerInfo({
138
- identityKey: identity.identityKey.toHex(),
139
- peerKey: identity.deviceKey.toHex(),
144
+ scheduleMicroTask(this._ctx, async () => {
145
+ using _ = await this._edgeIdentityUpdateMutex.acquire();
146
+
147
+ log.info('Setting identity on edge connection', {
148
+ identity: identity.identityKey.toHex(),
149
+ oldIdentity: this._edgeConnection!.identityKey,
150
+ swarms: this.networkManager.topics,
151
+ });
152
+
153
+ await warnAfterTimeout(10_000, 'Waiting for identity to be ready for edge connection', async () => {
154
+ await identity.ready();
155
+ });
156
+
157
+ invariant(identity.deviceCredentialChain);
158
+ this._edgeConnection!.setIdentity(
159
+ await createChainEdgeIdentity(
160
+ identity.signer,
161
+ identity.identityKey,
162
+ identity.deviceKey,
163
+ identity.deviceCredentialChain,
164
+ [], // TODO(dmaretskyi): Service access credentials.
165
+ ),
166
+ );
167
+ this.networkManager.setPeerInfo({
168
+ identityKey: identity.identityKey.toHex(),
169
+ peerKey: identity.deviceKey.toHex(),
170
+ });
140
171
  });
141
172
  }
142
173
  },
143
174
  },
144
- );
175
+ });
145
176
 
146
177
  this.echoHost = new EchoHost({ kv: this.level });
147
178
 
@@ -185,7 +216,11 @@ export class ServiceContext extends Resource {
185
216
 
186
217
  log('opening...');
187
218
  log.trace('dxos.sdk.service-context.open', trace.begin({ id: this._instanceId }));
188
- await this._edgeConnection?.open();
219
+ if (this._edgeConnection) {
220
+ // TODO(dmaretskyi): Use device key.
221
+ this._edgeConnection.setIdentity(await createEphemeralEdgeIdentity());
222
+ await this._edgeConnection.open();
223
+ }
189
224
  await this.signalManager.open();
190
225
  await this.networkManager.open();
191
226
 
@@ -292,6 +327,7 @@ export class ServiceContext extends Resource {
292
327
  echoHost: this.echoHost,
293
328
  invitationsManager: this.invitationsManager,
294
329
  edgeConnection: this._edgeConnection,
330
+ edgeHttpClient: this._edgeHttpClient,
295
331
  echoEdgeReplicator: this._echoEdgeReplicator,
296
332
  meshReplicator: this._meshReplicator,
297
333
  runtimeParams: this._runtimeParams as DataSpaceManagerRuntimeParams,
@@ -2,9 +2,8 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- import chai, { expect } from 'chai';
6
- import chaiAsPromised from 'chai-as-promised';
7
5
  import { rmSync } from 'node:fs';
6
+ import { afterEach, onTestFinished, describe, expect, test } from 'vitest';
8
7
 
9
8
  import { asyncTimeout, latch, Trigger } from '@dxos/async';
10
9
  import { Config } from '@dxos/config';
@@ -14,13 +13,10 @@ import { type PublicKey } from '@dxos/keys';
14
13
  import { MemorySignalManagerContext } from '@dxos/messaging';
15
14
  import { type Identity } from '@dxos/protocols/proto/dxos/client/services';
16
15
  import { type Credential } from '@dxos/protocols/proto/dxos/halo/credentials';
17
- import { afterTest, describe, test } from '@dxos/test';
18
16
  import { isNode } from '@dxos/util';
19
17
 
20
18
  import { createMockCredential, createServiceHost } from '../testing';
21
19
 
22
- chai.use(chaiAsPromised);
23
-
24
20
  describe('ClientServicesHost', () => {
25
21
  const dataRoot = '/tmp/dxos/client-services/service-host/storage';
26
22
 
@@ -38,7 +34,7 @@ describe('ClientServicesHost', () => {
38
34
  test('queryCredentials', async () => {
39
35
  const host = createServiceHost(new Config(), new MemorySignalManagerContext());
40
36
  await host.open(new Context());
41
- afterTest(() => host.close());
37
+ onTestFinished(() => host.close());
42
38
 
43
39
  await host.services.IdentityService!.createIdentity({});
44
40
  const { spaceKey } = await host.services.SpacesService!.createSpace();
@@ -49,7 +45,7 @@ describe('ClientServicesHost', () => {
49
45
  tick();
50
46
  // console.log(credential);
51
47
  });
52
- afterTest(() => stream.close());
48
+ onTestFinished(() => stream.close());
53
49
 
54
50
  await done();
55
51
  });
@@ -57,7 +53,7 @@ describe('ClientServicesHost', () => {
57
53
  test('write and query credentials', async () => {
58
54
  const host = createServiceHost(new Config(), new MemorySignalManagerContext());
59
55
  await host.open(new Context());
60
- afterTest(() => host.close());
56
+ onTestFinished(() => host.close());
61
57
 
62
58
  await host.services.IdentityService!.createIdentity({});
63
59
 
@@ -86,7 +82,7 @@ describe('ClientServicesHost', () => {
86
82
  queriedCredential.wake(credential);
87
83
  }
88
84
  });
89
- afterTest(() => credentials.close());
85
+ onTestFinished(() => credentials.close());
90
86
 
91
87
  await queriedCredential.wait();
92
88
  });
@@ -94,7 +90,7 @@ describe('ClientServicesHost', () => {
94
90
  test('sign presentation', async () => {
95
91
  const host = createServiceHost(new Config(), new MemorySignalManagerContext());
96
92
  await host.open(new Context());
97
- afterTest(() => host.close());
93
+ onTestFinished(() => host.close());
98
94
 
99
95
  await host.services.IdentityService!.createIdentity({});
100
96
 
@@ -147,9 +143,9 @@ describe('ClientServicesHost', () => {
147
143
  trigger.wake(identity.identity);
148
144
  }
149
145
  });
150
- await expect(asyncTimeout(trigger.wait(), 200)).to.be.rejectedWith();
146
+ await expect(asyncTimeout(trigger.wait(), 200)).rejects.toBeInstanceOf(Error);
151
147
  await stream?.close();
152
148
  await host.close();
153
149
  }
154
- }).onlyEnvironments('nodejs', 'chromium', 'firefox');
150
+ });
155
151
  });
@@ -6,7 +6,7 @@ import { Event, synchronized } from '@dxos/async';
6
6
  import { clientServiceBundle, type ClientServices } from '@dxos/client-protocol';
7
7
  import { type Config } from '@dxos/config';
8
8
  import { Context } from '@dxos/context';
9
- import { EdgeClient, type EdgeConnection } from '@dxos/edge-client';
9
+ import { EdgeClient, EdgeHttpClient, createStubEdgeIdentity, type EdgeConnection } from '@dxos/edge-client';
10
10
  import { invariant } from '@dxos/invariant';
11
11
  import { PublicKey } from '@dxos/keys';
12
12
  import { type LevelDB } from '@dxos/kv-store';
@@ -15,7 +15,7 @@ import { EdgeSignalManager, WebsocketSignalManager, type SignalManager } from '@
15
15
  import {
16
16
  SwarmNetworkManager,
17
17
  createIceProvider,
18
- createSimplePeerTransportFactory,
18
+ createRtcTransportFactory,
19
19
  type TransportFactory,
20
20
  } from '@dxos/network-manager';
21
21
  import { trace } from '@dxos/protocols';
@@ -29,9 +29,9 @@ import { ServiceRegistry } from './service-registry';
29
29
  import { DevicesServiceImpl } from '../devices';
30
30
  import { DevtoolsHostEvents, DevtoolsServiceImpl } from '../devtools';
31
31
  import {
32
- type CollectDiagnosticsBroadcastHandler,
33
32
  createCollectDiagnosticsBroadcastHandler,
34
33
  createDiagnostics,
34
+ type CollectDiagnosticsBroadcastHandler,
35
35
  } from '../diagnostics';
36
36
  import { IdentityServiceImpl, type CreateIdentityOptions } from '../identity';
37
37
  import { ContactsServiceImpl } from '../identity/contacts-service';
@@ -89,6 +89,7 @@ export class ClientServicesHost {
89
89
  private _callbacks?: ClientServicesHostCallbacks;
90
90
  private _devtoolsProxy?: WebsocketRpcClient<{}, ClientServices>;
91
91
  private _edgeConnection?: EdgeConnection = undefined;
92
+ private _edgeHttpClient?: EdgeHttpClient = undefined;
92
93
 
93
94
  private _serviceContext!: ServiceContext;
94
95
  private readonly _runtimeParams: ServiceContextRuntimeParams;
@@ -212,13 +213,13 @@ export class ClientServicesHost {
212
213
 
213
214
  const edgeEndpoint = config?.get('runtime.services.edge.url');
214
215
  if (edgeEndpoint) {
215
- const randomKey = PublicKey.random().toHex();
216
- this._edgeConnection = new EdgeClient(randomKey, randomKey, { socketEndpoint: edgeEndpoint });
216
+ this._edgeConnection = new EdgeClient(createStubEdgeIdentity(), { socketEndpoint: edgeEndpoint });
217
+ this._edgeHttpClient = new EdgeHttpClient(edgeEndpoint);
217
218
  }
218
219
 
219
220
  const {
220
221
  connectionLog = true,
221
- transportFactory = createSimplePeerTransportFactory(
222
+ transportFactory = createRtcTransportFactory(
222
223
  { iceServers: this._config?.get('runtime.services.ice') },
223
224
  this._config?.get('runtime.services.iceProviders') &&
224
225
  createIceProvider(this._config!.get('runtime.services.iceProviders')!),
@@ -278,6 +279,7 @@ export class ClientServicesHost {
278
279
  this._networkManager,
279
280
  this._signalManager,
280
281
  this._edgeConnection,
282
+ this._edgeHttpClient,
281
283
  this._runtimeParams,
282
284
  this._config.get('runtime.client.edgeFeatures'),
283
285
  );
@@ -2,7 +2,7 @@
2
2
  // Copyright 2022 DXOS.org
3
3
  //
4
4
 
5
- import { expect } from 'chai';
5
+ import { describe, expect, test } from 'vitest';
6
6
 
7
7
  import { Event } from '@dxos/async';
8
8
  import { type ClientServices } from '@dxos/client-protocol';
@@ -12,7 +12,6 @@ import { log } from '@dxos/log';
12
12
  import { schema } from '@dxos/protocols/proto';
13
13
  import { type SystemService, SystemStatus } from '@dxos/protocols/proto/dxos/client/services';
14
14
  import { createLinkedPorts, createProtoRpcPeer, createServiceBundle } from '@dxos/rpc';
15
- import { describe, test } from '@dxos/test';
16
15
 
17
16
  import { ServiceRegistry } from './service-registry';
18
17
  import { SystemServiceImpl } from '../system';