@dxos/echo-db 2.32.1-dev.ebff6d2e → 2.33.1-dev.0a5ae2e2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/echo.test.js +70 -0
- package/dist/src/echo.test.js.map +1 -1
- package/dist/src/invitations/greeting-initiator.d.ts +1 -4
- package/dist/src/invitations/greeting-initiator.d.ts.map +1 -1
- package/dist/src/invitations/greeting-initiator.js +1 -7
- package/dist/src/invitations/greeting-initiator.js.map +1 -1
- package/dist/src/invitations/halo-recovery-initiator.d.ts.map +1 -1
- package/dist/src/invitations/halo-recovery-initiator.js +2 -2
- package/dist/src/invitations/halo-recovery-initiator.js.map +1 -1
- package/dist/src/invitations/offline-invitation-claimer.d.ts.map +1 -1
- package/dist/src/invitations/offline-invitation-claimer.js +2 -2
- package/dist/src/invitations/offline-invitation-claimer.js.map +1 -1
- package/dist/src/parties/authenticator.d.ts +5 -0
- package/dist/src/parties/authenticator.d.ts.map +1 -0
- package/dist/src/parties/authenticator.js +27 -0
- package/dist/src/parties/authenticator.js.map +1 -0
- package/dist/src/parties/party-core.d.ts.map +1 -1
- package/dist/src/parties/party-core.js +1 -0
- package/dist/src/parties/party-core.js.map +1 -1
- package/dist/src/parties/party-factory.d.ts.map +1 -1
- package/dist/src/parties/party-factory.js +1 -5
- package/dist/src/parties/party-factory.js.map +1 -1
- package/dist/src/parties/party-internal.d.ts.map +1 -1
- package/dist/src/parties/party-internal.js +6 -4
- package/dist/src/parties/party-internal.js.map +1 -1
- package/dist/src/pipeline/party-feed-provider.d.ts +5 -1
- package/dist/src/pipeline/party-feed-provider.d.ts.map +1 -1
- package/dist/src/pipeline/party-feed-provider.js +36 -13
- package/dist/src/pipeline/party-feed-provider.js.map +1 -1
- package/dist/src/pipeline/party-processor.d.ts +2 -3
- package/dist/src/pipeline/party-processor.d.ts.map +1 -1
- package/dist/src/pipeline/party-processor.js +5 -11
- package/dist/src/pipeline/party-processor.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +20 -20
- package/src/echo.test.ts +100 -1
- package/src/invitations/greeting-initiator.ts +1 -26
- package/src/invitations/halo-recovery-initiator.ts +4 -4
- package/src/invitations/offline-invitation-claimer.ts +4 -4
- package/src/parties/authenticator.ts +31 -0
- package/src/parties/party-core.ts +1 -0
- package/src/parties/party-factory.ts +1 -6
- package/src/parties/party-internal.ts +15 -6
- package/src/pipeline/party-feed-provider.ts +40 -14
- package/src/pipeline/party-processor.ts +5 -15
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(
|
|
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 } =
|
|
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 } =
|
|
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(
|
|
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,
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
44
|
-
|
|
51
|
+
this._feeds.set(fullKey.publicKey, feed);
|
|
52
|
+
this.feedOpened.emit(feed);
|
|
45
53
|
return feed;
|
|
46
54
|
}
|
|
47
55
|
|
|
48
|
-
|
|
49
|
-
|
|
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
|
-
|
|
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
|
|
72
|
-
iterator.addFeedDescriptor(
|
|
97
|
+
for (const feed of this._feeds.values()) {
|
|
98
|
+
iterator.addFeedDescriptor(feed);
|
|
73
99
|
}
|
|
74
100
|
|
|
75
|
-
this.
|
|
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
|
}
|
|
@@ -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
|
-
|
|
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
|
-
|
|
66
|
-
|
|
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
|
|
94
|
-
return this.
|
|
83
|
+
get state () {
|
|
84
|
+
return this._state;
|
|
95
85
|
}
|
|
96
86
|
|
|
97
87
|
isFeedAdmitted (feedKey: FeedKey) {
|