@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.
- package/dist/lib/browser/{chunk-CSJSC47N.mjs → chunk-T32PP5QD.mjs} +1284 -625
- package/dist/lib/browser/chunk-T32PP5QD.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +70 -54
- package/dist/lib/browser/index.mjs.map +3 -3
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/packlets/testing/index.mjs +42 -48
- package/dist/lib/browser/packlets/testing/index.mjs.map +3 -3
- package/dist/lib/node/index.cjs +1439 -767
- package/dist/lib/node/index.cjs.map +4 -4
- package/dist/lib/node/meta.json +1 -1
- package/dist/lib/node/packlets/testing/index.cjs +1590 -936
- package/dist/lib/node/packlets/testing/index.cjs.map +4 -4
- package/dist/types/src/packlets/identity/identity-manager.d.ts +7 -1
- package/dist/types/src/packlets/identity/identity-manager.d.ts.map +1 -1
- package/dist/types/src/packlets/identity/identity-service.d.ts +7 -3
- package/dist/types/src/packlets/identity/identity-service.d.ts.map +1 -1
- package/dist/types/src/packlets/identity/identity.d.ts +3 -2
- package/dist/types/src/packlets/identity/identity.d.ts.map +1 -1
- package/dist/types/src/packlets/invitations/device-invitation-protocol.d.ts.map +1 -1
- package/dist/types/src/packlets/invitations/invitation-extension.d.ts.map +1 -1
- package/dist/types/src/packlets/invitations/invitations-handler.d.ts.map +1 -1
- package/dist/types/src/packlets/invitations/invitations-service.d.ts.map +1 -1
- package/dist/types/src/packlets/invitations/space-invitation-protocol.d.ts.map +1 -1
- package/dist/types/src/packlets/logging/logging-service.d.ts +6 -2
- package/dist/types/src/packlets/logging/logging-service.d.ts.map +1 -1
- package/dist/types/src/packlets/services/client-rpc-server.d.ts.map +1 -1
- package/dist/types/src/packlets/services/diagnostics.d.ts +46 -0
- package/dist/types/src/packlets/services/diagnostics.d.ts.map +1 -0
- package/dist/types/src/packlets/services/index.d.ts +1 -0
- package/dist/types/src/packlets/services/index.d.ts.map +1 -1
- package/dist/types/src/packlets/services/platform.d.ts +16 -0
- package/dist/types/src/packlets/services/platform.d.ts.map +1 -0
- package/dist/types/src/packlets/services/service-context.d.ts +2 -1
- package/dist/types/src/packlets/services/service-context.d.ts.map +1 -1
- package/dist/types/src/packlets/services/service-host.d.ts +7 -3
- package/dist/types/src/packlets/services/service-host.d.ts.map +1 -1
- package/dist/types/src/packlets/services/util.d.ts +6 -0
- package/dist/types/src/packlets/services/util.d.ts.map +1 -0
- package/dist/types/src/packlets/spaces/data-space-manager.d.ts.map +1 -1
- package/dist/types/src/packlets/spaces/data-space.d.ts.map +1 -1
- package/dist/types/src/packlets/spaces/notarization-plugin.d.ts.map +1 -1
- package/dist/types/src/packlets/spaces/spaces-service.d.ts.map +1 -1
- package/dist/types/src/packlets/system/system-service.d.ts +12 -2
- package/dist/types/src/packlets/system/system-service.d.ts.map +1 -1
- package/dist/types/src/packlets/testing/invitation-utils.d.ts.map +1 -1
- package/dist/types/src/packlets/vault/iframe-host-runtime.d.ts.map +1 -1
- package/dist/types/src/packlets/vault/shell-runtime.d.ts.map +1 -1
- package/dist/types/src/packlets/vault/worker-runtime.d.ts.map +1 -1
- package/dist/types/src/packlets/vault/worker-session.d.ts.map +1 -1
- package/dist/types/src/version.d.ts +2 -0
- package/dist/types/src/version.d.ts.map +1 -0
- package/package.json +34 -33
- package/src/packlets/devices/devices-service.test.ts +2 -1
- package/src/packlets/identity/authenticator.test.ts +2 -2
- package/src/packlets/identity/identity-manager.test.ts +15 -3
- package/src/packlets/identity/identity-manager.ts +28 -8
- package/src/packlets/identity/identity-service.test.ts +17 -2
- package/src/packlets/identity/identity-service.ts +24 -16
- package/src/packlets/identity/identity.test.ts +7 -6
- package/src/packlets/identity/identity.ts +9 -5
- package/src/packlets/invitations/device-invitation-protocol.test.ts +2 -1
- package/src/packlets/invitations/device-invitation-protocol.ts +1 -2
- package/src/packlets/invitations/invitation-extension.ts +1 -2
- package/src/packlets/invitations/invitations-handler.ts +3 -2
- package/src/packlets/invitations/invitations-service.ts +1 -2
- package/src/packlets/invitations/space-invitation-protocol.ts +1 -2
- package/src/packlets/locks/node.ts +1 -1
- package/src/packlets/logging/logging-service.ts +17 -9
- package/src/packlets/network/network-service.test.ts +2 -1
- package/src/packlets/services/client-rpc-server.ts +4 -4
- package/src/packlets/services/diagnostics.ts +212 -0
- package/src/packlets/services/index.ts +1 -0
- package/src/packlets/services/platform.ts +48 -0
- package/src/packlets/services/service-context.test.ts +59 -0
- package/src/packlets/services/service-context.ts +21 -15
- package/src/packlets/services/service-host.test.ts +12 -11
- package/src/packlets/services/service-host.ts +39 -20
- package/src/packlets/services/service-registry.test.ts +3 -1
- package/src/packlets/services/util.ts +33 -0
- package/src/packlets/spaces/data-space-manager.test.ts +80 -22
- package/src/packlets/spaces/data-space-manager.ts +7 -4
- package/src/packlets/spaces/data-space.ts +17 -5
- package/src/packlets/spaces/notarization-plugin.ts +1 -2
- package/src/packlets/spaces/spaces-service.test.ts +2 -1
- package/src/packlets/spaces/spaces-service.ts +19 -13
- package/src/packlets/system/system-service.test.ts +1 -0
- package/src/packlets/system/system-service.ts +34 -2
- package/src/packlets/testing/invitation-utils.ts +1 -2
- package/src/packlets/testing/test-builder.ts +1 -1
- package/src/packlets/vault/iframe-host-runtime.ts +2 -1
- package/src/packlets/vault/shell-runtime.ts +1 -2
- package/src/packlets/vault/worker-runtime.ts +3 -2
- package/src/packlets/vault/worker-session.ts +7 -3
- package/src/version.ts +1 -0
- 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
|
-
|
|
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.
|
|
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(
|
|
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.
|
|
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
|
|
160
|
-
|
|
161
|
-
|
|
161
|
+
test
|
|
162
|
+
.skip('Epoch truncates feeds', async () => {
|
|
163
|
+
const builder = new TestBuilder();
|
|
164
|
+
afterTest(async () => builder.destroy());
|
|
162
165
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
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
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
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
|
-
|
|
179
|
+
expect((await file.stat()).size === 0).to.be.true;
|
|
177
180
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
+
for (const _ in range(10)) {
|
|
182
|
+
await testLocalDatabase(space.dataPipeline);
|
|
183
|
+
}
|
|
181
184
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
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:
|
|
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 &&
|
|
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
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
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
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
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
|
-
|
|
196
|
-
|
|
197
|
-
|
|
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,
|
|
@@ -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({
|
|
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
|
);
|