@dxos/client-services 0.1.55 → 0.1.56-main.01afb72

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 (91) hide show
  1. package/dist/lib/browser/{chunk-XSZIBW37.mjs → chunk-53BV3Q7V.mjs} +1131 -463
  2. package/dist/lib/browser/chunk-53BV3Q7V.mjs.map +7 -0
  3. package/dist/lib/browser/index.mjs +63 -42
  4. package/dist/lib/browser/index.mjs.map +3 -3
  5. package/dist/lib/browser/meta.json +1 -1
  6. package/dist/lib/browser/packlets/testing/index.mjs +13 -4
  7. package/dist/lib/browser/packlets/testing/index.mjs.map +3 -3
  8. package/dist/lib/node/index.cjs +1279 -593
  9. package/dist/lib/node/index.cjs.map +4 -4
  10. package/dist/lib/node/meta.json +1 -1
  11. package/dist/lib/node/packlets/testing/index.cjs +1410 -736
  12. package/dist/lib/node/packlets/testing/index.cjs.map +4 -4
  13. package/dist/types/src/packlets/identity/identity-manager.d.ts +2 -1
  14. package/dist/types/src/packlets/identity/identity-manager.d.ts.map +1 -1
  15. package/dist/types/src/packlets/identity/identity-service.d.ts.map +1 -1
  16. package/dist/types/src/packlets/identity/identity.d.ts +3 -2
  17. package/dist/types/src/packlets/identity/identity.d.ts.map +1 -1
  18. package/dist/types/src/packlets/invitations/device-invitation-protocol.d.ts.map +1 -1
  19. package/dist/types/src/packlets/invitations/invitation-extension.d.ts.map +1 -1
  20. package/dist/types/src/packlets/invitations/invitations-handler.d.ts.map +1 -1
  21. package/dist/types/src/packlets/invitations/invitations-service.d.ts.map +1 -1
  22. package/dist/types/src/packlets/invitations/space-invitation-protocol.d.ts.map +1 -1
  23. package/dist/types/src/packlets/logging/logging-service.d.ts +6 -2
  24. package/dist/types/src/packlets/logging/logging-service.d.ts.map +1 -1
  25. package/dist/types/src/packlets/services/client-rpc-server.d.ts.map +1 -1
  26. package/dist/types/src/packlets/services/diagnostics.d.ts +46 -0
  27. package/dist/types/src/packlets/services/diagnostics.d.ts.map +1 -0
  28. package/dist/types/src/packlets/services/index.d.ts +1 -0
  29. package/dist/types/src/packlets/services/index.d.ts.map +1 -1
  30. package/dist/types/src/packlets/services/platform.d.ts +16 -0
  31. package/dist/types/src/packlets/services/platform.d.ts.map +1 -0
  32. package/dist/types/src/packlets/services/service-context.d.ts +2 -1
  33. package/dist/types/src/packlets/services/service-context.d.ts.map +1 -1
  34. package/dist/types/src/packlets/services/service-host.d.ts +7 -3
  35. package/dist/types/src/packlets/services/service-host.d.ts.map +1 -1
  36. package/dist/types/src/packlets/services/util.d.ts +6 -0
  37. package/dist/types/src/packlets/services/util.d.ts.map +1 -0
  38. package/dist/types/src/packlets/spaces/data-space-manager.d.ts.map +1 -1
  39. package/dist/types/src/packlets/spaces/notarization-plugin.d.ts.map +1 -1
  40. package/dist/types/src/packlets/spaces/spaces-service.d.ts.map +1 -1
  41. package/dist/types/src/packlets/system/system-service.d.ts +12 -2
  42. package/dist/types/src/packlets/system/system-service.d.ts.map +1 -1
  43. package/dist/types/src/packlets/testing/invitation-utils.d.ts.map +1 -1
  44. package/dist/types/src/packlets/vault/iframe-host-runtime.d.ts.map +1 -1
  45. package/dist/types/src/packlets/vault/shell-runtime.d.ts.map +1 -1
  46. package/dist/types/src/packlets/vault/worker-runtime.d.ts.map +1 -1
  47. package/dist/types/src/packlets/vault/worker-session.d.ts.map +1 -1
  48. package/dist/types/src/version.d.ts +2 -0
  49. package/dist/types/src/version.d.ts.map +1 -0
  50. package/package.json +34 -33
  51. package/src/packlets/devices/devices-service.test.ts +2 -1
  52. package/src/packlets/identity/authenticator.test.ts +2 -2
  53. package/src/packlets/identity/identity-manager.test.ts +4 -3
  54. package/src/packlets/identity/identity-manager.ts +10 -7
  55. package/src/packlets/identity/identity-service.test.ts +2 -1
  56. package/src/packlets/identity/identity-service.ts +1 -2
  57. package/src/packlets/identity/identity.test.ts +7 -6
  58. package/src/packlets/identity/identity.ts +9 -5
  59. package/src/packlets/invitations/device-invitation-protocol.test.ts +2 -1
  60. package/src/packlets/invitations/device-invitation-protocol.ts +1 -2
  61. package/src/packlets/invitations/invitation-extension.ts +1 -2
  62. package/src/packlets/invitations/invitations-handler.ts +1 -2
  63. package/src/packlets/invitations/invitations-service.ts +1 -2
  64. package/src/packlets/invitations/space-invitation-protocol.ts +1 -2
  65. package/src/packlets/locks/node.ts +1 -1
  66. package/src/packlets/logging/logging-service.ts +17 -9
  67. package/src/packlets/network/network-service.test.ts +2 -1
  68. package/src/packlets/services/client-rpc-server.ts +4 -4
  69. package/src/packlets/services/diagnostics.ts +211 -0
  70. package/src/packlets/services/index.ts +1 -0
  71. package/src/packlets/services/platform.ts +48 -0
  72. package/src/packlets/services/service-context.ts +14 -11
  73. package/src/packlets/services/service-host.test.ts +11 -10
  74. package/src/packlets/services/service-host.ts +38 -23
  75. package/src/packlets/services/service-registry.test.ts +3 -1
  76. package/src/packlets/services/util.ts +33 -0
  77. package/src/packlets/spaces/data-space-manager.ts +2 -3
  78. package/src/packlets/spaces/data-space.ts +1 -1
  79. package/src/packlets/spaces/notarization-plugin.ts +1 -2
  80. package/src/packlets/spaces/spaces-service.test.ts +2 -1
  81. package/src/packlets/spaces/spaces-service.ts +18 -12
  82. package/src/packlets/system/system-service.test.ts +1 -0
  83. package/src/packlets/system/system-service.ts +34 -2
  84. package/src/packlets/testing/invitation-utils.ts +1 -2
  85. package/src/packlets/testing/test-builder.ts +1 -1
  86. package/src/packlets/vault/iframe-host-runtime.ts +2 -1
  87. package/src/packlets/vault/shell-runtime.ts +1 -2
  88. package/src/packlets/vault/worker-runtime.ts +2 -1
  89. package/src/packlets/vault/worker-session.ts +7 -3
  90. package/src/version.ts +1 -0
  91. package/dist/lib/browser/chunk-XSZIBW37.mjs.map +0 -7
@@ -4,6 +4,7 @@
4
4
 
5
5
  import { expect } from 'chai';
6
6
 
7
+ import { Context } from '@dxos/context';
7
8
  import { valueEncoding, MetadataStore, SpaceManager, AuthStatus, SnapshotStore } from '@dxos/echo-pipeline';
8
9
  import { FeedFactory, FeedStore } from '@dxos/feed-store';
9
10
  import { Keyring } from '@dxos/keyring';
@@ -64,7 +65,7 @@ describe('identity/identity-manager', () => {
64
65
 
65
66
  test('creates identity', async () => {
66
67
  const { identityManager } = await setupPeer();
67
- await identityManager.open();
68
+ await identityManager.open(new Context());
68
69
  afterTest(() => identityManager.close());
69
70
 
70
71
  const identity = await identityManager.createIdentity();
@@ -75,13 +76,13 @@ describe('identity/identity-manager', () => {
75
76
  const storage = createStorage({ type: StorageType.RAM });
76
77
 
77
78
  const peer1 = await setupPeer({ storage });
78
- await peer1.identityManager.open();
79
+ await peer1.identityManager.open(new Context());
79
80
  const identity1 = await peer1.identityManager.createIdentity();
80
81
  await peer1.identityManager.close();
81
82
  await peer1.feedStore.close();
82
83
 
83
84
  const peer2 = await setupPeer({ storage });
84
- await peer2.identityManager.open();
85
+ await peer2.identityManager.open(new Context());
85
86
 
86
87
  expect(peer2.identityManager.identity).to.exist;
87
88
  expect(peer2.identityManager.identity!.identityKey).to.deep.eq(identity1.identityKey);
@@ -2,12 +2,12 @@
2
2
  // Copyright 2022 DXOS.org
3
3
  //
4
4
 
5
- import invariant from 'tiny-invariant';
6
-
7
5
  import { Event } from '@dxos/async';
6
+ import { Context } from '@dxos/context';
8
7
  import { createCredentialSignerWithKey, CredentialGenerator } from '@dxos/credentials';
9
8
  import { MetadataStore, SpaceManager, SwarmIdentity } from '@dxos/echo-pipeline';
10
9
  import { FeedStore } from '@dxos/feed-store';
10
+ import { invariant } from '@dxos/invariant';
11
11
  import { Keyring } from '@dxos/keyring';
12
12
  import { PublicKey } from '@dxos/keys';
13
13
  import { log } from '@dxos/log';
@@ -16,6 +16,7 @@ import { FeedMessage } from '@dxos/protocols/proto/dxos/echo/feed';
16
16
  import { IdentityRecord, SpaceMetadata } from '@dxos/protocols/proto/dxos/echo/metadata';
17
17
  import { AdmittedFeed } from '@dxos/protocols/proto/dxos/halo/credentials';
18
18
  import { Timeframe } from '@dxos/timeframe';
19
+ import { trace as Trace } from '@dxos/tracing';
19
20
  import { deferFunction } from '@dxos/util';
20
21
 
21
22
  import { createAuthProvider } from './authenticator';
@@ -47,6 +48,7 @@ export type CreateIdentityOptions = {
47
48
  };
48
49
 
49
50
  // TODO(dmaretskyi): Rename: represents the peer's state machine.
51
+ @Trace.resource()
50
52
  export class IdentityManager {
51
53
  readonly stateUpdate = new Event();
52
54
 
@@ -65,7 +67,8 @@ export class IdentityManager {
65
67
  return this._identity;
66
68
  }
67
69
 
68
- async open() {
70
+ @Trace.span()
71
+ async open(ctx: Context) {
69
72
  const traceId = PublicKey.random().toHex();
70
73
  log.trace('dxos.halo.identity-manager.open', trace.begin({ id: traceId }));
71
74
  await this._metadataStore.load();
@@ -74,7 +77,7 @@ export class IdentityManager {
74
77
  log('identity record', { identityRecord });
75
78
  if (identityRecord) {
76
79
  this._identity = await this._constructIdentity(identityRecord);
77
- await this._identity.open();
80
+ await this._identity.open(ctx);
78
81
  await this._identity.ready();
79
82
  log.trace('dxos.halo.identity', {
80
83
  identityKey: identityRecord.identityKey,
@@ -86,7 +89,7 @@ export class IdentityManager {
86
89
  }
87
90
 
88
91
  async close() {
89
- await this._identity?.close();
92
+ await this._identity?.close(new Context());
90
93
  }
91
94
 
92
95
  async createIdentity({ displayName }: CreateIdentityOptions = {}) {
@@ -106,7 +109,7 @@ export class IdentityManager {
106
109
  };
107
110
 
108
111
  const identity = await this._constructIdentity(identityRecord);
109
- await identity.open();
112
+ await identity.open(new Context());
110
113
 
111
114
  {
112
115
  const generator = new CredentialGenerator(this._keyring, identityRecord.identityKey, identityRecord.deviceKey);
@@ -176,7 +179,7 @@ export class IdentityManager {
176
179
  };
177
180
  const identity = await this._constructIdentity(identityRecord);
178
181
 
179
- await identity.open();
182
+ await identity.open(new Context());
180
183
  this._identity = identity;
181
184
  await this._metadataStore.setIdentityRecord(identityRecord);
182
185
  await this._identity.ready();
@@ -6,6 +6,7 @@ import chai, { expect } from 'chai';
6
6
  import chaiAsPromised from 'chai-as-promised';
7
7
 
8
8
  import { Trigger } from '@dxos/async';
9
+ import { Context } from '@dxos/context';
9
10
  import { PublicKey } from '@dxos/keys';
10
11
  import { Identity, IdentityService } from '@dxos/protocols/proto/dxos/client/services';
11
12
  import { afterEach, afterTest, beforeEach, describe, test } from '@dxos/test';
@@ -22,7 +23,7 @@ describe('IdentityService', () => {
22
23
 
23
24
  beforeEach(async () => {
24
25
  serviceContext = createServiceContext();
25
- await serviceContext.open();
26
+ await serviceContext.open(new Context());
26
27
  identityService = new IdentityServiceImpl(serviceContext);
27
28
  });
28
29
 
@@ -2,11 +2,10 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- import invariant from 'tiny-invariant';
6
-
7
5
  import { Stream } from '@dxos/codec-protobuf';
8
6
  import { signPresentation } from '@dxos/credentials';
9
7
  import { todo } from '@dxos/debug';
8
+ import { invariant } from '@dxos/invariant';
10
9
  import {
11
10
  Identity,
12
11
  IdentityService,
@@ -4,6 +4,7 @@
4
4
 
5
5
  import expect from 'expect';
6
6
 
7
+ import { Context } from '@dxos/context';
7
8
  import { CredentialGenerator, verifyCredential } from '@dxos/credentials';
8
9
  import {
9
10
  MetadataStore,
@@ -106,8 +107,8 @@ describe('identity/identity', () => {
106
107
  space,
107
108
  });
108
109
 
109
- await identity.open();
110
- afterTest(() => identity.close());
110
+ await identity.open(new Context());
111
+ afterTest(() => identity.close(new Context()));
111
112
 
112
113
  //
113
114
  // Identity genesis
@@ -219,8 +220,8 @@ describe('identity/identity', () => {
219
220
  space,
220
221
  }));
221
222
 
222
- await identity.open();
223
- afterTest(() => identity.close());
223
+ await identity.open(new Context());
224
+ afterTest(() => identity.close(new Context()));
224
225
 
225
226
  //
226
227
  // Identity genesis
@@ -310,8 +311,8 @@ describe('identity/identity', () => {
310
311
  space,
311
312
  }));
312
313
 
313
- await identity.open();
314
- afterTest(() => identity.close());
314
+ await identity.open(new Context());
315
+ afterTest(() => identity.close(new Context()));
315
316
  }
316
317
 
317
318
  //
@@ -2,10 +2,9 @@
2
2
  // Copyright 2022 DXOS.org
3
3
  //
4
4
 
5
- import invariant from 'tiny-invariant';
6
-
7
5
  import { Event } from '@dxos/async';
8
6
  import { AUTH_TIMEOUT, LOAD_CONTROL_FEEDS_TIMEOUT } from '@dxos/client-protocol';
7
+ import { Context } from '@dxos/context';
9
8
  import {
10
9
  DeviceStateMachine,
11
10
  CredentialSigner,
@@ -17,11 +16,13 @@ import { Signer } from '@dxos/crypto';
17
16
  import { failUndefined } from '@dxos/debug';
18
17
  import { Space } from '@dxos/echo-pipeline';
19
18
  import { writeMessages } from '@dxos/feed-store';
19
+ import { invariant } from '@dxos/invariant';
20
20
  import { PublicKey } from '@dxos/keys';
21
21
  import { log } from '@dxos/log';
22
22
  import { FeedMessage } from '@dxos/protocols/proto/dxos/echo/feed';
23
23
  import { AdmittedFeed, ProfileDocument } from '@dxos/protocols/proto/dxos/halo/credentials';
24
24
  import { DeviceAdmissionRequest } from '@dxos/protocols/proto/dxos/halo/invitations';
25
+ import { trace } from '@dxos/tracing';
25
26
  import { ComplexSet } from '@dxos/util';
26
27
 
27
28
  import { TrustedKeySetAuthVerifier } from './authenticator';
@@ -36,6 +37,7 @@ export type IdentityParams = {
36
37
  /**
37
38
  * Agent identity manager, which includes the agent's Halo space.
38
39
  */
40
+ @trace.resource()
39
41
  export class Identity {
40
42
  public readonly space: Space;
41
43
  private readonly _signer: Signer;
@@ -77,13 +79,15 @@ export class Identity {
77
79
  return this._deviceStateMachine.authorizedDeviceKeys;
78
80
  }
79
81
 
80
- async open() {
82
+ @trace.span()
83
+ async open(ctx: Context) {
81
84
  await this.space.spaceState.addCredentialProcessor(this._deviceStateMachine);
82
85
  await this.space.spaceState.addCredentialProcessor(this._profileStateMachine);
83
- await this.space.open();
86
+ await this.space.open(ctx);
84
87
  }
85
88
 
86
- async close() {
89
+ @trace.span()
90
+ async close(ctx: Context) {
87
91
  await this.authVerifier.close();
88
92
  await this.space.spaceState.removeCredentialProcessor(this._profileStateMachine);
89
93
  await this.space.spaceState.removeCredentialProcessor(this._deviceStateMachine);
@@ -5,6 +5,7 @@
5
5
  import { expect } from 'chai';
6
6
 
7
7
  import { asyncChain } from '@dxos/async';
8
+ import { Context } from '@dxos/context';
8
9
  import { Invitation } from '@dxos/protocols/proto/dxos/client/services';
9
10
  import { describe, test, afterTest } from '@dxos/test';
10
11
 
@@ -19,7 +20,7 @@ const closeAfterTest = async (peer: ServiceContext) => {
19
20
  describe('services/device', () => {
20
21
  test('creates identity', async () => {
21
22
  const peer = createServiceContext();
22
- await peer.open();
23
+ await peer.open(new Context());
23
24
  afterTest(() => peer.close());
24
25
 
25
26
  const identity = await peer.createIdentity();
@@ -2,8 +2,7 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- import invariant from 'tiny-invariant';
6
-
5
+ import { invariant } from '@dxos/invariant';
7
6
  import { Keyring } from '@dxos/keyring';
8
7
  import { Invitation } from '@dxos/protocols/proto/dxos/client/services';
9
8
  import { AdmissionRequest, AdmissionResponse, IntroductionRequest } from '@dxos/protocols/proto/dxos/halo/invitations';
@@ -2,11 +2,10 @@
2
2
  // Copyright 2022 DXOS.org
3
3
  //
4
4
 
5
- import invariant from 'tiny-invariant';
6
-
7
5
  import { Trigger } from '@dxos/async';
8
6
  import { cancelWithContext, Context } from '@dxos/context';
9
7
  import { InvalidInvitationExtensionRoleError } from '@dxos/errors';
8
+ import { invariant } from '@dxos/invariant';
10
9
  import { PublicKey } from '@dxos/keys';
11
10
  import { log } from '@dxos/log';
12
11
  import { schema, trace } from '@dxos/protocols';
@@ -2,8 +2,6 @@
2
2
  // Copyright 2022 DXOS.org
3
3
  //
4
4
 
5
- import invariant from 'tiny-invariant';
6
-
7
5
  import { PushStream, scheduleTask, TimeoutError, Trigger } from '@dxos/async';
8
6
  import {
9
7
  AuthenticatingInvitationObservable,
@@ -14,6 +12,7 @@ import {
14
12
  import { Context } from '@dxos/context';
15
13
  import { generatePasscode } from '@dxos/credentials';
16
14
  import { InvalidInvitationExtensionRoleError } from '@dxos/errors';
15
+ import { invariant } from '@dxos/invariant';
17
16
  import { PublicKey } from '@dxos/keys';
18
17
  import { log } from '@dxos/log';
19
18
  import { createTeleportProtocolFactory, NetworkManager, StarTopology, SwarmConnection } from '@dxos/network-manager';
@@ -2,11 +2,10 @@
2
2
  // Copyright 2022 DXOS.org
3
3
  //
4
4
 
5
- import invariant from 'tiny-invariant';
6
-
7
5
  import { Event } from '@dxos/async';
8
6
  import { AuthenticatingInvitationObservable, CancellableInvitationObservable } from '@dxos/client-protocol';
9
7
  import { Stream } from '@dxos/codec-protobuf';
8
+ import { invariant } from '@dxos/invariant';
10
9
  import { log } from '@dxos/log';
11
10
  import {
12
11
  AuthenticationRequest,
@@ -2,10 +2,9 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- import invariant from 'tiny-invariant';
6
-
7
5
  import { createAdmissionCredentials, getCredentialAssertion } from '@dxos/credentials';
8
6
  import { writeMessages } from '@dxos/feed-store';
7
+ import { invariant } from '@dxos/invariant';
9
8
  import { Keyring } from '@dxos/keyring';
10
9
  import { PublicKey } from '@dxos/keys';
11
10
  import { log } from '@dxos/log';
@@ -3,8 +3,8 @@
3
3
  //
4
4
 
5
5
  import { FileHandle } from 'node:fs/promises';
6
- import invariant from 'tiny-invariant';
7
6
 
7
+ import { invariant } from '@dxos/invariant';
8
8
  import { LockFile } from '@dxos/lock-file';
9
9
  import { log, logInfo } from '@dxos/log';
10
10
 
@@ -13,6 +13,7 @@ import {
13
13
  ControlMetricsRequest,
14
14
  ControlMetricsResponse,
15
15
  QueryMetricsRequest,
16
+ QueryMetricsResponse,
16
17
  } from '@dxos/protocols/proto/dxos/client/services';
17
18
  import { jsonify, numericalValues, tracer } from '@dxos/util';
18
19
 
@@ -21,6 +22,7 @@ import { jsonify, numericalValues, tracer } from '@dxos/util';
21
22
  */
22
23
  export class LoggingServiceImpl implements LoggingService {
23
24
  private readonly _logs = new Event<NaturalLogEntry>();
25
+ private readonly _started = new Date();
24
26
 
25
27
  async open() {
26
28
  log.runtimeConfig.processors.push(this._logProcessor);
@@ -45,11 +47,14 @@ export class LoggingServiceImpl implements LoggingService {
45
47
  return { recording: tracer.recording };
46
48
  }
47
49
 
48
- queryMetrics({ interval }: QueryMetricsRequest): Stream<Metrics> {
50
+ /**
51
+ * @deprecated (Move to diagnostics).
52
+ */
53
+ queryMetrics({ interval = 5_000 }: QueryMetricsRequest): Stream<QueryMetricsResponse> {
49
54
  // TODO(burdon): Map all traces; how to bind to reducer/metrics shape (e.g., numericalValues)?
50
- const createNumericalValues = (key: string) => {
51
- const consume = tracer.get(key) ?? [];
52
- return { key, stats: numericalValues(consume, 'duration') };
55
+ const getNumericalValues = (key: string) => {
56
+ const events = tracer.get(key) ?? [];
57
+ return { key, stats: numericalValues(events, 'duration') };
53
58
  };
54
59
 
55
60
  return new Stream(({ next }) => {
@@ -57,16 +62,19 @@ export class LoggingServiceImpl implements LoggingService {
57
62
  const metrics: Metrics = {
58
63
  timestamp: new Date(),
59
64
  values: [
60
- createNumericalValues('dxos.echo.pipeline.control'),
61
- createNumericalValues('dxos.echo.pipeline.data'),
62
- ],
65
+ getNumericalValues('dxos.echo.pipeline.control'),
66
+ getNumericalValues('dxos.echo.pipeline.data'),
67
+ ].filter(Boolean) as Metrics.KeyPair[],
63
68
  };
64
69
 
65
- next(metrics);
70
+ next({
71
+ timestamp: new Date(),
72
+ metrics,
73
+ });
66
74
  };
67
75
 
68
76
  update();
69
- const i = setInterval(update, interval);
77
+ const i = setInterval(update, Math.max(interval, 1_000));
70
78
  return () => {
71
79
  clearInterval(i);
72
80
  };
@@ -5,6 +5,7 @@
5
5
  import { expect } from 'chai';
6
6
 
7
7
  import { Trigger } from '@dxos/async';
8
+ import { Context } from '@dxos/context';
8
9
  import { NetworkService, ConnectionState } from '@dxos/protocols/proto/dxos/client/services';
9
10
  import { afterEach, afterTest, beforeEach, describe, test } from '@dxos/test';
10
11
 
@@ -18,7 +19,7 @@ describe('NetworkService', () => {
18
19
 
19
20
  beforeEach(async () => {
20
21
  serviceContext = createServiceContext();
21
- await serviceContext.open();
22
+ await serviceContext.open(new Context());
22
23
  networkService = new NetworkServiceImpl(serviceContext.networkManager, serviceContext.signalManager);
23
24
  });
24
25
 
@@ -5,7 +5,7 @@
5
5
  import { ClientServices } from '@dxos/client-protocol';
6
6
  import { Any, ServiceHandler, Stream } from '@dxos/codec-protobuf';
7
7
  import { raise } from '@dxos/debug';
8
- import { parseMethodName, RpcPeer, RpcPeerOptions } from '@dxos/rpc';
8
+ import { parseMethodName, RpcPeer, RpcPeerOptions, ServiceBundle } from '@dxos/rpc';
9
9
  import { MaybePromise } from '@dxos/util';
10
10
 
11
11
  import { ServiceRegistry } from './service-registry';
@@ -74,16 +74,16 @@ export class ClientRpcServer {
74
74
  private _getServiceHandler(serviceName: string) {
75
75
  if (!this._handlerCache.has(serviceName)) {
76
76
  const [key, descriptor] =
77
- Object.entries(this._serviceRegistry.descriptors).find(
77
+ Object.entries(this._serviceRegistry.descriptors as ServiceBundle<Record<string, any>>).find(
78
78
  ([key, descriptor]) => descriptor.name === serviceName,
79
79
  ) ?? raise(new Error(`Service not available: ${serviceName}`));
80
80
 
81
- const service = this._serviceRegistry.services[key as keyof ClientServices];
81
+ const service = this._serviceRegistry.services[key as keyof ClientServices] as any;
82
82
  if (!service) {
83
83
  throw new Error(`Service not available: ${serviceName}`);
84
84
  }
85
85
 
86
- this._handlerCache.set(serviceName, descriptor.createServer(service as any));
86
+ this._handlerCache.set(serviceName, descriptor.createServer(service));
87
87
  }
88
88
 
89
89
  return this._handlerCache.get(serviceName)!;
@@ -0,0 +1,211 @@
1
+ //
2
+ // Copyright 2022 DXOS.org
3
+ //
4
+
5
+ import { ClientServices } from '@dxos/client-protocol';
6
+ import { getFirstStreamValue } from '@dxos/codec-protobuf';
7
+ import { Config, ConfigProto } from '@dxos/config';
8
+ import { credentialTypeFilter } from '@dxos/credentials';
9
+ import { DocumentModel, DocumentModelState } from '@dxos/document-model';
10
+ import { invariant } from '@dxos/invariant';
11
+ import { PublicKey } from '@dxos/keys';
12
+ import { log } from '@dxos/log';
13
+ import { STORAGE_VERSION } from '@dxos/protocols';
14
+ import {
15
+ Device,
16
+ Identity,
17
+ Metrics,
18
+ Space as SpaceProto,
19
+ SpaceMember,
20
+ } from '@dxos/protocols/proto/dxos/client/services';
21
+ import { SubscribeToFeedsResponse } from '@dxos/protocols/proto/dxos/devtools/host';
22
+ import { Epoch } from '@dxos/protocols/proto/dxos/halo/credentials';
23
+
24
+ import { DXOS_VERSION } from '../../version';
25
+ import { DataSpace } from '../spaces';
26
+ import { getPlatform, Platform } from './platform';
27
+ import { ServiceContext } from './service-context';
28
+
29
+ const DEFAULT_TIMEOUT = 1_000;
30
+
31
+ export type Diagnostics = {
32
+ created: string;
33
+ platform: Platform;
34
+ config?: ConfigProto;
35
+ client: {
36
+ version: string;
37
+ storage: {
38
+ version: number;
39
+ };
40
+ };
41
+ identity?: Identity;
42
+ devices?: Device[];
43
+ spaces?: SpaceStats[];
44
+ feeds?: Partial<SubscribeToFeedsResponse.Feed>[];
45
+ metrics?: Metrics;
46
+ };
47
+
48
+ // TODO(burdon): Normalize for ECHO/HALO.
49
+ export type SpaceStats = {
50
+ key: PublicKey;
51
+ properties?: {
52
+ name: string;
53
+ };
54
+ db?: {
55
+ objects: number;
56
+ };
57
+ metrics?: SpaceProto.Metrics & {
58
+ startupTime?: number;
59
+ };
60
+ epochs?: (Epoch & { id?: PublicKey })[];
61
+ members?: SpaceMember[];
62
+ pipeline?: SpaceProto.PipelineState;
63
+ };
64
+
65
+ /**
66
+ * Create diagnostics to provide snapshot of current system state.
67
+ */
68
+ export const createDiagnostics = async (
69
+ clientServices: Partial<ClientServices>,
70
+ serviceContext: ServiceContext,
71
+ config: Config,
72
+ ): Promise<Diagnostics> => {
73
+ const diagnostics: Diagnostics = {
74
+ created: new Date().toISOString(),
75
+ platform: getPlatform(),
76
+ client: {
77
+ version: DXOS_VERSION,
78
+ storage: {
79
+ version: STORAGE_VERSION,
80
+ },
81
+ },
82
+ };
83
+
84
+ // Trace metrics.
85
+ // TODO(burdon): Move here from logging service?
86
+ {
87
+ invariant(clientServices.LoggingService, 'SystemService is not available.');
88
+ diagnostics.metrics = await getFirstStreamValue(clientServices.LoggingService.queryMetrics({}), {
89
+ timeout: DEFAULT_TIMEOUT,
90
+ }).catch(() => undefined);
91
+ }
92
+
93
+ const identity = serviceContext.identityManager.identity;
94
+ if (identity) {
95
+ // Identity.
96
+ diagnostics.identity = {
97
+ identityKey: identity.identityKey,
98
+ spaceKey: identity.space.key,
99
+ profile: identity.profileDocument,
100
+ };
101
+
102
+ // Devices.
103
+ const { devices } =
104
+ (await getFirstStreamValue(clientServices.DevicesService!.queryDevices(), {
105
+ timeout: DEFAULT_TIMEOUT,
106
+ }).catch(() => undefined)) ?? {};
107
+ diagnostics.devices = devices;
108
+
109
+ // TODO(dmaretskyi): Add metrics for halo space.
110
+
111
+ // Spaces.
112
+ if (serviceContext.dataSpaceManager) {
113
+ diagnostics.spaces = await Promise.all(
114
+ Array.from(serviceContext.dataSpaceManager.spaces.values()).map((space) => getSpaceStats(space)) ?? [],
115
+ );
116
+ }
117
+
118
+ // Feeds.
119
+ const { feeds = [] } =
120
+ (await getFirstStreamValue(clientServices.DevtoolsHost!.subscribeToFeeds({}), {
121
+ timeout: DEFAULT_TIMEOUT,
122
+ }).catch(() => undefined)) ?? {};
123
+ diagnostics.feeds = feeds.map(({ feedKey, bytes, length }) => ({ feedKey, bytes, length }));
124
+ }
125
+
126
+ diagnostics.config = config.values;
127
+
128
+ return diagnostics;
129
+ };
130
+
131
+ const getProperties = (space: DataSpace) => {
132
+ let properties: any = {};
133
+ try {
134
+ // Add properties to cache.
135
+ const propertiesItem = space.dataPipeline.itemManager.items.find(
136
+ (item) =>
137
+ item.modelMeta?.type === DocumentModel.meta.type &&
138
+ (item.state as DocumentModelState)?.type === 'dxos.sdk.client.Properties',
139
+ );
140
+
141
+ const state = propertiesItem?.state as DocumentModelState;
142
+ properties = state?.data;
143
+ } catch (err: any) {
144
+ log.warn(err.message);
145
+ }
146
+
147
+ return properties;
148
+ };
149
+
150
+ const getSpaceStats = async (space: DataSpace): Promise<SpaceStats> => {
151
+ const stats: SpaceStats = {
152
+ key: space.key,
153
+ metrics: space.metrics,
154
+
155
+ epochs: space.inner.spaceState.credentials
156
+ .filter(credentialTypeFilter('dxos.halo.credentials.Epoch'))
157
+ .map((credential) => ({
158
+ ...credential.subject.assertion,
159
+ id: credential.id,
160
+ })),
161
+
162
+ members: Array.from(space.inner.spaceState.members.values()).map((member) => ({
163
+ identity: {
164
+ identityKey: member.key,
165
+ profile: {
166
+ displayName: member.assertion.profile?.displayName,
167
+ },
168
+ },
169
+ presence:
170
+ space.presence.getPeersOnline().filter(({ identityKey }) => identityKey.equals(member.key)).length > 0
171
+ ? SpaceMember.PresenceState.ONLINE
172
+ : SpaceMember.PresenceState.OFFLINE,
173
+ })),
174
+
175
+ pipeline: {
176
+ // TODO(burdon): Pick properties from credentials if needed.
177
+ // currentEpoch: space.dataPipeline.currentEpoch,
178
+ // appliedEpoch: space.dataPipeline.appliedEpoch,
179
+
180
+ controlFeeds: space.inner.controlPipeline.state.feeds.map((feed) => feed.key),
181
+ currentControlTimeframe: space.inner.controlPipeline.state.timeframe,
182
+ targetControlTimeframe: space.inner.controlPipeline.state.targetTimeframe,
183
+ totalControlTimeframe: space.inner.controlPipeline.state.endTimeframe,
184
+
185
+ // TODO(burdon): Empty?
186
+ dataFeeds: space.dataPipeline.pipelineState?.feeds.map((feed) => feed.key) ?? [],
187
+ startDataTimeframe: space.dataPipeline.pipelineState?.startTimeframe,
188
+ currentDataTimeframe: space.dataPipeline.pipelineState?.timeframe,
189
+ targetDataTimeframe: space.dataPipeline.pipelineState?.targetTimeframe,
190
+ totalDataTimeframe: space.dataPipeline.pipelineState?.endTimeframe,
191
+ },
192
+ };
193
+
194
+ // TODO(burdon): May not be open?
195
+ if (space.dataPipeline.itemManager) {
196
+ Object.assign(stats, {
197
+ properties: getProperties(space),
198
+ db: {
199
+ objects: space.dataPipeline.itemManager.entities.size,
200
+ },
201
+ } as SpaceStats);
202
+ }
203
+
204
+ // TODO(burdon): Factor out.
205
+ if (stats.metrics) {
206
+ const { open, ready } = stats.metrics;
207
+ stats.metrics.startupTime = open && ready && ready.getTime() - open.getTime();
208
+ }
209
+
210
+ return stats;
211
+ };
@@ -3,6 +3,7 @@
3
3
  //
4
4
 
5
5
  export * from './client-rpc-server';
6
+ export * from './diagnostics';
6
7
  export * from './service-context';
7
8
  export * from './service-host';
8
9
  export * from './service-registry';