@dxos/echo-db 2.33.0 → 2.33.1-dev.054d3c0b

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 (60) hide show
  1. package/dist/src/database/data-mirror.js +2 -2
  2. package/dist/src/database/data-mirror.js.map +1 -1
  3. package/dist/src/database/item-demuxer.test.js +2 -2
  4. package/dist/src/database/item-demuxer.test.js.map +1 -1
  5. package/dist/src/echo.test.js +70 -0
  6. package/dist/src/echo.test.js.map +1 -1
  7. package/dist/src/invitations/greeting-initiator.d.ts +1 -4
  8. package/dist/src/invitations/greeting-initiator.d.ts.map +1 -1
  9. package/dist/src/invitations/greeting-initiator.js +1 -7
  10. package/dist/src/invitations/greeting-initiator.js.map +1 -1
  11. package/dist/src/invitations/halo-recovery-initiator.d.ts.map +1 -1
  12. package/dist/src/invitations/halo-recovery-initiator.js +2 -2
  13. package/dist/src/invitations/halo-recovery-initiator.js.map +1 -1
  14. package/dist/src/invitations/offline-invitation-claimer.d.ts.map +1 -1
  15. package/dist/src/invitations/offline-invitation-claimer.js +2 -2
  16. package/dist/src/invitations/offline-invitation-claimer.js.map +1 -1
  17. package/dist/src/parties/authenticator.d.ts +5 -0
  18. package/dist/src/parties/authenticator.d.ts.map +1 -0
  19. package/dist/src/parties/authenticator.js +27 -0
  20. package/dist/src/parties/authenticator.js.map +1 -0
  21. package/dist/src/parties/party-core.d.ts.map +1 -1
  22. package/dist/src/parties/party-core.js +1 -0
  23. package/dist/src/parties/party-core.js.map +1 -1
  24. package/dist/src/parties/party-factory.d.ts.map +1 -1
  25. package/dist/src/parties/party-factory.js +1 -5
  26. package/dist/src/parties/party-factory.js.map +1 -1
  27. package/dist/src/parties/party-internal.d.ts.map +1 -1
  28. package/dist/src/parties/party-internal.js +6 -4
  29. package/dist/src/parties/party-internal.js.map +1 -1
  30. package/dist/src/pipeline/party-feed-provider.d.ts +5 -1
  31. package/dist/src/pipeline/party-feed-provider.d.ts.map +1 -1
  32. package/dist/src/pipeline/party-feed-provider.js +36 -13
  33. package/dist/src/pipeline/party-feed-provider.js.map +1 -1
  34. package/dist/src/pipeline/party-processor.d.ts +2 -3
  35. package/dist/src/pipeline/party-processor.d.ts.map +1 -1
  36. package/dist/src/pipeline/party-processor.js +5 -11
  37. package/dist/src/pipeline/party-processor.js.map +1 -1
  38. package/dist/src/pipeline/party-processor.test.js +8 -8
  39. package/dist/src/pipeline/party-processor.test.js.map +1 -1
  40. package/dist/src/pipeline/pipeline.js +1 -1
  41. package/dist/src/pipeline/pipeline.js.map +1 -1
  42. package/dist/src/pipeline/pipeline.test.js +1 -1
  43. package/dist/src/pipeline/pipeline.test.js.map +1 -1
  44. package/dist/tsconfig.tsbuildinfo +1 -1
  45. package/package.json +17 -17
  46. package/src/database/data-mirror.ts +2 -2
  47. package/src/database/item-demuxer.test.ts +3 -3
  48. package/src/echo.test.ts +100 -1
  49. package/src/invitations/greeting-initiator.ts +1 -26
  50. package/src/invitations/halo-recovery-initiator.ts +4 -4
  51. package/src/invitations/offline-invitation-claimer.ts +4 -4
  52. package/src/parties/authenticator.ts +31 -0
  53. package/src/parties/party-core.ts +1 -0
  54. package/src/parties/party-factory.ts +1 -6
  55. package/src/parties/party-internal.ts +15 -6
  56. package/src/pipeline/party-feed-provider.ts +40 -14
  57. package/src/pipeline/party-processor.test.ts +8 -8
  58. package/src/pipeline/party-processor.ts +5 -15
  59. package/src/pipeline/pipeline.test.ts +1 -1
  60. package/src/pipeline/pipeline.ts +1 -1
package/src/echo.test.ts CHANGED
@@ -7,7 +7,7 @@ import debug from 'debug';
7
7
  import expect from 'expect';
8
8
  import { it as test } from 'mocha';
9
9
 
10
- import { latch, waitForCondition } from '@dxos/async';
10
+ import { latch, promiseTimeout, waitForCondition } from '@dxos/async';
11
11
  import { defaultSecretProvider, defaultSecretValidator } from '@dxos/credentials';
12
12
  import { generateSeedPhrase, keyPairFromSeedPhrase, createKeyPair } from '@dxos/crypto';
13
13
  import { ObjectModel } from '@dxos/object-model';
@@ -329,6 +329,105 @@ describe('ECHO', () => {
329
329
  expect(a.queryParties().value[1].key).toEqual(b.queryParties().value[1].key);
330
330
  }).timeout(10_000);
331
331
 
332
+ test('Mutations from another device', async () => {
333
+ const a = await setup({ createProfile: true });
334
+ const b = await setup();
335
+
336
+ await a.createParty();
337
+
338
+ const invitation = await a.halo.createInvitation(defaultInvitationAuthenticator);
339
+ await b.halo.join(invitation, defaultSecretProvider);
340
+
341
+ // Check the initial party is opened.
342
+ await waitForCondition(() => b.queryParties().value.length === 1, 1000);
343
+
344
+ const partyA = a.queryParties().value[0];
345
+ await partyA.open();
346
+ const partyB = b.queryParties().value[0];
347
+ await partyB.open();
348
+
349
+ {
350
+ // Subscribe to Item updates on B.
351
+ const selection = partyA.database.select({ type: 'example:item/test' }).exec();
352
+ const updated = selection.update.waitFor(result => result.entities.length > 0);
353
+
354
+ // Create a new Item on A.
355
+ const itemB = await partyB.database
356
+ .createItem({ model: ObjectModel, type: 'example:item/test' }) as Item<any>;
357
+ log(`A created ${itemB.id}`);
358
+
359
+ // Now wait to see it on B.
360
+ await updated;
361
+ log(`B has ${itemB.id}`);
362
+
363
+ expect(selection.entities[0].id).toEqual(itemB.id);
364
+ }
365
+
366
+ await a.close();
367
+ await b.close();
368
+
369
+ await a.open();
370
+
371
+ {
372
+ const partyA = a.queryParties().first;
373
+
374
+ expect(partyA.database.select({ type: 'example:item/test' }).exec().entities.length > 0).toEqual(true);
375
+ }
376
+
377
+ }).timeout(10_000);
378
+
379
+ test.skip('3 devices', async () => {
380
+ const a = await setup({ createProfile: true });
381
+ const b = await setup();
382
+
383
+ await a.createParty();
384
+
385
+ await b.halo.join(await a.halo.createInvitation(defaultInvitationAuthenticator), defaultSecretProvider);
386
+
387
+ // Check the initial party is opened.
388
+ await waitForCondition(() => b.queryParties().value.length === 1, 1000);
389
+
390
+ const partyA = a.queryParties().value[0];
391
+ await partyA.open();
392
+ const partyB = b.queryParties().value[0];
393
+ await partyB.open();
394
+
395
+ {
396
+ // Subscribe to Item updates on B.
397
+ const selection = partyA.database.select({ type: 'example:item/test' }).exec();
398
+ const updated = selection.update.waitFor(result => result.entities.length > 0);
399
+
400
+ // Create a new Item on A.
401
+ const itemB = await partyB.database
402
+ .createItem({ model: ObjectModel, type: 'example:item/test' }) as Item<any>;
403
+ log(`A created ${itemB.id}`);
404
+
405
+ // Now wait to see it on B.
406
+ await updated;
407
+ log(`B has ${itemB.id}`);
408
+
409
+ expect(selection.entities[0].id).toEqual(itemB.id);
410
+ }
411
+
412
+ await a.close();
413
+ await b.close();
414
+
415
+ await a.open();
416
+
417
+ {
418
+ const partyA = a.queryParties().first;
419
+
420
+ expect(partyA.database.select({ type: 'example:item/test' }).exec().entities.length > 0).toEqual(true);
421
+ }
422
+
423
+ const c = await setup();
424
+ await c.halo.join(await a.halo.createInvitation(defaultInvitationAuthenticator), defaultSecretProvider);
425
+ await waitForCondition(() => c.queryParties().value.length === 1, 1000);
426
+ const partyC = c.queryParties().first;
427
+
428
+ await promiseTimeout(partyC.database.waitForItem({ type: 'example:item/test' }), 1000, new Error('timeout'));
429
+ }).timeout(10_000);
430
+
332
431
  test('Two users, two devices each', async () => {
333
432
  const a1 = await setup({ createProfile: true });
334
433
  const a2 = await setup();
@@ -8,7 +8,6 @@ import debug from 'debug';
8
8
  import { waitForEvent } from '@dxos/async';
9
9
  import {
10
10
  createEnvelopeMessage,
11
- createFeedAdmitMessage,
12
11
  createGreetingBeginMessage,
13
12
  createGreetingFinishMessage,
14
13
  createGreetingHandshakeMessage,
@@ -23,7 +22,6 @@ import {
23
22
  ERR_GREET_CONNECTED_TO_SWARM_TIMEOUT
24
23
  } from '@dxos/credentials';
25
24
  import { keyToString, PublicKey } from '@dxos/crypto';
26
- import { PartyKey } from '@dxos/echo-protocol';
27
25
  import { FullyConnectedTopology, NetworkManager } from '@dxos/network-manager';
28
26
 
29
27
  import { Identity } from '../halo';
@@ -54,8 +52,7 @@ export class GreetingInitiator {
54
52
  constructor (
55
53
  private readonly _networkManager: NetworkManager,
56
54
  private readonly _identity: Identity,
57
- private readonly _invitationDescriptor: InvitationDescriptor,
58
- private readonly _feedInitializer: (partyKey: PartyKey) => Promise<PublicKey>
55
+ private readonly _invitationDescriptor: InvitationDescriptor
59
56
  ) {
60
57
  assert(InvitationDescriptorType.INTERACTIVE === this._invitationDescriptor.type);
61
58
  }
@@ -149,8 +146,6 @@ export class GreetingInitiator {
149
146
  const { nonce } = handshakeResponse;
150
147
  const partyKey = handshakeResponse.partyKey;
151
148
 
152
- const feedKey = await this._feedInitializer(partyKey);
153
-
154
149
  const credentialMessages = [];
155
150
  if (haloInvitation) {
156
151
  assert(this._identity.deviceKey, 'Device key required');
@@ -164,16 +159,6 @@ export class GreetingInitiator {
164
159
  [],
165
160
  nonce)
166
161
  );
167
-
168
- // And Feed, signed for by the FEED and the DEVICE.
169
- credentialMessages.push(
170
- createFeedAdmitMessage(
171
- this._identity.signer,
172
- partyKey,
173
- feedKey,
174
- [this._identity.deviceKey],
175
- nonce)
176
- );
177
162
  } else {
178
163
  assert(this._identity.deviceKeyChain, 'Device key required');
179
164
  assert(this._identity.identityGenesis, 'Identity genesis message required');
@@ -187,16 +172,6 @@ export class GreetingInitiator {
187
172
  [this._identity.deviceKeyChain],
188
173
  nonce)
189
174
  );
190
-
191
- // And the Feed, signed for by the FEED and by the DEVICE keychain, as above.
192
- credentialMessages.push(
193
- createFeedAdmitMessage(
194
- this._identity.signer,
195
- partyKey,
196
- feedKey,
197
- [this._identity.deviceKeyChain],
198
- nonce)
199
- );
200
175
  }
201
176
 
202
177
  // Send the signed payload to the greeting responder.
@@ -7,7 +7,6 @@ import debug from 'debug';
7
7
 
8
8
  import { waitForEvent } from '@dxos/async';
9
9
  import {
10
- Authenticator,
11
10
  ClaimResponse,
12
11
  Keyring,
13
12
  KeyType,
@@ -17,7 +16,8 @@ import {
17
16
  createGreetingClaimMessage,
18
17
  SecretProvider,
19
18
  SecretValidator,
20
- SignedMessage
19
+ SignedMessage,
20
+ codec
21
21
  } from '@dxos/credentials';
22
22
  import { keyToBuffer, keyToString, PublicKey, randomBytes, verify } from '@dxos/crypto';
23
23
  import { raise } from '@dxos/debug';
@@ -144,7 +144,7 @@ export class HaloRecoveryInitiator {
144
144
 
145
145
  // The secretProvider should provide an `Auth` message signed directly by the Identity key.
146
146
  createSecretProvider (): SecretProvider {
147
- return async (info: any) => Buffer.from(Authenticator.encodePayload(
147
+ return async (info: any) => Buffer.from(codec.encode(
148
148
  /* The signed portion of the Auth message includes the ID and authNonce provided
149
149
  * by "info". These values will be validated on the other end.
150
150
  */
@@ -182,7 +182,7 @@ export class HaloRecoveryInitiator {
182
182
  });
183
183
 
184
184
  const secretValidator: SecretValidator = async (invitation, secret) => {
185
- const { payload: authMessage } = Authenticator.decodePayload(secret);
185
+ const { payload: authMessage } = codec.decode(secret);
186
186
 
187
187
  return keyring.verify(<unknown>authMessage as SignedMessage) &&
188
188
  authMessage.signed.payload.partyKey.equals(invitation.id) &&
@@ -7,7 +7,6 @@ import debug from 'debug';
7
7
 
8
8
  import { waitForEvent } from '@dxos/async';
9
9
  import {
10
- Authenticator,
11
10
  ClaimResponse,
12
11
  Keyring,
13
12
  KeyType,
@@ -18,7 +17,8 @@ import {
18
17
  SecretInfo,
19
18
  SecretProvider,
20
19
  SecretValidator,
21
- SignedMessage
20
+ SignedMessage,
21
+ codec
22
22
  } from '@dxos/credentials';
23
23
  import { keyToBuffer, keyToString, PublicKey, randomBytes } from '@dxos/crypto';
24
24
  import { raise } from '@dxos/debug';
@@ -156,7 +156,7 @@ export class OfflineInvitationClaimer {
156
156
  });
157
157
 
158
158
  const secretValidator: SecretValidator = async (invitation, secret) => {
159
- const { payload: authMessage } = Authenticator.decodePayload(secret);
159
+ const { payload: authMessage } = codec.decode(secret);
160
160
 
161
161
  return keyring.verify(<unknown>authMessage as SignedMessage) &&
162
162
  authMessage.signed.payload.partyKey.equals(invitation.id) &&
@@ -172,7 +172,7 @@ export class OfflineInvitationClaimer {
172
172
  // The secretProvider should provide an `Auth` message signed directly by the Identity key.
173
173
  static createSecretProvider (identity: Identity): SecretProvider {
174
174
  return async (info?: SecretInfo) => {
175
- return Buffer.from(Authenticator.encodePayload(
175
+ return Buffer.from(codec.encode(
176
176
  /* The signed portion of the Auth message includes the ID and authNonce provided
177
177
  * by the `info` object. These values will be validated on the other end.
178
178
  */
@@ -0,0 +1,31 @@
1
+ //
2
+ // Copyright 2022 DXOS.org
3
+ //
4
+
5
+ import debug from 'debug';
6
+
7
+ import { Authenticator, createEnvelopeMessage, PartyAuthenticator } from '@dxos/credentials';
8
+
9
+ import { IdentityProvider } from '../halo';
10
+ import { PartyProcessor } from '../pipeline';
11
+
12
+ const log = debug('dxos:echo-db:authenticator');
13
+
14
+ export function createAuthenticator (partyProcessor: PartyProcessor, identityProvider: IdentityProvider): Authenticator {
15
+ return new PartyAuthenticator(partyProcessor.state, async auth => {
16
+ if (auth.feedAdmit && auth.feedKey && !partyProcessor.isFeedAdmitted(auth.feedKey)) {
17
+ const deviceKeyChain = identityProvider().deviceKeyChain ?? identityProvider().deviceKey;
18
+ if (!deviceKeyChain) {
19
+ log('Not device key chain available to admit new member feed');
20
+ return;
21
+ }
22
+
23
+ await partyProcessor.writeHaloMessage(createEnvelopeMessage(
24
+ identityProvider().keyring,
25
+ partyProcessor.partyKey,
26
+ auth.feedAdmit,
27
+ [deviceKeyChain]
28
+ ));
29
+ }
30
+ });
31
+ }
@@ -122,6 +122,7 @@ export class PartyCore {
122
122
  // Pipeline
123
123
  //
124
124
 
125
+ await this._feedProvider.openKnownFeeds();
125
126
  const iterator = await this._feedProvider.createIterator(
126
127
  createMessageSelector(this._partyProcessor, this._timeframeClock),
127
128
  this._initialTimeframe
@@ -205,12 +205,7 @@ export class PartyFactory {
205
205
  const initiator = new GreetingInitiator(
206
206
  this._networkManager,
207
207
  identity,
208
- invitationDescriptor,
209
- async partyKey => {
210
- const feedProvider = this._createFeedProvider(partyKey);
211
- const feed = await feedProvider.createOrOpenWritableFeed();
212
- return feed.key;
213
- }
208
+ invitationDescriptor
214
209
  );
215
210
 
216
211
  await initiator.connect();
@@ -5,7 +5,7 @@
5
5
  import assert from 'assert';
6
6
 
7
7
  import { synchronized, Event } from '@dxos/async';
8
- import { KeyHint, createAuthMessage, Authenticator } from '@dxos/credentials';
8
+ import { KeyHint, createAuthMessage, createFeedAdmitMessage, codec } from '@dxos/credentials';
9
9
  import { PublicKey } from '@dxos/crypto';
10
10
  import { failUndefined, raise, timed } from '@dxos/debug';
11
11
  import { PartyKey, PartySnapshot, Timeframe, FeedKey } from '@dxos/echo-protocol';
@@ -19,6 +19,7 @@ import { ActivationOptions, PartyPreferences, IdentityProvider } from '../halo';
19
19
  import { InvitationManager } from '../invitations';
20
20
  import { CredentialsProvider, PartyFeedProvider, PartyProtocolFactory } from '../pipeline';
21
21
  import { SnapshotStore } from '../snapshots';
22
+ import { createAuthenticator } from './authenticator';
22
23
  import { PartyCore, PartyOptions } from './party-core';
23
24
  import { CONTACT_DEBOUNCE_INTERVAL } from './party-manager';
24
25
 
@@ -78,7 +79,7 @@ export class PartyInternal {
78
79
  key: this.key.toHex(),
79
80
  isOpen: this.isOpen,
80
81
  isActive: this.isActive,
81
- feedKeys: this._feedProvider.getFeedKeys().length,
82
+ feedKeys: this._feedProvider.getFeeds().length,
82
83
  timeframe: this.isOpen ? this._partyCore.timeframe : undefined,
83
84
  properties: this.isOpen ? this.getPropertiesSet().expectOne().model.toObject() : undefined
84
85
  };
@@ -178,7 +179,7 @@ export class PartyInternal {
178
179
  this._identityProvider,
179
180
  this._createCredentialsProvider(this._partyCore.key, writeFeed.key),
180
181
  this._invitationManager,
181
- this._partyCore.processor.authenticator,
182
+ createAuthenticator(this._partyCore.processor, this._identityProvider),
182
183
  this._partyCore.processor.getActiveFeedSet()
183
184
  );
184
185
 
@@ -274,12 +275,20 @@ export class PartyInternal {
274
275
  return {
275
276
  get: () => {
276
277
  const identity = this._identityProvider();
277
- return Buffer.from(Authenticator.encodePayload(createAuthMessage(
278
+ const signingKey = identity.deviceKeyChain ?? identity.deviceKey ?? raise(new IdentityNotInitializedError());
279
+ return Buffer.from(codec.encode(createAuthMessage(
278
280
  identity.signer,
279
281
  partyKey,
280
282
  identity.identityKey ?? raise(new IdentityNotInitializedError()),
281
- identity.deviceKeyChain ?? identity.deviceKey ?? raise(new IdentityNotInitializedError()),
282
- identity.keyring.getKey(feedKey)
283
+ signingKey,
284
+ identity.keyring.getKey(feedKey),
285
+ undefined,
286
+ createFeedAdmitMessage(
287
+ identity.signer,
288
+ partyKey,
289
+ feedKey,
290
+ [identity.keyring.getKey(feedKey) ?? failUndefined(), signingKey]
291
+ )
283
292
  )));
284
293
  }
285
294
  };
@@ -5,10 +5,12 @@
5
5
  import assert from 'assert';
6
6
  import debug from 'debug';
7
7
 
8
+ import { Event } from '@dxos/async';
8
9
  import { Keyring, KeyType } from '@dxos/credentials';
9
10
  import { PublicKey } from '@dxos/crypto';
10
11
  import { FeedStoreIterator, MessageSelector, Timeframe } from '@dxos/echo-protocol';
11
12
  import { FeedDescriptor, FeedStore } from '@dxos/feed-store';
13
+ import { ComplexMap } from '@dxos/util';
12
14
 
13
15
  import { MetadataStore } from '../metadata';
14
16
 
@@ -16,6 +18,9 @@ const STALL_TIMEOUT = 1000;
16
18
  const warn = debug('dxos:echo-db:party-feed-provider:warn');
17
19
 
18
20
  export class PartyFeedProvider {
21
+ private readonly _feeds = new ComplexMap<PublicKey, FeedDescriptor>(x => x.toHex())
22
+ readonly feedOpened = new Event<FeedDescriptor>();
23
+
19
24
  constructor (
20
25
  private readonly _metadataStore: MetadataStore,
21
26
  private readonly _keyring: Keyring,
@@ -23,14 +28,13 @@ export class PartyFeedProvider {
23
28
  private readonly _partyKey: PublicKey
24
29
  ) {}
25
30
 
26
- // TODO(dmaretskyi): Consider refactoring this to have write feed stored separeately in metadata.
31
+ getFeeds (): FeedDescriptor[] {
32
+ return Array.from(this._feeds.values());
33
+ }
34
+
27
35
  async createOrOpenWritableFeed () {
28
36
  const partyMetadata = this._metadataStore.getParty(this._partyKey);
29
- if (!partyMetadata) {
30
- return this._createReadWriteFeed();
31
- }
32
-
33
- if (!partyMetadata.dataFeedKey) {
37
+ if (!partyMetadata?.dataFeedKey) {
34
38
  return this._createReadWriteFeed();
35
39
  }
36
40
 
@@ -39,22 +43,42 @@ export class PartyFeedProvider {
39
43
  return this._createReadWriteFeed();
40
44
  }
41
45
 
46
+ if (this._feeds.has(fullKey.publicKey)) {
47
+ return this._feeds.get(fullKey.publicKey)!;
48
+ }
49
+
42
50
  const feed = await this._feedStore.openReadWriteFeed(fullKey.publicKey, fullKey.secretKey);
43
- const feedKey = this._keyring.getKey(feed.key);
44
- assert(feedKey, 'Feed key not found');
51
+ this._feeds.set(fullKey.publicKey, feed);
52
+ this.feedOpened.emit(feed);
45
53
  return feed;
46
54
  }
47
55
 
48
- getFeedKeys () {
49
- return this._metadataStore.getParty(this._partyKey)?.feedKeys ?? [];
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
+ }
50
67
  }
51
68
 
52
69
  async createOrOpenReadOnlyFeed (feedKey: PublicKey): Promise<FeedDescriptor> {
70
+ if (this._feeds.has(feedKey)) {
71
+ return this._feeds.get(feedKey)!;
72
+ }
73
+
53
74
  await this._metadataStore.addPartyFeed(this._partyKey, feedKey);
54
75
  if (!this._keyring.hasKey(feedKey)) {
55
76
  await this._keyring.addPublicKey({ type: KeyType.FEED, publicKey: feedKey });
56
77
  }
57
- return this._feedStore.openReadOnlyFeed(feedKey);
78
+ const feed = await this._feedStore.openReadOnlyFeed(feedKey);
79
+ this._feeds.set(feedKey, feed);
80
+ this.feedOpened.emit(feed);
81
+ return feed;
58
82
  }
59
83
 
60
84
  private async _createReadWriteFeed () {
@@ -63,16 +87,18 @@ export class PartyFeedProvider {
63
87
  assert(fullKey && fullKey.secretKey);
64
88
  await this._metadataStore.setDataFeed(this._partyKey, fullKey.publicKey);
65
89
  const feed = await this._feedStore.openReadWriteFeed(fullKey.publicKey, fullKey.secretKey);
90
+ this._feeds.set(fullKey.publicKey, feed);
91
+ this.feedOpened.emit(feed);
66
92
  return feed;
67
93
  }
68
94
 
69
95
  async createIterator (messageSelector: MessageSelector, initialTimeframe?: Timeframe) {
70
96
  const iterator = new FeedStoreIterator(() => true, messageSelector, initialTimeframe ?? new Timeframe());
71
- for (const feedKey of this.getFeedKeys()) {
72
- iterator.addFeedDescriptor(await this.createOrOpenReadOnlyFeed(feedKey));
97
+ for (const feed of this._feeds.values()) {
98
+ iterator.addFeedDescriptor(feed);
73
99
  }
74
100
 
75
- this._feedStore.feedOpenedEvent.on((descriptor) => {
101
+ this.feedOpened.on((descriptor) => {
76
102
  if (this._metadataStore.getParty(this._partyKey)?.feedKeys?.find(feedKey => feedKey.equals(descriptor.key))) {
77
103
  iterator.addFeedDescriptor(descriptor);
78
104
  }
@@ -36,7 +36,7 @@ describe('party-processor', () => {
36
36
 
37
37
  const message: IHaloStream = {
38
38
  meta: {
39
- feedKey: feedKey.publicKey.asUint8Array(),
39
+ feedKey: feedKey.publicKey,
40
40
  seq: 0
41
41
  // TODO(telackey): Should ownership data go here?
42
42
  },
@@ -67,7 +67,7 @@ describe('party-processor', () => {
67
67
 
68
68
  const genesisMessage: IHaloStream = {
69
69
  meta: {
70
- feedKey: feedKey.publicKey.asUint8Array(),
70
+ feedKey: feedKey.publicKey,
71
71
  seq: 0
72
72
  // TODO(telackey): Should ownership data go here?
73
73
  },
@@ -78,7 +78,7 @@ describe('party-processor', () => {
78
78
  const feedKey2 = await keyring.createKeyRecord({ type: KeyType.FEED });
79
79
  const feedAdmit: IHaloStream = {
80
80
  meta: {
81
- feedKey: feedKey.publicKey.asUint8Array(),
81
+ feedKey: feedKey.publicKey,
82
82
  seq: 0
83
83
  // TODO(telackey): Should ownership data go here?
84
84
  },
@@ -106,7 +106,7 @@ describe('party-processor', () => {
106
106
 
107
107
  const genesisMessage: IHaloStream = {
108
108
  meta: {
109
- feedKey: feedKey.publicKey.asUint8Array(),
109
+ feedKey: feedKey.publicKey,
110
110
  seq: 0
111
111
  // TODO(telackey): Should ownership data go here?
112
112
  },
@@ -115,7 +115,7 @@ describe('party-processor', () => {
115
115
  await partyProcessor.processMessage(genesisMessage);
116
116
  const feedAdmit: IHaloStream = {
117
117
  meta: {
118
- feedKey: feedKey.publicKey.asUint8Array(),
118
+ feedKey: feedKey.publicKey,
119
119
  seq: 0
120
120
  // TODO(telackey): Should ownership data go here?
121
121
  },
@@ -129,7 +129,7 @@ describe('party-processor', () => {
129
129
 
130
130
  const keyAdmit: IHaloStream = {
131
131
  meta: {
132
- feedKey: feedKey.publicKey.asUint8Array(),
132
+ feedKey: feedKey.publicKey,
133
133
  seq: 1
134
134
  },
135
135
  data: createEnvelopeMessage(keyring, partyKey.publicKey,
@@ -141,7 +141,7 @@ describe('party-processor', () => {
141
141
 
142
142
  const feedAdmit2: IHaloStream = {
143
143
  meta: {
144
- feedKey: feedKey.publicKey.asUint8Array(),
144
+ feedKey: feedKey.publicKey,
145
145
  seq: 1
146
146
  // TODO(telackey): Should ownership data go here?
147
147
  },
@@ -170,7 +170,7 @@ describe('party-processor', () => {
170
170
  const partyProcessor = new PartyProcessor(partyKey.publicKey);
171
171
  expect(partyProcessor.partyKey).toBeTruthy();
172
172
 
173
- const meta = (seq: number) => ({ feedKey: feedKey.publicKey.asUint8Array(), seq });
173
+ const meta = (seq: number) => ({ feedKey: feedKey.publicKey, seq });
174
174
 
175
175
  await partyProcessor.processMessage({
176
176
  meta: meta(0),
@@ -7,11 +7,9 @@ import debug from 'debug';
7
7
 
8
8
  import { Event } from '@dxos/async';
9
9
  import {
10
- Authenticator,
11
10
  KeyHint,
12
11
  KeyRecord,
13
12
  PartyState,
14
- PartyAuthenticator,
15
13
  Message as HaloMessage,
16
14
  IdentityEventType,
17
15
  PartyEventType
@@ -32,7 +30,6 @@ export interface FeedSetProvider {
32
30
  */
33
31
  export class PartyProcessor {
34
32
  private readonly _state: PartyState;
35
- private readonly _authenticator: Authenticator;
36
33
 
37
34
  private _outboundHaloStream: FeedWriter<HaloMessage> | undefined;
38
35
 
@@ -49,21 +46,14 @@ export class PartyProcessor {
49
46
  private readonly _partyKey: PartyKey
50
47
  ) {
51
48
  this._state = new PartyState(this._partyKey);
52
- this._authenticator = new PartyAuthenticator(this._state);
53
-
54
- /* TODO(telackey): `@dxos/credentials` was only half converted to TS. In its current state, the KeyRecord type
55
- * is not exported, and the PartyStateMachine being used is not properly understood as an EventEmitter by TS.
56
- * Casting to 'any' is a workaround for the compiler, but the fix is fully to convert @dxos/credentials to TS.
57
- */
58
- const state = this._state as any;
59
49
 
60
50
  // TODO(marik-d): Use `Event.wrap` here.
61
- state.on(PartyEventType.ADMIT_FEED, (keyRecord: any) => {
51
+ this._state.on(PartyEventType.ADMIT_FEED, (keyRecord: any) => {
62
52
  log(`Feed key admitted ${keyRecord.publicKey.toHex()}`);
63
53
  this._feedAdded.emit(keyRecord.publicKey);
64
54
  });
65
- state.on(PartyEventType.ADMIT_KEY, (keyRecord: KeyRecord) => this.keyOrInfoAdded.emit(keyRecord.publicKey));
66
- state.on(IdentityEventType.UPDATE_IDENTITY, (publicKey: PublicKey) => this.keyOrInfoAdded.emit(publicKey));
55
+ this._state.on(PartyEventType.ADMIT_KEY, (keyRecord: KeyRecord) => this.keyOrInfoAdded.emit(keyRecord.publicKey));
56
+ this._state.on(IdentityEventType.UPDATE_IDENTITY, (publicKey: PublicKey) => this.keyOrInfoAdded.emit(publicKey));
67
57
  }
68
58
 
69
59
  get partyKey () {
@@ -90,8 +80,8 @@ export class PartyProcessor {
90
80
  return this._state.credentialMessages.size === 0;
91
81
  }
92
82
 
93
- get authenticator () {
94
- return this._authenticator;
83
+ get state () {
84
+ return this._state;
95
85
  }
96
86
 
97
87
  isFeedAdmitted (feedKey: FeedKey) {
@@ -52,7 +52,7 @@ describe('pipeline', () => {
52
52
  await partyProcessor.processMessage({
53
53
  data: createPartyGenesisMessage(keyring, partyKey, feedKey.publicKey, identityKey),
54
54
  meta: {
55
- feedKey: feedKey.publicKey.asBuffer(),
55
+ feedKey: feedKey.publicKey,
56
56
  seq: 0
57
57
  }
58
58
  });
@@ -141,7 +141,7 @@ export class Pipeline {
141
141
  meta: {
142
142
  seq: block.seq,
143
143
  feedKey: block.key,
144
- memberKey: memberKey.asUint8Array(),
144
+ memberKey,
145
145
  timeframe: message.echo.timeframe ?? new Timeframe()
146
146
  },
147
147
  data: message.echo