@dxos/client-services 0.6.11 → 0.6.12-main.5a87ad5
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-QYVLLBAA.mjs → chunk-5W6ZU2YY.mjs} +5154 -5061
- package/dist/lib/browser/chunk-5W6ZU2YY.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +3 -3
- package/dist/lib/browser/index.mjs.map +3 -3
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/testing/index.mjs +4 -5
- package/dist/lib/browser/testing/index.mjs.map +2 -2
- package/dist/lib/node/{chunk-F7G2TXVG.cjs → chunk-MQZYBWFJ.cjs} +4980 -4887
- package/dist/lib/node/chunk-MQZYBWFJ.cjs.map +7 -0
- package/dist/lib/node/index.cjs +46 -46
- package/dist/lib/node/index.cjs.map +3 -3
- package/dist/lib/node/meta.json +1 -1
- package/dist/lib/node/testing/index.cjs +11 -12
- package/dist/lib/node/testing/index.cjs.map +2 -2
- package/dist/lib/node-esm/chunk-2YR6LJEW.mjs +7809 -0
- package/dist/lib/node-esm/chunk-2YR6LJEW.mjs.map +7 -0
- package/dist/lib/node-esm/index.mjs +416 -0
- package/dist/lib/node-esm/index.mjs.map +7 -0
- package/dist/lib/node-esm/meta.json +1 -0
- package/dist/lib/node-esm/testing/index.mjs +418 -0
- package/dist/lib/node-esm/testing/index.mjs.map +7 -0
- package/dist/types/src/packlets/diagnostics/diagnostics-broadcast.d.ts.map +1 -1
- package/dist/types/src/packlets/identity/authenticator.node.test.d.ts +2 -0
- package/dist/types/src/packlets/identity/authenticator.node.test.d.ts.map +1 -0
- package/dist/types/src/packlets/identity/identity-manager.d.ts +19 -7
- package/dist/types/src/packlets/identity/identity-manager.d.ts.map +1 -1
- package/dist/types/src/packlets/identity/identity.d.ts +7 -1
- package/dist/types/src/packlets/identity/identity.d.ts.map +1 -1
- package/dist/types/src/packlets/invitations/invitation-host-extension.d.ts.map +1 -1
- package/dist/types/src/packlets/services/service-context.d.ts +3 -4
- package/dist/types/src/packlets/services/service-context.d.ts.map +1 -1
- package/dist/types/src/packlets/services/service-host.d.ts.map +1 -1
- package/dist/types/src/packlets/spaces/data-space-manager.d.ts +1 -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 +1 -2
- package/dist/types/src/packlets/spaces/data-space.d.ts.map +1 -1
- package/dist/types/src/packlets/spaces/edge-feed-replicator.d.ts +1 -0
- package/dist/types/src/packlets/spaces/edge-feed-replicator.d.ts.map +1 -1
- package/dist/types/src/packlets/spaces/edge-feed-replicator.test.d.ts +2 -0
- package/dist/types/src/packlets/spaces/edge-feed-replicator.test.d.ts.map +1 -0
- package/dist/types/src/packlets/spaces/epoch-migrations.d.ts +1 -1
- package/dist/types/src/packlets/spaces/epoch-migrations.d.ts.map +1 -1
- package/dist/types/src/packlets/storage/storage.d.ts.map +1 -1
- package/dist/types/src/packlets/testing/test-builder.d.ts +1 -2
- package/dist/types/src/packlets/testing/test-builder.d.ts.map +1 -1
- package/dist/types/src/packlets/worker/worker-runtime.d.ts.map +1 -1
- package/dist/types/src/testing/setup.d.ts +3 -0
- package/dist/types/src/testing/setup.d.ts.map +1 -0
- package/dist/types/src/version.d.ts +1 -1
- package/dist/types/src/version.d.ts.map +1 -1
- package/package.json +43 -39
- package/src/packlets/devices/devices-service.test.ts +4 -5
- package/src/packlets/diagnostics/diagnostics-broadcast.ts +1 -0
- package/src/packlets/identity/{authenticator.test.ts → authenticator.node.test.ts} +2 -3
- package/src/packlets/identity/identity-manager.test.ts +5 -6
- package/src/packlets/identity/identity-manager.ts +35 -19
- package/src/packlets/identity/identity-service.test.ts +4 -8
- package/src/packlets/identity/identity.test.ts +128 -239
- package/src/packlets/identity/identity.ts +38 -8
- package/src/packlets/invitations/device-invitation-protocol.test.ts +7 -4
- package/src/packlets/invitations/invitation-host-extension.ts +0 -3
- package/src/packlets/invitations/invitations-handler.test.ts +14 -7
- package/src/packlets/invitations/invitations-handler.ts +1 -1
- package/src/packlets/invitations/space-invitation-protocol.test.ts +4 -3
- package/src/packlets/logging/logging.test.ts +1 -2
- package/src/packlets/network/network-service.test.ts +2 -3
- package/src/packlets/services/service-context.test.ts +3 -1
- package/src/packlets/services/service-context.ts +24 -12
- package/src/packlets/services/service-host.test.ts +8 -12
- package/src/packlets/services/service-host.ts +2 -2
- package/src/packlets/services/service-registry.test.ts +1 -2
- package/src/packlets/spaces/data-space-manager.test.ts +2 -2
- package/src/packlets/spaces/data-space-manager.ts +4 -1
- package/src/packlets/spaces/data-space.ts +7 -2
- package/src/packlets/spaces/edge-feed-replicator.test.ts +244 -0
- package/src/packlets/spaces/edge-feed-replicator.ts +59 -21
- package/src/packlets/spaces/epoch-migrations.ts +2 -2
- package/src/packlets/spaces/notarization-plugin.test.ts +2 -3
- package/src/packlets/spaces/spaces-service.test.ts +5 -9
- package/src/packlets/storage/storage.ts +0 -1
- package/src/packlets/system/system-service.test.ts +1 -2
- package/src/packlets/testing/test-builder.ts +1 -2
- package/src/packlets/worker/worker-runtime.ts +2 -2
- package/src/testing/setup.ts +11 -0
- package/src/version.ts +1 -5
- package/dist/lib/browser/chunk-QYVLLBAA.mjs.map +0 -7
- package/dist/lib/node/chunk-F7G2TXVG.cjs.map +0 -7
- package/dist/types/src/packlets/identity/authenticator.test.d.ts +0 -2
- package/dist/types/src/packlets/identity/authenticator.test.d.ts.map +0 -1
- package/dist/types/src/packlets/services/automerge-host.test.d.ts +0 -2
- package/dist/types/src/packlets/services/automerge-host.test.d.ts.map +0 -1
- package/src/packlets/services/automerge-host.test.ts +0 -60
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
// Copyright 2022 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import expect from '
|
|
5
|
+
import { onTestFinished, describe, expect, test } from 'vitest';
|
|
6
6
|
|
|
7
|
+
import { Event } from '@dxos/async';
|
|
7
8
|
import { Context } from '@dxos/context';
|
|
8
9
|
import { CredentialGenerator, verifyCredential } from '@dxos/credentials';
|
|
9
10
|
import {
|
|
@@ -15,7 +16,9 @@ import {
|
|
|
15
16
|
SpaceProtocol,
|
|
16
17
|
valueEncoding,
|
|
17
18
|
} from '@dxos/echo-pipeline';
|
|
19
|
+
import { type EdgeConnection, type MessageListener } from '@dxos/edge-client';
|
|
18
20
|
import { FeedFactory, FeedStore } from '@dxos/feed-store';
|
|
21
|
+
import { type FeedWrapper } from '@dxos/feed-store';
|
|
19
22
|
import { Keyring } from '@dxos/keyring';
|
|
20
23
|
import { type PublicKey } from '@dxos/keys';
|
|
21
24
|
import { MemorySignalManager, MemorySignalManagerContext } from '@dxos/messaging';
|
|
@@ -24,7 +27,6 @@ import { type FeedMessage } from '@dxos/protocols/proto/dxos/echo/feed';
|
|
|
24
27
|
import { AdmittedFeed } from '@dxos/protocols/proto/dxos/halo/credentials';
|
|
25
28
|
import { createStorage, StorageType } from '@dxos/random-access-storage';
|
|
26
29
|
import { BlobStore } from '@dxos/teleport-extension-object-sync';
|
|
27
|
-
import { afterTest, describe, test } from '@dxos/test';
|
|
28
30
|
|
|
29
31
|
import { Identity } from './identity';
|
|
30
32
|
|
|
@@ -42,16 +44,108 @@ const createStores = () => {
|
|
|
42
44
|
|
|
43
45
|
describe('identity/identity', () => {
|
|
44
46
|
test('create', async () => {
|
|
47
|
+
const setup = await setupIdentity();
|
|
48
|
+
|
|
49
|
+
await writeGenesisCredential(setup);
|
|
50
|
+
|
|
51
|
+
// Wait for identity to be ready.
|
|
52
|
+
await setup.identity.ready();
|
|
53
|
+
const identitySigner = setup.identity.getIdentityCredentialSigner();
|
|
54
|
+
const credential = await identitySigner.createCredential({
|
|
55
|
+
subject: setup.identityKey,
|
|
56
|
+
assertion: {
|
|
57
|
+
'@type': 'dxos.halo.credentials.IdentityProfile',
|
|
58
|
+
profile: {
|
|
59
|
+
displayName: 'Alice',
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
expect(credential.issuer).toEqual(setup.identityKey);
|
|
65
|
+
expect(await verifyCredential(credential)).toEqual({ kind: 'pass' });
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test('two devices', async () => {
|
|
69
|
+
const signalContext = new MemorySignalManagerContext();
|
|
70
|
+
|
|
71
|
+
const owner = await setupIdentity({ signalContext });
|
|
72
|
+
await writeGenesisCredential(owner);
|
|
73
|
+
await owner.identity.ready();
|
|
74
|
+
|
|
75
|
+
const secondDevice = (
|
|
76
|
+
await setupIdentity({
|
|
77
|
+
signalContext,
|
|
78
|
+
spaceKey: owner.spaceKey,
|
|
79
|
+
identityKey: owner.identityKey,
|
|
80
|
+
genesisFeedKey: owner.controlFeed.key,
|
|
81
|
+
})
|
|
82
|
+
).identity;
|
|
83
|
+
|
|
84
|
+
//
|
|
85
|
+
// Second device admission
|
|
86
|
+
//
|
|
87
|
+
{
|
|
88
|
+
const signer = owner.identity.getIdentityCredentialSigner();
|
|
89
|
+
void owner.identity.controlPipeline.writer.write({
|
|
90
|
+
credential: {
|
|
91
|
+
credential: await signer.createCredential({
|
|
92
|
+
subject: secondDevice.deviceKey,
|
|
93
|
+
assertion: {
|
|
94
|
+
'@type': 'dxos.halo.credentials.AuthorizedDevice',
|
|
95
|
+
identityKey: owner.identityKey,
|
|
96
|
+
deviceKey: secondDevice.deviceKey,
|
|
97
|
+
},
|
|
98
|
+
}),
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
await secondDevice.ready();
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
expect(Array.from(owner.identity.authorizedDeviceKeys.keys())).toEqual([owner.deviceKey, secondDevice.deviceKey]);
|
|
106
|
+
expect(Array.from(secondDevice.authorizedDeviceKeys.keys())).toEqual([owner.deviceKey, secondDevice.deviceKey]);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test('edge feed replicator', async () => {
|
|
110
|
+
let replicationStarted = false;
|
|
111
|
+
const connectedEvent = new Event();
|
|
112
|
+
const setup = await setupIdentity({
|
|
113
|
+
edgeConnection: {
|
|
114
|
+
connected: connectedEvent,
|
|
115
|
+
open: async () => {},
|
|
116
|
+
close: async () => {},
|
|
117
|
+
addListener: (_: MessageListener): (() => void) => {
|
|
118
|
+
return () => {};
|
|
119
|
+
},
|
|
120
|
+
send: async (_) => {
|
|
121
|
+
replicationStarted = true;
|
|
122
|
+
},
|
|
123
|
+
} as EdgeConnection,
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
await writeGenesisCredential(setup);
|
|
127
|
+
connectedEvent.emit();
|
|
128
|
+
|
|
129
|
+
await expect.poll(() => replicationStarted).toBeTruthy();
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
const setupIdentity = async (args?: {
|
|
133
|
+
signalContext?: MemorySignalManagerContext;
|
|
134
|
+
spaceKey?: PublicKey;
|
|
135
|
+
identityKey?: PublicKey;
|
|
136
|
+
genesisFeedKey?: PublicKey;
|
|
137
|
+
edgeConnection?: EdgeConnection;
|
|
138
|
+
}): Promise<TestIdentitySetup> => {
|
|
45
139
|
const { storage, metadataStore, blobStore } = createStores();
|
|
46
140
|
|
|
47
141
|
const keyring = new Keyring();
|
|
48
|
-
const identityKey = await keyring.createKey();
|
|
49
142
|
const deviceKey = await keyring.createKey();
|
|
50
|
-
const
|
|
143
|
+
const identityKey = args?.identityKey ?? (await keyring.createKey());
|
|
144
|
+
const spaceKey = args?.spaceKey ?? (await keyring.createKey());
|
|
51
145
|
|
|
52
146
|
const feedStore = new FeedStore<FeedMessage>({
|
|
53
147
|
factory: new FeedFactory<FeedMessage>({
|
|
54
|
-
root: storage.createDirectory(
|
|
148
|
+
root: storage.createDirectory(),
|
|
55
149
|
signer: keyring,
|
|
56
150
|
hypercore: {
|
|
57
151
|
valueEncoding,
|
|
@@ -77,7 +171,7 @@ describe('identity/identity', () => {
|
|
|
77
171
|
},
|
|
78
172
|
blobStore,
|
|
79
173
|
networkManager: new SwarmNetworkManager({
|
|
80
|
-
signalManager: new MemorySignalManager(new MemorySignalManagerContext()),
|
|
174
|
+
signalManager: new MemorySignalManager(args?.signalContext ?? new MemorySignalManagerContext()),
|
|
81
175
|
transportFactory: MemoryTransportFactory,
|
|
82
176
|
}),
|
|
83
177
|
});
|
|
@@ -87,7 +181,7 @@ describe('identity/identity', () => {
|
|
|
87
181
|
id: await createIdFromSpaceKey(spaceKey),
|
|
88
182
|
spaceKey,
|
|
89
183
|
protocol,
|
|
90
|
-
genesisFeed: controlFeed,
|
|
184
|
+
genesisFeed: args?.genesisFeedKey ? await feedStore.openFeed(args.genesisFeedKey) : controlFeed,
|
|
91
185
|
feedProvider: (feedKey) => feedStore.openFeed(feedKey),
|
|
92
186
|
memberKey: identityKey,
|
|
93
187
|
metadataStore,
|
|
@@ -103,242 +197,37 @@ describe('identity/identity', () => {
|
|
|
103
197
|
identityKey,
|
|
104
198
|
deviceKey,
|
|
105
199
|
space,
|
|
200
|
+
edgeFeatures: args?.edgeConnection && { feedReplicator: true },
|
|
201
|
+
edgeConnection: args?.edgeConnection,
|
|
106
202
|
});
|
|
107
203
|
|
|
108
204
|
await identity.open(new Context());
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
// Identity genesis
|
|
113
|
-
//
|
|
114
|
-
{
|
|
115
|
-
const generator = new CredentialGenerator(keyring, identityKey, deviceKey);
|
|
116
|
-
const credentials = [
|
|
117
|
-
...(await generator.createSpaceGenesis(spaceKey, controlFeed.key)),
|
|
118
|
-
await generator.createDeviceAuthorization(deviceKey),
|
|
119
|
-
await generator.createFeedAdmission(spaceKey, dataFeed.key, AdmittedFeed.Designation.DATA),
|
|
120
|
-
];
|
|
121
|
-
|
|
122
|
-
for (const credential of credentials) {
|
|
123
|
-
await identity.controlPipeline.writer.write({
|
|
124
|
-
credential: { credential },
|
|
125
|
-
});
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
// Wait for identity to be ready.
|
|
130
|
-
await identity.ready();
|
|
131
|
-
const identitySigner = identity.getIdentityCredentialSigner();
|
|
132
|
-
const credential = await identitySigner.createCredential({
|
|
133
|
-
subject: identityKey,
|
|
134
|
-
assertion: {
|
|
135
|
-
'@type': 'dxos.halo.credentials.IdentityProfile',
|
|
136
|
-
profile: {
|
|
137
|
-
displayName: 'Alice',
|
|
138
|
-
},
|
|
139
|
-
},
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
expect(credential.issuer).toEqual(identityKey);
|
|
143
|
-
expect(await verifyCredential(credential)).toEqual({ kind: 'pass' });
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
test('two devices', async () => {
|
|
147
|
-
const signalContext = new MemorySignalManagerContext();
|
|
148
|
-
|
|
149
|
-
let spaceKey: PublicKey;
|
|
150
|
-
let identityKey: PublicKey;
|
|
151
|
-
let genesisFeedKey: PublicKey;
|
|
152
|
-
let identity1: Identity;
|
|
153
|
-
let identity2: Identity;
|
|
154
|
-
|
|
155
|
-
//
|
|
156
|
-
// First device
|
|
157
|
-
//
|
|
158
|
-
{
|
|
159
|
-
const { storage, metadataStore, blobStore } = createStores();
|
|
160
|
-
|
|
161
|
-
const keyring = new Keyring();
|
|
162
|
-
identityKey = await keyring.createKey();
|
|
163
|
-
const deviceKey = await keyring.createKey();
|
|
164
|
-
spaceKey = await keyring.createKey();
|
|
165
|
-
|
|
166
|
-
const feedStore = new FeedStore<FeedMessage>({
|
|
167
|
-
factory: new FeedFactory<FeedMessage>({
|
|
168
|
-
root: storage.createDirectory(),
|
|
169
|
-
signer: keyring,
|
|
170
|
-
hypercore: {
|
|
171
|
-
valueEncoding,
|
|
172
|
-
},
|
|
173
|
-
}),
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
const createFeed = async () => {
|
|
177
|
-
const feedKey = await keyring.createKey();
|
|
178
|
-
return feedStore.openFeed(feedKey, { writable: true });
|
|
179
|
-
};
|
|
180
|
-
|
|
181
|
-
const controlFeed = await createFeed();
|
|
182
|
-
genesisFeedKey = controlFeed.key;
|
|
183
|
-
|
|
184
|
-
const dataFeed = await createFeed();
|
|
185
|
-
|
|
186
|
-
const protocol = new SpaceProtocol({
|
|
187
|
-
topic: spaceKey,
|
|
188
|
-
swarmIdentity: {
|
|
189
|
-
peerKey: deviceKey,
|
|
190
|
-
identityKey,
|
|
191
|
-
credentialProvider: MOCK_AUTH_PROVIDER, // createHaloAuthProvider(createCredentialSignerWithKey(keyring, device_key)),
|
|
192
|
-
credentialAuthenticator: MOCK_AUTH_VERIFIER, // createHaloAuthVerifier(() => identity.authorizedDeviceKeys),
|
|
193
|
-
},
|
|
194
|
-
blobStore,
|
|
195
|
-
networkManager: new SwarmNetworkManager({
|
|
196
|
-
signalManager: new MemorySignalManager(signalContext),
|
|
197
|
-
transportFactory: MemoryTransportFactory,
|
|
198
|
-
}),
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
await metadataStore.setIdentityRecord({ haloSpace: { key: spaceKey }, identityKey, deviceKey });
|
|
202
|
-
const space = new Space({
|
|
203
|
-
id: await createIdFromSpaceKey(spaceKey),
|
|
204
|
-
spaceKey,
|
|
205
|
-
protocol,
|
|
206
|
-
genesisFeed: controlFeed,
|
|
207
|
-
feedProvider: (feedKey) => feedStore.openFeed(feedKey),
|
|
208
|
-
memberKey: identityKey,
|
|
209
|
-
metadataStore,
|
|
210
|
-
onDelegatedInvitationStatusChange: async () => {},
|
|
211
|
-
onMemberRolesChanged: async () => {},
|
|
212
|
-
});
|
|
213
|
-
await space.setControlFeed(controlFeed);
|
|
214
|
-
await space.setDataFeed(dataFeed);
|
|
215
|
-
|
|
216
|
-
const identity = (identity1 = new Identity({
|
|
217
|
-
signer: keyring,
|
|
218
|
-
identityKey,
|
|
219
|
-
deviceKey,
|
|
220
|
-
space,
|
|
221
|
-
}));
|
|
222
|
-
|
|
223
|
-
await identity.open(new Context());
|
|
224
|
-
afterTest(() => identity.close(new Context()));
|
|
225
|
-
|
|
226
|
-
//
|
|
227
|
-
// Identity genesis
|
|
228
|
-
//
|
|
229
|
-
{
|
|
230
|
-
const generator = new CredentialGenerator(keyring, identityKey, deviceKey);
|
|
231
|
-
const credentials = [
|
|
232
|
-
...(await generator.createSpaceGenesis(spaceKey, controlFeed.key)),
|
|
233
|
-
await generator.createDeviceAuthorization(deviceKey),
|
|
234
|
-
await generator.createFeedAdmission(spaceKey, dataFeed.key, AdmittedFeed.Designation.DATA),
|
|
235
|
-
];
|
|
236
|
-
|
|
237
|
-
for (const credential of credentials) {
|
|
238
|
-
await identity.controlPipeline.writer.write({
|
|
239
|
-
credential: { credential },
|
|
240
|
-
});
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
// Wait for identity to be ready.
|
|
245
|
-
await identity.ready();
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
//
|
|
249
|
-
// Second device
|
|
250
|
-
//
|
|
251
|
-
{
|
|
252
|
-
const { storage, metadataStore, blobStore } = createStores();
|
|
253
|
-
|
|
254
|
-
const keyring = new Keyring();
|
|
255
|
-
const deviceKey = await keyring.createKey();
|
|
256
|
-
|
|
257
|
-
const feedStore = new FeedStore<FeedMessage>({
|
|
258
|
-
factory: new FeedFactory<FeedMessage>({
|
|
259
|
-
root: storage.createDirectory(),
|
|
260
|
-
signer: keyring,
|
|
261
|
-
hypercore: {
|
|
262
|
-
valueEncoding,
|
|
263
|
-
},
|
|
264
|
-
}),
|
|
265
|
-
});
|
|
266
|
-
|
|
267
|
-
const createFeed = async () => {
|
|
268
|
-
const feedKey = await keyring.createKey();
|
|
269
|
-
return feedStore.openFeed(feedKey, { writable: true });
|
|
270
|
-
};
|
|
271
|
-
|
|
272
|
-
const controlFeed = await createFeed();
|
|
273
|
-
const dataFeed = await createFeed();
|
|
274
|
-
|
|
275
|
-
const protocol = new SpaceProtocol({
|
|
276
|
-
topic: spaceKey,
|
|
277
|
-
swarmIdentity: {
|
|
278
|
-
peerKey: deviceKey,
|
|
279
|
-
identityKey,
|
|
280
|
-
credentialProvider: MOCK_AUTH_PROVIDER, // createHaloAuthProvider(createCredentialSignerWithKey(keyring, device_key)),
|
|
281
|
-
credentialAuthenticator: MOCK_AUTH_VERIFIER, // createHaloAuthVerifier(() => identity.authorizedDeviceKeys),
|
|
282
|
-
},
|
|
283
|
-
blobStore,
|
|
284
|
-
networkManager: new SwarmNetworkManager({
|
|
285
|
-
signalManager: new MemorySignalManager(signalContext),
|
|
286
|
-
transportFactory: MemoryTransportFactory,
|
|
287
|
-
}),
|
|
288
|
-
});
|
|
205
|
+
onTestFinished(() => identity.close(new Context()));
|
|
206
|
+
return { identity, identityKey, keyring, deviceKey, controlFeed, spaceKey, dataFeed };
|
|
207
|
+
};
|
|
289
208
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
memberKey: identityKey,
|
|
302
|
-
metadataStore,
|
|
303
|
-
onDelegatedInvitationStatusChange: async () => {},
|
|
304
|
-
onMemberRolesChanged: async () => {},
|
|
209
|
+
const writeGenesisCredential = async (setup: TestIdentitySetup) => {
|
|
210
|
+
const generator = new CredentialGenerator(setup.keyring, setup.identityKey, setup.deviceKey);
|
|
211
|
+
const credentials = [
|
|
212
|
+
...(await generator.createSpaceGenesis(setup.spaceKey, setup.controlFeed.key)),
|
|
213
|
+
await generator.createDeviceAuthorization(setup.deviceKey),
|
|
214
|
+
await generator.createFeedAdmission(setup.spaceKey, setup.dataFeed.key, AdmittedFeed.Designation.DATA),
|
|
215
|
+
];
|
|
216
|
+
|
|
217
|
+
for (const credential of credentials) {
|
|
218
|
+
await setup.identity.controlPipeline.writer.write({
|
|
219
|
+
credential: { credential },
|
|
305
220
|
});
|
|
306
|
-
await space.setControlFeed(controlFeed);
|
|
307
|
-
await space.setDataFeed(dataFeed);
|
|
308
|
-
|
|
309
|
-
const identity = (identity2 = new Identity({
|
|
310
|
-
signer: keyring,
|
|
311
|
-
identityKey: identity1.identityKey,
|
|
312
|
-
deviceKey,
|
|
313
|
-
space,
|
|
314
|
-
}));
|
|
315
|
-
|
|
316
|
-
await identity.open(new Context());
|
|
317
|
-
afterTest(() => identity.close(new Context()));
|
|
318
221
|
}
|
|
319
|
-
|
|
320
|
-
//
|
|
321
|
-
// Second device admission
|
|
322
|
-
//
|
|
323
|
-
{
|
|
324
|
-
const signer = identity1.getIdentityCredentialSigner();
|
|
325
|
-
void identity1.controlPipeline.writer.write({
|
|
326
|
-
credential: {
|
|
327
|
-
credential: await signer.createCredential({
|
|
328
|
-
subject: identity2.deviceKey,
|
|
329
|
-
assertion: {
|
|
330
|
-
'@type': 'dxos.halo.credentials.AuthorizedDevice',
|
|
331
|
-
identityKey: identity1.identityKey,
|
|
332
|
-
deviceKey: identity2.deviceKey,
|
|
333
|
-
},
|
|
334
|
-
}),
|
|
335
|
-
},
|
|
336
|
-
});
|
|
337
|
-
|
|
338
|
-
await identity2.ready();
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
expect(Array.from(identity1.authorizedDeviceKeys.keys())).toEqual([identity1.deviceKey, identity2.deviceKey]);
|
|
342
|
-
expect(Array.from(identity2.authorizedDeviceKeys.keys())).toEqual([identity1.deviceKey, identity2.deviceKey]);
|
|
343
|
-
});
|
|
222
|
+
};
|
|
344
223
|
});
|
|
224
|
+
|
|
225
|
+
type TestIdentitySetup = {
|
|
226
|
+
identity: Identity;
|
|
227
|
+
keyring: Keyring;
|
|
228
|
+
identityKey: PublicKey;
|
|
229
|
+
deviceKey: PublicKey;
|
|
230
|
+
spaceKey: PublicKey;
|
|
231
|
+
controlFeed: FeedWrapper<FeedMessage>;
|
|
232
|
+
dataFeed: FeedWrapper<FeedMessage>;
|
|
233
|
+
};
|
|
@@ -14,10 +14,12 @@ import {
|
|
|
14
14
|
} from '@dxos/credentials';
|
|
15
15
|
import { type Signer } from '@dxos/crypto';
|
|
16
16
|
import { type Space } from '@dxos/echo-pipeline';
|
|
17
|
-
import {
|
|
17
|
+
import { type EdgeConnection } from '@dxos/edge-client';
|
|
18
|
+
import { writeMessages, type FeedWrapper } from '@dxos/feed-store';
|
|
18
19
|
import { invariant } from '@dxos/invariant';
|
|
19
20
|
import { PublicKey, type SpaceId } from '@dxos/keys';
|
|
20
21
|
import { log } from '@dxos/log';
|
|
22
|
+
import { type Runtime } from '@dxos/protocols/proto/dxos/config';
|
|
21
23
|
import { type FeedMessage } from '@dxos/protocols/proto/dxos/echo/feed';
|
|
22
24
|
import {
|
|
23
25
|
AdmittedFeed,
|
|
@@ -32,6 +34,7 @@ import { type ComplexMap, ComplexSet } from '@dxos/util';
|
|
|
32
34
|
|
|
33
35
|
import { TrustedKeySetAuthVerifier } from './authenticator';
|
|
34
36
|
import { DefaultSpaceStateMachine } from './default-space-state-machine';
|
|
37
|
+
import { EdgeFeedReplicator } from '../spaces';
|
|
35
38
|
|
|
36
39
|
export type IdentityParams = {
|
|
37
40
|
identityKey: PublicKey;
|
|
@@ -39,6 +42,9 @@ export type IdentityParams = {
|
|
|
39
42
|
signer: Signer;
|
|
40
43
|
space: Space;
|
|
41
44
|
presence?: Presence;
|
|
45
|
+
|
|
46
|
+
edgeConnection?: EdgeConnection;
|
|
47
|
+
edgeFeatures?: Runtime.Client.EdgeFeatures;
|
|
42
48
|
};
|
|
43
49
|
|
|
44
50
|
/**
|
|
@@ -52,6 +58,8 @@ export class Identity {
|
|
|
52
58
|
private readonly _deviceStateMachine: DeviceStateMachine;
|
|
53
59
|
private readonly _profileStateMachine: ProfileStateMachine;
|
|
54
60
|
private readonly _defaultSpaceStateMachine: DefaultSpaceStateMachine;
|
|
61
|
+
private readonly _edgeFeedReplicator?: EdgeFeedReplicator = undefined;
|
|
62
|
+
|
|
55
63
|
public readonly authVerifier: TrustedKeySetAuthVerifier;
|
|
56
64
|
|
|
57
65
|
public readonly identityKey: PublicKey;
|
|
@@ -59,15 +67,15 @@ export class Identity {
|
|
|
59
67
|
|
|
60
68
|
public readonly stateUpdate = new Event();
|
|
61
69
|
|
|
62
|
-
constructor(
|
|
63
|
-
this.space = space;
|
|
64
|
-
this._signer = signer;
|
|
65
|
-
this._presence = presence;
|
|
70
|
+
constructor(params: IdentityParams) {
|
|
71
|
+
this.space = params.space;
|
|
72
|
+
this._signer = params.signer;
|
|
73
|
+
this._presence = params.presence;
|
|
66
74
|
|
|
67
|
-
this.identityKey = identityKey;
|
|
68
|
-
this.deviceKey = deviceKey;
|
|
75
|
+
this.identityKey = params.identityKey;
|
|
76
|
+
this.deviceKey = params.deviceKey;
|
|
69
77
|
|
|
70
|
-
log.trace('dxos.halo.device', { deviceKey });
|
|
78
|
+
log.trace('dxos.halo.device', { deviceKey: params.deviceKey });
|
|
71
79
|
|
|
72
80
|
this._deviceStateMachine = new DeviceStateMachine({
|
|
73
81
|
identityKey: this.identityKey,
|
|
@@ -88,6 +96,10 @@ export class Identity {
|
|
|
88
96
|
update: this.stateUpdate,
|
|
89
97
|
authTimeout: AUTH_TIMEOUT,
|
|
90
98
|
});
|
|
99
|
+
|
|
100
|
+
if (params.edgeConnection && params.edgeFeatures?.feedReplicator) {
|
|
101
|
+
this._edgeFeedReplicator = new EdgeFeedReplicator({ messenger: params.edgeConnection, spaceId: this.space.id });
|
|
102
|
+
}
|
|
91
103
|
}
|
|
92
104
|
|
|
93
105
|
// TODO(burdon): Expose state object?
|
|
@@ -105,7 +117,14 @@ export class Identity {
|
|
|
105
117
|
await this.space.spaceState.addCredentialProcessor(this._deviceStateMachine);
|
|
106
118
|
await this.space.spaceState.addCredentialProcessor(this._profileStateMachine);
|
|
107
119
|
await this.space.spaceState.addCredentialProcessor(this._defaultSpaceStateMachine);
|
|
120
|
+
|
|
121
|
+
if (this._edgeFeedReplicator) {
|
|
122
|
+
this.space.protocol.feedAdded.append(this._onFeedAdded);
|
|
123
|
+
}
|
|
124
|
+
|
|
108
125
|
await this.space.open(ctx);
|
|
126
|
+
|
|
127
|
+
await this._edgeFeedReplicator?.open();
|
|
109
128
|
}
|
|
110
129
|
|
|
111
130
|
@trace.span()
|
|
@@ -115,6 +134,13 @@ export class Identity {
|
|
|
115
134
|
await this.space.spaceState.removeCredentialProcessor(this._defaultSpaceStateMachine);
|
|
116
135
|
await this.space.spaceState.removeCredentialProcessor(this._profileStateMachine);
|
|
117
136
|
await this.space.spaceState.removeCredentialProcessor(this._deviceStateMachine);
|
|
137
|
+
|
|
138
|
+
if (this._edgeFeedReplicator) {
|
|
139
|
+
this.space.protocol.feedAdded.remove(this._onFeedAdded);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
await this._edgeFeedReplicator?.close();
|
|
143
|
+
|
|
118
144
|
await this.space.close();
|
|
119
145
|
}
|
|
120
146
|
|
|
@@ -223,4 +249,8 @@ export class Identity {
|
|
|
223
249
|
].map((credential): FeedMessage.Payload => ({ credential: { credential } })),
|
|
224
250
|
);
|
|
225
251
|
}
|
|
252
|
+
|
|
253
|
+
private _onFeedAdded = async (feed: FeedWrapper<any>) => {
|
|
254
|
+
await this._edgeFeedReplicator!.addFeed(feed);
|
|
255
|
+
};
|
|
226
256
|
}
|
|
@@ -2,19 +2,20 @@
|
|
|
2
2
|
// Copyright 2022 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import { expect } from '
|
|
5
|
+
import { describe, expect, test, onTestFinished } from 'vitest';
|
|
6
6
|
|
|
7
7
|
import { asyncChain } from '@dxos/async';
|
|
8
8
|
import { Context } from '@dxos/context';
|
|
9
9
|
import { AlreadyJoinedError } from '@dxos/protocols';
|
|
10
10
|
import { Invitation } from '@dxos/protocols/proto/dxos/client/services';
|
|
11
|
-
import { describe, test, afterTest } from '@dxos/test';
|
|
12
11
|
|
|
13
12
|
import { type ServiceContext } from '../services';
|
|
14
13
|
import { createPeers, createServiceContext, performInvitation } from '../testing';
|
|
15
14
|
|
|
16
15
|
const closeAfterTest = async (peer: ServiceContext) => {
|
|
17
|
-
|
|
16
|
+
onTestFinished(async () => {
|
|
17
|
+
await peer.close();
|
|
18
|
+
});
|
|
18
19
|
return peer;
|
|
19
20
|
};
|
|
20
21
|
|
|
@@ -22,7 +23,9 @@ describe('services/device', () => {
|
|
|
22
23
|
test('creates identity', async () => {
|
|
23
24
|
const peer = await createServiceContext();
|
|
24
25
|
await peer.open(new Context());
|
|
25
|
-
|
|
26
|
+
onTestFinished(async () => {
|
|
27
|
+
await peer.close();
|
|
28
|
+
});
|
|
26
29
|
|
|
27
30
|
const identity = await peer.createIdentity();
|
|
28
31
|
expect(identity).not.to.be.undefined;
|
|
@@ -106,13 +106,11 @@ export class InvitationHostExtension extends RpcExtension<
|
|
|
106
106
|
|
|
107
107
|
introduce: async (request) => {
|
|
108
108
|
const { profile, invitationId } = request;
|
|
109
|
-
|
|
110
109
|
const traceId = PublicKey.random().toHex();
|
|
111
110
|
log.trace('dxos.sdk.invitation-handler.host.introduce', trace.begin({ id: traceId }));
|
|
112
111
|
|
|
113
112
|
const invitation = this._requireActiveInvitation();
|
|
114
113
|
this._assertInvitationState(Invitation.State.CONNECTED);
|
|
115
|
-
|
|
116
114
|
if (invitationId !== invitation?.invitationId) {
|
|
117
115
|
log.warn('incorrect invitationId', { expected: invitation.invitationId, actual: invitationId });
|
|
118
116
|
this._callbacks.onError(new Error('Incorrect invitationId.'));
|
|
@@ -126,7 +124,6 @@ export class InvitationHostExtension extends RpcExtension<
|
|
|
126
124
|
log('guest introduced themselves', { guestProfile: profile });
|
|
127
125
|
this.guestProfile = profile;
|
|
128
126
|
this._callbacks.onStateUpdate(Invitation.State.READY_FOR_AUTHENTICATION);
|
|
129
|
-
|
|
130
127
|
this._challenge =
|
|
131
128
|
invitation.authMethod === Invitation.AuthMethod.KNOWN_PUBLIC_KEY ? randomBytes(32) : undefined;
|
|
132
129
|
|
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import { expect } from '
|
|
5
|
+
import { beforeEach, onTestFinished, describe, expect, test } from 'vitest';
|
|
6
6
|
|
|
7
7
|
import { type PushStream, sleep, Trigger, waitForCondition } from '@dxos/async';
|
|
8
8
|
import { Context } from '@dxos/context';
|
|
9
9
|
import { PublicKey } from '@dxos/keys';
|
|
10
10
|
import { Invitation } from '@dxos/protocols/proto/dxos/client/services';
|
|
11
|
-
import {
|
|
11
|
+
import { openAndClose } from '@dxos/test-utils';
|
|
12
12
|
import { range } from '@dxos/util';
|
|
13
13
|
|
|
14
14
|
import { type InvitationProtocol } from './invitation-protocol';
|
|
@@ -34,6 +34,7 @@ type StateUpdateSink = PushStream<Invitation> & {
|
|
|
34
34
|
|
|
35
35
|
describe('InvitationHandler', () => {
|
|
36
36
|
let testBuilder: TestBuilder;
|
|
37
|
+
|
|
37
38
|
beforeEach(() => {
|
|
38
39
|
testBuilder = new TestBuilder();
|
|
39
40
|
});
|
|
@@ -163,7 +164,7 @@ describe('InvitationHandler', () => {
|
|
|
163
164
|
expect(guest.ctx.disposed).to.be.true;
|
|
164
165
|
});
|
|
165
166
|
|
|
166
|
-
test('guest gives up after trying with three hosts', async () => {
|
|
167
|
+
test('guest gives up after trying with three hosts', { timeout: 20_000 }, async () => {
|
|
167
168
|
const hosts: PeerSetup[] = [await createPeer()];
|
|
168
169
|
const [host] = hosts;
|
|
169
170
|
const invitation = await createInvitation(host, { multiUse: true });
|
|
@@ -181,7 +182,7 @@ describe('InvitationHandler', () => {
|
|
|
181
182
|
|
|
182
183
|
await sleep(10);
|
|
183
184
|
expect(guest.sink.lastState).to.eq(Invitation.State.ERROR);
|
|
184
|
-
})
|
|
185
|
+
});
|
|
185
186
|
|
|
186
187
|
test('single host - many guests', async () => {
|
|
187
188
|
const hosts: PeerSetup[] = [await createPeer()];
|
|
@@ -261,7 +262,9 @@ describe('InvitationHandler', () => {
|
|
|
261
262
|
});
|
|
262
263
|
const protocol = new SpaceInvitationProtocol(peer.dataSpaceManager, peer.identity, peer.keyring, spaceKey);
|
|
263
264
|
const ctx = new Context();
|
|
264
|
-
|
|
265
|
+
onTestFinished(async () => {
|
|
266
|
+
await ctx.dispose();
|
|
267
|
+
});
|
|
265
268
|
const sink = newStateUpdateSink();
|
|
266
269
|
return { ctx, sink, peer, protocol, handler: invitationHandler, spaceKey };
|
|
267
270
|
};
|
|
@@ -269,14 +272,18 @@ describe('InvitationHandler', () => {
|
|
|
269
272
|
const hostInvitation = async (setup: PeerSetup, invitation: Invitation) => {
|
|
270
273
|
await setup.ctx.dispose();
|
|
271
274
|
setup.ctx = new Context();
|
|
272
|
-
|
|
275
|
+
onTestFinished(async () => {
|
|
276
|
+
await setup.ctx.dispose();
|
|
277
|
+
});
|
|
273
278
|
setup.handler.handleInvitationFlow(setup.ctx, setup.sink, setup.protocol, invitation);
|
|
274
279
|
};
|
|
275
280
|
|
|
276
281
|
const acceptInvitation = async (setup: PeerSetup, invitation: Invitation): Promise<Trigger<string>> => {
|
|
277
282
|
await setup.ctx.dispose();
|
|
278
283
|
setup.ctx = new Context();
|
|
279
|
-
|
|
284
|
+
onTestFinished(async () => {
|
|
285
|
+
await setup.ctx.dispose();
|
|
286
|
+
});
|
|
280
287
|
const authCodeInput = new Trigger<string>();
|
|
281
288
|
setup.handler.acceptInvitation(setup.ctx, setup.sink, setup.protocol, invitation, authCodeInput);
|
|
282
289
|
return authCodeInput;
|
|
@@ -463,7 +463,7 @@ export class InvitationsHandler {
|
|
|
463
463
|
oldState: stateToString(invitation.state),
|
|
464
464
|
});
|
|
465
465
|
} else {
|
|
466
|
-
log
|
|
466
|
+
log('invitation state update', {
|
|
467
467
|
actor: actor?.constructor.name,
|
|
468
468
|
newState: stateToString(newState),
|
|
469
469
|
oldState: stateToString(invitation.state),
|