@dxos/client-services 0.6.12 → 0.6.13-main.548ca8d

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 (118) hide show
  1. package/dist/lib/browser/{chunk-TOAILL4T.mjs → chunk-UEQIHAL2.mjs} +5838 -5151
  2. package/dist/lib/browser/chunk-UEQIHAL2.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 +8 -7
  7. package/dist/lib/browser/testing/index.mjs.map +3 -3
  8. package/dist/lib/node/{chunk-H6C4XY6B.cjs → chunk-MA5EWTRH.cjs} +5858 -5175
  9. package/dist/lib/node/chunk-MA5EWTRH.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 +14 -13
  14. package/dist/lib/node/testing/index.cjs.map +3 -3
  15. package/dist/lib/node-esm/chunk-AIBLDI4U.mjs +8403 -0
  16. package/dist/lib/node-esm/chunk-AIBLDI4U.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 +420 -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/edge-invitation-handler.d.ts +30 -0
  33. package/dist/types/src/packlets/invitations/edge-invitation-handler.d.ts.map +1 -0
  34. package/dist/types/src/packlets/invitations/invitation-guest-extenstion.d.ts +2 -1
  35. package/dist/types/src/packlets/invitations/invitation-guest-extenstion.d.ts.map +1 -1
  36. package/dist/types/src/packlets/invitations/invitation-host-extension.d.ts +2 -1
  37. package/dist/types/src/packlets/invitations/invitation-host-extension.d.ts.map +1 -1
  38. package/dist/types/src/packlets/invitations/invitation-state.d.ts +19 -0
  39. package/dist/types/src/packlets/invitations/invitation-state.d.ts.map +1 -0
  40. package/dist/types/src/packlets/invitations/invitations-handler.d.ts +8 -8
  41. package/dist/types/src/packlets/invitations/invitations-handler.d.ts.map +1 -1
  42. package/dist/types/src/packlets/invitations/space-invitation-protocol.d.ts.map +1 -1
  43. package/dist/types/src/packlets/services/service-context.d.ts +9 -9
  44. package/dist/types/src/packlets/services/service-context.d.ts.map +1 -1
  45. package/dist/types/src/packlets/services/service-host.d.ts +1 -0
  46. package/dist/types/src/packlets/services/service-host.d.ts.map +1 -1
  47. package/dist/types/src/packlets/spaces/data-space-manager.d.ts +6 -3
  48. package/dist/types/src/packlets/spaces/data-space-manager.d.ts.map +1 -1
  49. package/dist/types/src/packlets/spaces/data-space.d.ts +4 -3
  50. package/dist/types/src/packlets/spaces/data-space.d.ts.map +1 -1
  51. package/dist/types/src/packlets/spaces/edge-feed-replicator.d.ts +3 -0
  52. package/dist/types/src/packlets/spaces/edge-feed-replicator.d.ts.map +1 -1
  53. package/dist/types/src/packlets/spaces/edge-feed-replicator.test.d.ts +2 -0
  54. package/dist/types/src/packlets/spaces/edge-feed-replicator.test.d.ts.map +1 -0
  55. package/dist/types/src/packlets/spaces/epoch-migrations.d.ts +1 -1
  56. package/dist/types/src/packlets/spaces/epoch-migrations.d.ts.map +1 -1
  57. package/dist/types/src/packlets/spaces/notarization-plugin.d.ts +31 -6
  58. package/dist/types/src/packlets/spaces/notarization-plugin.d.ts.map +1 -1
  59. package/dist/types/src/packlets/spaces/spaces-service.d.ts +1 -1
  60. package/dist/types/src/packlets/spaces/spaces-service.d.ts.map +1 -1
  61. package/dist/types/src/packlets/storage/storage.d.ts.map +1 -1
  62. package/dist/types/src/packlets/testing/test-builder.d.ts +1 -2
  63. package/dist/types/src/packlets/testing/test-builder.d.ts.map +1 -1
  64. package/dist/types/src/packlets/worker/worker-runtime.d.ts.map +1 -1
  65. package/dist/types/src/testing/setup.d.ts +3 -0
  66. package/dist/types/src/testing/setup.d.ts.map +1 -0
  67. package/dist/types/src/version.d.ts +1 -1
  68. package/dist/types/src/version.d.ts.map +1 -1
  69. package/package.json +43 -39
  70. package/src/packlets/devices/devices-service.test.ts +4 -5
  71. package/src/packlets/diagnostics/diagnostics-broadcast.ts +1 -0
  72. package/src/packlets/identity/{authenticator.test.ts → authenticator.node.test.ts} +2 -3
  73. package/src/packlets/identity/authenticator.ts +5 -2
  74. package/src/packlets/identity/contacts-service.ts +1 -1
  75. package/src/packlets/identity/identity-manager.test.ts +5 -6
  76. package/src/packlets/identity/identity-manager.ts +35 -19
  77. package/src/packlets/identity/identity-service.test.ts +4 -8
  78. package/src/packlets/identity/identity.test.ts +128 -239
  79. package/src/packlets/identity/identity.ts +42 -8
  80. package/src/packlets/invitations/device-invitation-protocol.test.ts +7 -4
  81. package/src/packlets/invitations/edge-invitation-handler.ts +184 -0
  82. package/src/packlets/invitations/invitation-guest-extenstion.ts +8 -4
  83. package/src/packlets/invitations/invitation-host-extension.ts +8 -7
  84. package/src/packlets/invitations/invitation-state.ts +111 -0
  85. package/src/packlets/invitations/invitations-handler.test.ts +16 -9
  86. package/src/packlets/invitations/invitations-handler.ts +23 -92
  87. package/src/packlets/invitations/space-invitation-protocol.test.ts +4 -3
  88. package/src/packlets/invitations/space-invitation-protocol.ts +4 -0
  89. package/src/packlets/logging/logging.test.ts +1 -2
  90. package/src/packlets/network/network-service.test.ts +2 -3
  91. package/src/packlets/services/service-context.test.ts +3 -1
  92. package/src/packlets/services/service-context.ts +68 -31
  93. package/src/packlets/services/service-host.test.ts +8 -12
  94. package/src/packlets/services/service-host.ts +8 -6
  95. package/src/packlets/services/service-registry.test.ts +1 -2
  96. package/src/packlets/spaces/data-space-manager.test.ts +2 -2
  97. package/src/packlets/spaces/data-space-manager.ts +40 -5
  98. package/src/packlets/spaces/data-space.ts +34 -6
  99. package/src/packlets/spaces/edge-feed-replicator.test.ts +253 -0
  100. package/src/packlets/spaces/edge-feed-replicator.ts +80 -22
  101. package/src/packlets/spaces/epoch-migrations.ts +2 -2
  102. package/src/packlets/spaces/notarization-plugin.test.ts +10 -7
  103. package/src/packlets/spaces/notarization-plugin.ts +169 -29
  104. package/src/packlets/spaces/spaces-service.test.ts +5 -9
  105. package/src/packlets/spaces/spaces-service.ts +6 -1
  106. package/src/packlets/storage/storage.ts +0 -1
  107. package/src/packlets/system/system-service.test.ts +1 -2
  108. package/src/packlets/testing/test-builder.ts +3 -4
  109. package/src/packlets/worker/worker-runtime.ts +2 -2
  110. package/src/testing/setup.ts +11 -0
  111. package/src/version.ts +1 -5
  112. package/dist/lib/browser/chunk-TOAILL4T.mjs.map +0 -7
  113. package/dist/lib/node/chunk-H6C4XY6B.cjs.map +0 -7
  114. package/dist/types/src/packlets/identity/authenticator.test.d.ts +0 -2
  115. package/dist/types/src/packlets/identity/authenticator.test.d.ts.map +0 -1
  116. package/dist/types/src/packlets/services/automerge-host.test.d.ts +0 -2
  117. package/dist/types/src/packlets/services/automerge-host.test.d.ts.map +0 -1
  118. package/src/packlets/services/automerge-host.test.ts +0 -60
@@ -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';
@@ -23,7 +30,6 @@ import { type Runtime } from '@dxos/protocols/proto/dxos/config';
23
30
  import type { FeedMessage } from '@dxos/protocols/proto/dxos/echo/feed';
24
31
  import { type Credential, type ProfileDocument } from '@dxos/protocols/proto/dxos/halo/credentials';
25
32
  import { type Storage } from '@dxos/random-access-storage';
26
- import type { TeleportParams } from '@dxos/teleport';
27
33
  import { BlobStore } from '@dxos/teleport-extension-object-sync';
28
34
  import { trace as Trace } from '@dxos/tracing';
29
35
  import { safeInstanceof } from '@dxos/util';
@@ -31,7 +37,7 @@ import { safeInstanceof } from '@dxos/util';
31
37
  import {
32
38
  IdentityManager,
33
39
  type CreateIdentityOptions,
34
- type IdentityManagerRuntimeParams,
40
+ type IdentityManagerParams,
35
41
  type JoinIdentityParams,
36
42
  } from '../identity';
37
43
  import {
@@ -40,12 +46,16 @@ import {
40
46
  InvitationsManager,
41
47
  SpaceInvitationProtocol,
42
48
  type InvitationProtocol,
49
+ type InvitationConnectionParams,
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
- invitationConnectionDefaultParams?: Partial<TeleportParams>;
58
+ invitationConnectionDefaultParams?: InvitationConnectionParams;
49
59
  disableP2pReplication?: boolean;
50
60
  };
51
61
  /**
@@ -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,39 +129,58 @@ 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
 
148
179
  this._meshReplicator = new MeshEchoReplicator();
149
180
 
150
181
  this.invitations = new InvitationsHandler(
151
- this.networkManager, //
182
+ this.networkManager,
183
+ this._edgeHttpClient,
152
184
  _runtimeParams?.invitationConnectionDefaultParams,
153
185
  );
154
186
  this.invitationsManager = new InvitationsManager(
@@ -185,7 +217,11 @@ export class ServiceContext extends Resource {
185
217
 
186
218
  log('opening...');
187
219
  log.trace('dxos.sdk.service-context.open', trace.begin({ id: this._instanceId }));
188
- await this._edgeConnection?.open();
220
+ if (this._edgeConnection) {
221
+ // TODO(dmaretskyi): Use device key.
222
+ this._edgeConnection.setIdentity(await createEphemeralEdgeIdentity());
223
+ await this._edgeConnection.open();
224
+ }
189
225
  await this.signalManager.open();
190
226
  await this.networkManager.open();
191
227
 
@@ -292,6 +328,7 @@ export class ServiceContext extends Resource {
292
328
  echoHost: this.echoHost,
293
329
  invitationsManager: this.invitationsManager,
294
330
  edgeConnection: this._edgeConnection,
331
+ edgeHttpClient: this._edgeHttpClient,
295
332
  echoEdgeReplicator: this._echoEdgeReplicator,
296
333
  meshReplicator: this._meshReplicator,
297
334
  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';
@@ -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 { asyncTimeout, latch } from '@dxos/async';
8
8
  import { createAdmissionCredentials } from '@dxos/credentials';
@@ -10,7 +10,7 @@ import { AuthStatus } from '@dxos/echo-pipeline';
10
10
  import { writeMessages } from '@dxos/feed-store';
11
11
  import { log } from '@dxos/log';
12
12
  import { SpaceState } from '@dxos/protocols/proto/dxos/client/services';
13
- import { describe, openAndClose, test } from '@dxos/test';
13
+ import { openAndClose } from '@dxos/test-utils';
14
14
 
15
15
  import { TestBuilder, type TestPeer } from '../testing';
16
16
 
@@ -14,8 +14,11 @@ import {
14
14
  type DelegateInvitationCredential,
15
15
  type MemberInfo,
16
16
  } from '@dxos/credentials';
17
- import { convertLegacyReferences, findInlineObjectOfType, type EchoEdgeReplicator, type EchoHost } from '@dxos/echo-db';
18
17
  import {
18
+ convertLegacyReferences,
19
+ findInlineObjectOfType,
20
+ type EchoEdgeReplicator,
21
+ type EchoHost,
19
22
  AuthStatus,
20
23
  CredentialServerExtension,
21
24
  type MeshEchoReplicator,
@@ -33,7 +36,7 @@ import {
33
36
  type SpaceDoc,
34
37
  } from '@dxos/echo-protocol';
35
38
  import { TYPE_PROPERTIES, generateEchoId, getTypeReference } from '@dxos/echo-schema';
36
- import type { EdgeConnection } from '@dxos/edge-client';
39
+ import type { EdgeConnection, EdgeHttpClient } from '@dxos/edge-client';
37
40
  import { writeMessages, type FeedStore } from '@dxos/feed-store';
38
41
  import { invariant } from '@dxos/invariant';
39
42
  import { type Keyring } from '@dxos/keyring';
@@ -43,7 +46,7 @@ import { AlreadyJoinedError, trace as Trace } from '@dxos/protocols';
43
46
  import { Invitation, SpaceState } from '@dxos/protocols/proto/dxos/client/services';
44
47
  import { type Runtime } from '@dxos/protocols/proto/dxos/config';
45
48
  import { type FeedMessage } from '@dxos/protocols/proto/dxos/echo/feed';
46
- import { type SpaceMetadata } from '@dxos/protocols/proto/dxos/echo/metadata';
49
+ import { type SpaceMetadata, EdgeReplicationSetting } from '@dxos/protocols/proto/dxos/echo/metadata';
47
50
  import { SpaceMember, type Credential, type ProfileDocument } from '@dxos/protocols/proto/dxos/halo/credentials';
48
51
  import { type DelegateSpaceInvitation } from '@dxos/protocols/proto/dxos/halo/invitations';
49
52
  import { type PeerState } from '@dxos/protocols/proto/dxos/mesh/presence';
@@ -107,6 +110,7 @@ export type DataSpaceManagerParams = {
107
110
  echoHost: EchoHost;
108
111
  invitationsManager: InvitationsManager;
109
112
  edgeConnection?: EdgeConnection;
113
+ edgeHttpClient?: EdgeHttpClient;
110
114
  meshReplicator?: MeshEchoReplicator;
111
115
  echoEdgeReplicator?: EchoEdgeReplicator;
112
116
  runtimeParams?: DataSpaceManagerRuntimeParams;
@@ -135,6 +139,7 @@ export class DataSpaceManager extends Resource {
135
139
  private readonly _echoHost: EchoHost;
136
140
  private readonly _invitationsManager: InvitationsManager;
137
141
  private readonly _edgeConnection?: EdgeConnection = undefined;
142
+ private readonly _edgeHttpClient?: EdgeHttpClient = undefined;
138
143
  private readonly _edgeFeatures?: Runtime.Client.EdgeFeatures = undefined;
139
144
  private readonly _meshReplicator?: MeshEchoReplicator = undefined;
140
145
  private readonly _echoEdgeReplicator?: EchoEdgeReplicator = undefined;
@@ -154,6 +159,7 @@ export class DataSpaceManager extends Resource {
154
159
  this._edgeConnection = params.edgeConnection;
155
160
  this._edgeFeatures = params.edgeFeatures;
156
161
  this._echoEdgeReplicator = params.echoEdgeReplicator;
162
+ this._edgeHttpClient = params.edgeHttpClient;
157
163
  this._runtimeParams = params.runtimeParams;
158
164
 
159
165
  trace.diagnostic({
@@ -388,6 +394,26 @@ export class DataSpaceManager extends Resource {
388
394
  });
389
395
  }
390
396
 
397
+ async setSpaceEdgeReplicationSetting(spaceKey: PublicKey, setting: EdgeReplicationSetting) {
398
+ const space = this._spaces.get(spaceKey);
399
+ invariant(space, 'Space not found.');
400
+
401
+ await this._metadataStore.setSpaceEdgeReplicationSetting(spaceKey, setting);
402
+
403
+ if (space.isOpen) {
404
+ switch (setting) {
405
+ case EdgeReplicationSetting.DISABLED:
406
+ await this._echoEdgeReplicator?.disconnectFromSpace(space.id);
407
+ break;
408
+ case EdgeReplicationSetting.ENABLED:
409
+ await this._echoEdgeReplicator?.connectToSpace(space.id);
410
+ break;
411
+ }
412
+ }
413
+
414
+ space.stateUpdate.emit();
415
+ }
416
+
391
417
  private async _constructSpace(metadata: SpaceMetadata) {
392
418
  log('construct space', { metadata });
393
419
  const gossip = new Gossip({
@@ -479,13 +505,22 @@ export class DataSpaceManager extends Resource {
479
505
  },
480
506
  cache: metadata.cache,
481
507
  edgeConnection: this._edgeConnection,
508
+ edgeHttpClient: this._edgeHttpClient,
482
509
  edgeFeatures: this._edgeFeatures,
483
510
  });
484
511
  dataSpace.postOpen.append(async () => {
485
- await this._echoEdgeReplicator?.connectToSpace(dataSpace.id);
512
+ const setting = dataSpace.getEdgeReplicationSetting();
513
+ if (setting === EdgeReplicationSetting.ENABLED) {
514
+ await this._echoEdgeReplicator?.connectToSpace(dataSpace.id);
515
+ } else if (this._echoEdgeReplicator) {
516
+ log('not connecting EchoEdgeReplicator because of EdgeReplicationSetting', { spaceId: dataSpace.id });
517
+ }
486
518
  });
487
519
  dataSpace.preClose.append(async () => {
488
- await this._echoEdgeReplicator?.disconnectFromSpace(dataSpace.id);
520
+ const setting = dataSpace.getEdgeReplicationSetting();
521
+ if (setting === EdgeReplicationSetting.ENABLED) {
522
+ await this._echoEdgeReplicator?.disconnectFromSpace(dataSpace.id);
523
+ }
489
524
  });
490
525
 
491
526
  presence.newPeer.on((peerState) => {
@@ -7,10 +7,15 @@ import { AUTH_TIMEOUT } from '@dxos/client-protocol';
7
7
  import { Context, ContextDisposedError, cancelWithContext } from '@dxos/context';
8
8
  import type { SpecificCredential } from '@dxos/credentials';
9
9
  import { timed, warnAfterTimeout } from '@dxos/debug';
10
- import { type EchoHost, type DatabaseRoot } from '@dxos/echo-db';
11
- import { createMappedFeedWriter, type MetadataStore, type Space } from '@dxos/echo-pipeline';
10
+ import {
11
+ type EchoHost,
12
+ type DatabaseRoot,
13
+ createMappedFeedWriter,
14
+ type MetadataStore,
15
+ type Space,
16
+ } from '@dxos/echo-pipeline';
12
17
  import { SpaceDocVersion } from '@dxos/echo-protocol';
13
- import type { EdgeConnection } from '@dxos/edge-client';
18
+ import type { EdgeConnection, EdgeHttpClient } from '@dxos/edge-client';
14
19
  import { type FeedStore, type FeedWrapper } from '@dxos/feed-store';
15
20
  import { failedInvariant } from '@dxos/invariant';
16
21
  import { type Keyring } from '@dxos/keyring';
@@ -75,6 +80,7 @@ export type DataSpaceParams = {
75
80
  callbacks?: DataSpaceCallbacks;
76
81
  cache?: SpaceCache;
77
82
  edgeConnection?: EdgeConnection;
83
+ edgeHttpClient?: EdgeHttpClient;
78
84
  edgeFeatures?: Runtime.Client.EdgeFeatures;
79
85
  };
80
86
 
@@ -96,7 +102,7 @@ export class DataSpace {
96
102
  private readonly _feedStore: FeedStore<FeedMessage>;
97
103
  private readonly _metadataStore: MetadataStore;
98
104
  private readonly _signingContext: SigningContext;
99
- private readonly _notarizationPlugin = new NotarizationPlugin();
105
+ private readonly _notarizationPlugin: NotarizationPlugin;
100
106
  private readonly _callbacks: DataSpaceCallbacks;
101
107
  private readonly _cache?: SpaceCache = undefined;
102
108
  private readonly _echoHost: EchoHost;
@@ -136,6 +142,11 @@ export class DataSpace {
136
142
  this._signingContext = params.signingContext;
137
143
  this._callbacks = params.callbacks ?? {};
138
144
  this._echoHost = params.echoHost;
145
+ this._notarizationPlugin = new NotarizationPlugin({
146
+ spaceId: this._inner.id,
147
+ edgeClient: params.edgeHttpClient,
148
+ edgeFeatures: params.edgeFeatures,
149
+ });
139
150
 
140
151
  this.authVerifier = new TrustedKeySetAuthVerifier({
141
152
  trustedKeysProvider: () =>
@@ -318,6 +329,7 @@ export class DataSpace {
318
329
  this._state = SpaceState.SPACE_INITIALIZING;
319
330
  log('new state', { state: SpaceState[this._state] });
320
331
 
332
+ log('initializing control pipeline');
321
333
  await this._initializeAndReadControlPipeline();
322
334
 
323
335
  // Allow other tasks to run before loading the data pipeline.
@@ -325,10 +337,13 @@ export class DataSpace {
325
337
 
326
338
  const ready = this.stateUpdate.waitForCondition(() => this._state === SpaceState.SPACE_READY);
327
339
 
340
+ log('initializing automerge root');
328
341
  this._automergeSpaceState.startProcessingRootDocs();
329
342
 
330
343
  // TODO(dmaretskyi): Change so `initializeDataPipeline` doesn't wait for the space to be READY, but rather any state with a valid root.
344
+ log('waiting for space to be ready');
331
345
  await ready;
346
+ log('space is ready');
332
347
  }
333
348
 
334
349
  private async _enterReadyState() {
@@ -345,6 +360,7 @@ export class DataSpace {
345
360
  private async _initializeAndReadControlPipeline() {
346
361
  await this._inner.controlPipeline.state.waitUntilReachedTargetTimeframe({
347
362
  ctx: this._ctx,
363
+ timeout: 10_000,
348
364
  breakOnStall: false,
349
365
  });
350
366
 
@@ -408,8 +424,16 @@ export class DataSpace {
408
424
  }
409
425
 
410
426
  if (credentials.length > 0) {
411
- // Never times out
412
- await this.notarizationPlugin.notarize({ ctx: this._ctx, credentials, timeout: 0 });
427
+ try {
428
+ log('will notarize credentials for feed admission', { count: credentials.length });
429
+ // Never times out
430
+ await this.notarizationPlugin.notarize({ ctx: this._ctx, credentials, timeout: 0 });
431
+
432
+ log('credentials notarized');
433
+ } catch (err) {
434
+ log.error('error notarizing credentials for feed admission', err);
435
+ throw err;
436
+ }
413
437
 
414
438
  // Set this after credentials are notarized so that on failure we will retry.
415
439
  await this._metadataStore.setWritableFeedKeys(this.key, this.inner.controlFeedKey!, this.inner.dataFeedKey!);
@@ -546,6 +570,10 @@ export class DataSpace {
546
570
  this.stateUpdate.emit();
547
571
  }
548
572
 
573
+ getEdgeReplicationSetting() {
574
+ return this._metadataStore.getSpaceEdgeReplicationSetting(this.key);
575
+ }
576
+
549
577
  private _onFeedAdded = async (feed: FeedWrapper<any>) => {
550
578
  await this._edgeFeedReplicator!.addFeed(feed);
551
579
  };