@dxos/client-services 0.6.12 → 0.6.13-main.548ca8d

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.
Files changed (118) hide show
  1. package/dist/lib/browser/{chunk-TOAILL4T.mjs → chunk-UEQIHAL2.mjs} +5838 -5151
  2. package/dist/lib/browser/chunk-UEQIHAL2.mjs.map +7 -0
  3. package/dist/lib/browser/index.mjs +3 -3
  4. package/dist/lib/browser/index.mjs.map +3 -3
  5. package/dist/lib/browser/meta.json +1 -1
  6. package/dist/lib/browser/testing/index.mjs +8 -7
  7. package/dist/lib/browser/testing/index.mjs.map +3 -3
  8. package/dist/lib/node/{chunk-H6C4XY6B.cjs → chunk-MA5EWTRH.cjs} +5858 -5175
  9. package/dist/lib/node/chunk-MA5EWTRH.cjs.map +7 -0
  10. package/dist/lib/node/index.cjs +46 -46
  11. package/dist/lib/node/index.cjs.map +3 -3
  12. package/dist/lib/node/meta.json +1 -1
  13. package/dist/lib/node/testing/index.cjs +14 -13
  14. package/dist/lib/node/testing/index.cjs.map +3 -3
  15. package/dist/lib/node-esm/chunk-AIBLDI4U.mjs +8403 -0
  16. package/dist/lib/node-esm/chunk-AIBLDI4U.mjs.map +7 -0
  17. package/dist/lib/node-esm/index.mjs +416 -0
  18. package/dist/lib/node-esm/index.mjs.map +7 -0
  19. package/dist/lib/node-esm/meta.json +1 -0
  20. package/dist/lib/node-esm/testing/index.mjs +420 -0
  21. package/dist/lib/node-esm/testing/index.mjs.map +7 -0
  22. package/dist/types/src/packlets/diagnostics/diagnostics-broadcast.d.ts.map +1 -1
  23. package/dist/types/src/packlets/identity/authenticator.d.ts.map +1 -1
  24. package/dist/types/src/packlets/identity/authenticator.node.test.d.ts +2 -0
  25. package/dist/types/src/packlets/identity/authenticator.node.test.d.ts.map +1 -0
  26. package/dist/types/src/packlets/identity/contacts-service.d.ts +1 -1
  27. package/dist/types/src/packlets/identity/contacts-service.d.ts.map +1 -1
  28. package/dist/types/src/packlets/identity/identity-manager.d.ts +19 -7
  29. package/dist/types/src/packlets/identity/identity-manager.d.ts.map +1 -1
  30. package/dist/types/src/packlets/identity/identity.d.ts +8 -1
  31. package/dist/types/src/packlets/identity/identity.d.ts.map +1 -1
  32. package/dist/types/src/packlets/invitations/edge-invitation-handler.d.ts +30 -0
  33. package/dist/types/src/packlets/invitations/edge-invitation-handler.d.ts.map +1 -0
  34. package/dist/types/src/packlets/invitations/invitation-guest-extenstion.d.ts +2 -1
  35. package/dist/types/src/packlets/invitations/invitation-guest-extenstion.d.ts.map +1 -1
  36. package/dist/types/src/packlets/invitations/invitation-host-extension.d.ts +2 -1
  37. package/dist/types/src/packlets/invitations/invitation-host-extension.d.ts.map +1 -1
  38. package/dist/types/src/packlets/invitations/invitation-state.d.ts +19 -0
  39. package/dist/types/src/packlets/invitations/invitation-state.d.ts.map +1 -0
  40. package/dist/types/src/packlets/invitations/invitations-handler.d.ts +8 -8
  41. package/dist/types/src/packlets/invitations/invitations-handler.d.ts.map +1 -1
  42. package/dist/types/src/packlets/invitations/space-invitation-protocol.d.ts.map +1 -1
  43. package/dist/types/src/packlets/services/service-context.d.ts +9 -9
  44. package/dist/types/src/packlets/services/service-context.d.ts.map +1 -1
  45. package/dist/types/src/packlets/services/service-host.d.ts +1 -0
  46. package/dist/types/src/packlets/services/service-host.d.ts.map +1 -1
  47. package/dist/types/src/packlets/spaces/data-space-manager.d.ts +6 -3
  48. package/dist/types/src/packlets/spaces/data-space-manager.d.ts.map +1 -1
  49. package/dist/types/src/packlets/spaces/data-space.d.ts +4 -3
  50. package/dist/types/src/packlets/spaces/data-space.d.ts.map +1 -1
  51. package/dist/types/src/packlets/spaces/edge-feed-replicator.d.ts +3 -0
  52. package/dist/types/src/packlets/spaces/edge-feed-replicator.d.ts.map +1 -1
  53. package/dist/types/src/packlets/spaces/edge-feed-replicator.test.d.ts +2 -0
  54. package/dist/types/src/packlets/spaces/edge-feed-replicator.test.d.ts.map +1 -0
  55. package/dist/types/src/packlets/spaces/epoch-migrations.d.ts +1 -1
  56. package/dist/types/src/packlets/spaces/epoch-migrations.d.ts.map +1 -1
  57. package/dist/types/src/packlets/spaces/notarization-plugin.d.ts +31 -6
  58. package/dist/types/src/packlets/spaces/notarization-plugin.d.ts.map +1 -1
  59. package/dist/types/src/packlets/spaces/spaces-service.d.ts +1 -1
  60. package/dist/types/src/packlets/spaces/spaces-service.d.ts.map +1 -1
  61. package/dist/types/src/packlets/storage/storage.d.ts.map +1 -1
  62. package/dist/types/src/packlets/testing/test-builder.d.ts +1 -2
  63. package/dist/types/src/packlets/testing/test-builder.d.ts.map +1 -1
  64. package/dist/types/src/packlets/worker/worker-runtime.d.ts.map +1 -1
  65. package/dist/types/src/testing/setup.d.ts +3 -0
  66. package/dist/types/src/testing/setup.d.ts.map +1 -0
  67. package/dist/types/src/version.d.ts +1 -1
  68. package/dist/types/src/version.d.ts.map +1 -1
  69. package/package.json +43 -39
  70. package/src/packlets/devices/devices-service.test.ts +4 -5
  71. package/src/packlets/diagnostics/diagnostics-broadcast.ts +1 -0
  72. package/src/packlets/identity/{authenticator.test.ts → authenticator.node.test.ts} +2 -3
  73. package/src/packlets/identity/authenticator.ts +5 -2
  74. package/src/packlets/identity/contacts-service.ts +1 -1
  75. package/src/packlets/identity/identity-manager.test.ts +5 -6
  76. package/src/packlets/identity/identity-manager.ts +35 -19
  77. package/src/packlets/identity/identity-service.test.ts +4 -8
  78. package/src/packlets/identity/identity.test.ts +128 -239
  79. package/src/packlets/identity/identity.ts +42 -8
  80. package/src/packlets/invitations/device-invitation-protocol.test.ts +7 -4
  81. package/src/packlets/invitations/edge-invitation-handler.ts +184 -0
  82. package/src/packlets/invitations/invitation-guest-extenstion.ts +8 -4
  83. package/src/packlets/invitations/invitation-host-extension.ts +8 -7
  84. package/src/packlets/invitations/invitation-state.ts +111 -0
  85. package/src/packlets/invitations/invitations-handler.test.ts +16 -9
  86. package/src/packlets/invitations/invitations-handler.ts +23 -92
  87. package/src/packlets/invitations/space-invitation-protocol.test.ts +4 -3
  88. package/src/packlets/invitations/space-invitation-protocol.ts +4 -0
  89. package/src/packlets/logging/logging.test.ts +1 -2
  90. package/src/packlets/network/network-service.test.ts +2 -3
  91. package/src/packlets/services/service-context.test.ts +3 -1
  92. package/src/packlets/services/service-context.ts +68 -31
  93. package/src/packlets/services/service-host.test.ts +8 -12
  94. package/src/packlets/services/service-host.ts +8 -6
  95. package/src/packlets/services/service-registry.test.ts +1 -2
  96. package/src/packlets/spaces/data-space-manager.test.ts +2 -2
  97. package/src/packlets/spaces/data-space-manager.ts +40 -5
  98. package/src/packlets/spaces/data-space.ts +34 -6
  99. package/src/packlets/spaces/edge-feed-replicator.test.ts +253 -0
  100. package/src/packlets/spaces/edge-feed-replicator.ts +80 -22
  101. package/src/packlets/spaces/epoch-migrations.ts +2 -2
  102. package/src/packlets/spaces/notarization-plugin.test.ts +10 -7
  103. package/src/packlets/spaces/notarization-plugin.ts +169 -29
  104. package/src/packlets/spaces/spaces-service.test.ts +5 -9
  105. package/src/packlets/spaces/spaces-service.ts +6 -1
  106. package/src/packlets/storage/storage.ts +0 -1
  107. package/src/packlets/system/system-service.test.ts +1 -2
  108. package/src/packlets/testing/test-builder.ts +3 -4
  109. package/src/packlets/worker/worker-runtime.ts +2 -2
  110. package/src/testing/setup.ts +11 -0
  111. package/src/version.ts +1 -5
  112. package/dist/lib/browser/chunk-TOAILL4T.mjs.map +0 -7
  113. package/dist/lib/node/chunk-H6C4XY6B.cjs.map +0 -7
  114. package/dist/types/src/packlets/identity/authenticator.test.d.ts +0 -2
  115. package/dist/types/src/packlets/identity/authenticator.test.d.ts.map +0 -1
  116. package/dist/types/src/packlets/services/automerge-host.test.d.ts +0 -2
  117. package/dist/types/src/packlets/services/automerge-host.test.d.ts.map +0 -1
  118. 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 'expect';
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 spaceKey = await keyring.createKey();
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('feeds'),
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
- 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
- });
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
- 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 () => {},
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 { writeMessages } from '@dxos/feed-store';
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({ space, signer, identityKey, deviceKey, presence }: IdentityParams) {
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
 
@@ -151,6 +177,10 @@ export class Identity {
151
177
  return this._presence;
152
178
  }
153
179
 
180
+ get signer() {
181
+ return this._signer;
182
+ }
183
+
154
184
  /**
155
185
  * Issues credentials as identity.
156
186
  * Requires identity to be ready.
@@ -223,4 +253,8 @@ export class Identity {
223
253
  ].map((credential): FeedMessage.Payload => ({ credential: { credential } })),
224
254
  );
225
255
  }
256
+
257
+ private _onFeedAdded = async (feed: FeedWrapper<any>) => {
258
+ await this._edgeFeedReplicator!.addFeed(feed);
259
+ };
226
260
  }
@@ -2,19 +2,20 @@
2
2
  // Copyright 2022 DXOS.org
3
3
  //
4
4
 
5
- import { expect } from 'chai';
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
- afterTest(() => peer.close());
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
- afterTest(() => peer.close());
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;