@dxos/client-services 0.1.22 → 0.1.24-next.6b1f434
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-ZPZBLOQW.mjs +4086 -0
- package/dist/lib/browser/chunk-ZPZBLOQW.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +68 -3027
- 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 +1694 -667
- 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 +3806 -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 +9 -9
- package/dist/types/src/packlets/identity/authenticator.d.ts.map +1 -1
- package/dist/types/src/packlets/identity/identity-manager.d.ts +8 -2
- package/dist/types/src/packlets/identity/identity-manager.d.ts.map +1 -1
- package/dist/types/src/packlets/identity/identity.d.ts +5 -4
- 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 +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 +3 -1
- 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-manager.test.d.ts +2 -0
- package/dist/types/src/packlets/spaces/data-space-manager.test.d.ts.map +1 -0
- package/dist/types/src/packlets/spaces/data-space.d.ts +20 -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 +3 -2
- 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 +31 -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 +35 -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} +9 -4
- package/dist/types/src/packlets/vault/iframe-proxy-runtime.d.ts.map +1 -0
- package/dist/types/src/packlets/vault/index.d.ts +6 -1
- package/dist/types/src/packlets/vault/index.d.ts.map +1 -1
- package/dist/types/src/packlets/vault/services.d.ts +9 -1
- package/dist/types/src/packlets/vault/services.d.ts.map +1 -1
- package/dist/types/src/packlets/vault/shell-controller.d.ts +24 -0
- package/dist/types/src/packlets/vault/shell-controller.d.ts.map +1 -0
- package/dist/types/src/packlets/vault/shell-runtime.d.ts +45 -0
- package/dist/types/src/packlets/vault/shell-runtime.d.ts.map +1 -0
- 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 +2 -1
- package/dist/types/src/packlets/vault/worker-runtime.d.ts.map +1 -1
- package/dist/types/src/packlets/vault/worker-session.d.ts +7 -4
- package/dist/types/src/packlets/vault/worker-session.d.ts.map +1 -1
- package/package.json +30 -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 +8 -7
- package/src/packlets/identity/authenticator.test.ts +4 -4
- package/src/packlets/identity/authenticator.ts +27 -17
- package/src/packlets/identity/identity-manager.test.ts +25 -16
- package/src/packlets/identity/identity-manager.ts +25 -13
- package/src/packlets/identity/identity.test.ts +22 -23
- package/src/packlets/identity/identity.ts +15 -10
- 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 +9 -12
- package/src/packlets/services/service-context.test.ts +56 -0
- package/src/packlets/services/service-context.ts +62 -10
- 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 +86 -24
- 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 +131 -0
- package/src/packlets/spaces/data-space-manager.ts +61 -15
- package/src/packlets/spaces/data-space.ts +125 -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 +10 -2
- 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 +96 -14
- package/src/packlets/vault/iframe-host-runtime.ts +131 -0
- package/src/packlets/vault/{iframe-runtime.ts → iframe-proxy-runtime.ts} +18 -3
- package/src/packlets/vault/index.ts +6 -1
- package/src/packlets/vault/services.ts +17 -1
- package/src/packlets/vault/shell-controller.ts +77 -0
- package/src/packlets/vault/shell-runtime.ts +133 -0
- package/src/packlets/vault/vault-resource-lock.ts +76 -0
- package/src/packlets/vault/worker-runtime.ts +16 -4
- package/src/packlets/vault/worker-session.ts +36 -26
- package/dist/types/src/packlets/vault/iframe-runtime.d.ts.map +0 -1
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import expect from 'expect';
|
|
6
6
|
|
|
7
7
|
import { CredentialGenerator, verifyCredential } from '@dxos/credentials';
|
|
8
|
-
import { MOCK_AUTH_PROVIDER, MOCK_AUTH_VERIFIER, Space, SpaceProtocol, valueEncoding } from '@dxos/echo-
|
|
8
|
+
import { MOCK_AUTH_PROVIDER, MOCK_AUTH_VERIFIER, Space, SpaceProtocol, valueEncoding } from '@dxos/echo-pipeline';
|
|
9
9
|
import { FeedFactory, FeedStore } from '@dxos/feed-store';
|
|
10
10
|
import { Keyring } from '@dxos/keyring';
|
|
11
11
|
import { PublicKey } from '@dxos/keys';
|
|
@@ -60,10 +60,10 @@ describe('identity/identity', () => {
|
|
|
60
60
|
spaceKey,
|
|
61
61
|
protocol,
|
|
62
62
|
genesisFeed: controlFeed,
|
|
63
|
-
controlFeed,
|
|
64
|
-
dataFeed,
|
|
65
63
|
feedProvider: (feedKey) => feedStore.openFeed(feedKey)
|
|
66
|
-
})
|
|
64
|
+
})
|
|
65
|
+
.setControlFeed(controlFeed)
|
|
66
|
+
.setDataFeed(dataFeed);
|
|
67
67
|
|
|
68
68
|
const identity = new Identity({
|
|
69
69
|
signer: keyring,
|
|
@@ -88,8 +88,7 @@ describe('identity/identity', () => {
|
|
|
88
88
|
|
|
89
89
|
for (const credential of credentials) {
|
|
90
90
|
await identity.controlPipeline.writer.write({
|
|
91
|
-
|
|
92
|
-
credential
|
|
91
|
+
credential: { credential }
|
|
93
92
|
});
|
|
94
93
|
}
|
|
95
94
|
}
|
|
@@ -166,10 +165,10 @@ describe('identity/identity', () => {
|
|
|
166
165
|
spaceKey,
|
|
167
166
|
protocol,
|
|
168
167
|
genesisFeed: controlFeed,
|
|
169
|
-
controlFeed,
|
|
170
|
-
dataFeed,
|
|
171
168
|
feedProvider: (feedKey) => feedStore.openFeed(feedKey)
|
|
172
|
-
})
|
|
169
|
+
})
|
|
170
|
+
.setControlFeed(controlFeed)
|
|
171
|
+
.setDataFeed(dataFeed);
|
|
173
172
|
|
|
174
173
|
const identity = (identity1 = new Identity({
|
|
175
174
|
signer: keyring,
|
|
@@ -194,8 +193,7 @@ describe('identity/identity', () => {
|
|
|
194
193
|
|
|
195
194
|
for (const credential of credentials) {
|
|
196
195
|
await identity.controlPipeline.writer.write({
|
|
197
|
-
|
|
198
|
-
credential
|
|
196
|
+
credential: { credential }
|
|
199
197
|
});
|
|
200
198
|
}
|
|
201
199
|
}
|
|
@@ -246,10 +244,10 @@ describe('identity/identity', () => {
|
|
|
246
244
|
spaceKey,
|
|
247
245
|
protocol,
|
|
248
246
|
genesisFeed: await feedStore.openFeed(genesisFeedKey),
|
|
249
|
-
controlFeed,
|
|
250
|
-
dataFeed,
|
|
251
247
|
feedProvider: (feedKey) => feedStore.openFeed(feedKey)
|
|
252
|
-
})
|
|
248
|
+
})
|
|
249
|
+
.setControlFeed(controlFeed)
|
|
250
|
+
.setDataFeed(dataFeed);
|
|
253
251
|
|
|
254
252
|
const identity = (identity2 = new Identity({
|
|
255
253
|
signer: keyring,
|
|
@@ -268,15 +266,16 @@ describe('identity/identity', () => {
|
|
|
268
266
|
{
|
|
269
267
|
const signer = identity1.getIdentityCredentialSigner();
|
|
270
268
|
void identity1.controlPipeline.writer.write({
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
269
|
+
credential: {
|
|
270
|
+
credential: await signer.createCredential({
|
|
271
|
+
subject: identity2.deviceKey,
|
|
272
|
+
assertion: {
|
|
273
|
+
'@type': 'dxos.halo.credentials.AuthorizedDevice',
|
|
274
|
+
identityKey: identity1.identityKey,
|
|
275
|
+
deviceKey: identity2.deviceKey
|
|
276
|
+
}
|
|
277
|
+
})
|
|
278
|
+
}
|
|
280
279
|
});
|
|
281
280
|
|
|
282
281
|
await identity2.ready();
|
|
@@ -14,16 +14,17 @@ import {
|
|
|
14
14
|
CredentialConsumer
|
|
15
15
|
} from '@dxos/credentials';
|
|
16
16
|
import { Signer } from '@dxos/crypto';
|
|
17
|
-
import {
|
|
17
|
+
import { failUndefined } from '@dxos/debug';
|
|
18
|
+
import { Space } from '@dxos/echo-pipeline';
|
|
18
19
|
import { writeMessages } from '@dxos/feed-store';
|
|
19
20
|
import { PublicKey } from '@dxos/keys';
|
|
20
21
|
import { log } from '@dxos/log';
|
|
21
|
-
import {
|
|
22
|
+
import { FeedMessage } from '@dxos/protocols/proto/dxos/echo/feed';
|
|
22
23
|
import { AdmittedFeed, ProfileDocument } from '@dxos/protocols/proto/dxos/halo/credentials';
|
|
23
24
|
import { HaloAdmissionCredentials } from '@dxos/protocols/proto/dxos/halo/invitations';
|
|
24
25
|
import { ComplexSet } from '@dxos/util';
|
|
25
26
|
|
|
26
|
-
import {
|
|
27
|
+
import { TrustedKeySetAuthVerifier } from './authenticator';
|
|
27
28
|
|
|
28
29
|
/**
|
|
29
30
|
* Timeout for the device to be added to the trusted set during auth.
|
|
@@ -45,7 +46,7 @@ export class Identity {
|
|
|
45
46
|
private readonly _signer: Signer;
|
|
46
47
|
private readonly _deviceStateMachine: CredentialConsumer<DeviceStateMachine>;
|
|
47
48
|
private readonly _profileStateMachine: CredentialConsumer<ProfileStateMachine>;
|
|
48
|
-
public readonly authVerifier:
|
|
49
|
+
public readonly authVerifier: TrustedKeySetAuthVerifier;
|
|
49
50
|
|
|
50
51
|
public readonly identityKey: PublicKey;
|
|
51
52
|
public readonly deviceKey: PublicKey;
|
|
@@ -73,8 +74,8 @@ export class Identity {
|
|
|
73
74
|
})
|
|
74
75
|
);
|
|
75
76
|
|
|
76
|
-
this.authVerifier = new
|
|
77
|
-
|
|
77
|
+
this.authVerifier = new TrustedKeySetAuthVerifier({
|
|
78
|
+
trustedKeysProvider: () => this.authorizedDeviceKeys,
|
|
78
79
|
update: this.stateUpdate,
|
|
79
80
|
authTimeout: AUTH_TIMEOUT
|
|
80
81
|
});
|
|
@@ -101,7 +102,7 @@ export class Identity {
|
|
|
101
102
|
async ready() {
|
|
102
103
|
await this._deviceStateMachine.processor.deviceChainReady.wait();
|
|
103
104
|
|
|
104
|
-
|
|
105
|
+
await this.controlPipeline.state.waitUntilReachedTargetTimeframe({ timeout: 3_000 });
|
|
105
106
|
}
|
|
106
107
|
|
|
107
108
|
get profileDocument(): ProfileDocument | undefined {
|
|
@@ -123,11 +124,15 @@ export class Identity {
|
|
|
123
124
|
return this.space.genesisFeedKey;
|
|
124
125
|
}
|
|
125
126
|
|
|
127
|
+
get deviceCredentialChain() {
|
|
128
|
+
return this._deviceStateMachine.processor.deviceCredentialChain;
|
|
129
|
+
}
|
|
130
|
+
|
|
126
131
|
getAdmissionCredentials(): HaloAdmissionCredentials {
|
|
127
132
|
return {
|
|
128
133
|
deviceKey: this.deviceKey,
|
|
129
|
-
controlFeedKey: this.space.controlFeedKey,
|
|
130
|
-
dataFeedKey: this.space.dataFeedKey
|
|
134
|
+
controlFeedKey: this.space.controlFeedKey ?? failUndefined(),
|
|
135
|
+
dataFeedKey: this.space.dataFeedKey ?? failUndefined()
|
|
131
136
|
};
|
|
132
137
|
}
|
|
133
138
|
|
|
@@ -191,7 +196,7 @@ export class Identity {
|
|
|
191
196
|
designation: AdmittedFeed.Designation.DATA
|
|
192
197
|
}
|
|
193
198
|
})
|
|
194
|
-
].map((credential):
|
|
199
|
+
].map((credential): FeedMessage.Payload => ({ credential: { credential } }))
|
|
195
200
|
);
|
|
196
201
|
}
|
|
197
202
|
}
|
|
@@ -3,16 +3,13 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import { expect } from 'chai';
|
|
6
|
-
import assert from 'node:assert';
|
|
7
6
|
|
|
8
|
-
import { asyncChain
|
|
9
|
-
import { raise } from '@dxos/debug';
|
|
10
|
-
import { PublicKey } from '@dxos/keys';
|
|
11
|
-
import { Invitation } from '@dxos/protocols/proto/dxos/client/services';
|
|
7
|
+
import { asyncChain } from '@dxos/async';
|
|
12
8
|
import { describe, test, afterTest } from '@dxos/test';
|
|
13
9
|
|
|
14
10
|
import { ServiceContext } from '../services';
|
|
15
11
|
import { createPeers, createServiceContext } from '../testing';
|
|
12
|
+
import { performInvitation } from '../testing/invitation-utils';
|
|
16
13
|
|
|
17
14
|
const closeAfterTest = async (peer: ServiceContext) => {
|
|
18
15
|
afterTest(() => peer.close());
|
|
@@ -35,45 +32,7 @@ describe('services/halo', () => {
|
|
|
35
32
|
const identity1 = await host.createIdentity();
|
|
36
33
|
expect(host.identityManager.identity).to.eq(identity1);
|
|
37
34
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
const complete1 = new Trigger<PublicKey>();
|
|
41
|
-
const complete2 = new Trigger<PublicKey>();
|
|
42
|
-
|
|
43
|
-
const authenticationCode = new Trigger<string>();
|
|
44
|
-
|
|
45
|
-
observable1.subscribe({
|
|
46
|
-
onConnecting: async (invitation1: Invitation) => {
|
|
47
|
-
const observable2 = guest.haloInvitations!.acceptInvitation(invitation1);
|
|
48
|
-
observable2.subscribe({
|
|
49
|
-
onConnecting: async () => {},
|
|
50
|
-
onConnected: async (invitation2: Invitation) => {
|
|
51
|
-
expect(invitation1.swarmKey).to.eq(invitation2.swarmKey);
|
|
52
|
-
},
|
|
53
|
-
onAuthenticating: async () => {
|
|
54
|
-
await observable2.authenticate(await authenticationCode.wait());
|
|
55
|
-
},
|
|
56
|
-
onSuccess: (invitation: Invitation) => {
|
|
57
|
-
complete2.wake(invitation.identityKey!);
|
|
58
|
-
},
|
|
59
|
-
onCancelled: () => raise(new Error()),
|
|
60
|
-
onTimeout: (err: Error) => raise(new Error(err.message)),
|
|
61
|
-
onError: (err: Error) => raise(new Error(err.message))
|
|
62
|
-
});
|
|
63
|
-
},
|
|
64
|
-
onConnected: (invitation: Invitation) => {
|
|
65
|
-
assert(invitation.authenticationCode);
|
|
66
|
-
authenticationCode.wake(invitation.authenticationCode);
|
|
67
|
-
},
|
|
68
|
-
onSuccess: (invitation: Invitation) => {
|
|
69
|
-
complete1.wake(host.identityManager.identity!.identityKey);
|
|
70
|
-
},
|
|
71
|
-
onCancelled: () => raise(new Error()),
|
|
72
|
-
onTimeout: (err: Error) => raise(new Error(err.message)),
|
|
73
|
-
onError: (err: Error) => raise(new Error(err.message))
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
const [identityKey1, identityKey2] = await Promise.all([complete1.wait(), complete2.wait()]);
|
|
77
|
-
expect(identityKey1).to.deep.eq(identityKey2);
|
|
35
|
+
await performInvitation(host.haloInvitations, guest.haloInvitations, undefined);
|
|
36
|
+
expect(guest.identityManager.identity?.identityKey).to.deep.eq(identity1.identityKey);
|
|
78
37
|
});
|
|
79
38
|
});
|
|
@@ -7,7 +7,7 @@ import assert from 'node:assert';
|
|
|
7
7
|
import { scheduleTask, sleep, Trigger } from '@dxos/async';
|
|
8
8
|
import { Context } from '@dxos/context';
|
|
9
9
|
import { generatePasscode } from '@dxos/credentials';
|
|
10
|
-
import {
|
|
10
|
+
import { Keyring } from '@dxos/keyring';
|
|
11
11
|
import { PublicKey } from '@dxos/keys';
|
|
12
12
|
import { log } from '@dxos/log';
|
|
13
13
|
import { createTeleportProtocolFactory, NetworkManager, StarTopology } from '@dxos/network-manager';
|
|
@@ -22,7 +22,7 @@ import {
|
|
|
22
22
|
} from '@dxos/protocols/proto/dxos/halo/invitations';
|
|
23
23
|
import { ExtensionContext, RpcExtension } from '@dxos/teleport';
|
|
24
24
|
|
|
25
|
-
import {
|
|
25
|
+
import { Identity, JoinIdentityParams } from '../identity';
|
|
26
26
|
import {
|
|
27
27
|
AuthenticatingInvitationProvider,
|
|
28
28
|
AUTHENTICATION_CODE_LENGTH,
|
|
@@ -33,6 +33,14 @@ import {
|
|
|
33
33
|
} from './invitations';
|
|
34
34
|
import { AbstractInvitationsHandler, InvitationsOptions } from './invitations-handler';
|
|
35
35
|
|
|
36
|
+
type HaloInvitationsHandlerParams = {
|
|
37
|
+
networkManager: NetworkManager;
|
|
38
|
+
keyring: Keyring;
|
|
39
|
+
|
|
40
|
+
getIdentity: () => Identity;
|
|
41
|
+
acceptIdentity: (identity: JoinIdentityParams) => Promise<Identity>;
|
|
42
|
+
};
|
|
43
|
+
|
|
36
44
|
/**
|
|
37
45
|
* Handles the life-cycle of Halo invitations between peers.
|
|
38
46
|
*/
|
|
@@ -40,10 +48,9 @@ import { AbstractInvitationsHandler, InvitationsOptions } from './invitations-ha
|
|
|
40
48
|
export class HaloInvitationsHandler extends AbstractInvitationsHandler {
|
|
41
49
|
// prettier-ignore
|
|
42
50
|
constructor(
|
|
43
|
-
|
|
44
|
-
private readonly _identityManager: IdentityManager
|
|
51
|
+
private readonly _params: HaloInvitationsHandlerParams
|
|
45
52
|
) {
|
|
46
|
-
super(networkManager);
|
|
53
|
+
super(_params.networkManager);
|
|
47
54
|
}
|
|
48
55
|
|
|
49
56
|
/**
|
|
@@ -52,7 +59,7 @@ export class HaloInvitationsHandler extends AbstractInvitationsHandler {
|
|
|
52
59
|
createInvitation(context: void, options?: InvitationsOptions): CancellableInvitationObservable {
|
|
53
60
|
const { type, timeout = INVITATION_TIMEOUT, swarmKey } = options ?? {};
|
|
54
61
|
assert(type !== Invitation.Type.OFFLINE);
|
|
55
|
-
const identity = this.
|
|
62
|
+
const identity = this._params.getIdentity();
|
|
56
63
|
|
|
57
64
|
// TODO(dmaretskyi): Add invitation kind: halo/space.
|
|
58
65
|
const invitation: Invitation = {
|
|
@@ -78,8 +85,8 @@ export class HaloInvitationsHandler extends AbstractInvitationsHandler {
|
|
|
78
85
|
const complete = new Trigger<PublicKey>();
|
|
79
86
|
|
|
80
87
|
// Called for every connecting peer.
|
|
81
|
-
const createExtension = ()
|
|
82
|
-
|
|
88
|
+
const createExtension = () =>
|
|
89
|
+
new HostHaloInvitationExtension({
|
|
83
90
|
requestAdmission: async () => {
|
|
84
91
|
log('responding with admission offer', {
|
|
85
92
|
host: identity.deviceKey
|
|
@@ -145,8 +152,6 @@ export class HaloInvitationsHandler extends AbstractInvitationsHandler {
|
|
|
145
152
|
});
|
|
146
153
|
}
|
|
147
154
|
});
|
|
148
|
-
return hostInvitationExtension;
|
|
149
|
-
};
|
|
150
155
|
|
|
151
156
|
scheduleTask(ctx, async () => {
|
|
152
157
|
const topic = invitation.swarmKey!;
|
|
@@ -224,18 +229,29 @@ export class HaloInvitationsHandler extends AbstractInvitationsHandler {
|
|
|
224
229
|
await extension.rpc.HaloHostService.authenticate({ authenticationCode });
|
|
225
230
|
}
|
|
226
231
|
|
|
227
|
-
|
|
232
|
+
const deviceKey = await this._params.keyring.createKey();
|
|
233
|
+
const controlFeedKey = await this._params.keyring.createKey();
|
|
234
|
+
const dataFeedKey = await this._params.keyring.createKey();
|
|
235
|
+
|
|
236
|
+
// 3. Send admission credentials to host (with local identity keys).
|
|
237
|
+
log('presenting admission credentials', { identityKey, deviceKey, controlFeedKey, dataFeedKey });
|
|
238
|
+
await extension.rpc.HaloHostService.presentAdmissionCredentials({
|
|
239
|
+
deviceKey,
|
|
240
|
+
controlFeedKey,
|
|
241
|
+
dataFeedKey
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
// 4. Create local identity.
|
|
228
245
|
// TODO(burdon): Abandon if does not complete (otherwise retry will fail).
|
|
229
|
-
const identity = await this.
|
|
246
|
+
const identity = await this._params.acceptIdentity({
|
|
230
247
|
identityKey,
|
|
248
|
+
deviceKey,
|
|
231
249
|
haloSpaceKey,
|
|
232
|
-
haloGenesisFeedKey: genesisFeedKey
|
|
250
|
+
haloGenesisFeedKey: genesisFeedKey,
|
|
251
|
+
controlFeedKey,
|
|
252
|
+
dataFeedKey
|
|
233
253
|
});
|
|
234
254
|
|
|
235
|
-
// 4. Send admission credentials to host (with local identity keys).
|
|
236
|
-
log('presenting admission credentials', { guest: identity.deviceKey, identityKey });
|
|
237
|
-
await extension.rpc.HaloHostService.presentAdmissionCredentials(identity.getAdmissionCredentials());
|
|
238
|
-
|
|
239
255
|
// 5. Success.
|
|
240
256
|
log('admitted by host', { guest: identity.deviceKey, identityKey });
|
|
241
257
|
complete.wake(identityKey);
|
|
@@ -18,7 +18,7 @@ export const AUTHENTICATION_CODE_LENGTH = 6;
|
|
|
18
18
|
export const INVITATION_TIMEOUT = 3 * 60_000; // 3 mins.
|
|
19
19
|
|
|
20
20
|
// TODO(burdon): Don't close until RPC has complete (bug).
|
|
21
|
-
export const ON_CLOSE_DELAY =
|
|
21
|
+
export const ON_CLOSE_DELAY = 1000;
|
|
22
22
|
|
|
23
23
|
export interface InvitationsService {
|
|
24
24
|
createInvitation(invitation: Invitation): Stream<Invitation>;
|
|
@@ -46,6 +46,7 @@ export interface CancellableInvitationObservable extends CancellableObservable<I
|
|
|
46
46
|
get invitation(): Invitation | undefined;
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
+
// TODO(wittjosiah): Update with Observable.value.
|
|
49
50
|
export class InvitationObservableProvider
|
|
50
51
|
extends CancellableObservableProvider<InvitationEvents>
|
|
51
52
|
implements CancellableInvitationObservable
|
|
@@ -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-pipeline/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
|
})
|
|
@@ -7,7 +7,7 @@ import assert from 'node:assert';
|
|
|
7
7
|
import { scheduleTask, sleep, Trigger } from '@dxos/async';
|
|
8
8
|
import { Context } from '@dxos/context';
|
|
9
9
|
import { createAdmissionCredentials, generatePasscode, getCredentialAssertion } from '@dxos/credentials';
|
|
10
|
-
import { SigningContext } from '@dxos/echo-
|
|
10
|
+
import { SigningContext } from '@dxos/echo-pipeline';
|
|
11
11
|
import { writeMessages } from '@dxos/feed-store';
|
|
12
12
|
import { Keyring } from '@dxos/keyring';
|
|
13
13
|
import { PublicKey } from '@dxos/keys';
|
|
@@ -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
|
+
});
|