@dxos/client-services 0.1.23 → 0.1.24
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-R2FEBB66.mjs +3865 -0
- package/dist/lib/browser/chunk-R2FEBB66.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +56 -3066
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/packlets/testing/index.mjs +198 -0
- package/dist/lib/browser/packlets/testing/index.mjs.map +7 -0
- package/dist/lib/node/index.cjs +1401 -645
- package/dist/lib/node/index.cjs.map +4 -4
- package/dist/lib/node/meta.json +1 -1
- package/dist/lib/node/packlets/testing/index.cjs +3688 -0
- package/dist/lib/node/packlets/testing/index.cjs.map +7 -0
- package/dist/types/src/packlets/deprecated/profile.d.ts +3 -1
- package/dist/types/src/packlets/deprecated/profile.d.ts.map +1 -1
- package/dist/types/src/packlets/deprecated/space.d.ts.map +1 -1
- package/dist/types/src/packlets/deprecated/system.d.ts +15 -9
- package/dist/types/src/packlets/deprecated/system.d.ts.map +1 -1
- package/dist/types/src/packlets/devtools/devtools.d.ts +2 -2
- package/dist/types/src/packlets/devtools/devtools.d.ts.map +1 -1
- package/dist/types/src/packlets/devtools/feeds.d.ts.map +1 -1
- package/dist/types/src/packlets/devtools/network.d.ts +3 -3
- package/dist/types/src/packlets/devtools/network.d.ts.map +1 -1
- package/dist/types/src/packlets/devtools/spaces.d.ts.map +1 -1
- package/dist/types/src/packlets/identity/authenticator.d.ts +1 -1
- package/dist/types/src/packlets/identity/authenticator.d.ts.map +1 -1
- package/dist/types/src/packlets/identity/identity-manager.d.ts +7 -1
- package/dist/types/src/packlets/identity/identity-manager.d.ts.map +1 -1
- package/dist/types/src/packlets/identity/identity.d.ts +1 -0
- package/dist/types/src/packlets/identity/identity.d.ts.map +1 -1
- package/dist/types/src/packlets/invitations/halo-invitations-handler.d.ts +11 -3
- package/dist/types/src/packlets/invitations/halo-invitations-handler.d.ts.map +1 -1
- package/dist/types/src/packlets/invitations/invitations.d.ts +1 -1
- package/dist/types/src/packlets/invitations/invitations.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-context.d.ts +2 -0
- package/dist/types/src/packlets/services/service-context.d.ts.map +1 -1
- package/dist/types/src/packlets/services/service-context.test.d.ts +2 -0
- package/dist/types/src/packlets/services/service-context.test.d.ts.map +1 -0
- package/dist/types/src/packlets/services/service-definitions.d.ts +1 -1
- package/dist/types/src/packlets/services/service-definitions.d.ts.map +1 -1
- package/dist/types/src/packlets/services/service-host.d.ts +9 -6
- package/dist/types/src/packlets/services/service-host.d.ts.map +1 -1
- package/dist/types/src/packlets/services/service-registry.d.ts +4 -3
- package/dist/types/src/packlets/services/service-registry.d.ts.map +1 -1
- package/dist/types/src/packlets/spaces/data-space-manager.d.ts +9 -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 +18 -4
- package/dist/types/src/packlets/spaces/data-space.d.ts.map +1 -1
- package/dist/types/src/packlets/spaces/notarization-plugin.d.ts +50 -0
- package/dist/types/src/packlets/spaces/notarization-plugin.d.ts.map +1 -0
- package/dist/types/src/packlets/spaces/notarization-plugin.test.d.ts +2 -0
- package/dist/types/src/packlets/spaces/notarization-plugin.test.d.ts.map +1 -0
- package/dist/types/src/packlets/spaces/spaces-service.d.ts +2 -1
- package/dist/types/src/packlets/spaces/spaces-service.d.ts.map +1 -1
- package/dist/types/src/packlets/testing/credential-utils.d.ts +8 -0
- package/dist/types/src/packlets/testing/credential-utils.d.ts.map +1 -0
- package/dist/types/src/packlets/testing/index.d.ts +2 -0
- package/dist/types/src/packlets/testing/index.d.ts.map +1 -1
- package/dist/types/src/packlets/testing/invitation-utils.d.ts +3 -0
- package/dist/types/src/packlets/testing/invitation-utils.d.ts.map +1 -0
- package/dist/types/src/packlets/testing/test-builder.d.ts +2 -2
- package/dist/types/src/packlets/testing/test-builder.d.ts.map +1 -1
- package/dist/types/src/packlets/vault/iframe-host-runtime.d.ts +28 -0
- package/dist/types/src/packlets/vault/iframe-host-runtime.d.ts.map +1 -0
- package/dist/types/src/packlets/vault/{iframe-runtime.d.ts → iframe-proxy-runtime.d.ts} +4 -4
- package/dist/types/src/packlets/vault/iframe-proxy-runtime.d.ts.map +1 -0
- package/dist/types/src/packlets/vault/index.d.ts +4 -1
- package/dist/types/src/packlets/vault/index.d.ts.map +1 -1
- package/dist/types/src/packlets/vault/vault-resource-lock.d.ts +19 -0
- package/dist/types/src/packlets/vault/vault-resource-lock.d.ts.map +1 -0
- package/dist/types/src/packlets/vault/worker-runtime.d.ts.map +1 -1
- package/dist/types/src/packlets/vault/worker-session.d.ts +4 -4
- package/dist/types/src/packlets/vault/worker-session.d.ts.map +1 -1
- package/package.json +29 -29
- package/src/packlets/deprecated/profile.ts +20 -2
- package/src/packlets/deprecated/space.ts +41 -37
- package/src/packlets/deprecated/system.ts +26 -13
- package/src/packlets/devtools/devtools.ts +4 -4
- package/src/packlets/devtools/feeds.ts +11 -6
- package/src/packlets/devtools/network.ts +20 -9
- package/src/packlets/devtools/spaces.ts +7 -6
- package/src/packlets/identity/authenticator.ts +11 -6
- package/src/packlets/identity/identity-manager.test.ts +24 -15
- package/src/packlets/identity/identity-manager.ts +22 -8
- package/src/packlets/identity/identity.test.ts +21 -22
- package/src/packlets/identity/identity.ts +10 -5
- package/src/packlets/invitations/halo-invitations-handler.test.ts +4 -45
- package/src/packlets/invitations/halo-invitations-handler.ts +33 -17
- package/src/packlets/invitations/invitations.ts +2 -1
- package/src/packlets/invitations/space-invitations-handler.test.ts +43 -26
- package/src/packlets/invitations/space-invitations-handler.ts +8 -11
- package/src/packlets/services/service-context.test.ts +56 -0
- package/src/packlets/services/service-context.ts +60 -4
- package/src/packlets/services/service-definitions.ts +1 -1
- package/src/packlets/services/service-host.test.ts +74 -5
- package/src/packlets/services/service-host.ts +85 -23
- package/src/packlets/services/service-registry.test.ts +2 -1
- package/src/packlets/services/service-registry.ts +5 -1
- package/src/packlets/spaces/data-space-manager.test.ts +71 -14
- package/src/packlets/spaces/data-space-manager.ts +49 -8
- package/src/packlets/spaces/data-space.ts +113 -14
- package/src/packlets/spaces/notarization-plugin.test.ts +81 -0
- package/src/packlets/spaces/notarization-plugin.ts +211 -0
- package/src/packlets/spaces/spaces-service.ts +9 -1
- package/src/packlets/testing/credential-utils.ts +28 -0
- package/src/packlets/testing/index.ts +2 -0
- package/src/packlets/testing/invitation-utils.ts +44 -0
- package/src/packlets/testing/test-builder.ts +14 -18
- package/src/packlets/vault/iframe-host-runtime.ts +112 -0
- package/src/packlets/vault/{iframe-runtime.ts → iframe-proxy-runtime.ts} +3 -3
- package/src/packlets/vault/index.ts +4 -1
- package/src/packlets/vault/vault-resource-lock.ts +76 -0
- package/src/packlets/vault/worker-runtime.ts +13 -3
- package/src/packlets/vault/worker-session.ts +17 -17
- package/dist/types/src/packlets/vault/iframe-runtime.d.ts.map +0 -1
|
@@ -7,14 +7,15 @@ import assert from 'node:assert';
|
|
|
7
7
|
|
|
8
8
|
import { asyncChain, latch, Trigger } from '@dxos/async';
|
|
9
9
|
import { raise } from '@dxos/debug';
|
|
10
|
+
import { testLocalDatabase } from '@dxos/echo-db/testing';
|
|
10
11
|
import { PublicKey } from '@dxos/keys';
|
|
11
|
-
import { ObjectModel } from '@dxos/object-model';
|
|
12
12
|
import { Invitation } from '@dxos/protocols/proto/dxos/client/services';
|
|
13
|
-
import { describe, test
|
|
13
|
+
import { afterTest, describe, test } from '@dxos/test';
|
|
14
14
|
import { range } from '@dxos/util';
|
|
15
15
|
|
|
16
16
|
import { ServiceContext } from '../services';
|
|
17
17
|
import { createIdentity, createPeers, syncItems } from '../testing';
|
|
18
|
+
import { performInvitation } from '../testing/invitation-utils';
|
|
18
19
|
|
|
19
20
|
const closeAfterTest = async (peer: ServiceContext) => {
|
|
20
21
|
afterTest(() => peer.close());
|
|
@@ -26,7 +27,6 @@ describe('services/space-invitations-handler', () => {
|
|
|
26
27
|
const [peer] = await asyncChain<ServiceContext>([createIdentity, closeAfterTest])(createPeers(1));
|
|
27
28
|
|
|
28
29
|
const space = await peer.dataSpaceManager!.createSpace();
|
|
29
|
-
expect(space.database).not.to.be.undefined;
|
|
30
30
|
expect(peer.dataSpaceManager!.spaces.has(space.key)).to.be.true;
|
|
31
31
|
await space.close();
|
|
32
32
|
});
|
|
@@ -34,18 +34,30 @@ describe('services/space-invitations-handler', () => {
|
|
|
34
34
|
test('genesis with database mutations', async () => {
|
|
35
35
|
const [peer] = await asyncChain<ServiceContext>([createIdentity, closeAfterTest])(createPeers(1));
|
|
36
36
|
const space = await peer.dataSpaceManager!.createSpace();
|
|
37
|
+
afterTest(() => space.close());
|
|
37
38
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
39
|
+
await testLocalDatabase(space.dataPipelineController);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test('invitation with no auth', async () => {
|
|
43
|
+
const [host, guest] = await asyncChain<ServiceContext>([createIdentity, closeAfterTest])(createPeers(2));
|
|
44
|
+
|
|
45
|
+
const space1 = await host.dataSpaceManager!.createSpace();
|
|
46
|
+
const spaceKey = space1.key;
|
|
47
|
+
|
|
48
|
+
await performInvitation(host.spaceInvitations!, guest.spaceInvitations!, space1);
|
|
42
49
|
|
|
43
50
|
{
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
51
|
+
const space1 = host.dataSpaceManager!.spaces.get(spaceKey)!;
|
|
52
|
+
const space2 = guest.dataSpaceManager!.spaces.get(spaceKey)!;
|
|
53
|
+
expect(space1).not.to.be.undefined;
|
|
54
|
+
expect(space2).not.to.be.undefined;
|
|
47
55
|
|
|
48
|
-
|
|
56
|
+
await syncItems(space1.dataPipelineController, space2.dataPipelineController);
|
|
57
|
+
|
|
58
|
+
await space1.close();
|
|
59
|
+
await space2.close();
|
|
60
|
+
}
|
|
49
61
|
});
|
|
50
62
|
|
|
51
63
|
test('creates and accepts invitation with retry', async () => {
|
|
@@ -79,8 +91,8 @@ describe('services/space-invitations-handler', () => {
|
|
|
79
91
|
complete2.wake(invitation.spaceKey!);
|
|
80
92
|
},
|
|
81
93
|
onCancelled: () => raise(new Error()),
|
|
82
|
-
onTimeout: (err: Error) => raise(
|
|
83
|
-
onError: (err: Error) => raise(
|
|
94
|
+
onTimeout: (err: Error) => raise(err),
|
|
95
|
+
onError: (err: Error) => raise(err)
|
|
84
96
|
});
|
|
85
97
|
},
|
|
86
98
|
onConnected: (invitation: Invitation) => {
|
|
@@ -91,8 +103,8 @@ describe('services/space-invitations-handler', () => {
|
|
|
91
103
|
complete1.wake(invitation.spaceKey!);
|
|
92
104
|
},
|
|
93
105
|
onCancelled: () => raise(new Error()),
|
|
94
|
-
onTimeout: (err: Error) => raise(
|
|
95
|
-
onError: (err: Error) => raise(
|
|
106
|
+
onTimeout: (err: Error) => raise(err),
|
|
107
|
+
onError: (err: Error) => raise(err)
|
|
96
108
|
});
|
|
97
109
|
|
|
98
110
|
const [spaceKey1, spaceKey2] = await Promise.all([complete1.wait(), complete2.wait()]);
|
|
@@ -104,11 +116,15 @@ describe('services/space-invitations-handler', () => {
|
|
|
104
116
|
expect(space1).not.to.be.undefined;
|
|
105
117
|
expect(space2).not.to.be.undefined;
|
|
106
118
|
|
|
107
|
-
await syncItems(space1, space2);
|
|
119
|
+
await syncItems(space1.dataPipelineController, space2.dataPipelineController);
|
|
108
120
|
|
|
109
121
|
await space1.close();
|
|
110
122
|
await space2.close();
|
|
111
123
|
}
|
|
124
|
+
|
|
125
|
+
expect(
|
|
126
|
+
guest.identityManager.identity?.space.spaceState.getCredentialsOfType('dxos.halo.credentials.SpaceMember').length
|
|
127
|
+
).to.equal(2); // own halo + newly joined space.
|
|
112
128
|
});
|
|
113
129
|
|
|
114
130
|
test('cancels invitation', async () => {
|
|
@@ -133,8 +149,8 @@ describe('services/space-invitations-handler', () => {
|
|
|
133
149
|
onConnected: async (invitation2: Invitation) => {},
|
|
134
150
|
onSuccess: () => {},
|
|
135
151
|
onCancelled: () => raise(new Error()),
|
|
136
|
-
onTimeout: (err: Error) => raise(
|
|
137
|
-
onError: (err: Error) => raise(
|
|
152
|
+
onTimeout: (err: Error) => raise(err),
|
|
153
|
+
onError: (err: Error) => raise(err)
|
|
138
154
|
});
|
|
139
155
|
},
|
|
140
156
|
onConnected: async (invitation1: Invitation) => {},
|
|
@@ -142,13 +158,13 @@ describe('services/space-invitations-handler', () => {
|
|
|
142
158
|
cancelled.wake();
|
|
143
159
|
},
|
|
144
160
|
onSuccess: () => raise(new Error()),
|
|
145
|
-
onTimeout: (err: Error) => raise(
|
|
146
|
-
onError: (err: Error) => raise(
|
|
161
|
+
onTimeout: (err: Error) => raise(err),
|
|
162
|
+
onError: (err: Error) => raise(err)
|
|
147
163
|
});
|
|
148
164
|
|
|
149
165
|
const invitation1 = await connecting1.wait();
|
|
150
166
|
const invitation2 = await connecting2.wait();
|
|
151
|
-
expect(invitation1.swarmKey).to.eq(invitation2.swarmKey);
|
|
167
|
+
expect(invitation1.swarmKey).to.eq(invitation2.swarmKey); // TODO(burdon): Normalize to use to.deep.eq.
|
|
152
168
|
|
|
153
169
|
// TODO(burdon): Simulate network latency.
|
|
154
170
|
setTimeout(async () => {
|
|
@@ -159,7 +175,8 @@ describe('services/space-invitations-handler', () => {
|
|
|
159
175
|
await space1.close();
|
|
160
176
|
});
|
|
161
177
|
|
|
162
|
-
|
|
178
|
+
// TODO(burdon): Flaky.
|
|
179
|
+
test.skip('test multi-use invitation', async () => {
|
|
163
180
|
const GUEST_COUNT = 3;
|
|
164
181
|
const [host, ...guests] = await asyncChain<ServiceContext>([createIdentity, closeAfterTest])(
|
|
165
182
|
createPeers(GUEST_COUNT + 1)
|
|
@@ -180,8 +197,8 @@ describe('services/space-invitations-handler', () => {
|
|
|
180
197
|
count();
|
|
181
198
|
},
|
|
182
199
|
onCancelled: () => {},
|
|
183
|
-
onTimeout: (err: Error) => raise(
|
|
184
|
-
onError: (err: Error) => raise(
|
|
200
|
+
onTimeout: (err: Error) => raise(err),
|
|
201
|
+
onError: (err: Error) => raise(err)
|
|
185
202
|
});
|
|
186
203
|
|
|
187
204
|
await Promise.all(
|
|
@@ -198,8 +215,8 @@ describe('services/space-invitations-handler', () => {
|
|
|
198
215
|
success.wake();
|
|
199
216
|
},
|
|
200
217
|
onCancelled: () => raise(new Error()),
|
|
201
|
-
onTimeout: (err: Error) => raise(
|
|
202
|
-
onError: (err: Error) => raise(
|
|
218
|
+
onTimeout: (err: Error) => raise(err),
|
|
219
|
+
onError: (err: Error) => raise(err)
|
|
203
220
|
});
|
|
204
221
|
await success.wait({ timeout: 300 });
|
|
205
222
|
})
|
|
@@ -15,6 +15,7 @@ import { log } from '@dxos/log';
|
|
|
15
15
|
import { createTeleportProtocolFactory, NetworkManager, StarTopology, SwarmConnection } from '@dxos/network-manager';
|
|
16
16
|
import { schema } from '@dxos/protocols';
|
|
17
17
|
import { Invitation } from '@dxos/protocols/proto/dxos/client/services';
|
|
18
|
+
import { FeedMessage } from '@dxos/protocols/proto/dxos/echo/feed';
|
|
18
19
|
import { ProfileDocument } from '@dxos/protocols/proto/dxos/halo/credentials';
|
|
19
20
|
import {
|
|
20
21
|
AuthenticationRequest,
|
|
@@ -137,20 +138,17 @@ export class SpaceInvitationsHandler extends AbstractInvitationsHandler<DataSpac
|
|
|
137
138
|
|
|
138
139
|
log('writing guest credentials', { host: this._signingContext.deviceKey, guest: deviceKey });
|
|
139
140
|
// TODO(burdon): Check if already admitted.
|
|
140
|
-
const credentials = await createAdmissionCredentials(
|
|
141
|
+
const credentials: FeedMessage.Payload[] = await createAdmissionCredentials(
|
|
141
142
|
this._signingContext.credentialSigner,
|
|
142
143
|
identityKey,
|
|
143
|
-
deviceKey,
|
|
144
144
|
space.key,
|
|
145
|
-
controlFeedKey,
|
|
146
|
-
dataFeedKey,
|
|
147
145
|
space.inner.genesisFeedKey,
|
|
148
146
|
guestProfile
|
|
149
147
|
);
|
|
150
148
|
|
|
151
149
|
// TODO(dmaretskyi): Refactor.
|
|
152
|
-
assert(credentials[0]
|
|
153
|
-
const spaceMemberCredential = credentials[0].credential;
|
|
150
|
+
assert(credentials[0].credential);
|
|
151
|
+
const spaceMemberCredential = credentials[0].credential.credential;
|
|
154
152
|
assert(getCredentialAssertion(spaceMemberCredential)['@type'] === 'dxos.halo.credentials.SpaceMember');
|
|
155
153
|
|
|
156
154
|
await writeMessages(space.inner.controlPipeline.writer, credentials);
|
|
@@ -295,8 +293,6 @@ export class SpaceInvitationsHandler extends AbstractInvitationsHandler<DataSpac
|
|
|
295
293
|
dataFeedKey
|
|
296
294
|
});
|
|
297
295
|
|
|
298
|
-
// TODO(dmaretskyi): Record credential in our HALO.
|
|
299
|
-
|
|
300
296
|
// 4. Create local space.
|
|
301
297
|
const assertion = getCredentialAssertion(credential);
|
|
302
298
|
assert(assertion['@type'] === 'dxos.halo.credentials.SpaceMember', 'Invalid credential');
|
|
@@ -304,11 +300,12 @@ export class SpaceInvitationsHandler extends AbstractInvitationsHandler<DataSpac
|
|
|
304
300
|
|
|
305
301
|
const space = await this._spaceManager.acceptSpace({
|
|
306
302
|
spaceKey: assertion.spaceKey,
|
|
307
|
-
genesisFeedKey: assertion.genesisFeedKey
|
|
308
|
-
controlFeedKey,
|
|
309
|
-
dataFeedKey
|
|
303
|
+
genesisFeedKey: assertion.genesisFeedKey
|
|
310
304
|
});
|
|
311
305
|
|
|
306
|
+
// Record credential in our HALO.
|
|
307
|
+
await this._signingContext.recordCredential(credential);
|
|
308
|
+
|
|
312
309
|
// 5. Success.
|
|
313
310
|
log('admitted by host', { guest: this._signingContext.deviceKey, spaceKey: space.key });
|
|
314
311
|
complete.wake();
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { MemorySignalManagerContext } from '@dxos/messaging';
|
|
6
|
+
import { describe, test } from '@dxos/test';
|
|
7
|
+
|
|
8
|
+
import { createServiceContext, syncItems } from '../testing';
|
|
9
|
+
import { performInvitation } from '../testing/invitation-utils';
|
|
10
|
+
|
|
11
|
+
describe('services/ServiceContext', () => {
|
|
12
|
+
test('existing space is synchronized on device invitations', async () => {
|
|
13
|
+
const networkContext = new MemorySignalManagerContext();
|
|
14
|
+
const device1 = createServiceContext({ signalContext: networkContext });
|
|
15
|
+
await device1.createIdentity();
|
|
16
|
+
const space1 = await device1.dataSpaceManager!.createSpace();
|
|
17
|
+
|
|
18
|
+
const device2 = createServiceContext({ signalContext: networkContext });
|
|
19
|
+
await performInvitation(device1.haloInvitations, device2.haloInvitations, undefined);
|
|
20
|
+
|
|
21
|
+
const space2 = await device2.dataSpaceManager!.spaces.get(space1.key);
|
|
22
|
+
await syncItems(space1.dataPipelineController, space2!.dataPipelineController);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test('new space is synchronized on device invitations', async () => {
|
|
26
|
+
const networkContext = new MemorySignalManagerContext();
|
|
27
|
+
const device1 = createServiceContext({ signalContext: networkContext });
|
|
28
|
+
await device1.createIdentity();
|
|
29
|
+
|
|
30
|
+
const device2 = createServiceContext({ signalContext: networkContext });
|
|
31
|
+
await performInvitation(device1.haloInvitations, device2.haloInvitations, undefined);
|
|
32
|
+
|
|
33
|
+
const space1 = await device1.dataSpaceManager!.createSpace();
|
|
34
|
+
await device2.dataSpaceManager?.updated.waitForCondition(() => !!device2.dataSpaceManager!.spaces.get(space1.key));
|
|
35
|
+
const space2 = await device2.dataSpaceManager!.spaces.get(space1.key);
|
|
36
|
+
await syncItems(space1.dataPipelineController, space2!.dataPipelineController);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test('joined space is synchronized on device invitations', async () => {
|
|
40
|
+
const networkContext = new MemorySignalManagerContext();
|
|
41
|
+
const device1 = createServiceContext({ signalContext: networkContext });
|
|
42
|
+
await device1.createIdentity();
|
|
43
|
+
|
|
44
|
+
const device2 = createServiceContext({ signalContext: networkContext });
|
|
45
|
+
await performInvitation(device1.haloInvitations, device2.haloInvitations, undefined);
|
|
46
|
+
|
|
47
|
+
const identity2 = createServiceContext({ signalContext: networkContext });
|
|
48
|
+
await identity2.createIdentity();
|
|
49
|
+
const space1 = await identity2.dataSpaceManager!.createSpace();
|
|
50
|
+
await performInvitation(identity2.spaceInvitations!, device1.spaceInvitations!, space1);
|
|
51
|
+
|
|
52
|
+
await device2.dataSpaceManager?.updated.waitForCondition(() => !!device2.dataSpaceManager!.spaces.get(space1.key));
|
|
53
|
+
const space2 = await device2.dataSpaceManager!.spaces.get(space1.key);
|
|
54
|
+
await syncItems(space1.dataPipelineController, space2!.dataPipelineController);
|
|
55
|
+
});
|
|
56
|
+
});
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import { Trigger } from '@dxos/async';
|
|
6
|
+
import { CredentialConsumer, getCredentialAssertion } from '@dxos/credentials';
|
|
6
7
|
import { failUndefined } from '@dxos/debug';
|
|
7
8
|
import {
|
|
8
9
|
valueEncoding,
|
|
@@ -18,16 +19,16 @@ import { log } from '@dxos/log';
|
|
|
18
19
|
import { ModelFactory } from '@dxos/model-factory';
|
|
19
20
|
import { NetworkManager } from '@dxos/network-manager';
|
|
20
21
|
import type { FeedMessage } from '@dxos/protocols/proto/dxos/echo/feed';
|
|
22
|
+
import { Credential } from '@dxos/protocols/proto/dxos/halo/credentials';
|
|
21
23
|
import { Storage } from '@dxos/random-access-storage';
|
|
22
24
|
|
|
23
|
-
import { CreateIdentityOptions, IdentityManager } from '../identity';
|
|
25
|
+
import { CreateIdentityOptions, IdentityManager, JoinIdentityParams } from '../identity';
|
|
24
26
|
import { HaloInvitationsHandler, SpaceInvitationsHandler } from '../invitations';
|
|
25
27
|
import { DataSpaceManager } from '../spaces/data-space-manager';
|
|
26
28
|
|
|
27
29
|
/**
|
|
28
30
|
* Shared backend for all client services.
|
|
29
31
|
*/
|
|
30
|
-
|
|
31
32
|
// TODO(burdon): Rename/break-up into smaller components. And/or make members private.
|
|
32
33
|
export class ServiceContext {
|
|
33
34
|
public readonly initialized = new Trigger();
|
|
@@ -40,6 +41,8 @@ export class ServiceContext {
|
|
|
40
41
|
public readonly identityManager: IdentityManager;
|
|
41
42
|
public readonly haloInvitations: HaloInvitationsHandler;
|
|
42
43
|
|
|
44
|
+
private _deviceSpaceSync?: CredentialConsumer<any>;
|
|
45
|
+
|
|
43
46
|
// Initialized after identity is initialized.
|
|
44
47
|
public dataSpaceManager?: DataSpaceManager;
|
|
45
48
|
public spaceInvitations?: SpaceInvitationsHandler;
|
|
@@ -64,19 +67,27 @@ export class ServiceContext {
|
|
|
64
67
|
}
|
|
65
68
|
})
|
|
66
69
|
});
|
|
70
|
+
|
|
67
71
|
this.spaceManager = new SpaceManager({
|
|
68
72
|
feedStore: this.feedStore,
|
|
69
73
|
networkManager: this.networkManager
|
|
70
74
|
});
|
|
75
|
+
|
|
71
76
|
this.identityManager = new IdentityManager(
|
|
72
77
|
this.metadataStore,
|
|
73
78
|
this.keyring,
|
|
79
|
+
this.feedStore,
|
|
74
80
|
this.spaceManager
|
|
75
81
|
);
|
|
76
82
|
|
|
77
83
|
// TODO(burdon): _initialize called in multiple places.
|
|
78
84
|
// TODO(burdon): Call _initialize on success.
|
|
79
|
-
this.haloInvitations = new HaloInvitationsHandler(
|
|
85
|
+
this.haloInvitations = new HaloInvitationsHandler({
|
|
86
|
+
networkManager: this.networkManager,
|
|
87
|
+
getIdentity: () => this.identityManager.identity ?? failUndefined(),
|
|
88
|
+
acceptIdentity: this._acceptIdentity.bind(this),
|
|
89
|
+
keyring: this.keyring
|
|
90
|
+
});
|
|
80
91
|
}
|
|
81
92
|
|
|
82
93
|
async open() {
|
|
@@ -92,6 +103,7 @@ export class ServiceContext {
|
|
|
92
103
|
|
|
93
104
|
async close() {
|
|
94
105
|
log('closing...');
|
|
106
|
+
await this._deviceSpaceSync?.close();
|
|
95
107
|
await this.dataSpaceManager?.close();
|
|
96
108
|
await this.identityManager.close();
|
|
97
109
|
await this.spaceManager.close();
|
|
@@ -115,6 +127,13 @@ export class ServiceContext {
|
|
|
115
127
|
return identity;
|
|
116
128
|
}
|
|
117
129
|
|
|
130
|
+
private async _acceptIdentity(params: JoinIdentityParams) {
|
|
131
|
+
const identity = await this.identityManager.acceptIdentity(params);
|
|
132
|
+
|
|
133
|
+
await this._initialize();
|
|
134
|
+
return identity;
|
|
135
|
+
}
|
|
136
|
+
|
|
118
137
|
// Called when identity is created.
|
|
119
138
|
private async _initialize() {
|
|
120
139
|
log('initializing spaces...');
|
|
@@ -123,7 +142,10 @@ export class ServiceContext {
|
|
|
123
142
|
credentialSigner: identity.getIdentityCredentialSigner(),
|
|
124
143
|
identityKey: identity.identityKey,
|
|
125
144
|
deviceKey: identity.deviceKey,
|
|
126
|
-
profile: identity.profileDocument
|
|
145
|
+
profile: identity.profileDocument,
|
|
146
|
+
recordCredential: async (credential) => {
|
|
147
|
+
await identity.controlPipeline.writer.write({ credential: { credential } });
|
|
148
|
+
}
|
|
127
149
|
};
|
|
128
150
|
|
|
129
151
|
this.dataSpaceManager = new DataSpaceManager(
|
|
@@ -133,9 +155,11 @@ export class ServiceContext {
|
|
|
133
155
|
this.keyring,
|
|
134
156
|
signingContext,
|
|
135
157
|
this.modelFactory,
|
|
158
|
+
this.feedStore,
|
|
136
159
|
this.snapshotStore
|
|
137
160
|
);
|
|
138
161
|
await this.dataSpaceManager.open();
|
|
162
|
+
|
|
139
163
|
this.spaceInvitations = new SpaceInvitationsHandler(
|
|
140
164
|
this.networkManager,
|
|
141
165
|
this.dataSpaceManager,
|
|
@@ -143,5 +167,37 @@ export class ServiceContext {
|
|
|
143
167
|
this.keyring
|
|
144
168
|
);
|
|
145
169
|
this.initialized.wake();
|
|
170
|
+
|
|
171
|
+
this._deviceSpaceSync = identity.space.spaceState.registerProcessor({
|
|
172
|
+
process: async (credential: Credential) => {
|
|
173
|
+
const assertion = getCredentialAssertion(credential);
|
|
174
|
+
if (assertion['@type'] !== 'dxos.halo.credentials.SpaceMember') {
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
if (assertion.spaceKey.equals(identity.space.key)) {
|
|
178
|
+
// ignore halo space
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
if (!this.dataSpaceManager) {
|
|
182
|
+
log('dataSpaceManager not initialized yet, ignoring space admission', { details: assertion });
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
if (this.dataSpaceManager.spaces.has(assertion.spaceKey)) {
|
|
186
|
+
log('space already exists, ignoring space admission', { details: assertion });
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
try {
|
|
191
|
+
log('accepting space recorded in halo', { details: assertion });
|
|
192
|
+
await this.dataSpaceManager.acceptSpace({
|
|
193
|
+
spaceKey: assertion.spaceKey,
|
|
194
|
+
genesisFeedKey: assertion.genesisFeedKey
|
|
195
|
+
});
|
|
196
|
+
} catch (err) {
|
|
197
|
+
log.catch(err);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
await this._deviceSpaceSync.open();
|
|
146
202
|
}
|
|
147
203
|
}
|
|
@@ -2,12 +2,17 @@
|
|
|
2
2
|
// Copyright 2023 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { expect } from 'chai';
|
|
6
|
+
|
|
7
|
+
import { latch, Trigger } from '@dxos/async';
|
|
6
8
|
import { Config } from '@dxos/config';
|
|
9
|
+
import { verifyPresentation } from '@dxos/credentials';
|
|
10
|
+
import { PublicKey } from '@dxos/keys';
|
|
7
11
|
import { MemorySignalManagerContext } from '@dxos/messaging';
|
|
12
|
+
import { Credential } from '@dxos/protocols/proto/dxos/halo/credentials';
|
|
8
13
|
import { afterTest, describe, test } from '@dxos/test';
|
|
9
14
|
|
|
10
|
-
import { createServiceHost } from '../testing';
|
|
15
|
+
import { createMockCredential, createServiceHost } from '../testing';
|
|
11
16
|
|
|
12
17
|
describe('ClientServicesHost', () => {
|
|
13
18
|
test('queryCredentials', async () => {
|
|
@@ -15,10 +20,10 @@ describe('ClientServicesHost', () => {
|
|
|
15
20
|
await host.open();
|
|
16
21
|
afterTest(() => host.close());
|
|
17
22
|
|
|
18
|
-
await host.services.ProfileService
|
|
19
|
-
const { publicKey: spaceKey } = await host.services.SpaceService
|
|
23
|
+
await host.services.ProfileService!.createProfile({});
|
|
24
|
+
const { publicKey: spaceKey } = await host.services.SpaceService!.createSpace();
|
|
20
25
|
|
|
21
|
-
const stream = host.services.SpacesService
|
|
26
|
+
const stream = host.services.SpacesService!.queryCredentials({ spaceKey });
|
|
22
27
|
const [done, tick] = latch({ count: 3 });
|
|
23
28
|
stream.subscribe((credential) => {
|
|
24
29
|
tick();
|
|
@@ -28,4 +33,68 @@ describe('ClientServicesHost', () => {
|
|
|
28
33
|
|
|
29
34
|
await done();
|
|
30
35
|
});
|
|
36
|
+
|
|
37
|
+
test('write and query credentials', async () => {
|
|
38
|
+
const host = createServiceHost(new Config(), new MemorySignalManagerContext());
|
|
39
|
+
await host.open();
|
|
40
|
+
afterTest(() => host.close());
|
|
41
|
+
|
|
42
|
+
await host.services.ProfileService!.createProfile({});
|
|
43
|
+
|
|
44
|
+
const testCredential = await createMockCredential({
|
|
45
|
+
signer: host._serviceContext.keyring,
|
|
46
|
+
issuer: host._serviceContext.identityManager.identity!.deviceKey
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
// Test if Profile exposes haloSpace key.
|
|
50
|
+
const haloSpace = new Trigger<PublicKey>();
|
|
51
|
+
host.services.ProfileService!.subscribeProfile()!.subscribe(({ profile }) => {
|
|
52
|
+
if (profile?.haloSpace) {
|
|
53
|
+
haloSpace.wake(profile.haloSpace);
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
await host.services.SpacesService?.writeCredentials({
|
|
58
|
+
spaceKey: await haloSpace.wait(),
|
|
59
|
+
credentials: [testCredential]
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
const credentials = host.services.SpacesService!.queryCredentials({ spaceKey: await haloSpace.wait() });
|
|
63
|
+
const queriedCredential = new Trigger<Credential>();
|
|
64
|
+
credentials.subscribe((credential) => {
|
|
65
|
+
if (credential.subject.id.equals(testCredential.subject.id)) {
|
|
66
|
+
queriedCredential.wake(credential);
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
afterTest(() => credentials.close());
|
|
70
|
+
|
|
71
|
+
await queriedCredential.wait();
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test('sign presentation', async () => {
|
|
75
|
+
const host = createServiceHost(new Config(), new MemorySignalManagerContext());
|
|
76
|
+
await host.open();
|
|
77
|
+
afterTest(() => host.close());
|
|
78
|
+
|
|
79
|
+
await host.services.ProfileService!.createProfile({});
|
|
80
|
+
|
|
81
|
+
const testCredential = await createMockCredential({
|
|
82
|
+
signer: host._serviceContext.keyring,
|
|
83
|
+
issuer: host._serviceContext.identityManager.identity!.deviceKey
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
const nonce = new Uint8Array([0, 0, 0, 0]);
|
|
87
|
+
|
|
88
|
+
const presentation = await host.services.ProfileService!.signPresentation({
|
|
89
|
+
presentation: {
|
|
90
|
+
credentials: [testCredential]
|
|
91
|
+
},
|
|
92
|
+
nonce
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
expect(presentation.proofs?.[0].nonce).to.deep.equal(nonce);
|
|
96
|
+
expect(await verifyPresentation(presentation)).to.deep.equal({
|
|
97
|
+
kind: 'pass'
|
|
98
|
+
});
|
|
99
|
+
});
|
|
31
100
|
});
|