@dxos/client-services 0.5.9-main.bdf733d → 0.5.9-main.bf3bb8f

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 (66) hide show
  1. package/dist/lib/browser/{chunk-KNGR7BYM.mjs → chunk-IUSAD4RP.mjs} +1678 -935
  2. package/dist/lib/browser/chunk-IUSAD4RP.mjs.map +7 -0
  3. package/dist/lib/browser/index.mjs +13 -4
  4. package/dist/lib/browser/index.mjs.map +1 -1
  5. package/dist/lib/browser/meta.json +1 -1
  6. package/dist/lib/browser/packlets/testing/index.mjs +20 -13
  7. package/dist/lib/browser/packlets/testing/index.mjs.map +3 -3
  8. package/dist/lib/node/{chunk-WWHTBQNR.cjs → chunk-5PALJZPW.cjs} +1936 -1200
  9. package/dist/lib/node/chunk-5PALJZPW.cjs.map +7 -0
  10. package/dist/lib/node/index.cjs +53 -44
  11. package/dist/lib/node/index.cjs.map +1 -1
  12. package/dist/lib/node/meta.json +1 -1
  13. package/dist/lib/node/packlets/testing/index.cjs +26 -19
  14. package/dist/lib/node/packlets/testing/index.cjs.map +3 -3
  15. package/dist/types/src/packlets/identity/contacts-service.d.ts +14 -0
  16. package/dist/types/src/packlets/identity/contacts-service.d.ts.map +1 -0
  17. package/dist/types/src/packlets/identity/default-space-state-machine.d.ts +19 -0
  18. package/dist/types/src/packlets/identity/default-space-state-machine.d.ts.map +1 -0
  19. package/dist/types/src/packlets/identity/identity-service.d.ts +14 -7
  20. package/dist/types/src/packlets/identity/identity-service.d.ts.map +1 -1
  21. package/dist/types/src/packlets/identity/identity.d.ts +4 -1
  22. package/dist/types/src/packlets/identity/identity.d.ts.map +1 -1
  23. package/dist/types/src/packlets/invitations/invitations-handler.d.ts.map +1 -1
  24. package/dist/types/src/packlets/invitations/space-invitation-protocol.d.ts.map +1 -1
  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 -1
  27. package/dist/types/src/packlets/services/service-host.d.ts.map +1 -1
  28. package/dist/types/src/packlets/spaces/automerge-space-state.d.ts +4 -1
  29. package/dist/types/src/packlets/spaces/automerge-space-state.d.ts.map +1 -1
  30. package/dist/types/src/packlets/spaces/data-space-manager.d.ts +15 -4
  31. package/dist/types/src/packlets/spaces/data-space-manager.d.ts.map +1 -1
  32. package/dist/types/src/packlets/spaces/data-space.d.ts +10 -9
  33. package/dist/types/src/packlets/spaces/data-space.d.ts.map +1 -1
  34. package/dist/types/src/packlets/spaces/epoch-migrations.d.ts +23 -0
  35. package/dist/types/src/packlets/spaces/epoch-migrations.d.ts.map +1 -0
  36. package/dist/types/src/packlets/spaces/spaces-service.d.ts +5 -2
  37. package/dist/types/src/packlets/spaces/spaces-service.d.ts.map +1 -1
  38. package/dist/types/src/packlets/storage/index.d.ts +1 -0
  39. package/dist/types/src/packlets/storage/index.d.ts.map +1 -1
  40. package/dist/types/src/packlets/storage/profile-archive.d.ts +14 -0
  41. package/dist/types/src/packlets/storage/profile-archive.d.ts.map +1 -0
  42. package/dist/types/src/packlets/testing/test-builder.d.ts +8 -6
  43. package/dist/types/src/packlets/testing/test-builder.d.ts.map +1 -1
  44. package/dist/types/src/version.d.ts +1 -1
  45. package/package.json +36 -36
  46. package/src/packlets/identity/contacts-service.ts +85 -0
  47. package/src/packlets/identity/default-space-state-machine.ts +44 -0
  48. package/src/packlets/identity/identity-service.test.ts +35 -5
  49. package/src/packlets/identity/identity-service.ts +82 -8
  50. package/src/packlets/identity/identity.ts +25 -2
  51. package/src/packlets/invitations/invitations-handler.ts +13 -5
  52. package/src/packlets/invitations/space-invitation-protocol.ts +11 -32
  53. package/src/packlets/services/service-context.ts +1 -4
  54. package/src/packlets/services/service-host.ts +23 -42
  55. package/src/packlets/spaces/automerge-space-state.ts +11 -2
  56. package/src/packlets/spaces/data-space-manager.test.ts +46 -1
  57. package/src/packlets/spaces/data-space-manager.ts +136 -33
  58. package/src/packlets/spaces/data-space.ts +89 -140
  59. package/src/packlets/spaces/epoch-migrations.ts +154 -0
  60. package/src/packlets/spaces/spaces-service.ts +56 -4
  61. package/src/packlets/storage/index.ts +1 -0
  62. package/src/packlets/storage/profile-archive.ts +111 -0
  63. package/src/packlets/testing/test-builder.ts +12 -10
  64. package/src/version.ts +1 -1
  65. package/dist/lib/browser/chunk-KNGR7BYM.mjs.map +0 -7
  66. package/dist/lib/node/chunk-WWHTBQNR.cjs.map +0 -7
@@ -16,7 +16,7 @@ import { type Signer } from '@dxos/crypto';
16
16
  import { type Space } from '@dxos/echo-pipeline';
17
17
  import { writeMessages } from '@dxos/feed-store';
18
18
  import { invariant } from '@dxos/invariant';
19
- import { PublicKey } from '@dxos/keys';
19
+ import { PublicKey, type SpaceId } from '@dxos/keys';
20
20
  import { log } from '@dxos/log';
21
21
  import { type FeedMessage } from '@dxos/protocols/proto/dxos/echo/feed';
22
22
  import {
@@ -26,10 +26,12 @@ import {
26
26
  } from '@dxos/protocols/proto/dxos/halo/credentials';
27
27
  import { type DeviceAdmissionRequest } from '@dxos/protocols/proto/dxos/halo/invitations';
28
28
  import { type Presence } from '@dxos/teleport-extension-gossip';
29
+ import { Timeframe } from '@dxos/timeframe';
29
30
  import { trace } from '@dxos/tracing';
30
31
  import { type ComplexMap, ComplexSet } from '@dxos/util';
31
32
 
32
33
  import { TrustedKeySetAuthVerifier } from './authenticator';
34
+ import { DefaultSpaceStateMachine } from './default-space-state-machine';
33
35
 
34
36
  export type IdentityParams = {
35
37
  identityKey: PublicKey;
@@ -49,6 +51,7 @@ export class Identity {
49
51
  private readonly _presence?: Presence;
50
52
  private readonly _deviceStateMachine: DeviceStateMachine;
51
53
  private readonly _profileStateMachine: ProfileStateMachine;
54
+ private readonly _defaultSpaceStateMachine: DefaultSpaceStateMachine;
52
55
  public readonly authVerifier: TrustedKeySetAuthVerifier;
53
56
 
54
57
  public readonly identityKey: PublicKey;
@@ -75,6 +78,10 @@ export class Identity {
75
78
  identityKey: this.identityKey,
76
79
  onUpdate: () => this.stateUpdate.emit(),
77
80
  });
81
+ this._defaultSpaceStateMachine = new DefaultSpaceStateMachine({
82
+ identityKey: this.identityKey,
83
+ onUpdate: () => this.stateUpdate.emit(),
84
+ });
78
85
 
79
86
  this.authVerifier = new TrustedKeySetAuthVerifier({
80
87
  trustedKeysProvider: () => new ComplexSet(PublicKey.hash, this.authorizedDeviceKeys.keys()),
@@ -88,17 +95,24 @@ export class Identity {
88
95
  return this._deviceStateMachine.authorizedDeviceKeys;
89
96
  }
90
97
 
98
+ get defaultSpaceId(): SpaceId | undefined {
99
+ return this._defaultSpaceStateMachine.spaceId;
100
+ }
101
+
91
102
  @trace.span()
92
103
  async open(ctx: Context) {
104
+ await this._presence?.open();
93
105
  await this.space.spaceState.addCredentialProcessor(this._deviceStateMachine);
94
106
  await this.space.spaceState.addCredentialProcessor(this._profileStateMachine);
107
+ await this.space.spaceState.addCredentialProcessor(this._defaultSpaceStateMachine);
95
108
  await this.space.open(ctx);
96
109
  }
97
110
 
98
111
  @trace.span()
99
112
  async close(ctx: Context) {
100
- await this._presence?.destroy();
113
+ await this._presence?.close();
101
114
  await this.authVerifier.close();
115
+ await this.space.spaceState.removeCredentialProcessor(this._defaultSpaceStateMachine);
102
116
  await this.space.spaceState.removeCredentialProcessor(this._profileStateMachine);
103
117
  await this.space.spaceState.removeCredentialProcessor(this._deviceStateMachine);
104
118
  await this.space.close();
@@ -157,6 +171,15 @@ export class Identity {
157
171
  return createCredentialSignerWithKey(this._signer, this.deviceKey);
158
172
  }
159
173
 
174
+ async updateDefaultSpace(spaceId: SpaceId) {
175
+ const credential = await this.getDeviceCredentialSigner().createCredential({
176
+ subject: this.identityKey,
177
+ assertion: { '@type': 'dxos.halo.credentials.DefaultSpace', spaceId },
178
+ });
179
+ const receipt = await this.controlPipeline.writer.write({ credential: { credential } });
180
+ await this.controlPipeline.state.waitUntilTimeframe(new Timeframe([[receipt.feedKey, receipt.seq]]));
181
+ }
182
+
160
183
  async admitDevice({ deviceKey, controlFeedKey, dataFeedKey }: DeviceAdmissionRequest) {
161
184
  log('Admitting device:', {
162
185
  identityKey: this.identityKey,
@@ -435,11 +435,19 @@ export class InvitationsHandler {
435
435
  }
436
436
 
437
437
  private _logStateUpdate(invitation: Invitation, actor: any, newState: Invitation.State) {
438
- log('invitation state update', {
439
- actor: actor?.constructor.name,
440
- newState: stateToString(newState),
441
- oldState: stateToString(invitation.state),
442
- });
438
+ if (this._isNotTerminal(newState)) {
439
+ log('invitation state update', {
440
+ actor: actor?.constructor.name,
441
+ newState: stateToString(newState),
442
+ oldState: stateToString(invitation.state),
443
+ });
444
+ } else {
445
+ log.info('invitation state update', {
446
+ actor: actor?.constructor.name,
447
+ newState: stateToString(newState),
448
+ oldState: stateToString(invitation.state),
449
+ });
450
+ }
443
451
  }
444
452
 
445
453
  private _isNotTerminal(currentState: Invitation.State): boolean {
@@ -3,7 +3,6 @@
3
3
  //
4
4
 
5
5
  import {
6
- createAdmissionCredentials,
7
6
  createCancelDelegatedSpaceInvitationCredential,
8
7
  createDelegatedSpaceInvitationCredential,
9
8
  getCredentialAssertion,
@@ -21,7 +20,6 @@ import {
21
20
  SpaceNotFoundError,
22
21
  } from '@dxos/protocols';
23
22
  import { Invitation } from '@dxos/protocols/proto/dxos/client/services';
24
- import { type FeedMessage } from '@dxos/protocols/proto/dxos/echo/feed';
25
23
  import { SpaceMember, type ProfileDocument } from '@dxos/protocols/proto/dxos/halo/credentials';
26
24
  import {
27
25
  type AdmissionRequest,
@@ -73,41 +71,22 @@ export class SpaceInvitationProtocol implements InvitationProtocol {
73
71
  request: AdmissionRequest,
74
72
  guestProfile?: ProfileDocument | undefined,
75
73
  ): Promise<AdmissionResponse> {
76
- invariant(this._spaceKey);
77
- const space = this._spaceManager.spaces.get(this._spaceKey);
78
- invariant(space);
79
-
80
- invariant(request.space);
81
- const { identityKey, deviceKey } = request.space;
74
+ invariant(this._spaceKey && request.space);
75
+ log('writing guest credentials', { host: this._signingContext.deviceKey, guest: request.space.deviceKey });
82
76
 
83
- if (space.inner.spaceState.getMemberRole(identityKey) !== SpaceMember.Role.REMOVED) {
84
- throw new AlreadyJoinedError();
85
- }
86
-
87
- log('writing guest credentials', { host: this._signingContext.deviceKey, guest: deviceKey });
88
- // TODO(burdon): Check if already admitted.
89
- const credentials: FeedMessage.Payload[] = await createAdmissionCredentials(
90
- this._signingContext.credentialSigner,
91
- identityKey,
92
- space.key,
93
- space.inner.genesisFeedKey,
94
- invitation.role ?? SpaceMember.Role.ADMIN,
95
- space.inner.spaceState.membershipChainHeads,
96
- guestProfile,
97
- invitation.delegationCredentialId,
98
- );
99
-
100
- // TODO(dmaretskyi): Refactor.
101
- invariant(credentials[0].credential);
102
- const spaceMemberCredential = credentials[0].credential.credential;
103
- invariant(getCredentialAssertion(spaceMemberCredential)['@type'] === 'dxos.halo.credentials.SpaceMember');
104
-
105
- await writeMessages(space.inner.controlPipeline.writer, credentials);
77
+ const spaceMemberCredential = await this._spaceManager.admitMember({
78
+ spaceKey: this._spaceKey,
79
+ identityKey: request.space.identityKey,
80
+ role: invitation.role ?? SpaceMember.Role.ADMIN,
81
+ profile: guestProfile,
82
+ delegationCredentialId: invitation.delegationCredentialId,
83
+ });
106
84
 
85
+ const space = this._spaceManager.spaces.get(this._spaceKey);
107
86
  return {
108
87
  space: {
109
88
  credential: spaceMemberCredential,
110
- controlTimeframe: space.inner.controlPipeline.state.timeframe,
89
+ controlTimeframe: space?.inner.controlPipeline.state.timeframe,
111
90
  },
112
91
  };
113
92
  }
@@ -120,10 +120,7 @@ export class ServiceContext extends Resource {
120
120
  this._runtimeParams as IdentityManagerRuntimeParams,
121
121
  );
122
122
 
123
- this.echoHost = new EchoHost({
124
- kv: this.level,
125
- storage: this.storage,
126
- });
123
+ this.echoHost = new EchoHost({ kv: this.level });
127
124
 
128
125
  this.invitations = new InvitationsHandler(this.networkManager, _runtimeParams?.invitationConnectionDefaultParams);
129
126
  this.invitationsManager = new InvitationsManager(
@@ -3,11 +3,9 @@
3
3
  //
4
4
 
5
5
  import { Event, synchronized } from '@dxos/async';
6
- import { clientServiceBundle, defaultKey, type ClientServices, PropertiesType } from '@dxos/client-protocol';
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 { type ObjectStructure, encodeReference, type SpaceDoc } from '@dxos/echo-protocol';
10
- import { getTypeReference } from '@dxos/echo-schema';
11
9
  import { invariant } from '@dxos/invariant';
12
10
  import { PublicKey } from '@dxos/keys';
13
11
  import { type LevelDB } from '@dxos/kv-store';
@@ -18,7 +16,6 @@ import { trace } from '@dxos/protocols';
18
16
  import { SystemStatus } from '@dxos/protocols/proto/dxos/client/services';
19
17
  import { type Storage } from '@dxos/random-access-storage';
20
18
  import { TRACE_PROCESSOR, trace as Trace } from '@dxos/tracing';
21
- import { assignDeep } from '@dxos/util';
22
19
  import { WebsocketRpcClient } from '@dxos/websocket-rpc';
23
20
 
24
21
  import { ServiceContext, type ServiceContextRuntimeParams } from './service-context';
@@ -31,6 +28,7 @@ import {
31
28
  createDiagnostics,
32
29
  } from '../diagnostics';
33
30
  import { IdentityServiceImpl, type CreateIdentityOptions } from '../identity';
31
+ import { ContactsServiceImpl } from '../identity/contacts-service';
34
32
  import { InvitationsServiceImpl } from '../invitations';
35
33
  import { Lock, type ResourceLock } from '../locks';
36
34
  import { LoggingServiceImpl } from '../logging';
@@ -86,7 +84,7 @@ export class ClientServicesHost {
86
84
  private _devtoolsProxy?: WebsocketRpcClient<{}, ClientServices>;
87
85
 
88
86
  private _serviceContext!: ServiceContext;
89
- private readonly _runtimeParams?: ServiceContextRuntimeParams;
87
+ private readonly _runtimeParams: ServiceContextRuntimeParams;
90
88
  private diagnosticsBroadcastHandler: CollectDiagnosticsBroadcastHandler;
91
89
 
92
90
  @Trace.info()
@@ -109,7 +107,7 @@ export class ClientServicesHost {
109
107
  this._storage = storage;
110
108
  this._level = level;
111
109
  this._callbacks = callbacks;
112
- this._runtimeParams = runtimeParams;
110
+ this._runtimeParams = runtimeParams ?? {};
113
111
 
114
112
  if (config) {
115
113
  this.initialize({ config, transportFactory, signalManager });
@@ -254,14 +252,26 @@ export class ClientServicesHost {
254
252
  this._runtimeParams,
255
253
  );
256
254
 
255
+ const dataSpaceManagerProvider = async () => {
256
+ await this._serviceContext.initialized.wait();
257
+ return this._serviceContext.dataSpaceManager!;
258
+ };
259
+
260
+ const identityService = new IdentityServiceImpl(
261
+ this._serviceContext.identityManager,
262
+ this._serviceContext.keyring,
263
+ () => this._serviceContext.dataSpaceManager!,
264
+ (params) => this._createIdentity(params),
265
+ (profile) => this._serviceContext.broadcastProfileUpdate(profile),
266
+ );
267
+
257
268
  this._serviceRegistry.setServices({
258
269
  SystemService: this._systemService,
259
-
260
- IdentityService: new IdentityServiceImpl(
261
- (params) => this._createIdentity(params),
270
+ IdentityService: identityService,
271
+ ContactsService: new ContactsServiceImpl(
262
272
  this._serviceContext.identityManager,
263
- this._serviceContext.keyring,
264
- (profile) => this._serviceContext.broadcastProfileUpdate(profile),
273
+ this._serviceContext.spaceManager,
274
+ dataSpaceManagerProvider,
265
275
  ),
266
276
 
267
277
  InvitationsService: new InvitationsServiceImpl(this._serviceContext.invitationsManager),
@@ -271,10 +281,7 @@ export class ClientServicesHost {
271
281
  SpacesService: new SpacesServiceImpl(
272
282
  this._serviceContext.identityManager,
273
283
  this._serviceContext.spaceManager,
274
- async () => {
275
- await this._serviceContext.initialized.wait();
276
- return this._serviceContext.dataSpaceManager!;
277
- },
284
+ dataSpaceManagerProvider,
278
285
  ),
279
286
 
280
287
  DataService: this._serviceContext.echoHost.dataService,
@@ -294,6 +301,7 @@ export class ClientServicesHost {
294
301
  });
295
302
 
296
303
  await this._serviceContext.open(ctx);
304
+ await identityService.open();
297
305
 
298
306
  const devtoolsProxy = this._config?.get('runtime.client.devtoolsProxy');
299
307
  if (devtoolsProxy) {
@@ -349,34 +357,7 @@ export class ClientServicesHost {
349
357
 
350
358
  private async _createIdentity(params: CreateIdentityOptions) {
351
359
  const identity = await this._serviceContext.createIdentity(params);
352
-
353
- // Setup default space.
354
360
  await this._serviceContext.initialized.wait();
355
- const space = await this._serviceContext.dataSpaceManager!.createSpace();
356
-
357
- const automergeIndex = space.automergeSpaceState.rootUrl;
358
- invariant(automergeIndex);
359
- const document = this._serviceContext.echoHost.automergeRepo.find<SpaceDoc>(automergeIndex as any);
360
- await document.whenReady();
361
-
362
- // TODO(dmaretskyi): Better API for low-level data access.
363
- const properties: ObjectStructure = {
364
- system: {
365
- type: encodeReference(getTypeReference(PropertiesType)!),
366
- },
367
- data: {
368
- [defaultKey]: identity.identityKey.toHex(),
369
- },
370
- meta: {
371
- keys: [],
372
- },
373
- };
374
- const propertiesId = PublicKey.random().toHex();
375
- document.change((doc: SpaceDoc) => {
376
- assignDeep(doc, ['objects', propertiesId], properties);
377
- });
378
-
379
- await this._serviceContext.echoHost.flush();
380
361
  return identity;
381
362
  }
382
363
  }
@@ -3,10 +3,11 @@
3
3
  //
4
4
 
5
5
  import { Event } from '@dxos/async';
6
+ import { Resource, type Context } from '@dxos/context';
6
7
  import { type CredentialProcessor, type SpecificCredential, checkCredentialType } from '@dxos/credentials';
7
8
  import { type Credential, type Epoch } from '@dxos/protocols/proto/dxos/halo/credentials';
8
9
 
9
- export class AutomergeSpaceState implements CredentialProcessor {
10
+ export class AutomergeSpaceState extends Resource implements CredentialProcessor {
10
11
  public rootUrl: string | undefined = undefined;
11
12
  public lastEpoch: SpecificCredential<Epoch> | undefined = undefined;
12
13
 
@@ -14,7 +15,15 @@ export class AutomergeSpaceState implements CredentialProcessor {
14
15
 
15
16
  private _isProcessingRootDocs = false;
16
17
 
17
- constructor(private readonly _onNewRoot: (rootUrl: string) => void) {}
18
+ constructor(private readonly _onNewRoot: (rootUrl: string) => void) {
19
+ super();
20
+ }
21
+
22
+ protected override async _open(ctx: Context): Promise<void> {}
23
+
24
+ protected override async _close(ctx: Context): Promise<void> {
25
+ this._isProcessingRootDocs = false;
26
+ }
18
27
 
19
28
  async processCredential(credential: Credential) {
20
29
  if (!checkCredentialType(credential, 'dxos.halo.credentials.Epoch')) {
@@ -12,7 +12,7 @@ import { log } from '@dxos/log';
12
12
  import { SpaceState } from '@dxos/protocols/proto/dxos/client/services';
13
13
  import { describe, openAndClose, test } from '@dxos/test';
14
14
 
15
- import { TestBuilder } from '../testing';
15
+ import { TestBuilder, type TestPeer } from '../testing';
16
16
 
17
17
  describe('DataSpaceManager', () => {
18
18
  test('create space', async () => {
@@ -168,5 +168,50 @@ describe('DataSpaceManager', () => {
168
168
  500,
169
169
  );
170
170
  });
171
+
172
+ test('activate opens a lazily loaded space', async () => {
173
+ const builder = new TestBuilder();
174
+
175
+ const peer = builder.createPeer();
176
+ await peer.createIdentity();
177
+ await openAndClose(peer.echoHost, peer.dataSpaceManager);
178
+
179
+ await peer.dataSpaceManager.createSpace();
180
+ await reloadDataSpaces(peer);
181
+
182
+ const space = getFirstSpace(peer);
183
+ expect(space.state).to.equal(SpaceState.CLOSED);
184
+ await space.activate();
185
+ await asyncTimeout(
186
+ space.stateUpdate.waitForCondition(() => space.state === SpaceState.READY),
187
+ 500,
188
+ );
189
+ });
190
+
191
+ test('deactivate lazily loaded space ', async () => {
192
+ const builder = new TestBuilder();
193
+
194
+ const peer = builder.createPeer();
195
+ await peer.createIdentity();
196
+ await openAndClose(peer.echoHost, peer.dataSpaceManager);
197
+
198
+ await peer.dataSpaceManager.createSpace();
199
+ await reloadDataSpaces(peer);
200
+
201
+ await getFirstSpace(peer).deactivate();
202
+
203
+ await reloadDataSpaces(peer);
204
+
205
+ expect(getFirstSpace(peer).state).to.eq(SpaceState.INACTIVE);
206
+ });
171
207
  });
208
+
209
+ const reloadDataSpaces = async (peer: TestPeer) => {
210
+ await peer.dataSpaceManager.close();
211
+ await peer.dataSpaceManager.open();
212
+ };
213
+
214
+ const getFirstSpace = (peer: TestPeer) => {
215
+ return Array.from(peer.dataSpaceManager.spaces.values())[0];
216
+ };
172
217
  });