@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
@@ -0,0 +1,48 @@
1
+ //
2
+ // Copyright 2022 DXOS.org
3
+ //
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
+ };
19
+
20
+ export const getPlatform = (): Platform => {
21
+ if ((process as any).browser) {
22
+ if (typeof window !== 'undefined') {
23
+ // Browser.
24
+ const { userAgent } = window.navigator;
25
+ return {
26
+ type: 'browser',
27
+ userAgent,
28
+ uptime: Math.floor((Date.now() - window.performance.timeOrigin) / 1_000),
29
+ };
30
+ } else {
31
+ // Shared worker.
32
+ return {
33
+ type: 'shared-worker',
34
+ uptime: Math.floor((Date.now() - performance.timeOrigin) / 1_000),
35
+ };
36
+ }
37
+ } else {
38
+ // Node.
39
+ const { platform, version, arch } = process;
40
+ return {
41
+ type: 'node',
42
+ platform: `${platform} ${version} ${arch}`,
43
+ runtime: process.version,
44
+ uptime: Math.floor(process.uptime()),
45
+ memory: process.memoryUsage(),
46
+ };
47
+ }
48
+ };
@@ -2,9 +2,8 @@
2
2
  // Copyright 2022 DXOS.org
3
3
  //
4
4
 
5
- import invariant from 'tiny-invariant';
6
-
7
5
  import { Trigger } from '@dxos/async';
6
+ import { Context } from '@dxos/context';
8
7
  import { CredentialProcessor, getCredentialAssertion } from '@dxos/credentials';
9
8
  import { failUndefined } from '@dxos/debug';
10
9
  import {
@@ -15,6 +14,7 @@ import {
15
14
  SnapshotStore,
16
15
  } from '@dxos/echo-pipeline';
17
16
  import { FeedFactory, FeedStore } from '@dxos/feed-store';
17
+ import { invariant } from '@dxos/invariant';
18
18
  import { Keyring } from '@dxos/keyring';
19
19
  import { PublicKey } from '@dxos/keys';
20
20
  import { log } from '@dxos/log';
@@ -27,6 +27,7 @@ import type { FeedMessage } from '@dxos/protocols/proto/dxos/echo/feed';
27
27
  import { Credential } from '@dxos/protocols/proto/dxos/halo/credentials';
28
28
  import { Storage } from '@dxos/random-access-storage';
29
29
  import { BlobStore } from '@dxos/teleport-extension-object-sync';
30
+ import { trace as Trace } from '@dxos/tracing';
30
31
 
31
32
  import { CreateIdentityOptions, IdentityManager, JoinIdentityParams } from '../identity';
32
33
  import {
@@ -41,6 +42,7 @@ import { DataSpaceManager, SigningContext } from '../spaces';
41
42
  * Shared backend for all client services.
42
43
  */
43
44
  // TODO(burdon): Rename/break-up into smaller components. And/or make members private.
45
+ @Trace.resource()
44
46
  export class ServiceContext {
45
47
  public readonly initialized = new Trigger();
46
48
  public readonly dataServiceSubscriptions = new DataServiceSubscriptions();
@@ -122,21 +124,21 @@ export class ServiceContext {
122
124
  );
123
125
  }
124
126
 
125
- async open() {
126
- log.trace('dxos.sdk.service-context.open', trace.begin({ id: this._instanceId }));
127
-
127
+ @Trace.span()
128
+ async open(ctx: Context) {
128
129
  await this._checkStorageVersion();
129
130
 
130
131
  log('opening...');
132
+ log.trace('dxos.sdk.service-context.open', trace.begin({ id: this._instanceId }));
131
133
  await this.signalManager.open();
132
134
  await this.networkManager.open();
133
135
  await this.spaceManager.open();
134
- await this.identityManager.open();
136
+ await this.identityManager.open(ctx);
135
137
  if (this.identityManager.identity) {
136
- await this._initialize();
138
+ await this._initialize(ctx);
137
139
  }
138
- log('opened');
139
140
  log.trace('dxos.sdk.service-context.open', trace.end({ id: this._instanceId }));
141
+ log('opened');
140
142
  }
141
143
 
142
144
  async close() {
@@ -157,7 +159,7 @@ export class ServiceContext {
157
159
  async createIdentity(params: CreateIdentityOptions = {}) {
158
160
  const identity = await this.identityManager.createIdentity(params);
159
161
 
160
- await this._initialize();
162
+ await this._initialize(new Context());
161
163
  return identity;
162
164
  }
163
165
 
@@ -170,7 +172,7 @@ export class ServiceContext {
170
172
  private async _acceptIdentity(params: JoinIdentityParams) {
171
173
  const identity = await this.identityManager.acceptIdentity(params);
172
174
 
173
- await this._initialize();
175
+ await this._initialize(new Context());
174
176
  return identity;
175
177
  }
176
178
 
@@ -183,7 +185,8 @@ export class ServiceContext {
183
185
  }
184
186
 
185
187
  // Called when identity is created.
186
- private async _initialize() {
188
+ @Trace.span()
189
+ private async _initialize(ctx: Context) {
187
190
  log('initializing spaces...');
188
191
  const identity = this.identityManager.identity ?? failUndefined();
189
192
  const signingContext: SigningContext = {
@@ -8,6 +8,7 @@ import { rmSync } from 'node:fs';
8
8
 
9
9
  import { asyncTimeout, latch, Trigger } from '@dxos/async';
10
10
  import { Config } from '@dxos/config';
11
+ import { Context } from '@dxos/context';
11
12
  import { verifyPresentation } from '@dxos/credentials';
12
13
  import { PublicKey } from '@dxos/keys';
13
14
  import { MemorySignalManagerContext } from '@dxos/messaging';
@@ -30,7 +31,7 @@ describe('ClientServicesHost', () => {
30
31
 
31
32
  test('queryCredentials', async () => {
32
33
  const host = createServiceHost(new Config(), new MemorySignalManagerContext());
33
- await host.open();
34
+ await host.open(new Context());
34
35
  afterTest(() => host.close());
35
36
 
36
37
  await host.services.IdentityService!.createIdentity({});
@@ -49,14 +50,14 @@ describe('ClientServicesHost', () => {
49
50
 
50
51
  test('write and query credentials', async () => {
51
52
  const host = createServiceHost(new Config(), new MemorySignalManagerContext());
52
- await host.open();
53
+ await host.open(new Context());
53
54
  afterTest(() => host.close());
54
55
 
55
56
  await host.services.IdentityService!.createIdentity({});
56
57
 
57
58
  const testCredential = await createMockCredential({
58
- signer: host._serviceContext.keyring,
59
- issuer: host._serviceContext.identityManager.identity!.deviceKey,
59
+ signer: host.context.keyring,
60
+ issuer: host.context.identityManager.identity!.deviceKey,
60
61
  });
61
62
 
62
63
  // Test if Identity exposes haloSpace key.
@@ -86,14 +87,14 @@ describe('ClientServicesHost', () => {
86
87
 
87
88
  test('sign presentation', async () => {
88
89
  const host = createServiceHost(new Config(), new MemorySignalManagerContext());
89
- await host.open();
90
+ await host.open(new Context());
90
91
  afterTest(() => host.close());
91
92
 
92
93
  await host.services.IdentityService!.createIdentity({});
93
94
 
94
95
  const testCredential = await createMockCredential({
95
- signer: host._serviceContext.keyring,
96
- issuer: host._serviceContext.identityManager.identity!.deviceKey,
96
+ signer: host.context.keyring,
97
+ issuer: host.context.identityManager.identity!.deviceKey,
97
98
  });
98
99
 
99
100
  const nonce = new Uint8Array([0, 0, 0, 0]);
@@ -117,11 +118,11 @@ describe('ClientServicesHost', () => {
117
118
  });
118
119
  {
119
120
  const host = createServiceHost(config, new MemorySignalManagerContext());
120
- await host.open();
121
+ await host.open(new Context());
121
122
 
122
123
  await host.services.IdentityService?.createIdentity({});
123
124
 
124
- expect(host._serviceContext.storage.size).to.exist;
125
+ expect(host.context.storage.size).to.exist;
125
126
 
126
127
  await asyncTimeout(host.reset(), 1000);
127
128
  await host.close();
@@ -129,7 +130,7 @@ describe('ClientServicesHost', () => {
129
130
 
130
131
  {
131
132
  const host = createServiceHost(config, new MemorySignalManagerContext());
132
- await host.open();
133
+ await host.open(new Context());
133
134
  const trigger = new Trigger<Identity>();
134
135
 
135
136
  const stream = host.services.IdentityService?.queryIdentity();
@@ -2,13 +2,13 @@
2
2
  // Copyright 2021 DXOS.org
3
3
  //
4
4
 
5
- import invariant from 'tiny-invariant';
6
-
7
5
  import { Event, synchronized } from '@dxos/async';
8
6
  import { clientServiceBundle, ClientServices } from '@dxos/client-protocol';
9
7
  import { Config } from '@dxos/config';
8
+ import { Context } from '@dxos/context';
10
9
  import { DocumentModel } from '@dxos/document-model';
11
10
  import { DataServiceImpl } from '@dxos/echo-pipeline';
11
+ import { invariant } from '@dxos/invariant';
12
12
  import { PublicKey } from '@dxos/keys';
13
13
  import { log } from '@dxos/log';
14
14
  import { SignalManager, WebsocketSignalManager } from '@dxos/messaging';
@@ -18,6 +18,7 @@ import { trace } from '@dxos/protocols';
18
18
  import { SystemStatus } from '@dxos/protocols/proto/dxos/client/services';
19
19
  import { Storage } from '@dxos/random-access-storage';
20
20
  import { TextModel } from '@dxos/text-model';
21
+ import { TRACE_PROCESSOR, trace as Trace } from '@dxos/tracing';
21
22
 
22
23
  import { DevicesServiceImpl } from '../devices';
23
24
  import { DevtoolsServiceImpl, DevtoolsHostEvents } from '../devtools';
@@ -29,6 +30,7 @@ import { NetworkServiceImpl } from '../network';
29
30
  import { SpacesServiceImpl } from '../spaces';
30
31
  import { createStorageObjects } from '../storage';
31
32
  import { SystemServiceImpl } from '../system';
33
+ import { createDiagnostics } from './diagnostics';
32
34
  import { ServiceContext } from './service-context';
33
35
  import { ServiceRegistry } from './service-registry';
34
36
 
@@ -65,11 +67,13 @@ export type InitializeOptions = {
65
67
  /**
66
68
  * Remote service implementation.
67
69
  */
70
+ @Trace.resource()
68
71
  export class ClientServicesHost {
69
72
  private readonly _resourceLock?: ResourceLock;
70
73
  private readonly _serviceRegistry: ServiceRegistry<ClientServices>;
71
74
  private readonly _systemService: SystemServiceImpl;
72
75
  private readonly _loggingService: LoggingServiceImpl;
76
+ private readonly _tracingService = TRACE_PROCESSOR.createTraceSender();
73
77
 
74
78
  private _config?: Config;
75
79
  private readonly _statusUpdate = new Event<void>();
@@ -79,17 +83,19 @@ export class ClientServicesHost {
79
83
  private _storage?: Storage;
80
84
  private _callbacks?: ClientServicesHostCallbacks;
81
85
 
82
- _serviceContext!: ServiceContext;
86
+ private _serviceContext!: ServiceContext;
87
+
88
+ @Trace.info()
83
89
  private _opening = false;
90
+
91
+ @Trace.info()
84
92
  private _open = false;
85
93
 
86
94
  constructor({
87
95
  config,
88
96
  modelFactory = createDefaultModelFactory(),
89
- // TODO(burdon): Create ApolloLink abstraction (see Client).
90
97
  transportFactory,
91
98
  signalManager,
92
- connectionLog,
93
99
  storage,
94
100
  // TODO(wittjosiah): Turn this on by default.
95
101
  lockKey,
@@ -108,7 +114,7 @@ export class ClientServicesHost {
108
114
  lockKey,
109
115
  onAcquire: () => {
110
116
  if (!this._opening) {
111
- void this.open();
117
+ void this.open(new Context());
112
118
  }
113
119
  },
114
120
  onRelease: () => this.close(),
@@ -117,11 +123,11 @@ export class ClientServicesHost {
117
123
 
118
124
  this._systemService = new SystemServiceImpl({
119
125
  config: this._config,
120
-
121
126
  statusUpdate: this._statusUpdate,
122
-
123
127
  getCurrentStatus: () => (this.isOpen ? SystemStatus.ACTIVE : SystemStatus.INACTIVE),
124
-
128
+ getDiagnostics: () => {
129
+ return createDiagnostics(this._serviceRegistry.services, this._serviceContext, this._config!);
130
+ },
125
131
  onUpdateStatus: async (status: SystemStatus) => {
126
132
  if (!this.isOpen && status === SystemStatus.ACTIVE) {
127
133
  await this._resourceLock?.acquire();
@@ -129,7 +135,6 @@ export class ClientServicesHost {
129
135
  await this._resourceLock?.release();
130
136
  }
131
137
  },
132
-
133
138
  onReset: async () => {
134
139
  await this.reset();
135
140
  },
@@ -137,9 +142,9 @@ export class ClientServicesHost {
137
142
 
138
143
  this._loggingService = new LoggingServiceImpl();
139
144
 
140
- // TODO(burdon): Start to think of DMG (dynamic services).
141
145
  this._serviceRegistry = new ServiceRegistry<ClientServices>(clientServiceBundle, {
142
146
  SystemService: this._systemService,
147
+ TracingService: this._tracingService,
143
148
  });
144
149
  }
145
150
 
@@ -147,6 +152,14 @@ export class ClientServicesHost {
147
152
  return this._open;
148
153
  }
149
154
 
155
+ get config() {
156
+ return this._config;
157
+ }
158
+
159
+ get context() {
160
+ return this._serviceContext;
161
+ }
162
+
150
163
  get serviceRegistry() {
151
164
  return this._serviceRegistry;
152
165
  }
@@ -166,10 +179,10 @@ export class ClientServicesHost {
166
179
  */
167
180
  initialize({ config, ...options }: InitializeOptions) {
168
181
  invariant(!this._open, 'service host is open');
182
+ log.info('initializing...');
169
183
 
170
184
  if (config) {
171
185
  invariant(!this._config, 'config already set');
172
-
173
186
  this._config = config;
174
187
  if (!this._storage) {
175
188
  this._storage = createStorageObjects(config.get('runtime.client.storage', {})!).storage;
@@ -191,16 +204,19 @@ export class ClientServicesHost {
191
204
  transportFactory,
192
205
  signalManager,
193
206
  });
207
+
208
+ log.info('initialized');
194
209
  }
195
210
 
196
211
  @synchronized
197
- async open() {
212
+ @Trace.span()
213
+ async open(ctx: Context) {
198
214
  if (this._open) {
199
215
  return;
200
216
  }
201
217
 
202
218
  const traceId = PublicKey.random().toHex();
203
- log.trace('dxos.sdk.client-services-host.open', trace.begin({ id: traceId }));
219
+ log.trace('dxos.client-services.host.open', trace.begin({ id: traceId }));
204
220
 
205
221
  invariant(this._config, 'config not set');
206
222
  invariant(this._storage, 'storage not set');
@@ -208,13 +224,11 @@ export class ClientServicesHost {
208
224
  invariant(this._networkManager, 'network manager not set');
209
225
 
210
226
  this._opening = true;
211
- log('opening...', { lockKey: this._resourceLock?.lockKey });
227
+ log.info('opening...', { lockKey: this._resourceLock?.lockKey });
212
228
  await this._resourceLock?.acquire();
213
229
 
214
230
  await this._loggingService.open();
215
231
 
216
- // TODO(wittjosiah): Make re-entrant.
217
- // TODO(burdon): Break into components.
218
232
  this._serviceContext = new ServiceContext(
219
233
  this._storage,
220
234
  this._networkManager,
@@ -222,7 +236,6 @@ export class ClientServicesHost {
222
236
  this._modelFactory,
223
237
  );
224
238
 
225
- // TODO(burdon): Start to think of DMG (dynamic services).
226
239
  this._serviceRegistry.setServices({
227
240
  SystemService: this._systemService,
228
241
 
@@ -249,6 +262,7 @@ export class ClientServicesHost {
249
262
  NetworkService: new NetworkServiceImpl(this._serviceContext.networkManager, this._serviceContext.signalManager),
250
263
 
251
264
  LoggingService: this._loggingService,
265
+ TracingService: this._tracingService,
252
266
 
253
267
  // TODO(burdon): Move to new protobuf definitions.
254
268
  DevtoolsHost: new DevtoolsServiceImpl({
@@ -258,29 +272,30 @@ export class ClientServicesHost {
258
272
  }),
259
273
  });
260
274
 
261
- await this._serviceContext.open();
275
+ await this._serviceContext.open(ctx);
262
276
  this._opening = false;
263
277
  this._open = true;
264
278
  this._statusUpdate.emit();
265
279
  const deviceKey = this._serviceContext.identityManager.identity?.deviceKey;
266
- log('opened', { deviceKey });
267
- log.trace('dxos.sdk.client-services-host.open', trace.end({ id: traceId }));
280
+ log.info('opened', { deviceKey });
281
+ log.trace('dxos.client-services.host.open', trace.end({ id: traceId }));
268
282
  }
269
283
 
270
284
  @synchronized
285
+ @Trace.span()
271
286
  async close() {
272
287
  if (!this._open) {
273
288
  return;
274
289
  }
275
290
 
276
291
  const deviceKey = this._serviceContext.identityManager.identity?.deviceKey;
277
- log('closing...', { deviceKey });
292
+ log.info('closing...', { deviceKey });
278
293
  this._serviceRegistry.setServices({ SystemService: this._systemService });
279
294
  await this._loggingService.close();
280
295
  await this._serviceContext.close();
281
296
  this._open = false;
282
297
  this._statusUpdate.emit();
283
- log('closed', { deviceKey });
298
+ log.info('closed', { deviceKey });
284
299
  }
285
300
 
286
301
  async reset() {
@@ -7,6 +7,7 @@ import { expect } from 'chai';
7
7
  import { Event } from '@dxos/async';
8
8
  import { ClientServices } from '@dxos/client-protocol';
9
9
  import { Config } from '@dxos/config';
10
+ import { Context } from '@dxos/context';
10
11
  import { log } from '@dxos/log';
11
12
  import { schema } from '@dxos/protocols';
12
13
  import { SystemService, SystemStatus } from '@dxos/protocols/proto/dxos/client/services';
@@ -31,12 +32,13 @@ describe('service registry', () => {
31
32
  test('builds a service registry', async () => {
32
33
  const remoteSource = 'https://remote.source';
33
34
  const serviceContext = createServiceContext();
34
- await serviceContext.open();
35
+ await serviceContext.open(new Context());
35
36
 
36
37
  const serviceRegistry = new ServiceRegistry(serviceBundle, {
37
38
  SystemService: new SystemServiceImpl({
38
39
  config: new Config({ runtime: { client: { remoteSource } } }),
39
40
  getCurrentStatus: () => SystemStatus.ACTIVE,
41
+ getDiagnostics: async () => ({}),
40
42
  onReset: () => {},
41
43
  onUpdateStatus: () => {},
42
44
  statusUpdate: new Event(),
@@ -0,0 +1,33 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ import { PublicKey } from '@dxos/keys';
6
+ import { humanize } from '@dxos/util';
7
+
8
+ // TODO(burdon): Move to util.
9
+
10
+ export type JsonStringifyOptions = {
11
+ truncate?: boolean;
12
+ humanize?: boolean;
13
+ };
14
+
15
+ export const jsonStringify = <T>(data: T, options: JsonStringifyOptions) => {
16
+ if (options.humanize || options.truncate) {
17
+ return JSON.parse(JSON.stringify(data, jsonKeyReplacer(options)));
18
+ }
19
+
20
+ return data;
21
+ };
22
+
23
+ const jsonKeyReplacer = (options: JsonStringifyOptions) => (key: string, value: any) => {
24
+ if (typeof value === 'string') {
25
+ // Infer if key.
26
+ const key = PublicKey.fromHex(value);
27
+ if (key.toHex() === value) {
28
+ return options.humanize ? humanize(key) : key.truncate();
29
+ }
30
+ }
31
+
32
+ return value;
33
+ };
@@ -2,13 +2,12 @@
2
2
  // Copyright 2022 DXOS.org
3
3
  //
4
4
 
5
- import invariant from 'tiny-invariant';
6
-
7
5
  import { Event, synchronized, trackLeaks } from '@dxos/async';
8
6
  import { cancelWithContext, Context } from '@dxos/context';
9
7
  import { CredentialSigner, getCredentialAssertion } from '@dxos/credentials';
10
8
  import { DataServiceSubscriptions, MetadataStore, Space, SpaceManager } from '@dxos/echo-pipeline';
11
9
  import { FeedStore } from '@dxos/feed-store';
10
+ import { invariant } from '@dxos/invariant';
12
11
  import { Keyring } from '@dxos/keyring';
13
12
  import { PublicKey } from '@dxos/keys';
14
13
  import { log } from '@dxos/log';
@@ -185,7 +184,7 @@ export class DataSpaceManager {
185
184
  localPeerId: this._signingContext.deviceKey,
186
185
  });
187
186
  const presence = new Presence({
188
- announceInterval: 500,
187
+ announceInterval: 1_000,
189
188
  offlineTimeout: 5_000, // TODO(burdon): Config.
190
189
  identityKey: this._signingContext.identityKey,
191
190
  gossip,
@@ -145,7 +145,7 @@ export class DataSpace {
145
145
  private async _open() {
146
146
  await this._notarizationPlugin.open();
147
147
  await this._inner.spaceState.addCredentialProcessor(this._notarizationPlugin);
148
- await this._inner.open();
148
+ await this._inner.open(new Context());
149
149
  this._state = SpaceState.CONTROL_ONLY;
150
150
  this.stateUpdate.emit();
151
151
  this.metrics = {};
@@ -2,12 +2,11 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- import invariant from 'tiny-invariant';
6
-
7
5
  import { DeferredTask, Event, scheduleTask, sleep, TimeoutError, Trigger } from '@dxos/async';
8
6
  import { Context, rejectOnDispose } from '@dxos/context';
9
7
  import { CredentialProcessor } from '@dxos/credentials';
10
8
  import { FeedWriter } from '@dxos/feed-store';
9
+ import { invariant } from '@dxos/invariant';
11
10
  import { PublicKey } from '@dxos/keys';
12
11
  import { log } from '@dxos/log';
13
12
  import { schema } from '@dxos/protocols';
@@ -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 { Space, SpacesService } from '@dxos/protocols/proto/dxos/client/services';
11
12
  import { afterEach, afterTest, beforeEach, describe, test } from '@dxos/test';
@@ -22,7 +23,7 @@ describe('SpacesService', () => {
22
23
 
23
24
  beforeEach(async () => {
24
25
  serviceContext = createServiceContext();
25
- await serviceContext.open();
26
+ await serviceContext.open(new Context());
26
27
  spacesService = new SpacesServiceImpl(
27
28
  serviceContext.identityManager,
28
29
  serviceContext.spaceManager,
@@ -185,19 +185,25 @@ export class SpacesServiceImpl implements SpacesService {
185
185
  targetDataTimeframe: space.dataPipeline.pipelineState?.targetTimeframe,
186
186
  totalDataTimeframe: space.dataPipeline.pipelineState?.endTimeframe,
187
187
  },
188
- members: Array.from(space.inner.spaceState.members.values()).map((member) => ({
189
- identity: {
190
- identityKey: member.key,
191
- profile: {
192
- displayName: member.assertion.profile?.displayName ?? humanize(member.key),
188
+ members: Array.from(space.inner.spaceState.members.values()).map((member) => {
189
+ const peers = space.presence.getPeersOnline().filter(({ identityKey }) => identityKey.equals(member.key));
190
+ const isMe = this._identityManager.identity?.identityKey.equals(member.key);
191
+
192
+ if (isMe) {
193
+ peers.push(space.presence.getLocalState());
194
+ }
195
+
196
+ return {
197
+ identity: {
198
+ identityKey: member.key,
199
+ profile: {
200
+ displayName: member.assertion.profile?.displayName ?? humanize(member.key),
201
+ },
193
202
  },
194
- },
195
- presence:
196
- this._identityManager.identity?.identityKey.equals(member.key) ||
197
- space.presence.getPeersOnline().filter(({ identityKey }) => identityKey.equals(member.key)).length > 0
198
- ? SpaceMember.PresenceState.ONLINE
199
- : SpaceMember.PresenceState.OFFLINE,
200
- })),
203
+ presence: isMe || peers.length > 0 ? SpaceMember.PresenceState.ONLINE : SpaceMember.PresenceState.OFFLINE,
204
+ peerStates: peers,
205
+ };
206
+ }),
201
207
  creator: space.inner.spaceState.creator?.key,
202
208
  cache: space.cache,
203
209
  metrics: space.metrics,
@@ -35,6 +35,7 @@ describe('SystemService', () => {
35
35
  config,
36
36
  statusUpdate,
37
37
  getCurrentStatus: () => currentStatus,
38
+ getDiagnostics: async () => ({}),
38
39
  onUpdateStatus: (status) => {
39
40
  updateStatus.wake(status);
40
41
  },
@@ -6,18 +6,22 @@ import { Event } from '@dxos/async';
6
6
  import { Stream } from '@dxos/codec-protobuf';
7
7
  import { Config } from '@dxos/config';
8
8
  import {
9
+ GetDiagnosticsRequest,
9
10
  SystemService,
10
11
  SystemStatus,
11
12
  UpdateStatusRequest,
12
13
  QueryStatusRequest,
13
14
  QueryStatusResponse,
14
15
  } from '@dxos/protocols/proto/dxos/client/services';
15
- import { MaybePromise } from '@dxos/util';
16
+ import { jsonKeyReplacer, MaybePromise } from '@dxos/util';
17
+
18
+ import { Diagnostics } from '../services';
16
19
 
17
20
  export type SystemServiceOptions = {
18
21
  config?: Config;
19
22
  statusUpdate: Event<void>;
20
23
  getCurrentStatus: () => SystemStatus;
24
+ getDiagnostics: () => Promise<Partial<Diagnostics>>;
21
25
  onUpdateStatus: (status: SystemStatus) => MaybePromise<void>;
22
26
  onReset: () => MaybePromise<void>;
23
27
  };
@@ -28,11 +32,20 @@ export class SystemServiceImpl implements SystemService {
28
32
  private readonly _getCurrentStatus: SystemServiceOptions['getCurrentStatus'];
29
33
  private readonly _onUpdateStatus: SystemServiceOptions['onUpdateStatus'];
30
34
  private readonly _onReset: SystemServiceOptions['onReset'];
35
+ private readonly _getDiagnostics: SystemServiceOptions['getDiagnostics'];
31
36
 
32
- constructor({ config, statusUpdate, onUpdateStatus, getCurrentStatus, onReset }: SystemServiceOptions) {
37
+ constructor({
38
+ config,
39
+ statusUpdate,
40
+ getDiagnostics,
41
+ onUpdateStatus,
42
+ getCurrentStatus,
43
+ onReset,
44
+ }: SystemServiceOptions) {
33
45
  this._config = config;
34
46
  this._statusUpdate = statusUpdate;
35
47
  this._getCurrentStatus = getCurrentStatus;
48
+ this._getDiagnostics = getDiagnostics;
36
49
  this._onUpdateStatus = onUpdateStatus;
37
50
  this._onReset = onReset;
38
51
  }
@@ -41,6 +54,25 @@ export class SystemServiceImpl implements SystemService {
41
54
  return this._config?.values ?? {};
42
55
  }
43
56
 
57
+ /**
58
+ * NOTE: Since this is serialized as a JSON object, we allow the option to serialize keys.
59
+ */
60
+ async getDiagnostics({ keys }: GetDiagnosticsRequest = {}) {
61
+ const diagnostics = await this._getDiagnostics();
62
+ return {
63
+ timestamp: new Date(),
64
+ diagnostics: JSON.parse(
65
+ JSON.stringify(
66
+ diagnostics,
67
+ jsonKeyReplacer({
68
+ truncate: keys === GetDiagnosticsRequest.KEY_OPTION.TRUNCATE,
69
+ humanize: keys === GetDiagnosticsRequest.KEY_OPTION.HUMANIZE,
70
+ }),
71
+ ),
72
+ ),
73
+ };
74
+ }
75
+
44
76
  async updateStatus({ status }: UpdateStatusRequest) {
45
77
  await this._onUpdateStatus(status);
46
78
  }