@dxos/client-services 0.3.11-main.68db74e → 0.3.11-main.6901f09

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 (55) hide show
  1. package/dist/lib/browser/{chunk-S257HRMZ.mjs → chunk-PMVT5BLQ.mjs} +207 -100
  2. package/dist/lib/browser/chunk-PMVT5BLQ.mjs.map +7 -0
  3. package/dist/lib/browser/index.mjs +1 -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 +4 -11
  7. package/dist/lib/browser/packlets/testing/index.mjs.map +3 -3
  8. package/dist/lib/node/{chunk-PV2ZTP2T.cjs → chunk-BQVFPJSS.cjs} +348 -241
  9. package/dist/lib/node/chunk-BQVFPJSS.cjs.map +7 -0
  10. package/dist/lib/node/index.cjs +37 -37
  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 +11 -18
  14. package/dist/lib/node/packlets/testing/index.cjs.map +3 -3
  15. package/dist/types/src/packlets/invitations/device-invitation-protocol.d.ts +2 -0
  16. package/dist/types/src/packlets/invitations/device-invitation-protocol.d.ts.map +1 -1
  17. package/dist/types/src/packlets/invitations/invitation-protocol.d.ts +28 -3
  18. package/dist/types/src/packlets/invitations/invitation-protocol.d.ts.map +1 -1
  19. package/dist/types/src/packlets/invitations/invitations-handler.d.ts.map +1 -1
  20. package/dist/types/src/packlets/invitations/space-invitation-protocol.d.ts +2 -0
  21. package/dist/types/src/packlets/invitations/space-invitation-protocol.d.ts.map +1 -1
  22. package/dist/types/src/packlets/network/network-service.d.ts.map +1 -1
  23. package/dist/types/src/packlets/services/diagnostics.d.ts +32 -16
  24. package/dist/types/src/packlets/services/diagnostics.d.ts.map +1 -1
  25. package/dist/types/src/packlets/services/platform.d.ts +1 -14
  26. package/dist/types/src/packlets/services/platform.d.ts.map +1 -1
  27. package/dist/types/src/packlets/spaces/automerge-space-state.d.ts +2 -0
  28. package/dist/types/src/packlets/spaces/automerge-space-state.d.ts.map +1 -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 +2 -0
  31. package/dist/types/src/packlets/spaces/data-space.d.ts.map +1 -1
  32. package/dist/types/src/packlets/system/system-service.d.ts +3 -2
  33. package/dist/types/src/packlets/system/system-service.d.ts.map +1 -1
  34. package/dist/types/src/packlets/testing/invitation-utils.d.ts.map +1 -1
  35. package/dist/types/src/version.d.ts +1 -1
  36. package/package.json +35 -35
  37. package/src/packlets/identity/identity-manager.ts +1 -1
  38. package/src/packlets/invitations/device-invitation-protocol.test.ts +14 -0
  39. package/src/packlets/invitations/device-invitation-protocol.ts +14 -0
  40. package/src/packlets/invitations/invitation-protocol.ts +44 -6
  41. package/src/packlets/invitations/invitations-handler.ts +19 -17
  42. package/src/packlets/invitations/space-invitation-protocol.test.ts +28 -0
  43. package/src/packlets/invitations/space-invitation-protocol.ts +11 -0
  44. package/src/packlets/network/network-service.ts +5 -1
  45. package/src/packlets/services/diagnostics.ts +52 -16
  46. package/src/packlets/services/platform.ts +7 -19
  47. package/src/packlets/spaces/automerge-space-state.ts +4 -0
  48. package/src/packlets/spaces/data-space-manager.ts +5 -1
  49. package/src/packlets/spaces/data-space.ts +37 -4
  50. package/src/packlets/spaces/spaces-service.ts +3 -3
  51. package/src/packlets/system/system-service.ts +7 -1
  52. package/src/packlets/testing/invitation-utils.ts +2 -10
  53. package/src/version.ts +1 -1
  54. package/dist/lib/browser/chunk-S257HRMZ.mjs.map +0 -7
  55. package/dist/lib/node/chunk-PV2ZTP2T.cjs.map +0 -7
@@ -4,6 +4,7 @@
4
4
 
5
5
  import { invariant } from '@dxos/invariant';
6
6
  import { type Keyring } from '@dxos/keyring';
7
+ import { AlreadyJoinedError } from '@dxos/protocols';
7
8
  import { Invitation } from '@dxos/protocols/proto/dxos/client/services';
8
9
  import {
9
10
  type AdmissionRequest,
@@ -46,6 +47,17 @@ export class DeviceInvitationProtocol implements InvitationProtocol {
46
47
  };
47
48
  }
48
49
 
50
+ checkInvitation(invitation: Partial<Invitation>) {
51
+ try {
52
+ const identity = this._getIdentity();
53
+ if (identity) {
54
+ return new AlreadyJoinedError('Currently only one identity per client is supported.');
55
+ }
56
+ } catch {
57
+ // No identity.
58
+ }
59
+ }
60
+
49
61
  createIntroduction(): IntroductionRequest {
50
62
  return {};
51
63
  }
@@ -71,6 +83,8 @@ export class DeviceInvitationProtocol implements InvitationProtocol {
71
83
  invariant(request.device);
72
84
  const { deviceKey, controlFeedKey, dataFeedKey } = request.device;
73
85
 
86
+ // TODO(wittjosiah): When multiple identities are supported, verify identity doesn't already exist before accepting.
87
+
74
88
  await this._acceptIdentity({
75
89
  identityKey,
76
90
  deviceKey,
@@ -2,24 +2,62 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- import { type Invitation } from '@dxos/protocols/proto/dxos/client/services';
6
- import { type ProfileDocument } from '@dxos/protocols/proto/dxos/halo/credentials';
7
- import {
8
- type AdmissionRequest,
9
- type AdmissionResponse,
10
- type IntroductionRequest,
5
+ import type { ApiError } from '@dxos/protocols';
6
+ import type { Invitation } from '@dxos/protocols/proto/dxos/client/services';
7
+ import type { ProfileDocument } from '@dxos/protocols/proto/dxos/halo/credentials';
8
+ import type {
9
+ AdmissionRequest,
10
+ AdmissionResponse,
11
+ IntroductionRequest,
11
12
  } from '@dxos/protocols/proto/dxos/halo/invitations';
12
13
 
13
14
  export interface InvitationProtocol {
15
+ //
14
16
  // Debugging
17
+ //
18
+
19
+ /**
20
+ * Protocol-specific debug info to include in logs.
21
+ */
15
22
  toJSON(): object;
16
23
 
24
+ //
17
25
  // Host
26
+ //
27
+
28
+ /**
29
+ * Protocol-specific information to include in the invitation.
30
+ */
18
31
  getInvitationContext(): Partial<Invitation> & Pick<Invitation, 'kind'>;
32
+
33
+ /**
34
+ * Once authentication is successful, the host can admit the guest to the requested resource.
35
+ */
19
36
  admit(request: AdmissionRequest, guestProfile?: ProfileDocument): Promise<AdmissionResponse>;
20
37
 
38
+ //
21
39
  // Guest
40
+ //
41
+
42
+ /**
43
+ * Check if the invitation is valid.
44
+ *
45
+ * For example, the guest may already be a member of the space.
46
+ */
47
+ checkInvitation(invitation: Partial<Invitation>): ApiError | undefined;
48
+
49
+ /**
50
+ * Get profile information to send to the host to identify the guest.
51
+ */
22
52
  createIntroduction(): IntroductionRequest;
53
+
54
+ /**
55
+ * Get key information to send to the host in order to create an admission credential for the guest.
56
+ */
23
57
  createAdmissionRequest(): Promise<AdmissionRequest>;
58
+
59
+ /**
60
+ * Redeem the admission credential.
61
+ */
24
62
  accept(response: AdmissionResponse, request: AdmissionRequest): Promise<Partial<Invitation>>;
25
63
  }
@@ -303,9 +303,6 @@ export class InvitationsHandler {
303
303
  }
304
304
  }
305
305
  }
306
- } else {
307
- // Notify that introduction is complete even if auth is not required.
308
- setState({ state: Invitation.State.READY_FOR_AUTHENTICATION });
309
306
  }
310
307
 
311
308
  // 3. Send admission credentials to host (with local space keys).
@@ -355,20 +352,25 @@ export class InvitationsHandler {
355
352
  };
356
353
 
357
354
  scheduleTask(ctx, async () => {
358
- invariant(invitation.swarmKey);
359
- const topic = invitation.swarmKey;
360
- const swarmConnection = await this._networkManager.joinSwarm({
361
- topic,
362
- peerId: PublicKey.random(),
363
- protocolProvider: createTeleportProtocolFactory(async (teleport) => {
364
- teleport.addExtension('dxos.halo.invitations', createExtension());
365
- }),
366
- topology: new StarTopology(topic),
367
- label: 'invitation guest',
368
- });
369
- ctx.onDispose(() => swarmConnection.close());
370
-
371
- setState({ state: Invitation.State.CONNECTING });
355
+ const error = protocol.checkInvitation(invitation);
356
+ if (error) {
357
+ stream.error(error);
358
+ } else {
359
+ invariant(invitation.swarmKey);
360
+ const topic = invitation.swarmKey;
361
+ const swarmConnection = await this._networkManager.joinSwarm({
362
+ topic,
363
+ peerId: PublicKey.random(),
364
+ protocolProvider: createTeleportProtocolFactory(async (teleport) => {
365
+ teleport.addExtension('dxos.halo.invitations', createExtension());
366
+ }),
367
+ topology: new StarTopology(topic),
368
+ label: 'invitation guest',
369
+ });
370
+ ctx.onDispose(() => swarmConnection.close());
371
+
372
+ setState({ state: Invitation.State.CONNECTING });
373
+ }
372
374
  });
373
375
 
374
376
  const observable = new AuthenticatingInvitation({
@@ -7,6 +7,7 @@ import { expect } from 'chai';
7
7
  import { asyncChain, Trigger } from '@dxos/async';
8
8
  import { raise } from '@dxos/debug';
9
9
  import { testLocalDatabase } from '@dxos/echo-pipeline/testing';
10
+ import { AlreadyJoinedError } from '@dxos/protocols';
10
11
  import { Invitation } from '@dxos/protocols/proto/dxos/client/services';
11
12
  import { afterTest, describe, test } from '@dxos/test';
12
13
 
@@ -71,6 +72,33 @@ describe('services/space-invitations-protocol', () => {
71
72
  }
72
73
  });
73
74
 
75
+ test('invitation when already joined', async () => {
76
+ const [host, guest] = await asyncChain<ServiceContext>([createIdentity, closeAfterTest])(createPeers(2));
77
+
78
+ const space1 = await host.dataSpaceManager!.createSpace();
79
+ const spaceKey = space1.key;
80
+
81
+ await Promise.all(performInvitation({ host, guest, options: { kind: Invitation.Kind.SPACE, spaceKey } }));
82
+
83
+ {
84
+ const space1 = host.dataSpaceManager!.spaces.get(spaceKey)!;
85
+ const space2 = guest.dataSpaceManager!.spaces.get(spaceKey)!;
86
+ expect(space1).not.to.be.undefined;
87
+ expect(space2).not.to.be.undefined;
88
+
89
+ await host.dataSpaceManager?.waitUntilSpaceReady(space1.key);
90
+ await guest.dataSpaceManager?.waitUntilSpaceReady(space2.key);
91
+ }
92
+
93
+ const [_, guestResult] = performInvitation({
94
+ host,
95
+ guest,
96
+ options: { kind: Invitation.Kind.SPACE, spaceKey },
97
+ });
98
+
99
+ expect((await guestResult).error).to.be.instanceOf(AlreadyJoinedError);
100
+ });
101
+
74
102
  test('creates and accepts invitation with retry', async () => {
75
103
  const [host, guest] = await asyncChain<ServiceContext>([createIdentity, closeAfterTest])(createPeers(2));
76
104
 
@@ -8,6 +8,7 @@ import { invariant } from '@dxos/invariant';
8
8
  import { type Keyring } from '@dxos/keyring';
9
9
  import { type PublicKey } from '@dxos/keys';
10
10
  import { log } from '@dxos/log';
11
+ import { AlreadyJoinedError } from '@dxos/protocols';
11
12
  import { Invitation } from '@dxos/protocols/proto/dxos/client/services';
12
13
  import { type FeedMessage } from '@dxos/protocols/proto/dxos/echo/feed';
13
14
  import { type ProfileDocument } from '@dxos/protocols/proto/dxos/halo/credentials';
@@ -76,6 +77,12 @@ export class SpaceInvitationProtocol implements InvitationProtocol {
76
77
  };
77
78
  }
78
79
 
80
+ checkInvitation(invitation: Partial<Invitation>) {
81
+ if (invitation.spaceKey && this._spaceManager.spaces.has(invitation.spaceKey)) {
82
+ return new AlreadyJoinedError('Already joined space.');
83
+ }
84
+ }
85
+
79
86
  createIntroduction(): IntroductionRequest {
80
87
  return {
81
88
  profile: this._signingContext.getProfile(),
@@ -104,6 +111,10 @@ export class SpaceInvitationProtocol implements InvitationProtocol {
104
111
  invariant(assertion['@type'] === 'dxos.halo.credentials.SpaceMember', 'Invalid credential');
105
112
  invariant(credential.subject.id.equals(this._signingContext.identityKey));
106
113
 
114
+ if (this._spaceManager.spaces.has(assertion.spaceKey)) {
115
+ throw new AlreadyJoinedError('Already joined space.');
116
+ }
117
+
107
118
  // Create local space.
108
119
  await this._spaceManager.acceptSpace({
109
120
  spaceKey: assertion.spaceKey,
@@ -12,13 +12,17 @@ import {
12
12
  } from '@dxos/protocols/proto/dxos/client/services';
13
13
 
14
14
  export class NetworkServiceImpl implements NetworkService {
15
- constructor(private readonly networkManager: NetworkManager, private readonly signalManager: SignalManager) {}
15
+ constructor(
16
+ private readonly networkManager: NetworkManager,
17
+ private readonly signalManager: SignalManager,
18
+ ) {}
16
19
 
17
20
  queryStatus() {
18
21
  return new Stream<NetworkStatus>(({ next }) => {
19
22
  const update = () => {
20
23
  next({
21
24
  swarm: this.networkManager.connectionState,
25
+ connectionInfo: this.networkManager.connectionLog?.swarms,
22
26
  signaling: this.signalManager.getStatus().map(({ host, state }) => ({ server: host, state })),
23
27
  });
24
28
  };
@@ -18,13 +18,17 @@ import {
18
18
  type Metrics,
19
19
  type NetworkStatus,
20
20
  type Space as SpaceProto,
21
+ type Platform,
21
22
  SpaceMember,
23
+ type LogEntry,
22
24
  } from '@dxos/protocols/proto/dxos/client/services';
23
25
  import { type SubscribeToFeedsResponse } from '@dxos/protocols/proto/dxos/devtools/host';
24
26
  import { type SwarmInfo } from '@dxos/protocols/proto/dxos/devtools/swarm';
25
27
  import { type Epoch } from '@dxos/protocols/proto/dxos/halo/credentials';
28
+ import { type Resource, type Span } from '@dxos/protocols/proto/dxos/tracing';
29
+ import { TRACE_PROCESSOR } from '@dxos/tracing';
26
30
 
27
- import { getPlatform, type Platform } from './platform';
31
+ import { getPlatform } from './platform';
28
32
  import { type ServiceContext } from './service-context';
29
33
  import { DXOS_VERSION } from '../../version';
30
34
  import { type DataSpace } from '../spaces';
@@ -32,22 +36,36 @@ import { type DataSpace } from '../spaces';
32
36
  const DEFAULT_TIMEOUT = 1_000;
33
37
 
34
38
  export type Diagnostics = {
35
- created: string;
36
- platform: Platform;
37
- config?: ConfigProto;
38
39
  client: {
39
- version: string;
40
- storage: {
41
- version: number;
40
+ config: ConfigProto;
41
+ trace: TraceDiagnostic;
42
+ };
43
+ services: {
44
+ trace: TraceDiagnostic;
45
+ created: string;
46
+ platform: Platform;
47
+ config?: ConfigProto;
48
+ client: {
49
+ version: string;
50
+ storage: {
51
+ version: number;
52
+ };
42
53
  };
54
+ identity?: Identity;
55
+ devices?: Device[];
56
+ spaces?: SpaceStats[];
57
+ networkStatus?: NetworkStatus;
58
+ swarms?: SwarmInfo[];
59
+ feeds?: Partial<SubscribeToFeedsResponse.Feed>[];
60
+ metrics?: Metrics;
61
+ storage?: { file: string; count: number }[];
43
62
  };
44
- identity?: Identity;
45
- devices?: Device[];
46
- spaces?: SpaceStats[];
47
- networkStatus?: NetworkStatus;
48
- swarms?: SwarmInfo[];
49
- feeds?: Partial<SubscribeToFeedsResponse.Feed>[];
50
- metrics?: Metrics;
63
+ };
64
+
65
+ export type TraceDiagnostic = {
66
+ resources: Record<string, Resource>;
67
+ spans: Span[];
68
+ logs: LogEntry[];
51
69
  };
52
70
 
53
71
  // TODO(burdon): Normalize for ECHO/HALO.
@@ -74,8 +92,8 @@ export const createDiagnostics = async (
74
92
  clientServices: Partial<ClientServices>,
75
93
  serviceContext: ServiceContext,
76
94
  config: Config,
77
- ): Promise<Diagnostics> => {
78
- const diagnostics: Diagnostics = {
95
+ ): Promise<Diagnostics['services']> => {
96
+ const diagnostics: Diagnostics['services'] = {
79
97
  created: new Date().toISOString(),
80
98
  platform: getPlatform(),
81
99
  client: {
@@ -84,6 +102,7 @@ export const createDiagnostics = async (
84
102
  version: STORAGE_VERSION,
85
103
  },
86
104
  },
105
+ trace: TRACE_PROCESSOR.getDiagnostics(),
87
106
  };
88
107
 
89
108
  // Trace metrics.
@@ -95,6 +114,23 @@ export const createDiagnostics = async (
95
114
  }).catch(() => undefined);
96
115
  }
97
116
 
117
+ if (typeof navigator !== 'undefined' && navigator.storage) {
118
+ const map = new Map();
119
+ const dir = await navigator.storage.getDirectory();
120
+ for await (const filename of (dir as any)?.keys()) {
121
+ const idx = filename.indexOf('-', filename.indexOf('-') + 1);
122
+ if (idx === -1) {
123
+ continue;
124
+ }
125
+
126
+ map.set(filename.slice(0, idx), (map.get(filename.slice(0, idx)) ?? 0) + 1);
127
+ }
128
+
129
+ diagnostics.storage = Array.from(map.entries())
130
+ .sort((a, b) => b[1] - a[1])
131
+ .map(([file, count]) => ({ file, count }));
132
+ }
133
+
98
134
  const identity = serviceContext.identityManager.identity;
99
135
  if (identity) {
100
136
  // Identity.
@@ -2,20 +2,7 @@
2
2
  // Copyright 2022 DXOS.org
3
3
  //
4
4
 
5
- export type Platform = {
6
- type: 'browser' | 'shared-worker' | 'node';
7
- userAgent?: string;
8
- platform?: string;
9
- runtime?: string;
10
- uptime?: number;
11
- memory?: {
12
- rss: number;
13
- heapTotal: number;
14
- heapUsed: number;
15
- external: number;
16
- arrayBuffers: number;
17
- };
18
- };
5
+ import { Platform } from '@dxos/protocols/proto/dxos/client/services';
19
6
 
20
7
  export const getPlatform = (): Platform => {
21
8
  if ((process as any).browser) {
@@ -23,14 +10,14 @@ export const getPlatform = (): Platform => {
23
10
  // Browser.
24
11
  const { userAgent } = window.navigator;
25
12
  return {
26
- type: 'browser',
13
+ type: Platform.PLATFORM_TYPE.BROWSER,
27
14
  userAgent,
28
15
  uptime: Math.floor((Date.now() - window.performance.timeOrigin) / 1_000),
29
16
  };
30
17
  } else {
31
18
  // Shared worker.
32
19
  return {
33
- type: 'shared-worker',
20
+ type: Platform.PLATFORM_TYPE.SHARED_WORKER,
34
21
  uptime: Math.floor((Date.now() - performance.timeOrigin) / 1_000),
35
22
  };
36
23
  }
@@ -38,9 +25,10 @@ export const getPlatform = (): Platform => {
38
25
  // Node.
39
26
  const { platform, version, arch } = process;
40
27
  return {
41
- type: 'node',
42
- platform: `${platform} ${version} ${arch}`,
43
- runtime: process.version,
28
+ type: Platform.PLATFORM_TYPE.NODE,
29
+ platform,
30
+ arch,
31
+ runtime: version,
44
32
  uptime: Math.floor(process.uptime()),
45
33
  memory: process.memoryUsage(),
46
34
  };
@@ -9,6 +9,8 @@ export class AutomergeSpaceState implements CredentialProcessor {
9
9
  public rootUrl: string | undefined = undefined;
10
10
  public lastEpoch: SpecificCredential<Epoch> | undefined = undefined;
11
11
 
12
+ constructor(private readonly _onNewRoot: (rootUrl: string) => void) {}
13
+
12
14
  async processCredential(credential: Credential) {
13
15
  if (!checkCredentialType(credential, 'dxos.halo.credentials.Epoch')) {
14
16
  return;
@@ -17,6 +19,8 @@ export class AutomergeSpaceState implements CredentialProcessor {
17
19
  this.lastEpoch = credential;
18
20
  if (credential.subject.assertion.automergeRoot) {
19
21
  this.rootUrl = credential.subject.assertion.automergeRoot;
22
+
23
+ this._onNewRoot(this.rootUrl);
20
24
  }
21
25
  }
22
26
  }
@@ -143,6 +143,9 @@ export class DataSpaceManager {
143
143
  const space = await this._constructSpace(metadata);
144
144
 
145
145
  const automergeRoot = this._automergeHost.repo.create();
146
+ automergeRoot.change((doc: any) => {
147
+ doc.experimental_spaceKey = spaceKey.toHex();
148
+ });
146
149
 
147
150
  const credentials = await spaceGenesis(this._keyring, this._signingContext, space.inner, automergeRoot.url);
148
151
  await this._metadataStore.addSpace(metadata);
@@ -222,12 +225,13 @@ export class DataSpaceManager {
222
225
  credentialProvider: createAuthProvider(this._signingContext.credentialSigner),
223
226
  credentialAuthenticator: deferFunction(() => dataSpace.authVerifier.verifier),
224
227
  },
225
- onNetworkConnection: (session) => {
228
+ onAuthorizedConnection: (session) => {
226
229
  session.addExtension(
227
230
  'dxos.mesh.teleport.gossip',
228
231
  gossip.createExtension({ remotePeerId: session.remotePeerId }),
229
232
  );
230
233
  session.addExtension('dxos.mesh.teleport.notarization', dataSpace.notarizationPlugin.createExtension());
234
+ this._automergeHost.authorizeDevice(space.key, session.remotePeerId);
231
235
  session.addExtension('dxos.mesh.teleport.automerge', this._automergeHost.createExtension());
232
236
  },
233
237
  onAuthFailure: () => {
@@ -2,9 +2,9 @@
2
2
  // Copyright 2022 DXOS.org
3
3
  //
4
4
 
5
- import { Event, scheduleTask, sleep, synchronized, trackLeaks } from '@dxos/async';
5
+ import { Event, asyncTimeout, scheduleTask, sleep, synchronized, trackLeaks } from '@dxos/async';
6
6
  import { AUTH_TIMEOUT } from '@dxos/client-protocol';
7
- import { cancelWithContext, Context } from '@dxos/context';
7
+ import { cancelWithContext, Context, ContextDisposedError } from '@dxos/context';
8
8
  import { timed } from '@dxos/debug';
9
9
  import {
10
10
  type MetadataStore,
@@ -15,6 +15,7 @@ import {
15
15
  type AutomergeHost,
16
16
  } from '@dxos/echo-pipeline';
17
17
  import { type FeedStore } from '@dxos/feed-store';
18
+ import { failedInvariant } from '@dxos/invariant';
18
19
  import { type Keyring } from '@dxos/keyring';
19
20
  import { PublicKey } from '@dxos/keys';
20
21
  import { log } from '@dxos/log';
@@ -77,9 +78,12 @@ export type DataSpaceParams = {
77
78
  const ENABLE_FEED_PURGE = false;
78
79
 
79
80
  @trackLeaks('open', 'close')
81
+ @trace.resource()
80
82
  export class DataSpace {
81
83
  private _ctx = new Context();
84
+ @trace.info()
82
85
  private readonly _inner: Space;
86
+
83
87
  private readonly _gossip: Gossip;
84
88
  private readonly _presence: Presence;
85
89
  private readonly _keyring: Keyring;
@@ -92,7 +96,7 @@ export class DataSpace {
92
96
  private readonly _automergeHost: AutomergeHost;
93
97
 
94
98
  // TODO(dmaretskyi): Move into Space?
95
- private readonly _automergeSpaceState = new AutomergeSpaceState();
99
+ private readonly _automergeSpaceState = new AutomergeSpaceState((rootUrl) => this._onNewAutomergeRoot(rootUrl));
96
100
 
97
101
  private _state = SpaceState.CLOSED;
98
102
 
@@ -137,6 +141,7 @@ export class DataSpace {
137
141
  log('new state', { state: SpaceState[this._state] });
138
142
  }
139
143
 
144
+ @trace.info()
140
145
  get key() {
141
146
  return this._inner.key;
142
147
  }
@@ -145,6 +150,7 @@ export class DataSpace {
145
150
  return this._inner.isOpen;
146
151
  }
147
152
 
153
+ @trace.info({ enum: SpaceState })
148
154
  get state(): SpaceState {
149
155
  return this._state;
150
156
  }
@@ -174,6 +180,14 @@ export class DataSpace {
174
180
  return this._automergeSpaceState;
175
181
  }
176
182
 
183
+ @trace.info({ depth: null })
184
+ private get _automergeInfo() {
185
+ return {
186
+ rootUrl: this._automergeSpaceState.rootUrl,
187
+ lastEpoch: this._automergeSpaceState.lastEpoch,
188
+ };
189
+ }
190
+
177
191
  @synchronized
178
192
  async open() {
179
193
  await this._open();
@@ -232,7 +246,7 @@ export class DataSpace {
232
246
  this.metrics.pipelineInitBegin = new Date();
233
247
  await this.initializeDataPipeline();
234
248
  } catch (err) {
235
- if (err instanceof CancelledError) {
249
+ if (err instanceof CancelledError || err instanceof ContextDisposedError) {
236
250
  log('data pipeline initialization cancelled', err);
237
251
  return;
238
252
  }
@@ -363,6 +377,25 @@ export class DataSpace {
363
377
  }
364
378
  }
365
379
 
380
+ private _onNewAutomergeRoot(rootUrl: string) {
381
+ log('loading automerge root doc for space', { space: this.key, rootUrl });
382
+ const handle = this._automergeHost.repo.find(rootUrl as any);
383
+
384
+ queueMicrotask(async () => {
385
+ try {
386
+ await asyncTimeout(handle.whenReady(), 5_000);
387
+ const doc = handle.docSync() ?? failedInvariant();
388
+ if (!doc.experimental_spaceKey) {
389
+ handle.change((doc: any) => {
390
+ doc.experimental_spaceKey = this.key.toHex();
391
+ });
392
+ }
393
+ } catch (err) {
394
+ log.warn('error loading automerge root doc', { space: this.key, rootUrl, err });
395
+ }
396
+ });
397
+ }
398
+
366
399
  // TODO(dmaretskyi): Use profile from signing context.
367
400
  async updateOwnProfile(profile: ProfileDocument) {
368
401
  const credential = await this._signingContext.credentialSigner.createCredential({
@@ -41,7 +41,7 @@ export class SpacesServiceImpl implements SpacesService {
41
41
 
42
42
  async createSpace(): Promise<Space> {
43
43
  if (!this._identityManager.identity) {
44
- throw new Error('This device has no HALO identity available. See https://docs.dxos.org/guide/halo');
44
+ throw new Error('This device has no HALO identity available. See https://docs.dxos.org/guide/platform/halo');
45
45
  }
46
46
 
47
47
  const dataSpaceManager = await this._getDataSpaceManager();
@@ -222,8 +222,8 @@ export class SpacesServiceImpl implements SpacesService {
222
222
  presence: member.removed
223
223
  ? SpaceMember.PresenceState.REMOVED
224
224
  : isMe || peers.length > 0
225
- ? SpaceMember.PresenceState.ONLINE
226
- : SpaceMember.PresenceState.OFFLINE,
225
+ ? SpaceMember.PresenceState.ONLINE
226
+ : SpaceMember.PresenceState.OFFLINE,
227
227
  peerStates: peers,
228
228
  };
229
229
  }),
@@ -12,16 +12,18 @@ import {
12
12
  type UpdateStatusRequest,
13
13
  type QueryStatusRequest,
14
14
  type QueryStatusResponse,
15
+ type Platform,
15
16
  } from '@dxos/protocols/proto/dxos/client/services';
16
17
  import { jsonKeyReplacer, type MaybePromise } from '@dxos/util';
17
18
 
18
19
  import { type Diagnostics } from '../services';
20
+ import { getPlatform } from '../services/platform';
19
21
 
20
22
  export type SystemServiceOptions = {
21
23
  config?: Config;
22
24
  statusUpdate: Event<void>;
23
25
  getCurrentStatus: () => SystemStatus;
24
- getDiagnostics: () => Promise<Partial<Diagnostics>>;
26
+ getDiagnostics: () => Promise<Partial<Diagnostics['services']>>;
25
27
  onUpdateStatus: (status: SystemStatus) => MaybePromise<void>;
26
28
  onReset: () => MaybePromise<void>;
27
29
  };
@@ -73,6 +75,10 @@ export class SystemServiceImpl implements SystemService {
73
75
  };
74
76
  }
75
77
 
78
+ async getPlatform(): Promise<Platform> {
79
+ return getPlatform();
80
+ }
81
+
76
82
  async updateStatus({ status }: UpdateStatusRequest) {
77
83
  await this._onUpdateStatus(status);
78
84
  }
@@ -3,7 +3,7 @@
3
3
  //
4
4
 
5
5
  import { Trigger } from '@dxos/async';
6
- import { type AuthenticatingInvitation, type CancellableInvitation } from '@dxos/client-protocol';
6
+ import { InvitationEncoder, type AuthenticatingInvitation, type CancellableInvitation } from '@dxos/client-protocol';
7
7
  import { invariant } from '@dxos/invariant';
8
8
  import { Invitation } from '@dxos/protocols/proto/dxos/client/services';
9
9
 
@@ -13,15 +13,7 @@ import { ServiceContext } from '../services';
13
13
  * Strip secrets from invitation before giving it to the peer.
14
14
  */
15
15
  export const sanitizeInvitation = (invitation: Invitation): Invitation => {
16
- return {
17
- invitationId: invitation.invitationId,
18
- type: invitation.type,
19
- kind: invitation.kind,
20
- authMethod: invitation.authMethod,
21
- swarmKey: invitation.swarmKey,
22
- state: invitation.state,
23
- timeout: invitation.timeout,
24
- };
16
+ return InvitationEncoder.decode(InvitationEncoder.encode(invitation));
25
17
  };
26
18
 
27
19
  export type InvitationHost = {
package/src/version.ts CHANGED
@@ -1 +1 @@
1
- export const DXOS_VERSION = "0.3.11-main.68db74e";
1
+ export const DXOS_VERSION = "0.3.11-main.6901f09";