cojson 0.9.18 → 0.9.23
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 +3 -3
- package/CHANGELOG.md +12 -0
- package/dist/native/coValues/group.js +4 -4
- package/dist/native/coValues/group.js.map +1 -1
- package/dist/native/exports.js +1 -1
- package/dist/native/exports.js.map +1 -1
- package/dist/web/coValues/group.js +4 -4
- package/dist/web/coValues/group.js.map +1 -1
- package/dist/web/exports.js +1 -1
- package/dist/web/exports.js.map +1 -1
- package/package.json +1 -1
- package/src/coValues/group.ts +7 -4
- package/src/exports.ts +8 -1
- package/src/tests/SyncStateManager.test.ts +41 -91
- package/src/tests/permissions.test.ts +59 -1
- package/src/tests/sync.test.ts +257 -325
- package/src/tests/testUtils.ts +112 -19
package/src/tests/sync.test.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { afterEach, describe, expect, test, vi } from "vitest";
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
|
|
2
2
|
import { expectMap } from "../coValue.js";
|
|
3
3
|
import type { CoValueHeader } from "../coValueCore.js";
|
|
4
4
|
import type { RawAccountID } from "../coValues/account.js";
|
|
@@ -12,17 +12,27 @@ import { connectedPeers, newQueuePair } from "../streamUtils.js";
|
|
|
12
12
|
import type { SyncMessage } from "../sync.js";
|
|
13
13
|
import {
|
|
14
14
|
blockMessageTypeOnOutgoingPeer,
|
|
15
|
+
connectNodeToSyncServer,
|
|
15
16
|
connectTwoPeers,
|
|
17
|
+
createConnectedTestAgentNode,
|
|
18
|
+
createConnectedTestNode,
|
|
16
19
|
createTestMetricReader,
|
|
17
20
|
createTestNode,
|
|
18
21
|
loadCoValueOrFail,
|
|
19
22
|
randomAnonymousAccountAndSessionID,
|
|
23
|
+
setupSyncServer,
|
|
20
24
|
tearDownTestMetricReader,
|
|
21
25
|
waitFor,
|
|
22
26
|
} from "./testUtils.js";
|
|
23
27
|
|
|
24
28
|
const Crypto = await WasmCrypto.create();
|
|
25
29
|
|
|
30
|
+
let jazzCloud = setupSyncServer();
|
|
31
|
+
|
|
32
|
+
beforeEach(async () => {
|
|
33
|
+
jazzCloud = setupSyncServer();
|
|
34
|
+
});
|
|
35
|
+
|
|
26
36
|
test("Node replies with initial tx and header to empty subscribe", async () => {
|
|
27
37
|
const [admin, session] = randomAnonymousAccountAndSessionID();
|
|
28
38
|
const node = new LocalNode(admin, session, Crypto);
|
|
@@ -861,111 +871,34 @@ test.skip("When loading a coValue on one node, the server node it is requested f
|
|
|
861
871
|
});
|
|
862
872
|
|
|
863
873
|
test("Can sync a coValue through a server to another client", async () => {
|
|
864
|
-
const
|
|
865
|
-
|
|
866
|
-
const client1 = new LocalNode(admin, session, Crypto);
|
|
874
|
+
const { node: client1 } = await createConnectedTestNode();
|
|
867
875
|
|
|
868
876
|
const group = client1.createGroup();
|
|
869
877
|
|
|
870
878
|
const map = group.createMap();
|
|
871
879
|
map.set("hello", "world", "trusting");
|
|
872
880
|
|
|
873
|
-
const
|
|
874
|
-
|
|
875
|
-
const server = new LocalNode(serverUser, serverSession, Crypto);
|
|
876
|
-
|
|
877
|
-
const [serverAsPeerForClient1, client1AsPeer] = connectedPeers(
|
|
878
|
-
"serverFor1",
|
|
879
|
-
"client1",
|
|
880
|
-
{
|
|
881
|
-
peer1role: "server",
|
|
882
|
-
peer2role: "client",
|
|
883
|
-
// trace: true,
|
|
884
|
-
},
|
|
885
|
-
);
|
|
886
|
-
|
|
887
|
-
client1.syncManager.addPeer(serverAsPeerForClient1);
|
|
888
|
-
server.syncManager.addPeer(client1AsPeer);
|
|
889
|
-
|
|
890
|
-
const client2 = new LocalNode(
|
|
891
|
-
admin,
|
|
892
|
-
Crypto.newRandomSessionID(admin.id),
|
|
893
|
-
Crypto,
|
|
894
|
-
);
|
|
895
|
-
|
|
896
|
-
const [serverAsPeerForClient2, client2AsPeer] = connectedPeers(
|
|
897
|
-
"serverFor2",
|
|
898
|
-
"client2",
|
|
899
|
-
{
|
|
900
|
-
peer1role: "server",
|
|
901
|
-
peer2role: "client",
|
|
902
|
-
// trace: true,
|
|
903
|
-
},
|
|
904
|
-
);
|
|
905
|
-
|
|
906
|
-
client2.syncManager.addPeer(serverAsPeerForClient2);
|
|
907
|
-
server.syncManager.addPeer(client2AsPeer);
|
|
881
|
+
const { node: client2 } = await createConnectedTestNode();
|
|
908
882
|
|
|
909
|
-
const mapOnClient2 = await client2
|
|
910
|
-
if (mapOnClient2 === "unavailable") {
|
|
911
|
-
throw new Error("Map is unavailable");
|
|
912
|
-
}
|
|
883
|
+
const mapOnClient2 = await loadCoValueOrFail(client2, map.id);
|
|
913
884
|
|
|
914
|
-
expect(
|
|
915
|
-
"world",
|
|
916
|
-
);
|
|
885
|
+
expect(mapOnClient2.get("hello")).toEqual("world");
|
|
917
886
|
});
|
|
918
887
|
|
|
919
888
|
test("Can sync a coValue with private transactions through a server to another client", async () => {
|
|
920
|
-
const
|
|
921
|
-
|
|
922
|
-
const client1 = new LocalNode(admin, session, Crypto);
|
|
889
|
+
const { node: client1 } = await createConnectedTestNode();
|
|
923
890
|
|
|
924
891
|
const group = client1.createGroup();
|
|
925
892
|
|
|
926
893
|
const map = group.createMap();
|
|
927
894
|
map.set("hello", "world", "private");
|
|
895
|
+
group.addMember("everyone", "reader");
|
|
928
896
|
|
|
929
|
-
const
|
|
930
|
-
|
|
931
|
-
const server = new LocalNode(serverUser, serverSession, Crypto);
|
|
932
|
-
|
|
933
|
-
const [serverAsPeer, client1AsPeer] = connectedPeers("server", "client1", {
|
|
934
|
-
// trace: true,
|
|
935
|
-
peer1role: "server",
|
|
936
|
-
peer2role: "client",
|
|
937
|
-
});
|
|
938
|
-
|
|
939
|
-
client1.syncManager.addPeer(serverAsPeer);
|
|
940
|
-
server.syncManager.addPeer(client1AsPeer);
|
|
897
|
+
const { node: client2 } = await createConnectedTestNode();
|
|
941
898
|
|
|
942
|
-
const
|
|
943
|
-
admin,
|
|
944
|
-
client1.crypto.newRandomSessionID(admin.id),
|
|
945
|
-
Crypto,
|
|
946
|
-
);
|
|
947
|
-
|
|
948
|
-
const [serverAsOtherPeer, client2AsPeer] = connectedPeers(
|
|
949
|
-
"server",
|
|
950
|
-
"client2",
|
|
951
|
-
{
|
|
952
|
-
// trace: true,
|
|
953
|
-
peer1role: "server",
|
|
954
|
-
peer2role: "client",
|
|
955
|
-
},
|
|
956
|
-
);
|
|
899
|
+
const mapOnClient2 = await loadCoValueOrFail(client2, map.id);
|
|
957
900
|
|
|
958
|
-
|
|
959
|
-
server.syncManager.addPeer(client2AsPeer);
|
|
960
|
-
|
|
961
|
-
const mapOnClient2 = await client2.loadCoValueCore(map.core.id);
|
|
962
|
-
if (mapOnClient2 === "unavailable") {
|
|
963
|
-
throw new Error("Map is unavailable");
|
|
964
|
-
}
|
|
965
|
-
|
|
966
|
-
expect(expectMap(mapOnClient2.getCurrentContent()).get("hello")).toEqual(
|
|
967
|
-
"world",
|
|
968
|
-
);
|
|
901
|
+
expect(mapOnClient2.get("hello")).toEqual("world");
|
|
969
902
|
});
|
|
970
903
|
|
|
971
904
|
test.skip("When a peer's incoming/readable stream closes, we remove the peer", async () => {
|
|
@@ -1023,111 +956,32 @@ test.skip("When a peer's incoming/readable stream closes, we remove the peer", a
|
|
|
1023
956
|
*/
|
|
1024
957
|
});
|
|
1025
958
|
|
|
1026
|
-
test.skip("When a peer's outgoing/writable stream closes, we remove the peer", async () => {
|
|
1027
|
-
/*
|
|
1028
|
-
const [admin, session] = randomAnonymousAccountAndSessionID();
|
|
1029
|
-
const node = new LocalNode(admin, session, Crypto);
|
|
1030
|
-
|
|
1031
|
-
const group = node.createGroup();
|
|
1032
|
-
|
|
1033
|
-
const [inRx] = await Effect.runPromise(newStreamPair());
|
|
1034
|
-
const [outRx, outTx] = await Effect.runPromise(newStreamPair());
|
|
1035
|
-
|
|
1036
|
-
node.syncManager.addPeer({
|
|
1037
|
-
id: "test",
|
|
1038
|
-
incoming: inRx,
|
|
1039
|
-
outgoing: outTx,
|
|
1040
|
-
role: "server",
|
|
1041
|
-
});
|
|
1042
|
-
|
|
1043
|
-
// expect(yield* Queue.take(outRxQ)).toMatchObject({
|
|
1044
|
-
// action: "load",
|
|
1045
|
-
// id: admin.id,
|
|
1046
|
-
// });
|
|
1047
|
-
expect(yield * Queue.take(outRxQ)).toMatchObject({
|
|
1048
|
-
action: "load",
|
|
1049
|
-
id: group.core.id,
|
|
1050
|
-
});
|
|
1051
|
-
|
|
1052
|
-
const map = group.createMap();
|
|
1053
|
-
|
|
1054
|
-
const mapSubscribeMsg = await reader.read();
|
|
1055
|
-
|
|
1056
|
-
expect(mapSubscribeMsg.value).toEqual({
|
|
1057
|
-
action: "load",
|
|
1058
|
-
...map.core.knownState(),
|
|
1059
|
-
} satisfies SyncMessage);
|
|
1060
|
-
|
|
1061
|
-
// expect(yield* Queue.take(outRxQ)).toMatchObject(admContEx(admin.id));
|
|
1062
|
-
expect(yield * Queue.take(outRxQ)).toMatchObject(groupContentEx(group));
|
|
1063
|
-
|
|
1064
|
-
const mapContentMsg = await reader.read();
|
|
1065
|
-
|
|
1066
|
-
expect(mapContentMsg.value).toEqual({
|
|
1067
|
-
action: "content",
|
|
1068
|
-
id: map.core.id,
|
|
1069
|
-
header: map.core.header,
|
|
1070
|
-
new: {},
|
|
1071
|
-
} satisfies SyncMessage);
|
|
1072
|
-
|
|
1073
|
-
reader.releaseLock();
|
|
1074
|
-
await outRx.cancel();
|
|
1075
|
-
|
|
1076
|
-
map.set("hello", "world", "trusting");
|
|
1077
|
-
|
|
1078
|
-
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
1079
|
-
|
|
1080
|
-
expect(node.syncManager.peers["test"]).toBeUndefined();
|
|
1081
|
-
*/
|
|
1082
|
-
});
|
|
1083
|
-
|
|
1084
959
|
test("If we start loading a coValue before connecting to a peer that has it, it will load it once we connect", async () => {
|
|
1085
|
-
const
|
|
1086
|
-
|
|
1087
|
-
const node1 = new LocalNode(admin, session, Crypto);
|
|
960
|
+
const { node: node1 } = await createConnectedTestNode();
|
|
1088
961
|
|
|
1089
962
|
const group = node1.createGroup();
|
|
1090
963
|
|
|
1091
964
|
const map = group.createMap();
|
|
1092
965
|
map.set("hello", "world", "trusting");
|
|
1093
966
|
|
|
1094
|
-
const node2 =
|
|
1095
|
-
admin,
|
|
1096
|
-
Crypto.newRandomSessionID(admin.id),
|
|
1097
|
-
Crypto,
|
|
1098
|
-
);
|
|
1099
|
-
|
|
1100
|
-
const [node1asPeer, node2asPeer] = connectedPeers("peer1", "peer2", {
|
|
1101
|
-
peer1role: "server",
|
|
1102
|
-
peer2role: "client",
|
|
1103
|
-
// trace: true,
|
|
1104
|
-
});
|
|
967
|
+
const node2 = createTestNode();
|
|
1105
968
|
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
const mapOnNode2Promise = node2.loadCoValueCore(map.core.id);
|
|
969
|
+
const mapOnNode2Promise = loadCoValueOrFail(node2, map.id);
|
|
1109
970
|
|
|
1110
971
|
expect(node2.coValuesStore.get(map.core.id).state.type).toEqual("unknown");
|
|
1111
972
|
|
|
1112
|
-
node2
|
|
973
|
+
connectNodeToSyncServer(node2);
|
|
1113
974
|
|
|
1114
975
|
const mapOnNode2 = await mapOnNode2Promise;
|
|
1115
|
-
if (mapOnNode2 === "unavailable") {
|
|
1116
|
-
throw new Error("Map is unavailable");
|
|
1117
|
-
}
|
|
1118
976
|
|
|
1119
|
-
expect(
|
|
1120
|
-
"world",
|
|
1121
|
-
);
|
|
977
|
+
expect(mapOnNode2.get("hello")).toEqual("world");
|
|
1122
978
|
});
|
|
1123
979
|
|
|
1124
980
|
test("should keep the peer state when the peer closes", async () => {
|
|
1125
|
-
const
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
connectionWithClientAsPeer,
|
|
1130
|
-
} = createTwoConnectedNodes();
|
|
981
|
+
const client = createTestNode();
|
|
982
|
+
|
|
983
|
+
const { nodeToServerPeer, serverToNodePeer } =
|
|
984
|
+
connectNodeToSyncServer(client);
|
|
1131
985
|
|
|
1132
986
|
const group = jazzCloud.createGroup();
|
|
1133
987
|
const map = group.createMap();
|
|
@@ -1136,25 +990,23 @@ test("should keep the peer state when the peer closes", async () => {
|
|
|
1136
990
|
await client.loadCoValueCore(map.core.id);
|
|
1137
991
|
|
|
1138
992
|
const syncManager = client.syncManager;
|
|
1139
|
-
const peerState = syncManager.peers[
|
|
993
|
+
const peerState = syncManager.peers[nodeToServerPeer.id];
|
|
1140
994
|
|
|
1141
995
|
// @ts-expect-error Simulating a peer closing, leveraging the direct connection between the client/server peers
|
|
1142
|
-
await
|
|
996
|
+
await serverToNodePeer.outgoing.push("Disconnected");
|
|
1143
997
|
|
|
1144
998
|
await waitFor(() => peerState?.closed);
|
|
1145
999
|
|
|
1146
|
-
expect(syncManager.peers[
|
|
1000
|
+
expect(syncManager.peers[nodeToServerPeer.id]).not.toBeUndefined();
|
|
1147
1001
|
});
|
|
1148
1002
|
|
|
1149
1003
|
test("should delete the peer state when the peer closes if deletePeerStateOnClose is true", async () => {
|
|
1150
|
-
const
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
connectionWithClientAsPeer,
|
|
1155
|
-
} = createTwoConnectedNodes();
|
|
1004
|
+
const client = createTestNode();
|
|
1005
|
+
|
|
1006
|
+
const { nodeToServerPeer, serverToNodePeer } =
|
|
1007
|
+
connectNodeToSyncServer(client);
|
|
1156
1008
|
|
|
1157
|
-
|
|
1009
|
+
nodeToServerPeer.deletePeerStateOnClose = true;
|
|
1158
1010
|
|
|
1159
1011
|
const group = jazzCloud.createGroup();
|
|
1160
1012
|
const map = group.createMap();
|
|
@@ -1164,14 +1016,14 @@ test("should delete the peer state when the peer closes if deletePeerStateOnClos
|
|
|
1164
1016
|
|
|
1165
1017
|
const syncManager = client.syncManager;
|
|
1166
1018
|
|
|
1167
|
-
const peerState = syncManager.peers[
|
|
1019
|
+
const peerState = syncManager.peers[nodeToServerPeer.id];
|
|
1168
1020
|
|
|
1169
1021
|
// @ts-expect-error Simulating a peer closing, leveraging the direct connection between the client/server peers
|
|
1170
|
-
await
|
|
1022
|
+
await serverToNodePeer.outgoing.push("Disconnected");
|
|
1171
1023
|
|
|
1172
1024
|
await waitFor(() => peerState?.closed);
|
|
1173
1025
|
|
|
1174
|
-
expect(syncManager.peers[
|
|
1026
|
+
expect(syncManager.peers[nodeToServerPeer.id]).toBeUndefined();
|
|
1175
1027
|
});
|
|
1176
1028
|
|
|
1177
1029
|
describe("sync - extra tests", () => {
|
|
@@ -1624,29 +1476,6 @@ describe("sync - extra tests", () => {
|
|
|
1624
1476
|
});
|
|
1625
1477
|
});
|
|
1626
1478
|
|
|
1627
|
-
function createTwoConnectedNodes() {
|
|
1628
|
-
// Setup nodes
|
|
1629
|
-
const client = createTestNode();
|
|
1630
|
-
const jazzCloud = createTestNode();
|
|
1631
|
-
|
|
1632
|
-
// Connect nodes initially
|
|
1633
|
-
const [connectionWithClientAsPeer, jazzCloudConnectionAsPeer] =
|
|
1634
|
-
connectedPeers("connectionWithClient", "jazzCloudConnection", {
|
|
1635
|
-
peer1role: "client",
|
|
1636
|
-
peer2role: "server",
|
|
1637
|
-
});
|
|
1638
|
-
|
|
1639
|
-
client.syncManager.addPeer(jazzCloudConnectionAsPeer);
|
|
1640
|
-
jazzCloud.syncManager.addPeer(connectionWithClientAsPeer);
|
|
1641
|
-
|
|
1642
|
-
return {
|
|
1643
|
-
client,
|
|
1644
|
-
jazzCloud,
|
|
1645
|
-
connectionWithClientAsPeer,
|
|
1646
|
-
jazzCloudConnectionAsPeer,
|
|
1647
|
-
};
|
|
1648
|
-
}
|
|
1649
|
-
|
|
1650
1479
|
test("a value created on one node can be loaded on anotehr node even if not directly connected", async () => {
|
|
1651
1480
|
const userA = createTestNode();
|
|
1652
1481
|
const userB = createTestNode();
|
|
@@ -1671,7 +1500,7 @@ test("a value created on one node can be loaded on anotehr node even if not dire
|
|
|
1671
1500
|
|
|
1672
1501
|
describe("SyncManager - knownStates vs optimisticKnownStates", () => {
|
|
1673
1502
|
test("knownStates and optimisticKnownStates are the same when the coValue is fully synced", async () => {
|
|
1674
|
-
const { client
|
|
1503
|
+
const { node: client } = await createConnectedTestNode();
|
|
1675
1504
|
|
|
1676
1505
|
// Create test data
|
|
1677
1506
|
const group = client.createGroup();
|
|
@@ -1683,9 +1512,8 @@ describe("SyncManager - knownStates vs optimisticKnownStates", () => {
|
|
|
1683
1512
|
// Wait for the full sync to complete
|
|
1684
1513
|
await mapOnClient.core.waitForSync();
|
|
1685
1514
|
|
|
1686
|
-
const peerStateClient = client.syncManager.
|
|
1687
|
-
const peerStateJazzCloud =
|
|
1688
|
-
jazzCloud.syncManager.peers["connectionWithClient"]!;
|
|
1515
|
+
const peerStateClient = client.syncManager.getPeers()[0]!;
|
|
1516
|
+
const peerStateJazzCloud = jazzCloud.syncManager.getPeers()[0]!;
|
|
1689
1517
|
|
|
1690
1518
|
// The optimisticKnownStates should be the same as the knownStates after the full sync is complete
|
|
1691
1519
|
expect(
|
|
@@ -1699,7 +1527,7 @@ describe("SyncManager - knownStates vs optimisticKnownStates", () => {
|
|
|
1699
1527
|
});
|
|
1700
1528
|
|
|
1701
1529
|
test("optimisticKnownStates is updated as new transactions are sent, while knownStates only when the updates are acknowledged", async () => {
|
|
1702
|
-
const { client,
|
|
1530
|
+
const { node: client, nodeToServerPeer } = await createConnectedTestNode();
|
|
1703
1531
|
|
|
1704
1532
|
// Create test data and sync the first change
|
|
1705
1533
|
// We want that both the nodes know about the coValue so we can test
|
|
@@ -1717,7 +1545,7 @@ describe("SyncManager - knownStates vs optimisticKnownStates", () => {
|
|
|
1717
1545
|
// while knownStates is only updated when we receive the "known" messages
|
|
1718
1546
|
// that are acknowledging the receipt of the content messages
|
|
1719
1547
|
const outgoing = blockMessageTypeOnOutgoingPeer(
|
|
1720
|
-
|
|
1548
|
+
nodeToServerPeer,
|
|
1721
1549
|
"content",
|
|
1722
1550
|
);
|
|
1723
1551
|
|
|
@@ -1725,8 +1553,7 @@ describe("SyncManager - knownStates vs optimisticKnownStates", () => {
|
|
|
1725
1553
|
|
|
1726
1554
|
await client.syncManager.actuallySyncCoValue(map.core);
|
|
1727
1555
|
|
|
1728
|
-
const peerState = client.syncManager.peers[
|
|
1729
|
-
|
|
1556
|
+
const peerState = client.syncManager.peers[nodeToServerPeer.id]!;
|
|
1730
1557
|
expect(peerState.optimisticKnownStates.get(map.core.id)).not.toEqual(
|
|
1731
1558
|
peerState.knownStates.get(map.core.id),
|
|
1732
1559
|
);
|
|
@@ -1747,7 +1574,7 @@ describe("SyncManager - knownStates vs optimisticKnownStates", () => {
|
|
|
1747
1574
|
|
|
1748
1575
|
describe("SyncManager.addPeer", () => {
|
|
1749
1576
|
test("new peer gets a copy of previous peer's knownStates when replacing it", async () => {
|
|
1750
|
-
const { client } =
|
|
1577
|
+
const { node: client } = await createConnectedTestNode();
|
|
1751
1578
|
|
|
1752
1579
|
// Create test data
|
|
1753
1580
|
const group = client.createGroup();
|
|
@@ -1759,26 +1586,24 @@ describe("SyncManager.addPeer", () => {
|
|
|
1759
1586
|
// Wait for initial sync
|
|
1760
1587
|
await map.core.waitForSync();
|
|
1761
1588
|
|
|
1589
|
+
const firstPeerState = client.syncManager.getPeers()[0]!;
|
|
1590
|
+
|
|
1762
1591
|
// Store the initial known states
|
|
1763
|
-
const initialKnownStates =
|
|
1764
|
-
client.syncManager.peers["jazzCloudConnection"]!.knownStates;
|
|
1592
|
+
const initialKnownStates = firstPeerState.knownStates;
|
|
1765
1593
|
|
|
1766
1594
|
// Create new connection with same ID
|
|
1767
|
-
const [
|
|
1768
|
-
"
|
|
1769
|
-
"
|
|
1770
|
-
|
|
1771
|
-
peer1role: "server",
|
|
1772
|
-
peer2role: "client",
|
|
1773
|
-
},
|
|
1774
|
-
);
|
|
1595
|
+
const [secondPeer] = connectedPeers(firstPeerState.id, "unusedPeer", {
|
|
1596
|
+
peer1role: "server",
|
|
1597
|
+
peer2role: "client",
|
|
1598
|
+
});
|
|
1775
1599
|
|
|
1776
1600
|
// Add new peer with same ID
|
|
1777
|
-
client.syncManager.addPeer(
|
|
1601
|
+
client.syncManager.addPeer(secondPeer);
|
|
1602
|
+
|
|
1603
|
+
const newPeerState = client.syncManager.getPeers()[0]!;
|
|
1778
1604
|
|
|
1779
1605
|
// Verify that the new peer has a copy of the previous known states
|
|
1780
|
-
const newPeerKnownStates =
|
|
1781
|
-
client.syncManager.peers["jazzCloudConnection"]!.knownStates;
|
|
1606
|
+
const newPeerKnownStates = newPeerState.knownStates;
|
|
1782
1607
|
|
|
1783
1608
|
expect(newPeerKnownStates).not.toBe(initialKnownStates); // Should be a different instance
|
|
1784
1609
|
expect(newPeerKnownStates.get(map.core.id)).toEqual(
|
|
@@ -1787,7 +1612,7 @@ describe("SyncManager.addPeer", () => {
|
|
|
1787
1612
|
});
|
|
1788
1613
|
|
|
1789
1614
|
test("new peer with new ID starts with empty knownStates", async () => {
|
|
1790
|
-
const { client } =
|
|
1615
|
+
const { node: client } = await createConnectedTestNode();
|
|
1791
1616
|
|
|
1792
1617
|
// Create test data
|
|
1793
1618
|
const group = client.createGroup();
|
|
@@ -1815,23 +1640,19 @@ describe("SyncManager.addPeer", () => {
|
|
|
1815
1640
|
});
|
|
1816
1641
|
|
|
1817
1642
|
test("when adding a peer with the same ID as a previous peer, the previous peer is closed", async () => {
|
|
1818
|
-
const { client } =
|
|
1643
|
+
const { node: client } = await createConnectedTestNode();
|
|
1819
1644
|
|
|
1820
1645
|
// Store reference to first peer
|
|
1821
|
-
const firstPeer = client.syncManager.
|
|
1646
|
+
const firstPeer = client.syncManager.getPeers()[0]!;
|
|
1822
1647
|
const closeSpy = vi.spyOn(firstPeer, "gracefulShutdown");
|
|
1823
1648
|
|
|
1824
1649
|
// Create and add replacement peer
|
|
1825
|
-
const [
|
|
1826
|
-
"
|
|
1827
|
-
"
|
|
1828
|
-
|
|
1829
|
-
peer1role: "server",
|
|
1830
|
-
peer2role: "client",
|
|
1831
|
-
},
|
|
1832
|
-
);
|
|
1650
|
+
const [secondPeer] = connectedPeers(firstPeer.id, "unusedPeer", {
|
|
1651
|
+
peer1role: "server",
|
|
1652
|
+
peer2role: "client",
|
|
1653
|
+
});
|
|
1833
1654
|
|
|
1834
|
-
client.syncManager.addPeer(
|
|
1655
|
+
client.syncManager.addPeer(secondPeer);
|
|
1835
1656
|
|
|
1836
1657
|
// Verify thet the first peer had ben closed correctly
|
|
1837
1658
|
expect(closeSpy).toHaveBeenCalled();
|
|
@@ -1839,25 +1660,21 @@ describe("SyncManager.addPeer", () => {
|
|
|
1839
1660
|
});
|
|
1840
1661
|
|
|
1841
1662
|
test("when adding a peer with the same ID as a previous peer and the previous peer is closed, do not attempt to close it again", async () => {
|
|
1842
|
-
const { client } =
|
|
1663
|
+
const { node: client } = await createConnectedTestNode();
|
|
1843
1664
|
|
|
1844
1665
|
// Store reference to first peer
|
|
1845
|
-
const firstPeer = client.syncManager.
|
|
1666
|
+
const firstPeer = client.syncManager.getPeers()[0]!;
|
|
1846
1667
|
|
|
1847
1668
|
firstPeer.gracefulShutdown();
|
|
1848
1669
|
const closeSpy = vi.spyOn(firstPeer, "gracefulShutdown");
|
|
1849
1670
|
|
|
1850
1671
|
// Create and add replacement peer
|
|
1851
|
-
const [
|
|
1852
|
-
"
|
|
1853
|
-
"
|
|
1854
|
-
|
|
1855
|
-
peer1role: "server",
|
|
1856
|
-
peer2role: "client",
|
|
1857
|
-
},
|
|
1858
|
-
);
|
|
1672
|
+
const [secondPeer] = connectedPeers(firstPeer.id, "unusedPeer", {
|
|
1673
|
+
peer1role: "server",
|
|
1674
|
+
peer2role: "client",
|
|
1675
|
+
});
|
|
1859
1676
|
|
|
1860
|
-
client.syncManager.addPeer(
|
|
1677
|
+
client.syncManager.addPeer(secondPeer);
|
|
1861
1678
|
|
|
1862
1679
|
// Verify thet the first peer had not been closed again
|
|
1863
1680
|
expect(closeSpy).not.toHaveBeenCalled();
|
|
@@ -1865,24 +1682,15 @@ describe("SyncManager.addPeer", () => {
|
|
|
1865
1682
|
});
|
|
1866
1683
|
|
|
1867
1684
|
test("when adding a server peer the local coValues should be sent to it", async () => {
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
// Connect nodes initially
|
|
1873
|
-
const [connectionWithClientAsPeer, jazzCloudConnectionAsPeer] =
|
|
1874
|
-
connectedPeers("connectionWithClient", "jazzCloudConnection", {
|
|
1875
|
-
peer1role: "client",
|
|
1876
|
-
peer2role: "server",
|
|
1877
|
-
});
|
|
1878
|
-
|
|
1879
|
-
jazzCloud.syncManager.addPeer(connectionWithClientAsPeer);
|
|
1685
|
+
const { node: client, addServerPeer } = await createConnectedTestNode({
|
|
1686
|
+
connected: false,
|
|
1687
|
+
});
|
|
1880
1688
|
|
|
1881
1689
|
const group = client.createGroup();
|
|
1882
1690
|
const map = group.createMap();
|
|
1883
1691
|
map.set("key1", "value1", "trusting");
|
|
1884
1692
|
|
|
1885
|
-
|
|
1693
|
+
addServerPeer();
|
|
1886
1694
|
|
|
1887
1695
|
await map.core.waitForSync();
|
|
1888
1696
|
|
|
@@ -1892,18 +1700,8 @@ describe("SyncManager.addPeer", () => {
|
|
|
1892
1700
|
|
|
1893
1701
|
describe("loadCoValueCore with retry", () => {
|
|
1894
1702
|
test("should load the value if available on the server", async () => {
|
|
1895
|
-
const { client
|
|
1896
|
-
|
|
1897
|
-
const anotherClient = createTestNode();
|
|
1898
|
-
const [
|
|
1899
|
-
connectionWithAnotherClientAsPeer,
|
|
1900
|
-
jazzCloudConnectionAsPeerForAnotherClient,
|
|
1901
|
-
] = connectedPeers("connectionWithAnotherClient", "jazzCloudConnection", {
|
|
1902
|
-
peer1role: "client",
|
|
1903
|
-
peer2role: "server",
|
|
1904
|
-
});
|
|
1905
|
-
|
|
1906
|
-
jazzCloud.syncManager.addPeer(connectionWithAnotherClientAsPeer);
|
|
1703
|
+
const { node: client } = await createConnectedTestNode();
|
|
1704
|
+
const { node: anotherClient } = await createConnectedTestNode();
|
|
1907
1705
|
|
|
1908
1706
|
const group = anotherClient.createGroup();
|
|
1909
1707
|
const map = group.createMap();
|
|
@@ -1911,25 +1709,12 @@ describe("loadCoValueCore with retry", () => {
|
|
|
1911
1709
|
|
|
1912
1710
|
const promise = client.loadCoValueCore(map.id);
|
|
1913
1711
|
|
|
1914
|
-
anotherClient.syncManager.addPeer(
|
|
1915
|
-
jazzCloudConnectionAsPeerForAnotherClient,
|
|
1916
|
-
);
|
|
1917
1712
|
await expect(promise).resolves.not.toBe("unavailable");
|
|
1918
1713
|
});
|
|
1919
1714
|
|
|
1920
1715
|
test("should handle correctly two subsequent loads", async () => {
|
|
1921
|
-
const { client
|
|
1922
|
-
|
|
1923
|
-
const anotherClient = createTestNode();
|
|
1924
|
-
const [
|
|
1925
|
-
connectionWithAnotherClientAsPeer,
|
|
1926
|
-
jazzCloudConnectionAsPeerForAnotherClient,
|
|
1927
|
-
] = connectedPeers("connectionWithAnotherClient", "jazzCloudConnection", {
|
|
1928
|
-
peer1role: "client",
|
|
1929
|
-
peer2role: "server",
|
|
1930
|
-
});
|
|
1931
|
-
|
|
1932
|
-
jazzCloud.syncManager.addPeer(connectionWithAnotherClientAsPeer);
|
|
1716
|
+
const { node: client } = await createConnectedTestNode();
|
|
1717
|
+
const { node: anotherClient } = await createConnectedTestNode();
|
|
1933
1718
|
|
|
1934
1719
|
const group = anotherClient.createGroup();
|
|
1935
1720
|
const map = group.createMap();
|
|
@@ -1938,10 +1723,6 @@ describe("loadCoValueCore with retry", () => {
|
|
|
1938
1723
|
const promise1 = client.loadCoValueCore(map.id);
|
|
1939
1724
|
const promise2 = client.loadCoValueCore(map.id);
|
|
1940
1725
|
|
|
1941
|
-
anotherClient.syncManager.addPeer(
|
|
1942
|
-
jazzCloudConnectionAsPeerForAnotherClient,
|
|
1943
|
-
);
|
|
1944
|
-
|
|
1945
1726
|
await expect(promise1).resolves.not.toBe("unavailable");
|
|
1946
1727
|
await expect(promise2).resolves.not.toBe("unavailable");
|
|
1947
1728
|
});
|
|
@@ -1949,8 +1730,7 @@ describe("loadCoValueCore with retry", () => {
|
|
|
1949
1730
|
|
|
1950
1731
|
describe("waitForSyncWithPeer", () => {
|
|
1951
1732
|
test("should resolve when the coValue is fully uploaded into the peer", async () => {
|
|
1952
|
-
const {
|
|
1953
|
-
createTwoConnectedNodes();
|
|
1733
|
+
const { node: client } = await createConnectedTestNode();
|
|
1954
1734
|
|
|
1955
1735
|
// Create test data
|
|
1956
1736
|
const group = client.createGroup();
|
|
@@ -1959,21 +1739,32 @@ describe("waitForSyncWithPeer", () => {
|
|
|
1959
1739
|
|
|
1960
1740
|
await client.syncManager.actuallySyncCoValue(map.core);
|
|
1961
1741
|
|
|
1742
|
+
const peer = client.syncManager.getPeers()[0];
|
|
1743
|
+
|
|
1744
|
+
if (!peer) {
|
|
1745
|
+
throw new Error("No peer found");
|
|
1746
|
+
}
|
|
1747
|
+
|
|
1962
1748
|
await expect(
|
|
1963
1749
|
client.syncManager.waitForSyncWithPeer(peer.id, map.core.id, 100),
|
|
1964
1750
|
).resolves.toBe(true);
|
|
1965
1751
|
});
|
|
1966
1752
|
|
|
1967
1753
|
test("should not resolve when the coValue is not synced", async () => {
|
|
1968
|
-
const {
|
|
1969
|
-
|
|
1754
|
+
const { node: client } = await createConnectedTestNode();
|
|
1755
|
+
|
|
1756
|
+
const peer = client.syncManager.getPeers()[0];
|
|
1757
|
+
|
|
1758
|
+
if (!peer) {
|
|
1759
|
+
throw new Error("No peer found");
|
|
1760
|
+
}
|
|
1970
1761
|
|
|
1971
1762
|
// Create test data
|
|
1972
1763
|
const group = client.createGroup();
|
|
1973
1764
|
const map = group.createMap();
|
|
1974
1765
|
map.set("key1", "value1", "trusting");
|
|
1975
1766
|
|
|
1976
|
-
vi.spyOn(peer
|
|
1767
|
+
vi.spyOn(peer, "pushOutgoingMessage").mockImplementation(async () => {
|
|
1977
1768
|
return Promise.resolve();
|
|
1978
1769
|
});
|
|
1979
1770
|
|
|
@@ -1986,7 +1777,7 @@ describe("waitForSyncWithPeer", () => {
|
|
|
1986
1777
|
});
|
|
1987
1778
|
|
|
1988
1779
|
test("Should not crash when syncing an unknown coValue type", async () => {
|
|
1989
|
-
const { client
|
|
1780
|
+
const { node: client } = await createConnectedTestNode();
|
|
1990
1781
|
|
|
1991
1782
|
const coValue = client.createCoValue({
|
|
1992
1783
|
type: "ooops" as any,
|
|
@@ -1997,8 +1788,10 @@ test("Should not crash when syncing an unknown coValue type", async () => {
|
|
|
1997
1788
|
|
|
1998
1789
|
await coValue.waitForSync();
|
|
1999
1790
|
|
|
1791
|
+
const { node: anotherClient } = await createConnectedTestNode();
|
|
1792
|
+
|
|
2000
1793
|
const coValueOnTheOtherNode = await loadCoValueOrFail(
|
|
2001
|
-
|
|
1794
|
+
anotherClient,
|
|
2002
1795
|
coValue.getCurrentContent().id,
|
|
2003
1796
|
);
|
|
2004
1797
|
expect(coValueOnTheOtherNode.id).toBe(coValue.id);
|
|
@@ -2092,6 +1885,163 @@ describe("metrics", () => {
|
|
|
2092
1885
|
});
|
|
2093
1886
|
});
|
|
2094
1887
|
|
|
1888
|
+
describe("sync protocol", () => {
|
|
1889
|
+
test("should have the correct messages exchanged between client and server", async () => {
|
|
1890
|
+
// Creating the account from agent to simplify the messages exchange
|
|
1891
|
+
const { node: client, messages } = await createConnectedTestAgentNode();
|
|
1892
|
+
|
|
1893
|
+
const group = client.createGroup();
|
|
1894
|
+
const map = group.createMap();
|
|
1895
|
+
map.set("hello", "world", "trusting");
|
|
1896
|
+
|
|
1897
|
+
const mapOnJazzCloud = await loadCoValueOrFail(jazzCloud, map.id);
|
|
1898
|
+
expect(mapOnJazzCloud.get("hello")).toEqual("world");
|
|
1899
|
+
|
|
1900
|
+
expect(messages).toEqual([
|
|
1901
|
+
{
|
|
1902
|
+
from: "client",
|
|
1903
|
+
msg: {
|
|
1904
|
+
action: "load",
|
|
1905
|
+
header: true,
|
|
1906
|
+
id: group.id,
|
|
1907
|
+
sessions: {
|
|
1908
|
+
[client.currentSessionID]: 3,
|
|
1909
|
+
},
|
|
1910
|
+
},
|
|
1911
|
+
},
|
|
1912
|
+
{
|
|
1913
|
+
from: "server",
|
|
1914
|
+
msg: {
|
|
1915
|
+
action: "load",
|
|
1916
|
+
header: false,
|
|
1917
|
+
id: group.id,
|
|
1918
|
+
sessions: {},
|
|
1919
|
+
},
|
|
1920
|
+
},
|
|
1921
|
+
{
|
|
1922
|
+
from: "client",
|
|
1923
|
+
msg: {
|
|
1924
|
+
action: "load",
|
|
1925
|
+
header: true,
|
|
1926
|
+
id: map.id,
|
|
1927
|
+
sessions: {
|
|
1928
|
+
[client.currentSessionID]: 1,
|
|
1929
|
+
},
|
|
1930
|
+
},
|
|
1931
|
+
},
|
|
1932
|
+
{
|
|
1933
|
+
from: "server",
|
|
1934
|
+
msg: {
|
|
1935
|
+
action: "load",
|
|
1936
|
+
header: false,
|
|
1937
|
+
id: map.id,
|
|
1938
|
+
sessions: {},
|
|
1939
|
+
},
|
|
1940
|
+
},
|
|
1941
|
+
{
|
|
1942
|
+
from: "client",
|
|
1943
|
+
msg: {
|
|
1944
|
+
action: "content",
|
|
1945
|
+
header: {
|
|
1946
|
+
createdAt: expect.any(String),
|
|
1947
|
+
meta: null,
|
|
1948
|
+
ruleset: {
|
|
1949
|
+
initialAdmin: client.account.id,
|
|
1950
|
+
type: "group",
|
|
1951
|
+
},
|
|
1952
|
+
type: "comap",
|
|
1953
|
+
uniqueness: expect.any(String),
|
|
1954
|
+
},
|
|
1955
|
+
id: group.id,
|
|
1956
|
+
new: {
|
|
1957
|
+
[client.currentSessionID]: {
|
|
1958
|
+
after: 0,
|
|
1959
|
+
lastSignature: expect.any(String),
|
|
1960
|
+
newTransactions: expect.any(Array),
|
|
1961
|
+
},
|
|
1962
|
+
},
|
|
1963
|
+
priority: 0,
|
|
1964
|
+
},
|
|
1965
|
+
},
|
|
1966
|
+
{
|
|
1967
|
+
from: "client",
|
|
1968
|
+
msg: {
|
|
1969
|
+
action: "content",
|
|
1970
|
+
header: {
|
|
1971
|
+
createdAt: expect.any(String),
|
|
1972
|
+
meta: null,
|
|
1973
|
+
ruleset: {
|
|
1974
|
+
group: group.id,
|
|
1975
|
+
type: "ownedByGroup",
|
|
1976
|
+
},
|
|
1977
|
+
type: "comap",
|
|
1978
|
+
uniqueness: expect.any(String),
|
|
1979
|
+
},
|
|
1980
|
+
id: map.id,
|
|
1981
|
+
new: {
|
|
1982
|
+
[client.currentSessionID]: {
|
|
1983
|
+
after: 0,
|
|
1984
|
+
lastSignature: expect.any(String),
|
|
1985
|
+
newTransactions: expect.any(Array),
|
|
1986
|
+
},
|
|
1987
|
+
},
|
|
1988
|
+
priority: 3,
|
|
1989
|
+
},
|
|
1990
|
+
},
|
|
1991
|
+
{
|
|
1992
|
+
from: "server",
|
|
1993
|
+
msg: {
|
|
1994
|
+
action: "known",
|
|
1995
|
+
header: true,
|
|
1996
|
+
id: group.id,
|
|
1997
|
+
sessions: {
|
|
1998
|
+
[client.currentSessionID]: 3,
|
|
1999
|
+
},
|
|
2000
|
+
},
|
|
2001
|
+
},
|
|
2002
|
+
{
|
|
2003
|
+
// TODO: This is a redundant message, we should remove it
|
|
2004
|
+
from: "client",
|
|
2005
|
+
msg: {
|
|
2006
|
+
action: "content",
|
|
2007
|
+
header: {
|
|
2008
|
+
createdAt: expect.any(String),
|
|
2009
|
+
meta: null,
|
|
2010
|
+
ruleset: {
|
|
2011
|
+
group: group.id,
|
|
2012
|
+
type: "ownedByGroup",
|
|
2013
|
+
},
|
|
2014
|
+
type: "comap",
|
|
2015
|
+
uniqueness: expect.any(String),
|
|
2016
|
+
},
|
|
2017
|
+
id: map.id,
|
|
2018
|
+
new: {
|
|
2019
|
+
[client.currentSessionID]: {
|
|
2020
|
+
after: 0,
|
|
2021
|
+
lastSignature: expect.any(String),
|
|
2022
|
+
newTransactions: expect.any(Array),
|
|
2023
|
+
},
|
|
2024
|
+
},
|
|
2025
|
+
priority: 3,
|
|
2026
|
+
},
|
|
2027
|
+
},
|
|
2028
|
+
{
|
|
2029
|
+
// TODO: This is a redundant message, we should remove it
|
|
2030
|
+
from: "server",
|
|
2031
|
+
msg: {
|
|
2032
|
+
action: "known",
|
|
2033
|
+
asDependencyOf: undefined,
|
|
2034
|
+
header: true,
|
|
2035
|
+
id: group.id,
|
|
2036
|
+
sessions: {
|
|
2037
|
+
[client.currentSessionID]: 3,
|
|
2038
|
+
},
|
|
2039
|
+
},
|
|
2040
|
+
},
|
|
2041
|
+
]);
|
|
2042
|
+
});
|
|
2043
|
+
});
|
|
2044
|
+
|
|
2095
2045
|
function groupContentEx(group: RawGroup) {
|
|
2096
2046
|
return {
|
|
2097
2047
|
action: "content",
|
|
@@ -2099,27 +2049,9 @@ function groupContentEx(group: RawGroup) {
|
|
|
2099
2049
|
};
|
|
2100
2050
|
}
|
|
2101
2051
|
|
|
2102
|
-
function _admContEx(adminID: RawAccountID) {
|
|
2103
|
-
return {
|
|
2104
|
-
action: "content",
|
|
2105
|
-
id: adminID,
|
|
2106
|
-
};
|
|
2107
|
-
}
|
|
2108
|
-
|
|
2109
2052
|
function groupStateEx(group: RawGroup) {
|
|
2110
2053
|
return {
|
|
2111
2054
|
action: "known",
|
|
2112
2055
|
id: group.core.id,
|
|
2113
2056
|
};
|
|
2114
2057
|
}
|
|
2115
|
-
|
|
2116
|
-
function _admStateEx(adminID: RawAccountID) {
|
|
2117
|
-
return {
|
|
2118
|
-
action: "known",
|
|
2119
|
-
id: adminID,
|
|
2120
|
-
};
|
|
2121
|
-
}
|
|
2122
|
-
|
|
2123
|
-
function sleep(ms: number) {
|
|
2124
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
2125
|
-
}
|