cojson 0.13.18 → 0.13.21
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/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +15 -0
- package/dist/PeerState.d.ts +1 -1
- package/dist/PeerState.d.ts.map +1 -1
- package/dist/PeerState.js +7 -36
- package/dist/PeerState.js.map +1 -1
- package/dist/coValueCore/coValueCore.d.ts +2 -2
- package/dist/coValueCore/coValueCore.d.ts.map +1 -1
- package/dist/coValueCore/coValueCore.js +15 -10
- package/dist/coValueCore/coValueCore.js.map +1 -1
- package/dist/localNode.d.ts +2 -3
- package/dist/localNode.d.ts.map +1 -1
- package/dist/localNode.js +55 -35
- package/dist/localNode.js.map +1 -1
- package/dist/streamUtils.d.ts +5 -5
- package/dist/streamUtils.d.ts.map +1 -1
- package/dist/streamUtils.js +5 -20
- package/dist/streamUtils.js.map +1 -1
- package/dist/sync.d.ts +6 -4
- package/dist/sync.d.ts.map +1 -1
- package/dist/sync.js +35 -19
- package/dist/sync.js.map +1 -1
- package/dist/tests/PeerState.test.js +0 -31
- package/dist/tests/PeerState.test.js.map +1 -1
- package/dist/tests/SyncStateManager.test.js +41 -6
- package/dist/tests/SyncStateManager.test.js.map +1 -1
- package/dist/tests/account.test.js +16 -0
- package/dist/tests/account.test.js.map +1 -1
- package/dist/tests/group.test.js.map +1 -1
- package/dist/tests/permissions.test.js +5 -3
- package/dist/tests/permissions.test.js.map +1 -1
- package/dist/tests/sync.auth.test.js +64 -15
- package/dist/tests/sync.auth.test.js.map +1 -1
- package/dist/tests/sync.invite.test.d.ts +2 -0
- package/dist/tests/sync.invite.test.d.ts.map +1 -0
- package/dist/tests/sync.invite.test.js +123 -0
- package/dist/tests/sync.invite.test.js.map +1 -0
- package/dist/tests/sync.load.test.js +2 -2
- package/dist/tests/sync.load.test.js.map +1 -1
- package/dist/tests/testUtils.d.ts +11 -2
- package/dist/tests/testUtils.d.ts.map +1 -1
- package/dist/tests/testUtils.js +27 -30
- package/dist/tests/testUtils.js.map +1 -1
- package/package.json +1 -1
- package/src/PeerState.ts +8 -40
- package/src/coValueCore/coValueCore.ts +18 -22
- package/src/localNode.ts +79 -49
- package/src/streamUtils.ts +7 -34
- package/src/sync.ts +51 -22
- package/src/tests/PeerState.test.ts +0 -37
- package/src/tests/SyncStateManager.test.ts +56 -6
- package/src/tests/account.test.ts +24 -0
- package/src/tests/group.test.ts +0 -1
- package/src/tests/permissions.test.ts +15 -10
- package/src/tests/sync.auth.test.ts +79 -21
- package/src/tests/sync.invite.test.ts +163 -0
- package/src/tests/sync.load.test.ts +3 -2
- package/src/tests/testUtils.ts +35 -34
- package/dist/utils.d.ts +0 -5
- package/dist/utils.d.ts.map +0 -1
- package/dist/utils.js +0 -7
- package/dist/utils.js.map +0 -1
- package/src/utils.ts +0 -9
package/src/localNode.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Result,
|
|
1
|
+
import { Result, err, ok } from "neverthrow";
|
|
2
2
|
import { CoID } from "./coValue.js";
|
|
3
3
|
import { RawCoValue } from "./coValue.js";
|
|
4
4
|
import {
|
|
@@ -223,10 +223,19 @@ export class LocalNode {
|
|
|
223
223
|
account.set("profile", profile.id, "trusting");
|
|
224
224
|
}
|
|
225
225
|
|
|
226
|
-
|
|
226
|
+
const profileId = account.get("profile");
|
|
227
|
+
|
|
228
|
+
if (!profileId) {
|
|
227
229
|
throw new Error("Must set account profile in initial migration");
|
|
228
230
|
}
|
|
229
231
|
|
|
232
|
+
if (node.syncManager.hasStoragePeers()) {
|
|
233
|
+
await Promise.all([
|
|
234
|
+
node.syncManager.waitForStorageSync(account.id),
|
|
235
|
+
node.syncManager.waitForStorageSync(profileId),
|
|
236
|
+
]);
|
|
237
|
+
}
|
|
238
|
+
|
|
230
239
|
return {
|
|
231
240
|
node,
|
|
232
241
|
accountID: account.id,
|
|
@@ -272,11 +281,9 @@ export class LocalNode {
|
|
|
272
281
|
if (!profileID) {
|
|
273
282
|
throw new Error("Account has no profile");
|
|
274
283
|
}
|
|
275
|
-
const profile = await node.load(profileID);
|
|
276
284
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
}
|
|
285
|
+
// Preload the profile
|
|
286
|
+
await node.load(profileID);
|
|
280
287
|
|
|
281
288
|
if (migration) {
|
|
282
289
|
await migration(account, node);
|
|
@@ -426,32 +433,44 @@ export class LocalNode {
|
|
|
426
433
|
groupOrOwnedValueID: CoID<T>,
|
|
427
434
|
inviteSecret: InviteSecret,
|
|
428
435
|
): Promise<void> {
|
|
429
|
-
const
|
|
436
|
+
const value = await this.load(groupOrOwnedValueID);
|
|
430
437
|
|
|
431
|
-
if (
|
|
438
|
+
if (value === "unavailable") {
|
|
432
439
|
throw new Error(
|
|
433
440
|
"Trying to accept invite: Group/owned value unavailable from all peers",
|
|
434
441
|
);
|
|
435
442
|
}
|
|
436
443
|
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
444
|
+
const ruleset = value.core.verified.header.ruleset;
|
|
445
|
+
|
|
446
|
+
let group: RawGroup;
|
|
447
|
+
|
|
448
|
+
if (ruleset.type === "unsafeAllowAll") {
|
|
449
|
+
throw new Error("Can only accept invites to values owned by groups");
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
if (ruleset.type === "ownedByGroup") {
|
|
453
|
+
const owner = await this.load(ruleset.group as CoID<RawGroup>);
|
|
454
|
+
|
|
455
|
+
if (owner === "unavailable") {
|
|
456
|
+
throw new Error(
|
|
457
|
+
"Trying to accept invite: CoValue owner unavailable from all peers",
|
|
458
|
+
);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
group = expectGroup(owner);
|
|
462
|
+
} else {
|
|
463
|
+
group = expectGroup(value);
|
|
448
464
|
}
|
|
449
465
|
|
|
450
|
-
|
|
466
|
+
if (group.core.verified.header.meta?.type === "account") {
|
|
467
|
+
throw new Error("Can't accept invites to values owned by accounts");
|
|
468
|
+
}
|
|
451
469
|
|
|
452
470
|
const inviteAgentSecret = this.crypto.agentSecretFromSecretSeed(
|
|
453
471
|
secretSeedFromInviteSecret(inviteSecret),
|
|
454
472
|
);
|
|
473
|
+
|
|
455
474
|
const inviteAgentID = this.crypto.getAgentID(inviteAgentSecret);
|
|
456
475
|
|
|
457
476
|
const inviteRole = await new Promise((resolve, reject) => {
|
|
@@ -486,9 +505,10 @@ export class LocalNode {
|
|
|
486
505
|
}
|
|
487
506
|
|
|
488
507
|
const groupAsInvite = expectGroup(
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
508
|
+
this.loadCoValueAsDifferentAgent(
|
|
509
|
+
group.id,
|
|
510
|
+
inviteAgentSecret,
|
|
511
|
+
).getCurrentContent(),
|
|
492
512
|
);
|
|
493
513
|
|
|
494
514
|
groupAsInvite.addMemberInternal(
|
|
@@ -517,7 +537,7 @@ export class LocalNode {
|
|
|
517
537
|
|
|
518
538
|
if (!coValue.isAvailable()) {
|
|
519
539
|
throw new Error(
|
|
520
|
-
`${expectation ? expectation + ": " : ""}CoValue ${id} not yet loaded
|
|
540
|
+
`${expectation ? expectation + ": " : ""}CoValue ${id} not yet loaded.`,
|
|
521
541
|
);
|
|
522
542
|
}
|
|
523
543
|
return coValue;
|
|
@@ -614,46 +634,56 @@ export class LocalNode {
|
|
|
614
634
|
return group;
|
|
615
635
|
}
|
|
616
636
|
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
637
|
+
loadCoValueAsDifferentAgent(
|
|
638
|
+
id: RawCoID,
|
|
639
|
+
secret: AgentSecret,
|
|
640
|
+
accountId?: RawAccountID | AgentID,
|
|
641
|
+
) {
|
|
642
|
+
const agent = new ControlledAgent(secret, this.crypto);
|
|
643
|
+
|
|
621
644
|
const newNode = new LocalNode(
|
|
622
|
-
|
|
623
|
-
this.crypto.newRandomSessionID(
|
|
645
|
+
secret,
|
|
646
|
+
this.crypto.newRandomSessionID(accountId || agent.id),
|
|
624
647
|
this.crypto,
|
|
625
648
|
);
|
|
626
649
|
|
|
627
|
-
newNode.cloneVerifiedStateFrom(this);
|
|
650
|
+
newNode.cloneVerifiedStateFrom(this, id);
|
|
628
651
|
|
|
629
|
-
return newNode;
|
|
652
|
+
return newNode.expectCoValueLoaded(id);
|
|
630
653
|
}
|
|
631
654
|
|
|
632
655
|
/** @internal */
|
|
633
|
-
cloneVerifiedStateFrom(otherNode: LocalNode) {
|
|
634
|
-
const
|
|
656
|
+
cloneVerifiedStateFrom(otherNode: LocalNode, id: RawCoID) {
|
|
657
|
+
const coValuesIdsToCopy = [id];
|
|
635
658
|
|
|
636
|
-
|
|
637
|
-
|
|
659
|
+
// Scan all the dependencies and add them to the list
|
|
660
|
+
for (let i = 0; i < coValuesIdsToCopy.length; i++) {
|
|
661
|
+
const coValueID = coValuesIdsToCopy[i]!;
|
|
662
|
+
const coValue = otherNode.getCoValue(coValueID);
|
|
638
663
|
|
|
639
664
|
if (!coValue.isAvailable()) {
|
|
640
|
-
coValuesToCopy.pop();
|
|
641
665
|
continue;
|
|
642
|
-
}
|
|
643
|
-
const allDepsCopied = coValue
|
|
644
|
-
.getDependedOnCoValues()
|
|
645
|
-
.every((dep) => this.coValues.get(dep)?.isAvailable());
|
|
646
|
-
|
|
647
|
-
if (!allDepsCopied) {
|
|
648
|
-
// move to end of queue
|
|
649
|
-
coValuesToCopy.unshift(coValuesToCopy.pop()!);
|
|
650
|
-
continue;
|
|
651
|
-
}
|
|
666
|
+
}
|
|
652
667
|
|
|
653
|
-
|
|
668
|
+
for (const dep of coValue.getDependedOnCoValues()) {
|
|
669
|
+
coValuesIdsToCopy.push(dep);
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
// Copy the coValue all the dependencies by following the dependency order
|
|
674
|
+
while (coValuesIdsToCopy.length > 0) {
|
|
675
|
+
const coValueID = coValuesIdsToCopy.pop()!;
|
|
676
|
+
const coValue = otherNode.getCoValue(coValueID);
|
|
677
|
+
|
|
678
|
+
if (!coValue.isAvailable()) {
|
|
679
|
+
continue;
|
|
680
|
+
}
|
|
654
681
|
|
|
655
|
-
|
|
682
|
+
if (this.coValues.get(coValueID)?.isAvailable()) {
|
|
683
|
+
continue;
|
|
656
684
|
}
|
|
685
|
+
|
|
686
|
+
this.putCoValue(coValueID, coValue.verified);
|
|
657
687
|
}
|
|
658
688
|
}
|
|
659
689
|
|
package/src/streamUtils.ts
CHANGED
|
@@ -6,23 +6,17 @@ export function connectedPeers(
|
|
|
6
6
|
peer1id: PeerID,
|
|
7
7
|
peer2id: PeerID,
|
|
8
8
|
{
|
|
9
|
-
trace = false,
|
|
10
9
|
peer1role = "client",
|
|
11
10
|
peer2role = "client",
|
|
12
11
|
crashOnClose = false,
|
|
13
12
|
}: {
|
|
14
|
-
trace?: boolean;
|
|
15
13
|
peer1role?: Peer["role"];
|
|
16
14
|
peer2role?: Peer["role"];
|
|
17
15
|
crashOnClose?: boolean;
|
|
18
16
|
} = {},
|
|
19
17
|
): [Peer, Peer] {
|
|
20
|
-
const [from1to2Rx, from1to2Tx] = newQueuePair(
|
|
21
|
-
|
|
22
|
-
);
|
|
23
|
-
const [from2to1Rx, from2to1Tx] = newQueuePair(
|
|
24
|
-
trace ? { traceAs: `${peer2id} -> ${peer1id}` } : undefined,
|
|
25
|
-
);
|
|
18
|
+
const [from1to2Rx, from1to2Tx] = newQueuePair();
|
|
19
|
+
const [from2to1Rx, from2to1Tx] = newQueuePair();
|
|
26
20
|
|
|
27
21
|
const peer2AsPeer: Peer = {
|
|
28
22
|
id: peer2id,
|
|
@@ -43,32 +37,11 @@ export function connectedPeers(
|
|
|
43
37
|
return [peer1AsPeer, peer2AsPeer];
|
|
44
38
|
}
|
|
45
39
|
|
|
46
|
-
export function newQueuePair(
|
|
47
|
-
|
|
48
|
-
|
|
40
|
+
export function newQueuePair(): [
|
|
41
|
+
AsyncIterable<SyncMessage>,
|
|
42
|
+
Channel<SyncMessage>,
|
|
43
|
+
] {
|
|
49
44
|
const channel = new Channel<SyncMessage>();
|
|
50
45
|
|
|
51
|
-
|
|
52
|
-
return [
|
|
53
|
-
(async function* () {
|
|
54
|
-
for await (const msg of channel) {
|
|
55
|
-
console.debug(
|
|
56
|
-
options.traceAs,
|
|
57
|
-
JSON.stringify(
|
|
58
|
-
msg,
|
|
59
|
-
(k, v) =>
|
|
60
|
-
k === "changes" || k === "encryptedChanges"
|
|
61
|
-
? v.slice(0, 20) + "..."
|
|
62
|
-
: v,
|
|
63
|
-
2,
|
|
64
|
-
),
|
|
65
|
-
);
|
|
66
|
-
yield msg;
|
|
67
|
-
}
|
|
68
|
-
})(),
|
|
69
|
-
channel,
|
|
70
|
-
];
|
|
71
|
-
} else {
|
|
72
|
-
return [channel.wrap(), channel];
|
|
73
|
-
}
|
|
46
|
+
return [channel.wrap(), channel];
|
|
74
47
|
}
|
package/src/sync.ts
CHANGED
|
@@ -159,6 +159,12 @@ export class SyncManager {
|
|
|
159
159
|
);
|
|
160
160
|
}
|
|
161
161
|
|
|
162
|
+
hasStoragePeers(): boolean {
|
|
163
|
+
return this.getPeers().some(
|
|
164
|
+
(peer) => peer.role === "storage" && !peer.closed,
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
|
|
162
168
|
handleSyncMessage(msg: SyncMessage, peer: PeerState) {
|
|
163
169
|
if (this.local.getCoValue(msg.id).isErroredInPeer(peer.id)) {
|
|
164
170
|
logger.warn(
|
|
@@ -393,10 +399,12 @@ export class SyncManager {
|
|
|
393
399
|
|
|
394
400
|
return;
|
|
395
401
|
} else {
|
|
396
|
-
//
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
402
|
+
// Syncronously updates the state loading is possible
|
|
403
|
+
coValue
|
|
404
|
+
.loadFromPeers(this.getServerAndStoragePeers(peer.id))
|
|
405
|
+
.catch((e) => {
|
|
406
|
+
logger.error("Error loading coValue in handleLoad", { err: e });
|
|
407
|
+
});
|
|
400
408
|
}
|
|
401
409
|
}
|
|
402
410
|
|
|
@@ -622,28 +630,24 @@ export class SyncManager {
|
|
|
622
630
|
|
|
623
631
|
handleUnsubscribe(_msg: DoneMessage) {}
|
|
624
632
|
|
|
625
|
-
requestedSyncs = new
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
633
|
+
requestedSyncs = new Set<RawCoID>();
|
|
634
|
+
requestCoValueSync(coValue: CoValueCore) {
|
|
635
|
+
if (this.requestedSyncs.has(coValue.id)) {
|
|
636
|
+
return;
|
|
637
|
+
}
|
|
629
638
|
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
this.requestedSyncs.delete(coValue.id);
|
|
636
|
-
this.syncCoValue(coValue);
|
|
637
|
-
resolve();
|
|
638
|
-
});
|
|
639
|
-
});
|
|
639
|
+
queueMicrotask(() => {
|
|
640
|
+
if (this.requestedSyncs.has(coValue.id)) {
|
|
641
|
+
this.syncCoValue(coValue);
|
|
642
|
+
}
|
|
643
|
+
});
|
|
640
644
|
|
|
641
|
-
|
|
642
|
-
return promise;
|
|
643
|
-
}
|
|
645
|
+
this.requestedSyncs.add(coValue.id);
|
|
644
646
|
}
|
|
645
647
|
|
|
646
648
|
async syncCoValue(coValue: CoValueCore) {
|
|
649
|
+
this.requestedSyncs.delete(coValue.id);
|
|
650
|
+
|
|
647
651
|
for (const peer of this.peersInPriorityOrder()) {
|
|
648
652
|
if (peer.closed) continue;
|
|
649
653
|
if (coValue.isErroredInPeer(peer.id)) continue;
|
|
@@ -674,6 +678,21 @@ export class SyncManager {
|
|
|
674
678
|
return true;
|
|
675
679
|
}
|
|
676
680
|
|
|
681
|
+
const peerState = this.peers[peerId];
|
|
682
|
+
|
|
683
|
+
// The peer has been closed, so it isn't possible to sync
|
|
684
|
+
if (!peerState || peerState.closed) {
|
|
685
|
+
return true;
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
// The client isn't subscribed to the coValue, so we won't sync it
|
|
689
|
+
if (
|
|
690
|
+
peerState.role === "client" &&
|
|
691
|
+
!peerState.optimisticKnownStates.has(id)
|
|
692
|
+
) {
|
|
693
|
+
return true;
|
|
694
|
+
}
|
|
695
|
+
|
|
677
696
|
return new Promise((resolve, reject) => {
|
|
678
697
|
const unsubscribe = this.syncState.subscribeToPeerUpdates(
|
|
679
698
|
peerId,
|
|
@@ -693,10 +712,20 @@ export class SyncManager {
|
|
|
693
712
|
});
|
|
694
713
|
}
|
|
695
714
|
|
|
715
|
+
async waitForStorageSync(id: RawCoID, timeout = 30_000) {
|
|
716
|
+
const peers = this.getPeers();
|
|
717
|
+
|
|
718
|
+
await Promise.all(
|
|
719
|
+
peers
|
|
720
|
+
.filter((peer) => peer.role === "storage")
|
|
721
|
+
.map((peer) => this.waitForSyncWithPeer(peer.id, id, timeout)),
|
|
722
|
+
);
|
|
723
|
+
}
|
|
724
|
+
|
|
696
725
|
async waitForSync(id: RawCoID, timeout = 30_000) {
|
|
697
726
|
const peers = this.getPeers();
|
|
698
727
|
|
|
699
|
-
|
|
728
|
+
await Promise.all(
|
|
700
729
|
peers.map((peer) => this.waitForSyncWithPeer(peer.id, id, timeout)),
|
|
701
730
|
);
|
|
702
731
|
}
|
|
@@ -174,9 +174,6 @@ describe("PeerState", () => {
|
|
|
174
174
|
test("should dispatch to both states", () => {
|
|
175
175
|
const { peerState } = setup();
|
|
176
176
|
const knownStatesSpy = vi.spyOn(peerState._knownStates, "set");
|
|
177
|
-
if (peerState._optimisticKnownStates === "assumeInfallible") {
|
|
178
|
-
throw new Error("Expected normal optimisticKnownStates");
|
|
179
|
-
}
|
|
180
177
|
|
|
181
178
|
const optimisticKnownStatesSpy = vi.spyOn(
|
|
182
179
|
peerState._optimisticKnownStates,
|
|
@@ -195,40 +192,6 @@ describe("PeerState", () => {
|
|
|
195
192
|
expect(optimisticKnownStatesSpy).toHaveBeenCalledWith("co_z1", state);
|
|
196
193
|
});
|
|
197
194
|
|
|
198
|
-
test("should use same reference for knownStates and optimisticKnownStates for storage peers", () => {
|
|
199
|
-
const mockStoragePeer: Peer = {
|
|
200
|
-
id: "test-storage-peer",
|
|
201
|
-
role: "storage",
|
|
202
|
-
priority: 1,
|
|
203
|
-
crashOnClose: false,
|
|
204
|
-
incoming: (async function* () {})(),
|
|
205
|
-
outgoing: {
|
|
206
|
-
push: vi.fn().mockResolvedValue(undefined),
|
|
207
|
-
close: vi.fn(),
|
|
208
|
-
},
|
|
209
|
-
};
|
|
210
|
-
const peerState = new PeerState(mockStoragePeer, undefined);
|
|
211
|
-
|
|
212
|
-
// Verify they are the same reference
|
|
213
|
-
expect(peerState.knownStates).toBe(peerState.optimisticKnownStates);
|
|
214
|
-
|
|
215
|
-
// Verify that dispatching only updates one state
|
|
216
|
-
const knownStatesSpy = vi.spyOn(peerState._knownStates, "set");
|
|
217
|
-
expect(peerState._optimisticKnownStates).toBe("assumeInfallible");
|
|
218
|
-
|
|
219
|
-
const state: CoValueKnownState = {
|
|
220
|
-
id: "co_z1",
|
|
221
|
-
header: false,
|
|
222
|
-
sessions: {},
|
|
223
|
-
};
|
|
224
|
-
|
|
225
|
-
peerState.setKnownState("co_z1", state);
|
|
226
|
-
|
|
227
|
-
// Only one dispatch should happen since they're the same reference
|
|
228
|
-
expect(knownStatesSpy).toHaveBeenCalledTimes(1);
|
|
229
|
-
expect(knownStatesSpy).toHaveBeenCalledWith("co_z1", state);
|
|
230
|
-
});
|
|
231
|
-
|
|
232
195
|
test("should use separate references for knownStates and optimisticKnownStates for non-storage peers", () => {
|
|
233
196
|
const { peerState } = setup(); // Uses a regular peer
|
|
234
197
|
|
|
@@ -70,12 +70,12 @@ describe("SyncStateManager", () => {
|
|
|
70
70
|
const map = group.createMap();
|
|
71
71
|
map.set("key1", "value1", "trusting");
|
|
72
72
|
|
|
73
|
-
const [
|
|
74
|
-
peer1role: "
|
|
75
|
-
peer2role: "
|
|
73
|
+
const [serverPeer] = connectedPeers("serverPeer", "unusedPeer", {
|
|
74
|
+
peer1role: "server",
|
|
75
|
+
peer2role: "client",
|
|
76
76
|
});
|
|
77
77
|
|
|
78
|
-
client.node.syncManager.addPeer(
|
|
78
|
+
client.node.syncManager.addPeer(serverPeer);
|
|
79
79
|
|
|
80
80
|
const subscriptionManager = client.node.syncManager.syncState;
|
|
81
81
|
|
|
@@ -86,7 +86,7 @@ describe("SyncStateManager", () => {
|
|
|
86
86
|
updateToJazzCloudSpy,
|
|
87
87
|
);
|
|
88
88
|
const unsubscribe2 = subscriptionManager.subscribeToPeerUpdates(
|
|
89
|
-
|
|
89
|
+
serverPeer.id,
|
|
90
90
|
updateToStorageSpy,
|
|
91
91
|
);
|
|
92
92
|
|
|
@@ -115,7 +115,7 @@ describe("SyncStateManager", () => {
|
|
|
115
115
|
);
|
|
116
116
|
|
|
117
117
|
expect(updateToStorageSpy).toHaveBeenLastCalledWith(
|
|
118
|
-
emptyKnownState(
|
|
118
|
+
emptyKnownState(group.core.id),
|
|
119
119
|
{ uploaded: false },
|
|
120
120
|
);
|
|
121
121
|
});
|
|
@@ -247,4 +247,54 @@ describe("SyncStateManager", () => {
|
|
|
247
247
|
),
|
|
248
248
|
).toEqual({ uploaded: true });
|
|
249
249
|
});
|
|
250
|
+
|
|
251
|
+
test("should skip closed peers", async () => {
|
|
252
|
+
const client = setupTestNode();
|
|
253
|
+
const { peerState } = client.connectToSyncServer();
|
|
254
|
+
|
|
255
|
+
peerState.gracefulShutdown();
|
|
256
|
+
|
|
257
|
+
const group = client.node.createGroup();
|
|
258
|
+
const map = group.createMap();
|
|
259
|
+
|
|
260
|
+
await expect(map.core.waitForSync()).resolves.toBeUndefined();
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
test("should skip client peers that are not subscribed to the coValue", async () => {
|
|
264
|
+
const server = setupTestNode({ isSyncServer: true });
|
|
265
|
+
const client = setupTestNode();
|
|
266
|
+
|
|
267
|
+
client.connectToSyncServer({
|
|
268
|
+
syncServer: server.node,
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
const group = server.node.createGroup();
|
|
272
|
+
const map = group.createMap();
|
|
273
|
+
|
|
274
|
+
await map.core.waitForSync();
|
|
275
|
+
|
|
276
|
+
expect(client.node.getCoValue(map.id).isAvailable()).toBe(false);
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
test("should wait for client peers that are subscribed to the coValue", async () => {
|
|
280
|
+
const server = setupTestNode({ isSyncServer: true });
|
|
281
|
+
const client = setupTestNode();
|
|
282
|
+
|
|
283
|
+
const { peerStateOnServer } = client.connectToSyncServer();
|
|
284
|
+
|
|
285
|
+
const group = server.node.createGroup();
|
|
286
|
+
const map = group.createMap();
|
|
287
|
+
map.set("key1", "value1", "trusting");
|
|
288
|
+
|
|
289
|
+
// Simulate the subscription to the coValue
|
|
290
|
+
peerStateOnServer.setKnownState(map.core.id, {
|
|
291
|
+
id: map.core.id,
|
|
292
|
+
header: true,
|
|
293
|
+
sessions: {},
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
await map.core.waitForSync();
|
|
297
|
+
|
|
298
|
+
expect(client.node.getCoValue(map.id).isAvailable()).toBe(true);
|
|
299
|
+
});
|
|
250
300
|
});
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { expect, test } from "vitest";
|
|
2
|
+
import { expectAccount } from "../coValues/account.js";
|
|
2
3
|
import { WasmCrypto } from "../crypto/WasmCrypto.js";
|
|
3
4
|
import { LocalNode } from "../localNode.js";
|
|
4
5
|
import { connectedPeers } from "../streamUtils.js";
|
|
6
|
+
import { createMockStoragePeer } from "./testUtils.js";
|
|
5
7
|
|
|
6
8
|
const Crypto = await WasmCrypto.create();
|
|
7
9
|
|
|
@@ -85,3 +87,25 @@ test("throws an error if the user tried to create an invite from an account", as
|
|
|
85
87
|
"Cannot create invite from an account",
|
|
86
88
|
);
|
|
87
89
|
});
|
|
90
|
+
|
|
91
|
+
test("wait for storage sync before resolving withNewlyCreatedAccount", async () => {
|
|
92
|
+
const { storage, peer } = createMockStoragePeer({
|
|
93
|
+
peerId: "account-node",
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
const { node, accountID } = await LocalNode.withNewlyCreatedAccount({
|
|
97
|
+
creationProps: { name: "Hermes Puggington" },
|
|
98
|
+
crypto: Crypto,
|
|
99
|
+
peersToLoadFrom: [peer],
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
const account = storage.getCoValue(accountID);
|
|
103
|
+
|
|
104
|
+
expect(account.isAvailable()).toBe(true);
|
|
105
|
+
|
|
106
|
+
const profile = storage.getCoValue(
|
|
107
|
+
expectAccount(account.getCurrentContent()).get("profile")!,
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
expect(profile.isAvailable()).toBe(true);
|
|
111
|
+
});
|
package/src/tests/group.test.ts
CHANGED
|
@@ -750,7 +750,6 @@ describe("extend with role mapping", () => {
|
|
|
750
750
|
const mapOnNode2 = await loadCoValueOrFail(node2.node, map.id);
|
|
751
751
|
|
|
752
752
|
expect(mapOnNode2.get("test")).toEqual("Written from the admin");
|
|
753
|
-
|
|
754
753
|
mapOnNode2.set("test", "Written from the inherited role");
|
|
755
754
|
expect(mapOnNode2.get("test")).toEqual("Written from the inherited role");
|
|
756
755
|
|
|
@@ -512,7 +512,6 @@ test("Admins can set group read key and then use it to create private transactio
|
|
|
512
512
|
const { node, groupCore, admin } = newGroup();
|
|
513
513
|
|
|
514
514
|
const reader1 = createAccountInNode(node);
|
|
515
|
-
|
|
516
515
|
const reader2 = createAccountInNode(node);
|
|
517
516
|
|
|
518
517
|
const { secret: readKey, id: readKeyID } = Crypto.newRandomKeySecret();
|
|
@@ -583,6 +582,9 @@ test("Admins can set group read key and then use it to create private transactio
|
|
|
583
582
|
childObject.contentInClonedNodeWithDifferentAccount(reader2),
|
|
584
583
|
);
|
|
585
584
|
|
|
585
|
+
// Need to copy the account coValue to the new node to be able to read the readKey
|
|
586
|
+
childObjectAsReader2.core.node.cloneVerifiedStateFrom(node, reader2.id);
|
|
587
|
+
|
|
586
588
|
expect(childObjectAsReader2.core.getCurrentReadKey().secret).toEqual(readKey);
|
|
587
589
|
expect(childObjectAsReader2.get("foo")).toEqual("bar");
|
|
588
590
|
});
|
|
@@ -1113,9 +1115,10 @@ test("Admins can create an adminInvite, which can add an admin (high-level)", as
|
|
|
1113
1115
|
const invitedAdminSecret = Crypto.newRandomAgentSecret();
|
|
1114
1116
|
const invitedAdminID = Crypto.getAgentID(invitedAdminSecret);
|
|
1115
1117
|
|
|
1116
|
-
const nodeAsInvitedAdmin = node.
|
|
1117
|
-
|
|
1118
|
-
|
|
1118
|
+
const nodeAsInvitedAdmin = node.loadCoValueAsDifferentAgent(
|
|
1119
|
+
group.id,
|
|
1120
|
+
invitedAdminSecret,
|
|
1121
|
+
).node;
|
|
1119
1122
|
|
|
1120
1123
|
await nodeAsInvitedAdmin.acceptInvite(group.id, inviteSecret);
|
|
1121
1124
|
|
|
@@ -1219,9 +1222,10 @@ test("Admins can create a writerInvite, which can add a writer (high-level)", as
|
|
|
1219
1222
|
const invitedWriterSecret = Crypto.newRandomAgentSecret();
|
|
1220
1223
|
const invitedWriterID = Crypto.getAgentID(invitedWriterSecret);
|
|
1221
1224
|
|
|
1222
|
-
const nodeAsInvitedWriter = node.
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
+
const nodeAsInvitedWriter = node.loadCoValueAsDifferentAgent(
|
|
1226
|
+
group.id,
|
|
1227
|
+
invitedWriterSecret,
|
|
1228
|
+
).node;
|
|
1225
1229
|
|
|
1226
1230
|
await nodeAsInvitedWriter.acceptInvite(group.id, inviteSecret);
|
|
1227
1231
|
|
|
@@ -1308,9 +1312,10 @@ test("Admins can create a readerInvite, which can add a reader (high-level)", as
|
|
|
1308
1312
|
const invitedReaderSecret = Crypto.newRandomAgentSecret();
|
|
1309
1313
|
const invitedReaderID = Crypto.getAgentID(invitedReaderSecret);
|
|
1310
1314
|
|
|
1311
|
-
const nodeAsInvitedReader = node.
|
|
1312
|
-
|
|
1313
|
-
|
|
1315
|
+
const nodeAsInvitedReader = node.loadCoValueAsDifferentAgent(
|
|
1316
|
+
group.id,
|
|
1317
|
+
invitedReaderSecret,
|
|
1318
|
+
).node;
|
|
1314
1319
|
|
|
1315
1320
|
await nodeAsInvitedReader.acceptInvite(group.id, inviteSecret);
|
|
1316
1321
|
|