@dxos/client-services 0.4.9 → 0.4.10-main.068c3d8
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-RQ33OGAG.mjs → chunk-U3AY6WTI.mjs} +1358 -991
- package/dist/lib/browser/chunk-U3AY6WTI.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +62 -22
- 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 +12 -5
- package/dist/lib/browser/packlets/testing/index.mjs.map +3 -3
- package/dist/lib/node/{chunk-CBSM4HU4.cjs → chunk-3ILFTQW5.cjs} +1181 -902
- package/dist/lib/node/chunk-3ILFTQW5.cjs.map +7 -0
- package/dist/lib/node/index.cjs +98 -58
- package/dist/lib/node/index.cjs.map +3 -3
- package/dist/lib/node/meta.json +1 -1
- package/dist/lib/node/packlets/testing/index.cjs +16 -9
- package/dist/lib/node/packlets/testing/index.cjs.map +3 -3
- package/dist/types/src/index.d.ts +1 -0
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/packlets/diagnostics/browser-diagnostics-broadcast.d.ts +5 -0
- package/dist/types/src/packlets/diagnostics/browser-diagnostics-broadcast.d.ts.map +1 -0
- package/dist/types/src/packlets/diagnostics/diagnostics-broadcast.d.ts +5 -0
- package/dist/types/src/packlets/diagnostics/diagnostics-broadcast.d.ts.map +1 -0
- package/dist/types/src/packlets/diagnostics/diagnostics-collector.d.ts +15 -0
- package/dist/types/src/packlets/diagnostics/diagnostics-collector.d.ts.map +1 -0
- package/dist/types/src/packlets/{services → diagnostics}/diagnostics.d.ts +1 -1
- package/dist/types/src/packlets/diagnostics/diagnostics.d.ts.map +1 -0
- package/dist/types/src/packlets/diagnostics/index.d.ts +4 -0
- package/dist/types/src/packlets/diagnostics/index.d.ts.map +1 -0
- package/dist/types/src/packlets/indexing/util.d.ts +3 -2
- package/dist/types/src/packlets/indexing/util.d.ts.map +1 -1
- package/dist/types/src/packlets/invitations/index.d.ts +1 -0
- package/dist/types/src/packlets/invitations/index.d.ts.map +1 -1
- package/dist/types/src/packlets/invitations/invitation-extension.d.ts +1 -0
- package/dist/types/src/packlets/invitations/invitation-extension.d.ts.map +1 -1
- package/dist/types/src/packlets/invitations/invitations-handler.d.ts +4 -2
- package/dist/types/src/packlets/invitations/invitations-handler.d.ts.map +1 -1
- package/dist/types/src/packlets/invitations/invitations-manager.d.ts +42 -0
- package/dist/types/src/packlets/invitations/invitations-manager.d.ts.map +1 -0
- package/dist/types/src/packlets/invitations/invitations-service.d.ts +7 -23
- package/dist/types/src/packlets/invitations/invitations-service.d.ts.map +1 -1
- package/dist/types/src/packlets/services/index.d.ts +1 -1
- package/dist/types/src/packlets/services/index.d.ts.map +1 -1
- package/dist/types/src/packlets/services/service-context.d.ts +9 -5
- package/dist/types/src/packlets/services/service-context.d.ts.map +1 -1
- package/dist/types/src/packlets/services/service-host.d.ts +5 -1
- package/dist/types/src/packlets/services/service-host.d.ts.map +1 -1
- package/dist/types/src/packlets/services/util.d.ts +1 -0
- package/dist/types/src/packlets/services/util.d.ts.map +1 -1
- package/dist/types/src/packlets/spaces/data-space.d.ts.map +1 -1
- package/dist/types/src/packlets/storage/index.d.ts +1 -0
- package/dist/types/src/packlets/storage/index.d.ts.map +1 -1
- package/dist/types/src/packlets/storage/level.d.ts +4 -0
- package/dist/types/src/packlets/storage/level.d.ts.map +1 -0
- package/dist/types/src/packlets/storage/storage.d.ts.map +1 -1
- package/dist/types/src/packlets/storage/util.d.ts +4 -0
- package/dist/types/src/packlets/storage/util.d.ts.map +1 -0
- package/dist/types/src/packlets/system/system-service.d.ts +1 -1
- package/dist/types/src/packlets/system/system-service.d.ts.map +1 -1
- package/dist/types/src/packlets/testing/test-builder.d.ts +4 -2
- package/dist/types/src/packlets/testing/test-builder.d.ts.map +1 -1
- package/dist/types/src/packlets/vault/shared-worker-connection.d.ts +5 -5
- package/dist/types/src/packlets/vault/shared-worker-connection.d.ts.map +1 -1
- package/dist/types/src/packlets/vault/worker-runtime.d.ts +2 -0
- package/dist/types/src/packlets/vault/worker-runtime.d.ts.map +1 -1
- package/dist/types/src/packlets/vault/worker-session.d.ts +2 -0
- package/dist/types/src/packlets/vault/worker-session.d.ts.map +1 -1
- package/dist/types/src/version.d.ts +1 -1
- package/dist/types/src/version.d.ts.map +1 -1
- package/package.json +36 -34
- package/src/index.ts +1 -0
- package/src/packlets/devices/devices-service.test.ts +1 -1
- package/src/packlets/diagnostics/browser-diagnostics-broadcast.ts +94 -0
- package/src/packlets/diagnostics/diagnostics-broadcast.ts +20 -0
- package/src/packlets/diagnostics/diagnostics-collector.ts +65 -0
- package/src/packlets/{services → diagnostics}/diagnostics.ts +2 -2
- package/src/packlets/diagnostics/index.ts +7 -0
- package/src/packlets/identity/identity-service.test.ts +1 -1
- package/src/packlets/indexing/util.ts +6 -6
- package/src/packlets/invitations/device-invitation-protocol.test.ts +1 -1
- package/src/packlets/invitations/index.ts +1 -0
- package/src/packlets/invitations/invitation-extension.ts +28 -1
- package/src/packlets/invitations/invitations-handler.ts +74 -34
- package/src/packlets/invitations/invitations-manager.ts +197 -0
- package/src/packlets/invitations/invitations-service.ts +21 -168
- package/src/packlets/network/network-service.test.ts +1 -1
- package/src/packlets/services/automerge-host.test.ts +9 -3
- package/src/packlets/services/index.ts +1 -1
- package/src/packlets/services/service-context.test.ts +9 -6
- package/src/packlets/services/service-context.ts +29 -11
- package/src/packlets/services/service-host.ts +53 -19
- package/src/packlets/services/service-registry.test.ts +1 -1
- package/src/packlets/services/util.ts +2 -0
- package/src/packlets/spaces/data-space-manager.test.ts +4 -4
- package/src/packlets/spaces/data-space.ts +51 -2
- package/src/packlets/spaces/spaces-service.test.ts +1 -1
- package/src/packlets/storage/index.ts +1 -0
- package/src/packlets/storage/level.ts +19 -0
- package/src/packlets/storage/storage.ts +3 -9
- package/src/packlets/storage/util.ts +19 -0
- package/src/packlets/system/system-service.ts +1 -1
- package/src/packlets/testing/test-builder.ts +23 -5
- package/src/packlets/vault/shared-worker-connection.ts +3 -8
- package/src/packlets/vault/worker-runtime.ts +27 -2
- package/src/packlets/vault/worker-session.ts +6 -0
- package/src/version.ts +1 -5
- package/dist/lib/browser/chunk-RQ33OGAG.mjs.map +0 -7
- package/dist/lib/node/chunk-CBSM4HU4.cjs.map +0 -7
- package/dist/types/src/packlets/services/diagnostics.d.ts.map +0 -1
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import { MemorySignalManagerContext } from '@dxos/messaging';
|
|
6
6
|
import { Invitation } from '@dxos/protocols/proto/dxos/client/services';
|
|
7
|
-
import { describe, test } from '@dxos/test';
|
|
7
|
+
import { describe, openAndClose, test } from '@dxos/test';
|
|
8
8
|
|
|
9
9
|
import { createServiceContext } from '../testing';
|
|
10
10
|
import { performInvitation } from '../testing/invitation-utils';
|
|
@@ -12,10 +12,10 @@ import { performInvitation } from '../testing/invitation-utils';
|
|
|
12
12
|
describe('services/ServiceContext', () => {
|
|
13
13
|
test('new space is synchronized on device invitations', async () => {
|
|
14
14
|
const networkContext = new MemorySignalManagerContext();
|
|
15
|
-
const device1 = createServiceContext({ signalContext: networkContext });
|
|
15
|
+
const device1 = await createServiceContext({ signalContext: networkContext });
|
|
16
16
|
await device1.createIdentity();
|
|
17
17
|
|
|
18
|
-
const device2 = createServiceContext({ signalContext: networkContext });
|
|
18
|
+
const device2 = await createServiceContext({ signalContext: networkContext });
|
|
19
19
|
await Promise.all(performInvitation({ host: device1, guest: device2, options: { kind: Invitation.Kind.DEVICE } }));
|
|
20
20
|
|
|
21
21
|
const space1 = await device1.dataSpaceManager!.createSpace();
|
|
@@ -26,13 +26,16 @@ describe('services/ServiceContext', () => {
|
|
|
26
26
|
|
|
27
27
|
test('joined space is synchronized on device invitations', async () => {
|
|
28
28
|
const networkContext = new MemorySignalManagerContext();
|
|
29
|
-
const device1 = createServiceContext({ signalContext: networkContext });
|
|
29
|
+
const device1 = await createServiceContext({ signalContext: networkContext });
|
|
30
|
+
await openAndClose(device1.automergeHost);
|
|
30
31
|
await device1.createIdentity();
|
|
31
32
|
|
|
32
|
-
const device2 = createServiceContext({ signalContext: networkContext });
|
|
33
|
+
const device2 = await createServiceContext({ signalContext: networkContext });
|
|
34
|
+
await openAndClose(device2.automergeHost);
|
|
33
35
|
await Promise.all(performInvitation({ host: device1, guest: device2, options: { kind: Invitation.Kind.DEVICE } }));
|
|
34
36
|
|
|
35
|
-
const identity2 = createServiceContext({ signalContext: networkContext });
|
|
37
|
+
const identity2 = await createServiceContext({ signalContext: networkContext });
|
|
38
|
+
await openAndClose(identity2.automergeHost);
|
|
36
39
|
await identity2.createIdentity();
|
|
37
40
|
const space1 = await identity2.dataSpaceManager!.createSpace();
|
|
38
41
|
await Promise.all(
|
|
@@ -2,13 +2,15 @@
|
|
|
2
2
|
// Copyright 2022 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
+
import { type Level } from 'level';
|
|
6
|
+
|
|
5
7
|
import { Trigger } from '@dxos/async';
|
|
6
|
-
import { Context } from '@dxos/context';
|
|
8
|
+
import { Context, Resource } from '@dxos/context';
|
|
7
9
|
import { getCredentialAssertion, type CredentialProcessor } from '@dxos/credentials';
|
|
8
10
|
import { failUndefined } from '@dxos/debug';
|
|
9
11
|
import { AutomergeHost, MetadataStore, SnapshotStore, SpaceManager, valueEncoding } from '@dxos/echo-pipeline';
|
|
10
12
|
import { FeedFactory, FeedStore } from '@dxos/feed-store';
|
|
11
|
-
import { IndexMetadataStore, IndexStore, Indexer } from '@dxos/indexing';
|
|
13
|
+
import { IndexMetadataStore, IndexStore, Indexer, createStorageCallbacks } from '@dxos/indexing';
|
|
12
14
|
import { invariant } from '@dxos/invariant';
|
|
13
15
|
import { Keyring } from '@dxos/keyring';
|
|
14
16
|
import { PublicKey } from '@dxos/keys';
|
|
@@ -30,13 +32,14 @@ import {
|
|
|
30
32
|
type IdentityManagerRuntimeParams,
|
|
31
33
|
type JoinIdentityParams,
|
|
32
34
|
} from '../identity';
|
|
33
|
-
import {
|
|
35
|
+
import { createDocumentsIterator, createSelectedDocumentsIterator } from '../indexing';
|
|
34
36
|
import {
|
|
35
37
|
DeviceInvitationProtocol,
|
|
36
38
|
InvitationsHandler,
|
|
37
39
|
SpaceInvitationProtocol,
|
|
38
40
|
type InvitationProtocol,
|
|
39
41
|
} from '../invitations';
|
|
42
|
+
import { InvitationsManager } from '../invitations/invitations-manager';
|
|
40
43
|
import { DataSpaceManager, type DataSpaceManagerRuntimeParams, type SigningContext } from '../spaces';
|
|
41
44
|
|
|
42
45
|
export type ServiceContextRuntimeParams = IdentityManagerRuntimeParams & DataSpaceManagerRuntimeParams;
|
|
@@ -47,7 +50,7 @@ export type ServiceContextRuntimeParams = IdentityManagerRuntimeParams & DataSpa
|
|
|
47
50
|
// TODO(dmaretskyi): Gets duplicated in CJS build between normal and testing bundles.
|
|
48
51
|
@safeInstanceof('dxos.client-services.ServiceContext')
|
|
49
52
|
@Trace.resource()
|
|
50
|
-
export class ServiceContext {
|
|
53
|
+
export class ServiceContext extends Resource {
|
|
51
54
|
public readonly initialized = new Trigger();
|
|
52
55
|
public readonly metadataStore: MetadataStore;
|
|
53
56
|
/**
|
|
@@ -60,6 +63,7 @@ export class ServiceContext {
|
|
|
60
63
|
public readonly spaceManager: SpaceManager;
|
|
61
64
|
public readonly identityManager: IdentityManager;
|
|
62
65
|
public readonly invitations: InvitationsHandler;
|
|
66
|
+
public readonly invitationsManager: InvitationsManager;
|
|
63
67
|
public readonly automergeHost: AutomergeHost;
|
|
64
68
|
public readonly indexMetadata: IndexMetadataStore;
|
|
65
69
|
public readonly indexer: Indexer;
|
|
@@ -78,10 +82,13 @@ export class ServiceContext {
|
|
|
78
82
|
|
|
79
83
|
constructor(
|
|
80
84
|
public readonly storage: Storage,
|
|
85
|
+
public readonly level: Level<string, string>,
|
|
81
86
|
public readonly networkManager: NetworkManager,
|
|
82
87
|
public readonly signalManager: SignalManager,
|
|
83
88
|
public readonly _runtimeParams?: IdentityManagerRuntimeParams & DataSpaceManagerRuntimeParams,
|
|
84
89
|
) {
|
|
90
|
+
super();
|
|
91
|
+
|
|
85
92
|
// TODO(burdon): Move strings to constants.
|
|
86
93
|
this.metadataStore = new MetadataStore(storage.createDirectory('metadata'));
|
|
87
94
|
this.snapshotStore = new SnapshotStore(storage.createDirectory('snapshots'));
|
|
@@ -115,21 +122,27 @@ export class ServiceContext {
|
|
|
115
122
|
this._runtimeParams as IdentityManagerRuntimeParams,
|
|
116
123
|
);
|
|
117
124
|
|
|
118
|
-
this.indexMetadata = new IndexMetadataStore({
|
|
125
|
+
this.indexMetadata = new IndexMetadataStore({ db: level.sublevel('index-metadata') });
|
|
119
126
|
|
|
120
127
|
this.automergeHost = new AutomergeHost({
|
|
121
128
|
directory: storage.createDirectory('automerge'),
|
|
122
|
-
|
|
129
|
+
db: level.sublevel('automerge'),
|
|
130
|
+
storageCallbacks: createStorageCallbacks({ host: () => this.automergeHost, metadata: this.indexMetadata }),
|
|
123
131
|
});
|
|
124
132
|
|
|
125
133
|
this.indexer = new Indexer({
|
|
126
|
-
indexStore: new IndexStore({
|
|
134
|
+
indexStore: new IndexStore({ db: level.sublevel('index-storage') }),
|
|
127
135
|
metadataStore: this.indexMetadata,
|
|
128
|
-
loadDocuments:
|
|
129
|
-
getAllDocuments:
|
|
136
|
+
loadDocuments: createSelectedDocumentsIterator(this.automergeHost),
|
|
137
|
+
getAllDocuments: createDocumentsIterator(this.automergeHost),
|
|
130
138
|
});
|
|
131
139
|
|
|
132
140
|
this.invitations = new InvitationsHandler(this.networkManager);
|
|
141
|
+
this.invitationsManager = new InvitationsManager(
|
|
142
|
+
this.invitations,
|
|
143
|
+
(invitation) => this.getInvitationHandler(invitation),
|
|
144
|
+
this.metadataStore,
|
|
145
|
+
);
|
|
133
146
|
|
|
134
147
|
// TODO(burdon): _initialize called in multiple places.
|
|
135
148
|
// TODO(burdon): Call _initialize on success.
|
|
@@ -145,7 +158,7 @@ export class ServiceContext {
|
|
|
145
158
|
}
|
|
146
159
|
|
|
147
160
|
@Trace.span()
|
|
148
|
-
async
|
|
161
|
+
protected override async _open(ctx: Context) {
|
|
149
162
|
await this._checkStorageVersion();
|
|
150
163
|
|
|
151
164
|
log('opening...');
|
|
@@ -153,17 +166,22 @@ export class ServiceContext {
|
|
|
153
166
|
await this.signalManager.open();
|
|
154
167
|
await this.networkManager.open();
|
|
155
168
|
|
|
169
|
+
await this.automergeHost.open();
|
|
156
170
|
await this.metadataStore.load();
|
|
157
171
|
await this.spaceManager.open();
|
|
158
172
|
await this.identityManager.open(ctx);
|
|
159
173
|
if (this.identityManager.identity) {
|
|
160
174
|
await this._initialize(ctx);
|
|
161
175
|
}
|
|
176
|
+
|
|
177
|
+
const loadedInvitations = await this.invitationsManager.loadPersistentInvitations();
|
|
178
|
+
log('loaded persistent invitations', { count: loadedInvitations.invitations?.length });
|
|
179
|
+
|
|
162
180
|
log.trace('dxos.sdk.service-context.open', trace.end({ id: this._instanceId }));
|
|
163
181
|
log('opened');
|
|
164
182
|
}
|
|
165
183
|
|
|
166
|
-
async
|
|
184
|
+
protected override async _close() {
|
|
167
185
|
log('closing...');
|
|
168
186
|
if (this._deviceSpaceSync && this.identityManager.identity) {
|
|
169
187
|
await this.identityManager.identity.space.spaceState.removeCredentialProcessor(this._deviceSpaceSync);
|
|
@@ -2,12 +2,20 @@
|
|
|
2
2
|
// Copyright 2021 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
+
import { type Level } from 'level';
|
|
6
|
+
|
|
5
7
|
import { Event, synchronized } from '@dxos/async';
|
|
6
|
-
import {
|
|
8
|
+
import { clientServiceBundle, defaultKey, type ClientServices, Properties } from '@dxos/client-protocol';
|
|
7
9
|
import { type Config } from '@dxos/config';
|
|
8
10
|
import { Context } from '@dxos/context';
|
|
9
|
-
import {
|
|
10
|
-
|
|
11
|
+
import {
|
|
12
|
+
DataServiceImpl,
|
|
13
|
+
type ObjectStructure,
|
|
14
|
+
encodeReference,
|
|
15
|
+
type SpaceDoc,
|
|
16
|
+
type LevelDB,
|
|
17
|
+
} from '@dxos/echo-pipeline';
|
|
18
|
+
import { getTypeReference } from '@dxos/echo-schema';
|
|
11
19
|
import { IndexServiceImpl } from '@dxos/indexing';
|
|
12
20
|
import { invariant } from '@dxos/invariant';
|
|
13
21
|
import { PublicKey } from '@dxos/keys';
|
|
@@ -21,18 +29,22 @@ import { TRACE_PROCESSOR, trace as Trace } from '@dxos/tracing';
|
|
|
21
29
|
import { assignDeep } from '@dxos/util';
|
|
22
30
|
import { WebsocketRpcClient } from '@dxos/websocket-rpc';
|
|
23
31
|
|
|
24
|
-
import { createDiagnostics } from './diagnostics';
|
|
25
32
|
import { ServiceContext, type ServiceContextRuntimeParams } from './service-context';
|
|
26
33
|
import { ServiceRegistry } from './service-registry';
|
|
27
34
|
import { DevicesServiceImpl } from '../devices';
|
|
28
35
|
import { DevtoolsHostEvents, DevtoolsServiceImpl } from '../devtools';
|
|
36
|
+
import {
|
|
37
|
+
type CollectDiagnosticsBroadcastHandler,
|
|
38
|
+
createCollectDiagnosticsBroadcastHandler,
|
|
39
|
+
createDiagnostics,
|
|
40
|
+
} from '../diagnostics';
|
|
29
41
|
import { IdentityServiceImpl, type CreateIdentityOptions } from '../identity';
|
|
30
42
|
import { InvitationsServiceImpl } from '../invitations';
|
|
31
43
|
import { Lock, type ResourceLock } from '../locks';
|
|
32
44
|
import { LoggingServiceImpl } from '../logging';
|
|
33
45
|
import { NetworkServiceImpl } from '../network';
|
|
34
46
|
import { SpacesServiceImpl } from '../spaces';
|
|
35
|
-
import { createStorageObjects } from '../storage';
|
|
47
|
+
import { createLevel, createStorageObjects } from '../storage';
|
|
36
48
|
import { SystemServiceImpl } from '../system';
|
|
37
49
|
|
|
38
50
|
export type ClientServicesHostParams = {
|
|
@@ -44,6 +56,7 @@ export type ClientServicesHostParams = {
|
|
|
44
56
|
signalManager?: SignalManager;
|
|
45
57
|
connectionLog?: boolean;
|
|
46
58
|
storage?: Storage;
|
|
59
|
+
level?: LevelDB;
|
|
47
60
|
lockKey?: string;
|
|
48
61
|
callbacks?: ClientServicesHostCallbacks;
|
|
49
62
|
runtimeParams?: ServiceContextRuntimeParams;
|
|
@@ -76,11 +89,13 @@ export class ClientServicesHost {
|
|
|
76
89
|
private _signalManager?: SignalManager;
|
|
77
90
|
private _networkManager?: NetworkManager;
|
|
78
91
|
private _storage?: Storage;
|
|
92
|
+
private _level?: Level<string, string>;
|
|
79
93
|
private _callbacks?: ClientServicesHostCallbacks;
|
|
80
94
|
private _devtoolsProxy?: WebsocketRpcClient<{}, ClientServices>;
|
|
81
95
|
|
|
82
96
|
private _serviceContext!: ServiceContext;
|
|
83
97
|
private readonly _runtimeParams?: ServiceContextRuntimeParams;
|
|
98
|
+
private diagnosticsBroadcastHandler: CollectDiagnosticsBroadcastHandler;
|
|
84
99
|
|
|
85
100
|
@Trace.info()
|
|
86
101
|
private _opening = false;
|
|
@@ -93,12 +108,14 @@ export class ClientServicesHost {
|
|
|
93
108
|
transportFactory,
|
|
94
109
|
signalManager,
|
|
95
110
|
storage,
|
|
111
|
+
level,
|
|
96
112
|
// TODO(wittjosiah): Turn this on by default.
|
|
97
113
|
lockKey,
|
|
98
114
|
callbacks,
|
|
99
115
|
runtimeParams,
|
|
100
116
|
}: ClientServicesHostParams = {}) {
|
|
101
117
|
this._storage = storage;
|
|
118
|
+
this._level = level;
|
|
102
119
|
this._callbacks = callbacks;
|
|
103
120
|
this._runtimeParams = runtimeParams;
|
|
104
121
|
|
|
@@ -138,6 +155,7 @@ export class ClientServicesHost {
|
|
|
138
155
|
},
|
|
139
156
|
});
|
|
140
157
|
|
|
158
|
+
this.diagnosticsBroadcastHandler = createCollectDiagnosticsBroadcastHandler(this._systemService);
|
|
141
159
|
this._loggingService = new LoggingServiceImpl();
|
|
142
160
|
|
|
143
161
|
this._serviceRegistry = new ServiceRegistry<ClientServices>(clientServiceBundle, {
|
|
@@ -187,6 +205,9 @@ export class ClientServicesHost {
|
|
|
187
205
|
}
|
|
188
206
|
}
|
|
189
207
|
|
|
208
|
+
if (!options.signalManager) {
|
|
209
|
+
log.warn('running signaling without telemetry metadata.');
|
|
210
|
+
}
|
|
190
211
|
const {
|
|
191
212
|
connectionLog = true,
|
|
192
213
|
transportFactory = createSimplePeerTransportFactory({
|
|
@@ -223,12 +244,19 @@ export class ClientServicesHost {
|
|
|
223
244
|
|
|
224
245
|
this._opening = true;
|
|
225
246
|
log('opening...', { lockKey: this._resourceLock?.lockKey });
|
|
247
|
+
|
|
248
|
+
if (!this._level) {
|
|
249
|
+
this._level = await createLevel(this._config.get('runtime.client.storage', {})!);
|
|
250
|
+
}
|
|
251
|
+
await this._level.open();
|
|
252
|
+
|
|
226
253
|
await this._resourceLock?.acquire();
|
|
227
254
|
|
|
228
255
|
await this._loggingService.open();
|
|
229
256
|
|
|
230
257
|
this._serviceContext = new ServiceContext(
|
|
231
258
|
this._storage,
|
|
259
|
+
this._level,
|
|
232
260
|
this._networkManager,
|
|
233
261
|
this._signalManager,
|
|
234
262
|
this._runtimeParams,
|
|
@@ -244,11 +272,7 @@ export class ClientServicesHost {
|
|
|
244
272
|
(profile) => this._serviceContext.broadcastProfileUpdate(profile),
|
|
245
273
|
),
|
|
246
274
|
|
|
247
|
-
InvitationsService: new InvitationsServiceImpl(
|
|
248
|
-
this._serviceContext.invitations,
|
|
249
|
-
(invitation) => this._serviceContext.getInvitationHandler(invitation),
|
|
250
|
-
this._serviceContext.metadataStore,
|
|
251
|
-
),
|
|
275
|
+
InvitationsService: new InvitationsServiceImpl(this._serviceContext.invitationsManager),
|
|
252
276
|
|
|
253
277
|
DevicesService: new DevicesServiceImpl(this._serviceContext.identityManager),
|
|
254
278
|
|
|
@@ -282,11 +306,6 @@ export class ClientServicesHost {
|
|
|
282
306
|
});
|
|
283
307
|
|
|
284
308
|
await this._serviceContext.open(ctx);
|
|
285
|
-
// TODO(nf): move to InvitationManager in ServiceContext?
|
|
286
|
-
invariant(this.serviceRegistry.services.InvitationsService);
|
|
287
|
-
const loadedInvitations = await this.serviceRegistry.services.InvitationsService.loadPersistentInvitations();
|
|
288
|
-
|
|
289
|
-
log('loaded persistent invitations', { count: loadedInvitations.invitations?.length });
|
|
290
309
|
|
|
291
310
|
const devtoolsProxy = this._config?.get('runtime.client.devtoolsProxy');
|
|
292
311
|
if (devtoolsProxy) {
|
|
@@ -298,6 +317,7 @@ export class ClientServicesHost {
|
|
|
298
317
|
});
|
|
299
318
|
void this._devtoolsProxy.open();
|
|
300
319
|
}
|
|
320
|
+
this.diagnosticsBroadcastHandler.start();
|
|
301
321
|
|
|
302
322
|
this._opening = false;
|
|
303
323
|
this._open = true;
|
|
@@ -316,10 +336,12 @@ export class ClientServicesHost {
|
|
|
316
336
|
|
|
317
337
|
const deviceKey = this._serviceContext.identityManager.identity?.deviceKey;
|
|
318
338
|
log('closing...', { deviceKey });
|
|
339
|
+
this.diagnosticsBroadcastHandler.stop();
|
|
319
340
|
await this._devtoolsProxy?.close();
|
|
320
341
|
this._serviceRegistry.setServices({ SystemService: this._systemService });
|
|
321
342
|
await this._loggingService.close();
|
|
322
343
|
await this._serviceContext.close();
|
|
344
|
+
await this._level?.close();
|
|
323
345
|
this._open = false;
|
|
324
346
|
this._statusUpdate.emit();
|
|
325
347
|
log('closed', { deviceKey });
|
|
@@ -344,18 +366,30 @@ export class ClientServicesHost {
|
|
|
344
366
|
await this._serviceContext.initialized.wait();
|
|
345
367
|
const space = await this._serviceContext.dataSpaceManager!.createSpace();
|
|
346
368
|
|
|
347
|
-
const obj: TypedObject = new Properties(undefined);
|
|
348
|
-
obj[defaultKey] = identity.identityKey.toHex();
|
|
349
|
-
|
|
350
369
|
const automergeIndex = space.automergeSpaceState.rootUrl;
|
|
351
370
|
invariant(automergeIndex);
|
|
352
371
|
const document = await this._serviceContext.automergeHost.repo.find<SpaceDoc>(automergeIndex as any);
|
|
353
372
|
await document.whenReady();
|
|
354
373
|
|
|
374
|
+
// TODO(dmaretskyi): Better API for low-level data access.
|
|
375
|
+
const properties: ObjectStructure = {
|
|
376
|
+
system: {
|
|
377
|
+
type: encodeReference(getTypeReference(Properties)!),
|
|
378
|
+
},
|
|
379
|
+
data: {
|
|
380
|
+
[defaultKey]: identity.identityKey.toHex(),
|
|
381
|
+
},
|
|
382
|
+
meta: {
|
|
383
|
+
keys: [],
|
|
384
|
+
},
|
|
385
|
+
};
|
|
386
|
+
const propertiesId = PublicKey.random().toHex();
|
|
355
387
|
document.change((doc: SpaceDoc) => {
|
|
356
|
-
assignDeep(doc, ['objects',
|
|
388
|
+
assignDeep(doc, ['objects', propertiesId], properties);
|
|
357
389
|
});
|
|
358
390
|
|
|
391
|
+
await this._serviceContext.automergeHost.repo.flush();
|
|
392
|
+
|
|
359
393
|
return identity;
|
|
360
394
|
}
|
|
361
395
|
}
|
|
@@ -31,7 +31,7 @@ const serviceBundle = createServiceBundle<TestServices>({
|
|
|
31
31
|
describe('service registry', () => {
|
|
32
32
|
test('builds a service registry', async () => {
|
|
33
33
|
const remoteSource = 'https://remote.source';
|
|
34
|
-
const serviceContext = createServiceContext();
|
|
34
|
+
const serviceContext = await createServiceContext();
|
|
35
35
|
await serviceContext.open(new Context());
|
|
36
36
|
|
|
37
37
|
const serviceRegistry = new ServiceRegistry(serviceBundle, {
|
|
@@ -20,7 +20,7 @@ describe('DataSpaceManager', () => {
|
|
|
20
20
|
|
|
21
21
|
const peer = builder.createPeer();
|
|
22
22
|
await peer.createIdentity();
|
|
23
|
-
await openAndClose(peer.dataSpaceManager);
|
|
23
|
+
await openAndClose(peer.automergeHost, peer.dataSpaceManager);
|
|
24
24
|
|
|
25
25
|
const space = await peer.dataSpaceManager.createSpace();
|
|
26
26
|
|
|
@@ -42,7 +42,7 @@ describe('DataSpaceManager', () => {
|
|
|
42
42
|
const peer2 = builder.createPeer();
|
|
43
43
|
await peer2.createIdentity();
|
|
44
44
|
|
|
45
|
-
await openAndClose(peer1.dataSpaceManager, peer2.dataSpaceManager);
|
|
45
|
+
await openAndClose(peer1.automergeHost, peer1.dataSpaceManager, peer2.automergeHost, peer2.dataSpaceManager);
|
|
46
46
|
|
|
47
47
|
const space1 = await peer1.dataSpaceManager.createSpace();
|
|
48
48
|
await space1.inner.controlPipeline.state.waitUntilTimeframe(space1.inner.controlPipeline.state.endTimeframe);
|
|
@@ -111,7 +111,7 @@ describe('DataSpaceManager', () => {
|
|
|
111
111
|
await peer2.createIdentity();
|
|
112
112
|
await peer2.dataSpaceManager.open();
|
|
113
113
|
|
|
114
|
-
await openAndClose(peer1.dataSpaceManager, peer2.dataSpaceManager);
|
|
114
|
+
await openAndClose(peer1.automergeHost, peer1.dataSpaceManager, peer2.automergeHost, peer2.dataSpaceManager);
|
|
115
115
|
|
|
116
116
|
const space1 = await peer1.dataSpaceManager.createSpace();
|
|
117
117
|
await space1.inner.controlPipeline.state.waitUntilTimeframe(space1.inner.controlPipeline.state.endTimeframe);
|
|
@@ -153,7 +153,7 @@ describe('DataSpaceManager', () => {
|
|
|
153
153
|
|
|
154
154
|
const peer = builder.createPeer();
|
|
155
155
|
await peer.createIdentity();
|
|
156
|
-
await openAndClose(peer.dataSpaceManager);
|
|
156
|
+
await openAndClose(peer.automergeHost, peer.dataSpaceManager);
|
|
157
157
|
|
|
158
158
|
const space = await peer.dataSpaceManager.createSpace();
|
|
159
159
|
await space.inner.controlPipeline.state.waitUntilTimeframe(space.inner.controlPipeline.state.endTimeframe);
|
|
@@ -6,7 +6,15 @@ import { Event, asyncTimeout, scheduleTask, sleep, synchronized, trackLeaks } fr
|
|
|
6
6
|
import { AUTH_TIMEOUT } from '@dxos/client-protocol';
|
|
7
7
|
import { cancelWithContext, Context, ContextDisposedError } from '@dxos/context';
|
|
8
8
|
import { timed, warnAfterTimeout } from '@dxos/debug';
|
|
9
|
-
import {
|
|
9
|
+
import { TYPE_PROPERTIES } from '@dxos/echo-db';
|
|
10
|
+
import {
|
|
11
|
+
type MetadataStore,
|
|
12
|
+
type Space,
|
|
13
|
+
createMappedFeedWriter,
|
|
14
|
+
type AutomergeHost,
|
|
15
|
+
type SpaceDoc,
|
|
16
|
+
} from '@dxos/echo-pipeline';
|
|
17
|
+
import { AutomergeDocumentLoaderImpl } from '@dxos/echo-pipeline';
|
|
10
18
|
import { type FeedStore } from '@dxos/feed-store';
|
|
11
19
|
import { failedInvariant, invariant } from '@dxos/invariant';
|
|
12
20
|
import { type Keyring } from '@dxos/keyring';
|
|
@@ -26,7 +34,7 @@ import { type GossipMessage } from '@dxos/protocols/proto/dxos/mesh/teleport/gos
|
|
|
26
34
|
import { type Gossip, type Presence } from '@dxos/teleport-extension-gossip';
|
|
27
35
|
import { Timeframe } from '@dxos/timeframe';
|
|
28
36
|
import { trace } from '@dxos/tracing';
|
|
29
|
-
import { ComplexSet } from '@dxos/util';
|
|
37
|
+
import { ComplexSet, assignDeep } from '@dxos/util';
|
|
30
38
|
|
|
31
39
|
import { AutomergeSpaceState } from './automerge-space-state';
|
|
32
40
|
import { type SigningContext } from './data-space-manager';
|
|
@@ -437,6 +445,47 @@ export class DataSpace {
|
|
|
437
445
|
};
|
|
438
446
|
}
|
|
439
447
|
break;
|
|
448
|
+
case CreateEpochRequest.Migration.FRAGMENT_AUTOMERGE_ROOT:
|
|
449
|
+
{
|
|
450
|
+
log.info('Fragmenting');
|
|
451
|
+
|
|
452
|
+
const currentRootUrl = this._automergeSpaceState.rootUrl;
|
|
453
|
+
const rootHandle = this._automergeHost.repo.find<SpaceDoc>(currentRootUrl as any);
|
|
454
|
+
await cancelWithContext(this._ctx, asyncTimeout(rootHandle.whenReady(), 10_000));
|
|
455
|
+
|
|
456
|
+
// Find properties object.
|
|
457
|
+
const objects = Object.entries((rootHandle.docSync() as SpaceDoc).objects!);
|
|
458
|
+
const properties = objects.find(([_, value]) => value.system.type?.itemId === TYPE_PROPERTIES);
|
|
459
|
+
const otherObjects = objects.filter(([key]) => key !== properties?.[0]);
|
|
460
|
+
invariant(properties, 'Properties not found');
|
|
461
|
+
|
|
462
|
+
// Create a new space doc with the properties object.
|
|
463
|
+
const newSpaceDoc: SpaceDoc = { ...rootHandle.docSync(), objects: Object.fromEntries([properties]) };
|
|
464
|
+
const newRoot = this._automergeHost.repo.create(newSpaceDoc);
|
|
465
|
+
invariant(typeof newRoot.url === 'string' && newRoot.url.length > 0);
|
|
466
|
+
|
|
467
|
+
// Create new automerge documents for all objects.
|
|
468
|
+
const docLoader = new AutomergeDocumentLoaderImpl(this.key, this._automergeHost.repo);
|
|
469
|
+
await docLoader.loadSpaceRootDocHandle(this._ctx, { rootUrl: newRoot.url });
|
|
470
|
+
|
|
471
|
+
otherObjects.forEach(([key, value]) => {
|
|
472
|
+
const handle = docLoader.createDocumentForObject(key);
|
|
473
|
+
handle.change((doc: any) => {
|
|
474
|
+
assignDeep(doc, ['objects', key], value);
|
|
475
|
+
});
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
// TODO(mykola): Delete old root.
|
|
479
|
+
|
|
480
|
+
// TODO(dmaretskyi): Unify epoch construction.
|
|
481
|
+
epoch = {
|
|
482
|
+
previousId: this._automergeSpaceState.lastEpoch?.id,
|
|
483
|
+
number: (this._automergeSpaceState.lastEpoch?.subject.assertion.number ?? -1) + 1,
|
|
484
|
+
timeframe: this._automergeSpaceState.lastEpoch?.subject.assertion.timeframe ?? new Timeframe(),
|
|
485
|
+
automergeRoot: newRoot.url,
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
break;
|
|
440
489
|
}
|
|
441
490
|
|
|
442
491
|
if (!epoch) {
|
|
@@ -22,7 +22,7 @@ describe('SpacesService', () => {
|
|
|
22
22
|
let spacesService: SpacesService;
|
|
23
23
|
|
|
24
24
|
beforeEach(async () => {
|
|
25
|
-
serviceContext = createServiceContext();
|
|
25
|
+
serviceContext = await createServiceContext();
|
|
26
26
|
await serviceContext.open(new Context());
|
|
27
27
|
spacesService = new SpacesServiceImpl(serviceContext.identityManager, serviceContext.spaceManager, async () => {
|
|
28
28
|
await serviceContext.initialized.wait();
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { Level } from 'level';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
|
|
8
|
+
import { PublicKey } from '@dxos/keys';
|
|
9
|
+
import { type Runtime } from '@dxos/protocols/proto/dxos/config';
|
|
10
|
+
|
|
11
|
+
import { getRootPath, isPersistent } from './util';
|
|
12
|
+
|
|
13
|
+
export const createLevel = async (config: Runtime.Client.Storage) => {
|
|
14
|
+
const persistent = isPersistent(config);
|
|
15
|
+
const storagePath = persistent ? path.join(getRootPath(config), 'level') : `/tmp/dxos-${PublicKey.random().toHex()}`;
|
|
16
|
+
const level = new Level<string, string>(storagePath);
|
|
17
|
+
await level.open();
|
|
18
|
+
return level;
|
|
19
|
+
};
|
|
@@ -4,22 +4,16 @@
|
|
|
4
4
|
// Copyright 2023 DXOS.org
|
|
5
5
|
//
|
|
6
6
|
|
|
7
|
-
import { DX_DATA } from '@dxos/client-protocol';
|
|
8
7
|
import { InvalidConfigError } from '@dxos/protocols';
|
|
9
8
|
import { Runtime } from '@dxos/protocols/proto/dxos/config';
|
|
10
9
|
import { createStorage, StorageType } from '@dxos/random-access-storage';
|
|
11
|
-
import { isNode } from '@dxos/util';
|
|
12
10
|
|
|
13
11
|
import StorageDriver = Runtime.Client.Storage.StorageDriver;
|
|
12
|
+
import { getRootPath } from './util';
|
|
14
13
|
|
|
15
14
|
// TODO(burdon): Factor out.
|
|
16
15
|
export const createStorageObjects = (config: Runtime.Client.Storage) => {
|
|
17
|
-
const {
|
|
18
|
-
persistent = false,
|
|
19
|
-
keyStore,
|
|
20
|
-
dataStore,
|
|
21
|
-
dataRoot = isNode() ? DX_DATA : 'dxos/storage', // TODO(burdon): Factor out const.
|
|
22
|
-
} = config ?? {};
|
|
16
|
+
const { persistent = false, keyStore, dataStore } = config ?? {};
|
|
23
17
|
|
|
24
18
|
if (persistent && dataStore === StorageDriver.RAM) {
|
|
25
19
|
throw new InvalidConfigError('RAM storage cannot be used in persistent mode.');
|
|
@@ -37,7 +31,7 @@ export const createStorageObjects = (config: Runtime.Client.Storage) => {
|
|
|
37
31
|
return {
|
|
38
32
|
storage: createStorage({
|
|
39
33
|
type: persistent ? toStorageType(dataStore) : StorageType.RAM,
|
|
40
|
-
root:
|
|
34
|
+
root: getRootPath(config),
|
|
41
35
|
}),
|
|
42
36
|
};
|
|
43
37
|
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { DX_DATA } from '@dxos/client-protocol';
|
|
6
|
+
import { Runtime } from '@dxos/protocols/proto/dxos/config';
|
|
7
|
+
import { isNode } from '@dxos/util';
|
|
8
|
+
|
|
9
|
+
export const getRootPath = (config: Runtime.Client.Storage) => {
|
|
10
|
+
const { dataRoot = isNode() ? DX_DATA : 'dxos/storage' } = config ?? {};
|
|
11
|
+
return `${dataRoot}/`;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export const isPersistent = (config: Runtime.Client.Storage) => {
|
|
15
|
+
const { persistent = false } = config ?? {};
|
|
16
|
+
return (
|
|
17
|
+
(config.dataStore !== undefined && config.dataStore !== Runtime.Client.Storage.StorageDriver.RAM) || persistent
|
|
18
|
+
);
|
|
19
|
+
};
|
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
} from '@dxos/protocols/proto/dxos/client/services';
|
|
17
17
|
import { jsonKeyReplacer, type MaybePromise } from '@dxos/util';
|
|
18
18
|
|
|
19
|
-
import { type Diagnostics } from '../
|
|
19
|
+
import { type Diagnostics } from '../diagnostics';
|
|
20
20
|
import { getPlatform } from '../services/platform';
|
|
21
21
|
|
|
22
22
|
export type SystemServiceOptions = {
|