@dxos/client-services 0.6.12-staging.e11e696 → 0.6.12
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-67FEJJ6J.mjs → chunk-TOAILL4T.mjs} +5127 -5565
- package/dist/lib/browser/chunk-TOAILL4T.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 +6 -5
- package/dist/lib/browser/testing/index.mjs.map +2 -2
- package/dist/lib/node/{chunk-2KIDYJ7O.cjs → chunk-H6C4XY6B.cjs} +4908 -5346
- package/dist/lib/node/chunk-H6C4XY6B.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 +12 -11
- package/dist/lib/node/testing/index.cjs.map +2 -2
- package/dist/types/src/packlets/diagnostics/diagnostics-broadcast.d.ts.map +1 -1
- package/dist/types/src/packlets/identity/authenticator.d.ts.map +1 -1
- package/dist/types/src/packlets/identity/authenticator.test.d.ts +2 -0
- package/dist/types/src/packlets/identity/authenticator.test.d.ts.map +1 -0
- package/dist/types/src/packlets/identity/contacts-service.d.ts +1 -1
- package/dist/types/src/packlets/identity/contacts-service.d.ts.map +1 -1
- package/dist/types/src/packlets/identity/identity-manager.d.ts +7 -19
- package/dist/types/src/packlets/identity/identity-manager.d.ts.map +1 -1
- package/dist/types/src/packlets/identity/identity.d.ts +1 -8
- 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/automerge-host.test.d.ts +2 -0
- package/dist/types/src/packlets/services/automerge-host.test.d.ts.map +1 -0
- package/dist/types/src/packlets/services/service-context.d.ts +6 -7
- package/dist/types/src/packlets/services/service-context.d.ts.map +1 -1
- package/dist/types/src/packlets/services/service-host.d.ts +0 -1
- package/dist/types/src/packlets/services/service-host.d.ts.map +1 -1
- package/dist/types/src/packlets/spaces/data-space-manager.d.ts +3 -4
- package/dist/types/src/packlets/spaces/data-space-manager.d.ts.map +1 -1
- package/dist/types/src/packlets/spaces/data-space.d.ts +3 -3
- package/dist/types/src/packlets/spaces/data-space.d.ts.map +1 -1
- package/dist/types/src/packlets/spaces/edge-feed-replicator.d.ts +0 -3
- package/dist/types/src/packlets/spaces/edge-feed-replicator.d.ts.map +1 -1
- 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/spaces/notarization-plugin.d.ts +6 -31
- package/dist/types/src/packlets/spaces/notarization-plugin.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 +2 -1
- 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/version.d.ts +1 -1
- package/dist/types/src/version.d.ts.map +1 -1
- package/package.json +39 -43
- package/src/packlets/devices/devices-service.test.ts +5 -4
- package/src/packlets/diagnostics/diagnostics-broadcast.ts +0 -1
- package/src/packlets/identity/{authenticator.node.test.ts → authenticator.test.ts} +3 -2
- package/src/packlets/identity/authenticator.ts +2 -5
- package/src/packlets/identity/contacts-service.ts +1 -1
- package/src/packlets/identity/identity-manager.test.ts +6 -5
- package/src/packlets/identity/identity-manager.ts +19 -35
- package/src/packlets/identity/identity-service.test.ts +8 -4
- package/src/packlets/identity/identity.test.ts +239 -128
- package/src/packlets/identity/identity.ts +8 -42
- package/src/packlets/invitations/device-invitation-protocol.test.ts +4 -7
- package/src/packlets/invitations/invitation-host-extension.ts +3 -0
- package/src/packlets/invitations/invitations-handler.test.ts +7 -14
- package/src/packlets/invitations/invitations-handler.ts +1 -1
- package/src/packlets/invitations/space-invitation-protocol.test.ts +3 -4
- package/src/packlets/logging/logging.test.ts +2 -1
- package/src/packlets/network/network-service.test.ts +3 -2
- package/src/packlets/services/automerge-host.test.ts +60 -0
- package/src/packlets/services/service-context.test.ts +1 -3
- package/src/packlets/services/service-context.ts +28 -64
- package/src/packlets/services/service-host.test.ts +12 -8
- package/src/packlets/services/service-host.ts +6 -8
- package/src/packlets/services/service-registry.test.ts +2 -1
- package/src/packlets/spaces/data-space-manager.test.ts +2 -2
- package/src/packlets/spaces/data-space-manager.ts +2 -9
- package/src/packlets/spaces/data-space.ts +6 -30
- package/src/packlets/spaces/edge-feed-replicator.ts +22 -80
- package/src/packlets/spaces/epoch-migrations.ts +2 -2
- package/src/packlets/spaces/notarization-plugin.test.ts +7 -10
- package/src/packlets/spaces/notarization-plugin.ts +29 -169
- package/src/packlets/spaces/spaces-service.test.ts +9 -5
- package/src/packlets/storage/storage.ts +1 -0
- package/src/packlets/system/system-service.test.ts +2 -1
- package/src/packlets/testing/test-builder.ts +3 -2
- package/src/packlets/worker/worker-runtime.ts +2 -2
- package/src/version.ts +5 -1
- package/dist/lib/browser/chunk-67FEJJ6J.mjs.map +0 -7
- package/dist/lib/node/chunk-2KIDYJ7O.cjs.map +0 -7
- package/dist/lib/node-esm/chunk-36ZRRDQI.mjs +0 -8154
- package/dist/lib/node-esm/chunk-36ZRRDQI.mjs.map +0 -7
- package/dist/lib/node-esm/index.mjs +0 -416
- package/dist/lib/node-esm/index.mjs.map +0 -7
- package/dist/lib/node-esm/meta.json +0 -1
- package/dist/lib/node-esm/testing/index.mjs +0 -418
- package/dist/lib/node-esm/testing/index.mjs.map +0 -7
- package/dist/types/src/packlets/identity/authenticator.node.test.d.ts +0 -2
- package/dist/types/src/packlets/identity/authenticator.node.test.d.ts.map +0 -1
- package/dist/types/src/packlets/spaces/edge-feed-replicator.test.d.ts +0 -2
- package/dist/types/src/packlets/spaces/edge-feed-replicator.test.d.ts.map +0 -1
- package/dist/types/src/testing/setup.d.ts +0 -3
- package/dist/types/src/testing/setup.d.ts.map +0 -1
- package/src/packlets/spaces/edge-feed-replicator.test.ts +0 -251
- package/src/testing/setup.ts +0 -11
|
@@ -2,9 +2,8 @@
|
|
|
2
2
|
// Copyright 2022 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import
|
|
5
|
+
import expect from 'expect';
|
|
6
6
|
|
|
7
|
-
import { Event } from '@dxos/async';
|
|
8
7
|
import { Context } from '@dxos/context';
|
|
9
8
|
import { CredentialGenerator, verifyCredential } from '@dxos/credentials';
|
|
10
9
|
import {
|
|
@@ -16,9 +15,7 @@ import {
|
|
|
16
15
|
SpaceProtocol,
|
|
17
16
|
valueEncoding,
|
|
18
17
|
} from '@dxos/echo-pipeline';
|
|
19
|
-
import { type EdgeConnection, type MessageListener } from '@dxos/edge-client';
|
|
20
18
|
import { FeedFactory, FeedStore } from '@dxos/feed-store';
|
|
21
|
-
import { type FeedWrapper } from '@dxos/feed-store';
|
|
22
19
|
import { Keyring } from '@dxos/keyring';
|
|
23
20
|
import { type PublicKey } from '@dxos/keys';
|
|
24
21
|
import { MemorySignalManager, MemorySignalManagerContext } from '@dxos/messaging';
|
|
@@ -27,6 +24,7 @@ import { type FeedMessage } from '@dxos/protocols/proto/dxos/echo/feed';
|
|
|
27
24
|
import { AdmittedFeed } from '@dxos/protocols/proto/dxos/halo/credentials';
|
|
28
25
|
import { createStorage, StorageType } from '@dxos/random-access-storage';
|
|
29
26
|
import { BlobStore } from '@dxos/teleport-extension-object-sync';
|
|
27
|
+
import { afterTest, describe, test } from '@dxos/test';
|
|
30
28
|
|
|
31
29
|
import { Identity } from './identity';
|
|
32
30
|
|
|
@@ -44,108 +42,16 @@ const createStores = () => {
|
|
|
44
42
|
|
|
45
43
|
describe('identity/identity', () => {
|
|
46
44
|
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> => {
|
|
139
45
|
const { storage, metadataStore, blobStore } = createStores();
|
|
140
46
|
|
|
141
47
|
const keyring = new Keyring();
|
|
48
|
+
const identityKey = await keyring.createKey();
|
|
142
49
|
const deviceKey = await keyring.createKey();
|
|
143
|
-
const
|
|
144
|
-
const spaceKey = args?.spaceKey ?? (await keyring.createKey());
|
|
50
|
+
const spaceKey = await keyring.createKey();
|
|
145
51
|
|
|
146
52
|
const feedStore = new FeedStore<FeedMessage>({
|
|
147
53
|
factory: new FeedFactory<FeedMessage>({
|
|
148
|
-
root: storage.createDirectory(),
|
|
54
|
+
root: storage.createDirectory('feeds'),
|
|
149
55
|
signer: keyring,
|
|
150
56
|
hypercore: {
|
|
151
57
|
valueEncoding,
|
|
@@ -171,7 +77,7 @@ describe('identity/identity', () => {
|
|
|
171
77
|
},
|
|
172
78
|
blobStore,
|
|
173
79
|
networkManager: new SwarmNetworkManager({
|
|
174
|
-
signalManager: new MemorySignalManager(
|
|
80
|
+
signalManager: new MemorySignalManager(new MemorySignalManagerContext()),
|
|
175
81
|
transportFactory: MemoryTransportFactory,
|
|
176
82
|
}),
|
|
177
83
|
});
|
|
@@ -181,7 +87,7 @@ describe('identity/identity', () => {
|
|
|
181
87
|
id: await createIdFromSpaceKey(spaceKey),
|
|
182
88
|
spaceKey,
|
|
183
89
|
protocol,
|
|
184
|
-
genesisFeed:
|
|
90
|
+
genesisFeed: controlFeed,
|
|
185
91
|
feedProvider: (feedKey) => feedStore.openFeed(feedKey),
|
|
186
92
|
memberKey: identityKey,
|
|
187
93
|
metadataStore,
|
|
@@ -197,37 +103,242 @@ describe('identity/identity', () => {
|
|
|
197
103
|
identityKey,
|
|
198
104
|
deviceKey,
|
|
199
105
|
space,
|
|
200
|
-
edgeFeatures: args?.edgeConnection && { feedReplicator: true },
|
|
201
|
-
edgeConnection: args?.edgeConnection,
|
|
202
106
|
});
|
|
203
107
|
|
|
204
108
|
await identity.open(new Context());
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
109
|
+
afterTest(() => identity.close(new Context()));
|
|
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
|
+
});
|
|
208
145
|
|
|
209
|
-
|
|
210
|
-
const
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
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 () => {},
|
|
220
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();
|
|
221
246
|
}
|
|
222
|
-
};
|
|
223
|
-
});
|
|
224
247
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
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
|
+
});
|
|
289
|
+
|
|
290
|
+
await metadataStore.setIdentityRecord({
|
|
291
|
+
haloSpace: { key: spaceKey },
|
|
292
|
+
identityKey: identity1.identityKey,
|
|
293
|
+
deviceKey,
|
|
294
|
+
});
|
|
295
|
+
const space = new Space({
|
|
296
|
+
id: await createIdFromSpaceKey(spaceKey),
|
|
297
|
+
spaceKey,
|
|
298
|
+
protocol,
|
|
299
|
+
genesisFeed: await feedStore.openFeed(genesisFeedKey),
|
|
300
|
+
feedProvider: (feedKey) => feedStore.openFeed(feedKey),
|
|
301
|
+
memberKey: identityKey,
|
|
302
|
+
metadataStore,
|
|
303
|
+
onDelegatedInvitationStatusChange: async () => {},
|
|
304
|
+
onMemberRolesChanged: async () => {},
|
|
305
|
+
});
|
|
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
|
+
}
|
|
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
|
+
});
|
|
344
|
+
});
|
|
@@ -14,12 +14,10 @@ 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 {
|
|
18
|
-
import { writeMessages, type FeedWrapper } from '@dxos/feed-store';
|
|
17
|
+
import { writeMessages } from '@dxos/feed-store';
|
|
19
18
|
import { invariant } from '@dxos/invariant';
|
|
20
19
|
import { PublicKey, type SpaceId } from '@dxos/keys';
|
|
21
20
|
import { log } from '@dxos/log';
|
|
22
|
-
import { type Runtime } from '@dxos/protocols/proto/dxos/config';
|
|
23
21
|
import { type FeedMessage } from '@dxos/protocols/proto/dxos/echo/feed';
|
|
24
22
|
import {
|
|
25
23
|
AdmittedFeed,
|
|
@@ -34,7 +32,6 @@ import { type ComplexMap, ComplexSet } from '@dxos/util';
|
|
|
34
32
|
|
|
35
33
|
import { TrustedKeySetAuthVerifier } from './authenticator';
|
|
36
34
|
import { DefaultSpaceStateMachine } from './default-space-state-machine';
|
|
37
|
-
import { EdgeFeedReplicator } from '../spaces';
|
|
38
35
|
|
|
39
36
|
export type IdentityParams = {
|
|
40
37
|
identityKey: PublicKey;
|
|
@@ -42,9 +39,6 @@ export type IdentityParams = {
|
|
|
42
39
|
signer: Signer;
|
|
43
40
|
space: Space;
|
|
44
41
|
presence?: Presence;
|
|
45
|
-
|
|
46
|
-
edgeConnection?: EdgeConnection;
|
|
47
|
-
edgeFeatures?: Runtime.Client.EdgeFeatures;
|
|
48
42
|
};
|
|
49
43
|
|
|
50
44
|
/**
|
|
@@ -58,8 +52,6 @@ export class Identity {
|
|
|
58
52
|
private readonly _deviceStateMachine: DeviceStateMachine;
|
|
59
53
|
private readonly _profileStateMachine: ProfileStateMachine;
|
|
60
54
|
private readonly _defaultSpaceStateMachine: DefaultSpaceStateMachine;
|
|
61
|
-
private readonly _edgeFeedReplicator?: EdgeFeedReplicator = undefined;
|
|
62
|
-
|
|
63
55
|
public readonly authVerifier: TrustedKeySetAuthVerifier;
|
|
64
56
|
|
|
65
57
|
public readonly identityKey: PublicKey;
|
|
@@ -67,15 +59,15 @@ export class Identity {
|
|
|
67
59
|
|
|
68
60
|
public readonly stateUpdate = new Event();
|
|
69
61
|
|
|
70
|
-
constructor(
|
|
71
|
-
this.space =
|
|
72
|
-
this._signer =
|
|
73
|
-
this._presence =
|
|
62
|
+
constructor({ space, signer, identityKey, deviceKey, presence }: IdentityParams) {
|
|
63
|
+
this.space = space;
|
|
64
|
+
this._signer = signer;
|
|
65
|
+
this._presence = presence;
|
|
74
66
|
|
|
75
|
-
this.identityKey =
|
|
76
|
-
this.deviceKey =
|
|
67
|
+
this.identityKey = identityKey;
|
|
68
|
+
this.deviceKey = deviceKey;
|
|
77
69
|
|
|
78
|
-
log.trace('dxos.halo.device', { deviceKey
|
|
70
|
+
log.trace('dxos.halo.device', { deviceKey });
|
|
79
71
|
|
|
80
72
|
this._deviceStateMachine = new DeviceStateMachine({
|
|
81
73
|
identityKey: this.identityKey,
|
|
@@ -96,10 +88,6 @@ export class Identity {
|
|
|
96
88
|
update: this.stateUpdate,
|
|
97
89
|
authTimeout: AUTH_TIMEOUT,
|
|
98
90
|
});
|
|
99
|
-
|
|
100
|
-
if (params.edgeConnection && params.edgeFeatures?.feedReplicator) {
|
|
101
|
-
this._edgeFeedReplicator = new EdgeFeedReplicator({ messenger: params.edgeConnection, spaceId: this.space.id });
|
|
102
|
-
}
|
|
103
91
|
}
|
|
104
92
|
|
|
105
93
|
// TODO(burdon): Expose state object?
|
|
@@ -117,14 +105,7 @@ export class Identity {
|
|
|
117
105
|
await this.space.spaceState.addCredentialProcessor(this._deviceStateMachine);
|
|
118
106
|
await this.space.spaceState.addCredentialProcessor(this._profileStateMachine);
|
|
119
107
|
await this.space.spaceState.addCredentialProcessor(this._defaultSpaceStateMachine);
|
|
120
|
-
|
|
121
|
-
if (this._edgeFeedReplicator) {
|
|
122
|
-
this.space.protocol.feedAdded.append(this._onFeedAdded);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
108
|
await this.space.open(ctx);
|
|
126
|
-
|
|
127
|
-
await this._edgeFeedReplicator?.open();
|
|
128
109
|
}
|
|
129
110
|
|
|
130
111
|
@trace.span()
|
|
@@ -134,13 +115,6 @@ export class Identity {
|
|
|
134
115
|
await this.space.spaceState.removeCredentialProcessor(this._defaultSpaceStateMachine);
|
|
135
116
|
await this.space.spaceState.removeCredentialProcessor(this._profileStateMachine);
|
|
136
117
|
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
|
-
|
|
144
118
|
await this.space.close();
|
|
145
119
|
}
|
|
146
120
|
|
|
@@ -177,10 +151,6 @@ export class Identity {
|
|
|
177
151
|
return this._presence;
|
|
178
152
|
}
|
|
179
153
|
|
|
180
|
-
get signer() {
|
|
181
|
-
return this._signer;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
154
|
/**
|
|
185
155
|
* Issues credentials as identity.
|
|
186
156
|
* Requires identity to be ready.
|
|
@@ -253,8 +223,4 @@ export class Identity {
|
|
|
253
223
|
].map((credential): FeedMessage.Payload => ({ credential: { credential } })),
|
|
254
224
|
);
|
|
255
225
|
}
|
|
256
|
-
|
|
257
|
-
private _onFeedAdded = async (feed: FeedWrapper<any>) => {
|
|
258
|
-
await this._edgeFeedReplicator!.addFeed(feed);
|
|
259
|
-
};
|
|
260
226
|
}
|
|
@@ -2,20 +2,19 @@
|
|
|
2
2
|
// Copyright 2022 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { expect } from 'chai';
|
|
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';
|
|
11
12
|
|
|
12
13
|
import { type ServiceContext } from '../services';
|
|
13
14
|
import { createPeers, createServiceContext, performInvitation } from '../testing';
|
|
14
15
|
|
|
15
16
|
const closeAfterTest = async (peer: ServiceContext) => {
|
|
16
|
-
|
|
17
|
-
await peer.close();
|
|
18
|
-
});
|
|
17
|
+
afterTest(() => peer.close());
|
|
19
18
|
return peer;
|
|
20
19
|
};
|
|
21
20
|
|
|
@@ -23,9 +22,7 @@ describe('services/device', () => {
|
|
|
23
22
|
test('creates identity', async () => {
|
|
24
23
|
const peer = await createServiceContext();
|
|
25
24
|
await peer.open(new Context());
|
|
26
|
-
|
|
27
|
-
await peer.close();
|
|
28
|
-
});
|
|
25
|
+
afterTest(() => peer.close());
|
|
29
26
|
|
|
30
27
|
const identity = await peer.createIdentity();
|
|
31
28
|
expect(identity).not.to.be.undefined;
|
|
@@ -106,11 +106,13 @@ export class InvitationHostExtension extends RpcExtension<
|
|
|
106
106
|
|
|
107
107
|
introduce: async (request) => {
|
|
108
108
|
const { profile, invitationId } = request;
|
|
109
|
+
|
|
109
110
|
const traceId = PublicKey.random().toHex();
|
|
110
111
|
log.trace('dxos.sdk.invitation-handler.host.introduce', trace.begin({ id: traceId }));
|
|
111
112
|
|
|
112
113
|
const invitation = this._requireActiveInvitation();
|
|
113
114
|
this._assertInvitationState(Invitation.State.CONNECTED);
|
|
115
|
+
|
|
114
116
|
if (invitationId !== invitation?.invitationId) {
|
|
115
117
|
log.warn('incorrect invitationId', { expected: invitation.invitationId, actual: invitationId });
|
|
116
118
|
this._callbacks.onError(new Error('Incorrect invitationId.'));
|
|
@@ -124,6 +126,7 @@ export class InvitationHostExtension extends RpcExtension<
|
|
|
124
126
|
log('guest introduced themselves', { guestProfile: profile });
|
|
125
127
|
this.guestProfile = profile;
|
|
126
128
|
this._callbacks.onStateUpdate(Invitation.State.READY_FOR_AUTHENTICATION);
|
|
129
|
+
|
|
127
130
|
this._challenge =
|
|
128
131
|
invitation.authMethod === Invitation.AuthMethod.KNOWN_PUBLIC_KEY ? randomBytes(32) : undefined;
|
|
129
132
|
|
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { expect } from 'chai';
|
|
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 { openAndClose } from '@dxos/test
|
|
11
|
+
import { afterTest, describe, openAndClose, test } from '@dxos/test';
|
|
12
12
|
import { range } from '@dxos/util';
|
|
13
13
|
|
|
14
14
|
import { type InvitationProtocol } from './invitation-protocol';
|
|
@@ -34,7 +34,6 @@ type StateUpdateSink = PushStream<Invitation> & {
|
|
|
34
34
|
|
|
35
35
|
describe('InvitationHandler', () => {
|
|
36
36
|
let testBuilder: TestBuilder;
|
|
37
|
-
|
|
38
37
|
beforeEach(() => {
|
|
39
38
|
testBuilder = new TestBuilder();
|
|
40
39
|
});
|
|
@@ -164,7 +163,7 @@ describe('InvitationHandler', () => {
|
|
|
164
163
|
expect(guest.ctx.disposed).to.be.true;
|
|
165
164
|
});
|
|
166
165
|
|
|
167
|
-
test('guest gives up after trying with three hosts',
|
|
166
|
+
test('guest gives up after trying with three hosts', async () => {
|
|
168
167
|
const hosts: PeerSetup[] = [await createPeer()];
|
|
169
168
|
const [host] = hosts;
|
|
170
169
|
const invitation = await createInvitation(host, { multiUse: true });
|
|
@@ -182,7 +181,7 @@ describe('InvitationHandler', () => {
|
|
|
182
181
|
|
|
183
182
|
await sleep(10);
|
|
184
183
|
expect(guest.sink.lastState).to.eq(Invitation.State.ERROR);
|
|
185
|
-
});
|
|
184
|
+
}).timeout(20_000);
|
|
186
185
|
|
|
187
186
|
test('single host - many guests', async () => {
|
|
188
187
|
const hosts: PeerSetup[] = [await createPeer()];
|
|
@@ -262,9 +261,7 @@ describe('InvitationHandler', () => {
|
|
|
262
261
|
});
|
|
263
262
|
const protocol = new SpaceInvitationProtocol(peer.dataSpaceManager, peer.identity, peer.keyring, spaceKey);
|
|
264
263
|
const ctx = new Context();
|
|
265
|
-
|
|
266
|
-
await ctx.dispose();
|
|
267
|
-
});
|
|
264
|
+
afterTest(() => ctx.dispose());
|
|
268
265
|
const sink = newStateUpdateSink();
|
|
269
266
|
return { ctx, sink, peer, protocol, handler: invitationHandler, spaceKey };
|
|
270
267
|
};
|
|
@@ -272,18 +269,14 @@ describe('InvitationHandler', () => {
|
|
|
272
269
|
const hostInvitation = async (setup: PeerSetup, invitation: Invitation) => {
|
|
273
270
|
await setup.ctx.dispose();
|
|
274
271
|
setup.ctx = new Context();
|
|
275
|
-
|
|
276
|
-
await setup.ctx.dispose();
|
|
277
|
-
});
|
|
272
|
+
afterTest(() => setup.ctx.dispose());
|
|
278
273
|
setup.handler.handleInvitationFlow(setup.ctx, setup.sink, setup.protocol, invitation);
|
|
279
274
|
};
|
|
280
275
|
|
|
281
276
|
const acceptInvitation = async (setup: PeerSetup, invitation: Invitation): Promise<Trigger<string>> => {
|
|
282
277
|
await setup.ctx.dispose();
|
|
283
278
|
setup.ctx = new Context();
|
|
284
|
-
|
|
285
|
-
await setup.ctx.dispose();
|
|
286
|
-
});
|
|
279
|
+
afterTest(() => setup.ctx.dispose());
|
|
287
280
|
const authCodeInput = new Trigger<string>();
|
|
288
281
|
setup.handler.acceptInvitation(setup.ctx, setup.sink, setup.protocol, invitation, authCodeInput);
|
|
289
282
|
return authCodeInput;
|
|
@@ -463,7 +463,7 @@ export class InvitationsHandler {
|
|
|
463
463
|
oldState: stateToString(invitation.state),
|
|
464
464
|
});
|
|
465
465
|
} else {
|
|
466
|
-
log('invitation state update', {
|
|
466
|
+
log.info('invitation state update', {
|
|
467
467
|
actor: actor?.constructor.name,
|
|
468
468
|
newState: stateToString(newState),
|
|
469
469
|
oldState: stateToString(invitation.state),
|