@dxos/client-services 0.5.9-main.4ed8273 → 0.5.9-main.72c50cd
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-YJ5SPRLY.mjs → chunk-G25WUL6O.mjs} +589 -731
- package/dist/lib/browser/chunk-G25WUL6O.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +1 -1
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/packlets/testing/index.mjs +9 -10
- package/dist/lib/browser/packlets/testing/index.mjs.map +3 -3
- package/dist/lib/node/{chunk-RQHBQWKH.cjs → chunk-FTGPY2GQ.cjs} +809 -947
- package/dist/lib/node/chunk-FTGPY2GQ.cjs.map +7 -0
- package/dist/lib/node/index.cjs +42 -42
- package/dist/lib/node/meta.json +1 -1
- package/dist/lib/node/packlets/testing/index.cjs +15 -16
- package/dist/lib/node/packlets/testing/index.cjs.map +3 -3
- package/dist/types/src/packlets/identity/identity-service.d.ts +7 -14
- package/dist/types/src/packlets/identity/identity-service.d.ts.map +1 -1
- package/dist/types/src/packlets/identity/identity.d.ts +1 -4
- package/dist/types/src/packlets/identity/identity.d.ts.map +1 -1
- package/dist/types/src/packlets/services/service-host.d.ts +1 -1
- package/dist/types/src/packlets/services/service-host.d.ts.map +1 -1
- package/dist/types/src/packlets/spaces/data-space-manager.d.ts +3 -5
- package/dist/types/src/packlets/spaces/data-space-manager.d.ts.map +1 -1
- package/dist/types/src/packlets/spaces/data-space.d.ts.map +1 -1
- package/dist/types/src/packlets/testing/test-builder.d.ts +6 -8
- package/dist/types/src/packlets/testing/test-builder.d.ts.map +1 -1
- package/dist/types/src/version.d.ts +1 -1
- package/package.json +36 -36
- package/src/packlets/identity/identity-service.test.ts +5 -35
- package/src/packlets/identity/identity-service.ts +8 -50
- package/src/packlets/identity/identity.ts +2 -25
- package/src/packlets/services/service-host.ts +40 -13
- package/src/packlets/spaces/data-space-manager.test.ts +1 -46
- package/src/packlets/spaces/data-space-manager.ts +25 -54
- package/src/packlets/spaces/data-space.ts +5 -10
- package/src/packlets/testing/test-builder.ts +6 -11
- package/src/version.ts +1 -1
- package/dist/lib/browser/chunk-YJ5SPRLY.mjs.map +0 -7
- package/dist/lib/node/chunk-RQHBQWKH.cjs.map +0 -7
- package/dist/types/src/packlets/identity/default-space-state-machine.d.ts +0 -19
- package/dist/types/src/packlets/identity/default-space-state-machine.d.ts.map +0 -1
- package/src/packlets/identity/default-space-state-machine.ts +0 -44
|
@@ -24,7 +24,11 @@ describe('IdentityService', () => {
|
|
|
24
24
|
beforeEach(async () => {
|
|
25
25
|
serviceContext = await createServiceContext();
|
|
26
26
|
await serviceContext.open(new Context());
|
|
27
|
-
identityService =
|
|
27
|
+
identityService = new IdentityServiceImpl(
|
|
28
|
+
(options) => serviceContext.createIdentity(options),
|
|
29
|
+
serviceContext.identityManager,
|
|
30
|
+
serviceContext.keyring,
|
|
31
|
+
);
|
|
28
32
|
});
|
|
29
33
|
|
|
30
34
|
afterEach(async () => {
|
|
@@ -91,37 +95,3 @@ describe('IdentityService', () => {
|
|
|
91
95
|
});
|
|
92
96
|
});
|
|
93
97
|
});
|
|
94
|
-
|
|
95
|
-
describe('open', () => {
|
|
96
|
-
test('identity without default space fixed', async () => {
|
|
97
|
-
const serviceContext = await createServiceContext();
|
|
98
|
-
await serviceContext.open(new Context());
|
|
99
|
-
const identity = await serviceContext.createIdentity();
|
|
100
|
-
const identityService = createIdentityService(serviceContext);
|
|
101
|
-
const getDataSpaces = () => [...(serviceContext.dataSpaceManager?.spaces?.values() ?? [])];
|
|
102
|
-
expect(getDataSpaces().length).to.eq(0);
|
|
103
|
-
expect(identity.defaultSpaceId).to.be.undefined;
|
|
104
|
-
await identityService.open();
|
|
105
|
-
expect(getDataSpaces()[0].id === identity.defaultSpaceId).to.be.true;
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
test('identity without default space credential fixed', async () => {
|
|
109
|
-
const serviceContext = await createServiceContext();
|
|
110
|
-
await serviceContext.open(new Context());
|
|
111
|
-
const identity = await serviceContext.createIdentity();
|
|
112
|
-
const space = await serviceContext.dataSpaceManager!.createDefaultSpace();
|
|
113
|
-
const identityService = createIdentityService(serviceContext);
|
|
114
|
-
expect(identity.defaultSpaceId).to.be.undefined;
|
|
115
|
-
await identityService.open();
|
|
116
|
-
expect(identity.defaultSpaceId === space.id).to.be.true;
|
|
117
|
-
});
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
const createIdentityService = (serviceContext: ServiceContext) => {
|
|
121
|
-
return new IdentityServiceImpl(
|
|
122
|
-
serviceContext.identityManager,
|
|
123
|
-
serviceContext.keyring,
|
|
124
|
-
() => serviceContext.dataSpaceManager!,
|
|
125
|
-
(options) => serviceContext.createIdentity(options),
|
|
126
|
-
);
|
|
127
|
-
};
|
|
@@ -3,59 +3,36 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import { Stream } from '@dxos/codec-protobuf';
|
|
6
|
-
import { Resource } from '@dxos/context';
|
|
7
6
|
import { signPresentation } from '@dxos/credentials';
|
|
8
7
|
import { todo } from '@dxos/debug';
|
|
9
8
|
import { invariant } from '@dxos/invariant';
|
|
10
9
|
import { type Keyring } from '@dxos/keyring';
|
|
11
10
|
import {
|
|
12
11
|
type CreateIdentityRequest,
|
|
13
|
-
type Identity
|
|
12
|
+
type Identity,
|
|
14
13
|
type IdentityService,
|
|
15
14
|
type QueryIdentityResponse,
|
|
16
15
|
type RecoverIdentityRequest,
|
|
17
16
|
type SignPresentationRequest,
|
|
18
|
-
SpaceState,
|
|
19
17
|
} from '@dxos/protocols/proto/dxos/client/services';
|
|
20
18
|
import { type Presentation, type ProfileDocument } from '@dxos/protocols/proto/dxos/halo/credentials';
|
|
21
19
|
|
|
22
|
-
import { type Identity } from './identity';
|
|
23
20
|
import { type CreateIdentityOptions, type IdentityManager } from './identity-manager';
|
|
24
|
-
import { type DataSpaceManager } from '../spaces';
|
|
25
21
|
|
|
26
|
-
export class IdentityServiceImpl
|
|
22
|
+
export class IdentityServiceImpl implements IdentityService {
|
|
27
23
|
constructor(
|
|
24
|
+
private readonly _createIdentity: (params: CreateIdentityOptions) => Promise<Identity>,
|
|
28
25
|
private readonly _identityManager: IdentityManager,
|
|
29
26
|
private readonly _keyring: Keyring,
|
|
30
|
-
private readonly _dataSpaceManagerProvider: () => DataSpaceManager,
|
|
31
|
-
private readonly _createIdentity: (params: CreateIdentityOptions) => Promise<Identity>,
|
|
32
27
|
private readonly _onProfileUpdate?: (profile: ProfileDocument | undefined) => Promise<void>,
|
|
33
|
-
) {
|
|
34
|
-
super();
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
protected override async _open() {
|
|
38
|
-
const identity = this._identityManager.identity;
|
|
39
|
-
if (identity && !identity.defaultSpaceId) {
|
|
40
|
-
await this._fixIdentityWithoutDefaultSpace(identity);
|
|
41
|
-
}
|
|
42
|
-
}
|
|
28
|
+
) {}
|
|
43
29
|
|
|
44
|
-
async createIdentity(request: CreateIdentityRequest): Promise<
|
|
30
|
+
async createIdentity(request: CreateIdentityRequest): Promise<Identity> {
|
|
45
31
|
await this._createIdentity({ displayName: request.profile?.displayName, deviceProfile: request.deviceProfile });
|
|
46
|
-
const dataSpaceManager = this._dataSpaceManagerProvider();
|
|
47
|
-
await this._createDefaultSpace(dataSpaceManager);
|
|
48
32
|
return this._getIdentity()!;
|
|
49
33
|
}
|
|
50
34
|
|
|
51
|
-
|
|
52
|
-
const space = await dataSpaceManager!.createDefaultSpace();
|
|
53
|
-
const identity = this._identityManager.identity;
|
|
54
|
-
invariant(identity);
|
|
55
|
-
await identity.updateDefaultSpace(space.id);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
async recoverIdentity(request: RecoverIdentityRequest): Promise<IdentityProto> {
|
|
35
|
+
async recoverIdentity(request: RecoverIdentityRequest): Promise<Identity> {
|
|
59
36
|
return todo();
|
|
60
37
|
}
|
|
61
38
|
|
|
@@ -68,7 +45,7 @@ export class IdentityServiceImpl extends Resource implements IdentityService {
|
|
|
68
45
|
});
|
|
69
46
|
}
|
|
70
47
|
|
|
71
|
-
private _getIdentity():
|
|
48
|
+
private _getIdentity(): Identity | undefined {
|
|
72
49
|
if (!this._identityManager.identity) {
|
|
73
50
|
return undefined;
|
|
74
51
|
}
|
|
@@ -80,7 +57,7 @@ export class IdentityServiceImpl extends Resource implements IdentityService {
|
|
|
80
57
|
};
|
|
81
58
|
}
|
|
82
59
|
|
|
83
|
-
async updateProfile(profile: ProfileDocument): Promise<
|
|
60
|
+
async updateProfile(profile: ProfileDocument): Promise<Identity> {
|
|
84
61
|
invariant(this._identityManager.identity, 'Identity not initialized.');
|
|
85
62
|
await this._identityManager.updateProfile(profile);
|
|
86
63
|
await this._onProfileUpdate?.(this._identityManager.identity.profileDocument);
|
|
@@ -98,23 +75,4 @@ export class IdentityServiceImpl extends Resource implements IdentityService {
|
|
|
98
75
|
nonce,
|
|
99
76
|
});
|
|
100
77
|
}
|
|
101
|
-
|
|
102
|
-
private async _fixIdentityWithoutDefaultSpace(identity: Identity) {
|
|
103
|
-
let hasDefaultSpace = false;
|
|
104
|
-
const dataSpaceManager = this._dataSpaceManagerProvider();
|
|
105
|
-
for (const space of dataSpaceManager.spaces.values()) {
|
|
106
|
-
if (space.state === SpaceState.CLOSED) {
|
|
107
|
-
await space.open();
|
|
108
|
-
await space.initializeDataPipeline();
|
|
109
|
-
}
|
|
110
|
-
if (await dataSpaceManager.isDefaultSpace(space)) {
|
|
111
|
-
await identity.updateDefaultSpace(space.id);
|
|
112
|
-
hasDefaultSpace = true;
|
|
113
|
-
break;
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
if (!hasDefaultSpace) {
|
|
117
|
-
await this._createDefaultSpace(dataSpaceManager);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
78
|
}
|
|
@@ -16,7 +16,7 @@ import { type Signer } from '@dxos/crypto';
|
|
|
16
16
|
import { type Space } from '@dxos/echo-pipeline';
|
|
17
17
|
import { writeMessages } from '@dxos/feed-store';
|
|
18
18
|
import { invariant } from '@dxos/invariant';
|
|
19
|
-
import { PublicKey
|
|
19
|
+
import { PublicKey } from '@dxos/keys';
|
|
20
20
|
import { log } from '@dxos/log';
|
|
21
21
|
import { type FeedMessage } from '@dxos/protocols/proto/dxos/echo/feed';
|
|
22
22
|
import {
|
|
@@ -26,12 +26,10 @@ import {
|
|
|
26
26
|
} from '@dxos/protocols/proto/dxos/halo/credentials';
|
|
27
27
|
import { type DeviceAdmissionRequest } from '@dxos/protocols/proto/dxos/halo/invitations';
|
|
28
28
|
import { type Presence } from '@dxos/teleport-extension-gossip';
|
|
29
|
-
import { Timeframe } from '@dxos/timeframe';
|
|
30
29
|
import { trace } from '@dxos/tracing';
|
|
31
30
|
import { type ComplexMap, ComplexSet } from '@dxos/util';
|
|
32
31
|
|
|
33
32
|
import { TrustedKeySetAuthVerifier } from './authenticator';
|
|
34
|
-
import { DefaultSpaceStateMachine } from './default-space-state-machine';
|
|
35
33
|
|
|
36
34
|
export type IdentityParams = {
|
|
37
35
|
identityKey: PublicKey;
|
|
@@ -51,7 +49,6 @@ export class Identity {
|
|
|
51
49
|
private readonly _presence?: Presence;
|
|
52
50
|
private readonly _deviceStateMachine: DeviceStateMachine;
|
|
53
51
|
private readonly _profileStateMachine: ProfileStateMachine;
|
|
54
|
-
private readonly _defaultSpaceStateMachine: DefaultSpaceStateMachine;
|
|
55
52
|
public readonly authVerifier: TrustedKeySetAuthVerifier;
|
|
56
53
|
|
|
57
54
|
public readonly identityKey: PublicKey;
|
|
@@ -78,10 +75,6 @@ export class Identity {
|
|
|
78
75
|
identityKey: this.identityKey,
|
|
79
76
|
onUpdate: () => this.stateUpdate.emit(),
|
|
80
77
|
});
|
|
81
|
-
this._defaultSpaceStateMachine = new DefaultSpaceStateMachine({
|
|
82
|
-
identityKey: this.identityKey,
|
|
83
|
-
onUpdate: () => this.stateUpdate.emit(),
|
|
84
|
-
});
|
|
85
78
|
|
|
86
79
|
this.authVerifier = new TrustedKeySetAuthVerifier({
|
|
87
80
|
trustedKeysProvider: () => new ComplexSet(PublicKey.hash, this.authorizedDeviceKeys.keys()),
|
|
@@ -95,24 +88,17 @@ export class Identity {
|
|
|
95
88
|
return this._deviceStateMachine.authorizedDeviceKeys;
|
|
96
89
|
}
|
|
97
90
|
|
|
98
|
-
get defaultSpaceId(): SpaceId | undefined {
|
|
99
|
-
return this._defaultSpaceStateMachine.spaceId;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
91
|
@trace.span()
|
|
103
92
|
async open(ctx: Context) {
|
|
104
|
-
await this._presence?.open();
|
|
105
93
|
await this.space.spaceState.addCredentialProcessor(this._deviceStateMachine);
|
|
106
94
|
await this.space.spaceState.addCredentialProcessor(this._profileStateMachine);
|
|
107
|
-
await this.space.spaceState.addCredentialProcessor(this._defaultSpaceStateMachine);
|
|
108
95
|
await this.space.open(ctx);
|
|
109
96
|
}
|
|
110
97
|
|
|
111
98
|
@trace.span()
|
|
112
99
|
async close(ctx: Context) {
|
|
113
|
-
await this._presence?.
|
|
100
|
+
await this._presence?.destroy();
|
|
114
101
|
await this.authVerifier.close();
|
|
115
|
-
await this.space.spaceState.removeCredentialProcessor(this._defaultSpaceStateMachine);
|
|
116
102
|
await this.space.spaceState.removeCredentialProcessor(this._profileStateMachine);
|
|
117
103
|
await this.space.spaceState.removeCredentialProcessor(this._deviceStateMachine);
|
|
118
104
|
await this.space.close();
|
|
@@ -171,15 +157,6 @@ export class Identity {
|
|
|
171
157
|
return createCredentialSignerWithKey(this._signer, this.deviceKey);
|
|
172
158
|
}
|
|
173
159
|
|
|
174
|
-
async updateDefaultSpace(spaceId: SpaceId) {
|
|
175
|
-
const credential = await this.getDeviceCredentialSigner().createCredential({
|
|
176
|
-
subject: this.identityKey,
|
|
177
|
-
assertion: { '@type': 'dxos.halo.credentials.DefaultSpace', spaceId },
|
|
178
|
-
});
|
|
179
|
-
const receipt = await this.controlPipeline.writer.write({ credential: { credential } });
|
|
180
|
-
await this.controlPipeline.state.waitUntilTimeframe(new Timeframe([[receipt.feedKey, receipt.seq]]));
|
|
181
|
-
}
|
|
182
|
-
|
|
183
160
|
async admitDevice({ deviceKey, controlFeedKey, dataFeedKey }: DeviceAdmissionRequest) {
|
|
184
161
|
log('Admitting device:', {
|
|
185
162
|
identityKey: this.identityKey,
|
|
@@ -3,9 +3,11 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import { Event, synchronized } from '@dxos/async';
|
|
6
|
-
import { clientServiceBundle, type ClientServices } from '@dxos/client-protocol';
|
|
6
|
+
import { clientServiceBundle, defaultKey, type ClientServices, PropertiesType } from '@dxos/client-protocol';
|
|
7
7
|
import { type Config } from '@dxos/config';
|
|
8
8
|
import { Context } from '@dxos/context';
|
|
9
|
+
import { type ObjectStructure, encodeReference, type SpaceDoc } from '@dxos/echo-protocol';
|
|
10
|
+
import { getTypeReference } from '@dxos/echo-schema';
|
|
9
11
|
import { invariant } from '@dxos/invariant';
|
|
10
12
|
import { PublicKey } from '@dxos/keys';
|
|
11
13
|
import { type LevelDB } from '@dxos/kv-store';
|
|
@@ -16,6 +18,7 @@ import { trace } from '@dxos/protocols';
|
|
|
16
18
|
import { SystemStatus } from '@dxos/protocols/proto/dxos/client/services';
|
|
17
19
|
import { type Storage } from '@dxos/random-access-storage';
|
|
18
20
|
import { TRACE_PROCESSOR, trace as Trace } from '@dxos/tracing';
|
|
21
|
+
import { assignDeep } from '@dxos/util';
|
|
19
22
|
import { WebsocketRpcClient } from '@dxos/websocket-rpc';
|
|
20
23
|
|
|
21
24
|
import { ServiceContext, type ServiceContextRuntimeParams } from './service-context';
|
|
@@ -83,7 +86,7 @@ export class ClientServicesHost {
|
|
|
83
86
|
private _devtoolsProxy?: WebsocketRpcClient<{}, ClientServices>;
|
|
84
87
|
|
|
85
88
|
private _serviceContext!: ServiceContext;
|
|
86
|
-
private readonly _runtimeParams
|
|
89
|
+
private readonly _runtimeParams?: ServiceContextRuntimeParams;
|
|
87
90
|
private diagnosticsBroadcastHandler: CollectDiagnosticsBroadcastHandler;
|
|
88
91
|
|
|
89
92
|
@Trace.info()
|
|
@@ -106,7 +109,7 @@ export class ClientServicesHost {
|
|
|
106
109
|
this._storage = storage;
|
|
107
110
|
this._level = level;
|
|
108
111
|
this._callbacks = callbacks;
|
|
109
|
-
this._runtimeParams = runtimeParams
|
|
112
|
+
this._runtimeParams = runtimeParams;
|
|
110
113
|
|
|
111
114
|
if (config) {
|
|
112
115
|
this.initialize({ config, transportFactory, signalManager });
|
|
@@ -251,17 +254,15 @@ export class ClientServicesHost {
|
|
|
251
254
|
this._runtimeParams,
|
|
252
255
|
);
|
|
253
256
|
|
|
254
|
-
const identityService = new IdentityServiceImpl(
|
|
255
|
-
this._serviceContext.identityManager,
|
|
256
|
-
this._serviceContext.keyring,
|
|
257
|
-
() => this._serviceContext.dataSpaceManager!,
|
|
258
|
-
(params) => this._createIdentity(params),
|
|
259
|
-
(profile) => this._serviceContext.broadcastProfileUpdate(profile),
|
|
260
|
-
);
|
|
261
|
-
|
|
262
257
|
this._serviceRegistry.setServices({
|
|
263
258
|
SystemService: this._systemService,
|
|
264
|
-
|
|
259
|
+
|
|
260
|
+
IdentityService: new IdentityServiceImpl(
|
|
261
|
+
(params) => this._createIdentity(params),
|
|
262
|
+
this._serviceContext.identityManager,
|
|
263
|
+
this._serviceContext.keyring,
|
|
264
|
+
(profile) => this._serviceContext.broadcastProfileUpdate(profile),
|
|
265
|
+
),
|
|
265
266
|
|
|
266
267
|
InvitationsService: new InvitationsServiceImpl(this._serviceContext.invitationsManager),
|
|
267
268
|
|
|
@@ -293,7 +294,6 @@ export class ClientServicesHost {
|
|
|
293
294
|
});
|
|
294
295
|
|
|
295
296
|
await this._serviceContext.open(ctx);
|
|
296
|
-
await identityService.open();
|
|
297
297
|
|
|
298
298
|
const devtoolsProxy = this._config?.get('runtime.client.devtoolsProxy');
|
|
299
299
|
if (devtoolsProxy) {
|
|
@@ -349,7 +349,34 @@ export class ClientServicesHost {
|
|
|
349
349
|
|
|
350
350
|
private async _createIdentity(params: CreateIdentityOptions) {
|
|
351
351
|
const identity = await this._serviceContext.createIdentity(params);
|
|
352
|
+
|
|
353
|
+
// Setup default space.
|
|
352
354
|
await this._serviceContext.initialized.wait();
|
|
355
|
+
const space = await this._serviceContext.dataSpaceManager!.createSpace();
|
|
356
|
+
|
|
357
|
+
const automergeIndex = space.automergeSpaceState.rootUrl;
|
|
358
|
+
invariant(automergeIndex);
|
|
359
|
+
const document = this._serviceContext.echoHost.automergeRepo.find<SpaceDoc>(automergeIndex as any);
|
|
360
|
+
await document.whenReady();
|
|
361
|
+
|
|
362
|
+
// TODO(dmaretskyi): Better API for low-level data access.
|
|
363
|
+
const properties: ObjectStructure = {
|
|
364
|
+
system: {
|
|
365
|
+
type: encodeReference(getTypeReference(PropertiesType)!),
|
|
366
|
+
},
|
|
367
|
+
data: {
|
|
368
|
+
[defaultKey]: identity.identityKey.toHex(),
|
|
369
|
+
},
|
|
370
|
+
meta: {
|
|
371
|
+
keys: [],
|
|
372
|
+
},
|
|
373
|
+
};
|
|
374
|
+
const propertiesId = PublicKey.random().toHex();
|
|
375
|
+
document.change((doc: SpaceDoc) => {
|
|
376
|
+
assignDeep(doc, ['objects', propertiesId], properties);
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
await this._serviceContext.echoHost.flush();
|
|
353
380
|
return identity;
|
|
354
381
|
}
|
|
355
382
|
}
|
|
@@ -12,7 +12,7 @@ import { log } from '@dxos/log';
|
|
|
12
12
|
import { SpaceState } from '@dxos/protocols/proto/dxos/client/services';
|
|
13
13
|
import { describe, openAndClose, test } from '@dxos/test';
|
|
14
14
|
|
|
15
|
-
import { TestBuilder
|
|
15
|
+
import { TestBuilder } from '../testing';
|
|
16
16
|
|
|
17
17
|
describe('DataSpaceManager', () => {
|
|
18
18
|
test('create space', async () => {
|
|
@@ -168,50 +168,5 @@ describe('DataSpaceManager', () => {
|
|
|
168
168
|
500,
|
|
169
169
|
);
|
|
170
170
|
});
|
|
171
|
-
|
|
172
|
-
test('activate opens a lazily loaded space', async () => {
|
|
173
|
-
const builder = new TestBuilder();
|
|
174
|
-
|
|
175
|
-
const peer = builder.createPeer();
|
|
176
|
-
await peer.createIdentity();
|
|
177
|
-
await openAndClose(peer.echoHost, peer.dataSpaceManager);
|
|
178
|
-
|
|
179
|
-
await peer.dataSpaceManager.createSpace();
|
|
180
|
-
await reloadDataSpaces(peer);
|
|
181
|
-
|
|
182
|
-
const space = getFirstSpace(peer);
|
|
183
|
-
expect(space.state).to.equal(SpaceState.CLOSED);
|
|
184
|
-
await space.activate();
|
|
185
|
-
await asyncTimeout(
|
|
186
|
-
space.stateUpdate.waitForCondition(() => space.state === SpaceState.READY),
|
|
187
|
-
500,
|
|
188
|
-
);
|
|
189
|
-
});
|
|
190
|
-
|
|
191
|
-
test('deactivate lazily loaded space ', async () => {
|
|
192
|
-
const builder = new TestBuilder();
|
|
193
|
-
|
|
194
|
-
const peer = builder.createPeer();
|
|
195
|
-
await peer.createIdentity();
|
|
196
|
-
await openAndClose(peer.echoHost, peer.dataSpaceManager);
|
|
197
|
-
|
|
198
|
-
await peer.dataSpaceManager.createSpace();
|
|
199
|
-
await reloadDataSpaces(peer);
|
|
200
|
-
|
|
201
|
-
await getFirstSpace(peer).deactivate();
|
|
202
|
-
|
|
203
|
-
await reloadDataSpaces(peer);
|
|
204
|
-
|
|
205
|
-
expect(getFirstSpace(peer).state).to.eq(SpaceState.INACTIVE);
|
|
206
|
-
});
|
|
207
171
|
});
|
|
208
|
-
|
|
209
|
-
const reloadDataSpaces = async (peer: TestPeer) => {
|
|
210
|
-
await peer.dataSpaceManager.close();
|
|
211
|
-
await peer.dataSpaceManager.open();
|
|
212
|
-
};
|
|
213
|
-
|
|
214
|
-
const getFirstSpace = (peer: TestPeer) => {
|
|
215
|
-
return Array.from(peer.dataSpaceManager.spaces.values())[0];
|
|
216
|
-
};
|
|
217
172
|
});
|
|
@@ -4,8 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import { Event, synchronized, trackLeaks } from '@dxos/async';
|
|
6
6
|
import { type Doc } from '@dxos/automerge/automerge';
|
|
7
|
-
import { type
|
|
8
|
-
import { PropertiesType } from '@dxos/client-protocol';
|
|
7
|
+
import { type AutomergeUrl } from '@dxos/automerge/automerge-repo';
|
|
9
8
|
import { cancelWithContext, Context } from '@dxos/context';
|
|
10
9
|
import {
|
|
11
10
|
type CredentialSigner,
|
|
@@ -22,8 +21,7 @@ import {
|
|
|
22
21
|
type SpaceProtocol,
|
|
23
22
|
type SpaceProtocolSession,
|
|
24
23
|
} from '@dxos/echo-pipeline';
|
|
25
|
-
import {
|
|
26
|
-
import { getTypeReference } from '@dxos/echo-schema';
|
|
24
|
+
import { type SpaceDoc } from '@dxos/echo-protocol';
|
|
27
25
|
import { type FeedStore } from '@dxos/feed-store';
|
|
28
26
|
import { invariant } from '@dxos/invariant';
|
|
29
27
|
import { type Keyring } from '@dxos/keyring';
|
|
@@ -39,7 +37,7 @@ import { type PeerState } from '@dxos/protocols/proto/dxos/mesh/presence';
|
|
|
39
37
|
import { Gossip, Presence } from '@dxos/teleport-extension-gossip';
|
|
40
38
|
import { type Timeframe } from '@dxos/timeframe';
|
|
41
39
|
import { trace } from '@dxos/tracing';
|
|
42
|
-
import {
|
|
40
|
+
import { ComplexMap, deferFunction, forEachAsync } from '@dxos/util';
|
|
43
41
|
|
|
44
42
|
import { DataSpace, findPropertiesObject } from './data-space';
|
|
45
43
|
import { spaceGenesis } from './genesis';
|
|
@@ -49,9 +47,6 @@ import { type InvitationsManager } from '../invitations';
|
|
|
49
47
|
const PRESENCE_ANNOUNCE_INTERVAL = 10_000;
|
|
50
48
|
const PRESENCE_OFFLINE_TIMEOUT = 20_000;
|
|
51
49
|
|
|
52
|
-
// Space properties key for default metadata.
|
|
53
|
-
const DEFAULT_SPACE_KEY = '__DEFAULT__';
|
|
54
|
-
|
|
55
50
|
export interface SigningContext {
|
|
56
51
|
identityKey: PublicKey;
|
|
57
52
|
deviceKey: PublicKey;
|
|
@@ -93,6 +88,8 @@ export class DataSpaceManager {
|
|
|
93
88
|
|
|
94
89
|
private _isOpen = false;
|
|
95
90
|
private readonly _instanceId = PublicKey.random().toHex();
|
|
91
|
+
private readonly _spaceMemberPresenceAnnounceInterval: number;
|
|
92
|
+
private readonly _spaceMemberPresenceOfflineTimeout: number;
|
|
96
93
|
|
|
97
94
|
constructor(
|
|
98
95
|
private readonly _spaceManager: SpaceManager,
|
|
@@ -102,8 +99,15 @@ export class DataSpaceManager {
|
|
|
102
99
|
private readonly _feedStore: FeedStore<FeedMessage>,
|
|
103
100
|
private readonly _echoHost: EchoHost,
|
|
104
101
|
private readonly _invitationsManager: InvitationsManager,
|
|
105
|
-
|
|
102
|
+
params?: DataSpaceManagerRuntimeParams,
|
|
106
103
|
) {
|
|
104
|
+
const {
|
|
105
|
+
spaceMemberPresenceAnnounceInterval = PRESENCE_ANNOUNCE_INTERVAL,
|
|
106
|
+
spaceMemberPresenceOfflineTimeout = PRESENCE_OFFLINE_TIMEOUT,
|
|
107
|
+
} = params ?? {};
|
|
108
|
+
this._spaceMemberPresenceAnnounceInterval = spaceMemberPresenceAnnounceInterval;
|
|
109
|
+
this._spaceMemberPresenceOfflineTimeout = spaceMemberPresenceOfflineTimeout;
|
|
110
|
+
|
|
107
111
|
trace.diagnostic({
|
|
108
112
|
id: 'spaces',
|
|
109
113
|
name: 'Spaces',
|
|
@@ -153,6 +157,12 @@ export class DataSpaceManager {
|
|
|
153
157
|
this._isOpen = true;
|
|
154
158
|
this.updated.emit();
|
|
155
159
|
|
|
160
|
+
for (const space of this._spaces.values()) {
|
|
161
|
+
if (space.state !== SpaceState.INACTIVE) {
|
|
162
|
+
space.initializeDataPipelineAsync();
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
156
166
|
log.trace('dxos.echo.data-space-manager.open', Trace.end({ id: this._instanceId }));
|
|
157
167
|
}
|
|
158
168
|
|
|
@@ -164,7 +174,6 @@ export class DataSpaceManager {
|
|
|
164
174
|
for (const space of this._spaces.values()) {
|
|
165
175
|
await space.close();
|
|
166
176
|
}
|
|
167
|
-
this._spaces.clear();
|
|
168
177
|
}
|
|
169
178
|
|
|
170
179
|
/**
|
|
@@ -188,7 +197,6 @@ export class DataSpaceManager {
|
|
|
188
197
|
|
|
189
198
|
const root = await this._echoHost.createSpaceRoot(spaceKey);
|
|
190
199
|
const space = await this._constructSpace(metadata);
|
|
191
|
-
await space.open();
|
|
192
200
|
|
|
193
201
|
const credentials = await spaceGenesis(this._keyring, this._signingContext, space.inner, root.url);
|
|
194
202
|
await this._metadataStore.addSpace(metadata);
|
|
@@ -203,46 +211,6 @@ export class DataSpaceManager {
|
|
|
203
211
|
return space;
|
|
204
212
|
}
|
|
205
213
|
|
|
206
|
-
async isDefaultSpace(space: DataSpace): Promise<boolean> {
|
|
207
|
-
const rootDoc = await this._getSpaceRootDocument(space);
|
|
208
|
-
const [_, properties] = findPropertiesObject(rootDoc.docSync()) ?? [];
|
|
209
|
-
return properties?.data?.[DEFAULT_SPACE_KEY] === this._signingContext.identityKey.toHex();
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
async createDefaultSpace() {
|
|
213
|
-
const space = await this.createSpace();
|
|
214
|
-
const document = await this._getSpaceRootDocument(space);
|
|
215
|
-
|
|
216
|
-
// TODO(dmaretskyi): Better API for low-level data access.
|
|
217
|
-
const properties: ObjectStructure = {
|
|
218
|
-
system: {
|
|
219
|
-
type: encodeReference(getTypeReference(PropertiesType)!),
|
|
220
|
-
},
|
|
221
|
-
data: {
|
|
222
|
-
[DEFAULT_SPACE_KEY]: this._signingContext.identityKey.toHex(),
|
|
223
|
-
},
|
|
224
|
-
meta: {
|
|
225
|
-
keys: [],
|
|
226
|
-
},
|
|
227
|
-
};
|
|
228
|
-
|
|
229
|
-
const propertiesId = PublicKey.random().toHex();
|
|
230
|
-
document.change((doc: SpaceDoc) => {
|
|
231
|
-
assignDeep(doc, ['objects', propertiesId], properties);
|
|
232
|
-
});
|
|
233
|
-
|
|
234
|
-
await this._echoHost.flush();
|
|
235
|
-
return space;
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
private async _getSpaceRootDocument(space: DataSpace): Promise<DocHandle<SpaceDoc>> {
|
|
239
|
-
const automergeIndex = space.automergeSpaceState.rootUrl;
|
|
240
|
-
invariant(automergeIndex);
|
|
241
|
-
const document = this._echoHost.automergeRepo.find<SpaceDoc>(automergeIndex as any);
|
|
242
|
-
await document.whenReady();
|
|
243
|
-
return document;
|
|
244
|
-
}
|
|
245
|
-
|
|
246
214
|
// TODO(burdon): Rename join space.
|
|
247
215
|
@synchronized
|
|
248
216
|
async acceptSpace(opts: AcceptSpaceOptions): Promise<DataSpace> {
|
|
@@ -258,7 +226,6 @@ export class DataSpaceManager {
|
|
|
258
226
|
};
|
|
259
227
|
|
|
260
228
|
const space = await this._constructSpace(metadata);
|
|
261
|
-
await space.open();
|
|
262
229
|
await this._metadataStore.addSpace(metadata);
|
|
263
230
|
space.initializeDataPipelineAsync();
|
|
264
231
|
|
|
@@ -287,8 +254,8 @@ export class DataSpaceManager {
|
|
|
287
254
|
localPeerId: this._signingContext.deviceKey,
|
|
288
255
|
});
|
|
289
256
|
const presence = new Presence({
|
|
290
|
-
announceInterval: this.
|
|
291
|
-
offlineTimeout: this.
|
|
257
|
+
announceInterval: this._spaceMemberPresenceAnnounceInterval,
|
|
258
|
+
offlineTimeout: this._spaceMemberPresenceOfflineTimeout,
|
|
292
259
|
identityKey: this._signingContext.identityKey,
|
|
293
260
|
gossip,
|
|
294
261
|
});
|
|
@@ -369,6 +336,10 @@ export class DataSpaceManager {
|
|
|
369
336
|
}
|
|
370
337
|
});
|
|
371
338
|
|
|
339
|
+
if (metadata.state !== SpaceState.INACTIVE) {
|
|
340
|
+
await dataSpace.open();
|
|
341
|
+
}
|
|
342
|
+
|
|
372
343
|
if (metadata.controlTimeframe) {
|
|
373
344
|
dataSpace.inner.controlPipeline.state.setTargetTimeframe(metadata.controlTimeframe);
|
|
374
345
|
}
|
|
@@ -193,13 +193,10 @@ export class DataSpace {
|
|
|
193
193
|
|
|
194
194
|
@synchronized
|
|
195
195
|
async open() {
|
|
196
|
-
|
|
197
|
-
await this._open();
|
|
198
|
-
}
|
|
196
|
+
await this._open();
|
|
199
197
|
}
|
|
200
198
|
|
|
201
199
|
private async _open() {
|
|
202
|
-
await this._presence.open();
|
|
203
200
|
await this._gossip.open();
|
|
204
201
|
await this._notarizationPlugin.open();
|
|
205
202
|
await this._inner.spaceState.addCredentialProcessor(this._notarizationPlugin);
|
|
@@ -231,7 +228,7 @@ export class DataSpace {
|
|
|
231
228
|
await this._inner.spaceState.removeCredentialProcessor(this._notarizationPlugin);
|
|
232
229
|
await this._notarizationPlugin.close();
|
|
233
230
|
|
|
234
|
-
await this._presence.
|
|
231
|
+
await this._presence.destroy();
|
|
235
232
|
await this._gossip.close();
|
|
236
233
|
}
|
|
237
234
|
|
|
@@ -456,7 +453,6 @@ export class DataSpace {
|
|
|
456
453
|
const rootHandle = this._echoHost.automergeRepo.find(currentRootUrl as any);
|
|
457
454
|
await cancelWithContext(this._ctx, asyncTimeout(rootHandle.whenReady(), 10_000));
|
|
458
455
|
const newRoot = this._echoHost.automergeRepo.create(rootHandle.docSync());
|
|
459
|
-
await this._echoHost.automergeRepo.flush([newRoot.documentId]);
|
|
460
456
|
invariant(typeof newRoot.url === 'string' && newRoot.url.length > 0);
|
|
461
457
|
// TODO(dmaretskyi): Unify epoch construction.
|
|
462
458
|
epoch = {
|
|
@@ -548,7 +544,7 @@ export class DataSpace {
|
|
|
548
544
|
|
|
549
545
|
@synchronized
|
|
550
546
|
async activate() {
|
|
551
|
-
if (
|
|
547
|
+
if (this._state !== SpaceState.INACTIVE) {
|
|
552
548
|
return;
|
|
553
549
|
}
|
|
554
550
|
|
|
@@ -562,11 +558,10 @@ export class DataSpace {
|
|
|
562
558
|
if (this._state === SpaceState.INACTIVE) {
|
|
563
559
|
return;
|
|
564
560
|
}
|
|
561
|
+
|
|
565
562
|
// Unregister from data service.
|
|
566
563
|
await this._metadataStore.setSpaceState(this.key, SpaceState.INACTIVE);
|
|
567
|
-
|
|
568
|
-
await this._close();
|
|
569
|
-
}
|
|
564
|
+
await this._close();
|
|
570
565
|
this._state = SpaceState.INACTIVE;
|
|
571
566
|
log('new state', { state: SpaceState[this._state] });
|
|
572
567
|
this.stateUpdate.emit();
|