@dxos/echo-db 2.33.1-dev.83d113fe → 2.33.1-dev.eb88db79
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/database/data-mirror.js +2 -2
- package/dist/src/database/data-mirror.js.map +1 -1
- package/dist/src/database/item-demuxer.test.js +2 -2
- package/dist/src/database/item-demuxer.test.js.map +1 -1
- package/dist/src/echo.d.ts +19 -19
- package/dist/src/echo.d.ts.map +1 -1
- package/dist/src/echo.js +32 -19
- package/dist/src/echo.js.map +1 -1
- package/dist/src/echo.test.js +1 -1
- 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 +7 -3
- package/dist/src/parties/party-core.js.map +1 -1
- package/dist/src/parties/party-core.test.js +136 -5
- package/dist/src/parties/party-core.test.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 +20 -13
- package/dist/src/parties/party-internal.js.map +1 -1
- package/dist/src/parties/party-manager.test.js +4 -4
- package/dist/src/parties/party-manager.test.js.map +1 -1
- package/dist/src/pipeline/party-processor.d.ts +3 -4
- package/dist/src/pipeline/party-processor.d.ts.map +1 -1
- package/dist/src/pipeline/party-processor.js +8 -14
- package/dist/src/pipeline/party-processor.js.map +1 -1
- package/dist/src/pipeline/party-processor.test.js +8 -8
- package/dist/src/pipeline/party-processor.test.js.map +1 -1
- package/dist/src/pipeline/party-protocol-factory.d.ts +11 -0
- package/dist/src/pipeline/party-protocol-factory.d.ts.map +1 -1
- package/dist/src/pipeline/party-protocol-factory.js +2 -1
- package/dist/src/pipeline/party-protocol-factory.js.map +1 -1
- package/dist/src/pipeline/pipeline.js +1 -1
- package/dist/src/pipeline/pipeline.js.map +1 -1
- package/dist/src/pipeline/pipeline.test.js +3 -3
- package/dist/src/pipeline/pipeline.test.js.map +1 -1
- package/dist/src/util/persistant-ram-storage.d.ts.map +1 -1
- package/dist/src/util/persistant-ram-storage.js +1 -1
- package/dist/src/util/persistant-ram-storage.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +17 -17
- package/src/database/data-mirror.ts +2 -2
- package/src/database/item-demuxer.test.ts +3 -3
- package/src/echo.test.ts +1 -1
- package/src/echo.ts +76 -73
- 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.test.ts +233 -10
- package/src/parties/party-core.ts +8 -3
- package/src/parties/party-factory.ts +1 -6
- package/src/parties/party-internal.ts +28 -14
- package/src/parties/party-manager.test.ts +5 -5
- package/src/pipeline/party-processor.test.ts +8 -8
- package/src/pipeline/party-processor.ts +8 -18
- package/src/pipeline/party-protocol-factory.ts +1 -1
- package/src/pipeline/pipeline.test.ts +4 -4
- package/src/pipeline/pipeline.ts +1 -1
- package/src/util/persistant-ram-storage.ts +3 -3
package/src/echo.ts
CHANGED
|
@@ -6,6 +6,7 @@ import assert from 'assert';
|
|
|
6
6
|
import debug from 'debug';
|
|
7
7
|
import memdown from 'memdown';
|
|
8
8
|
|
|
9
|
+
import { synchronized } from '@dxos/async';
|
|
9
10
|
import { Keyring, KeyStore, SecretProvider } from '@dxos/credentials';
|
|
10
11
|
import { PublicKey } from '@dxos/crypto';
|
|
11
12
|
import { InvalidStateError } from '@dxos/debug';
|
|
@@ -237,6 +238,7 @@ export class ECHO {
|
|
|
237
238
|
*
|
|
238
239
|
* Previously active parties will be opened and will begin replication.
|
|
239
240
|
*/
|
|
241
|
+
@synchronized
|
|
240
242
|
async open (onProgressCallback?: ((progress: OpenProgress) => void) | undefined) {
|
|
241
243
|
if (this.isOpen) {
|
|
242
244
|
return;
|
|
@@ -258,6 +260,7 @@ export class ECHO {
|
|
|
258
260
|
/**
|
|
259
261
|
* Closes the ECHO instance.
|
|
260
262
|
*/
|
|
263
|
+
@synchronized
|
|
261
264
|
async close () {
|
|
262
265
|
if (!this.isOpen) {
|
|
263
266
|
return;
|
|
@@ -276,107 +279,107 @@ export class ECHO {
|
|
|
276
279
|
await this.networkManager.destroy();
|
|
277
280
|
}
|
|
278
281
|
|
|
279
|
-
|
|
282
|
+
/**
|
|
280
283
|
* Removes all data and closes this ECHO instance.
|
|
281
284
|
*
|
|
282
285
|
* The instance will be in an unusable state at this point and a page refresh is recommended.
|
|
283
286
|
*/
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
287
|
+
// TODO(burdon): Enable re-open.
|
|
288
|
+
async reset () {
|
|
289
|
+
await this.close();
|
|
290
|
+
|
|
291
|
+
try {
|
|
292
|
+
if (this._feedStore.storage.destroy) {
|
|
293
|
+
await this._feedStore.storage.destroy();
|
|
294
|
+
}
|
|
295
|
+
} catch (err: any) {
|
|
296
|
+
error('Error clearing feed storage:', err);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
await this.halo.reset();
|
|
300
|
+
|
|
301
|
+
try {
|
|
302
|
+
await this._snapshotStore.clear();
|
|
303
|
+
} catch (err: any) {
|
|
304
|
+
error('Error clearing snapshot storage:', err);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
try {
|
|
308
|
+
await this._metadataStore.clear();
|
|
309
|
+
} catch (err: any) {
|
|
310
|
+
error('Error clearing metadata storage:', err);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
//
|
|
315
|
+
// Parties.
|
|
316
|
+
//
|
|
317
|
+
|
|
318
|
+
/**
|
|
316
319
|
* Creates a new party.
|
|
317
320
|
*/
|
|
318
|
-
|
|
319
|
-
|
|
321
|
+
async createParty (): Promise<PartyInternal> {
|
|
322
|
+
await this.open();
|
|
320
323
|
|
|
321
|
-
|
|
322
|
-
|
|
324
|
+
const party = await this._partyManager.createParty();
|
|
325
|
+
await party.open();
|
|
323
326
|
|
|
324
|
-
|
|
325
|
-
|
|
327
|
+
return party;
|
|
328
|
+
}
|
|
326
329
|
|
|
327
|
-
|
|
330
|
+
/**
|
|
328
331
|
* Clones an existing party from a snapshot.
|
|
329
332
|
* @param snapshot
|
|
330
333
|
*/
|
|
331
|
-
|
|
332
|
-
|
|
334
|
+
async cloneParty (snapshot: PartySnapshot) {
|
|
335
|
+
await this.open();
|
|
333
336
|
|
|
334
|
-
|
|
335
|
-
|
|
337
|
+
const party = await this._partyManager.cloneParty(snapshot);
|
|
338
|
+
await party.open();
|
|
336
339
|
|
|
337
|
-
|
|
338
|
-
|
|
340
|
+
return party;
|
|
341
|
+
}
|
|
339
342
|
|
|
340
|
-
|
|
343
|
+
/**
|
|
341
344
|
* Returns an individual party by it's key.
|
|
342
345
|
* @param {PartyKey} partyKey
|
|
343
346
|
*/
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
347
|
+
getParty (partyKey: PartyKey): PartyInternal | undefined {
|
|
348
|
+
if (!this._partyManager.isOpen) {
|
|
349
|
+
throw new InvalidStateError();
|
|
350
|
+
}
|
|
348
351
|
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
+
const party = this._partyManager.parties.find(party => party.key.equals(partyKey));
|
|
353
|
+
return party;
|
|
354
|
+
}
|
|
352
355
|
|
|
353
|
-
|
|
356
|
+
/**
|
|
354
357
|
* Queries for a set of Parties matching the optional filter.
|
|
355
358
|
* @param {PartyFilter} filter
|
|
356
359
|
*/
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
360
|
+
// eslint-disable-next-line unused-imports/no-unused-vars
|
|
361
|
+
queryParties (filter?: PartyFilter): ResultSet<PartyInternal> {
|
|
362
|
+
if (!this._partyManager.isOpen) {
|
|
363
|
+
throw new InvalidStateError();
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
return new ResultSet(
|
|
367
|
+
this._partyManager.update.discardParameter(),
|
|
368
|
+
() => this._partyManager.parties
|
|
369
|
+
);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/**
|
|
370
373
|
* Joins a party that was created by another peer and starts replicating with it.
|
|
371
374
|
* @param invitationDescriptor Invitation descriptor passed from another peer.
|
|
372
375
|
* @param secretProvider Shared secret provider, the other peer creating the invitation must have the same secret.
|
|
373
376
|
*/
|
|
374
|
-
|
|
375
|
-
|
|
377
|
+
async joinParty (invitationDescriptor: InvitationDescriptor, secretProvider?: SecretProvider): Promise<PartyInternal> {
|
|
378
|
+
assert(this._partyManager.isOpen, new InvalidStateError());
|
|
376
379
|
|
|
377
|
-
|
|
380
|
+
const actualSecretProvider =
|
|
378
381
|
secretProvider ?? OfflineInvitationClaimer.createSecretProvider(this.halo.identity);
|
|
379
382
|
|
|
380
|
-
|
|
381
|
-
|
|
383
|
+
return this._partyManager.joinParty(invitationDescriptor, actualSecretProvider);
|
|
384
|
+
}
|
|
382
385
|
}
|
|
@@ -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
|
+
}
|
|
@@ -5,24 +5,26 @@
|
|
|
5
5
|
import expect from 'expect';
|
|
6
6
|
import { it as test } from 'mocha';
|
|
7
7
|
|
|
8
|
+
import { promiseTimeout } from '@dxos/async';
|
|
8
9
|
import { createFeedAdmitMessage, createPartyGenesisMessage, Keyring, KeyType } from '@dxos/credentials';
|
|
9
|
-
import { PublicKey } from '@dxos/crypto';
|
|
10
|
-
import { codec } from '@dxos/echo-protocol';
|
|
10
|
+
import { createId, PublicKey } from '@dxos/crypto';
|
|
11
|
+
import { codec, Timeframe } from '@dxos/echo-protocol';
|
|
11
12
|
import { FeedStore } from '@dxos/feed-store';
|
|
13
|
+
import { createTestProtocolPair } from '@dxos/mesh-protocol';
|
|
12
14
|
import { ModelFactory } from '@dxos/model-factory';
|
|
13
15
|
import { ObjectModel } from '@dxos/object-model';
|
|
14
|
-
import { createStorage,
|
|
16
|
+
import { createStorage, StorageType } from '@dxos/random-access-multi-storage';
|
|
15
17
|
import { afterTest } from '@dxos/testutils';
|
|
16
18
|
|
|
17
19
|
import { MetadataStore } from '../metadata';
|
|
18
|
-
import { PartyFeedProvider } from '../pipeline';
|
|
20
|
+
import { PartyFeedProvider, ReplicatorProtocolPluginFactory } from '../pipeline';
|
|
19
21
|
import { SnapshotStore } from '../snapshots';
|
|
20
22
|
import { createRamStorage } from '../util';
|
|
21
23
|
import { PartyCore } from './party-core';
|
|
22
24
|
|
|
23
25
|
describe('PartyCore', () => {
|
|
24
26
|
const setup = async () => {
|
|
25
|
-
const storage = createStorage('',
|
|
27
|
+
const storage = createStorage('', StorageType.RAM);
|
|
26
28
|
const feedStore = new FeedStore(storage, { valueEncoding: codec });
|
|
27
29
|
afterTest(async () => feedStore.close());
|
|
28
30
|
|
|
@@ -31,7 +33,7 @@ describe('PartyCore', () => {
|
|
|
31
33
|
const metadataStore = new MetadataStore(createRamStorage());
|
|
32
34
|
|
|
33
35
|
const modelFactory = new ModelFactory().registerModel(ObjectModel);
|
|
34
|
-
const snapshotStore = new SnapshotStore(createStorage('',
|
|
36
|
+
const snapshotStore = new SnapshotStore(createStorage('', StorageType.RAM));
|
|
35
37
|
|
|
36
38
|
const partyKey = await keyring.createKeyRecord({ type: KeyType.PARTY });
|
|
37
39
|
|
|
@@ -65,10 +67,7 @@ describe('PartyCore', () => {
|
|
|
65
67
|
[partyKey]
|
|
66
68
|
));
|
|
67
69
|
|
|
68
|
-
|
|
69
|
-
await keyring.deleteSecretKey(partyKey);
|
|
70
|
-
|
|
71
|
-
return { party, feedKey: feed.key, feed, feedStore };
|
|
70
|
+
return { party, feedKey: feed.key, feed, feedStore, partyKey, keyring, partyFeedProvider };
|
|
72
71
|
};
|
|
73
72
|
|
|
74
73
|
test('create & have the feed key admitted', async () => {
|
|
@@ -115,4 +114,228 @@ describe('PartyCore', () => {
|
|
|
115
114
|
expect(parent.children).toContain(child);
|
|
116
115
|
}
|
|
117
116
|
});
|
|
117
|
+
|
|
118
|
+
test('feed admit message triggers new feed to be opened', async () => {
|
|
119
|
+
const { party, partyKey, keyring, partyFeedProvider, feedStore } = await setup();
|
|
120
|
+
|
|
121
|
+
const feedKey = await keyring.createKeyRecord({ type: KeyType.FEED });
|
|
122
|
+
|
|
123
|
+
const eventFired = feedStore.feedOpenedEvent.waitForCount(1);
|
|
124
|
+
await party.processor.writeHaloMessage(createFeedAdmitMessage(
|
|
125
|
+
keyring,
|
|
126
|
+
party.key,
|
|
127
|
+
feedKey.publicKey,
|
|
128
|
+
[partyKey]
|
|
129
|
+
));
|
|
130
|
+
await promiseTimeout(eventFired, 1000, new Error('timeout'));
|
|
131
|
+
expect(partyFeedProvider.getFeeds().find(k => k.key.equals(feedKey.publicKey))).toBeTruthy();
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
test('opens feed from hints', async () => {
|
|
135
|
+
const storage = createStorage('', StorageType.RAM);
|
|
136
|
+
const feedStore = new FeedStore(storage, { valueEncoding: codec });
|
|
137
|
+
afterTest(async () => feedStore.close());
|
|
138
|
+
|
|
139
|
+
const keyring = new Keyring();
|
|
140
|
+
|
|
141
|
+
const metadataStore = new MetadataStore(createRamStorage());
|
|
142
|
+
|
|
143
|
+
const modelFactory = new ModelFactory().registerModel(ObjectModel);
|
|
144
|
+
const snapshotStore = new SnapshotStore(createStorage('', StorageType.RAM));
|
|
145
|
+
|
|
146
|
+
const partyKey = await keyring.createKeyRecord({ type: KeyType.PARTY });
|
|
147
|
+
|
|
148
|
+
const partyFeedProvider = new PartyFeedProvider(metadataStore, keyring, feedStore, partyKey.publicKey);
|
|
149
|
+
|
|
150
|
+
const otherFeedKey = PublicKey.random();
|
|
151
|
+
|
|
152
|
+
const party = new PartyCore(
|
|
153
|
+
partyKey.publicKey,
|
|
154
|
+
partyFeedProvider,
|
|
155
|
+
modelFactory,
|
|
156
|
+
snapshotStore,
|
|
157
|
+
PublicKey.random()
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
await partyFeedProvider.createOrOpenWritableFeed();
|
|
161
|
+
|
|
162
|
+
const feedOpened = feedStore.feedOpenedEvent.waitForCount(1);
|
|
163
|
+
|
|
164
|
+
await party.open([{ type: KeyType.FEED, publicKey: otherFeedKey }]);
|
|
165
|
+
afterTest(async () => party.close());
|
|
166
|
+
|
|
167
|
+
await feedOpened;
|
|
168
|
+
|
|
169
|
+
expect(partyFeedProvider.getFeeds().some(k => k.key.equals(otherFeedKey))).toEqual(true);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
test('manually create item', async () => {
|
|
173
|
+
const { party, partyFeedProvider } = await setup();
|
|
174
|
+
await party.open();
|
|
175
|
+
|
|
176
|
+
const feed = await partyFeedProvider.createOrOpenWritableFeed();
|
|
177
|
+
|
|
178
|
+
const itemId = createId();
|
|
179
|
+
await feed.feed.append({
|
|
180
|
+
echo: {
|
|
181
|
+
itemId,
|
|
182
|
+
genesis: {
|
|
183
|
+
itemType: 'dxos:example',
|
|
184
|
+
modelType: ObjectModel.meta.type
|
|
185
|
+
},
|
|
186
|
+
timeframe: new Timeframe()
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
await promiseTimeout(party.database.waitForItem({ id: itemId }), 1000, new Error('timeout'));
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
test('admit a second feed to the party', async () => {
|
|
194
|
+
const { party, keyring, partyKey, feedStore } = await setup();
|
|
195
|
+
await party.open();
|
|
196
|
+
|
|
197
|
+
const feedKey = await keyring.createKeyRecord({ type: KeyType.FEED });
|
|
198
|
+
const fullKey = keyring.getFullKey(feedKey.publicKey);
|
|
199
|
+
const feed2 = await feedStore.openReadWriteFeed(fullKey!.publicKey, fullKey!.secretKey!);
|
|
200
|
+
|
|
201
|
+
await party.processor.writeHaloMessage(createFeedAdmitMessage(
|
|
202
|
+
keyring,
|
|
203
|
+
party.key,
|
|
204
|
+
feed2.key,
|
|
205
|
+
[partyKey]
|
|
206
|
+
));
|
|
207
|
+
|
|
208
|
+
const itemId = createId();
|
|
209
|
+
await feed2.append({
|
|
210
|
+
echo: {
|
|
211
|
+
itemId,
|
|
212
|
+
genesis: {
|
|
213
|
+
itemType: 'dxos:example',
|
|
214
|
+
modelType: ObjectModel.meta.type
|
|
215
|
+
},
|
|
216
|
+
timeframe: new Timeframe()
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
await promiseTimeout(party.database.waitForItem({ id: itemId }), 1000, new Error('timeout'));
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
test('admit feed and then open it', async () => {
|
|
224
|
+
const { party, keyring, partyKey, feedStore } = await setup();
|
|
225
|
+
await party.open();
|
|
226
|
+
|
|
227
|
+
const feedKey = await keyring.createKeyRecord({ type: KeyType.FEED });
|
|
228
|
+
const fullKey = keyring.getFullKey(feedKey.publicKey);
|
|
229
|
+
|
|
230
|
+
await party.processor.writeHaloMessage(createFeedAdmitMessage(
|
|
231
|
+
keyring,
|
|
232
|
+
party.key,
|
|
233
|
+
feedKey.publicKey,
|
|
234
|
+
[partyKey]
|
|
235
|
+
));
|
|
236
|
+
|
|
237
|
+
const feed2 = await feedStore.openReadWriteFeed(fullKey!.publicKey, fullKey!.secretKey!);
|
|
238
|
+
const itemId = createId();
|
|
239
|
+
await feed2.append({
|
|
240
|
+
echo: {
|
|
241
|
+
itemId,
|
|
242
|
+
genesis: {
|
|
243
|
+
itemType: 'dxos:example',
|
|
244
|
+
modelType: ObjectModel.meta.type
|
|
245
|
+
},
|
|
246
|
+
timeframe: new Timeframe()
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
await promiseTimeout(party.database.waitForItem({ id: itemId }), 1000, new Error('timeout'));
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
test('self-admitting feed with a hint', async () => {
|
|
254
|
+
const { party, keyring, partyKey, feedStore } = await setup();
|
|
255
|
+
await party.open();
|
|
256
|
+
|
|
257
|
+
const feedKey = await keyring.createKeyRecord({ type: KeyType.FEED });
|
|
258
|
+
const fullKey = keyring.getFullKey(feedKey.publicKey);
|
|
259
|
+
const feed2 = await feedStore.openReadWriteFeed(fullKey!.publicKey, fullKey!.secretKey!);
|
|
260
|
+
|
|
261
|
+
await party.processor.takeHints([{
|
|
262
|
+
type: KeyType.FEED,
|
|
263
|
+
publicKey: feedKey.publicKey
|
|
264
|
+
}]);
|
|
265
|
+
|
|
266
|
+
await feed2.append({
|
|
267
|
+
halo: createFeedAdmitMessage(
|
|
268
|
+
keyring,
|
|
269
|
+
party.key,
|
|
270
|
+
feedKey.publicKey,
|
|
271
|
+
[partyKey]
|
|
272
|
+
)
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
const itemId = createId();
|
|
276
|
+
await feed2.append({
|
|
277
|
+
echo: {
|
|
278
|
+
itemId,
|
|
279
|
+
genesis: {
|
|
280
|
+
itemType: 'dxos:example',
|
|
281
|
+
modelType: ObjectModel.meta.type
|
|
282
|
+
},
|
|
283
|
+
timeframe: new Timeframe()
|
|
284
|
+
}
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
await promiseTimeout(party.database.waitForItem({ id: itemId }), 1000, new Error('timeout'));
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
test('two instances replicating', async () => {
|
|
291
|
+
const peer1 = await setup();
|
|
292
|
+
|
|
293
|
+
const storage = createStorage('', StorageType.RAM);
|
|
294
|
+
const feedStore = new FeedStore(storage, { valueEncoding: codec });
|
|
295
|
+
afterTest(async () => feedStore.close());
|
|
296
|
+
|
|
297
|
+
const metadataStore = new MetadataStore(createRamStorage());
|
|
298
|
+
|
|
299
|
+
const modelFactory = new ModelFactory().registerModel(ObjectModel);
|
|
300
|
+
const snapshotStore = new SnapshotStore(createStorage('', StorageType.RAM));
|
|
301
|
+
|
|
302
|
+
const partyFeedProvider = new PartyFeedProvider(metadataStore, peer1.keyring, feedStore, peer1.party.key);
|
|
303
|
+
|
|
304
|
+
const party2 = new PartyCore(
|
|
305
|
+
peer1.party.key,
|
|
306
|
+
partyFeedProvider,
|
|
307
|
+
modelFactory,
|
|
308
|
+
snapshotStore,
|
|
309
|
+
PublicKey.random()
|
|
310
|
+
);
|
|
311
|
+
|
|
312
|
+
const feed2 = await partyFeedProvider.createOrOpenWritableFeed();
|
|
313
|
+
|
|
314
|
+
await peer1.party.processor.writeHaloMessage(createFeedAdmitMessage(
|
|
315
|
+
peer1.keyring,
|
|
316
|
+
peer1.party.key,
|
|
317
|
+
feed2.key,
|
|
318
|
+
[peer1.partyKey]
|
|
319
|
+
));
|
|
320
|
+
|
|
321
|
+
await party2.open([{
|
|
322
|
+
publicKey: peer1.feedKey,
|
|
323
|
+
type: KeyType.FEED
|
|
324
|
+
}]);
|
|
325
|
+
afterTest(async () => party2.close());
|
|
326
|
+
|
|
327
|
+
createTestProtocolPair(
|
|
328
|
+
new ReplicatorProtocolPluginFactory(
|
|
329
|
+
peer1.partyFeedProvider,
|
|
330
|
+
peer1.party.processor.getActiveFeedSet()
|
|
331
|
+
).createPlugins().map(r => r.createExtension()),
|
|
332
|
+
new ReplicatorProtocolPluginFactory(
|
|
333
|
+
partyFeedProvider,
|
|
334
|
+
peer1.party.processor.getActiveFeedSet()
|
|
335
|
+
).createPlugins().map(r => r.createExtension())
|
|
336
|
+
);
|
|
337
|
+
|
|
338
|
+
const item1 = await peer1.party.database.createItem();
|
|
339
|
+
await promiseTimeout(party2.database.waitForItem({ id: item1.id }), 1000, new Error('timeout'));
|
|
340
|
+
});
|
|
118
341
|
});
|
|
@@ -113,9 +113,14 @@ export class PartyCore {
|
|
|
113
113
|
|
|
114
114
|
if (!this._partyProcessor) {
|
|
115
115
|
this._partyProcessor = new PartyProcessor(this._partyKey);
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
116
|
+
}
|
|
117
|
+
// Automatically open new admitted feeds.
|
|
118
|
+
this._subscriptions.push(this._partyProcessor.feedAdded.on(feed => {
|
|
119
|
+
void this._feedProvider.createOrOpenReadOnlyFeed(feed);
|
|
120
|
+
}));
|
|
121
|
+
|
|
122
|
+
if (keyHints.length > 0) {
|
|
123
|
+
await this._partyProcessor.takeHints(keyHints);
|
|
119
124
|
}
|
|
120
125
|
|
|
121
126
|
//
|
|
@@ -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();
|