@dxos/client-services 0.5.3-main.f752aaa → 0.5.3-main.fffc127

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 (53) hide show
  1. package/dist/lib/browser/{chunk-3ZUGGKNX.mjs → chunk-OYIJIAJI.mjs} +293 -120
  2. package/dist/lib/browser/chunk-OYIJIAJI.mjs.map +7 -0
  3. package/dist/lib/browser/index.mjs +3 -1
  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 +1 -1
  7. package/dist/lib/node/{chunk-TQQUIYBW.cjs → chunk-FIKOGQP2.cjs} +293 -119
  8. package/dist/lib/node/chunk-FIKOGQP2.cjs.map +7 -0
  9. package/dist/lib/node/index.cjs +45 -43
  10. package/dist/lib/node/index.cjs.map +1 -1
  11. package/dist/lib/node/meta.json +1 -1
  12. package/dist/lib/node/packlets/testing/index.cjs +8 -8
  13. package/dist/types/src/packlets/devtools/feeds.d.ts.map +1 -1
  14. package/dist/types/src/packlets/devtools/keys.d.ts.map +1 -1
  15. package/dist/types/src/packlets/devtools/metadata.d.ts.map +1 -1
  16. package/dist/types/src/packlets/devtools/network.d.ts.map +1 -1
  17. package/dist/types/src/packlets/identity/identity-manager.d.ts.map +1 -1
  18. package/dist/types/src/packlets/invitations/device-invitation-protocol.d.ts +2 -1
  19. package/dist/types/src/packlets/invitations/device-invitation-protocol.d.ts.map +1 -1
  20. package/dist/types/src/packlets/invitations/invitation-protocol.d.ts +1 -0
  21. package/dist/types/src/packlets/invitations/invitation-protocol.d.ts.map +1 -1
  22. package/dist/types/src/packlets/invitations/invitations-handler.d.ts +1 -0
  23. package/dist/types/src/packlets/invitations/invitations-handler.d.ts.map +1 -1
  24. package/dist/types/src/packlets/invitations/invitations-manager.d.ts.map +1 -1
  25. package/dist/types/src/packlets/invitations/space-invitation-protocol.d.ts +3 -2
  26. package/dist/types/src/packlets/invitations/space-invitation-protocol.d.ts.map +1 -1
  27. package/dist/types/src/packlets/services/service-host.d.ts +1 -1
  28. package/dist/types/src/packlets/services/service-host.d.ts.map +1 -1
  29. package/dist/types/src/packlets/services/util.d.ts.map +1 -1
  30. package/dist/types/src/packlets/spaces/data-space-manager.d.ts +2 -0
  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 +6 -1
  33. package/dist/types/src/packlets/spaces/data-space.d.ts.map +1 -1
  34. package/dist/types/src/packlets/spaces/spaces-service.d.ts +2 -1
  35. package/dist/types/src/packlets/spaces/spaces-service.d.ts.map +1 -1
  36. package/dist/types/src/packlets/testing/credential-utils.d.ts.map +1 -1
  37. package/dist/types/src/packlets/testing/test-builder.d.ts +2 -2
  38. package/dist/types/src/packlets/testing/test-builder.d.ts.map +1 -1
  39. package/dist/types/src/version.d.ts +1 -1
  40. package/package.json +36 -36
  41. package/src/packlets/diagnostics/diagnostics.ts +1 -0
  42. package/src/packlets/identity/identity-manager.ts +1 -0
  43. package/src/packlets/identity/identity.test.ts +3 -0
  44. package/src/packlets/invitations/device-invitation-protocol.ts +5 -1
  45. package/src/packlets/invitations/invitation-protocol.ts +2 -0
  46. package/src/packlets/invitations/invitations-manager.ts +5 -0
  47. package/src/packlets/invitations/space-invitation-protocol.ts +29 -2
  48. package/src/packlets/spaces/data-space-manager.ts +100 -10
  49. package/src/packlets/spaces/data-space.ts +35 -8
  50. package/src/packlets/spaces/spaces-service.ts +46 -15
  51. package/src/version.ts +1 -1
  52. package/dist/lib/browser/chunk-3ZUGGKNX.mjs.map +0 -7
  53. package/dist/lib/node/chunk-TQQUIYBW.cjs.map +0 -7
@@ -3,26 +3,43 @@
3
3
  //
4
4
 
5
5
  import { Event, synchronized, trackLeaks } from '@dxos/async';
6
- import { Context, cancelWithContext } from '@dxos/context';
7
- import { getCredentialAssertion, type CredentialSigner, type DelegateInvitationCredential } from '@dxos/credentials';
6
+ import { type Doc } from '@dxos/automerge/automerge';
7
+ import { type AutomergeUrl } from '@dxos/automerge/automerge-repo';
8
+ import { cancelWithContext, Context } from '@dxos/context';
9
+ import {
10
+ type CredentialSigner,
11
+ type DelegateInvitationCredential,
12
+ getCredentialAssertion,
13
+ type MemberInfo,
14
+ } from '@dxos/credentials';
8
15
  import { type EchoHost } from '@dxos/echo-db';
9
- import { type MetadataStore, type Space, type SpaceManager } from '@dxos/echo-pipeline';
16
+ import {
17
+ AuthStatus,
18
+ type MetadataStore,
19
+ type Space,
20
+ type SpaceManager,
21
+ type SpaceProtocol,
22
+ type SpaceProtocolSession,
23
+ } from '@dxos/echo-pipeline';
24
+ import { type SpaceDoc } from '@dxos/echo-protocol';
10
25
  import { type FeedStore } from '@dxos/feed-store';
11
26
  import { invariant } from '@dxos/invariant';
12
27
  import { type Keyring } from '@dxos/keyring';
13
28
  import { PublicKey } from '@dxos/keys';
14
29
  import { log } from '@dxos/log';
15
- import { trace } from '@dxos/protocols';
30
+ import { trace as Trace } from '@dxos/protocols';
16
31
  import { Invitation, SpaceState } from '@dxos/protocols/proto/dxos/client/services';
17
32
  import { type FeedMessage } from '@dxos/protocols/proto/dxos/echo/feed';
18
33
  import { type SpaceMetadata } from '@dxos/protocols/proto/dxos/echo/metadata';
19
- import { type Credential, type ProfileDocument } from '@dxos/protocols/proto/dxos/halo/credentials';
34
+ import { type Credential, type ProfileDocument, SpaceMember } from '@dxos/protocols/proto/dxos/halo/credentials';
20
35
  import { type DelegateSpaceInvitation } from '@dxos/protocols/proto/dxos/halo/invitations';
36
+ import { type PeerState } from '@dxos/protocols/proto/dxos/mesh/presence';
21
37
  import { Gossip, Presence } from '@dxos/teleport-extension-gossip';
22
38
  import { type Timeframe } from '@dxos/timeframe';
39
+ import { trace } from '@dxos/tracing';
23
40
  import { ComplexMap, deferFunction, forEachAsync } from '@dxos/util';
24
41
 
25
- import { DataSpace } from './data-space';
42
+ import { DataSpace, findPropertiesObject } from './data-space';
26
43
  import { spaceGenesis } from './genesis';
27
44
  import { createAuthProvider } from '../identity';
28
45
  import { type InvitationsManager } from '../invitations';
@@ -90,6 +107,31 @@ export class DataSpaceManager {
90
107
  } = params ?? {};
91
108
  this._spaceMemberPresenceAnnounceInterval = spaceMemberPresenceAnnounceInterval;
92
109
  this._spaceMemberPresenceOfflineTimeout = spaceMemberPresenceOfflineTimeout;
110
+
111
+ trace.diagnostic({
112
+ id: 'spaces',
113
+ name: 'Spaces',
114
+ fetch: async () => {
115
+ return Array.from(this._spaces.values()).map((space) => {
116
+ const rootUrl = space.automergeSpaceState.rootUrl;
117
+ const rootHandle = rootUrl ? this._echoHost.automergeRepo.find(rootUrl as AutomergeUrl) : undefined;
118
+ const rootDoc = rootHandle?.docSync() as Doc<SpaceDoc> | undefined;
119
+
120
+ const properties = rootDoc && findPropertiesObject(rootDoc);
121
+
122
+ return {
123
+ key: space.key.toHex(),
124
+ state: SpaceState[space.state],
125
+ name: properties?.[1].data.name ?? null,
126
+ inlineObjects: rootDoc ? Object.keys(rootDoc.objects ?? {}).length : null,
127
+ linkedObjects: rootDoc ? Object.keys(rootDoc.links ?? {}).length : null,
128
+ credentials: space.inner.spaceState.credentials.length,
129
+ members: space.inner.spaceState.members.size,
130
+ rootUrl,
131
+ };
132
+ });
133
+ },
134
+ });
93
135
  }
94
136
 
95
137
  // TODO(burdon): Remove.
@@ -100,7 +142,7 @@ export class DataSpaceManager {
100
142
  @synchronized
101
143
  async open() {
102
144
  log('open');
103
- log.trace('dxos.echo.data-space-manager.open', trace.begin({ id: this._instanceId }));
145
+ log.trace('dxos.echo.data-space-manager.open', Trace.begin({ id: this._instanceId }));
104
146
  log('metadata loaded', { spaces: this._metadataStore.spaces.length });
105
147
 
106
148
  await forEachAsync(this._metadataStore.spaces, async (spaceMetadata) => {
@@ -121,7 +163,7 @@ export class DataSpaceManager {
121
163
  }
122
164
  }
123
165
 
124
- log.trace('dxos.echo.data-space-manager.open', trace.end({ id: this._instanceId }));
166
+ log.trace('dxos.echo.data-space-manager.open', Trace.end({ id: this._instanceId }));
125
167
  }
126
168
 
127
169
  @synchronized
@@ -153,10 +195,10 @@ export class DataSpaceManager {
153
195
 
154
196
  log('creating space...', { spaceKey });
155
197
 
156
- const automergeRootUrl = await this._echoHost.createSpaceRoot(spaceKey);
198
+ const root = await this._echoHost.createSpaceRoot(spaceKey);
157
199
  const space = await this._constructSpace(metadata);
158
200
 
159
- const credentials = await spaceGenesis(this._keyring, this._signingContext, space.inner, automergeRootUrl);
201
+ const credentials = await spaceGenesis(this._keyring, this._signingContext, space.inner, root.url);
160
202
  await this._metadataStore.addSpace(metadata);
161
203
 
162
204
  const memberCredential = credentials[1];
@@ -246,6 +288,11 @@ export class DataSpaceManager {
246
288
  onAuthFailure: () => {
247
289
  log.warn('auth failure');
248
290
  },
291
+ onMemberRolesChanged: async (members: MemberInfo[]) => {
292
+ if (dataSpace?.state === SpaceState.READY) {
293
+ this._handleMemberRoleChanges(presence, space.protocol, members);
294
+ }
295
+ },
249
296
  memberKey: this._signingContext.identityKey,
250
297
  onDelegatedInvitationStatusChange: (invitation, isActive) => {
251
298
  return this._handleInvitationStatusChange(dataSpace, invitation, isActive);
@@ -272,6 +319,7 @@ export class DataSpaceManager {
272
319
  log('after space ready', { space: space.key, open: this._isOpen });
273
320
  if (this._isOpen) {
274
321
  await this._createDelegatedInvitations(dataSpace, [...space.spaceState.invitations.entries()]);
322
+ this._handleMemberRoleChanges(presence, space.protocol, [...space.spaceState.members.values()]);
275
323
  this.updated.emit();
276
324
  }
277
325
  },
@@ -282,6 +330,12 @@ export class DataSpaceManager {
282
330
  cache: metadata.cache,
283
331
  });
284
332
 
333
+ presence.newPeer.on((peerState) => {
334
+ if (dataSpace.state === SpaceState.READY) {
335
+ this._handleNewPeerConnected(space, peerState);
336
+ }
337
+ });
338
+
285
339
  if (metadata.state !== SpaceState.INACTIVE) {
286
340
  await dataSpace.open();
287
341
  }
@@ -294,6 +348,42 @@ export class DataSpaceManager {
294
348
  return dataSpace;
295
349
  }
296
350
 
351
+ private _handleMemberRoleChanges(presence: Presence, spaceProtocol: SpaceProtocol, memberInfo: MemberInfo[]): void {
352
+ let closedSessions = 0;
353
+ for (const member of memberInfo) {
354
+ if (member.key.equals(presence.getLocalState().identityKey)) {
355
+ continue;
356
+ }
357
+ const peers = presence.getPeersByIdentityKey(member.key);
358
+ const sessions = peers.map((p) => p.peerId && spaceProtocol.sessions.get(p.peerId));
359
+ const sessionsToClose = sessions.filter((s): s is SpaceProtocolSession => {
360
+ return (s && (member.role === SpaceMember.Role.REMOVED) !== (s.authStatus === AuthStatus.FAILURE)) ?? false;
361
+ });
362
+ sessionsToClose.forEach((session) => {
363
+ void session.close().catch(log.error);
364
+ });
365
+ closedSessions += sessionsToClose.length;
366
+ }
367
+ log('processed member role changes', {
368
+ roleChangeCount: memberInfo.length,
369
+ peersOnline: presence.getPeersOnline().length,
370
+ closedSessions,
371
+ });
372
+ // Handle the case when there was a removed peer online, we can now establish a connection with them
373
+ spaceProtocol.updateTopology();
374
+ }
375
+
376
+ private _handleNewPeerConnected(space: Space, peerState: PeerState): void {
377
+ const role = space.spaceState.getMemberRole(peerState.identityKey);
378
+ if (role === SpaceMember.Role.REMOVED) {
379
+ const session = peerState.peerId && space.protocol.sessions.get(peerState.peerId);
380
+ if (session != null) {
381
+ log('closing a session with a removed peer', { peerId: peerState.peerId });
382
+ void session.close().catch(log.error);
383
+ }
384
+ }
385
+ }
386
+
297
387
  private async _handleInvitationStatusChange(
298
388
  dataSpace: DataSpace | undefined,
299
389
  delegatedInvitation: DelegateInvitationCredential,
@@ -4,12 +4,16 @@
4
4
 
5
5
  import { Event, asyncTimeout, scheduleTask, sleep, synchronized, trackLeaks } from '@dxos/async';
6
6
  import { AUTH_TIMEOUT } from '@dxos/client-protocol';
7
- import { cancelWithContext, Context, ContextDisposedError } from '@dxos/context';
7
+ import { Context, ContextDisposedError, cancelWithContext } from '@dxos/context';
8
8
  import { timed, warnAfterTimeout } from '@dxos/debug';
9
9
  import { type EchoHost } from '@dxos/echo-db';
10
- import { type MetadataStore, type Space, createMappedFeedWriter } from '@dxos/echo-pipeline';
11
- import { AutomergeDocumentLoaderImpl } from '@dxos/echo-pipeline';
12
- import { type SpaceDoc } from '@dxos/echo-protocol';
10
+ import {
11
+ AutomergeDocumentLoaderImpl,
12
+ createMappedFeedWriter,
13
+ type MetadataStore,
14
+ type Space,
15
+ } from '@dxos/echo-pipeline';
16
+ import { type ObjectStructure, type SpaceDoc } from '@dxos/echo-protocol';
13
17
  import { TYPE_PROPERTIES } from '@dxos/echo-schema';
14
18
  import { type FeedStore } from '@dxos/feed-store';
15
19
  import { failedInvariant, invariant } from '@dxos/invariant';
@@ -17,15 +21,15 @@ import { type Keyring } from '@dxos/keyring';
17
21
  import { PublicKey } from '@dxos/keys';
18
22
  import { log } from '@dxos/log';
19
23
  import { CancelledError, SystemError } from '@dxos/protocols';
20
- import { SpaceState, type Space as SpaceProto, CreateEpochRequest } from '@dxos/protocols/proto/dxos/client/services';
24
+ import { CreateEpochRequest, SpaceState, type Space as SpaceProto } from '@dxos/protocols/proto/dxos/client/services';
21
25
  import { type FeedMessage } from '@dxos/protocols/proto/dxos/echo/feed';
22
26
  import { type SpaceCache } from '@dxos/protocols/proto/dxos/echo/metadata';
23
- import { SpaceMember } from '@dxos/protocols/proto/dxos/halo/credentials';
24
27
  import {
25
28
  AdmittedFeed,
26
- type ProfileDocument,
29
+ SpaceMember,
27
30
  type Credential,
28
31
  type Epoch,
32
+ type ProfileDocument,
29
33
  } from '@dxos/protocols/proto/dxos/halo/credentials';
30
34
  import { type GossipMessage } from '@dxos/protocols/proto/dxos/mesh/teleport/gossip';
31
35
  import { type Gossip, type Presence } from '@dxos/teleport-extension-gossip';
@@ -360,6 +364,8 @@ export class DataSpace {
360
364
 
361
365
  private _onNewAutomergeRoot(rootUrl: string) {
362
366
  log('loading automerge root doc for space', { space: this.key, rootUrl });
367
+ // Override share policy = true for the root document.
368
+ // Workaround for https://github.com/automerge/automerge-repo/pull/292
363
369
  this._echoHost.replicateDocument(rootUrl);
364
370
  const handle = this._echoHost.automergeRepo.find(rootUrl as any);
365
371
 
@@ -378,6 +384,14 @@ export class DataSpace {
378
384
  doc.access = { spaceKey: this.key.toHex() };
379
385
  });
380
386
  }
387
+
388
+ // TODO(dmaretskyi): Close roots.
389
+ // TODO(dmaretskyi): How do we handle changing to the next EPOCH?
390
+ if (!this._echoHost.roots.has(handle.documentId)) {
391
+ await this._echoHost.openSpaceRoot(handle.url);
392
+ } else {
393
+ log.warn('echo database root already exists', { space: this.key, rootUrl });
394
+ }
381
395
  } catch (err) {
382
396
  if (err instanceof ContextDisposedError) {
383
397
  return;
@@ -452,7 +466,7 @@ export class DataSpace {
452
466
 
453
467
  // Find properties object.
454
468
  const objects = Object.entries((rootHandle.docSync() as SpaceDoc).objects!);
455
- const properties = objects.find(([_, value]) => value.system.type?.itemId === TYPE_PROPERTIES);
469
+ const properties = findPropertiesObject(rootHandle.docSync() as SpaceDoc);
456
470
  const otherObjects = objects.filter(([key]) => key !== properties?.[0]);
457
471
  invariant(properties, 'Properties not found');
458
472
 
@@ -529,3 +543,16 @@ export class DataSpace {
529
543
  this.stateUpdate.emit();
530
544
  }
531
545
  }
546
+
547
+ /**
548
+ * Assumes properties are at root.
549
+ */
550
+ export const findPropertiesObject = (spaceDoc: SpaceDoc): [string, ObjectStructure] | undefined => {
551
+ for (const id in spaceDoc.objects ?? {}) {
552
+ const obj = spaceDoc.objects![id];
553
+ if (obj.system.type?.itemId === TYPE_PROPERTIES) {
554
+ return [id, obj];
555
+ }
556
+ }
557
+ return undefined;
558
+ };
@@ -4,12 +4,19 @@
4
4
 
5
5
  import { EventSubscriptions, UpdateScheduler, scheduleTask } from '@dxos/async';
6
6
  import { Stream } from '@dxos/codec-protobuf';
7
- import { type CredentialProcessor } from '@dxos/credentials';
7
+ import { createAdmissionCredentials, type CredentialProcessor, getCredentialAssertion } from '@dxos/credentials';
8
8
  import { raise } from '@dxos/debug';
9
9
  import { type SpaceManager } from '@dxos/echo-pipeline';
10
+ import { writeMessages } from '@dxos/feed-store';
10
11
  import { invariant } from '@dxos/invariant';
11
12
  import { log } from '@dxos/log';
12
- import { ApiError, SpaceNotFoundError, encodeError } from '@dxos/protocols';
13
+ import {
14
+ ApiError,
15
+ SpaceNotFoundError,
16
+ encodeError,
17
+ IdentityNotInitializedError,
18
+ AuthorizationError,
19
+ } from '@dxos/protocols';
13
20
  import {
14
21
  SpaceMember,
15
22
  SpaceState,
@@ -24,7 +31,7 @@ import {
24
31
  type WriteCredentialsRequest,
25
32
  type UpdateMemberRoleRequest,
26
33
  } from '@dxos/protocols/proto/dxos/client/services';
27
- import { type Credential, SpaceMember as HaloSpaceMember } from '@dxos/protocols/proto/dxos/halo/credentials';
34
+ import { type Credential } from '@dxos/protocols/proto/dxos/halo/credentials';
28
35
  import { type GossipMessage } from '@dxos/protocols/proto/dxos/mesh/teleport/gossip';
29
36
  import { type Provider } from '@dxos/util';
30
37
 
@@ -40,10 +47,7 @@ export class SpacesServiceImpl implements SpacesService {
40
47
  ) {}
41
48
 
42
49
  async createSpace(): Promise<Space> {
43
- if (!this._identityManager.identity) {
44
- throw new Error('This device has no HALO identity available. See https://docs.dxos.org/guide/platform/halo');
45
- }
46
-
50
+ this._requireIdentity();
47
51
  const dataSpaceManager = await this._getDataSpaceManager();
48
52
  const space = await dataSpaceManager.createSpace();
49
53
  return this._serializeSpace(space);
@@ -68,8 +72,30 @@ export class SpacesServiceImpl implements SpacesService {
68
72
  }
69
73
  }
70
74
 
71
- async updateMemberRole(_: UpdateMemberRoleRequest): Promise<void> {
72
- throw new Error('not implemented');
75
+ async updateMemberRole(request: UpdateMemberRoleRequest): Promise<void> {
76
+ const identity = this._requireIdentity();
77
+ const space = this._spaceManager.spaces.get(request.spaceKey);
78
+ if (space == null) {
79
+ throw new SpaceNotFoundError(request.spaceKey);
80
+ }
81
+ if (!space.spaceState.hasMembershipManagementPermission(identity.identityKey)) {
82
+ throw new AuthorizationError('No member management permission.', {
83
+ spaceKey: space.key,
84
+ role: space.spaceState.getMemberRole(identity.identityKey),
85
+ });
86
+ }
87
+ const credentials = await createAdmissionCredentials(
88
+ identity.getIdentityCredentialSigner(),
89
+ request.memberKey,
90
+ space.key,
91
+ space.genesisFeedKey,
92
+ request.newRole,
93
+ space.spaceState.membershipChainHeads,
94
+ );
95
+ invariant(credentials[0].credential);
96
+ const spaceMemberCredential = credentials[0].credential.credential;
97
+ invariant(getCredentialAssertion(spaceMemberCredential)['@type'] === 'dxos.halo.credentials.SpaceMember');
98
+ await writeMessages(space.controlPipeline.writer, credentials);
73
99
  }
74
100
 
75
101
  querySpaces(): Stream<QuerySpacesResponse> {
@@ -218,12 +244,8 @@ export class SpacesServiceImpl implements SpacesService {
218
244
  identityKey: member.key,
219
245
  profile: member.profile ?? {},
220
246
  },
221
- presence:
222
- member.role === HaloSpaceMember.Role.REMOVED
223
- ? SpaceMember.PresenceState.REMOVED
224
- : isMe || peers.length > 0
225
- ? SpaceMember.PresenceState.ONLINE
226
- : SpaceMember.PresenceState.OFFLINE,
247
+ role: member.role,
248
+ presence: peers.length > 0 ? SpaceMember.PresenceState.ONLINE : SpaceMember.PresenceState.OFFLINE,
227
249
  peerStates: peers,
228
250
  };
229
251
  }),
@@ -232,6 +254,15 @@ export class SpacesServiceImpl implements SpacesService {
232
254
  metrics: space.metrics,
233
255
  };
234
256
  }
257
+
258
+ private _requireIdentity() {
259
+ if (!this._identityManager.identity) {
260
+ throw new IdentityNotInitializedError(
261
+ 'This device has no HALO identity available. See https://docs.dxos.org/guide/platform/halo',
262
+ );
263
+ }
264
+ return this._identityManager.identity;
265
+ }
235
266
  }
236
267
 
237
268
  // Add `user-channel` prefix to the channel name, so that it doesn't collide with the internal channels.
package/src/version.ts CHANGED
@@ -1 +1 @@
1
- export const DXOS_VERSION = "0.5.3-main.f752aaa";
1
+ export const DXOS_VERSION = "0.5.3-main.fffc127";