@dxos/client-services 0.1.56-main.bf228a7 → 0.1.56-main.c19b7cd
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-FD4L7N4K.mjs → chunk-ZOTHWLK5.mjs} +623 -335
- package/dist/lib/browser/chunk-ZOTHWLK5.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +23 -19
- 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 +2 -2
- package/dist/lib/browser/packlets/testing/index.mjs.map +3 -3
- package/dist/lib/node/index.cjs +752 -461
- 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 +1016 -729
- package/dist/lib/node/packlets/testing/index.cjs.map +4 -4
- package/dist/types/src/packlets/identity/identity-manager.d.ts +2 -1
- package/dist/types/src/packlets/identity/identity-manager.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/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.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/vault/iframe-host-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/identity-manager.test.ts +4 -3
- package/src/packlets/identity/identity-manager.ts +9 -5
- package/src/packlets/identity/identity-service.test.ts +2 -1
- package/src/packlets/identity/identity.test.ts +7 -6
- package/src/packlets/identity/identity.ts +8 -3
- package/src/packlets/invitations/device-invitation-protocol.test.ts +2 -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 +211 -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 +13 -9
- package/src/packlets/services/service-host.test.ts +11 -10
- package/src/packlets/services/service-host.ts +37 -21
- package/src/packlets/services/service-registry.test.ts +3 -1
- package/src/packlets/services/util.ts +33 -0
- package/src/packlets/spaces/data-space.ts +17 -5
- package/src/packlets/spaces/spaces-service.test.ts +2 -1
- package/src/packlets/system/system-service.test.ts +1 -0
- package/src/packlets/system/system-service.ts +34 -2
- package/src/packlets/testing/test-builder.ts +1 -1
- package/src/packlets/vault/iframe-host-runtime.ts +2 -1
- package/src/packlets/vault/worker-runtime.ts +2 -1
- package/src/packlets/vault/worker-session.ts +6 -1
- package/src/version.ts +1 -0
- package/dist/lib/browser/chunk-FD4L7N4K.mjs.map +0 -7
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import expect from 'expect';
|
|
6
6
|
|
|
7
|
+
import { Context } from '@dxos/context';
|
|
7
8
|
import { CredentialGenerator, verifyCredential } from '@dxos/credentials';
|
|
8
9
|
import {
|
|
9
10
|
MetadataStore,
|
|
@@ -106,8 +107,8 @@ describe('identity/identity', () => {
|
|
|
106
107
|
space,
|
|
107
108
|
});
|
|
108
109
|
|
|
109
|
-
await identity.open();
|
|
110
|
-
afterTest(() => identity.close());
|
|
110
|
+
await identity.open(new Context());
|
|
111
|
+
afterTest(() => identity.close(new Context()));
|
|
111
112
|
|
|
112
113
|
//
|
|
113
114
|
// Identity genesis
|
|
@@ -219,8 +220,8 @@ describe('identity/identity', () => {
|
|
|
219
220
|
space,
|
|
220
221
|
}));
|
|
221
222
|
|
|
222
|
-
await identity.open();
|
|
223
|
-
afterTest(() => identity.close());
|
|
223
|
+
await identity.open(new Context());
|
|
224
|
+
afterTest(() => identity.close(new Context()));
|
|
224
225
|
|
|
225
226
|
//
|
|
226
227
|
// Identity genesis
|
|
@@ -310,8 +311,8 @@ describe('identity/identity', () => {
|
|
|
310
311
|
space,
|
|
311
312
|
}));
|
|
312
313
|
|
|
313
|
-
await identity.open();
|
|
314
|
-
afterTest(() => identity.close());
|
|
314
|
+
await identity.open(new Context());
|
|
315
|
+
afterTest(() => identity.close(new Context()));
|
|
315
316
|
}
|
|
316
317
|
|
|
317
318
|
//
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import { Event } from '@dxos/async';
|
|
6
6
|
import { AUTH_TIMEOUT, LOAD_CONTROL_FEEDS_TIMEOUT } from '@dxos/client-protocol';
|
|
7
|
+
import { Context } from '@dxos/context';
|
|
7
8
|
import {
|
|
8
9
|
DeviceStateMachine,
|
|
9
10
|
CredentialSigner,
|
|
@@ -21,6 +22,7 @@ import { log } from '@dxos/log';
|
|
|
21
22
|
import { FeedMessage } from '@dxos/protocols/proto/dxos/echo/feed';
|
|
22
23
|
import { AdmittedFeed, ProfileDocument } from '@dxos/protocols/proto/dxos/halo/credentials';
|
|
23
24
|
import { DeviceAdmissionRequest } from '@dxos/protocols/proto/dxos/halo/invitations';
|
|
25
|
+
import { trace } from '@dxos/tracing';
|
|
24
26
|
import { ComplexSet } from '@dxos/util';
|
|
25
27
|
|
|
26
28
|
import { TrustedKeySetAuthVerifier } from './authenticator';
|
|
@@ -35,6 +37,7 @@ export type IdentityParams = {
|
|
|
35
37
|
/**
|
|
36
38
|
* Agent identity manager, which includes the agent's Halo space.
|
|
37
39
|
*/
|
|
40
|
+
@trace.resource()
|
|
38
41
|
export class Identity {
|
|
39
42
|
public readonly space: Space;
|
|
40
43
|
private readonly _signer: Signer;
|
|
@@ -76,13 +79,15 @@ export class Identity {
|
|
|
76
79
|
return this._deviceStateMachine.authorizedDeviceKeys;
|
|
77
80
|
}
|
|
78
81
|
|
|
79
|
-
|
|
82
|
+
@trace.span()
|
|
83
|
+
async open(ctx: Context) {
|
|
80
84
|
await this.space.spaceState.addCredentialProcessor(this._deviceStateMachine);
|
|
81
85
|
await this.space.spaceState.addCredentialProcessor(this._profileStateMachine);
|
|
82
|
-
await this.space.open();
|
|
86
|
+
await this.space.open(ctx);
|
|
83
87
|
}
|
|
84
88
|
|
|
85
|
-
|
|
89
|
+
@trace.span()
|
|
90
|
+
async close(ctx: Context) {
|
|
86
91
|
await this.authVerifier.close();
|
|
87
92
|
await this.space.spaceState.removeCredentialProcessor(this._profileStateMachine);
|
|
88
93
|
await this.space.spaceState.removeCredentialProcessor(this._deviceStateMachine);
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import { expect } from 'chai';
|
|
6
6
|
|
|
7
7
|
import { asyncChain } from '@dxos/async';
|
|
8
|
+
import { Context } from '@dxos/context';
|
|
8
9
|
import { Invitation } from '@dxos/protocols/proto/dxos/client/services';
|
|
9
10
|
import { describe, test, afterTest } from '@dxos/test';
|
|
10
11
|
|
|
@@ -19,7 +20,7 @@ const closeAfterTest = async (peer: ServiceContext) => {
|
|
|
19
20
|
describe('services/device', () => {
|
|
20
21
|
test('creates identity', async () => {
|
|
21
22
|
const peer = createServiceContext();
|
|
22
|
-
await peer.open();
|
|
23
|
+
await peer.open(new Context());
|
|
23
24
|
afterTest(() => peer.close());
|
|
24
25
|
|
|
25
26
|
const identity = await peer.createIdentity();
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
ControlMetricsRequest,
|
|
14
14
|
ControlMetricsResponse,
|
|
15
15
|
QueryMetricsRequest,
|
|
16
|
+
QueryMetricsResponse,
|
|
16
17
|
} from '@dxos/protocols/proto/dxos/client/services';
|
|
17
18
|
import { jsonify, numericalValues, tracer } from '@dxos/util';
|
|
18
19
|
|
|
@@ -21,6 +22,7 @@ import { jsonify, numericalValues, tracer } from '@dxos/util';
|
|
|
21
22
|
*/
|
|
22
23
|
export class LoggingServiceImpl implements LoggingService {
|
|
23
24
|
private readonly _logs = new Event<NaturalLogEntry>();
|
|
25
|
+
private readonly _started = new Date();
|
|
24
26
|
|
|
25
27
|
async open() {
|
|
26
28
|
log.runtimeConfig.processors.push(this._logProcessor);
|
|
@@ -45,11 +47,14 @@ export class LoggingServiceImpl implements LoggingService {
|
|
|
45
47
|
return { recording: tracer.recording };
|
|
46
48
|
}
|
|
47
49
|
|
|
48
|
-
|
|
50
|
+
/**
|
|
51
|
+
* @deprecated (Move to diagnostics).
|
|
52
|
+
*/
|
|
53
|
+
queryMetrics({ interval = 5_000 }: QueryMetricsRequest): Stream<QueryMetricsResponse> {
|
|
49
54
|
// TODO(burdon): Map all traces; how to bind to reducer/metrics shape (e.g., numericalValues)?
|
|
50
|
-
const
|
|
51
|
-
const
|
|
52
|
-
return { key, stats: numericalValues(
|
|
55
|
+
const getNumericalValues = (key: string) => {
|
|
56
|
+
const events = tracer.get(key) ?? [];
|
|
57
|
+
return { key, stats: numericalValues(events, 'duration') };
|
|
53
58
|
};
|
|
54
59
|
|
|
55
60
|
return new Stream(({ next }) => {
|
|
@@ -57,16 +62,19 @@ export class LoggingServiceImpl implements LoggingService {
|
|
|
57
62
|
const metrics: Metrics = {
|
|
58
63
|
timestamp: new Date(),
|
|
59
64
|
values: [
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
],
|
|
65
|
+
getNumericalValues('dxos.echo.pipeline.control'),
|
|
66
|
+
getNumericalValues('dxos.echo.pipeline.data'),
|
|
67
|
+
].filter(Boolean) as Metrics.KeyPair[],
|
|
63
68
|
};
|
|
64
69
|
|
|
65
|
-
next(
|
|
70
|
+
next({
|
|
71
|
+
timestamp: new Date(),
|
|
72
|
+
metrics,
|
|
73
|
+
});
|
|
66
74
|
};
|
|
67
75
|
|
|
68
76
|
update();
|
|
69
|
-
const i = setInterval(update, interval);
|
|
77
|
+
const i = setInterval(update, Math.max(interval, 1_000));
|
|
70
78
|
return () => {
|
|
71
79
|
clearInterval(i);
|
|
72
80
|
};
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import { expect } from 'chai';
|
|
6
6
|
|
|
7
7
|
import { Trigger } from '@dxos/async';
|
|
8
|
+
import { Context } from '@dxos/context';
|
|
8
9
|
import { NetworkService, ConnectionState } from '@dxos/protocols/proto/dxos/client/services';
|
|
9
10
|
import { afterEach, afterTest, beforeEach, describe, test } from '@dxos/test';
|
|
10
11
|
|
|
@@ -18,7 +19,7 @@ describe('NetworkService', () => {
|
|
|
18
19
|
|
|
19
20
|
beforeEach(async () => {
|
|
20
21
|
serviceContext = createServiceContext();
|
|
21
|
-
await serviceContext.open();
|
|
22
|
+
await serviceContext.open(new Context());
|
|
22
23
|
networkService = new NetworkServiceImpl(serviceContext.networkManager, serviceContext.signalManager);
|
|
23
24
|
});
|
|
24
25
|
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import { ClientServices } from '@dxos/client-protocol';
|
|
6
6
|
import { Any, ServiceHandler, Stream } from '@dxos/codec-protobuf';
|
|
7
7
|
import { raise } from '@dxos/debug';
|
|
8
|
-
import { parseMethodName, RpcPeer, RpcPeerOptions } from '@dxos/rpc';
|
|
8
|
+
import { parseMethodName, RpcPeer, RpcPeerOptions, ServiceBundle } from '@dxos/rpc';
|
|
9
9
|
import { MaybePromise } from '@dxos/util';
|
|
10
10
|
|
|
11
11
|
import { ServiceRegistry } from './service-registry';
|
|
@@ -74,16 +74,16 @@ export class ClientRpcServer {
|
|
|
74
74
|
private _getServiceHandler(serviceName: string) {
|
|
75
75
|
if (!this._handlerCache.has(serviceName)) {
|
|
76
76
|
const [key, descriptor] =
|
|
77
|
-
Object.entries(this._serviceRegistry.descriptors).find(
|
|
77
|
+
Object.entries(this._serviceRegistry.descriptors as ServiceBundle<Record<string, any>>).find(
|
|
78
78
|
([key, descriptor]) => descriptor.name === serviceName,
|
|
79
79
|
) ?? raise(new Error(`Service not available: ${serviceName}`));
|
|
80
80
|
|
|
81
|
-
const service = this._serviceRegistry.services[key as keyof ClientServices];
|
|
81
|
+
const service = this._serviceRegistry.services[key as keyof ClientServices] as any;
|
|
82
82
|
if (!service) {
|
|
83
83
|
throw new Error(`Service not available: ${serviceName}`);
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
-
this._handlerCache.set(serviceName, descriptor.createServer(service
|
|
86
|
+
this._handlerCache.set(serviceName, descriptor.createServer(service));
|
|
87
87
|
}
|
|
88
88
|
|
|
89
89
|
return this._handlerCache.get(serviceName)!;
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2022 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { ClientServices } from '@dxos/client-protocol';
|
|
6
|
+
import { getFirstStreamValue } from '@dxos/codec-protobuf';
|
|
7
|
+
import { Config, ConfigProto } from '@dxos/config';
|
|
8
|
+
import { credentialTypeFilter } from '@dxos/credentials';
|
|
9
|
+
import { DocumentModel, DocumentModelState } from '@dxos/document-model';
|
|
10
|
+
import { invariant } from '@dxos/invariant';
|
|
11
|
+
import { PublicKey } from '@dxos/keys';
|
|
12
|
+
import { log } from '@dxos/log';
|
|
13
|
+
import { STORAGE_VERSION } from '@dxos/protocols';
|
|
14
|
+
import {
|
|
15
|
+
Device,
|
|
16
|
+
Identity,
|
|
17
|
+
Metrics,
|
|
18
|
+
Space as SpaceProto,
|
|
19
|
+
SpaceMember,
|
|
20
|
+
} from '@dxos/protocols/proto/dxos/client/services';
|
|
21
|
+
import { SubscribeToFeedsResponse } from '@dxos/protocols/proto/dxos/devtools/host';
|
|
22
|
+
import { Epoch } from '@dxos/protocols/proto/dxos/halo/credentials';
|
|
23
|
+
|
|
24
|
+
import { DXOS_VERSION } from '../../version';
|
|
25
|
+
import { DataSpace } from '../spaces';
|
|
26
|
+
import { getPlatform, Platform } from './platform';
|
|
27
|
+
import { ServiceContext } from './service-context';
|
|
28
|
+
|
|
29
|
+
const DEFAULT_TIMEOUT = 1_000;
|
|
30
|
+
|
|
31
|
+
export type Diagnostics = {
|
|
32
|
+
created: string;
|
|
33
|
+
platform: Platform;
|
|
34
|
+
config?: ConfigProto;
|
|
35
|
+
client: {
|
|
36
|
+
version: string;
|
|
37
|
+
storage: {
|
|
38
|
+
version: number;
|
|
39
|
+
};
|
|
40
|
+
};
|
|
41
|
+
identity?: Identity;
|
|
42
|
+
devices?: Device[];
|
|
43
|
+
spaces?: SpaceStats[];
|
|
44
|
+
feeds?: Partial<SubscribeToFeedsResponse.Feed>[];
|
|
45
|
+
metrics?: Metrics;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
// TODO(burdon): Normalize for ECHO/HALO.
|
|
49
|
+
export type SpaceStats = {
|
|
50
|
+
key: PublicKey;
|
|
51
|
+
properties?: {
|
|
52
|
+
name: string;
|
|
53
|
+
};
|
|
54
|
+
db?: {
|
|
55
|
+
objects: number;
|
|
56
|
+
};
|
|
57
|
+
metrics?: SpaceProto.Metrics & {
|
|
58
|
+
startupTime?: number;
|
|
59
|
+
};
|
|
60
|
+
epochs?: (Epoch & { id?: PublicKey })[];
|
|
61
|
+
members?: SpaceMember[];
|
|
62
|
+
pipeline?: SpaceProto.PipelineState;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Create diagnostics to provide snapshot of current system state.
|
|
67
|
+
*/
|
|
68
|
+
export const createDiagnostics = async (
|
|
69
|
+
clientServices: Partial<ClientServices>,
|
|
70
|
+
serviceContext: ServiceContext,
|
|
71
|
+
config: Config,
|
|
72
|
+
): Promise<Diagnostics> => {
|
|
73
|
+
const diagnostics: Diagnostics = {
|
|
74
|
+
created: new Date().toISOString(),
|
|
75
|
+
platform: getPlatform(),
|
|
76
|
+
client: {
|
|
77
|
+
version: DXOS_VERSION,
|
|
78
|
+
storage: {
|
|
79
|
+
version: STORAGE_VERSION,
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
// Trace metrics.
|
|
85
|
+
// TODO(burdon): Move here from logging service?
|
|
86
|
+
{
|
|
87
|
+
invariant(clientServices.LoggingService, 'SystemService is not available.');
|
|
88
|
+
diagnostics.metrics = await getFirstStreamValue(clientServices.LoggingService.queryMetrics({}), {
|
|
89
|
+
timeout: DEFAULT_TIMEOUT,
|
|
90
|
+
}).catch(() => undefined);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const identity = serviceContext.identityManager.identity;
|
|
94
|
+
if (identity) {
|
|
95
|
+
// Identity.
|
|
96
|
+
diagnostics.identity = {
|
|
97
|
+
identityKey: identity.identityKey,
|
|
98
|
+
spaceKey: identity.space.key,
|
|
99
|
+
profile: identity.profileDocument,
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
// Devices.
|
|
103
|
+
const { devices } =
|
|
104
|
+
(await getFirstStreamValue(clientServices.DevicesService!.queryDevices(), {
|
|
105
|
+
timeout: DEFAULT_TIMEOUT,
|
|
106
|
+
}).catch(() => undefined)) ?? {};
|
|
107
|
+
diagnostics.devices = devices;
|
|
108
|
+
|
|
109
|
+
// TODO(dmaretskyi): Add metrics for halo space.
|
|
110
|
+
|
|
111
|
+
// Spaces.
|
|
112
|
+
if (serviceContext.dataSpaceManager) {
|
|
113
|
+
diagnostics.spaces = await Promise.all(
|
|
114
|
+
Array.from(serviceContext.dataSpaceManager.spaces.values()).map((space) => getSpaceStats(space)) ?? [],
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Feeds.
|
|
119
|
+
const { feeds = [] } =
|
|
120
|
+
(await getFirstStreamValue(clientServices.DevtoolsHost!.subscribeToFeeds({}), {
|
|
121
|
+
timeout: DEFAULT_TIMEOUT,
|
|
122
|
+
}).catch(() => undefined)) ?? {};
|
|
123
|
+
diagnostics.feeds = feeds.map(({ feedKey, bytes, length }) => ({ feedKey, bytes, length }));
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
diagnostics.config = config.values;
|
|
127
|
+
|
|
128
|
+
return diagnostics;
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
const getProperties = (space: DataSpace) => {
|
|
132
|
+
let properties: any = {};
|
|
133
|
+
try {
|
|
134
|
+
// Add properties to cache.
|
|
135
|
+
const propertiesItem = space.dataPipeline.itemManager.items.find(
|
|
136
|
+
(item) =>
|
|
137
|
+
item.modelMeta?.type === DocumentModel.meta.type &&
|
|
138
|
+
(item.state as DocumentModelState)?.type === 'dxos.sdk.client.Properties',
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
const state = propertiesItem?.state as DocumentModelState;
|
|
142
|
+
properties = state?.data;
|
|
143
|
+
} catch (err: any) {
|
|
144
|
+
log.warn(err.message);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return properties;
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
const getSpaceStats = async (space: DataSpace): Promise<SpaceStats> => {
|
|
151
|
+
const stats: SpaceStats = {
|
|
152
|
+
key: space.key,
|
|
153
|
+
metrics: space.metrics,
|
|
154
|
+
|
|
155
|
+
epochs: space.inner.spaceState.credentials
|
|
156
|
+
.filter(credentialTypeFilter('dxos.halo.credentials.Epoch'))
|
|
157
|
+
.map((credential) => ({
|
|
158
|
+
...credential.subject.assertion,
|
|
159
|
+
id: credential.id,
|
|
160
|
+
})),
|
|
161
|
+
|
|
162
|
+
members: Array.from(space.inner.spaceState.members.values()).map((member) => ({
|
|
163
|
+
identity: {
|
|
164
|
+
identityKey: member.key,
|
|
165
|
+
profile: {
|
|
166
|
+
displayName: member.assertion.profile?.displayName,
|
|
167
|
+
},
|
|
168
|
+
},
|
|
169
|
+
presence:
|
|
170
|
+
space.presence.getPeersOnline().filter(({ identityKey }) => identityKey.equals(member.key)).length > 0
|
|
171
|
+
? SpaceMember.PresenceState.ONLINE
|
|
172
|
+
: SpaceMember.PresenceState.OFFLINE,
|
|
173
|
+
})),
|
|
174
|
+
|
|
175
|
+
pipeline: {
|
|
176
|
+
// TODO(burdon): Pick properties from credentials if needed.
|
|
177
|
+
// currentEpoch: space.dataPipeline.currentEpoch,
|
|
178
|
+
// appliedEpoch: space.dataPipeline.appliedEpoch,
|
|
179
|
+
|
|
180
|
+
controlFeeds: space.inner.controlPipeline.state.feeds.map((feed) => feed.key),
|
|
181
|
+
currentControlTimeframe: space.inner.controlPipeline.state.timeframe,
|
|
182
|
+
targetControlTimeframe: space.inner.controlPipeline.state.targetTimeframe,
|
|
183
|
+
totalControlTimeframe: space.inner.controlPipeline.state.endTimeframe,
|
|
184
|
+
|
|
185
|
+
// TODO(burdon): Empty?
|
|
186
|
+
dataFeeds: space.dataPipeline.pipelineState?.feeds.map((feed) => feed.key) ?? [],
|
|
187
|
+
startDataTimeframe: space.dataPipeline.pipelineState?.startTimeframe,
|
|
188
|
+
currentDataTimeframe: space.dataPipeline.pipelineState?.timeframe,
|
|
189
|
+
targetDataTimeframe: space.dataPipeline.pipelineState?.targetTimeframe,
|
|
190
|
+
totalDataTimeframe: space.dataPipeline.pipelineState?.endTimeframe,
|
|
191
|
+
},
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
// TODO(burdon): May not be open?
|
|
195
|
+
if (space.dataPipeline.itemManager) {
|
|
196
|
+
Object.assign(stats, {
|
|
197
|
+
properties: getProperties(space),
|
|
198
|
+
db: {
|
|
199
|
+
objects: space.dataPipeline.itemManager.entities.size,
|
|
200
|
+
},
|
|
201
|
+
} as SpaceStats);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// TODO(burdon): Factor out.
|
|
205
|
+
if (stats.metrics) {
|
|
206
|
+
const { open, ready } = stats.metrics;
|
|
207
|
+
stats.metrics.startupTime = open && ready && ready.getTime() - open.getTime();
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return stats;
|
|
211
|
+
};
|
|
@@ -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,18 @@
|
|
|
2
2
|
// Copyright 2023 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
+
import { expect } from 'chai';
|
|
6
|
+
|
|
7
|
+
import { DocumentModel, DocumentModelState, MutationBuilder } from '@dxos/document-model';
|
|
8
|
+
import { createModelMutation, encodeModelMutation, genesisMutation } from '@dxos/echo-db';
|
|
9
|
+
import { WriteReceipt } from '@dxos/feed-store';
|
|
10
|
+
import { PublicKey } from '@dxos/keys';
|
|
11
|
+
import { log } from '@dxos/log';
|
|
5
12
|
import { MemorySignalManagerContext } from '@dxos/messaging';
|
|
6
13
|
import { Invitation } from '@dxos/protocols/proto/dxos/client/services';
|
|
7
14
|
import { describe, test } from '@dxos/test';
|
|
15
|
+
import { Timeframe } from '@dxos/timeframe';
|
|
16
|
+
import { range } from '@dxos/util';
|
|
8
17
|
|
|
9
18
|
import { createServiceContext, syncItemsLocal } from '../testing';
|
|
10
19
|
import { performInvitation } from '../testing/invitation-utils';
|
|
@@ -16,11 +25,61 @@ describe('services/ServiceContext', () => {
|
|
|
16
25
|
await device1.createIdentity();
|
|
17
26
|
const space1 = await device1.dataSpaceManager!.createSpace();
|
|
18
27
|
|
|
28
|
+
const itemId = PublicKey.random().toHex();
|
|
29
|
+
await space1.dataPipeline.databaseHost!.getWriteStream()?.write({
|
|
30
|
+
batch: genesisMutation(itemId, DocumentModel.meta.type),
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
let counter = 0;
|
|
34
|
+
|
|
35
|
+
for (const _ in range(5)) {
|
|
36
|
+
const receipts: WriteReceipt[] = [];
|
|
37
|
+
for (const _ in range(50)) {
|
|
38
|
+
receipts.push(
|
|
39
|
+
await space1.dataPipeline.databaseHost!.getWriteStream()!.write({
|
|
40
|
+
batch: createModelMutation(
|
|
41
|
+
itemId,
|
|
42
|
+
encodeModelMutation(DocumentModel.meta, new MutationBuilder().set('counter', ++counter).build()),
|
|
43
|
+
),
|
|
44
|
+
}),
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
await space1.dataPipeline.pipelineState!.waitUntilTimeframe(
|
|
49
|
+
Timeframe.merge(...receipts.map((receipt) => new Timeframe([[receipt.feedKey, receipt.seq]]))),
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
await space1.createEpoch();
|
|
53
|
+
log.info('epoch', { number: space1.dataPipeline.currentEpoch?.subject.assertion.number });
|
|
54
|
+
}
|
|
55
|
+
|
|
19
56
|
const device2 = createServiceContext({ signalContext: networkContext });
|
|
20
57
|
await Promise.all(performInvitation({ host: device1, guest: device2, options: { kind: Invitation.Kind.DEVICE } }));
|
|
21
58
|
|
|
22
59
|
await device2.dataSpaceManager!.waitUntilSpaceReady(space1!.key);
|
|
23
60
|
const space2 = await device2.dataSpaceManager!.spaces.get(space1.key);
|
|
61
|
+
|
|
62
|
+
log.info('peer 2', {
|
|
63
|
+
currentEpoch: space2!.dataPipeline.currentEpoch?.subject.assertion.number,
|
|
64
|
+
appliedEpoch: space2!.dataPipeline.appliedEpoch?.subject.assertion.number,
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
await space2?.dataPipeline.onNewEpoch.waitForCondition(
|
|
68
|
+
() =>
|
|
69
|
+
space2!.dataPipeline.appliedEpoch?.subject.assertion.number ===
|
|
70
|
+
space1.dataPipeline.currentEpoch?.subject.assertion.number,
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
expect(space2!.dataPipeline.currentEpoch!.subject.assertion.number).to.equal(
|
|
74
|
+
space1.dataPipeline.currentEpoch!.subject.assertion.number,
|
|
75
|
+
);
|
|
76
|
+
expect(space2!.dataPipeline.appliedEpoch!.subject.assertion.number).to.equal(
|
|
77
|
+
space1.dataPipeline.appliedEpoch!.subject.assertion.number,
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
const item2 = space2!.dataPipeline.itemManager.entities.get(itemId);
|
|
81
|
+
expect((item2!.state as DocumentModelState).data.counter).to.equal(counter);
|
|
82
|
+
|
|
24
83
|
await syncItemsLocal(space1.dataPipeline, space2!.dataPipeline);
|
|
25
84
|
});
|
|
26
85
|
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import { Trigger } from '@dxos/async';
|
|
6
|
+
import { Context } from '@dxos/context';
|
|
6
7
|
import { CredentialProcessor, getCredentialAssertion } from '@dxos/credentials';
|
|
7
8
|
import { failUndefined } from '@dxos/debug';
|
|
8
9
|
import {
|
|
@@ -26,6 +27,7 @@ import type { FeedMessage } from '@dxos/protocols/proto/dxos/echo/feed';
|
|
|
26
27
|
import { Credential } from '@dxos/protocols/proto/dxos/halo/credentials';
|
|
27
28
|
import { Storage } from '@dxos/random-access-storage';
|
|
28
29
|
import { BlobStore } from '@dxos/teleport-extension-object-sync';
|
|
30
|
+
import { trace as Trace } from '@dxos/tracing';
|
|
29
31
|
|
|
30
32
|
import { CreateIdentityOptions, IdentityManager, JoinIdentityParams } from '../identity';
|
|
31
33
|
import {
|
|
@@ -40,6 +42,7 @@ import { DataSpaceManager, SigningContext } from '../spaces';
|
|
|
40
42
|
* Shared backend for all client services.
|
|
41
43
|
*/
|
|
42
44
|
// TODO(burdon): Rename/break-up into smaller components. And/or make members private.
|
|
45
|
+
@Trace.resource()
|
|
43
46
|
export class ServiceContext {
|
|
44
47
|
public readonly initialized = new Trigger();
|
|
45
48
|
public readonly dataServiceSubscriptions = new DataServiceSubscriptions();
|
|
@@ -121,21 +124,21 @@ export class ServiceContext {
|
|
|
121
124
|
);
|
|
122
125
|
}
|
|
123
126
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
+
@Trace.span()
|
|
128
|
+
async open(ctx: Context) {
|
|
127
129
|
await this._checkStorageVersion();
|
|
128
130
|
|
|
129
131
|
log('opening...');
|
|
132
|
+
log.trace('dxos.sdk.service-context.open', trace.begin({ id: this._instanceId }));
|
|
130
133
|
await this.signalManager.open();
|
|
131
134
|
await this.networkManager.open();
|
|
132
135
|
await this.spaceManager.open();
|
|
133
|
-
await this.identityManager.open();
|
|
136
|
+
await this.identityManager.open(ctx);
|
|
134
137
|
if (this.identityManager.identity) {
|
|
135
|
-
await this._initialize();
|
|
138
|
+
await this._initialize(ctx);
|
|
136
139
|
}
|
|
137
|
-
log('opened');
|
|
138
140
|
log.trace('dxos.sdk.service-context.open', trace.end({ id: this._instanceId }));
|
|
141
|
+
log('opened');
|
|
139
142
|
}
|
|
140
143
|
|
|
141
144
|
async close() {
|
|
@@ -156,7 +159,7 @@ export class ServiceContext {
|
|
|
156
159
|
async createIdentity(params: CreateIdentityOptions = {}) {
|
|
157
160
|
const identity = await this.identityManager.createIdentity(params);
|
|
158
161
|
|
|
159
|
-
await this._initialize();
|
|
162
|
+
await this._initialize(new Context());
|
|
160
163
|
return identity;
|
|
161
164
|
}
|
|
162
165
|
|
|
@@ -169,7 +172,7 @@ export class ServiceContext {
|
|
|
169
172
|
private async _acceptIdentity(params: JoinIdentityParams) {
|
|
170
173
|
const identity = await this.identityManager.acceptIdentity(params);
|
|
171
174
|
|
|
172
|
-
await this._initialize();
|
|
175
|
+
await this._initialize(new Context());
|
|
173
176
|
return identity;
|
|
174
177
|
}
|
|
175
178
|
|
|
@@ -182,7 +185,8 @@ export class ServiceContext {
|
|
|
182
185
|
}
|
|
183
186
|
|
|
184
187
|
// Called when identity is created.
|
|
185
|
-
|
|
188
|
+
@Trace.span()
|
|
189
|
+
private async _initialize(ctx: Context) {
|
|
186
190
|
log('initializing spaces...');
|
|
187
191
|
const identity = this.identityManager.identity ?? failUndefined();
|
|
188
192
|
const signingContext: SigningContext = {
|