@dxos/echo-db 2.33.1 → 2.33.2-dev.18eaaced

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 (217) hide show
  1. package/dist/src/echo.d.ts +8 -8
  2. package/dist/src/echo.d.ts.map +1 -1
  3. package/dist/src/echo.js +15 -16
  4. package/dist/src/echo.js.map +1 -1
  5. package/dist/src/echo.test.js +7 -13
  6. package/dist/src/echo.test.js.map +1 -1
  7. package/dist/src/halo/contact-manager.d.ts +4 -4
  8. package/dist/src/halo/contact-manager.d.ts.map +1 -1
  9. package/dist/src/halo/contact-manager.js +10 -4
  10. package/dist/src/halo/contact-manager.js.map +1 -1
  11. package/dist/src/halo/halo-factory.d.ts +14 -9
  12. package/dist/src/halo/halo-factory.d.ts.map +1 -1
  13. package/dist/src/halo/halo-factory.js +47 -14
  14. package/dist/src/halo/halo-factory.js.map +1 -1
  15. package/dist/src/halo/halo-party.d.ts +35 -7
  16. package/dist/src/halo/halo-party.d.ts.map +1 -1
  17. package/dist/src/halo/halo-party.js +97 -18
  18. package/dist/src/halo/halo-party.js.map +1 -1
  19. package/dist/src/halo/halo.d.ts +14 -12
  20. package/dist/src/halo/halo.d.ts.map +1 -1
  21. package/dist/src/halo/halo.js +31 -29
  22. package/dist/src/halo/halo.js.map +1 -1
  23. package/dist/src/halo/halo.test.d.ts +2 -0
  24. package/dist/src/halo/halo.test.d.ts.map +1 -0
  25. package/dist/src/halo/halo.test.js +162 -0
  26. package/dist/src/halo/halo.test.js.map +1 -0
  27. package/dist/src/halo/identity-manager.d.ts +10 -8
  28. package/dist/src/halo/identity-manager.d.ts.map +1 -1
  29. package/dist/src/halo/identity-manager.js +24 -20
  30. package/dist/src/halo/identity-manager.js.map +1 -1
  31. package/dist/src/halo/identity.d.ts +12 -6
  32. package/dist/src/halo/identity.d.ts.map +1 -1
  33. package/dist/src/halo/identity.js +11 -15
  34. package/dist/src/halo/identity.js.map +1 -1
  35. package/dist/src/halo/index.d.ts +1 -1
  36. package/dist/src/halo/index.d.ts.map +1 -1
  37. package/dist/src/halo/index.js +1 -1
  38. package/dist/src/halo/index.js.map +1 -1
  39. package/dist/src/halo/party-opener.js +1 -1
  40. package/dist/src/halo/party-opener.js.map +1 -1
  41. package/dist/src/halo/preferences.d.ts +9 -9
  42. package/dist/src/halo/preferences.d.ts.map +1 -1
  43. package/dist/src/halo/preferences.js +25 -15
  44. package/dist/src/halo/preferences.js.map +1 -1
  45. package/dist/src/index.d.ts +1 -1
  46. package/dist/src/index.js +1 -1
  47. package/dist/src/invitations/greeting-initiator.d.ts +14 -4
  48. package/dist/src/invitations/greeting-initiator.d.ts.map +1 -1
  49. package/dist/src/invitations/greeting-initiator.js +15 -16
  50. package/dist/src/invitations/greeting-initiator.js.map +1 -1
  51. package/dist/src/invitations/greeting-responder.d.ts +3 -3
  52. package/dist/src/invitations/greeting-responder.d.ts.map +1 -1
  53. package/dist/src/invitations/greeting-responder.js +3 -4
  54. package/dist/src/invitations/greeting-responder.js.map +1 -1
  55. package/dist/src/invitations/halo-recovery-initiator.d.ts +6 -4
  56. package/dist/src/invitations/halo-recovery-initiator.d.ts.map +1 -1
  57. package/dist/src/invitations/halo-recovery-initiator.js +15 -26
  58. package/dist/src/invitations/halo-recovery-initiator.js.map +1 -1
  59. package/dist/src/invitations/index.d.ts +1 -1
  60. package/dist/src/invitations/index.js +1 -1
  61. package/dist/src/invitations/{invitation-manager.d.ts → invitation-factory.d.ts} +5 -6
  62. package/dist/src/invitations/invitation-factory.d.ts.map +1 -0
  63. package/dist/src/invitations/{invitation-manager.js → invitation-factory.js} +9 -15
  64. package/dist/src/invitations/invitation-factory.js.map +1 -0
  65. package/dist/src/invitations/offline-invitation-claimer.d.ts +3 -3
  66. package/dist/src/invitations/offline-invitation-claimer.js +1 -1
  67. package/dist/src/parties/{party-internal.d.ts → data-party.d.ts} +13 -11
  68. package/dist/src/parties/data-party.d.ts.map +1 -0
  69. package/dist/src/parties/{party-internal.js → data-party.js} +27 -37
  70. package/dist/src/parties/data-party.js.map +1 -0
  71. package/dist/src/parties/index.d.ts +1 -2
  72. package/dist/src/parties/index.d.ts.map +1 -1
  73. package/dist/src/parties/index.js +1 -2
  74. package/dist/src/parties/index.js.map +1 -1
  75. package/dist/src/parties/party-factory.d.ts +10 -11
  76. package/dist/src/parties/party-factory.d.ts.map +1 -1
  77. package/dist/src/parties/party-factory.js +25 -16
  78. package/dist/src/parties/party-factory.js.map +1 -1
  79. package/dist/src/parties/party-manager.d.ts +9 -9
  80. package/dist/src/parties/party-manager.d.ts.map +1 -1
  81. package/dist/src/parties/party-manager.js +13 -13
  82. package/dist/src/parties/party-manager.js.map +1 -1
  83. package/dist/src/parties/party-manager.test.js +69 -68
  84. package/dist/src/parties/party-manager.test.js.map +1 -1
  85. package/dist/src/{halo → parties}/party-preferences.d.ts +3 -3
  86. package/dist/src/parties/party-preferences.d.ts.map +1 -0
  87. package/dist/src/{halo → parties}/party-preferences.js +3 -3
  88. package/dist/src/parties/party-preferences.js.map +1 -0
  89. package/dist/src/pipeline/index.d.ts +3 -1
  90. package/dist/src/pipeline/index.d.ts.map +1 -1
  91. package/dist/src/pipeline/index.js +3 -1
  92. package/dist/src/pipeline/index.js.map +1 -1
  93. package/dist/src/{metadata → pipeline}/metadata-store.d.ts +0 -0
  94. package/dist/src/{metadata → pipeline}/metadata-store.d.ts.map +1 -1
  95. package/dist/src/{metadata → pipeline}/metadata-store.js +0 -0
  96. package/dist/src/{metadata → pipeline}/metadata-store.js.map +1 -1
  97. package/dist/src/{metadata → pipeline}/metadata-store.test.d.ts +0 -0
  98. package/dist/src/pipeline/metadata-store.test.d.ts.map +1 -0
  99. package/dist/src/{metadata → pipeline}/metadata-store.test.js +0 -0
  100. package/dist/src/{metadata → pipeline}/metadata-store.test.js.map +1 -1
  101. package/dist/src/{parties → pipeline}/party-core.d.ts +0 -0
  102. package/dist/src/pipeline/party-core.d.ts.map +1 -0
  103. package/dist/src/{parties → pipeline}/party-core.js +8 -4
  104. package/dist/src/pipeline/party-core.js.map +1 -0
  105. package/dist/src/{parties → pipeline}/party-core.test.d.ts +0 -0
  106. package/dist/src/pipeline/party-core.test.d.ts.map +1 -0
  107. package/dist/src/{parties → pipeline}/party-core.test.js +3 -4
  108. package/dist/src/pipeline/party-core.test.js.map +1 -0
  109. package/dist/src/pipeline/party-feed-provider.d.ts +3 -2
  110. package/dist/src/pipeline/party-feed-provider.d.ts.map +1 -1
  111. package/dist/src/pipeline/party-feed-provider.js +40 -19
  112. package/dist/src/pipeline/party-feed-provider.js.map +1 -1
  113. package/dist/src/protocol/auth-plugin.d.ts +7 -0
  114. package/dist/src/protocol/auth-plugin.d.ts.map +1 -0
  115. package/dist/src/protocol/auth-plugin.js +16 -0
  116. package/dist/src/protocol/auth-plugin.js.map +1 -0
  117. package/dist/src/protocol/authenticator.d.ts +14 -0
  118. package/dist/src/protocol/authenticator.d.ts.map +1 -0
  119. package/dist/src/protocol/authenticator.js +31 -0
  120. package/dist/src/protocol/authenticator.js.map +1 -0
  121. package/dist/src/protocol/authenticator.test.d.ts +2 -0
  122. package/dist/src/protocol/authenticator.test.d.ts.map +1 -0
  123. package/dist/src/protocol/authenticator.test.js +45 -0
  124. package/dist/src/protocol/authenticator.test.js.map +1 -0
  125. package/dist/src/protocol/credentials-signer.d.ts +31 -0
  126. package/dist/src/protocol/credentials-signer.d.ts.map +1 -0
  127. package/dist/src/protocol/credentials-signer.js +53 -0
  128. package/dist/src/protocol/credentials-signer.js.map +1 -0
  129. package/dist/src/protocol/halo-recovery-plugin.d.ts +10 -0
  130. package/dist/src/protocol/halo-recovery-plugin.d.ts.map +1 -0
  131. package/dist/src/protocol/halo-recovery-plugin.js +18 -0
  132. package/dist/src/protocol/halo-recovery-plugin.js.map +1 -0
  133. package/dist/src/protocol/index.d.ts +7 -0
  134. package/dist/src/protocol/index.d.ts.map +1 -0
  135. package/dist/src/{metadata → protocol}/index.js +7 -2
  136. package/dist/src/protocol/index.js.map +1 -0
  137. package/dist/src/protocol/offline-invitation-plugin.d.ts +9 -0
  138. package/dist/src/protocol/offline-invitation-plugin.d.ts.map +1 -0
  139. package/dist/src/protocol/offline-invitation-plugin.js +17 -0
  140. package/dist/src/protocol/offline-invitation-plugin.js.map +1 -0
  141. package/dist/src/{pipeline → protocol}/party-protocol-factory.d.ts +7 -17
  142. package/dist/src/protocol/party-protocol-factory.d.ts.map +1 -0
  143. package/dist/src/{pipeline → protocol}/party-protocol-factory.js +7 -47
  144. package/dist/src/protocol/party-protocol-factory.js.map +1 -0
  145. package/dist/src/snapshots/snapshot-generator.d.ts +1 -1
  146. package/dist/src/snapshots/snapshot-generator.d.ts.map +1 -1
  147. package/dist/src/testing/testing-factories.js.map +1 -1
  148. package/dist/src/testing/testing.d.ts +2 -2
  149. package/dist/src/testing/testing.d.ts.map +1 -1
  150. package/dist/src/testing/testing.js +1 -7
  151. package/dist/src/testing/testing.js.map +1 -1
  152. package/dist/tsconfig.tsbuildinfo +1 -1
  153. package/package.json +17 -17
  154. package/src/echo.test.ts +8 -14
  155. package/src/echo.ts +107 -109
  156. package/src/halo/contact-manager.ts +9 -5
  157. package/src/halo/halo-factory.ts +82 -19
  158. package/src/halo/halo-party.ts +137 -21
  159. package/src/halo/halo.test.ts +198 -0
  160. package/src/halo/halo.ts +40 -38
  161. package/src/halo/identity-manager.ts +33 -28
  162. package/src/halo/identity.ts +19 -23
  163. package/src/halo/index.ts +1 -1
  164. package/src/halo/party-opener.ts +1 -1
  165. package/src/halo/preferences.ts +29 -18
  166. package/src/index.ts +1 -1
  167. package/src/invitations/greeting-initiator.ts +37 -32
  168. package/src/invitations/greeting-responder.ts +4 -5
  169. package/src/invitations/halo-recovery-initiator.ts +16 -24
  170. package/src/invitations/index.ts +1 -1
  171. package/src/invitations/{invitation-manager.ts → invitation-factory.ts} +8 -15
  172. package/src/invitations/offline-invitation-claimer.ts +3 -3
  173. package/src/parties/{party-internal.ts → data-party.ts} +30 -55
  174. package/src/parties/index.ts +1 -2
  175. package/src/parties/party-factory.ts +31 -21
  176. package/src/parties/party-manager.test.ts +99 -73
  177. package/src/parties/party-manager.ts +17 -17
  178. package/src/{halo → parties}/party-preferences.ts +3 -3
  179. package/src/pipeline/index.ts +3 -1
  180. package/src/{metadata → pipeline}/metadata-store.test.ts +0 -0
  181. package/src/{metadata → pipeline}/metadata-store.ts +0 -0
  182. package/src/{parties → pipeline}/party-core.test.ts +1 -2
  183. package/src/{parties → pipeline}/party-core.ts +12 -7
  184. package/src/pipeline/party-feed-provider.ts +32 -21
  185. package/src/protocol/auth-plugin.ts +14 -0
  186. package/src/protocol/authenticator.test.ts +66 -0
  187. package/src/protocol/authenticator.ts +56 -0
  188. package/src/protocol/credentials-signer.ts +60 -0
  189. package/src/protocol/halo-recovery-plugin.ts +20 -0
  190. package/src/protocol/index.ts +10 -0
  191. package/src/protocol/offline-invitation-plugin.ts +19 -0
  192. package/src/{pipeline → protocol}/party-protocol-factory.ts +10 -82
  193. package/src/snapshots/snapshot-generator.ts +1 -1
  194. package/src/testing/testing-factories.ts +2 -2
  195. package/src/testing/testing.ts +3 -9
  196. package/dist/src/halo/party-preferences.d.ts.map +0 -1
  197. package/dist/src/halo/party-preferences.js.map +0 -1
  198. package/dist/src/invitations/invitation-manager.d.ts.map +0 -1
  199. package/dist/src/invitations/invitation-manager.js.map +0 -1
  200. package/dist/src/metadata/index.d.ts +0 -2
  201. package/dist/src/metadata/index.d.ts.map +0 -1
  202. package/dist/src/metadata/index.js.map +0 -1
  203. package/dist/src/metadata/metadata-store.test.d.ts.map +0 -1
  204. package/dist/src/parties/authenticator.d.ts +0 -5
  205. package/dist/src/parties/authenticator.d.ts.map +0 -1
  206. package/dist/src/parties/authenticator.js +0 -27
  207. package/dist/src/parties/authenticator.js.map +0 -1
  208. package/dist/src/parties/party-core.d.ts.map +0 -1
  209. package/dist/src/parties/party-core.js.map +0 -1
  210. package/dist/src/parties/party-core.test.d.ts.map +0 -1
  211. package/dist/src/parties/party-core.test.js.map +0 -1
  212. package/dist/src/parties/party-internal.d.ts.map +0 -1
  213. package/dist/src/parties/party-internal.js.map +0 -1
  214. package/dist/src/pipeline/party-protocol-factory.d.ts.map +0 -1
  215. package/dist/src/pipeline/party-protocol-factory.js.map +0 -1
  216. package/src/metadata/index.ts +0 -5
  217. package/src/parties/authenticator.ts +0 -31
@@ -12,13 +12,13 @@ import { it as test } from 'mocha';
12
12
  import { latch } from '@dxos/async';
13
13
  import {
14
14
  createPartyGenesisMessage,
15
+ defaultSecretProvider,
15
16
  Keyring, KeyType,
16
17
  SecretProvider,
17
18
  SecretValidator
18
19
  } from '@dxos/credentials';
19
20
  import {
20
- createKeyPair, generateSeedPhrase,
21
- keyPairFromSeedPhrase, PublicKey,
21
+ createKeyPair, PublicKey,
22
22
  randomBytes,
23
23
  sign,
24
24
  SIGNATURE_LENGTH, verify
@@ -30,18 +30,17 @@ import { ModelFactory } from '@dxos/model-factory';
30
30
  import { NetworkManager } from '@dxos/network-manager';
31
31
  import { ObjectModel } from '@dxos/object-model';
32
32
  import { createStorage, StorageType } from '@dxos/random-access-multi-storage';
33
- import { afterTest } from '@dxos/testutils';
33
+ import { afterTest, testTimeout } from '@dxos/testutils';
34
34
 
35
35
  import { Item } from '../api';
36
- import { autoPartyOpener, HaloFactory, IdentityManager } from '../halo';
37
- import { OfflineInvitationClaimer } from '../invitations';
38
- import { MetadataStore } from '../metadata';
39
- import { PartyFeedProvider } from '../pipeline';
36
+ import { HaloFactory, Identity, IdentityManager } from '../halo';
37
+ import { defaultInvitationAuthenticator, OfflineInvitationClaimer } from '../invitations';
38
+ import { MetadataStore, PartyFeedProvider } from '../pipeline';
40
39
  import { SnapshotStore } from '../snapshots';
41
40
  import { messageLogger } from '../testing';
42
41
  import { createRamStorage } from '../util';
42
+ import { PARTY_ITEM_TYPE } from './data-party';
43
43
  import { PartyFactory } from './party-factory';
44
- import { PARTY_ITEM_TYPE } from './party-internal';
45
44
  import { PartyManager } from './party-manager';
46
45
 
47
46
  const log = debug('dxos:echo:parties:party-manager:test');
@@ -55,62 +54,53 @@ const log = debug('dxos:echo:parties:party-manager:test');
55
54
  * @param open - Open the PartyManager
56
55
  * @param createIdentity - Create the identity key record.
57
56
  */
58
- const setup = async (open = true, createIdentity = true) => {
57
+ const setup = async () => {
59
58
  const keyring = new Keyring();
60
59
  const metadataStore = new MetadataStore(createRamStorage());
61
60
  const feedStore = new FeedStore(createStorage('', StorageType.RAM), { valueEncoding: codec });
62
61
 
63
- let seedPhrase;
64
- if (createIdentity) {
65
- seedPhrase = generateSeedPhrase();
66
- const keyPair = keyPairFromSeedPhrase(seedPhrase);
67
- await keyring.addKeyRecord({
68
- publicKey: PublicKey.from(keyPair.publicKey),
69
- secretKey: keyPair.secretKey,
70
- type: KeyType.IDENTITY
71
- });
62
+ const identityKey = await keyring.createKeyRecord({ type: KeyType.IDENTITY });
72
63
 
73
- assert(keyring.keys.length === 1);
74
- }
64
+ assert(keyring.keys.length === 1);
75
65
 
76
66
  const snapshotStore = new SnapshotStore(createStorage('', StorageType.RAM));
77
67
  const modelFactory = new ModelFactory().registerModel(ObjectModel);
78
68
  const networkManager = new NetworkManager();
79
69
  const feedProviderFactory = (partyKey: PublicKey) => new PartyFeedProvider(metadataStore, keyring, feedStore, partyKey);
80
- const partyFactory = new PartyFactory(
81
- () => identityManager.identity,
70
+
71
+ const haloFactory = new HaloFactory(
82
72
  networkManager,
83
73
  modelFactory,
84
74
  snapshotStore,
85
75
  feedProviderFactory,
76
+ keyring,
86
77
  {
87
78
  writeLogger: messageLogger('<<<'),
88
79
  readLogger: messageLogger('>>>')
89
80
  }
90
81
  );
91
-
92
- const haloFactory: HaloFactory = new HaloFactory(partyFactory, networkManager, keyring);
93
- const identityManager = new IdentityManager(keyring, haloFactory, metadataStore);
94
- const partyManager = new PartyManager(metadataStore, snapshotStore, () => identityManager.identity, partyFactory);
95
- afterTest(() => partyManager.close());
96
-
97
- identityManager.ready.once(() => {
98
- assert(identityManager.identity.halo?.isOpen);
99
- const unsub = autoPartyOpener(identityManager.identity.preferences!, partyManager);
100
- afterTest(unsub);
82
+ const haloParty = await haloFactory.createHalo({
83
+ identityDisplayName: identityKey.publicKey.humanize()
101
84
  });
85
+ afterTest(() => haloParty.close());
86
+ const identity = new Identity(keyring, haloParty);
102
87
 
103
- if (open) {
104
- await partyManager.open();
105
- if (createIdentity) {
106
- const haloParty = await identityManager.createHalo({
107
- identityDisplayName: identityManager.identity.identityKey!.publicKey.humanize()
108
- });
109
- afterTest(() => haloParty.close());
88
+ const partyFactory = new PartyFactory(
89
+ () => identity,
90
+ networkManager,
91
+ modelFactory,
92
+ snapshotStore,
93
+ feedProviderFactory,
94
+ {
95
+ writeLogger: messageLogger('<<<'),
96
+ readLogger: messageLogger('>>>')
110
97
  }
111
- }
98
+ );
99
+ const partyManager = new PartyManager(metadataStore, snapshotStore, () => identity, partyFactory);
100
+ await partyManager.open();
101
+ afterTest(() => partyManager.close());
112
102
 
113
- return { feedStore, partyManager, identityManager, seedPhrase };
103
+ return { feedStore, partyManager, identity };
114
104
  };
115
105
 
116
106
  describe('Party manager', () => {
@@ -120,7 +110,7 @@ describe('Party manager', () => {
120
110
  });
121
111
 
122
112
  test('Created locally', async () => {
123
- const { partyManager, identityManager } = await setup();
113
+ const { partyManager, identity } = await setup();
124
114
 
125
115
  const [update, setUpdated] = latch();
126
116
  const unsubscribe = partyManager.update.on((party) => {
@@ -134,10 +124,10 @@ describe('Party manager', () => {
134
124
  expect(party.isOpen).toBeTruthy();
135
125
 
136
126
  // The Party key is an inception key, so its secret should be destroyed immediately after use.
137
- const partyKey = identityManager.identity.keyring.getKey(party.key);
127
+ const partyKey = identity.keyring.getKey(party.key);
138
128
  expect(partyKey).toBeDefined();
139
129
  assert(partyKey);
140
- expect(identityManager.identity.keyring.hasSecretKey(partyKey)).toBe(false);
130
+ expect(identity.keyring.hasSecretKey(partyKey)).toBe(false);
141
131
 
142
132
  await update;
143
133
  });
@@ -192,8 +182,20 @@ describe('Party manager', () => {
192
182
  const snapshotStore = new SnapshotStore(createStorage('', StorageType.RAM));
193
183
  const networkManager = new NetworkManager();
194
184
  const feedProviderFactory = (partyKey: PublicKey) => new PartyFeedProvider(metadataStore, keyring, feedStore, partyKey);
195
- const partyFactory: PartyFactory = new PartyFactory(() => identityManager.identity, networkManager, modelFactory, snapshotStore, feedProviderFactory);
196
- const haloFactory = new HaloFactory(partyFactory, networkManager, keyring);
185
+ const partyFactory = new PartyFactory(
186
+ () => identityManager.identity,
187
+ networkManager,
188
+ modelFactory,
189
+ snapshotStore,
190
+ feedProviderFactory
191
+ );
192
+ const haloFactory: HaloFactory = new HaloFactory(
193
+ networkManager,
194
+ modelFactory,
195
+ snapshotStore,
196
+ feedProviderFactory,
197
+ keyring
198
+ );
197
199
  const identityManager = new IdentityManager(keyring, haloFactory, metadataStore);
198
200
  const partyManager = new PartyManager(metadataStore, snapshotStore, () => identityManager.identity, partyFactory);
199
201
 
@@ -233,6 +235,8 @@ describe('Party manager', () => {
233
235
  }
234
236
 
235
237
  // Open.
238
+ await identityManager.createHalo();
239
+ afterTest(() => identityManager.close());
236
240
  await partyManager.open();
237
241
  expect(partyManager.parties).toHaveLength(numParties);
238
242
  await partyManager.close();
@@ -246,7 +250,7 @@ describe('Party manager', () => {
246
250
  const PIN = Buffer.from('0000');
247
251
  const secretProvider: SecretProvider = async () => PIN;
248
252
  const secretValidator: SecretValidator = async (invitation, secret) => secret.equals(PIN);
249
- await partyA.invitationManager.createInvitation({ secretProvider, secretValidator }, { expiration: Date.now() + 3000 });
253
+ await partyA.invitationManager.createInvitation({ secretProvider, secretValidator }, { expiration: Date.now() + 1000 });
250
254
  });
251
255
 
252
256
  test('Create invitation', async () => {
@@ -278,8 +282,8 @@ describe('Party manager', () => {
278
282
  });
279
283
 
280
284
  test('Join a party - PIN', async () => {
281
- const { partyManager: partyManagerA, identityManager: identityManagerA } = await setup();
282
- const { partyManager: partyManagerB, identityManager: identityManagerB } = await setup();
285
+ const { partyManager: partyManagerA, identity: identityA } = await setup();
286
+ const { partyManager: partyManagerB, identity: identityB } = await setup();
283
287
 
284
288
  // Create the Party.
285
289
  expect(partyManagerA.parties).toHaveLength(0);
@@ -339,13 +343,13 @@ describe('Party manager', () => {
339
343
  const members = party.queryMembers().value;
340
344
  expect(members.length).toBe(2);
341
345
  for (const member of members) {
342
- if (identityManagerA.identity.identityKey!.publicKey.equals(member.publicKey)) {
343
- expect(member.displayName).toEqual(identityManagerA.identity.identityKey!.publicKey.humanize());
344
- expect(member.displayName).toEqual(identityManagerA.identity.displayName);
346
+ if (identityA.identityKey!.publicKey.equals(member.publicKey)) {
347
+ expect(member.displayName).toEqual(identityA.identityKey!.publicKey.humanize());
348
+ expect(member.displayName).toEqual(identityA.displayName);
345
349
  }
346
- if (identityManagerB.identity.identityKey!.publicKey.equals(member.publicKey)) {
347
- expect(member.displayName).toEqual(identityManagerB.identity.identityKey!.publicKey.humanize());
348
- expect(member.displayName).toEqual(identityManagerB.identity.displayName);
350
+ if (identityB.identityKey!.publicKey.equals(member.publicKey)) {
351
+ expect(member.displayName).toEqual(identityB.identityKey!.publicKey.humanize());
352
+ expect(member.displayName).toEqual(identityB.displayName);
349
353
  }
350
354
  }
351
355
  }
@@ -354,8 +358,8 @@ describe('Party manager', () => {
354
358
  });
355
359
 
356
360
  test('Join a party - signature', async () => {
357
- const { partyManager: partyManagerA, identityManager: identityManagerA } = await setup();
358
- const { partyManager: partyManagerB, identityManager: identityManagerB } = await setup();
361
+ const { partyManager: partyManagerA, identity: identityA } = await setup();
362
+ const { partyManager: partyManagerB, identity: identityB } = await setup();
359
363
 
360
364
  // This would typically be a keypair associated with BotFactory.
361
365
  const keyPair = createKeyPair();
@@ -420,21 +424,19 @@ describe('Party manager', () => {
420
424
  const members = party.queryMembers().value;
421
425
  expect(members.length).toBe(2);
422
426
  for (const member of members) {
423
- if (identityManagerA.identity.identityKey!.publicKey.equals(member.publicKey)) {
424
- expect(member.displayName).toEqual(identityManagerA.identity.identityKey!.publicKey.humanize());
427
+ if (identityA.identityKey!.publicKey.equals(member.publicKey)) {
428
+ expect(member.displayName).toEqual(identityA.identityKey!.publicKey.humanize());
425
429
  }
426
- if (identityManagerB.identity.identityKey!.publicKey.equals(member.publicKey)) {
427
- expect(member.displayName).toEqual(identityManagerB.identity.identityKey!.publicKey.humanize());
430
+ if (identityB.identityKey!.publicKey.equals(member.publicKey)) {
431
+ expect(member.displayName).toEqual(identityB.identityKey!.publicKey.humanize());
428
432
  }
429
433
  }
430
434
  }
431
435
  });
432
436
 
433
437
  test('Join a party - Offline', async () => {
434
- const { partyManager: partyManagerA, identityManager: identityManagerA } = await setup();
435
- const { partyManager: partyManagerB, identityManager: identityManagerB } = await setup();
436
- assert(identityManagerA.identity.identityKey);
437
- assert(identityManagerB.identity.identityKey);
438
+ const { partyManager: partyManagerA, identity: identityA } = await setup();
439
+ const { partyManager: partyManagerB, identity: identityB } = await setup();
438
440
 
439
441
  // Create the Party.
440
442
  expect(partyManagerA.parties).toHaveLength(0);
@@ -443,12 +445,12 @@ describe('Party manager', () => {
443
445
  log(`Created ${partyA.key.toHex()}`);
444
446
 
445
447
  const invitationDescriptor = await partyA.invitationManager
446
- .createOfflineInvitation(identityManagerB.identity.identityKey.publicKey);
448
+ .createOfflineInvitation(identityB.identityKey!.publicKey);
447
449
 
448
450
  // Redeem the invitation on B.
449
451
  expect(partyManagerB.parties).toHaveLength(0);
450
452
  const partyB = await partyManagerB.joinParty(invitationDescriptor,
451
- OfflineInvitationClaimer.createSecretProvider(identityManagerB.identity));
453
+ OfflineInvitationClaimer.createSecretProvider(identityB));
452
454
  expect(partyB).toBeDefined();
453
455
  log(`Joined ${partyB.key.toHex()}`);
454
456
 
@@ -479,18 +481,42 @@ describe('Party manager', () => {
479
481
  const members = party.queryMembers().value;
480
482
  expect(members.length).toBe(2);
481
483
  for (const member of members) {
482
- if (identityManagerA.identity.identityKey!.publicKey.equals(member.publicKey)) {
483
- expect(member.displayName).toEqual(identityManagerA.identity.identityKey!.publicKey.humanize());
484
- expect(member.displayName).toEqual(identityManagerA.identity.displayName);
484
+ if (identityA.identityKey!.publicKey.equals(member.publicKey)) {
485
+ expect(member.displayName).toEqual(identityA.identityKey!.publicKey.humanize());
486
+ expect(member.displayName).toEqual(identityA.displayName);
485
487
  }
486
- if (identityManagerB.identity.identityKey!.publicKey.equals(member.publicKey)) {
487
- expect(member.displayName).toEqual(identityManagerB.identity.identityKey!.publicKey.humanize());
488
- expect(member.displayName).toEqual(identityManagerB.identity.displayName);
488
+ if (identityB.identityKey!.publicKey.equals(member.publicKey)) {
489
+ expect(member.displayName).toEqual(identityB.identityKey!.publicKey.humanize());
490
+ expect(member.displayName).toEqual(identityB.displayName);
489
491
  }
490
492
  }
491
493
  }
492
494
  }).timeout(10_000);
493
495
 
496
+ test('3 peers in a party', async () => {
497
+ const { partyManager: partyManagerA } = await setup();
498
+ const { partyManager: partyManagerB } = await setup();
499
+ const { partyManager: partyManagerC } = await setup();
500
+
501
+ const partyA = await partyManagerA.createParty();
502
+
503
+ const invitationA = await partyA.invitationManager.createInvitation(defaultInvitationAuthenticator);
504
+ const partyB = await partyManagerB.joinParty(invitationA, defaultSecretProvider);
505
+
506
+ const invitationB = await partyB.invitationManager.createInvitation(defaultInvitationAuthenticator);
507
+ const partyC = await partyManagerC.joinParty(invitationB, defaultSecretProvider);
508
+
509
+ await partyA.database.createItem({ type: 'test:item-a' });
510
+ await partyB.database.createItem({ type: 'test:item-b' });
511
+ await partyC.database.createItem({ type: 'test:item-c' });
512
+
513
+ for (const party of [partyA, partyB, partyC]) {
514
+ await testTimeout(party.database.waitForItem({ type: 'test:item-a' }));
515
+ await testTimeout(party.database.waitForItem({ type: 'test:item-b' }));
516
+ await testTimeout(party.database.waitForItem({ type: 'test:item-c' }));
517
+ }
518
+ });
519
+
494
520
  test('Clone party', async () => {
495
521
  const { partyManager } = await setup();
496
522
 
@@ -15,10 +15,10 @@ import { ComplexMap, boolGuard } from '@dxos/util';
15
15
 
16
16
  import { IdentityProvider } from '../halo';
17
17
  import { InvitationDescriptor } from '../invitations';
18
- import { MetadataStore } from '../metadata';
18
+ import { MetadataStore } from '../pipeline';
19
19
  import { SnapshotStore } from '../snapshots';
20
+ import { DataParty, PARTY_ITEM_TYPE, PARTY_TITLE_PROPERTY } from './data-party';
20
21
  import { PartyFactory } from './party-factory';
21
- import { PartyInternal, PARTY_ITEM_TYPE, PARTY_TITLE_PROPERTY } from './party-internal';
22
22
 
23
23
  export const CONTACT_DEBOUNCE_INTERVAL = 500;
24
24
 
@@ -33,14 +33,14 @@ export interface OpenProgress {
33
33
  /**
34
34
  * Top-level class manages the complete life-cycle of parties.
35
35
  *
36
- * `PartyManager` => `PartyManager` => `PartyInternal` => `PartyCore`
36
+ * `ECHO` => `PartyManager` => `DataParty` => `PartyCore`
37
37
  */
38
38
  export class PartyManager {
39
39
  // External event listener.
40
- readonly update = new Event<PartyInternal>();
40
+ readonly update = new Event<DataParty>();
41
41
 
42
42
  // Map of parties by party key.
43
- private readonly _parties = new ComplexMap<PublicKey, PartyInternal>(key => key.toHex());
43
+ private readonly _parties = new ComplexMap<PublicKey, DataParty>(key => key.toHex());
44
44
 
45
45
  // Unsubscribe handlers.
46
46
  // TODO(burdon): Never used.
@@ -59,7 +59,7 @@ export class PartyManager {
59
59
  return this._open;
60
60
  }
61
61
 
62
- get parties (): PartyInternal[] {
62
+ get parties (): DataParty[] {
63
63
  return Array.from(this._parties.values());
64
64
  }
65
65
 
@@ -77,7 +77,7 @@ export class PartyManager {
77
77
  const identity = this._identityProvider();
78
78
 
79
79
  // TODO(telackey): Does it make any sense to load other parties if we don't have an HALO?
80
- if (identity.identityKey) {
80
+ if (identity?.identityKey) {
81
81
  partyKeys = partyKeys.filter(partyKey => !partyKey.equals(identity.identityKey!.publicKey));
82
82
  }
83
83
 
@@ -97,7 +97,7 @@ export class PartyManager {
97
97
  ? await this._partyFactory.constructPartyFromSnapshot(snapshot)
98
98
  : await this._partyFactory.constructParty(partyKey);
99
99
 
100
- const isActive = identity.preferences?.isPartyActive(partyKey) ?? true;
100
+ const isActive = identity?.preferences?.isPartyActive(partyKey) ?? true;
101
101
  if (isActive) {
102
102
  await party.open();
103
103
  // TODO(marik-d): Might not be required if separately snapshot this item.
@@ -141,7 +141,7 @@ export class PartyManager {
141
141
  * Creates a new party, writing its genesis block to the stream.
142
142
  */
143
143
  @synchronized
144
- async createParty (): Promise<PartyInternal> {
144
+ async createParty (): Promise<DataParty> {
145
145
  assert(this._open, 'PartyManager is not open.');
146
146
 
147
147
  const party = await this._partyFactory.createParty();
@@ -224,7 +224,7 @@ export class PartyManager {
224
224
  return party;
225
225
  }
226
226
 
227
- private _setParty (party: PartyInternal) {
227
+ private _setParty (party: DataParty) {
228
228
  const updateContact = async () => {
229
229
  try {
230
230
  await this._updateContactList(party);
@@ -258,7 +258,7 @@ export class PartyManager {
258
258
  }
259
259
 
260
260
  // TODO(burdon): Refactor.
261
- private async _updatePartyTitle (party: PartyInternal) {
261
+ private async _updatePartyTitle (party: DataParty) {
262
262
  if (!this._open) {
263
263
  return;
264
264
  }
@@ -266,14 +266,14 @@ export class PartyManager {
266
266
  const identity = this._identityProvider();
267
267
  const item = await party.getPropertiesItem();
268
268
  const currentTitle = item.model.get(PARTY_TITLE_PROPERTY);
269
- const storedTitle = identity.preferences?.getGlobalPartyPreference(party.key, PARTY_TITLE_PROPERTY);
269
+ const storedTitle = identity?.preferences?.getGlobalPartyPreference(party.key, PARTY_TITLE_PROPERTY);
270
270
  if (storedTitle !== currentTitle) {
271
- await identity.preferences?.setGlobalPartyPreference(party, PARTY_TITLE_PROPERTY, currentTitle);
271
+ await identity?.preferences?.setGlobalPartyPreference(party, PARTY_TITLE_PROPERTY, currentTitle);
272
272
  }
273
273
  }
274
274
 
275
275
  // TODO(burdon): Reconcile with `Halo.ContactManager`.
276
- private async _updateContactList (party: PartyInternal) {
276
+ private async _updateContactList (party: DataParty) {
277
277
  // Prevent any updates after we closed ECHO.
278
278
  // This will get re-run next time echo is loaded so we don't loose any data.
279
279
  if (!this._open) {
@@ -282,7 +282,7 @@ export class PartyManager {
282
282
 
283
283
  const identity = this._identityProvider();
284
284
 
285
- const contactListItem = identity.contacts?.getContactListItem();
285
+ const contactListItem = identity?.contacts?.getContactListItem();
286
286
  if (!contactListItem) {
287
287
  return;
288
288
  }
@@ -314,11 +314,11 @@ export class PartyManager {
314
314
  }
315
315
 
316
316
  @timed(5_000)
317
- private async _recordPartyJoining (party: PartyInternal) {
317
+ private async _recordPartyJoining (party: DataParty) {
318
318
  const identity = this._identityProvider();
319
319
 
320
320
  // TODO(marik-d): Extract HALO functionality from this class.
321
- if (!identity.preferences) {
321
+ if (!identity?.preferences) {
322
322
  return;
323
323
  }
324
324
 
@@ -4,8 +4,8 @@
4
4
 
5
5
  import assert from 'assert';
6
6
 
7
- import { PARTY_TITLE_PROPERTY, PartyInternal } from '../parties';
8
- import { Preferences } from './preferences';
7
+ import { PARTY_TITLE_PROPERTY, DataParty } from '.';
8
+ import { Preferences } from '../halo/preferences';
9
9
 
10
10
  export interface ActivationOptions {
11
11
  global?: boolean;
@@ -21,7 +21,7 @@ export interface ActivationOptions {
21
21
  export class PartyPreferences {
22
22
  constructor (
23
23
  private readonly _preferences: Preferences,
24
- private readonly _party: PartyInternal
24
+ private readonly _party: DataParty
25
25
  ) {
26
26
  assert(this._party);
27
27
  }
@@ -5,5 +5,7 @@
5
5
  export * from './message-selector';
6
6
  export * from './party-feed-provider';
7
7
  export * from './party-processor';
8
- export * from './party-protocol-factory';
8
+ export * from '../protocol/party-protocol-factory';
9
9
  export * from './pipeline';
10
+ export * from './party-core';
11
+ export * from './metadata-store';
File without changes
@@ -16,8 +16,7 @@ import { ObjectModel } from '@dxos/object-model';
16
16
  import { createStorage, StorageType } from '@dxos/random-access-multi-storage';
17
17
  import { afterTest } from '@dxos/testutils';
18
18
 
19
- import { MetadataStore } from '../metadata';
20
- import { PartyFeedProvider, ReplicatorProtocolPluginFactory } from '../pipeline';
19
+ import { MetadataStore, PartyFeedProvider, ReplicatorProtocolPluginFactory } from '../pipeline';
21
20
  import { SnapshotStore } from '../snapshots';
22
21
  import { createRamStorage } from '../util';
23
22
  import { PartyCore } from './party-core';
@@ -5,7 +5,7 @@
5
5
  import assert from 'assert';
6
6
 
7
7
  import { synchronized } from '@dxos/async';
8
- import { KeyHint } from '@dxos/credentials';
8
+ import { KeyHint, KeyType } from '@dxos/credentials';
9
9
  import { PublicKey } from '@dxos/crypto';
10
10
  import { timed } from '@dxos/debug';
11
11
  import { createFeedWriter, DatabaseSnapshot, PartyKey, PartySnapshot, Timeframe } from '@dxos/echo-protocol';
@@ -58,7 +58,7 @@ export class PartyCore {
58
58
  private readonly _memberKey: PublicKey,
59
59
  private readonly _initialTimeframe?: Timeframe,
60
60
  private readonly _options: PartyOptions = {}
61
- ) {}
61
+ ) { }
62
62
 
63
63
  get key (): PartyKey {
64
64
  return this._partyKey;
@@ -111,14 +111,23 @@ export class PartyCore {
111
111
 
112
112
  this._timeframeClock = new TimeframeClock(this._initialTimeframe);
113
113
 
114
+ // Open all feeds known from metadata and open or create a writable feed to the party.
115
+ await this._feedProvider.openKnownFeeds();
116
+ const writableFeed = await this._feedProvider.createOrOpenWritableFeed();
117
+
114
118
  if (!this._partyProcessor) {
115
119
  this._partyProcessor = new PartyProcessor(this._partyKey);
116
120
  }
121
+
117
122
  // Automatically open new admitted feeds.
118
123
  this._subscriptions.push(this._partyProcessor.feedAdded.on(feed => {
119
124
  void this._feedProvider.createOrOpenReadOnlyFeed(feed);
120
125
  }));
121
126
 
127
+ // Hint at our own writable feed.
128
+ // TODO(dmaretskyi): Does not seem like it should be required, but without it replication between devices (B -> A) breaks.
129
+ await this._partyProcessor.takeHints([{ type: KeyType.FEED, publicKey: writableFeed.key }]);
130
+
122
131
  if (keyHints.length > 0) {
123
132
  await this._partyProcessor.takeHints(keyHints);
124
133
  }
@@ -127,17 +136,13 @@ export class PartyCore {
127
136
  // Pipeline
128
137
  //
129
138
 
130
- await this._feedProvider.openKnownFeeds();
131
139
  const iterator = await this._feedProvider.createIterator(
132
140
  createMessageSelector(this._partyProcessor, this._timeframeClock),
133
141
  this._initialTimeframe
134
142
  );
135
143
 
136
- const { feed } = await this._feedProvider.createOrOpenWritableFeed();
137
- const feedWriteStream = createFeedWriter(feed);
138
-
139
144
  this._pipeline = new Pipeline(
140
- this._partyProcessor, iterator, this._timeframeClock, feedWriteStream, this._options);
145
+ this._partyProcessor, iterator, this._timeframeClock, createFeedWriter(writableFeed.feed), this._options);
141
146
 
142
147
  // TODO(burdon): Support read-only parties.
143
148
  const [readStream, writeStream] = await this._pipeline.open();
@@ -5,16 +5,17 @@
5
5
  import assert from 'assert';
6
6
  import debug from 'debug';
7
7
 
8
- import { Event } from '@dxos/async';
8
+ import { Event, synchronized } from '@dxos/async';
9
9
  import { Keyring, KeyType } from '@dxos/credentials';
10
10
  import { PublicKey } from '@dxos/crypto';
11
11
  import { FeedStoreIterator, MessageSelector, Timeframe } from '@dxos/echo-protocol';
12
12
  import { FeedDescriptor, FeedStore } from '@dxos/feed-store';
13
13
  import { ComplexMap } from '@dxos/util';
14
14
 
15
- import { MetadataStore } from '../metadata';
15
+ import { MetadataStore } from './metadata-store';
16
16
 
17
17
  const STALL_TIMEOUT = 1000;
18
+ const log = debug('dxos:echo-db:party-feed-provider');
18
19
  const warn = debug('dxos:echo-db:party-feed-provider:warn');
19
20
 
20
21
  export class PartyFeedProvider {
@@ -32,6 +33,20 @@ export class PartyFeedProvider {
32
33
  return Array.from(this._feeds.values());
33
34
  }
34
35
 
36
+ @synchronized
37
+ async openKnownFeeds () {
38
+ for (const feedKey of this._metadataStore.getParty(this._partyKey)?.feedKeys ?? []) {
39
+ if (!this._feeds.has(feedKey)) {
40
+ const fullKey = this._keyring.getFullKey(feedKey);
41
+ const feed = fullKey?.secretKey
42
+ ? await this._feedStore.openReadWriteFeed(fullKey.publicKey, fullKey.secretKey)
43
+ : await this._feedStore.openReadOnlyFeed(feedKey);
44
+ this._trackFeed(feed);
45
+ }
46
+ }
47
+ }
48
+
49
+ @synchronized
35
50
  async createOrOpenWritableFeed () {
36
51
  const partyMetadata = this._metadataStore.getParty(this._partyKey);
37
52
  if (!partyMetadata?.dataFeedKey) {
@@ -48,24 +63,11 @@ export class PartyFeedProvider {
48
63
  }
49
64
 
50
65
  const feed = await this._feedStore.openReadWriteFeed(fullKey.publicKey, fullKey.secretKey);
51
- this._feeds.set(fullKey.publicKey, feed);
52
- this.feedOpened.emit(feed);
66
+ this._trackFeed(feed);
53
67
  return feed;
54
68
  }
55
69
 
56
- async openKnownFeeds () {
57
- for (const feedKey of this._metadataStore.getParty(this._partyKey)?.feedKeys ?? []) {
58
- if (!this._feeds.has(feedKey)) {
59
- const fullKey = this._keyring.getFullKey(feedKey);
60
- const feed = fullKey?.secretKey
61
- ? await this._feedStore.openReadWriteFeed(fullKey.publicKey, fullKey.secretKey)
62
- : await this._feedStore.openReadOnlyFeed(feedKey);
63
- this._feeds.set(feedKey, feed);
64
- this.feedOpened.emit(feed);
65
- }
66
- }
67
- }
68
-
70
+ @synchronized
69
71
  async createOrOpenReadOnlyFeed (feedKey: PublicKey): Promise<FeedDescriptor> {
70
72
  if (this._feeds.has(feedKey)) {
71
73
  return this._feeds.get(feedKey)!;
@@ -76,19 +78,28 @@ export class PartyFeedProvider {
76
78
  await this._keyring.addPublicKey({ type: KeyType.FEED, publicKey: feedKey });
77
79
  }
78
80
  const feed = await this._feedStore.openReadOnlyFeed(feedKey);
79
- this._feeds.set(feedKey, feed);
80
- this.feedOpened.emit(feed);
81
+ this._trackFeed(feed);
81
82
  return feed;
82
83
  }
83
84
 
85
+ private _trackFeed (feed: FeedDescriptor) {
86
+ assert(!this._feeds.has(feed.key));
87
+ this._feeds.set(feed.key, feed);
88
+ this.feedOpened.emit(feed);
89
+
90
+ log(`Party feed set changed: ${JSON.stringify({
91
+ party: this._partyKey,
92
+ feeds: Array.from(this._feeds.values()).map(feed => feed.key)
93
+ })}`);
94
+ }
95
+
84
96
  private async _createReadWriteFeed () {
85
97
  const feedKey = await this._keyring.createKeyRecord({ type: KeyType.FEED });
86
98
  const fullKey = this._keyring.getFullKey(feedKey.publicKey);
87
99
  assert(fullKey && fullKey.secretKey);
88
100
  await this._metadataStore.setDataFeed(this._partyKey, fullKey.publicKey);
89
101
  const feed = await this._feedStore.openReadWriteFeed(fullKey.publicKey, fullKey.secretKey);
90
- this._feeds.set(fullKey.publicKey, feed);
91
- this.feedOpened.emit(feed);
102
+ this._trackFeed(feed);
92
103
  return feed;
93
104
  }
94
105
 
@@ -0,0 +1,14 @@
1
+ //
2
+ // Copyright 2022 DXOS.org
3
+ //
4
+
5
+ import { Authenticator, AuthPlugin } from '@dxos/credentials';
6
+ import { PublicKey } from '@dxos/crypto';
7
+ import { Replicator } from '@dxos/protocol-plugin-replicator';
8
+
9
+ /**
10
+ * Creates authenticator network-protocol plugin that guards access to the replicator.
11
+ */
12
+ export function createAuthPlugin (authenticator: Authenticator, peerId: PublicKey) {
13
+ return new AuthPlugin(peerId.asBuffer(), authenticator, [Replicator.extension]);
14
+ }