@dxos/client-services 0.1.56-main.09ca29d → 0.1.56-main.0e9f69e
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-II6TDINB.mjs} +782 -511
- package/dist/lib/browser/chunk-II6TDINB.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +30 -31
- 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 +31 -46
- package/dist/lib/browser/packlets/testing/index.mjs.map +3 -3
- package/dist/lib/node/index.cjs +918 -649
- 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 +1198 -939
- 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/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 +15 -3
- package/src/packlets/identity/identity-manager.ts +27 -6
- package/src/packlets/identity/identity-service.test.ts +17 -2
- package/src/packlets/identity/identity-service.ts +23 -14
- 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 +16 -9
- package/src/packlets/services/service-host.test.ts +11 -10
- package/src/packlets/services/service-host.ts +38 -18
- 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.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 +3 -2
- 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
|
@@ -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,8 @@ 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';
|
|
31
|
+
import { safeInstanceof } from '@dxos/util';
|
|
29
32
|
|
|
30
33
|
import { CreateIdentityOptions, IdentityManager, JoinIdentityParams } from '../identity';
|
|
31
34
|
import {
|
|
@@ -40,6 +43,9 @@ import { DataSpaceManager, SigningContext } from '../spaces';
|
|
|
40
43
|
* Shared backend for all client services.
|
|
41
44
|
*/
|
|
42
45
|
// TODO(burdon): Rename/break-up into smaller components. And/or make members private.
|
|
46
|
+
// TODO(dmaretskyi): Gets duplicated in CJS build between normal and testing bundles.
|
|
47
|
+
@safeInstanceof('dxos.client-services.ServiceContext')
|
|
48
|
+
@Trace.resource()
|
|
43
49
|
export class ServiceContext {
|
|
44
50
|
public readonly initialized = new Trigger();
|
|
45
51
|
public readonly dataServiceSubscriptions = new DataServiceSubscriptions();
|
|
@@ -121,21 +127,21 @@ export class ServiceContext {
|
|
|
121
127
|
);
|
|
122
128
|
}
|
|
123
129
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
130
|
+
@Trace.span()
|
|
131
|
+
async open(ctx: Context) {
|
|
127
132
|
await this._checkStorageVersion();
|
|
128
133
|
|
|
129
134
|
log('opening...');
|
|
135
|
+
log.trace('dxos.sdk.service-context.open', trace.begin({ id: this._instanceId }));
|
|
130
136
|
await this.signalManager.open();
|
|
131
137
|
await this.networkManager.open();
|
|
132
138
|
await this.spaceManager.open();
|
|
133
|
-
await this.identityManager.open();
|
|
139
|
+
await this.identityManager.open(ctx);
|
|
134
140
|
if (this.identityManager.identity) {
|
|
135
|
-
await this._initialize();
|
|
141
|
+
await this._initialize(ctx);
|
|
136
142
|
}
|
|
137
|
-
log('opened');
|
|
138
143
|
log.trace('dxos.sdk.service-context.open', trace.end({ id: this._instanceId }));
|
|
144
|
+
log('opened');
|
|
139
145
|
}
|
|
140
146
|
|
|
141
147
|
async close() {
|
|
@@ -156,7 +162,7 @@ export class ServiceContext {
|
|
|
156
162
|
async createIdentity(params: CreateIdentityOptions = {}) {
|
|
157
163
|
const identity = await this.identityManager.createIdentity(params);
|
|
158
164
|
|
|
159
|
-
await this._initialize();
|
|
165
|
+
await this._initialize(new Context());
|
|
160
166
|
return identity;
|
|
161
167
|
}
|
|
162
168
|
|
|
@@ -169,7 +175,7 @@ export class ServiceContext {
|
|
|
169
175
|
private async _acceptIdentity(params: JoinIdentityParams) {
|
|
170
176
|
const identity = await this.identityManager.acceptIdentity(params);
|
|
171
177
|
|
|
172
|
-
await this._initialize();
|
|
178
|
+
await this._initialize(new Context());
|
|
173
179
|
return identity;
|
|
174
180
|
}
|
|
175
181
|
|
|
@@ -182,7 +188,8 @@ export class ServiceContext {
|
|
|
182
188
|
}
|
|
183
189
|
|
|
184
190
|
// Called when identity is created.
|
|
185
|
-
|
|
191
|
+
@Trace.span()
|
|
192
|
+
private async _initialize(ctx: Context) {
|
|
186
193
|
log('initializing spaces...');
|
|
187
194
|
const identity = this.identityManager.identity ?? failUndefined();
|
|
188
195
|
const signingContext: SigningContext = {
|
|
@@ -8,6 +8,7 @@ import { rmSync } from 'node:fs';
|
|
|
8
8
|
|
|
9
9
|
import { asyncTimeout, latch, Trigger } from '@dxos/async';
|
|
10
10
|
import { Config } from '@dxos/config';
|
|
11
|
+
import { Context } from '@dxos/context';
|
|
11
12
|
import { verifyPresentation } from '@dxos/credentials';
|
|
12
13
|
import { PublicKey } from '@dxos/keys';
|
|
13
14
|
import { MemorySignalManagerContext } from '@dxos/messaging';
|
|
@@ -30,7 +31,7 @@ describe('ClientServicesHost', () => {
|
|
|
30
31
|
|
|
31
32
|
test('queryCredentials', async () => {
|
|
32
33
|
const host = createServiceHost(new Config(), new MemorySignalManagerContext());
|
|
33
|
-
await host.open();
|
|
34
|
+
await host.open(new Context());
|
|
34
35
|
afterTest(() => host.close());
|
|
35
36
|
|
|
36
37
|
await host.services.IdentityService!.createIdentity({});
|
|
@@ -49,14 +50,14 @@ describe('ClientServicesHost', () => {
|
|
|
49
50
|
|
|
50
51
|
test('write and query credentials', async () => {
|
|
51
52
|
const host = createServiceHost(new Config(), new MemorySignalManagerContext());
|
|
52
|
-
await host.open();
|
|
53
|
+
await host.open(new Context());
|
|
53
54
|
afterTest(() => host.close());
|
|
54
55
|
|
|
55
56
|
await host.services.IdentityService!.createIdentity({});
|
|
56
57
|
|
|
57
58
|
const testCredential = await createMockCredential({
|
|
58
|
-
signer: host.
|
|
59
|
-
issuer: host.
|
|
59
|
+
signer: host.context.keyring,
|
|
60
|
+
issuer: host.context.identityManager.identity!.deviceKey,
|
|
60
61
|
});
|
|
61
62
|
|
|
62
63
|
// Test if Identity exposes haloSpace key.
|
|
@@ -86,14 +87,14 @@ describe('ClientServicesHost', () => {
|
|
|
86
87
|
|
|
87
88
|
test('sign presentation', async () => {
|
|
88
89
|
const host = createServiceHost(new Config(), new MemorySignalManagerContext());
|
|
89
|
-
await host.open();
|
|
90
|
+
await host.open(new Context());
|
|
90
91
|
afterTest(() => host.close());
|
|
91
92
|
|
|
92
93
|
await host.services.IdentityService!.createIdentity({});
|
|
93
94
|
|
|
94
95
|
const testCredential = await createMockCredential({
|
|
95
|
-
signer: host.
|
|
96
|
-
issuer: host.
|
|
96
|
+
signer: host.context.keyring,
|
|
97
|
+
issuer: host.context.identityManager.identity!.deviceKey,
|
|
97
98
|
});
|
|
98
99
|
|
|
99
100
|
const nonce = new Uint8Array([0, 0, 0, 0]);
|
|
@@ -117,11 +118,11 @@ describe('ClientServicesHost', () => {
|
|
|
117
118
|
});
|
|
118
119
|
{
|
|
119
120
|
const host = createServiceHost(config, new MemorySignalManagerContext());
|
|
120
|
-
await host.open();
|
|
121
|
+
await host.open(new Context());
|
|
121
122
|
|
|
122
123
|
await host.services.IdentityService?.createIdentity({});
|
|
123
124
|
|
|
124
|
-
expect(host.
|
|
125
|
+
expect(host.context.storage.size).to.exist;
|
|
125
126
|
|
|
126
127
|
await asyncTimeout(host.reset(), 1000);
|
|
127
128
|
await host.close();
|
|
@@ -129,7 +130,7 @@ describe('ClientServicesHost', () => {
|
|
|
129
130
|
|
|
130
131
|
{
|
|
131
132
|
const host = createServiceHost(config, new MemorySignalManagerContext());
|
|
132
|
-
await host.open();
|
|
133
|
+
await host.open(new Context());
|
|
133
134
|
const trigger = new Trigger<Identity>();
|
|
134
135
|
|
|
135
136
|
const stream = host.services.IdentityService?.queryIdentity();
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import { Event, synchronized } from '@dxos/async';
|
|
6
6
|
import { clientServiceBundle, ClientServices } from '@dxos/client-protocol';
|
|
7
7
|
import { Config } from '@dxos/config';
|
|
8
|
+
import { Context } from '@dxos/context';
|
|
8
9
|
import { DocumentModel } from '@dxos/document-model';
|
|
9
10
|
import { DataServiceImpl } from '@dxos/echo-pipeline';
|
|
10
11
|
import { invariant } from '@dxos/invariant';
|
|
@@ -17,6 +18,7 @@ import { trace } from '@dxos/protocols';
|
|
|
17
18
|
import { SystemStatus } from '@dxos/protocols/proto/dxos/client/services';
|
|
18
19
|
import { Storage } from '@dxos/random-access-storage';
|
|
19
20
|
import { TextModel } from '@dxos/text-model';
|
|
21
|
+
import { TRACE_PROCESSOR, trace as Trace } from '@dxos/tracing';
|
|
20
22
|
|
|
21
23
|
import { DevicesServiceImpl } from '../devices';
|
|
22
24
|
import { DevtoolsServiceImpl, DevtoolsHostEvents } from '../devtools';
|
|
@@ -28,6 +30,7 @@ import { NetworkServiceImpl } from '../network';
|
|
|
28
30
|
import { SpacesServiceImpl } from '../spaces';
|
|
29
31
|
import { createStorageObjects } from '../storage';
|
|
30
32
|
import { SystemServiceImpl } from '../system';
|
|
33
|
+
import { createDiagnostics } from './diagnostics';
|
|
31
34
|
import { ServiceContext } from './service-context';
|
|
32
35
|
import { ServiceRegistry } from './service-registry';
|
|
33
36
|
|
|
@@ -64,11 +67,13 @@ export type InitializeOptions = {
|
|
|
64
67
|
/**
|
|
65
68
|
* Remote service implementation.
|
|
66
69
|
*/
|
|
70
|
+
@Trace.resource()
|
|
67
71
|
export class ClientServicesHost {
|
|
68
72
|
private readonly _resourceLock?: ResourceLock;
|
|
69
73
|
private readonly _serviceRegistry: ServiceRegistry<ClientServices>;
|
|
70
74
|
private readonly _systemService: SystemServiceImpl;
|
|
71
75
|
private readonly _loggingService: LoggingServiceImpl;
|
|
76
|
+
private readonly _tracingService = TRACE_PROCESSOR.createTraceSender();
|
|
72
77
|
|
|
73
78
|
private _config?: Config;
|
|
74
79
|
private readonly _statusUpdate = new Event<void>();
|
|
@@ -78,17 +83,19 @@ export class ClientServicesHost {
|
|
|
78
83
|
private _storage?: Storage;
|
|
79
84
|
private _callbacks?: ClientServicesHostCallbacks;
|
|
80
85
|
|
|
81
|
-
_serviceContext!: ServiceContext;
|
|
86
|
+
private _serviceContext!: ServiceContext;
|
|
87
|
+
|
|
88
|
+
@Trace.info()
|
|
82
89
|
private _opening = false;
|
|
90
|
+
|
|
91
|
+
@Trace.info()
|
|
83
92
|
private _open = false;
|
|
84
93
|
|
|
85
94
|
constructor({
|
|
86
95
|
config,
|
|
87
96
|
modelFactory = createDefaultModelFactory(),
|
|
88
|
-
// TODO(burdon): Create ApolloLink abstraction (see Client).
|
|
89
97
|
transportFactory,
|
|
90
98
|
signalManager,
|
|
91
|
-
connectionLog,
|
|
92
99
|
storage,
|
|
93
100
|
// TODO(wittjosiah): Turn this on by default.
|
|
94
101
|
lockKey,
|
|
@@ -107,7 +114,7 @@ export class ClientServicesHost {
|
|
|
107
114
|
lockKey,
|
|
108
115
|
onAcquire: () => {
|
|
109
116
|
if (!this._opening) {
|
|
110
|
-
void this.open();
|
|
117
|
+
void this.open(new Context());
|
|
111
118
|
}
|
|
112
119
|
},
|
|
113
120
|
onRelease: () => this.close(),
|
|
@@ -116,11 +123,11 @@ export class ClientServicesHost {
|
|
|
116
123
|
|
|
117
124
|
this._systemService = new SystemServiceImpl({
|
|
118
125
|
config: this._config,
|
|
119
|
-
|
|
120
126
|
statusUpdate: this._statusUpdate,
|
|
121
|
-
|
|
122
127
|
getCurrentStatus: () => (this.isOpen ? SystemStatus.ACTIVE : SystemStatus.INACTIVE),
|
|
123
|
-
|
|
128
|
+
getDiagnostics: () => {
|
|
129
|
+
return createDiagnostics(this._serviceRegistry.services, this._serviceContext, this._config!);
|
|
130
|
+
},
|
|
124
131
|
onUpdateStatus: async (status: SystemStatus) => {
|
|
125
132
|
if (!this.isOpen && status === SystemStatus.ACTIVE) {
|
|
126
133
|
await this._resourceLock?.acquire();
|
|
@@ -128,7 +135,6 @@ export class ClientServicesHost {
|
|
|
128
135
|
await this._resourceLock?.release();
|
|
129
136
|
}
|
|
130
137
|
},
|
|
131
|
-
|
|
132
138
|
onReset: async () => {
|
|
133
139
|
await this.reset();
|
|
134
140
|
},
|
|
@@ -136,9 +142,9 @@ export class ClientServicesHost {
|
|
|
136
142
|
|
|
137
143
|
this._loggingService = new LoggingServiceImpl();
|
|
138
144
|
|
|
139
|
-
// TODO(burdon): Start to think of DMG (dynamic services).
|
|
140
145
|
this._serviceRegistry = new ServiceRegistry<ClientServices>(clientServiceBundle, {
|
|
141
146
|
SystemService: this._systemService,
|
|
147
|
+
TracingService: this._tracingService,
|
|
142
148
|
});
|
|
143
149
|
}
|
|
144
150
|
|
|
@@ -146,6 +152,14 @@ export class ClientServicesHost {
|
|
|
146
152
|
return this._open;
|
|
147
153
|
}
|
|
148
154
|
|
|
155
|
+
get config() {
|
|
156
|
+
return this._config;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
get context() {
|
|
160
|
+
return this._serviceContext;
|
|
161
|
+
}
|
|
162
|
+
|
|
149
163
|
get serviceRegistry() {
|
|
150
164
|
return this._serviceRegistry;
|
|
151
165
|
}
|
|
@@ -165,10 +179,10 @@ export class ClientServicesHost {
|
|
|
165
179
|
*/
|
|
166
180
|
initialize({ config, ...options }: InitializeOptions) {
|
|
167
181
|
invariant(!this._open, 'service host is open');
|
|
182
|
+
log('initializing...');
|
|
168
183
|
|
|
169
184
|
if (config) {
|
|
170
185
|
invariant(!this._config, 'config already set');
|
|
171
|
-
|
|
172
186
|
this._config = config;
|
|
173
187
|
if (!this._storage) {
|
|
174
188
|
this._storage = createStorageObjects(config.get('runtime.client.storage', {})!).storage;
|
|
@@ -190,16 +204,19 @@ export class ClientServicesHost {
|
|
|
190
204
|
transportFactory,
|
|
191
205
|
signalManager,
|
|
192
206
|
});
|
|
207
|
+
|
|
208
|
+
log('initialized');
|
|
193
209
|
}
|
|
194
210
|
|
|
195
211
|
@synchronized
|
|
196
|
-
|
|
212
|
+
@Trace.span()
|
|
213
|
+
async open(ctx: Context) {
|
|
197
214
|
if (this._open) {
|
|
198
215
|
return;
|
|
199
216
|
}
|
|
200
217
|
|
|
201
218
|
const traceId = PublicKey.random().toHex();
|
|
202
|
-
log.trace('dxos.
|
|
219
|
+
log.trace('dxos.client-services.host.open', trace.begin({ id: traceId }));
|
|
203
220
|
|
|
204
221
|
invariant(this._config, 'config not set');
|
|
205
222
|
invariant(this._storage, 'storage not set');
|
|
@@ -212,8 +229,6 @@ export class ClientServicesHost {
|
|
|
212
229
|
|
|
213
230
|
await this._loggingService.open();
|
|
214
231
|
|
|
215
|
-
// TODO(wittjosiah): Make re-entrant.
|
|
216
|
-
// TODO(burdon): Break into components.
|
|
217
232
|
this._serviceContext = new ServiceContext(
|
|
218
233
|
this._storage,
|
|
219
234
|
this._networkManager,
|
|
@@ -221,11 +236,14 @@ export class ClientServicesHost {
|
|
|
221
236
|
this._modelFactory,
|
|
222
237
|
);
|
|
223
238
|
|
|
224
|
-
// TODO(burdon): Start to think of DMG (dynamic services).
|
|
225
239
|
this._serviceRegistry.setServices({
|
|
226
240
|
SystemService: this._systemService,
|
|
227
241
|
|
|
228
|
-
IdentityService: new IdentityServiceImpl(
|
|
242
|
+
IdentityService: new IdentityServiceImpl(
|
|
243
|
+
(params) => this._serviceContext.createIdentity(params),
|
|
244
|
+
this._serviceContext.identityManager,
|
|
245
|
+
this._serviceContext.keyring,
|
|
246
|
+
),
|
|
229
247
|
|
|
230
248
|
InvitationsService: new InvitationsServiceImpl(this._serviceContext.invitations, (invitation) =>
|
|
231
249
|
this._serviceContext.getInvitationHandler(invitation),
|
|
@@ -248,6 +266,7 @@ export class ClientServicesHost {
|
|
|
248
266
|
NetworkService: new NetworkServiceImpl(this._serviceContext.networkManager, this._serviceContext.signalManager),
|
|
249
267
|
|
|
250
268
|
LoggingService: this._loggingService,
|
|
269
|
+
TracingService: this._tracingService,
|
|
251
270
|
|
|
252
271
|
// TODO(burdon): Move to new protobuf definitions.
|
|
253
272
|
DevtoolsHost: new DevtoolsServiceImpl({
|
|
@@ -257,16 +276,17 @@ export class ClientServicesHost {
|
|
|
257
276
|
}),
|
|
258
277
|
});
|
|
259
278
|
|
|
260
|
-
await this._serviceContext.open();
|
|
279
|
+
await this._serviceContext.open(ctx);
|
|
261
280
|
this._opening = false;
|
|
262
281
|
this._open = true;
|
|
263
282
|
this._statusUpdate.emit();
|
|
264
283
|
const deviceKey = this._serviceContext.identityManager.identity?.deviceKey;
|
|
265
284
|
log('opened', { deviceKey });
|
|
266
|
-
log.trace('dxos.
|
|
285
|
+
log.trace('dxos.client-services.host.open', trace.end({ id: traceId }));
|
|
267
286
|
}
|
|
268
287
|
|
|
269
288
|
@synchronized
|
|
289
|
+
@Trace.span()
|
|
270
290
|
async close() {
|
|
271
291
|
if (!this._open) {
|
|
272
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', () => {
|