@dxos/client-services 0.1.20 → 0.1.21
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/index.mjs +306 -129
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node/index.cjs +333 -152
- package/dist/lib/node/index.cjs.map +4 -4
- package/dist/lib/node/meta.json +1 -1
- package/dist/types/src/packlets/identity/identity-manager.d.ts.map +1 -1
- package/dist/types/src/packlets/identity/identity.d.ts.map +1 -1
- package/dist/types/src/packlets/invitations/space-invitations-handler.d.ts.map +1 -1
- package/dist/types/src/packlets/network/index.d.ts +2 -0
- package/dist/types/src/packlets/network/index.d.ts.map +1 -0
- package/dist/types/src/packlets/network/network-service.d.ts +10 -0
- package/dist/types/src/packlets/network/network-service.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-definitions.d.ts +2 -1
- package/dist/types/src/packlets/services/service-definitions.d.ts.map +1 -1
- package/dist/types/src/packlets/services/service-host.d.ts +4 -1
- package/dist/types/src/packlets/services/service-host.d.ts.map +1 -1
- package/dist/types/src/packlets/services/service-host.test.d.ts +2 -0
- package/dist/types/src/packlets/services/service-host.test.d.ts.map +1 -0
- package/dist/types/src/packlets/spaces/data-space-manager.d.ts +4 -2
- package/dist/types/src/packlets/spaces/data-space-manager.d.ts.map +1 -1
- package/dist/types/src/packlets/spaces/data-space.d.ts +13 -6
- package/dist/types/src/packlets/spaces/data-space.d.ts.map +1 -1
- package/dist/types/src/packlets/spaces/spaces-service.d.ts +6 -1
- package/dist/types/src/packlets/spaces/spaces-service.d.ts.map +1 -1
- package/dist/types/src/packlets/storage/storage.d.ts +0 -2
- package/dist/types/src/packlets/storage/storage.d.ts.map +1 -1
- package/dist/types/src/packlets/vault/worker-session.d.ts.map +1 -1
- package/package.json +28 -34
- package/src/packlets/deprecated/space.ts +1 -1
- package/src/packlets/devtools/spaces.ts +1 -1
- package/src/packlets/identity/identity-manager.ts +3 -9
- package/src/packlets/identity/identity.test.ts +4 -33
- package/src/packlets/identity/identity.ts +26 -18
- package/src/packlets/invitations/space-invitations-handler.test.ts +53 -1
- package/src/packlets/invitations/space-invitations-handler.ts +9 -5
- package/src/packlets/network/index.ts +5 -0
- package/src/packlets/network/network-service.ts +24 -0
- package/src/packlets/services/service-context.ts +9 -2
- package/src/packlets/services/service-definitions.ts +5 -1
- package/src/packlets/services/service-host.test.ts +31 -0
- package/src/packlets/services/service-host.ts +13 -9
- package/src/packlets/spaces/data-space-manager.ts +46 -9
- package/src/packlets/spaces/data-space.ts +28 -10
- package/src/packlets/spaces/spaces-service.ts +21 -0
- package/src/packlets/storage/storage.ts +4 -41
- package/src/packlets/vault/worker-session.ts +1 -0
|
@@ -6,12 +6,11 @@ import assert from 'node:assert';
|
|
|
6
6
|
|
|
7
7
|
import { Event } from '@dxos/async';
|
|
8
8
|
import { createCredentialSignerWithKey, CredentialGenerator } from '@dxos/credentials';
|
|
9
|
-
import { MetadataStore,
|
|
9
|
+
import { MetadataStore, SpaceManager, SwarmIdentity } from '@dxos/echo-db';
|
|
10
10
|
import { Keyring } from '@dxos/keyring';
|
|
11
11
|
import { PublicKey } from '@dxos/keys';
|
|
12
12
|
import { log } from '@dxos/log';
|
|
13
13
|
import { AdmittedFeed, IdentityRecord, SpaceRecord } from '@dxos/protocols/proto/dxos/halo/credentials';
|
|
14
|
-
import { Presence } from '@dxos/teleport-extension-presence';
|
|
15
14
|
import { deferFunction } from '@dxos/util';
|
|
16
15
|
|
|
17
16
|
import { Identity } from '../identity';
|
|
@@ -55,6 +54,7 @@ export class IdentityManager {
|
|
|
55
54
|
await this._metadataStore.load();
|
|
56
55
|
|
|
57
56
|
const identityRecord = this._metadataStore.getIdentityRecord();
|
|
57
|
+
log('identity record', { identityRecord });
|
|
58
58
|
if (identityRecord) {
|
|
59
59
|
this._identity = await this._constructIdentity(identityRecord);
|
|
60
60
|
await this._identity.open();
|
|
@@ -192,14 +192,8 @@ export class IdentityManager {
|
|
|
192
192
|
controlFeedKey: spaceRecord.writeControlFeedKey,
|
|
193
193
|
dataFeedKey: spaceRecord.writeDataFeedKey
|
|
194
194
|
},
|
|
195
|
-
dataPipelineControllerProvider: () => new NoopDataPipelineController(),
|
|
196
195
|
swarmIdentity,
|
|
197
|
-
|
|
198
|
-
localPeerId: swarmIdentity.peerKey,
|
|
199
|
-
announceInterval: 1_000,
|
|
200
|
-
offlineTimeout: 30_000,
|
|
201
|
-
identityKey
|
|
202
|
-
})
|
|
196
|
+
onNetworkConnection: () => {}
|
|
203
197
|
});
|
|
204
198
|
}
|
|
205
199
|
}
|
|
@@ -5,14 +5,7 @@
|
|
|
5
5
|
import expect from 'expect';
|
|
6
6
|
|
|
7
7
|
import { CredentialGenerator, verifyCredential } from '@dxos/credentials';
|
|
8
|
-
import {
|
|
9
|
-
MOCK_AUTH_PROVIDER,
|
|
10
|
-
MOCK_AUTH_VERIFIER,
|
|
11
|
-
NoopDataPipelineController,
|
|
12
|
-
Space,
|
|
13
|
-
SpaceProtocol,
|
|
14
|
-
valueEncoding
|
|
15
|
-
} from '@dxos/echo-db';
|
|
8
|
+
import { MOCK_AUTH_PROVIDER, MOCK_AUTH_VERIFIER, Space, SpaceProtocol, valueEncoding } from '@dxos/echo-db';
|
|
16
9
|
import { FeedFactory, FeedStore } from '@dxos/feed-store';
|
|
17
10
|
import { Keyring } from '@dxos/keyring';
|
|
18
11
|
import { PublicKey } from '@dxos/keys';
|
|
@@ -21,7 +14,6 @@ import { MemoryTransportFactory, NetworkManager } from '@dxos/network-manager';
|
|
|
21
14
|
import { FeedMessage } from '@dxos/protocols/proto/dxos/echo/feed';
|
|
22
15
|
import { AdmittedFeed } from '@dxos/protocols/proto/dxos/halo/credentials';
|
|
23
16
|
import { createStorage, StorageType } from '@dxos/random-access-storage';
|
|
24
|
-
import { Presence } from '@dxos/teleport-extension-presence';
|
|
25
17
|
import { afterTest, describe, test } from '@dxos/test';
|
|
26
18
|
|
|
27
19
|
import { Identity } from './identity';
|
|
@@ -61,12 +53,6 @@ describe('identity/identity', () => {
|
|
|
61
53
|
networkManager: new NetworkManager({
|
|
62
54
|
signalManager: new MemorySignalManager(new MemorySignalManagerContext()),
|
|
63
55
|
transportFactory: MemoryTransportFactory
|
|
64
|
-
}),
|
|
65
|
-
presence: new Presence({
|
|
66
|
-
localPeerId: deviceKey,
|
|
67
|
-
announceInterval: 30,
|
|
68
|
-
offlineTimeout: 200,
|
|
69
|
-
identityKey
|
|
70
56
|
})
|
|
71
57
|
});
|
|
72
58
|
|
|
@@ -76,8 +62,7 @@ describe('identity/identity', () => {
|
|
|
76
62
|
genesisFeed: controlFeed,
|
|
77
63
|
controlFeed,
|
|
78
64
|
dataFeed,
|
|
79
|
-
feedProvider: (feedKey) => feedStore.openFeed(feedKey)
|
|
80
|
-
dataPipelineControllerProvider: () => new NoopDataPipelineController()
|
|
65
|
+
feedProvider: (feedKey) => feedStore.openFeed(feedKey)
|
|
81
66
|
});
|
|
82
67
|
|
|
83
68
|
const identity = new Identity({
|
|
@@ -174,12 +159,6 @@ describe('identity/identity', () => {
|
|
|
174
159
|
networkManager: new NetworkManager({
|
|
175
160
|
signalManager: new MemorySignalManager(signalContext),
|
|
176
161
|
transportFactory: MemoryTransportFactory
|
|
177
|
-
}),
|
|
178
|
-
presence: new Presence({
|
|
179
|
-
localPeerId: deviceKey,
|
|
180
|
-
announceInterval: 30,
|
|
181
|
-
offlineTimeout: 200,
|
|
182
|
-
identityKey
|
|
183
162
|
})
|
|
184
163
|
});
|
|
185
164
|
|
|
@@ -189,8 +168,7 @@ describe('identity/identity', () => {
|
|
|
189
168
|
genesisFeed: controlFeed,
|
|
190
169
|
controlFeed,
|
|
191
170
|
dataFeed,
|
|
192
|
-
feedProvider: (feedKey) => feedStore.openFeed(feedKey)
|
|
193
|
-
dataPipelineControllerProvider: () => new NoopDataPipelineController()
|
|
171
|
+
feedProvider: (feedKey) => feedStore.openFeed(feedKey)
|
|
194
172
|
});
|
|
195
173
|
|
|
196
174
|
const identity = (identity1 = new Identity({
|
|
@@ -261,12 +239,6 @@ describe('identity/identity', () => {
|
|
|
261
239
|
networkManager: new NetworkManager({
|
|
262
240
|
signalManager: new MemorySignalManager(signalContext),
|
|
263
241
|
transportFactory: MemoryTransportFactory
|
|
264
|
-
}),
|
|
265
|
-
presence: new Presence({
|
|
266
|
-
localPeerId: deviceKey,
|
|
267
|
-
announceInterval: 30,
|
|
268
|
-
offlineTimeout: 200,
|
|
269
|
-
identityKey
|
|
270
242
|
})
|
|
271
243
|
});
|
|
272
244
|
|
|
@@ -276,8 +248,7 @@ describe('identity/identity', () => {
|
|
|
276
248
|
genesisFeed: await feedStore.openFeed(genesisFeedKey),
|
|
277
249
|
controlFeed,
|
|
278
250
|
dataFeed,
|
|
279
|
-
feedProvider: (feedKey) => feedStore.openFeed(feedKey)
|
|
280
|
-
dataPipelineControllerProvider: () => new NoopDataPipelineController()
|
|
251
|
+
feedProvider: (feedKey) => feedStore.openFeed(feedKey)
|
|
281
252
|
});
|
|
282
253
|
|
|
283
254
|
const identity = (identity2 = new Identity({
|
|
@@ -10,7 +10,8 @@ import {
|
|
|
10
10
|
CredentialSigner,
|
|
11
11
|
createCredentialSignerWithKey,
|
|
12
12
|
createCredentialSignerWithChain,
|
|
13
|
-
ProfileStateMachine
|
|
13
|
+
ProfileStateMachine,
|
|
14
|
+
CredentialConsumer
|
|
14
15
|
} from '@dxos/credentials';
|
|
15
16
|
import { Signer } from '@dxos/crypto';
|
|
16
17
|
import { Space } from '@dxos/echo-db';
|
|
@@ -42,8 +43,8 @@ export type IdentityParams = {
|
|
|
42
43
|
export class Identity {
|
|
43
44
|
public readonly space: Space;
|
|
44
45
|
private readonly _signer: Signer;
|
|
45
|
-
private readonly _deviceStateMachine: DeviceStateMachine
|
|
46
|
-
private readonly _profileStateMachine: ProfileStateMachine
|
|
46
|
+
private readonly _deviceStateMachine: CredentialConsumer<DeviceStateMachine>;
|
|
47
|
+
private readonly _profileStateMachine: CredentialConsumer<ProfileStateMachine>;
|
|
47
48
|
public readonly authVerifier: HaloAuthVerifier;
|
|
48
49
|
|
|
49
50
|
public readonly identityKey: PublicKey;
|
|
@@ -58,16 +59,19 @@ export class Identity {
|
|
|
58
59
|
this.identityKey = identityKey;
|
|
59
60
|
this.deviceKey = deviceKey;
|
|
60
61
|
|
|
61
|
-
this._deviceStateMachine =
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
62
|
+
this._deviceStateMachine = this.space.spaceState.registerProcessor(
|
|
63
|
+
new DeviceStateMachine({
|
|
64
|
+
identityKey: this.identityKey,
|
|
65
|
+
deviceKey: this.deviceKey,
|
|
66
|
+
onUpdate: () => this.stateUpdate.emit()
|
|
67
|
+
})
|
|
68
|
+
);
|
|
69
|
+
this._profileStateMachine = this.space.spaceState.registerProcessor(
|
|
70
|
+
new ProfileStateMachine({
|
|
71
|
+
identityKey: this.identityKey,
|
|
72
|
+
onUpdate: () => this.stateUpdate.emit()
|
|
73
|
+
})
|
|
74
|
+
);
|
|
71
75
|
|
|
72
76
|
this.authVerifier = new HaloAuthVerifier({
|
|
73
77
|
trustedDevicesProvider: () => this.authorizedDeviceKeys,
|
|
@@ -78,26 +82,30 @@ export class Identity {
|
|
|
78
82
|
|
|
79
83
|
// TODO(burdon): Expose state object?
|
|
80
84
|
get authorizedDeviceKeys(): ComplexSet<PublicKey> {
|
|
81
|
-
return this._deviceStateMachine.authorizedDeviceKeys;
|
|
85
|
+
return this._deviceStateMachine.processor.authorizedDeviceKeys;
|
|
82
86
|
}
|
|
83
87
|
|
|
84
88
|
async open() {
|
|
89
|
+
await this._deviceStateMachine.open();
|
|
90
|
+
await this._profileStateMachine.open();
|
|
85
91
|
await this.space.open();
|
|
86
92
|
}
|
|
87
93
|
|
|
88
94
|
async close() {
|
|
89
95
|
await this.authVerifier.close();
|
|
96
|
+
await this._deviceStateMachine.close();
|
|
97
|
+
await this._profileStateMachine.close();
|
|
90
98
|
await this.space.close();
|
|
91
99
|
}
|
|
92
100
|
|
|
93
101
|
async ready() {
|
|
94
|
-
await this._deviceStateMachine.deviceChainReady.wait();
|
|
102
|
+
await this._deviceStateMachine.processor.deviceChainReady.wait();
|
|
95
103
|
|
|
96
104
|
// TODO(dmaretskyi): Should we also wait for our feeds to be admitted?
|
|
97
105
|
}
|
|
98
106
|
|
|
99
107
|
get profileDocument(): ProfileDocument | undefined {
|
|
100
|
-
return this._profileStateMachine.profile;
|
|
108
|
+
return this._profileStateMachine.processor.profile;
|
|
101
109
|
}
|
|
102
110
|
|
|
103
111
|
/**
|
|
@@ -128,10 +136,10 @@ export class Identity {
|
|
|
128
136
|
* Requires identity to be ready.
|
|
129
137
|
*/
|
|
130
138
|
getIdentityCredentialSigner(): CredentialSigner {
|
|
131
|
-
assert(this._deviceStateMachine.deviceCredentialChain, 'Device credential chain is not ready.');
|
|
139
|
+
assert(this._deviceStateMachine.processor.deviceCredentialChain, 'Device credential chain is not ready.');
|
|
132
140
|
return createCredentialSignerWithChain(
|
|
133
141
|
this._signer,
|
|
134
|
-
this._deviceStateMachine.deviceCredentialChain,
|
|
142
|
+
this._deviceStateMachine.processor.deviceCredentialChain,
|
|
135
143
|
this.deviceKey
|
|
136
144
|
);
|
|
137
145
|
}
|
|
@@ -5,12 +5,13 @@
|
|
|
5
5
|
import { expect } from 'chai';
|
|
6
6
|
import assert from 'node:assert';
|
|
7
7
|
|
|
8
|
-
import { asyncChain, Trigger } from '@dxos/async';
|
|
8
|
+
import { asyncChain, latch, Trigger } from '@dxos/async';
|
|
9
9
|
import { raise } from '@dxos/debug';
|
|
10
10
|
import { PublicKey } from '@dxos/keys';
|
|
11
11
|
import { ObjectModel } from '@dxos/object-model';
|
|
12
12
|
import { Invitation } from '@dxos/protocols/proto/dxos/client/services';
|
|
13
13
|
import { describe, test, afterTest } from '@dxos/test';
|
|
14
|
+
import { range } from '@dxos/util';
|
|
14
15
|
|
|
15
16
|
import { ServiceContext } from '../services';
|
|
16
17
|
import { createIdentity, createPeers, syncItems } from '../testing';
|
|
@@ -157,4 +158,55 @@ describe('services/space-invitations-handler', () => {
|
|
|
157
158
|
await cancelled.wait();
|
|
158
159
|
await space1.close();
|
|
159
160
|
});
|
|
161
|
+
|
|
162
|
+
test('test multi-use invitation', async () => {
|
|
163
|
+
const GUEST_COUNT = 3;
|
|
164
|
+
const [host, ...guests] = await asyncChain<ServiceContext>([createIdentity, closeAfterTest])(
|
|
165
|
+
createPeers(GUEST_COUNT + 1)
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
const hostSpace = await host.dataSpaceManager!.createSpace();
|
|
169
|
+
const swarmKey = PublicKey.random();
|
|
170
|
+
const hostObservable = await host.spaceInvitations!.createInvitation(hostSpace, {
|
|
171
|
+
swarmKey,
|
|
172
|
+
type: Invitation.Type.MULTIUSE_TESTING
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
const [done, count] = latch({ count: GUEST_COUNT });
|
|
176
|
+
hostObservable.subscribe({
|
|
177
|
+
onConnecting: async (invitation2: Invitation) => {},
|
|
178
|
+
onConnected: async (invitation2: Invitation) => {},
|
|
179
|
+
onSuccess: () => {
|
|
180
|
+
count();
|
|
181
|
+
},
|
|
182
|
+
onCancelled: () => {},
|
|
183
|
+
onTimeout: (err: Error) => raise(new Error(err.message)),
|
|
184
|
+
onError: (err: Error) => raise(new Error(err.message))
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
await Promise.all(
|
|
188
|
+
range(GUEST_COUNT).map(async (idx) => {
|
|
189
|
+
const observable = await guests[idx].spaceInvitations!.acceptInvitation({
|
|
190
|
+
swarmKey,
|
|
191
|
+
type: Invitation.Type.MULTIUSE_TESTING
|
|
192
|
+
});
|
|
193
|
+
const success = new Trigger();
|
|
194
|
+
observable.subscribe({
|
|
195
|
+
onConnecting: async (invitation2: Invitation) => {},
|
|
196
|
+
onConnected: async (invitation2: Invitation) => {},
|
|
197
|
+
onSuccess: () => {
|
|
198
|
+
success.wake();
|
|
199
|
+
},
|
|
200
|
+
onCancelled: () => raise(new Error()),
|
|
201
|
+
onTimeout: (err: Error) => raise(new Error(err.message)),
|
|
202
|
+
onError: (err: Error) => raise(new Error(err.message))
|
|
203
|
+
});
|
|
204
|
+
await success.wait({ timeout: 300 });
|
|
205
|
+
})
|
|
206
|
+
);
|
|
207
|
+
await done();
|
|
208
|
+
|
|
209
|
+
await hostObservable.cancel();
|
|
210
|
+
await hostSpace.close();
|
|
211
|
+
});
|
|
160
212
|
});
|
|
@@ -87,10 +87,9 @@ export class SpaceInvitationsHandler extends AbstractInvitationsHandler<DataSpac
|
|
|
87
87
|
let authenticationCode: string;
|
|
88
88
|
let authenticationRetry = 0;
|
|
89
89
|
|
|
90
|
-
const complete = new Trigger<PublicKey>();
|
|
91
|
-
|
|
92
90
|
// Called for every connecting peer.
|
|
93
91
|
const createExtension = (): HostSpaceInvitationExtension => {
|
|
92
|
+
const complete = new Trigger<PublicKey>();
|
|
94
93
|
let guestProfile: ProfileDocument | undefined;
|
|
95
94
|
|
|
96
95
|
const hostInvitationExtension = new HostSpaceInvitationExtension({
|
|
@@ -127,7 +126,10 @@ export class SpaceInvitationsHandler extends AbstractInvitationsHandler<DataSpac
|
|
|
127
126
|
requestAdmission: async ({ identityKey, deviceKey, controlFeedKey, dataFeedKey }) => {
|
|
128
127
|
try {
|
|
129
128
|
// Check authenticated.
|
|
130
|
-
if (
|
|
129
|
+
if (
|
|
130
|
+
invitation.type !== Invitation.Type.INTERACTIVE_TESTING &&
|
|
131
|
+
invitation.type !== Invitation.Type.MULTIUSE_TESTING
|
|
132
|
+
) {
|
|
131
133
|
if (invitation.authenticationCode === undefined || invitation.authenticationCode !== authenticationCode) {
|
|
132
134
|
throw new Error(`invalid authentication code: ${authenticationCode}`);
|
|
133
135
|
}
|
|
@@ -178,8 +180,10 @@ export class SpaceInvitationsHandler extends AbstractInvitationsHandler<DataSpac
|
|
|
178
180
|
observable.callback.onError(err);
|
|
179
181
|
}
|
|
180
182
|
} finally {
|
|
181
|
-
|
|
182
|
-
|
|
183
|
+
if (type !== Invitation.Type.MULTIUSE_TESTING) {
|
|
184
|
+
await sleep(ON_CLOSE_DELAY);
|
|
185
|
+
await ctx.dispose();
|
|
186
|
+
}
|
|
183
187
|
}
|
|
184
188
|
});
|
|
185
189
|
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2022 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { Stream } from '@dxos/codec-protobuf';
|
|
6
|
+
import { NetworkManager } from '@dxos/network-manager';
|
|
7
|
+
import { NetworkService, NetworkStatus, SetNetworkOptionsRequest } from '@dxos/protocols/proto/dxos/client/services';
|
|
8
|
+
|
|
9
|
+
export class NetworkServiceImpl implements NetworkService {
|
|
10
|
+
constructor(private readonly networkManager: NetworkManager) {}
|
|
11
|
+
|
|
12
|
+
subscribeToNetworkStatus() {
|
|
13
|
+
return new Stream<NetworkStatus>(({ next }) => {
|
|
14
|
+
const unsubscribe = this.networkManager.connectionStateChanged.on((state) => next({ state }));
|
|
15
|
+
next({ state: this.networkManager.connectionState });
|
|
16
|
+
|
|
17
|
+
return unsubscribe;
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async setNetworkOptions(request: SetNetworkOptionsRequest) {
|
|
22
|
+
await this.networkManager.setConnectionState(request.state);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -11,7 +11,8 @@ import {
|
|
|
11
11
|
DataServiceSubscriptions,
|
|
12
12
|
MetadataStore,
|
|
13
13
|
SpaceManager,
|
|
14
|
-
SigningContext
|
|
14
|
+
SigningContext,
|
|
15
|
+
SnapshotStore
|
|
15
16
|
} from '@dxos/echo-db';
|
|
16
17
|
import { FeedFactory, FeedStore } from '@dxos/feed-store';
|
|
17
18
|
import { Keyring } from '@dxos/keyring';
|
|
@@ -34,6 +35,7 @@ export class ServiceContext {
|
|
|
34
35
|
public readonly initialized = new Trigger();
|
|
35
36
|
public readonly dataServiceSubscriptions = new DataServiceSubscriptions();
|
|
36
37
|
public readonly metadataStore: MetadataStore;
|
|
38
|
+
public readonly snapshotStore: SnapshotStore;
|
|
37
39
|
public readonly feedStore: FeedStore<FeedMessage>;
|
|
38
40
|
public readonly keyring: Keyring;
|
|
39
41
|
public readonly spaceManager: SpaceManager;
|
|
@@ -52,6 +54,8 @@ export class ServiceContext {
|
|
|
52
54
|
) {
|
|
53
55
|
// TODO(burdon): Move strings to constants.
|
|
54
56
|
this.metadataStore = new MetadataStore(storage.createDirectory('metadata'));
|
|
57
|
+
this.snapshotStore = new SnapshotStore(storage.createDirectory('snapshots'));
|
|
58
|
+
|
|
55
59
|
this.keyring = new Keyring(storage.createDirectory('keyring'));
|
|
56
60
|
this.feedStore = new FeedStore<FeedMessage>({
|
|
57
61
|
factory: new FeedFactory<FeedMessage>({
|
|
@@ -79,6 +83,7 @@ export class ServiceContext {
|
|
|
79
83
|
|
|
80
84
|
async open() {
|
|
81
85
|
log('opening...');
|
|
86
|
+
await this.networkManager.open();
|
|
82
87
|
await this.spaceManager.open();
|
|
83
88
|
await this.identityManager.open();
|
|
84
89
|
if (this.identityManager.identity) {
|
|
@@ -114,6 +119,7 @@ export class ServiceContext {
|
|
|
114
119
|
|
|
115
120
|
// Called when identity is created.
|
|
116
121
|
private async _initialize() {
|
|
122
|
+
log('initializing spaces...');
|
|
117
123
|
const identity = this.identityManager.identity ?? failUndefined();
|
|
118
124
|
const signingContext: SigningContext = {
|
|
119
125
|
credentialProvider: MOCK_AUTH_PROVIDER,
|
|
@@ -130,7 +136,8 @@ export class ServiceContext {
|
|
|
130
136
|
this.dataServiceSubscriptions,
|
|
131
137
|
this.keyring,
|
|
132
138
|
signingContext,
|
|
133
|
-
this.modelFactory
|
|
139
|
+
this.modelFactory,
|
|
140
|
+
this.snapshotStore
|
|
134
141
|
);
|
|
135
142
|
await this.dataSpaceManager.open();
|
|
136
143
|
this.spaceInvitations = new SpaceInvitationsHandler(
|
|
@@ -8,7 +8,8 @@ import {
|
|
|
8
8
|
HaloInvitationsService,
|
|
9
9
|
SpaceInvitationsService,
|
|
10
10
|
SpacesService,
|
|
11
|
-
DevicesService
|
|
11
|
+
DevicesService,
|
|
12
|
+
NetworkService
|
|
12
13
|
} from '@dxos/protocols/proto/dxos/client/services';
|
|
13
14
|
import { DevtoolsHost, TracingService } from '@dxos/protocols/proto/dxos/devtools/host';
|
|
14
15
|
import { DataService } from '@dxos/protocols/proto/dxos/echo/service';
|
|
@@ -25,6 +26,8 @@ export type ClientServices = {
|
|
|
25
26
|
SpaceInvitationsService: SpaceInvitationsService;
|
|
26
27
|
SpacesService: SpacesService;
|
|
27
28
|
|
|
29
|
+
NetworkService: NetworkService;
|
|
30
|
+
|
|
28
31
|
// TODO(burdon): Deprecated.
|
|
29
32
|
SpaceService: SpaceService;
|
|
30
33
|
DataService: DataService;
|
|
@@ -53,6 +56,7 @@ export const clientServiceBundle = createServiceBundle<ClientServices>({
|
|
|
53
56
|
DevicesService: schema.getService('dxos.client.services.DevicesService'),
|
|
54
57
|
SpaceInvitationsService: schema.getService('dxos.client.services.SpaceInvitationsService'),
|
|
55
58
|
SpacesService: schema.getService('dxos.client.services.SpacesService'),
|
|
59
|
+
NetworkService: schema.getService('dxos.client.services.NetworkService'),
|
|
56
60
|
|
|
57
61
|
// TODO(burdon): Deprecated.
|
|
58
62
|
SpaceService: schema.getService('dxos.client.SpaceService'),
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { latch } from '@dxos/async';
|
|
6
|
+
import { Config } from '@dxos/config';
|
|
7
|
+
import { MemorySignalManagerContext } from '@dxos/messaging';
|
|
8
|
+
import { afterTest, describe, test } from '@dxos/test';
|
|
9
|
+
|
|
10
|
+
import { createServiceHost } from '../testing';
|
|
11
|
+
|
|
12
|
+
describe('ClientServicesHost', () => {
|
|
13
|
+
test('queryCredentials', async () => {
|
|
14
|
+
const host = createServiceHost(new Config(), new MemorySignalManagerContext());
|
|
15
|
+
await host.open();
|
|
16
|
+
afterTest(() => host.close());
|
|
17
|
+
|
|
18
|
+
await host.services.ProfileService.createProfile({});
|
|
19
|
+
const { publicKey: spaceKey } = await host.services.SpaceService.createSpace();
|
|
20
|
+
|
|
21
|
+
const stream = host.services.SpacesService.queryCredentials({ spaceKey });
|
|
22
|
+
const [done, tick] = latch({ count: 3 });
|
|
23
|
+
stream.subscribe((credential) => {
|
|
24
|
+
tick();
|
|
25
|
+
// console.log(credential);
|
|
26
|
+
});
|
|
27
|
+
afterTest(() => stream.close());
|
|
28
|
+
|
|
29
|
+
await done();
|
|
30
|
+
});
|
|
31
|
+
});
|
|
@@ -9,12 +9,14 @@ import { log } from '@dxos/log';
|
|
|
9
9
|
import { ModelFactory } from '@dxos/model-factory';
|
|
10
10
|
import { NetworkManager } from '@dxos/network-manager';
|
|
11
11
|
import { ObjectModel } from '@dxos/object-model';
|
|
12
|
+
import { Storage } from '@dxos/random-access-storage';
|
|
12
13
|
import { TextModel } from '@dxos/text-model';
|
|
13
14
|
|
|
14
15
|
import { SpaceServiceImpl, ProfileServiceImpl, SystemServiceImpl, TracingServiceImpl } from '../deprecated';
|
|
15
16
|
import { DevtoolsServiceImpl, DevtoolsHostEvents } from '../devtools';
|
|
16
17
|
import { DevicesServiceImpl } from '../identity/devices-service-impl';
|
|
17
18
|
import { HaloInvitationsServiceImpl, SpaceInvitationsServiceImpl } from '../invitations';
|
|
19
|
+
import { NetworkServiceImpl } from '../network';
|
|
18
20
|
import { SpacesServiceImpl } from '../spaces';
|
|
19
21
|
import { createStorageObjects } from '../storage';
|
|
20
22
|
import { ServiceContext } from './service-context';
|
|
@@ -31,6 +33,7 @@ type ClientServicesHostParams = {
|
|
|
31
33
|
config: Config;
|
|
32
34
|
modelFactory?: ModelFactory;
|
|
33
35
|
networkManager: NetworkManager;
|
|
36
|
+
storage?: Storage;
|
|
34
37
|
};
|
|
35
38
|
|
|
36
39
|
/**
|
|
@@ -43,16 +46,19 @@ export class ClientServicesHost implements ClientServicesProvider {
|
|
|
43
46
|
private readonly _config: Config;
|
|
44
47
|
private readonly _modelFactory: ModelFactory;
|
|
45
48
|
private readonly _networkManager: NetworkManager;
|
|
49
|
+
private _storage: Storage;
|
|
46
50
|
|
|
47
51
|
constructor({
|
|
48
52
|
config,
|
|
49
53
|
modelFactory = createDefaultModelFactory(),
|
|
50
54
|
// TODO(burdon): Create ApolloLink abstraction (see Client).
|
|
51
|
-
networkManager
|
|
55
|
+
networkManager,
|
|
56
|
+
storage = createStorageObjects(config.get('runtime.client.storage', {})!).storage
|
|
52
57
|
}: ClientServicesHostParams) {
|
|
53
58
|
this._config = config;
|
|
54
59
|
this._modelFactory = modelFactory;
|
|
55
60
|
this._networkManager = networkManager;
|
|
61
|
+
this._storage = storage;
|
|
56
62
|
}
|
|
57
63
|
|
|
58
64
|
get descriptors() {
|
|
@@ -64,11 +70,10 @@ export class ClientServicesHost implements ClientServicesProvider {
|
|
|
64
70
|
}
|
|
65
71
|
|
|
66
72
|
async open() {
|
|
67
|
-
|
|
68
|
-
const deviceKey = this._serviceContext.identityManager.identity?.deviceKey;
|
|
69
|
-
log('opening...', { deviceKey });
|
|
73
|
+
log('opening...');
|
|
70
74
|
await this._initialize();
|
|
71
75
|
await this._serviceContext.open();
|
|
76
|
+
const deviceKey = this._serviceContext.identityManager.identity?.deviceKey;
|
|
72
77
|
log('opened', { deviceKey });
|
|
73
78
|
}
|
|
74
79
|
|
|
@@ -81,11 +86,8 @@ export class ClientServicesHost implements ClientServicesProvider {
|
|
|
81
86
|
|
|
82
87
|
// TODO(burdon): Move into open.
|
|
83
88
|
async _initialize() {
|
|
84
|
-
// TODO(dmaretskyi): Remove keyStorage.
|
|
85
|
-
const { storage } = createStorageObjects(this._config.get('runtime.client.storage', {})!);
|
|
86
|
-
|
|
87
89
|
// TODO(burdon): Break into components.
|
|
88
|
-
this._serviceContext = new ServiceContext(
|
|
90
|
+
this._serviceContext = new ServiceContext(this._storage, this._networkManager, this._modelFactory);
|
|
89
91
|
|
|
90
92
|
// TODO(burdon): Start to think of DMG (dynamic services).
|
|
91
93
|
this._serviceRegistry = new ServiceRegistry<ClientServices>(clientServiceBundle, {
|
|
@@ -102,10 +104,12 @@ export class ClientServicesHost implements ClientServicesProvider {
|
|
|
102
104
|
() => this._serviceContext.dataSpaceManager ?? raise(new Error('SpaceManager not initialized'))
|
|
103
105
|
),
|
|
104
106
|
|
|
105
|
-
SpacesService: new SpacesServiceImpl(),
|
|
107
|
+
SpacesService: new SpacesServiceImpl(this._serviceContext.spaceManager),
|
|
106
108
|
|
|
107
109
|
DataService: new DataServiceImpl(this._serviceContext.dataServiceSubscriptions),
|
|
108
110
|
|
|
111
|
+
NetworkService: new NetworkServiceImpl(this._serviceContext.networkManager),
|
|
112
|
+
|
|
109
113
|
// TODO(burdon): Move to new protobuf definitions.
|
|
110
114
|
ProfileService: new ProfileServiceImpl(this._serviceContext),
|
|
111
115
|
SpaceService: new SpaceServiceImpl(this._serviceContext),
|
|
@@ -2,15 +2,18 @@
|
|
|
2
2
|
// Copyright 2022 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import { Event, synchronized } from '@dxos/async';
|
|
5
|
+
import { Event, synchronized, trackLeaks } from '@dxos/async';
|
|
6
|
+
import { Context } from '@dxos/context';
|
|
6
7
|
import {
|
|
7
8
|
AcceptSpaceOptions,
|
|
8
9
|
DataServiceSubscriptions,
|
|
9
10
|
MetadataStore,
|
|
10
11
|
SigningContext,
|
|
12
|
+
SnapshotStore,
|
|
11
13
|
Space,
|
|
14
|
+
spaceGenesis,
|
|
12
15
|
SpaceManager,
|
|
13
|
-
|
|
16
|
+
SnapshotManager
|
|
14
17
|
} from '@dxos/echo-db';
|
|
15
18
|
import { Keyring } from '@dxos/keyring';
|
|
16
19
|
import { PublicKey } from '@dxos/keys';
|
|
@@ -22,7 +25,10 @@ import { ComplexMap } from '@dxos/util';
|
|
|
22
25
|
|
|
23
26
|
import { DataSpace } from './data-space';
|
|
24
27
|
|
|
28
|
+
@trackLeaks('open', 'close')
|
|
25
29
|
export class DataSpaceManager {
|
|
30
|
+
private readonly _ctx = new Context();
|
|
31
|
+
|
|
26
32
|
public readonly updated = new Event();
|
|
27
33
|
|
|
28
34
|
private readonly _spaces = new ComplexMap<PublicKey, DataSpace>(PublicKey.hash);
|
|
@@ -33,7 +39,8 @@ export class DataSpaceManager {
|
|
|
33
39
|
private readonly _dataServiceSubscriptions: DataServiceSubscriptions,
|
|
34
40
|
private readonly _keyring: Keyring,
|
|
35
41
|
private readonly _signingContext: SigningContext,
|
|
36
|
-
private readonly _modelFactory: ModelFactory
|
|
42
|
+
private readonly _modelFactory: ModelFactory,
|
|
43
|
+
private readonly _snapshotStore: SnapshotStore
|
|
37
44
|
) {}
|
|
38
45
|
|
|
39
46
|
// TODO(burdon): Remove.
|
|
@@ -44,18 +51,30 @@ export class DataSpaceManager {
|
|
|
44
51
|
@synchronized
|
|
45
52
|
async open() {
|
|
46
53
|
await this._metadataStore.load();
|
|
54
|
+
log('metadata loaded', { spaces: this._metadataStore.spaces.length });
|
|
47
55
|
|
|
48
56
|
for (const spaceMetadata of this._metadataStore.spaces) {
|
|
49
|
-
|
|
57
|
+
log('load space', { spaceMetadata });
|
|
58
|
+
const space = await this._constructSpace(spaceMetadata);
|
|
59
|
+
if (spaceMetadata.latestTimeframe) {
|
|
60
|
+
log('waiting for latest timeframe', { spaceMetadata });
|
|
61
|
+
await space.dataPipelineController.waitUntilTimeframe(spaceMetadata.latestTimeframe);
|
|
62
|
+
}
|
|
50
63
|
}
|
|
51
64
|
}
|
|
52
65
|
|
|
53
66
|
@synchronized
|
|
54
|
-
async close() {
|
|
67
|
+
async close() {
|
|
68
|
+
await this._ctx.dispose();
|
|
69
|
+
for (const space of this._spaces.values()) {
|
|
70
|
+
await space.close();
|
|
71
|
+
}
|
|
72
|
+
}
|
|
55
73
|
|
|
56
74
|
/**
|
|
57
75
|
* Creates a new space writing the genesis credentials to the control feed.
|
|
58
76
|
*/
|
|
77
|
+
@synchronized
|
|
59
78
|
async createSpace() {
|
|
60
79
|
const spaceKey = await this._keyring.createKey();
|
|
61
80
|
const controlFeedKey = await this._keyring.createKey();
|
|
@@ -78,6 +97,7 @@ export class DataSpaceManager {
|
|
|
78
97
|
}
|
|
79
98
|
|
|
80
99
|
// TODO(burdon): Rename join space.
|
|
100
|
+
@synchronized
|
|
81
101
|
async acceptSpace(opts: AcceptSpaceOptions): Promise<DataSpace> {
|
|
82
102
|
const metadata: SpaceMetadata = {
|
|
83
103
|
key: opts.spaceKey,
|
|
@@ -99,6 +119,8 @@ export class DataSpaceManager {
|
|
|
99
119
|
offlineTimeout: 30_000,
|
|
100
120
|
identityKey: this._signingContext.identityKey
|
|
101
121
|
});
|
|
122
|
+
const snapshotManager = new SnapshotManager(this._snapshotStore);
|
|
123
|
+
|
|
102
124
|
const space: Space = await this._spaceManager.constructSpace({
|
|
103
125
|
metadata,
|
|
104
126
|
swarmIdentity: {
|
|
@@ -106,11 +128,26 @@ export class DataSpaceManager {
|
|
|
106
128
|
credentialProvider: this._signingContext.credentialProvider,
|
|
107
129
|
credentialAuthenticator: this._signingContext.credentialAuthenticator
|
|
108
130
|
},
|
|
109
|
-
|
|
110
|
-
|
|
131
|
+
onNetworkConnection: (session) => {
|
|
132
|
+
session.addExtension(
|
|
133
|
+
'dxos.mesh.teleport.presence',
|
|
134
|
+
presence.createExtension({ remotePeerId: session.remotePeerId })
|
|
135
|
+
);
|
|
136
|
+
session.addExtension('dxos.mesh.teleport.objectsync', snapshotManager.objectSync.createExtension());
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
const dataSpace = new DataSpace({
|
|
141
|
+
inner: space,
|
|
142
|
+
modelFactory: this._modelFactory,
|
|
143
|
+
metadataStore: this._metadataStore,
|
|
144
|
+
snapshotManager,
|
|
145
|
+
presence,
|
|
146
|
+
memberKey: this._signingContext.identityKey,
|
|
147
|
+
snapshotId: metadata.snapshot
|
|
111
148
|
});
|
|
112
|
-
|
|
113
|
-
await
|
|
149
|
+
|
|
150
|
+
await dataSpace.open();
|
|
114
151
|
this._dataServiceSubscriptions.registerSpace(space.key, dataSpace.database.createDataServiceHost());
|
|
115
152
|
this._spaces.set(metadata.key, dataSpace);
|
|
116
153
|
return dataSpace;
|