@dxos/client-services 0.1.31-next.ea8f0bc → 0.1.32-next.77d513e
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-ZIQUYEJS.mjs → chunk-E3FD43YN.mjs} +460 -213
- package/dist/lib/browser/chunk-E3FD43YN.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 +64 -6
- package/dist/lib/browser/packlets/testing/index.mjs.map +3 -3
- package/dist/lib/node/index.cjs +462 -215
- 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 +524 -218
- package/dist/lib/node/packlets/testing/index.cjs.map +3 -3
- package/dist/types/src/packlets/identity/identity-manager.d.ts +6 -0
- package/dist/types/src/packlets/identity/identity-manager.d.ts.map +1 -1
- package/dist/types/src/packlets/invitations/device-invitations-handler.d.ts.map +1 -1
- package/dist/types/src/packlets/invitations/space-invitations-handler.d.ts.map +1 -1
- package/dist/types/src/packlets/services/service-host.d.ts +1 -0
- package/dist/types/src/packlets/services/service-host.d.ts.map +1 -1
- package/dist/types/src/packlets/spaces/data-space-manager.d.ts +19 -1
- package/dist/types/src/packlets/spaces/data-space-manager.d.ts.map +1 -1
- package/dist/types/src/packlets/spaces/data-space.d.ts +32 -5
- package/dist/types/src/packlets/spaces/data-space.d.ts.map +1 -1
- package/dist/types/src/packlets/spaces/notarization-plugin.d.ts +29 -1
- package/dist/types/src/packlets/spaces/notarization-plugin.d.ts.map +1 -1
- package/dist/types/src/packlets/spaces/spaces-service.d.ts +4 -1
- package/dist/types/src/packlets/spaces/spaces-service.d.ts.map +1 -1
- package/dist/types/src/packlets/testing/host-test-builder.d.ts +2 -2
- package/dist/types/src/packlets/testing/host-test-builder.d.ts.map +1 -1
- package/dist/types/src/packlets/testing/test-builder.d.ts +2 -0
- package/dist/types/src/packlets/testing/test-builder.d.ts.map +1 -1
- package/dist/types/src/packlets/tests/text.test.d.ts +2 -0
- package/dist/types/src/packlets/tests/text.test.d.ts.map +1 -0
- package/dist/types/src/packlets/vault/vault-resource-lock.d.ts +1 -0
- package/dist/types/src/packlets/vault/vault-resource-lock.d.ts.map +1 -1
- package/package.json +31 -30
- package/src/packlets/identity/identity-manager.ts +32 -17
- package/src/packlets/invitations/device-invitations-handler.ts +5 -3
- package/src/packlets/invitations/space-invitations-handler.test.ts +20 -3
- package/src/packlets/invitations/space-invitations-handler.ts +15 -8
- package/src/packlets/services/service-context.test.ts +6 -5
- package/src/packlets/services/service-host.ts +7 -2
- package/src/packlets/spaces/data-space-manager.test.ts +77 -2
- package/src/packlets/spaces/data-space-manager.ts +87 -37
- package/src/packlets/spaces/data-space.ts +103 -16
- package/src/packlets/spaces/notarization-plugin.test.ts +3 -1
- package/src/packlets/spaces/notarization-plugin.ts +67 -19
- package/src/packlets/spaces/spaces-service.test.ts +1 -1
- package/src/packlets/spaces/spaces-service.ts +53 -17
- package/src/packlets/testing/host-test-builder.ts +2 -2
- package/src/packlets/testing/test-builder.ts +45 -5
- package/src/packlets/tests/client-services.test.ts +24 -11
- package/src/packlets/tests/client.test.ts +3 -6
- package/src/packlets/tests/halo-profile.test.ts +5 -5
- package/src/packlets/tests/spaces-invitations.test.ts +47 -7
- package/src/packlets/tests/spaces.test.ts +86 -9
- package/src/packlets/tests/text.test.ts +206 -0
- package/src/packlets/vault/vault-resource-lock.ts +10 -1
- package/dist/lib/browser/chunk-ZIQUYEJS.mjs.map +0 -7
|
@@ -28,6 +28,17 @@ describe('services/space-invitations-handler', () => {
|
|
|
28
28
|
|
|
29
29
|
const space = await peer.dataSpaceManager!.createSpace();
|
|
30
30
|
expect(peer.dataSpaceManager!.spaces.has(space.key)).to.be.true;
|
|
31
|
+
|
|
32
|
+
await space.close();
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test('genesis & ready', async () => {
|
|
36
|
+
const [peer] = await asyncChain<ServiceContext>([createIdentity, closeAfterTest])(createPeers(1));
|
|
37
|
+
|
|
38
|
+
const space = await peer.dataSpaceManager!.createSpace();
|
|
39
|
+
expect(peer.dataSpaceManager!.spaces.has(space.key)).to.be.true;
|
|
40
|
+
|
|
41
|
+
await peer.dataSpaceManager?.waitUntilSpaceReady(space.key);
|
|
31
42
|
await space.close();
|
|
32
43
|
});
|
|
33
44
|
|
|
@@ -36,7 +47,7 @@ describe('services/space-invitations-handler', () => {
|
|
|
36
47
|
const space = await peer.dataSpaceManager!.createSpace();
|
|
37
48
|
afterTest(() => space.close());
|
|
38
49
|
|
|
39
|
-
await testLocalDatabase(space.
|
|
50
|
+
await testLocalDatabase(space.dataPipeline);
|
|
40
51
|
});
|
|
41
52
|
|
|
42
53
|
test('invitation with no auth', async () => {
|
|
@@ -53,7 +64,10 @@ describe('services/space-invitations-handler', () => {
|
|
|
53
64
|
expect(space1).not.to.be.undefined;
|
|
54
65
|
expect(space2).not.to.be.undefined;
|
|
55
66
|
|
|
56
|
-
await
|
|
67
|
+
await host.dataSpaceManager?.waitUntilSpaceReady(space1.key);
|
|
68
|
+
await guest.dataSpaceManager?.waitUntilSpaceReady(space2.key);
|
|
69
|
+
|
|
70
|
+
await syncItemsLocal(space1.dataPipeline, space2.dataPipeline);
|
|
57
71
|
|
|
58
72
|
await space1.close();
|
|
59
73
|
await space2.close();
|
|
@@ -116,7 +130,10 @@ describe('services/space-invitations-handler', () => {
|
|
|
116
130
|
expect(space1).not.to.be.undefined;
|
|
117
131
|
expect(space2).not.to.be.undefined;
|
|
118
132
|
|
|
119
|
-
await
|
|
133
|
+
await host.dataSpaceManager?.waitUntilSpaceReady(space1.key);
|
|
134
|
+
await guest.dataSpaceManager?.waitUntilSpaceReady(space2.key);
|
|
135
|
+
|
|
136
|
+
await syncItemsLocal(space1.dataPipeline, space2.dataPipeline);
|
|
120
137
|
|
|
121
138
|
await space1.close();
|
|
122
139
|
await space2.close();
|
|
@@ -177,7 +177,11 @@ export class SpaceInvitationsHandler extends AbstractInvitationsHandler<DataSpac
|
|
|
177
177
|
// Updating credentials complete.
|
|
178
178
|
complete.wake(deviceKey);
|
|
179
179
|
|
|
180
|
-
return {
|
|
180
|
+
return {
|
|
181
|
+
credential: spaceMemberCredential,
|
|
182
|
+
controlTimeframe: space.inner.controlPipeline.state.timeframe,
|
|
183
|
+
dataTimeframe: space.dataPipeline.pipelineState?.timeframe
|
|
184
|
+
};
|
|
181
185
|
} catch (err) {
|
|
182
186
|
// TODO(burdon): Generic RPC callback to report error to client.
|
|
183
187
|
observable.callback.onError(err);
|
|
@@ -316,12 +320,13 @@ export class SpaceInvitationsHandler extends AbstractInvitationsHandler<DataSpac
|
|
|
316
320
|
|
|
317
321
|
// 4. Send admission credentials to host (with local space keys).
|
|
318
322
|
log('request admission', { guest: this._signingContext.deviceKey });
|
|
319
|
-
const { credential } =
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
323
|
+
const { credential, controlTimeframe, dataTimeframe } =
|
|
324
|
+
await extension.rpc.SpaceHostService.requestAdmission({
|
|
325
|
+
identityKey: this._signingContext.identityKey,
|
|
326
|
+
deviceKey: this._signingContext.deviceKey,
|
|
327
|
+
controlFeedKey,
|
|
328
|
+
dataFeedKey
|
|
329
|
+
});
|
|
325
330
|
|
|
326
331
|
// 4. Create local space.
|
|
327
332
|
const assertion = getCredentialAssertion(credential);
|
|
@@ -330,7 +335,9 @@ export class SpaceInvitationsHandler extends AbstractInvitationsHandler<DataSpac
|
|
|
330
335
|
|
|
331
336
|
const space = await this._spaceManager.acceptSpace({
|
|
332
337
|
spaceKey: assertion.spaceKey,
|
|
333
|
-
genesisFeedKey: assertion.genesisFeedKey
|
|
338
|
+
genesisFeedKey: assertion.genesisFeedKey,
|
|
339
|
+
controlTimeframe,
|
|
340
|
+
dataTimeframe
|
|
334
341
|
});
|
|
335
342
|
|
|
336
343
|
// Record credential in our HALO.
|
|
@@ -18,8 +18,9 @@ describe('services/ServiceContext', () => {
|
|
|
18
18
|
const device2 = createServiceContext({ signalContext: networkContext });
|
|
19
19
|
await performInvitation(device1.deviceInvitations, device2.deviceInvitations, undefined);
|
|
20
20
|
|
|
21
|
+
await device2.dataSpaceManager!.waitUntilSpaceReady(space1!.key);
|
|
21
22
|
const space2 = await device2.dataSpaceManager!.spaces.get(space1.key);
|
|
22
|
-
await syncItemsLocal(space1.
|
|
23
|
+
await syncItemsLocal(space1.dataPipeline, space2!.dataPipeline);
|
|
23
24
|
});
|
|
24
25
|
|
|
25
26
|
test('new space is synchronized on device invitations', async () => {
|
|
@@ -31,9 +32,9 @@ describe('services/ServiceContext', () => {
|
|
|
31
32
|
await performInvitation(device1.deviceInvitations, device2.deviceInvitations, undefined);
|
|
32
33
|
|
|
33
34
|
const space1 = await device1.dataSpaceManager!.createSpace();
|
|
34
|
-
await device2.dataSpaceManager
|
|
35
|
+
await device2.dataSpaceManager!.waitUntilSpaceReady(space1!.key);
|
|
35
36
|
const space2 = await device2.dataSpaceManager!.spaces.get(space1.key);
|
|
36
|
-
await syncItemsLocal(space1.
|
|
37
|
+
await syncItemsLocal(space1.dataPipeline, space2!.dataPipeline);
|
|
37
38
|
});
|
|
38
39
|
|
|
39
40
|
test('joined space is synchronized on device invitations', async () => {
|
|
@@ -49,8 +50,8 @@ describe('services/ServiceContext', () => {
|
|
|
49
50
|
const space1 = await identity2.dataSpaceManager!.createSpace();
|
|
50
51
|
await performInvitation(identity2.spaceInvitations!, device1.spaceInvitations!, space1);
|
|
51
52
|
|
|
52
|
-
await device2.dataSpaceManager
|
|
53
|
+
await device2.dataSpaceManager!.waitUntilSpaceReady(space1!.key);
|
|
53
54
|
const space2 = await device2.dataSpaceManager!.spaces.get(space1.key);
|
|
54
|
-
await syncItemsLocal(space1.
|
|
55
|
+
await syncItemsLocal(space1.dataPipeline, space2!.dataPipeline);
|
|
55
56
|
});
|
|
56
57
|
});
|
|
@@ -62,6 +62,7 @@ export class ClientServicesHost {
|
|
|
62
62
|
* @internal
|
|
63
63
|
*/
|
|
64
64
|
_serviceContext!: ServiceContext;
|
|
65
|
+
private _opening = false;
|
|
65
66
|
private _open = false;
|
|
66
67
|
|
|
67
68
|
constructor({
|
|
@@ -83,7 +84,9 @@ export class ClientServicesHost {
|
|
|
83
84
|
this._resourceLock = lockKey
|
|
84
85
|
? new VaultResourceLock({
|
|
85
86
|
lockKey,
|
|
86
|
-
onAcquire: () =>
|
|
87
|
+
onAcquire: () => {
|
|
88
|
+
this._opening || this.open();
|
|
89
|
+
},
|
|
87
90
|
onRelease: () => this.close()
|
|
88
91
|
})
|
|
89
92
|
: undefined;
|
|
@@ -163,7 +166,8 @@ export class ClientServicesHost {
|
|
|
163
166
|
assert(this._storage, 'storage not set');
|
|
164
167
|
assert(this._networkManager, 'network manager not set');
|
|
165
168
|
|
|
166
|
-
|
|
169
|
+
this._opening = true;
|
|
170
|
+
log('opening...', { lockKey: this._resourceLock?.lockKey });
|
|
167
171
|
await this._resourceLock?.acquire();
|
|
168
172
|
|
|
169
173
|
// TODO(wittjosiah): Make re-entrant.
|
|
@@ -213,6 +217,7 @@ export class ClientServicesHost {
|
|
|
213
217
|
});
|
|
214
218
|
|
|
215
219
|
await this._serviceContext.open();
|
|
220
|
+
this._opening = false;
|
|
216
221
|
this._open = true;
|
|
217
222
|
this._statusUpdate.emit();
|
|
218
223
|
const deviceKey = this._serviceContext.identityManager.identity?.deviceKey;
|
|
@@ -4,13 +4,14 @@
|
|
|
4
4
|
|
|
5
5
|
import { expect } from 'chai';
|
|
6
6
|
|
|
7
|
+
import { latch } from '@dxos/async';
|
|
7
8
|
import { createAdmissionCredentials } from '@dxos/credentials';
|
|
8
9
|
import { DocumentModel } from '@dxos/document-model';
|
|
9
10
|
import { AuthStatus, DataServiceSubscriptions } from '@dxos/echo-pipeline';
|
|
10
11
|
import { writeMessages } from '@dxos/feed-store';
|
|
11
12
|
import { log } from '@dxos/log';
|
|
12
13
|
import { ModelFactory } from '@dxos/model-factory';
|
|
13
|
-
import { test, describe } from '@dxos/test';
|
|
14
|
+
import { test, describe, afterTest } from '@dxos/test';
|
|
14
15
|
|
|
15
16
|
import { createSigningContext, HostTestBuilder, syncItemsLocal } from '../testing';
|
|
16
17
|
import { DataSpaceManager } from './data-space-manager';
|
|
@@ -31,6 +32,8 @@ describe('DataSpaceManager', () => {
|
|
|
31
32
|
peer.feedStore,
|
|
32
33
|
peer.snapshotStore
|
|
33
34
|
);
|
|
35
|
+
await dataSpaceManager.open();
|
|
36
|
+
afterTest(() => dataSpaceManager.close());
|
|
34
37
|
const space = await dataSpaceManager.createSpace();
|
|
35
38
|
|
|
36
39
|
// Process all written mutations.
|
|
@@ -57,6 +60,8 @@ describe('DataSpaceManager', () => {
|
|
|
57
60
|
peer1.feedStore,
|
|
58
61
|
peer1.snapshotStore
|
|
59
62
|
);
|
|
63
|
+
await dataSpaceManager1.open();
|
|
64
|
+
afterTest(() => dataSpaceManager1.close());
|
|
60
65
|
|
|
61
66
|
const peer2 = builder.createPeer();
|
|
62
67
|
const identity2 = await createSigningContext(peer2.keyring);
|
|
@@ -70,6 +75,8 @@ describe('DataSpaceManager', () => {
|
|
|
70
75
|
peer2.feedStore,
|
|
71
76
|
peer1.snapshotStore
|
|
72
77
|
);
|
|
78
|
+
await dataSpaceManager2.open();
|
|
79
|
+
afterTest(() => dataSpaceManager2.close());
|
|
73
80
|
|
|
74
81
|
const space1 = await dataSpaceManager1.createSpace();
|
|
75
82
|
await space1.inner.controlPipeline.state.waitUntilTimeframe(space1.inner.controlPipeline.state.endTimeframe);
|
|
@@ -90,6 +97,7 @@ describe('DataSpaceManager', () => {
|
|
|
90
97
|
spaceKey: space1.key,
|
|
91
98
|
genesisFeedKey: space1.inner.genesisFeedKey
|
|
92
99
|
});
|
|
100
|
+
await dataSpaceManager2.waitUntilSpaceReady(space2.key);
|
|
93
101
|
|
|
94
102
|
log('', {
|
|
95
103
|
peer1: {
|
|
@@ -121,11 +129,78 @@ describe('DataSpaceManager', () => {
|
|
|
121
129
|
});
|
|
122
130
|
log.break();
|
|
123
131
|
|
|
124
|
-
await syncItemsLocal(space1.
|
|
132
|
+
await syncItemsLocal(space1.dataPipeline, space2.dataPipeline);
|
|
125
133
|
|
|
126
134
|
expect(space1.inner.protocol.sessions.get(identity2.deviceKey)).to.exist;
|
|
127
135
|
expect(space1.inner.protocol.sessions.get(identity2.deviceKey)?.authStatus).to.equal(AuthStatus.SUCCESS);
|
|
128
136
|
expect(space2.inner.protocol.sessions.get(identity1.deviceKey)).to.exist;
|
|
129
137
|
expect(space2.inner.protocol.sessions.get(identity1.deviceKey)?.authStatus).to.equal(AuthStatus.SUCCESS);
|
|
130
138
|
});
|
|
139
|
+
|
|
140
|
+
test('pub/sub API', async () => {
|
|
141
|
+
const builder = new HostTestBuilder();
|
|
142
|
+
|
|
143
|
+
const peer1 = builder.createPeer();
|
|
144
|
+
const identity1 = await createSigningContext(peer1.keyring);
|
|
145
|
+
const dataSpaceManager1 = new DataSpaceManager(
|
|
146
|
+
peer1.spaceManager,
|
|
147
|
+
peer1.metadataStore,
|
|
148
|
+
new DataServiceSubscriptions(),
|
|
149
|
+
peer1.keyring,
|
|
150
|
+
identity1,
|
|
151
|
+
new ModelFactory().registerModel(DocumentModel),
|
|
152
|
+
peer1.feedStore,
|
|
153
|
+
peer1.snapshotStore
|
|
154
|
+
);
|
|
155
|
+
await dataSpaceManager1.open();
|
|
156
|
+
afterTest(() => dataSpaceManager1.close());
|
|
157
|
+
|
|
158
|
+
const peer2 = builder.createPeer();
|
|
159
|
+
const identity2 = await createSigningContext(peer2.keyring);
|
|
160
|
+
const dataSpaceManager2 = new DataSpaceManager(
|
|
161
|
+
peer2.spaceManager,
|
|
162
|
+
peer2.metadataStore,
|
|
163
|
+
new DataServiceSubscriptions(),
|
|
164
|
+
peer2.keyring,
|
|
165
|
+
identity2,
|
|
166
|
+
new ModelFactory().registerModel(DocumentModel),
|
|
167
|
+
peer2.feedStore,
|
|
168
|
+
peer1.snapshotStore
|
|
169
|
+
);
|
|
170
|
+
await dataSpaceManager2.open();
|
|
171
|
+
afterTest(() => dataSpaceManager2.close());
|
|
172
|
+
|
|
173
|
+
const space1 = await dataSpaceManager1.createSpace();
|
|
174
|
+
await space1.inner.controlPipeline.state.waitUntilTimeframe(space1.inner.controlPipeline.state.endTimeframe);
|
|
175
|
+
|
|
176
|
+
// Admit peer2 to space1.
|
|
177
|
+
await writeMessages(
|
|
178
|
+
space1.inner.controlPipeline.writer,
|
|
179
|
+
await createAdmissionCredentials(
|
|
180
|
+
identity1.credentialSigner,
|
|
181
|
+
identity2.identityKey,
|
|
182
|
+
space1.key,
|
|
183
|
+
space1.inner.genesisFeedKey
|
|
184
|
+
)
|
|
185
|
+
);
|
|
186
|
+
|
|
187
|
+
// Accept must be called after admission so that the peer can authenticate for notarization.
|
|
188
|
+
const space2 = await dataSpaceManager2.acceptSpace({
|
|
189
|
+
spaceKey: space1.key,
|
|
190
|
+
genesisFeedKey: space1.inner.genesisFeedKey
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
// Coincidentally, this also waits until a P2P connection is established between peers.
|
|
194
|
+
// TODO(dmaretskyi): Refine this to wait for connection specifically.
|
|
195
|
+
await dataSpaceManager2.waitUntilSpaceReady(space2.key);
|
|
196
|
+
|
|
197
|
+
const [receivedMessage, inc] = latch({ count: 1 });
|
|
198
|
+
space2.listen('test', (message) => {
|
|
199
|
+
expect(message.channelId).to.equal('test');
|
|
200
|
+
inc();
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
await space1.postMessage('test', { '@type': 'google.protobuf.Any', test: true });
|
|
204
|
+
await receivedMessage();
|
|
205
|
+
});
|
|
131
206
|
});
|
|
@@ -5,17 +5,18 @@
|
|
|
5
5
|
import assert from 'node:assert';
|
|
6
6
|
|
|
7
7
|
import { Event, synchronized, trackLeaks } from '@dxos/async';
|
|
8
|
-
import {
|
|
8
|
+
import { SpaceState } from '@dxos/client';
|
|
9
|
+
import { cancelWithContext, Context } from '@dxos/context';
|
|
9
10
|
import { getCredentialAssertion } from '@dxos/credentials';
|
|
10
11
|
import {
|
|
12
|
+
DataServiceSubscriptions,
|
|
11
13
|
MetadataStore,
|
|
12
14
|
SigningContext,
|
|
15
|
+
SnapshotManager,
|
|
16
|
+
SnapshotStore,
|
|
13
17
|
Space,
|
|
14
18
|
spaceGenesis,
|
|
15
|
-
SpaceManager
|
|
16
|
-
DataServiceSubscriptions,
|
|
17
|
-
SnapshotManager,
|
|
18
|
-
SnapshotStore
|
|
19
|
+
SpaceManager
|
|
19
20
|
} from '@dxos/echo-pipeline';
|
|
20
21
|
import { FeedStore } from '@dxos/feed-store';
|
|
21
22
|
import { Keyring } from '@dxos/keyring';
|
|
@@ -24,17 +25,28 @@ import { log } from '@dxos/log';
|
|
|
24
25
|
import { ModelFactory } from '@dxos/model-factory';
|
|
25
26
|
import { FeedMessage } from '@dxos/protocols/proto/dxos/echo/feed';
|
|
26
27
|
import { SpaceMetadata } from '@dxos/protocols/proto/dxos/echo/metadata';
|
|
27
|
-
import { Presence } from '@dxos/teleport-extension-
|
|
28
|
+
import { Gossip, Presence } from '@dxos/teleport-extension-gossip';
|
|
29
|
+
import { Timeframe } from '@dxos/timeframe';
|
|
28
30
|
import { ComplexMap, deferFunction } from '@dxos/util';
|
|
29
31
|
|
|
30
32
|
import { createAuthProvider } from '../identity';
|
|
31
33
|
import { DataSpace } from './data-space';
|
|
32
34
|
|
|
33
|
-
const DATA_PIPELINE_READY_TIMEOUT = 3_000;
|
|
34
|
-
|
|
35
35
|
export type AcceptSpaceOptions = {
|
|
36
36
|
spaceKey: PublicKey;
|
|
37
37
|
genesisFeedKey: PublicKey;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Latest known timeframe for the control pipeline.
|
|
41
|
+
* We will try to catch up to this timeframe before starting the data pipeline.
|
|
42
|
+
*/
|
|
43
|
+
controlTimeframe?: Timeframe;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Latest known timeframe for the data pipeline.
|
|
47
|
+
* We will try to catch up to this timeframe before initializing the database.
|
|
48
|
+
*/
|
|
49
|
+
dataTimeframe?: Timeframe;
|
|
38
50
|
};
|
|
39
51
|
|
|
40
52
|
@trackLeaks('open', 'close')
|
|
@@ -45,6 +57,8 @@ export class DataSpaceManager {
|
|
|
45
57
|
|
|
46
58
|
private readonly _spaces = new ComplexMap<PublicKey, DataSpace>(PublicKey.hash);
|
|
47
59
|
|
|
60
|
+
private _isOpen = false;
|
|
61
|
+
|
|
48
62
|
constructor(
|
|
49
63
|
private readonly _spaceManager: SpaceManager,
|
|
50
64
|
private readonly _metadataStore: MetadataStore,
|
|
@@ -63,29 +77,24 @@ export class DataSpaceManager {
|
|
|
63
77
|
|
|
64
78
|
@synchronized
|
|
65
79
|
async open() {
|
|
80
|
+
log('open');
|
|
66
81
|
await this._metadataStore.load();
|
|
67
82
|
log('metadata loaded', { spaces: this._metadataStore.spaces.length });
|
|
68
83
|
|
|
69
84
|
for (const spaceMetadata of this._metadataStore.spaces) {
|
|
70
85
|
log('load space', { spaceMetadata });
|
|
71
86
|
const space = await this._constructSpace(spaceMetadata);
|
|
72
|
-
|
|
73
|
-
if (spaceMetadata.dataTimeframe) {
|
|
74
|
-
log('waiting for latest timeframe', { spaceMetadata });
|
|
75
|
-
await space.dataPipelineController.pipelineState!.setTargetTimeframe(spaceMetadata.dataTimeframe);
|
|
76
|
-
}
|
|
77
|
-
await space.dataPipelineController.pipelineState!.waitUntilReachedTargetTimeframe({
|
|
78
|
-
timeout: DATA_PIPELINE_READY_TIMEOUT
|
|
79
|
-
});
|
|
80
|
-
this._dataServiceSubscriptions.registerSpace(
|
|
81
|
-
space.key,
|
|
82
|
-
space.dataPipelineController.databaseBackend!.createDataServiceHost()
|
|
83
|
-
);
|
|
87
|
+
space.initializeDataPipelineAsync();
|
|
84
88
|
}
|
|
89
|
+
|
|
90
|
+
this._isOpen = true;
|
|
91
|
+
this.updated.emit();
|
|
85
92
|
}
|
|
86
93
|
|
|
87
94
|
@synchronized
|
|
88
95
|
async close() {
|
|
96
|
+
log('close');
|
|
97
|
+
this._isOpen = false;
|
|
89
98
|
await this._ctx.dispose();
|
|
90
99
|
for (const space of this._spaces.values()) {
|
|
91
100
|
await space.close();
|
|
@@ -97,6 +106,7 @@ export class DataSpaceManager {
|
|
|
97
106
|
*/
|
|
98
107
|
@synchronized
|
|
99
108
|
async createSpace() {
|
|
109
|
+
assert(this._isOpen, 'Not open.');
|
|
100
110
|
const spaceKey = await this._keyring.createKey();
|
|
101
111
|
const controlFeedKey = await this._keyring.createKey();
|
|
102
112
|
const dataFeedKey = await this._keyring.createKey();
|
|
@@ -118,10 +128,6 @@ export class DataSpaceManager {
|
|
|
118
128
|
await this._signingContext.recordCredential(memberCredential);
|
|
119
129
|
|
|
120
130
|
await space.initializeDataPipeline();
|
|
121
|
-
this._dataServiceSubscriptions.registerSpace(
|
|
122
|
-
space.key,
|
|
123
|
-
space.dataPipelineController.databaseBackend!.createDataServiceHost()
|
|
124
|
-
);
|
|
125
131
|
|
|
126
132
|
this.updated.emit();
|
|
127
133
|
return space;
|
|
@@ -130,31 +136,51 @@ export class DataSpaceManager {
|
|
|
130
136
|
// TODO(burdon): Rename join space.
|
|
131
137
|
@synchronized
|
|
132
138
|
async acceptSpace(opts: AcceptSpaceOptions): Promise<DataSpace> {
|
|
139
|
+
log('accept space', { opts });
|
|
140
|
+
assert(this._isOpen, 'Not open.');
|
|
133
141
|
assert(!this._spaces.has(opts.spaceKey), 'Space already exists.');
|
|
134
142
|
|
|
135
143
|
const metadata: SpaceMetadata = {
|
|
136
144
|
key: opts.spaceKey,
|
|
137
|
-
genesisFeedKey: opts.genesisFeedKey
|
|
145
|
+
genesisFeedKey: opts.genesisFeedKey,
|
|
146
|
+
controlTimeframe: opts.controlTimeframe,
|
|
147
|
+
dataTimeframe: opts.dataTimeframe
|
|
138
148
|
};
|
|
139
149
|
|
|
140
150
|
const space = await this._constructSpace(metadata);
|
|
141
151
|
await this._metadataStore.addSpace(metadata);
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
space.key,
|
|
145
|
-
space.dataPipelineController.databaseBackend!.createDataServiceHost()
|
|
146
|
-
);
|
|
152
|
+
|
|
153
|
+
space.initializeDataPipelineAsync();
|
|
147
154
|
|
|
148
155
|
this.updated.emit();
|
|
149
156
|
return space;
|
|
150
157
|
}
|
|
151
158
|
|
|
159
|
+
/**
|
|
160
|
+
* Wait until the space data pipeline is fully initialized.
|
|
161
|
+
* Used by invitation handler.
|
|
162
|
+
* TODO(dmaretskyi): Consider removing.
|
|
163
|
+
*/
|
|
164
|
+
async waitUntilSpaceReady(spaceKey: PublicKey) {
|
|
165
|
+
await cancelWithContext(
|
|
166
|
+
this._ctx,
|
|
167
|
+
this.updated.waitForCondition(() => {
|
|
168
|
+
const space = this._spaces.get(spaceKey);
|
|
169
|
+
return !!space && space.state === SpaceState.READY;
|
|
170
|
+
})
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
|
|
152
174
|
private async _constructSpace(metadata: SpaceMetadata) {
|
|
175
|
+
log('construct space', { metadata });
|
|
176
|
+
const gossip = new Gossip({
|
|
177
|
+
localPeerId: this._signingContext.deviceKey
|
|
178
|
+
});
|
|
153
179
|
const presence = new Presence({
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
180
|
+
announceInterval: 500,
|
|
181
|
+
offlineTimeout: 5_000, // TODO(burdon): Config.
|
|
182
|
+
identityKey: this._signingContext.identityKey,
|
|
183
|
+
gossip
|
|
158
184
|
});
|
|
159
185
|
const snapshotManager = new SnapshotManager(this._snapshotStore);
|
|
160
186
|
|
|
@@ -171,8 +197,8 @@ export class DataSpaceManager {
|
|
|
171
197
|
},
|
|
172
198
|
onNetworkConnection: (session) => {
|
|
173
199
|
session.addExtension(
|
|
174
|
-
'dxos.mesh.teleport.
|
|
175
|
-
|
|
200
|
+
'dxos.mesh.teleport.gossip',
|
|
201
|
+
gossip.createExtension({ remotePeerId: session.remotePeerId })
|
|
176
202
|
);
|
|
177
203
|
session.addExtension('dxos.mesh.teleport.objectsync', snapshotManager.objectSync.createExtension());
|
|
178
204
|
session.addExtension('dxos.mesh.teleport.notarization', dataSpace.notarizationPlugin.createExtension());
|
|
@@ -186,15 +212,39 @@ export class DataSpaceManager {
|
|
|
186
212
|
modelFactory: this._modelFactory,
|
|
187
213
|
metadataStore: this._metadataStore,
|
|
188
214
|
snapshotManager,
|
|
215
|
+
gossip,
|
|
189
216
|
presence,
|
|
190
217
|
memberKey: this._signingContext.identityKey,
|
|
191
218
|
keyring: this._keyring,
|
|
192
219
|
feedStore: this._feedStore,
|
|
193
220
|
signingContext: this._signingContext,
|
|
194
|
-
snapshotId: metadata.snapshot
|
|
221
|
+
snapshotId: metadata.snapshot,
|
|
222
|
+
callbacks: {
|
|
223
|
+
beforeReady: async () => {
|
|
224
|
+
log('before space ready', { space: space.key });
|
|
225
|
+
this._dataServiceSubscriptions.registerSpace(
|
|
226
|
+
space.key,
|
|
227
|
+
dataSpace.dataPipeline.databaseBackend!.createDataServiceHost()
|
|
228
|
+
);
|
|
229
|
+
},
|
|
230
|
+
afterReady: async () => {
|
|
231
|
+
log('after space ready', { space: space.key, open: this._isOpen });
|
|
232
|
+
if (this._isOpen) {
|
|
233
|
+
this.updated.emit();
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
195
237
|
});
|
|
196
238
|
|
|
197
239
|
await dataSpace.open();
|
|
240
|
+
|
|
241
|
+
if (metadata.controlTimeframe) {
|
|
242
|
+
dataSpace.inner.controlPipeline.state.setTargetTimeframe(metadata.controlTimeframe);
|
|
243
|
+
}
|
|
244
|
+
if (metadata.dataTimeframe) {
|
|
245
|
+
dataSpace.dataPipeline.setTargetTimeframe(metadata.dataTimeframe);
|
|
246
|
+
}
|
|
247
|
+
|
|
198
248
|
this._spaces.set(metadata.key, dataSpace);
|
|
199
249
|
return dataSpace;
|
|
200
250
|
}
|