@dxos/client-services 0.1.56-main.e47dfd1 → 0.1.56-main.e79d64a

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 (95) hide show
  1. package/dist/lib/browser/{chunk-CSJSC47N.mjs → chunk-T32PP5QD.mjs} +1284 -625
  2. package/dist/lib/browser/chunk-T32PP5QD.mjs.map +7 -0
  3. package/dist/lib/browser/index.mjs +70 -54
  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 +42 -48
  7. package/dist/lib/browser/packlets/testing/index.mjs.map +3 -3
  8. package/dist/lib/node/index.cjs +1439 -767
  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 +1590 -936
  12. package/dist/lib/node/packlets/testing/index.cjs.map +4 -4
  13. package/dist/types/src/packlets/identity/identity-manager.d.ts +7 -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 +7 -3
  16. package/dist/types/src/packlets/identity/identity-service.d.ts.map +1 -1
  17. package/dist/types/src/packlets/identity/identity.d.ts +3 -2
  18. package/dist/types/src/packlets/identity/identity.d.ts.map +1 -1
  19. package/dist/types/src/packlets/invitations/device-invitation-protocol.d.ts.map +1 -1
  20. package/dist/types/src/packlets/invitations/invitation-extension.d.ts.map +1 -1
  21. package/dist/types/src/packlets/invitations/invitations-handler.d.ts.map +1 -1
  22. package/dist/types/src/packlets/invitations/invitations-service.d.ts.map +1 -1
  23. package/dist/types/src/packlets/invitations/space-invitation-protocol.d.ts.map +1 -1
  24. package/dist/types/src/packlets/logging/logging-service.d.ts +6 -2
  25. package/dist/types/src/packlets/logging/logging-service.d.ts.map +1 -1
  26. package/dist/types/src/packlets/services/client-rpc-server.d.ts.map +1 -1
  27. package/dist/types/src/packlets/services/diagnostics.d.ts +46 -0
  28. package/dist/types/src/packlets/services/diagnostics.d.ts.map +1 -0
  29. package/dist/types/src/packlets/services/index.d.ts +1 -0
  30. package/dist/types/src/packlets/services/index.d.ts.map +1 -1
  31. package/dist/types/src/packlets/services/platform.d.ts +16 -0
  32. package/dist/types/src/packlets/services/platform.d.ts.map +1 -0
  33. package/dist/types/src/packlets/services/service-context.d.ts +2 -1
  34. package/dist/types/src/packlets/services/service-context.d.ts.map +1 -1
  35. package/dist/types/src/packlets/services/service-host.d.ts +7 -3
  36. package/dist/types/src/packlets/services/service-host.d.ts.map +1 -1
  37. package/dist/types/src/packlets/services/util.d.ts +6 -0
  38. package/dist/types/src/packlets/services/util.d.ts.map +1 -0
  39. package/dist/types/src/packlets/spaces/data-space-manager.d.ts.map +1 -1
  40. package/dist/types/src/packlets/spaces/data-space.d.ts.map +1 -1
  41. package/dist/types/src/packlets/spaces/notarization-plugin.d.ts.map +1 -1
  42. package/dist/types/src/packlets/spaces/spaces-service.d.ts.map +1 -1
  43. package/dist/types/src/packlets/system/system-service.d.ts +12 -2
  44. package/dist/types/src/packlets/system/system-service.d.ts.map +1 -1
  45. package/dist/types/src/packlets/testing/invitation-utils.d.ts.map +1 -1
  46. package/dist/types/src/packlets/vault/iframe-host-runtime.d.ts.map +1 -1
  47. package/dist/types/src/packlets/vault/shell-runtime.d.ts.map +1 -1
  48. package/dist/types/src/packlets/vault/worker-runtime.d.ts.map +1 -1
  49. package/dist/types/src/packlets/vault/worker-session.d.ts.map +1 -1
  50. package/dist/types/src/version.d.ts +2 -0
  51. package/dist/types/src/version.d.ts.map +1 -0
  52. package/package.json +34 -33
  53. package/src/packlets/devices/devices-service.test.ts +2 -1
  54. package/src/packlets/identity/authenticator.test.ts +2 -2
  55. package/src/packlets/identity/identity-manager.test.ts +15 -3
  56. package/src/packlets/identity/identity-manager.ts +28 -8
  57. package/src/packlets/identity/identity-service.test.ts +17 -2
  58. package/src/packlets/identity/identity-service.ts +24 -16
  59. package/src/packlets/identity/identity.test.ts +7 -6
  60. package/src/packlets/identity/identity.ts +9 -5
  61. package/src/packlets/invitations/device-invitation-protocol.test.ts +2 -1
  62. package/src/packlets/invitations/device-invitation-protocol.ts +1 -2
  63. package/src/packlets/invitations/invitation-extension.ts +1 -2
  64. package/src/packlets/invitations/invitations-handler.ts +3 -2
  65. package/src/packlets/invitations/invitations-service.ts +1 -2
  66. package/src/packlets/invitations/space-invitation-protocol.ts +1 -2
  67. package/src/packlets/locks/node.ts +1 -1
  68. package/src/packlets/logging/logging-service.ts +17 -9
  69. package/src/packlets/network/network-service.test.ts +2 -1
  70. package/src/packlets/services/client-rpc-server.ts +4 -4
  71. package/src/packlets/services/diagnostics.ts +212 -0
  72. package/src/packlets/services/index.ts +1 -0
  73. package/src/packlets/services/platform.ts +48 -0
  74. package/src/packlets/services/service-context.test.ts +59 -0
  75. package/src/packlets/services/service-context.ts +21 -15
  76. package/src/packlets/services/service-host.test.ts +12 -11
  77. package/src/packlets/services/service-host.ts +39 -20
  78. package/src/packlets/services/service-registry.test.ts +3 -1
  79. package/src/packlets/services/util.ts +33 -0
  80. package/src/packlets/spaces/data-space-manager.test.ts +80 -22
  81. package/src/packlets/spaces/data-space-manager.ts +7 -4
  82. package/src/packlets/spaces/data-space.ts +17 -5
  83. package/src/packlets/spaces/notarization-plugin.ts +1 -2
  84. package/src/packlets/spaces/spaces-service.test.ts +2 -1
  85. package/src/packlets/spaces/spaces-service.ts +19 -13
  86. package/src/packlets/system/system-service.test.ts +1 -0
  87. package/src/packlets/system/system-service.ts +34 -2
  88. package/src/packlets/testing/invitation-utils.ts +1 -2
  89. package/src/packlets/testing/test-builder.ts +1 -1
  90. package/src/packlets/vault/iframe-host-runtime.ts +2 -1
  91. package/src/packlets/vault/shell-runtime.ts +1 -2
  92. package/src/packlets/vault/worker-runtime.ts +3 -2
  93. package/src/packlets/vault/worker-session.ts +7 -3
  94. package/src/version.ts +1 -0
  95. package/dist/lib/browser/chunk-CSJSC47N.mjs.map +0 -7
@@ -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('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('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');
@@ -213,8 +229,6 @@ export class ClientServicesHost {
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,11 +236,14 @@ 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
 
229
- IdentityService: new IdentityServiceImpl(this._serviceContext),
242
+ IdentityService: new IdentityServiceImpl(
243
+ (params) => this._serviceContext.createIdentity(params),
244
+ this._serviceContext.identityManager,
245
+ this._serviceContext.keyring,
246
+ ),
230
247
 
231
248
  InvitationsService: new InvitationsServiceImpl(this._serviceContext.invitations, (invitation) =>
232
249
  this._serviceContext.getInvitationHandler(invitation),
@@ -249,6 +266,7 @@ export class ClientServicesHost {
249
266
  NetworkService: new NetworkServiceImpl(this._serviceContext.networkManager, this._serviceContext.signalManager),
250
267
 
251
268
  LoggingService: this._loggingService,
269
+ TracingService: this._tracingService,
252
270
 
253
271
  // TODO(burdon): Move to new protobuf definitions.
254
272
  DevtoolsHost: new DevtoolsServiceImpl({
@@ -258,16 +276,17 @@ export class ClientServicesHost {
258
276
  }),
259
277
  });
260
278
 
261
- await this._serviceContext.open();
279
+ await this._serviceContext.open(ctx);
262
280
  this._opening = false;
263
281
  this._open = true;
264
282
  this._statusUpdate.emit();
265
283
  const deviceKey = this._serviceContext.identityManager.identity?.deviceKey;
266
284
  log('opened', { deviceKey });
267
- log.trace('dxos.sdk.client-services-host.open', trace.end({ id: traceId }));
285
+ log.trace('dxos.client-services.host.open', trace.end({ id: traceId }));
268
286
  }
269
287
 
270
288
  @synchronized
289
+ @Trace.span()
271
290
  async close() {
272
291
  if (!this._open) {
273
292
  return;
@@ -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
+ };
@@ -13,9 +13,11 @@ import { writeMessages } from '@dxos/feed-store';
13
13
  import { PublicKey } from '@dxos/keys';
14
14
  import { log } from '@dxos/log';
15
15
  import { SpaceState } from '@dxos/protocols/proto/dxos/client/services';
16
+ import { SpaceSnapshot } from '@dxos/protocols/proto/dxos/echo/snapshot';
16
17
  import { Epoch } from '@dxos/protocols/proto/dxos/halo/credentials';
17
18
  import { StorageType } from '@dxos/random-access-storage';
18
19
  import { afterTest, describe, openAndClose, test } from '@dxos/test';
20
+ import { Timeframe } from '@dxos/timeframe';
19
21
  import { range } from '@dxos/util';
20
22
 
21
23
  import { TestBuilder, syncItemsLocal } from '../testing';
@@ -156,33 +158,34 @@ describe('DataSpaceManager', () => {
156
158
  });
157
159
 
158
160
  describe('Epochs', () => {
159
- test('Epoch truncates feeds', async () => {
160
- const builder = new TestBuilder();
161
- afterTest(async () => builder.destroy());
161
+ test
162
+ .skip('Epoch truncates feeds', async () => {
163
+ const builder = new TestBuilder();
164
+ afterTest(async () => builder.destroy());
162
165
 
163
- const peer = builder.createPeer({
164
- storageType: typeof window === 'undefined' ? StorageType.NODE : StorageType.WEBFS,
165
- });
166
- await peer.createIdentity();
167
- await openAndClose(peer.dataSpaceManager);
168
- const space = await peer.dataSpaceManager.createSpace();
169
- await space.inner.controlPipeline.state.waitUntilTimeframe(space.inner.controlPipeline.state.endTimeframe);
166
+ const peer = builder.createPeer({
167
+ storageType: typeof window === 'undefined' ? StorageType.NODE : StorageType.WEBFS,
168
+ });
169
+ await peer.createIdentity();
170
+ await openAndClose(peer.dataSpaceManager);
171
+ const space = await peer.dataSpaceManager.createSpace();
172
+ await space.inner.controlPipeline.state.waitUntilTimeframe(space.inner.controlPipeline.state.endTimeframe);
170
173
 
171
- const feedDataPath = path.join(space.inner.dataPipeline.pipelineState!.feeds[0].key.toHex(), 'data');
172
- const directory = peer.storage.createDirectory('feeds');
173
- const file = directory.getOrCreateFile(feedDataPath);
174
- afterTest(() => file.close());
174
+ const feedDataPath = path.join(space.inner.dataPipeline.pipelineState!.feeds[0].key.toHex(), 'data');
175
+ const directory = peer.storage.createDirectory('feeds');
176
+ const file = directory.getOrCreateFile(feedDataPath);
177
+ afterTest(() => file.close());
175
178
 
176
- expect((await file.stat()).size === 0).to.be.true;
179
+ expect((await file.stat()).size === 0).to.be.true;
177
180
 
178
- for (const _ in range(10)) {
179
- await testLocalDatabase(space.dataPipeline);
180
- }
181
+ for (const _ in range(10)) {
182
+ await testLocalDatabase(space.dataPipeline);
183
+ }
181
184
 
182
- expect((await file.stat()).size !== 0).to.be.true;
183
- await space.createEpoch();
184
- expect((await file.stat()).size === 0).to.be.true;
185
- })
185
+ expect((await file.stat()).size !== 0).to.be.true;
186
+ await space.createEpoch();
187
+ expect((await file.stat()).size === 0).to.be.true;
188
+ })
186
189
  .onlyEnvironments('nodejs', 'chromium', 'firefox')
187
190
  .tag('flaky');
188
191
 
@@ -228,6 +231,61 @@ describe('DataSpaceManager', () => {
228
231
  expect(epochs).to.deep.equal([epochsNumber]);
229
232
  }
230
233
  });
234
+
235
+ test('Items are cleared before epoch applied', async () => {
236
+ const builder = new TestBuilder();
237
+ afterTest(async () => builder.destroy());
238
+ const peer = builder.createPeer();
239
+ await peer.createIdentity();
240
+ await openAndClose(peer.dataSpaceManager);
241
+
242
+ // Create space and fill it with Items.
243
+ const space = await peer.dataSpaceManager.createSpace();
244
+ await space.inner.controlPipeline.state.waitUntilTimeframe(space.inner.controlPipeline.state.endTimeframe);
245
+ const itemsNumber = 2;
246
+ for (const _ of range(itemsNumber)) {
247
+ await testLocalDatabase(space.dataPipeline);
248
+ }
249
+
250
+ // Create empty Epoch and check if it clears items.
251
+ {
252
+ const processedFirstEpoch = space.dataPipeline.onNewEpoch.waitFor(
253
+ (epoch) => epoch.subject.assertion.number === 1,
254
+ );
255
+
256
+ // Empty snapshot.
257
+ const snapshot: SpaceSnapshot = {
258
+ spaceKey: space.key.asUint8Array(),
259
+ timeframe: space.inner.dataPipeline.pipelineState!.timeframe,
260
+ database: {},
261
+ };
262
+
263
+ const snapshotCid = await peer.snapshotStore.saveSnapshot(snapshot);
264
+
265
+ const epoch: Epoch = {
266
+ previousId: space.dataPipeline.currentEpoch?.id,
267
+ timeframe: space.inner.dataPipeline.pipelineState!.timeframe,
268
+ number: (space.dataPipeline.currentEpoch?.subject.assertion as Epoch).number + 1,
269
+ snapshotCid,
270
+ };
271
+
272
+ const receipt = await space.inner.controlPipeline.writer.write({
273
+ credential: {
274
+ credential: await peer.props.signingContext!.credentialSigner.createCredential({
275
+ subject: space.key,
276
+ assertion: {
277
+ '@type': 'dxos.halo.credentials.Epoch',
278
+ ...epoch,
279
+ },
280
+ }),
281
+ },
282
+ });
283
+ await space.inner.controlPipeline.state.waitUntilTimeframe(new Timeframe([[receipt.feedKey, receipt.seq]]));
284
+ await processedFirstEpoch;
285
+ }
286
+
287
+ expect(Array.from(space.dataPipeline.itemManager.entities.keys()).length).to.equal(0);
288
+ });
231
289
  });
232
290
 
233
291
  describe('activation', () => {
@@ -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,
@@ -194,7 +193,11 @@ export class DataSpaceManager {
194
193
  const controlFeed =
195
194
  metadata.controlFeedKey && (await this._feedStore.openFeed(metadata.controlFeedKey, { writable: true }));
196
195
  const dataFeed =
197
- metadata.dataFeedKey && (await this._feedStore.openFeed(metadata.dataFeedKey, { writable: true, sparse: true }));
196
+ metadata.dataFeedKey &&
197
+ (await this._feedStore.openFeed(metadata.dataFeedKey, {
198
+ writable: true,
199
+ sparse: true,
200
+ }));
198
201
 
199
202
  const space: Space = await this._spaceManager.constructSpace({
200
203
  metadata,
@@ -55,6 +55,12 @@ export type DataSpaceParams = {
55
55
  cache?: SpaceCache;
56
56
  };
57
57
 
58
+ /**
59
+ * Delete feed blocks after an epoch is created.
60
+ */
61
+ // TODO(dmaretskyi): Disabled till better times https://github.com/dxos/dxos/issues/3949.
62
+ const ENABLE_FEED_PURGE = false;
63
+
58
64
  @trackLeaks('open', 'close')
59
65
  export class DataSpace {
60
66
  private _ctx = new Context();
@@ -145,7 +151,7 @@ export class DataSpace {
145
151
  private async _open() {
146
152
  await this._notarizationPlugin.open();
147
153
  await this._inner.spaceState.addCredentialProcessor(this._notarizationPlugin);
148
- await this._inner.open();
154
+ await this._inner.open(new Context());
149
155
  this._state = SpaceState.CONTROL_ONLY;
150
156
  this.stateUpdate.emit();
151
157
  this.metrics = {};
@@ -323,10 +329,16 @@ export class DataSpace {
323
329
  });
324
330
 
325
331
  await this.inner.controlPipeline.state.waitUntilTimeframe(new Timeframe([[receipt.feedKey, receipt.seq]]));
326
- for (const feed of this.inner.dataPipeline.pipelineState?.feeds ?? []) {
327
- const indexBeforeEpoch = epoch.timeframe.get(feed.key);
328
- if (indexBeforeEpoch) {
329
- await feed.clear(0, indexBeforeEpoch + 1);
332
+
333
+ // Clear feed blocks before epoch.
334
+ if (ENABLE_FEED_PURGE) {
335
+ for (const feed of this.inner.dataPipeline.pipelineState?.feeds ?? []) {
336
+ const indexBeforeEpoch = epoch.timeframe.get(feed.key);
337
+ if (indexBeforeEpoch === undefined) {
338
+ continue;
339
+ }
340
+
341
+ await feed.safeClear(0, indexBeforeEpoch + 1);
330
342
  }
331
343
  }
332
344
  }
@@ -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,
@@ -78,7 +78,7 @@ export class SpacesServiceImpl implements SpacesService {
78
78
  log('update', { spaces });
79
79
  next({ spaces });
80
80
  },
81
- { maxFrequency: 2 },
81
+ { maxFrequency: process.env.NODE_ENV === 'test' ? undefined : 2 },
82
82
  );
83
83
 
84
84
  scheduleTask(ctx, async () => {
@@ -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
  }
@@ -2,10 +2,9 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- import invariant from 'tiny-invariant';
6
-
7
5
  import { Trigger } from '@dxos/async';
8
6
  import { AuthenticatingInvitationObservable, CancellableInvitationObservable } from '@dxos/client-protocol';
7
+ import { invariant } from '@dxos/invariant';
9
8
  import { Invitation } from '@dxos/protocols/proto/dxos/client/services';
10
9
 
11
10
  import { ServiceContext } from '../services';
@@ -60,7 +60,7 @@ export const createPeers = async (numPeers: number) => {
60
60
  return await Promise.all(
61
61
  Array.from(Array(numPeers)).map(async () => {
62
62
  const peer = createServiceContext({ signalContext });
63
- await peer.open();
63
+ await peer.open(new Context());
64
64
  return peer;
65
65
  }),
66
66
  );