@dxos/client-services 0.6.13 → 0.6.14-main.7bd9c89

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 (138) hide show
  1. package/dist/lib/browser/{chunk-CRXXOI45.mjs → chunk-URZYQU7T.mjs} +6383 -5185
  2. package/dist/lib/browser/chunk-URZYQU7T.mjs.map +7 -0
  3. package/dist/lib/browser/index.mjs +7 -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 +12 -7
  7. package/dist/lib/browser/testing/index.mjs.map +3 -3
  8. package/dist/lib/node/{chunk-PZ3JJJ3K.cjs → chunk-APHU7U5B.cjs} +6294 -5106
  9. package/dist/lib/node/chunk-APHU7U5B.cjs.map +7 -0
  10. package/dist/lib/node/index.cjs +50 -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 +18 -13
  14. package/dist/lib/node/testing/index.cjs.map +3 -3
  15. package/dist/lib/node-esm/chunk-L5NEKNEY.mjs +8914 -0
  16. package/dist/lib/node-esm/chunk-L5NEKNEY.mjs.map +7 -0
  17. package/dist/lib/node-esm/index.mjs +420 -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 +424 -0
  21. package/dist/lib/node-esm/testing/index.mjs.map +7 -0
  22. package/dist/types/src/index.d.ts +1 -0
  23. package/dist/types/src/index.d.ts.map +1 -1
  24. package/dist/types/src/packlets/agents/edge-agent-manager.d.ts +35 -0
  25. package/dist/types/src/packlets/agents/edge-agent-manager.d.ts.map +1 -0
  26. package/dist/types/src/packlets/agents/edge-agent-service.d.ts +10 -0
  27. package/dist/types/src/packlets/agents/edge-agent-service.d.ts.map +1 -0
  28. package/dist/types/src/packlets/agents/index.d.ts +3 -0
  29. package/dist/types/src/packlets/agents/index.d.ts.map +1 -0
  30. package/dist/types/src/packlets/diagnostics/diagnostics-broadcast.d.ts.map +1 -1
  31. package/dist/types/src/packlets/identity/authenticator.d.ts.map +1 -1
  32. package/dist/types/src/packlets/identity/authenticator.node.test.d.ts +2 -0
  33. package/dist/types/src/packlets/identity/authenticator.node.test.d.ts.map +1 -0
  34. package/dist/types/src/packlets/identity/contacts-service.d.ts +1 -1
  35. package/dist/types/src/packlets/identity/contacts-service.d.ts.map +1 -1
  36. package/dist/types/src/packlets/identity/identity-manager.d.ts +28 -9
  37. package/dist/types/src/packlets/identity/identity-manager.d.ts.map +1 -1
  38. package/dist/types/src/packlets/identity/identity-recovery-manager.d.ts +18 -0
  39. package/dist/types/src/packlets/identity/identity-recovery-manager.d.ts.map +1 -0
  40. package/dist/types/src/packlets/identity/identity-service.d.ts +7 -2
  41. package/dist/types/src/packlets/identity/identity-service.d.ts.map +1 -1
  42. package/dist/types/src/packlets/identity/identity.d.ts +12 -3
  43. package/dist/types/src/packlets/identity/identity.d.ts.map +1 -1
  44. package/dist/types/src/packlets/invitations/device-invitation-protocol.d.ts.map +1 -1
  45. package/dist/types/src/packlets/invitations/edge-invitation-handler.d.ts +30 -0
  46. package/dist/types/src/packlets/invitations/edge-invitation-handler.d.ts.map +1 -0
  47. package/dist/types/src/packlets/invitations/invitation-guest-extenstion.d.ts +2 -1
  48. package/dist/types/src/packlets/invitations/invitation-guest-extenstion.d.ts.map +1 -1
  49. package/dist/types/src/packlets/invitations/invitation-host-extension.d.ts +2 -1
  50. package/dist/types/src/packlets/invitations/invitation-host-extension.d.ts.map +1 -1
  51. package/dist/types/src/packlets/invitations/invitation-state.d.ts +19 -0
  52. package/dist/types/src/packlets/invitations/invitation-state.d.ts.map +1 -0
  53. package/dist/types/src/packlets/invitations/invitations-handler.d.ts +8 -8
  54. package/dist/types/src/packlets/invitations/invitations-handler.d.ts.map +1 -1
  55. package/dist/types/src/packlets/invitations/space-invitation-protocol.d.ts.map +1 -1
  56. package/dist/types/src/packlets/services/service-context.d.ts +14 -9
  57. package/dist/types/src/packlets/services/service-context.d.ts.map +1 -1
  58. package/dist/types/src/packlets/services/service-host.d.ts +2 -0
  59. package/dist/types/src/packlets/services/service-host.d.ts.map +1 -1
  60. package/dist/types/src/packlets/spaces/data-space-manager.d.ts +7 -3
  61. package/dist/types/src/packlets/spaces/data-space-manager.d.ts.map +1 -1
  62. package/dist/types/src/packlets/spaces/data-space.d.ts +5 -3
  63. package/dist/types/src/packlets/spaces/data-space.d.ts.map +1 -1
  64. package/dist/types/src/packlets/spaces/edge-feed-replicator.d.ts +3 -0
  65. package/dist/types/src/packlets/spaces/edge-feed-replicator.d.ts.map +1 -1
  66. package/dist/types/src/packlets/spaces/edge-feed-replicator.test.d.ts +2 -0
  67. package/dist/types/src/packlets/spaces/edge-feed-replicator.test.d.ts.map +1 -0
  68. package/dist/types/src/packlets/spaces/epoch-migrations.d.ts +1 -1
  69. package/dist/types/src/packlets/spaces/epoch-migrations.d.ts.map +1 -1
  70. package/dist/types/src/packlets/spaces/notarization-plugin.d.ts +35 -6
  71. package/dist/types/src/packlets/spaces/notarization-plugin.d.ts.map +1 -1
  72. package/dist/types/src/packlets/spaces/spaces-service.d.ts +1 -1
  73. package/dist/types/src/packlets/spaces/spaces-service.d.ts.map +1 -1
  74. package/dist/types/src/packlets/storage/storage.d.ts.map +1 -1
  75. package/dist/types/src/packlets/testing/test-builder.d.ts +1 -2
  76. package/dist/types/src/packlets/testing/test-builder.d.ts.map +1 -1
  77. package/dist/types/src/packlets/worker/worker-runtime.d.ts.map +1 -1
  78. package/dist/types/src/testing/setup.d.ts +3 -0
  79. package/dist/types/src/testing/setup.d.ts.map +1 -0
  80. package/dist/types/src/version.d.ts +1 -1
  81. package/dist/types/src/version.d.ts.map +1 -1
  82. package/package.json +43 -39
  83. package/src/index.ts +1 -0
  84. package/src/packlets/agents/edge-agent-manager.ts +163 -0
  85. package/src/packlets/agents/edge-agent-service.ts +42 -0
  86. package/src/packlets/agents/index.ts +6 -0
  87. package/src/packlets/devices/devices-service.test.ts +4 -5
  88. package/src/packlets/diagnostics/diagnostics-broadcast.ts +1 -0
  89. package/src/packlets/identity/{authenticator.test.ts → authenticator.node.test.ts} +2 -3
  90. package/src/packlets/identity/authenticator.ts +5 -2
  91. package/src/packlets/identity/contacts-service.ts +1 -1
  92. package/src/packlets/identity/identity-manager.test.ts +31 -16
  93. package/src/packlets/identity/identity-manager.ts +76 -32
  94. package/src/packlets/identity/identity-recovery-manager.ts +95 -0
  95. package/src/packlets/identity/identity-service.test.ts +5 -8
  96. package/src/packlets/identity/identity-service.ts +11 -5
  97. package/src/packlets/identity/identity.test.ts +130 -239
  98. package/src/packlets/identity/identity.ts +60 -17
  99. package/src/packlets/invitations/device-invitation-protocol.test.ts +7 -4
  100. package/src/packlets/invitations/device-invitation-protocol.ts +5 -1
  101. package/src/packlets/invitations/edge-invitation-handler.ts +185 -0
  102. package/src/packlets/invitations/invitation-guest-extenstion.ts +8 -4
  103. package/src/packlets/invitations/invitation-host-extension.ts +8 -7
  104. package/src/packlets/invitations/invitation-state.ts +111 -0
  105. package/src/packlets/invitations/invitations-handler.test.ts +16 -9
  106. package/src/packlets/invitations/invitations-handler.ts +23 -93
  107. package/src/packlets/invitations/space-invitation-protocol.test.ts +4 -3
  108. package/src/packlets/invitations/space-invitation-protocol.ts +4 -0
  109. package/src/packlets/logging/logging.test.ts +1 -2
  110. package/src/packlets/network/network-service.test.ts +2 -3
  111. package/src/packlets/services/service-context.test.ts +3 -1
  112. package/src/packlets/services/service-context.ts +110 -35
  113. package/src/packlets/services/service-host.test.ts +8 -12
  114. package/src/packlets/services/service-host.ts +25 -7
  115. package/src/packlets/services/service-registry.test.ts +1 -2
  116. package/src/packlets/spaces/data-space-manager.test.ts +2 -2
  117. package/src/packlets/spaces/data-space-manager.ts +44 -7
  118. package/src/packlets/spaces/data-space.ts +37 -6
  119. package/src/packlets/spaces/edge-feed-replicator.test.ts +252 -0
  120. package/src/packlets/spaces/edge-feed-replicator.ts +80 -22
  121. package/src/packlets/spaces/epoch-migrations.ts +2 -2
  122. package/src/packlets/spaces/notarization-plugin.test.ts +10 -7
  123. package/src/packlets/spaces/notarization-plugin.ts +196 -29
  124. package/src/packlets/spaces/spaces-service.test.ts +5 -9
  125. package/src/packlets/spaces/spaces-service.ts +6 -1
  126. package/src/packlets/storage/storage.ts +0 -1
  127. package/src/packlets/system/system-service.test.ts +1 -2
  128. package/src/packlets/testing/test-builder.ts +7 -4
  129. package/src/packlets/worker/worker-runtime.ts +2 -2
  130. package/src/testing/setup.ts +11 -0
  131. package/src/version.ts +1 -5
  132. package/dist/lib/browser/chunk-CRXXOI45.mjs.map +0 -7
  133. package/dist/lib/node/chunk-PZ3JJJ3K.cjs.map +0 -7
  134. package/dist/types/src/packlets/identity/authenticator.test.d.ts +0 -2
  135. package/dist/types/src/packlets/identity/authenticator.test.d.ts.map +0 -1
  136. package/dist/types/src/packlets/services/automerge-host.test.d.ts +0 -2
  137. package/dist/types/src/packlets/services/automerge-host.test.d.ts.map +0 -1
  138. 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,8 +171,9 @@ 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,
176
+ peerInfo: { identityKey: identityKey.toHex(), peerKey: deviceKey.toHex() },
82
177
  }),
83
178
  });
84
179
 
@@ -87,7 +182,7 @@ describe('identity/identity', () => {
87
182
  id: await createIdFromSpaceKey(spaceKey),
88
183
  spaceKey,
89
184
  protocol,
90
- genesisFeed: controlFeed,
185
+ genesisFeed: args?.genesisFeedKey ? await feedStore.openFeed(args.genesisFeedKey) : controlFeed,
91
186
  feedProvider: (feedKey) => feedStore.openFeed(feedKey),
92
187
  memberKey: identityKey,
93
188
  metadataStore,
@@ -103,242 +198,38 @@ describe('identity/identity', () => {
103
198
  identityKey,
104
199
  deviceKey,
105
200
  space,
201
+ edgeFeatures: args?.edgeConnection && { feedReplicator: true },
202
+ edgeConnection: args?.edgeConnection,
106
203
  });
107
204
 
108
205
  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
- });
206
+ await identity.joinNetwork();
207
+ onTestFinished(() => identity.close(new Context()));
208
+ return { identity, identityKey, keyring, deviceKey, controlFeed, spaceKey, dataFeed };
209
+ };
289
210
 
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 () => {},
211
+ const writeGenesisCredential = async (setup: TestIdentitySetup) => {
212
+ const generator = new CredentialGenerator(setup.keyring, setup.identityKey, setup.deviceKey);
213
+ const credentials = [
214
+ ...(await generator.createSpaceGenesis(setup.spaceKey, setup.controlFeed.key)),
215
+ await generator.createDeviceAuthorization(setup.deviceKey),
216
+ await generator.createFeedAdmission(setup.spaceKey, setup.dataFeed.key, AdmittedFeed.Designation.DATA),
217
+ ];
218
+
219
+ for (const credential of credentials) {
220
+ await setup.identity.controlPipeline.writer.write({
221
+ credential: { credential },
305
222
  });
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
223
  }
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
- });
224
+ };
344
225
  });
226
+
227
+ type TestIdentitySetup = {
228
+ identity: Identity;
229
+ keyring: Keyring;
230
+ identityKey: PublicKey;
231
+ deviceKey: PublicKey;
232
+ spaceKey: PublicKey;
233
+ controlFeed: FeedWrapper<FeedMessage>;
234
+ dataFeed: FeedWrapper<FeedMessage>;
235
+ };
@@ -14,15 +14,18 @@ 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,
24
26
  type DeviceProfileDocument,
25
27
  type ProfileDocument,
28
+ type Credential,
26
29
  } from '@dxos/protocols/proto/dxos/halo/credentials';
27
30
  import { type DeviceAdmissionRequest } from '@dxos/protocols/proto/dxos/halo/invitations';
28
31
  import { type Presence } from '@dxos/teleport-extension-gossip';
@@ -32,6 +35,7 @@ import { type ComplexMap, ComplexSet } from '@dxos/util';
32
35
 
33
36
  import { TrustedKeySetAuthVerifier } from './authenticator';
34
37
  import { DefaultSpaceStateMachine } from './default-space-state-machine';
38
+ import { EdgeFeedReplicator } from '../spaces';
35
39
 
36
40
  export type IdentityParams = {
37
41
  identityKey: PublicKey;
@@ -39,6 +43,9 @@ export type IdentityParams = {
39
43
  signer: Signer;
40
44
  space: Space;
41
45
  presence?: Presence;
46
+
47
+ edgeConnection?: EdgeConnection;
48
+ edgeFeatures?: Runtime.Client.EdgeFeatures;
42
49
  };
43
50
 
44
51
  /**
@@ -52,6 +59,8 @@ export class Identity {
52
59
  private readonly _deviceStateMachine: DeviceStateMachine;
53
60
  private readonly _profileStateMachine: ProfileStateMachine;
54
61
  private readonly _defaultSpaceStateMachine: DefaultSpaceStateMachine;
62
+ private readonly _edgeFeedReplicator?: EdgeFeedReplicator = undefined;
63
+
55
64
  public readonly authVerifier: TrustedKeySetAuthVerifier;
56
65
 
57
66
  public readonly identityKey: PublicKey;
@@ -59,15 +68,15 @@ export class Identity {
59
68
 
60
69
  public readonly stateUpdate = new Event();
61
70
 
62
- constructor({ space, signer, identityKey, deviceKey, presence }: IdentityParams) {
63
- this.space = space;
64
- this._signer = signer;
65
- this._presence = presence;
71
+ constructor(params: IdentityParams) {
72
+ this.space = params.space;
73
+ this._signer = params.signer;
74
+ this._presence = params.presence;
66
75
 
67
- this.identityKey = identityKey;
68
- this.deviceKey = deviceKey;
76
+ this.identityKey = params.identityKey;
77
+ this.deviceKey = params.deviceKey;
69
78
 
70
- log.trace('dxos.halo.device', { deviceKey });
79
+ log.trace('dxos.halo.device', { deviceKey: params.deviceKey });
71
80
 
72
81
  this._deviceStateMachine = new DeviceStateMachine({
73
82
  identityKey: this.identityKey,
@@ -88,6 +97,10 @@ export class Identity {
88
97
  update: this.stateUpdate,
89
98
  authTimeout: AUTH_TIMEOUT,
90
99
  });
100
+
101
+ if (params.edgeConnection && params.edgeFeatures?.feedReplicator) {
102
+ this._edgeFeedReplicator = new EdgeFeedReplicator({ messenger: params.edgeConnection, spaceId: this.space.id });
103
+ }
91
104
  }
92
105
 
93
106
  // TODO(burdon): Expose state object?
@@ -105,9 +118,17 @@ export class Identity {
105
118
  await this.space.spaceState.addCredentialProcessor(this._deviceStateMachine);
106
119
  await this.space.spaceState.addCredentialProcessor(this._profileStateMachine);
107
120
  await this.space.spaceState.addCredentialProcessor(this._defaultSpaceStateMachine);
121
+ if (this._edgeFeedReplicator) {
122
+ this.space.protocol.feedAdded.append(this._onFeedAdded);
123
+ }
108
124
  await this.space.open(ctx);
109
125
  }
110
126
 
127
+ public async joinNetwork() {
128
+ await this.space.startProtocol();
129
+ await this._edgeFeedReplicator?.open();
130
+ }
131
+
111
132
  @trace.span()
112
133
  async close(ctx: Context) {
113
134
  await this._presence?.close();
@@ -115,6 +136,13 @@ export class Identity {
115
136
  await this.space.spaceState.removeCredentialProcessor(this._defaultSpaceStateMachine);
116
137
  await this.space.spaceState.removeCredentialProcessor(this._profileStateMachine);
117
138
  await this.space.spaceState.removeCredentialProcessor(this._deviceStateMachine);
139
+
140
+ if (this._edgeFeedReplicator) {
141
+ this.space.protocol.feedAdded.remove(this._onFeedAdded);
142
+ }
143
+
144
+ await this._edgeFeedReplicator?.close();
145
+
118
146
  await this.space.close();
119
147
  }
120
148
 
@@ -135,6 +163,10 @@ export class Identity {
135
163
  return this.space.controlPipeline;
136
164
  }
137
165
 
166
+ get haloSpaceId() {
167
+ return this.space.id;
168
+ }
169
+
138
170
  get haloSpaceKey() {
139
171
  return this.space.key;
140
172
  }
@@ -151,6 +183,10 @@ export class Identity {
151
183
  return this._presence;
152
184
  }
153
185
 
186
+ get signer() {
187
+ return this._signer;
188
+ }
189
+
154
190
  /**
155
191
  * Issues credentials as identity.
156
192
  * Requires identity to be ready.
@@ -180,7 +216,7 @@ export class Identity {
180
216
  await this.controlPipeline.state.waitUntilTimeframe(new Timeframe([[receipt.feedKey, receipt.seq]]));
181
217
  }
182
218
 
183
- async admitDevice({ deviceKey, controlFeedKey, dataFeedKey }: DeviceAdmissionRequest) {
219
+ async admitDevice({ deviceKey, controlFeedKey, dataFeedKey }: DeviceAdmissionRequest): Promise<Credential> {
184
220
  log('Admitting device:', {
185
221
  identityKey: this.identityKey,
186
222
  hostDevice: this.deviceKey,
@@ -189,17 +225,18 @@ export class Identity {
189
225
  dataFeedKey,
190
226
  });
191
227
  const signer = this.getIdentityCredentialSigner();
228
+ const deviceCredential = await signer.createCredential({
229
+ subject: deviceKey,
230
+ assertion: {
231
+ '@type': 'dxos.halo.credentials.AuthorizedDevice',
232
+ identityKey: this.identityKey,
233
+ deviceKey,
234
+ },
235
+ });
192
236
  await writeMessages(
193
237
  this.controlPipeline.writer,
194
238
  [
195
- await signer.createCredential({
196
- subject: deviceKey,
197
- assertion: {
198
- '@type': 'dxos.halo.credentials.AuthorizedDevice',
199
- identityKey: this.identityKey,
200
- deviceKey,
201
- },
202
- }),
239
+ deviceCredential,
203
240
  await signer.createCredential({
204
241
  subject: controlFeedKey,
205
242
  assertion: {
@@ -222,5 +259,11 @@ export class Identity {
222
259
  }),
223
260
  ].map((credential): FeedMessage.Payload => ({ credential: { credential } })),
224
261
  );
262
+
263
+ return deviceCredential;
225
264
  }
265
+
266
+ private _onFeedAdded = async (feed: FeedWrapper<any>) => {
267
+ await this._edgeFeedReplicator!.addFeed(feed);
268
+ };
226
269
  }
@@ -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;
@@ -2,6 +2,7 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
+ import { getCredentialAssertion } from '@dxos/credentials';
5
6
  import { invariant } from '@dxos/invariant';
6
7
  import { type Keyring } from '@dxos/keyring';
7
8
  import { type PublicKey } from '@dxos/keys';
@@ -49,7 +50,8 @@ export class DeviceInvitationProtocol implements InvitationProtocol {
49
50
  async admit(_: Invitation, request: AdmissionRequest): Promise<AdmissionResponse> {
50
51
  invariant(request.device);
51
52
  const identity = this._getIdentity();
52
- await identity.admitDevice(request.device);
53
+ const credential = await identity.admitDevice(request.device);
54
+ invariant(getCredentialAssertion(credential)['@type'] === 'dxos.halo.credentials.AuthorizedDevice');
53
55
 
54
56
  return {
55
57
  device: {
@@ -57,6 +59,7 @@ export class DeviceInvitationProtocol implements InvitationProtocol {
57
59
  haloSpaceKey: identity.haloSpaceKey,
58
60
  genesisFeedKey: identity.haloGenesisFeedKey,
59
61
  controlTimeframe: identity.controlPipeline.state.timeframe,
62
+ credential,
60
63
  },
61
64
  };
62
65
  }
@@ -109,6 +112,7 @@ export class DeviceInvitationProtocol implements InvitationProtocol {
109
112
  dataFeedKey,
110
113
  controlTimeframe,
111
114
  deviceProfile: profile,
115
+ authorizedDeviceCredential: response.device.credential,
112
116
  });
113
117
 
114
118
  return { identityKey };