@dxos/client-services 0.6.12-main.78ddbdf → 0.6.12-main.7907542

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 (56) hide show
  1. package/dist/lib/browser/{chunk-XSFLJVDP.mjs → chunk-6UTFANWL.mjs} +686 -304
  2. package/dist/lib/browser/chunk-6UTFANWL.mjs.map +7 -0
  3. package/dist/lib/browser/index.mjs +1 -1
  4. package/dist/lib/browser/meta.json +1 -1
  5. package/dist/lib/browser/testing/index.mjs +2 -2
  6. package/dist/lib/browser/testing/index.mjs.map +2 -2
  7. package/dist/lib/node/{chunk-F3WGFGEN.cjs → chunk-2UUETFZ2.cjs} +776 -394
  8. package/dist/lib/node/chunk-2UUETFZ2.cjs.map +7 -0
  9. package/dist/lib/node/index.cjs +45 -45
  10. package/dist/lib/node/meta.json +1 -1
  11. package/dist/lib/node/testing/index.cjs +8 -8
  12. package/dist/lib/node/testing/index.cjs.map +2 -2
  13. package/dist/lib/node-esm/{chunk-3HDLTAT2.mjs → chunk-EQAI46LQ.mjs} +686 -304
  14. package/dist/lib/node-esm/chunk-EQAI46LQ.mjs.map +7 -0
  15. package/dist/lib/node-esm/index.mjs +1 -1
  16. package/dist/lib/node-esm/meta.json +1 -1
  17. package/dist/lib/node-esm/testing/index.mjs +2 -2
  18. package/dist/lib/node-esm/testing/index.mjs.map +2 -2
  19. package/dist/types/src/packlets/identity/authenticator.d.ts.map +1 -1
  20. package/dist/types/src/packlets/identity/contacts-service.d.ts +1 -1
  21. package/dist/types/src/packlets/identity/contacts-service.d.ts.map +1 -1
  22. package/dist/types/src/packlets/identity/identity.d.ts +1 -0
  23. package/dist/types/src/packlets/identity/identity.d.ts.map +1 -1
  24. package/dist/types/src/packlets/services/service-context.d.ts +4 -2
  25. package/dist/types/src/packlets/services/service-context.d.ts.map +1 -1
  26. package/dist/types/src/packlets/services/service-host.d.ts +1 -0
  27. package/dist/types/src/packlets/services/service-host.d.ts.map +1 -1
  28. package/dist/types/src/packlets/spaces/data-space-manager.d.ts +5 -1
  29. package/dist/types/src/packlets/spaces/data-space-manager.d.ts.map +1 -1
  30. package/dist/types/src/packlets/spaces/data-space.d.ts +3 -1
  31. package/dist/types/src/packlets/spaces/data-space.d.ts.map +1 -1
  32. package/dist/types/src/packlets/spaces/edge-feed-replicator.d.ts +2 -0
  33. package/dist/types/src/packlets/spaces/edge-feed-replicator.d.ts.map +1 -1
  34. package/dist/types/src/packlets/spaces/notarization-plugin.d.ts +31 -6
  35. package/dist/types/src/packlets/spaces/notarization-plugin.d.ts.map +1 -1
  36. package/dist/types/src/packlets/spaces/spaces-service.d.ts +1 -1
  37. package/dist/types/src/packlets/spaces/spaces-service.d.ts.map +1 -1
  38. package/dist/types/src/version.d.ts +1 -1
  39. package/package.json +38 -38
  40. package/src/packlets/identity/authenticator.ts +5 -2
  41. package/src/packlets/identity/contacts-service.ts +1 -1
  42. package/src/packlets/identity/identity.ts +4 -0
  43. package/src/packlets/services/service-context.ts +41 -17
  44. package/src/packlets/services/service-host.ts +7 -5
  45. package/src/packlets/spaces/data-space-manager.ts +32 -4
  46. package/src/packlets/spaces/data-space.ts +27 -4
  47. package/src/packlets/spaces/edge-feed-replicator.test.ts +22 -15
  48. package/src/packlets/spaces/edge-feed-replicator.ts +45 -25
  49. package/src/packlets/spaces/notarization-plugin.test.ts +8 -4
  50. package/src/packlets/spaces/notarization-plugin.ts +169 -29
  51. package/src/packlets/spaces/spaces-service.ts +6 -1
  52. package/src/packlets/testing/test-builder.ts +1 -1
  53. package/src/version.ts +1 -1
  54. package/dist/lib/browser/chunk-XSFLJVDP.mjs.map +0 -7
  55. package/dist/lib/node/chunk-F3WGFGEN.cjs.map +0 -7
  56. package/dist/lib/node-esm/chunk-3HDLTAT2.mjs.map +0 -7
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dxos/client-services",
3
- "version": "0.6.12-main.78ddbdf",
3
+ "version": "0.6.12-main.7907542",
4
4
  "description": "DXOS client services implementation",
5
5
  "homepage": "https://dxos.org",
6
6
  "bugs": "https://github.com/dxos/dxos/issues",
@@ -47,48 +47,48 @@
47
47
  "dependencies": {
48
48
  "cbor-x": "^1.5.4",
49
49
  "platform": "^1.3.6",
50
- "@dxos/async": "0.6.12-main.78ddbdf",
51
- "@dxos/client-protocol": "0.6.12-main.78ddbdf",
52
- "@dxos/config": "0.6.12-main.78ddbdf",
53
- "@dxos/codec-protobuf": "0.6.12-main.78ddbdf",
54
- "@dxos/automerge": "0.6.12-main.78ddbdf",
55
- "@dxos/context": "0.6.12-main.78ddbdf",
56
- "@dxos/crypto": "0.6.12-main.78ddbdf",
57
- "@dxos/credentials": "0.6.12-main.78ddbdf",
58
- "@dxos/debug": "0.6.12-main.78ddbdf",
59
- "@dxos/echo-db": "0.6.12-main.78ddbdf",
60
- "@dxos/echo-pipeline": "0.6.12-main.78ddbdf",
61
- "@dxos/echo-protocol": "0.6.12-main.78ddbdf",
62
- "@dxos/echo-schema": "0.6.12-main.78ddbdf",
63
- "@dxos/feed-store": "0.6.12-main.78ddbdf",
64
- "@dxos/indexing": "0.6.12-main.78ddbdf",
65
- "@dxos/edge-client": "0.6.12-main.78ddbdf",
66
- "@dxos/invariant": "0.6.12-main.78ddbdf",
67
- "@dxos/keyring": "0.6.12-main.78ddbdf",
68
- "@dxos/keys": "0.6.12-main.78ddbdf",
69
- "@dxos/lock-file": "0.6.12-main.78ddbdf",
70
- "@dxos/log": "0.6.12-main.78ddbdf",
71
- "@dxos/kv-store": "0.6.12-main.78ddbdf",
72
- "@dxos/messaging": "0.6.12-main.78ddbdf",
73
- "@dxos/network-manager": "0.6.12-main.78ddbdf",
74
- "@dxos/node-std": "0.6.12-main.78ddbdf",
75
- "@dxos/random-access-storage": "0.6.12-main.78ddbdf",
76
- "@dxos/protocols": "0.6.12-main.78ddbdf",
77
- "@dxos/teleport": "0.6.12-main.78ddbdf",
78
- "@dxos/rpc": "0.6.12-main.78ddbdf",
79
- "@dxos/teleport-extension-gossip": "0.6.12-main.78ddbdf",
80
- "@dxos/timeframe": "0.6.12-main.78ddbdf",
81
- "@dxos/tracing": "0.6.12-main.78ddbdf",
82
- "@dxos/teleport-extension-object-sync": "0.6.12-main.78ddbdf",
83
- "@dxos/util": "0.6.12-main.78ddbdf",
84
- "@dxos/websocket-rpc": "0.6.12-main.78ddbdf"
50
+ "@dxos/async": "0.6.12-main.7907542",
51
+ "@dxos/client-protocol": "0.6.12-main.7907542",
52
+ "@dxos/automerge": "0.6.12-main.7907542",
53
+ "@dxos/codec-protobuf": "0.6.12-main.7907542",
54
+ "@dxos/config": "0.6.12-main.7907542",
55
+ "@dxos/context": "0.6.12-main.7907542",
56
+ "@dxos/credentials": "0.6.12-main.7907542",
57
+ "@dxos/debug": "0.6.12-main.7907542",
58
+ "@dxos/echo-db": "0.6.12-main.7907542",
59
+ "@dxos/crypto": "0.6.12-main.7907542",
60
+ "@dxos/echo-pipeline": "0.6.12-main.7907542",
61
+ "@dxos/echo-protocol": "0.6.12-main.7907542",
62
+ "@dxos/edge-client": "0.6.12-main.7907542",
63
+ "@dxos/echo-schema": "0.6.12-main.7907542",
64
+ "@dxos/feed-store": "0.6.12-main.7907542",
65
+ "@dxos/indexing": "0.6.12-main.7907542",
66
+ "@dxos/invariant": "0.6.12-main.7907542",
67
+ "@dxos/keyring": "0.6.12-main.7907542",
68
+ "@dxos/keys": "0.6.12-main.7907542",
69
+ "@dxos/lock-file": "0.6.12-main.7907542",
70
+ "@dxos/kv-store": "0.6.12-main.7907542",
71
+ "@dxos/log": "0.6.12-main.7907542",
72
+ "@dxos/messaging": "0.6.12-main.7907542",
73
+ "@dxos/network-manager": "0.6.12-main.7907542",
74
+ "@dxos/node-std": "0.6.12-main.7907542",
75
+ "@dxos/protocols": "0.6.12-main.7907542",
76
+ "@dxos/random-access-storage": "0.6.12-main.7907542",
77
+ "@dxos/rpc": "0.6.12-main.7907542",
78
+ "@dxos/teleport": "0.6.12-main.7907542",
79
+ "@dxos/teleport-extension-object-sync": "0.6.12-main.7907542",
80
+ "@dxos/teleport-extension-gossip": "0.6.12-main.7907542",
81
+ "@dxos/timeframe": "0.6.12-main.7907542",
82
+ "@dxos/tracing": "0.6.12-main.7907542",
83
+ "@dxos/util": "0.6.12-main.7907542",
84
+ "@dxos/websocket-rpc": "0.6.12-main.7907542"
85
85
  },
86
86
  "devDependencies": {
87
87
  "@types/platform": "^1.3.4",
88
88
  "@types/readable-stream": "^2.3.9",
89
89
  "get-port-please": "^3.1.1",
90
- "@dxos/signal": "0.6.12-main.78ddbdf",
91
- "@dxos/test-utils": "0.6.12-main.78ddbdf"
90
+ "@dxos/signal": "0.6.12-main.7907542",
91
+ "@dxos/test-utils": "0.6.12-main.7907542"
92
92
  },
93
93
  "publishConfig": {
94
94
  "access": "public"
@@ -67,7 +67,7 @@ export class TrustedKeySetAuthVerifier {
67
67
  }
68
68
 
69
69
  if (this._isTrustedKey(credential.issuer)) {
70
- log('key is not currently in trusted set, waiting...', { key: credential.issuer });
70
+ log('key is trusted -- auth success', { key: credential.issuer });
71
71
  return true;
72
72
  }
73
73
 
@@ -81,7 +81,10 @@ export class TrustedKeySetAuthVerifier {
81
81
  log('auth success', { key: credential.issuer });
82
82
  trigger.wake(true);
83
83
  } else {
84
- log('key is not currently in trusted set, waiting...', { key: credential.issuer });
84
+ log('key is not currently in trusted set, waiting...', {
85
+ key: credential.issuer,
86
+ trusted: [...this._params.trustedKeysProvider()],
87
+ });
85
88
  }
86
89
  });
87
90
 
@@ -5,7 +5,7 @@
5
5
  import { EventSubscriptions, scheduleTask, UpdateScheduler } from '@dxos/async';
6
6
  import { Stream } from '@dxos/codec-protobuf';
7
7
  import { type MemberInfo } from '@dxos/credentials';
8
- import type { SpaceManager } from '@dxos/echo-pipeline';
8
+ import { type SpaceManager } from '@dxos/echo-pipeline';
9
9
  import { PublicKey } from '@dxos/keys';
10
10
  import { type Contact, type ContactBook, type ContactsService } from '@dxos/protocols/proto/dxos/client/services';
11
11
  import { ComplexMap, ComplexSet } from '@dxos/util';
@@ -177,6 +177,10 @@ export class Identity {
177
177
  return this._presence;
178
178
  }
179
179
 
180
+ get signer() {
181
+ return this._signer;
182
+ }
183
+
180
184
  /**
181
185
  * Issues credentials as identity.
182
186
  * Requires identity to be ready.
@@ -2,10 +2,10 @@
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';
8
+ import { failUndefined, warnAfterTimeout } from '@dxos/debug';
9
9
  import {
10
10
  EchoEdgeReplicator,
11
11
  EchoHost,
@@ -14,7 +14,8 @@ import {
14
14
  SpaceManager,
15
15
  valueEncoding,
16
16
  } from '@dxos/echo-pipeline';
17
- import type { EdgeConnection } from '@dxos/edge-client';
17
+ import { createChainEdgeIdentity, createEphemeralEdgeIdentity } from '@dxos/edge-client';
18
+ import type { EdgeHttpClient, EdgeConnection } from '@dxos/edge-client';
18
19
  import { FeedFactory, FeedStore } from '@dxos/feed-store';
19
20
  import { invariant } from '@dxos/invariant';
20
21
  import { Keyring } from '@dxos/keyring';
@@ -37,8 +38,8 @@ import { safeInstanceof } from '@dxos/util';
37
38
  import {
38
39
  IdentityManager,
39
40
  type CreateIdentityOptions,
40
- type JoinIdentityParams,
41
41
  type IdentityManagerParams,
42
+ type JoinIdentityParams,
42
43
  } from '../identity';
43
44
  import {
44
45
  DeviceInvitationProtocol,
@@ -65,6 +66,8 @@ export type ServiceContextRuntimeParams = Pick<
65
66
  @safeInstanceof('dxos.client-services.ServiceContext')
66
67
  @Trace.resource()
67
68
  export class ServiceContext extends Resource {
69
+ private readonly _edgeIdentityUpdateMutex = new Mutex();
70
+
68
71
  public readonly initialized = new Trigger();
69
72
  public readonly metadataStore: MetadataStore;
70
73
  public readonly blobStore: BlobStore;
@@ -96,6 +99,7 @@ export class ServiceContext extends Resource {
96
99
  public readonly networkManager: SwarmNetworkManager,
97
100
  public readonly signalManager: SignalManager,
98
101
  private readonly _edgeConnection: EdgeConnection | undefined,
102
+ private readonly _edgeHttpClient: EdgeHttpClient | undefined,
99
103
  public readonly _runtimeParams?: ServiceContextRuntimeParams,
100
104
  private readonly _edgeFeatures?: Runtime.Client.EdgeFeatures,
101
105
  ) {
@@ -137,18 +141,33 @@ export class ServiceContext extends Resource {
137
141
  callbacks: {
138
142
  onIdentityConstruction: (identity) => {
139
143
  if (this._edgeConnection) {
140
- log.info('Setting identity on edge connection', {
141
- identity: identity.identityKey.toHex(),
142
- oldIdentity: this._edgeConnection.identityKey,
143
- swarms: this.networkManager.topics,
144
- });
145
- this._edgeConnection.setIdentity({
146
- peerKey: identity.deviceKey.toHex(),
147
- identityKey: identity.identityKey.toHex(),
148
- });
149
- this.networkManager.setPeerInfo({
150
- identityKey: identity.identityKey.toHex(),
151
- 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
+ });
152
171
  });
153
172
  }
154
173
  },
@@ -197,7 +216,11 @@ export class ServiceContext extends Resource {
197
216
 
198
217
  log('opening...');
199
218
  log.trace('dxos.sdk.service-context.open', trace.begin({ id: this._instanceId }));
200
- 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
+ }
201
224
  await this.signalManager.open();
202
225
  await this.networkManager.open();
203
226
 
@@ -304,6 +327,7 @@ export class ServiceContext extends Resource {
304
327
  echoHost: this.echoHost,
305
328
  invitationsManager: this.invitationsManager,
306
329
  edgeConnection: this._edgeConnection,
330
+ edgeHttpClient: this._edgeHttpClient,
307
331
  echoEdgeReplicator: this._echoEdgeReplicator,
308
332
  meshReplicator: this._meshReplicator,
309
333
  runtimeParams: this._runtimeParams as DataSpaceManagerRuntimeParams,
@@ -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,8 +15,8 @@ import { EdgeSignalManager, WebsocketSignalManager, type SignalManager } from '@
15
15
  import {
16
16
  SwarmNetworkManager,
17
17
  createIceProvider,
18
- type TransportFactory,
19
18
  createRtcTransportFactory,
19
+ type TransportFactory,
20
20
  } from '@dxos/network-manager';
21
21
  import { trace } from '@dxos/protocols';
22
22
  import { SystemStatus } from '@dxos/protocols/proto/dxos/client/services';
@@ -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,8 +213,8 @@ 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 {
@@ -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
  );
@@ -36,7 +36,7 @@ import {
36
36
  type SpaceDoc,
37
37
  } from '@dxos/echo-protocol';
38
38
  import { TYPE_PROPERTIES, generateEchoId, getTypeReference } from '@dxos/echo-schema';
39
- import type { EdgeConnection } from '@dxos/edge-client';
39
+ import type { EdgeConnection, EdgeHttpClient } from '@dxos/edge-client';
40
40
  import { writeMessages, type FeedStore } from '@dxos/feed-store';
41
41
  import { invariant } from '@dxos/invariant';
42
42
  import { type Keyring } from '@dxos/keyring';
@@ -46,7 +46,7 @@ import { AlreadyJoinedError, trace as Trace } from '@dxos/protocols';
46
46
  import { Invitation, SpaceState } from '@dxos/protocols/proto/dxos/client/services';
47
47
  import { type Runtime } from '@dxos/protocols/proto/dxos/config';
48
48
  import { type FeedMessage } from '@dxos/protocols/proto/dxos/echo/feed';
49
- import { type SpaceMetadata } from '@dxos/protocols/proto/dxos/echo/metadata';
49
+ import { type SpaceMetadata, EdgeReplicationSetting } from '@dxos/protocols/proto/dxos/echo/metadata';
50
50
  import { SpaceMember, type Credential, type ProfileDocument } from '@dxos/protocols/proto/dxos/halo/credentials';
51
51
  import { type DelegateSpaceInvitation } from '@dxos/protocols/proto/dxos/halo/invitations';
52
52
  import { type PeerState } from '@dxos/protocols/proto/dxos/mesh/presence';
@@ -110,6 +110,7 @@ export type DataSpaceManagerParams = {
110
110
  echoHost: EchoHost;
111
111
  invitationsManager: InvitationsManager;
112
112
  edgeConnection?: EdgeConnection;
113
+ edgeHttpClient?: EdgeHttpClient;
113
114
  meshReplicator?: MeshEchoReplicator;
114
115
  echoEdgeReplicator?: EchoEdgeReplicator;
115
116
  runtimeParams?: DataSpaceManagerRuntimeParams;
@@ -138,6 +139,7 @@ export class DataSpaceManager extends Resource {
138
139
  private readonly _echoHost: EchoHost;
139
140
  private readonly _invitationsManager: InvitationsManager;
140
141
  private readonly _edgeConnection?: EdgeConnection = undefined;
142
+ private readonly _edgeHttpClient?: EdgeHttpClient = undefined;
141
143
  private readonly _edgeFeatures?: Runtime.Client.EdgeFeatures = undefined;
142
144
  private readonly _meshReplicator?: MeshEchoReplicator = undefined;
143
145
  private readonly _echoEdgeReplicator?: EchoEdgeReplicator = undefined;
@@ -157,6 +159,7 @@ export class DataSpaceManager extends Resource {
157
159
  this._edgeConnection = params.edgeConnection;
158
160
  this._edgeFeatures = params.edgeFeatures;
159
161
  this._echoEdgeReplicator = params.echoEdgeReplicator;
162
+ this._edgeHttpClient = params.edgeHttpClient;
160
163
  this._runtimeParams = params.runtimeParams;
161
164
 
162
165
  trace.diagnostic({
@@ -391,6 +394,24 @@ export class DataSpaceManager extends Resource {
391
394
  });
392
395
  }
393
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
+
394
415
  private async _constructSpace(metadata: SpaceMetadata) {
395
416
  log('construct space', { metadata });
396
417
  const gossip = new Gossip({
@@ -482,13 +503,20 @@ export class DataSpaceManager extends Resource {
482
503
  },
483
504
  cache: metadata.cache,
484
505
  edgeConnection: this._edgeConnection,
506
+ edgeHttpClient: this._edgeHttpClient,
485
507
  edgeFeatures: this._edgeFeatures,
486
508
  });
487
509
  dataSpace.postOpen.append(async () => {
488
- await this._echoEdgeReplicator?.connectToSpace(dataSpace.id);
510
+ const setting = dataSpace.getEdgeReplicationSetting();
511
+ if (setting === EdgeReplicationSetting.ENABLED) {
512
+ await this._echoEdgeReplicator?.connectToSpace(dataSpace.id);
513
+ }
489
514
  });
490
515
  dataSpace.preClose.append(async () => {
491
- await this._echoEdgeReplicator?.disconnectFromSpace(dataSpace.id);
516
+ const setting = dataSpace.getEdgeReplicationSetting();
517
+ if (setting === EdgeReplicationSetting.ENABLED) {
518
+ await this._echoEdgeReplicator?.disconnectFromSpace(dataSpace.id);
519
+ }
492
520
  });
493
521
 
494
522
  presence.newPeer.on((peerState) => {
@@ -15,7 +15,7 @@ import {
15
15
  type Space,
16
16
  } from '@dxos/echo-pipeline';
17
17
  import { SpaceDocVersion } from '@dxos/echo-protocol';
18
- import type { EdgeConnection } from '@dxos/edge-client';
18
+ import type { EdgeConnection, EdgeHttpClient } from '@dxos/edge-client';
19
19
  import { type FeedStore, type FeedWrapper } from '@dxos/feed-store';
20
20
  import { failedInvariant } from '@dxos/invariant';
21
21
  import { type Keyring } from '@dxos/keyring';
@@ -80,6 +80,7 @@ export type DataSpaceParams = {
80
80
  callbacks?: DataSpaceCallbacks;
81
81
  cache?: SpaceCache;
82
82
  edgeConnection?: EdgeConnection;
83
+ edgeHttpClient?: EdgeHttpClient;
83
84
  edgeFeatures?: Runtime.Client.EdgeFeatures;
84
85
  };
85
86
 
@@ -101,7 +102,7 @@ export class DataSpace {
101
102
  private readonly _feedStore: FeedStore<FeedMessage>;
102
103
  private readonly _metadataStore: MetadataStore;
103
104
  private readonly _signingContext: SigningContext;
104
- private readonly _notarizationPlugin = new NotarizationPlugin();
105
+ private readonly _notarizationPlugin: NotarizationPlugin;
105
106
  private readonly _callbacks: DataSpaceCallbacks;
106
107
  private readonly _cache?: SpaceCache = undefined;
107
108
  private readonly _echoHost: EchoHost;
@@ -141,6 +142,11 @@ export class DataSpace {
141
142
  this._signingContext = params.signingContext;
142
143
  this._callbacks = params.callbacks ?? {};
143
144
  this._echoHost = params.echoHost;
145
+ this._notarizationPlugin = new NotarizationPlugin({
146
+ spaceId: this._inner.id,
147
+ edgeClient: params.edgeHttpClient,
148
+ edgeFeatures: params.edgeFeatures,
149
+ });
144
150
 
145
151
  this.authVerifier = new TrustedKeySetAuthVerifier({
146
152
  trustedKeysProvider: () =>
@@ -323,6 +329,7 @@ export class DataSpace {
323
329
  this._state = SpaceState.SPACE_INITIALIZING;
324
330
  log('new state', { state: SpaceState[this._state] });
325
331
 
332
+ log('initializing control pipeline');
326
333
  await this._initializeAndReadControlPipeline();
327
334
 
328
335
  // Allow other tasks to run before loading the data pipeline.
@@ -330,10 +337,13 @@ export class DataSpace {
330
337
 
331
338
  const ready = this.stateUpdate.waitForCondition(() => this._state === SpaceState.SPACE_READY);
332
339
 
340
+ log('initializing automerge root');
333
341
  this._automergeSpaceState.startProcessingRootDocs();
334
342
 
335
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');
336
345
  await ready;
346
+ log('space is ready');
337
347
  }
338
348
 
339
349
  private async _enterReadyState() {
@@ -350,6 +360,7 @@ export class DataSpace {
350
360
  private async _initializeAndReadControlPipeline() {
351
361
  await this._inner.controlPipeline.state.waitUntilReachedTargetTimeframe({
352
362
  ctx: this._ctx,
363
+ timeout: 10_000,
353
364
  breakOnStall: false,
354
365
  });
355
366
 
@@ -413,8 +424,16 @@ export class DataSpace {
413
424
  }
414
425
 
415
426
  if (credentials.length > 0) {
416
- // Never times out
417
- 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
+ }
418
437
 
419
438
  // Set this after credentials are notarized so that on failure we will retry.
420
439
  await this._metadataStore.setWritableFeedKeys(this.key, this.inner.controlFeedKey!, this.inner.dataFeedKey!);
@@ -551,6 +570,10 @@ export class DataSpace {
551
570
  this.stateUpdate.emit();
552
571
  }
553
572
 
573
+ getEdgeReplicationSetting() {
574
+ return this._metadataStore.getSpaceEdgeReplicationSetting(this.key);
575
+ }
576
+
554
577
  private _onFeedAdded = async (feed: FeedWrapper<any>) => {
555
578
  await this._edgeFeedReplicator!.addFeed(feed);
556
579
  };
@@ -9,12 +9,12 @@ import { describe, test, onTestFinished, vi, expect } from 'vitest';
9
9
  import { Trigger, sleep } from '@dxos/async';
10
10
  import { Context } from '@dxos/context';
11
11
  import { valueEncoding } from '@dxos/echo-pipeline';
12
- import { EdgeClient, EdgeIdentityChangedError } from '@dxos/edge-client';
12
+ import { createEphemeralEdgeIdentity, EdgeClient, EdgeIdentityChangedError } from '@dxos/edge-client';
13
13
  import { createTestEdgeWsServer } from '@dxos/edge-client/testing';
14
14
  import { FeedFactory, FeedStore } from '@dxos/feed-store';
15
15
  import { type FeedWrapper } from '@dxos/feed-store';
16
16
  import { Keyring } from '@dxos/keyring';
17
- import { PublicKey, SpaceId } from '@dxos/keys';
17
+ import { SpaceId } from '@dxos/keys';
18
18
  import { type FeedMessage } from '@dxos/protocols/proto/dxos/echo/feed';
19
19
  import { createStorage } from '@dxos/random-access-storage';
20
20
  import { openAndClose } from '@dxos/test-utils';
@@ -41,6 +41,16 @@ describe('EdgeFeedReplicator', () => {
41
41
  expect(messageSink[0].type).toEqual('get-metadata');
42
42
  });
43
43
 
44
+ test('replicates if added to a connected client', async () => {
45
+ const { endpoint, admitConnection, messageSink } = await createEdge();
46
+ const { messenger } = await createClient(endpoint);
47
+ admitConnection.wake();
48
+ await expect.poll(() => messenger.isConnected).toBeTruthy();
49
+
50
+ await attachReplicator(messenger);
51
+ await expect.poll(() => messageSink.length).toEqual(1);
52
+ });
53
+
44
54
  test('sends a block', async () => {
45
55
  const { endpoint, admitConnection, messageSink } = await createEdge();
46
56
  const { messenger } = await createClient(endpoint);
@@ -63,7 +73,7 @@ describe('EdgeFeedReplicator', () => {
63
73
  admitConnection.wake();
64
74
  await expect.poll(() => messageSink.length).toEqual(1);
65
75
 
66
- updateIdentity(messenger);
76
+ await updateIdentity(messenger);
67
77
  await messenger.reconnect.waitForCount(1);
68
78
 
69
79
  await expect.poll(() => messageSink.length).toEqual(2);
@@ -83,7 +93,7 @@ describe('EdgeFeedReplicator', () => {
83
93
 
84
94
  await expect.poll(() => sendSpy.mock.calls.length).toEqual(1);
85
95
  expect(messageSink.length).toEqual(0);
86
- updateIdentity(messenger);
96
+ await updateIdentity(messenger);
87
97
 
88
98
  await expect.poll(() => messageSink.length).toEqual(1);
89
99
  expect(messageSink[0].type).toEqual('get-metadata');
@@ -107,10 +117,9 @@ describe('EdgeFeedReplicator', () => {
107
117
 
108
118
  await expect.poll(() => sendSpy.mock.calls.length).toEqual(2);
109
119
  expect(messageSink.length).toEqual(0);
110
- updateIdentity(messenger);
120
+ await updateIdentity(messenger);
111
121
 
112
- await expect.poll(() => messageSink.length).toEqual(2);
113
- expect(messageSink[1].type).toEqual('data');
122
+ await expect.poll(() => messageSink.find((msg) => msg.type === 'data')).toBeDefined();
114
123
  });
115
124
 
116
125
  test('propagates errors unrelated to reconnect', async () => {
@@ -137,7 +146,7 @@ describe('EdgeFeedReplicator', () => {
137
146
  const { messenger } = await createClient(endpoint);
138
147
 
139
148
  await attachReplicator(messenger);
140
- updateIdentity(messenger);
149
+ await updateIdentity(messenger);
141
150
  await sleep(100);
142
151
  admitConnection.wake();
143
152
 
@@ -154,7 +163,7 @@ describe('EdgeFeedReplicator', () => {
154
163
  await sleep(10);
155
164
 
156
165
  admitConnection.reset();
157
- updateIdentity(messenger);
166
+ await updateIdentity(messenger);
158
167
  await appendMessage(feed);
159
168
  await sleep(20);
160
169
  admitConnection.wake();
@@ -171,7 +180,7 @@ describe('EdgeFeedReplicator', () => {
171
180
  await sleep(10);
172
181
 
173
182
  void appendMessage(feed);
174
- updateIdentity(messenger);
183
+ await updateIdentity(messenger);
175
184
 
176
185
  await expect.poll(() => feedLength()).toEqual(1);
177
186
  });
@@ -203,8 +212,7 @@ describe('EdgeFeedReplicator', () => {
203
212
  };
204
213
 
205
214
  const createClient = async (endpoint: string) => {
206
- const peerKey = PublicKey.random().toHex();
207
- const messenger = new EdgeClient(peerKey, peerKey, { socketEndpoint: endpoint });
215
+ const messenger = new EdgeClient(await createEphemeralEdgeIdentity(), { socketEndpoint: endpoint });
208
216
  const sendSpy = vi.spyOn(messenger, 'send');
209
217
  await openAndClose(messenger);
210
218
  return { messenger, sendSpy };
@@ -235,9 +243,8 @@ describe('EdgeFeedReplicator', () => {
235
243
  return feedStore.openFeed(await keyring.createKey(), { writable: true });
236
244
  };
237
245
 
238
- const updateIdentity = (messenger: EdgeClient) => {
239
- const identityKey = PublicKey.random().toHex();
240
- messenger.setIdentity({ peerKey: messenger.peerKey, identityKey });
246
+ const updateIdentity = async (messenger: EdgeClient) => {
247
+ messenger.setIdentity(await createEphemeralEdgeIdentity());
241
248
  };
242
249
 
243
250
  const appendMessage = (feed: FeedWrapper<FeedMessage>) => feed.append({ timeframe: new Timeframe() });