cojson 0.17.11 → 0.17.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +17 -0
  3. package/dist/coValueCore/SessionMap.d.ts +4 -3
  4. package/dist/coValueCore/SessionMap.d.ts.map +1 -1
  5. package/dist/coValueCore/SessionMap.js +15 -4
  6. package/dist/coValueCore/SessionMap.js.map +1 -1
  7. package/dist/coValueCore/coValueCore.d.ts +2 -2
  8. package/dist/coValueCore/coValueCore.d.ts.map +1 -1
  9. package/dist/coValueCore/coValueCore.js +33 -32
  10. package/dist/coValueCore/coValueCore.js.map +1 -1
  11. package/dist/coValueCore/utils.d.ts.map +1 -1
  12. package/dist/coValueCore/utils.js.map +1 -1
  13. package/dist/coValueCore/verifiedState.d.ts +8 -2
  14. package/dist/coValueCore/verifiedState.d.ts.map +1 -1
  15. package/dist/coValueCore/verifiedState.js +7 -4
  16. package/dist/coValueCore/verifiedState.js.map +1 -1
  17. package/dist/crypto/PureJSCrypto.d.ts +4 -3
  18. package/dist/crypto/PureJSCrypto.d.ts.map +1 -1
  19. package/dist/crypto/PureJSCrypto.js +34 -2
  20. package/dist/crypto/PureJSCrypto.js.map +1 -1
  21. package/dist/crypto/WasmCrypto.d.ts +4 -3
  22. package/dist/crypto/WasmCrypto.d.ts.map +1 -1
  23. package/dist/crypto/WasmCrypto.js +10 -4
  24. package/dist/crypto/WasmCrypto.js.map +1 -1
  25. package/dist/crypto/crypto.d.ts +4 -3
  26. package/dist/crypto/crypto.d.ts.map +1 -1
  27. package/dist/localNode.js +1 -1
  28. package/dist/localNode.js.map +1 -1
  29. package/dist/queue/LocalTransactionsSyncQueue.d.ts +15 -0
  30. package/dist/queue/LocalTransactionsSyncQueue.d.ts.map +1 -1
  31. package/dist/queue/LocalTransactionsSyncQueue.js +25 -0
  32. package/dist/queue/LocalTransactionsSyncQueue.js.map +1 -1
  33. package/dist/sync.d.ts +10 -7
  34. package/dist/sync.d.ts.map +1 -1
  35. package/dist/sync.js +33 -31
  36. package/dist/sync.js.map +1 -1
  37. package/dist/tests/PureJSCrypto.test.js +43 -0
  38. package/dist/tests/PureJSCrypto.test.js.map +1 -1
  39. package/dist/tests/WasmCrypto.test.js +55 -0
  40. package/dist/tests/WasmCrypto.test.js.map +1 -1
  41. package/dist/tests/coList.test.js +13 -0
  42. package/dist/tests/coList.test.js.map +1 -1
  43. package/dist/tests/coMap.test.js +14 -0
  44. package/dist/tests/coMap.test.js.map +1 -1
  45. package/dist/tests/coPlainText.test.js +13 -0
  46. package/dist/tests/coPlainText.test.js.map +1 -1
  47. package/dist/tests/coStream.test.js +25 -0
  48. package/dist/tests/coStream.test.js.map +1 -1
  49. package/dist/tests/coValueCore.test.js +28 -2
  50. package/dist/tests/coValueCore.test.js.map +1 -1
  51. package/dist/tests/coreWasm.test.js +1 -1
  52. package/dist/tests/coreWasm.test.js.map +1 -1
  53. package/dist/tests/sync.known.test.d.ts +2 -0
  54. package/dist/tests/sync.known.test.d.ts.map +1 -0
  55. package/dist/tests/sync.known.test.js +78 -0
  56. package/dist/tests/sync.known.test.js.map +1 -0
  57. package/dist/tests/sync.sharding.test.d.ts +2 -0
  58. package/dist/tests/sync.sharding.test.d.ts.map +1 -0
  59. package/dist/tests/sync.sharding.test.js +85 -0
  60. package/dist/tests/sync.sharding.test.js.map +1 -0
  61. package/dist/tests/sync.storage.test.js +31 -1
  62. package/dist/tests/sync.storage.test.js.map +1 -1
  63. package/dist/tests/sync.storageAsync.test.js +1 -1
  64. package/dist/tests/sync.storageAsync.test.js.map +1 -1
  65. package/dist/tests/sync.test.js +31 -2
  66. package/dist/tests/sync.test.js.map +1 -1
  67. package/dist/tests/sync.upload.test.js +26 -0
  68. package/dist/tests/sync.upload.test.js.map +1 -1
  69. package/dist/tests/testUtils.d.ts.map +1 -1
  70. package/dist/tests/testUtils.js +3 -1
  71. package/dist/tests/testUtils.js.map +1 -1
  72. package/package.json +2 -2
  73. package/src/coValueCore/SessionMap.ts +24 -1
  74. package/src/coValueCore/coValueCore.ts +46 -41
  75. package/src/coValueCore/utils.ts +0 -1
  76. package/src/coValueCore/verifiedState.ts +14 -3
  77. package/src/crypto/PureJSCrypto.ts +49 -1
  78. package/src/crypto/WasmCrypto.ts +15 -1
  79. package/src/crypto/crypto.ts +7 -1
  80. package/src/localNode.ts +1 -1
  81. package/src/queue/LocalTransactionsSyncQueue.ts +32 -1
  82. package/src/sync.ts +50 -36
  83. package/src/tests/PureJSCrypto.test.ts +66 -0
  84. package/src/tests/WasmCrypto.test.ts +88 -0
  85. package/src/tests/coList.test.ts +19 -0
  86. package/src/tests/coMap.test.ts +21 -0
  87. package/src/tests/coPlainText.test.ts +18 -0
  88. package/src/tests/coStream.test.ts +36 -0
  89. package/src/tests/coValueCore.test.ts +49 -0
  90. package/src/tests/coreWasm.test.ts +1 -0
  91. package/src/tests/sync.known.test.ts +109 -0
  92. package/src/tests/sync.sharding.test.ts +119 -0
  93. package/src/tests/sync.storage.test.ts +43 -1
  94. package/src/tests/sync.storageAsync.test.ts +1 -1
  95. package/src/tests/sync.test.ts +49 -2
  96. package/src/tests/sync.upload.test.ts +35 -0
  97. package/src/tests/testUtils.ts +3 -1
package/src/sync.ts CHANGED
@@ -128,6 +128,11 @@ export function combinedKnownStates(
128
128
  };
129
129
  }
130
130
 
131
+ export type ServerPeerSelector = (
132
+ id: RawCoID,
133
+ serverPeers: PeerState[],
134
+ ) => PeerState[];
135
+
131
136
  export class SyncManager {
132
137
  peers: { [key: PeerID]: PeerState } = {};
133
138
  local: LocalNode;
@@ -144,6 +149,8 @@ export class SyncManager {
144
149
  });
145
150
  private transactionsSizeHistogram: Histogram;
146
151
 
152
+ serverPeerSelector?: ServerPeerSelector;
153
+
147
154
  constructor(local: LocalNode) {
148
155
  this.local = local;
149
156
  this.syncState = new SyncStateManager(this);
@@ -163,23 +170,21 @@ export class SyncManager {
163
170
  this.skipVerify = true;
164
171
  }
165
172
 
166
- peersInPriorityOrder(): PeerState[] {
167
- return Object.values(this.peers).sort((a, b) => {
168
- const aPriority = a.priority || 0;
169
- const bPriority = b.priority || 0;
170
-
171
- return bPriority - aPriority;
172
- });
173
+ getPeers(id: RawCoID): PeerState[] {
174
+ return this.getServerPeers(id).concat(this.getClientPeers());
173
175
  }
174
176
 
175
- getPeers(): PeerState[] {
176
- return Object.values(this.peers);
177
+ getClientPeers(): PeerState[] {
178
+ return Object.values(this.peers).filter((peer) => peer.role === "client");
177
179
  }
178
180
 
179
- getServerPeers(excludePeerId?: PeerID): PeerState[] {
180
- return this.getPeers().filter(
181
+ getServerPeers(id: RawCoID, excludePeerId?: PeerID): PeerState[] {
182
+ const serverPeers = Object.values(this.peers).filter(
181
183
  (peer) => peer.role === "server" && peer.id !== excludePeerId,
182
184
  );
185
+ return this.serverPeerSelector
186
+ ? this.serverPeerSelector(id, serverPeers)
187
+ : serverPeers;
183
188
  }
184
189
 
185
190
  handleSyncMessage(msg: SyncMessage, peer: PeerState) {
@@ -221,6 +226,23 @@ export class SyncManager {
221
226
  id: RawCoID,
222
227
  peer: PeerState,
223
228
  seen: Set<RawCoID> = new Set(),
229
+ ) {
230
+ this.sendNewContent(id, peer, seen, true);
231
+ }
232
+
233
+ sendNewContentWithoutDependencies(
234
+ id: RawCoID,
235
+ peer: PeerState,
236
+ seen: Set<RawCoID> = new Set(),
237
+ ) {
238
+ this.sendNewContent(id, peer, seen, false);
239
+ }
240
+
241
+ private sendNewContent(
242
+ id: RawCoID,
243
+ peer: PeerState,
244
+ seen: Set<RawCoID> = new Set(),
245
+ includeDependencies: boolean,
224
246
  ) {
225
247
  if (seen.has(id)) {
226
248
  return;
@@ -234,8 +256,10 @@ export class SyncManager {
234
256
  return;
235
257
  }
236
258
 
237
- for (const dependency of coValue.getDependedOnCoValues()) {
238
- this.sendNewContentIncludingDependencies(dependency, peer, seen);
259
+ if (includeDependencies) {
260
+ for (const dependency of coValue.getDependedOnCoValues()) {
261
+ this.sendNewContentIncludingDependencies(dependency, peer, seen);
262
+ }
239
263
  }
240
264
 
241
265
  const newContentPieces = coValue.verified.newContentSince(
@@ -402,7 +426,7 @@ export class SyncManager {
402
426
  return;
403
427
  }
404
428
 
405
- const peers = this.getServerPeers(peer.id);
429
+ const peers = this.getServerPeers(msg.id, peer.id);
406
430
 
407
431
  coValue.load(peers);
408
432
 
@@ -442,7 +466,11 @@ export class SyncManager {
442
466
  }
443
467
 
444
468
  if (coValue.isAvailable()) {
445
- this.sendNewContentIncludingDependencies(msg.id, peer);
469
+ if (peer.role === "server") {
470
+ this.sendNewContentWithoutDependencies(msg.id, peer);
471
+ } else {
472
+ this.sendNewContentIncludingDependencies(msg.id, peer);
473
+ }
446
474
  }
447
475
  }
448
476
 
@@ -524,7 +552,7 @@ export class SyncManager {
524
552
  if (!dependencyCoValue.hasVerifiedContent()) {
525
553
  coValue.markMissingDependency(dependency);
526
554
 
527
- const peers = this.getServerPeers();
555
+ const peers = this.getServerPeers(dependencyCoValue.id);
528
556
 
529
557
  // if the peer that sent the content is a client, we add it to the list of peers
530
558
  // to also ask them for the dependency
@@ -607,7 +635,7 @@ export class SyncManager {
607
635
  // This covers the case where we are getting a new session on an already loaded coValue
608
636
  // where we need to load the account to get their public key
609
637
  if (!coValue.missingDependencies.has(accountId)) {
610
- const peers = this.getServerPeers();
638
+ const peers = this.getServerPeers(account.id);
611
639
 
612
640
  if (peer?.role === "client") {
613
641
  // if the peer that sent the content is a client, we add it to the list of peers
@@ -727,7 +755,7 @@ export class SyncManager {
727
755
  this.storeContent(contentToStore);
728
756
  }
729
757
 
730
- for (const peer of this.peersInPriorityOrder()) {
758
+ for (const peer of this.getPeers(coValue.id)) {
731
759
  /**
732
760
  * We sync the content against the source peer if it is a client or server peers
733
761
  * to upload any content that is available on the current node and not on the source peer.
@@ -771,26 +799,12 @@ export class SyncManager {
771
799
  return this.sendNewContentIncludingDependencies(msg.id, peer);
772
800
  }
773
801
 
774
- dirtyCoValuesTrackingSets: Set<Set<RawCoID>> = new Set();
775
- trackDirtyCoValues() {
776
- const trackingSet = new Set<RawCoID>();
777
-
778
- this.dirtyCoValuesTrackingSets.add(trackingSet);
779
-
780
- return {
781
- done: () => {
782
- this.dirtyCoValuesTrackingSets.delete(trackingSet);
783
-
784
- return trackingSet;
785
- },
786
- };
787
- }
788
-
789
802
  private syncQueue = new LocalTransactionsSyncQueue((content) =>
790
803
  this.syncContent(content),
791
804
  );
792
805
  syncHeader = this.syncQueue.syncHeader;
793
806
  syncLocalTransaction = this.syncQueue.syncTransaction;
807
+ trackDirtyCoValues = this.syncQueue.trackDirtyCoValues;
794
808
 
795
809
  syncContent(content: NewContentMessage) {
796
810
  const coValue = this.local.getCoValue(content.id);
@@ -799,7 +813,7 @@ export class SyncManager {
799
813
 
800
814
  const contentKnownState = knownStateFromContent(content);
801
815
 
802
- for (const peer of this.peersInPriorityOrder()) {
816
+ for (const peer of this.getPeers(coValue.id)) {
803
817
  if (peer.closed) continue;
804
818
  if (coValue.isErroredInPeer(peer.id)) continue;
805
819
 
@@ -818,7 +832,7 @@ export class SyncManager {
818
832
  peer.trackToldKnownState(coValue.id);
819
833
  }
820
834
 
821
- for (const peer of this.getPeers()) {
835
+ for (const peer of this.getPeers(coValue.id)) {
822
836
  this.syncState.triggerUpdate(peer.id, coValue.id);
823
837
  }
824
838
  }
@@ -899,7 +913,7 @@ export class SyncManager {
899
913
  }
900
914
 
901
915
  waitForSync(id: RawCoID, timeout = 60_000) {
902
- const peers = this.getPeers();
916
+ const peers = this.getPeers(id);
903
917
 
904
918
  return Promise.all(
905
919
  peers
@@ -126,6 +126,72 @@ describe("PureJSCrypto", () => {
126
126
 
127
127
  expect(map.get("count")).toEqual(0);
128
128
  });
129
+
130
+ it("can add a meta to a private transaction", async () => {
131
+ const client = setupTestNode({
132
+ connected: true,
133
+ });
134
+
135
+ const group = client.node.createGroup();
136
+ const map = group.createMap();
137
+
138
+ map.core.makeTransaction([], "private", {
139
+ meta: {
140
+ count: 1,
141
+ },
142
+ });
143
+
144
+ await map.core.waitForSync();
145
+
146
+ const session2 = client.spawnNewSession();
147
+
148
+ const mapInOtherSession = await loadCoValueOrFail(session2.node, map.id);
149
+
150
+ const decryptedMeta =
151
+ mapInOtherSession.core.verified.decryptTransactionMeta(
152
+ client.node.currentSessionID,
153
+ 0,
154
+ map.core.getCurrentReadKey().secret!,
155
+ );
156
+
157
+ expect(decryptedMeta).toEqual({
158
+ meta: {
159
+ count: 1,
160
+ },
161
+ });
162
+ });
163
+
164
+ it("can add a meta to a trusting transaction", async () => {
165
+ const client = setupTestNode({
166
+ connected: true,
167
+ });
168
+
169
+ const group = client.node.createGroup();
170
+ const map = group.createMap();
171
+
172
+ map.core.makeTransaction([], "trusting", {
173
+ meta: {
174
+ count: 1,
175
+ },
176
+ });
177
+
178
+ await map.core.waitForSync();
179
+
180
+ const session2 = client.spawnNewSession();
181
+
182
+ const mapInOtherSession = await loadCoValueOrFail(session2.node, map.id);
183
+
184
+ const transferredMeta = JSON.parse(
185
+ mapInOtherSession.core.verified.sessions.get(client.node.currentSessionID)
186
+ ?.transactions[0]?.meta!,
187
+ );
188
+
189
+ expect(transferredMeta).toEqual({
190
+ meta: {
191
+ count: 1,
192
+ },
193
+ });
194
+ });
129
195
  });
130
196
 
131
197
  describe("PureJSSessionLog", () => {
@@ -125,4 +125,92 @@ describe("WasmCrypto", () => {
125
125
 
126
126
  expect(map.get("count")).toEqual(0);
127
127
  });
128
+
129
+ it("can add a meta to a private transaction", async () => {
130
+ const client = setupTestNode({
131
+ connected: true,
132
+ });
133
+
134
+ const group = client.node.createGroup();
135
+ const map = group.createMap();
136
+
137
+ map.core.makeTransaction([], "private", {
138
+ meta: {
139
+ count: 1,
140
+ },
141
+ });
142
+
143
+ await map.core.waitForSync();
144
+
145
+ const session2 = client.spawnNewSession();
146
+
147
+ const mapInOtherSession = await loadCoValueOrFail(session2.node, map.id);
148
+
149
+ const decryptedMeta =
150
+ mapInOtherSession.core.verified.decryptTransactionMeta(
151
+ client.node.currentSessionID,
152
+ 0,
153
+ mapInOtherSession.core.getCurrentReadKey().secret!,
154
+ );
155
+
156
+ expect(decryptedMeta).toEqual({
157
+ meta: {
158
+ count: 1,
159
+ },
160
+ });
161
+ });
162
+
163
+ it("can add a meta to a trusting transaction", async () => {
164
+ const client = setupTestNode({
165
+ connected: true,
166
+ });
167
+
168
+ const group = client.node.createGroup();
169
+ const map = group.createMap();
170
+
171
+ map.core.makeTransaction([], "trusting", {
172
+ meta: {
173
+ count: 1,
174
+ },
175
+ });
176
+
177
+ await map.core.waitForSync();
178
+
179
+ const session2 = client.spawnNewSession();
180
+
181
+ const mapInOtherSession = await loadCoValueOrFail(session2.node, map.id);
182
+
183
+ const transferredMeta = JSON.parse(
184
+ mapInOtherSession.core.verified.sessions.get(client.node.currentSessionID)
185
+ ?.transactions[0]?.meta!,
186
+ );
187
+
188
+ expect(transferredMeta).toEqual({
189
+ meta: {
190
+ count: 1,
191
+ },
192
+ });
193
+ });
194
+
195
+ it("fails to verify signatures without a signer ID", async () => {
196
+ const agentSecret = wasmCrypto.newRandomAgentSecret();
197
+ const sessionID = wasmCrypto.newRandomSessionID(
198
+ wasmCrypto.getAgentID(agentSecret),
199
+ );
200
+
201
+ const sessionLog = wasmCrypto.createSessionLog("co_z12345678", sessionID);
202
+ expect(() =>
203
+ sessionLog.tryAdd(
204
+ [
205
+ {
206
+ privacy: "trusting",
207
+ changes: stableStringify([{ op: "set", key: "count", value: 1 }]),
208
+ madeAt: Date.now(),
209
+ },
210
+ ],
211
+ "signature_z12345678",
212
+ false,
213
+ ),
214
+ ).toThrow(expect.stringContaining("Signature verification failed"));
215
+ });
128
216
  });
@@ -409,3 +409,22 @@ test("totalValidTransactions should return the number of valid transactions proc
409
409
  listOnOtherClient.core.getCurrentContent().totalValidTransactions,
410
410
  ).toEqual(2);
411
411
  });
412
+
413
+ test("Should ignore unknown meta transactions", () => {
414
+ const node = nodeWithRandomAgentAndSessionID();
415
+
416
+ const coValue = node.createCoValue({
417
+ type: "colist",
418
+ ruleset: { type: "unsafeAllowAll" },
419
+ meta: null,
420
+ ...Crypto.createdNowUnique(),
421
+ });
422
+
423
+ coValue.makeTransaction([], "trusting", { unknownMeta: 1 });
424
+
425
+ const content = expectList(coValue.getCurrentContent());
426
+
427
+ content.append("first", 0, "trusting");
428
+
429
+ expect(content.toJSON()).toEqual(["first"]);
430
+ });
@@ -217,6 +217,27 @@ test("Can set items in bulk with assign", () => {
217
217
  });
218
218
  });
219
219
 
220
+ test("Should ignore unknown meta transactions", () => {
221
+ const node = nodeWithRandomAgentAndSessionID();
222
+
223
+ const coValue = node.createCoValue({
224
+ type: "comap",
225
+ ruleset: { type: "unsafeAllowAll" },
226
+ meta: null,
227
+ ...Crypto.createdNowUnique(),
228
+ });
229
+
230
+ coValue.makeTransaction([], "trusting", { unknownMeta: 1 });
231
+
232
+ const content = expectMap(coValue.getCurrentContent());
233
+
234
+ expect(content.type).toEqual("comap");
235
+
236
+ content.set("key1", "set1", "trusting");
237
+
238
+ expect(content.get("key1")).toEqual("set1");
239
+ });
240
+
220
241
  test("totalValidTransactions should return the number of valid transactions processed", async () => {
221
242
  const client = setupTestNode({
222
243
  connected: true,
@@ -298,6 +298,24 @@ test("Splits into and from grapheme string arrays", () => {
298
298
  expect(text).toEqual("👋 안녕!");
299
299
  });
300
300
 
301
+ test("Should ignore unknown meta transactions", () => {
302
+ const node = nodeWithRandomAgentAndSessionID();
303
+
304
+ const coValue = node.createCoValue({
305
+ type: "coplaintext",
306
+ ruleset: { type: "unsafeAllowAll" },
307
+ meta: null,
308
+ ...Crypto.createdNowUnique(),
309
+ });
310
+
311
+ coValue.makeTransaction([], "trusting", { unknownMeta: 1 });
312
+
313
+ const content = expectPlainText(coValue.getCurrentContent());
314
+
315
+ content.insertAfter(0, "hello", "trusting");
316
+ expect(content.toString()).toEqual("hello");
317
+ });
318
+
301
319
  test("chunks transactions when when the chars are longer than MAX_RECOMMENDED_TX_SIZE", async () => {
302
320
  setMaxRecommendedTxSize(5);
303
321
 
@@ -127,6 +127,42 @@ test("Can push into RawBinaryCoStream", () => {
127
127
  });
128
128
  });
129
129
 
130
+ test("Should ignore meta transactions on RawBinaryCoStream", () => {
131
+ const node = nodeWithRandomAgentAndSessionID();
132
+
133
+ const coValue = node.createCoValue({
134
+ type: "costream",
135
+ ruleset: { type: "unsafeAllowAll" },
136
+ meta: { type: "binary" },
137
+ ...Crypto.createdNowUnique(),
138
+ });
139
+
140
+ coValue.makeTransaction([], "trusting", { unknownMeta: 1 });
141
+
142
+ const content = coValue.getCurrentContent();
143
+
144
+ if (
145
+ content.type !== "costream" ||
146
+ content.headerMeta?.type !== "binary" ||
147
+ !(content instanceof RawBinaryCoStream)
148
+ ) {
149
+ throw new Error("Expected binary stream");
150
+ }
151
+
152
+ content.startBinaryStream(
153
+ { mimeType: "text/plain", fileName: "test.txt" },
154
+ "trusting",
155
+ );
156
+ content.pushBinaryStreamChunk(new Uint8Array([1, 2, 3]), "trusting");
157
+ content.endBinaryStream("trusting");
158
+ expect(content.getBinaryChunks()).toEqual({
159
+ mimeType: "text/plain",
160
+ fileName: "test.txt",
161
+ chunks: [new Uint8Array([1, 2, 3])],
162
+ finished: true,
163
+ });
164
+ });
165
+
130
166
  test("When adding large transactions (small fraction of MAX_RECOMMENDED_TX_SIZE), we store an inbetween signature every time we reach MAX_RECOMMENDED_TX_SIZE and split up newContentSince accordingly", () => {
131
167
  const node = nodeWithRandomAgentAndSessionID();
132
168
 
@@ -20,9 +20,11 @@ import {
20
20
  loadCoValueOrFail,
21
21
  nodeWithRandomAgentAndSessionID,
22
22
  randomAgentAndSessionID,
23
+ setupTestNode,
23
24
  tearDownTestMetricReader,
24
25
  } from "./testUtils.js";
25
26
  import { CO_VALUE_PRIORITY } from "../priority.js";
27
+ import { determineValidTransactions } from "../permissions.js";
26
28
 
27
29
  const Crypto = await WasmCrypto.create();
28
30
 
@@ -32,6 +34,7 @@ const agentSecret =
32
34
 
33
35
  beforeEach(() => {
34
36
  metricReader = createTestMetricReader();
37
+ setupTestNode({ isSyncServer: true });
35
38
  });
36
39
 
37
40
  afterEach(() => {
@@ -53,6 +56,7 @@ test("transactions with wrong signature are rejected", () => {
53
56
  node.currentSessionID,
54
57
  node.getCurrentAgent(),
55
58
  [{ hello: "world" }],
59
+ undefined,
56
60
  );
57
61
 
58
62
  transaction.madeAt = Date.now() + 1000;
@@ -264,6 +268,51 @@ test("listeners are notified even if the previous listener threw an error", asyn
264
268
  errorLog.mockRestore();
265
269
  });
266
270
 
271
+ test("creates a transaction with trusting meta information", async () => {
272
+ const client = setupTestNode();
273
+
274
+ const group = client.node.createGroup();
275
+ const map = group.createMap();
276
+ map.core.makeTransaction([], "trusting", {
277
+ meta: true,
278
+ });
279
+
280
+ const validTransactions = determineValidTransactions(map.core);
281
+
282
+ expect(validTransactions[0]?.tx.meta).toBe(`{"meta":true}`);
283
+ });
284
+
285
+ test("creates a transaction with private meta information", async () => {
286
+ const client = setupTestNode({ connected: true });
287
+
288
+ const group = client.node.createGroup();
289
+ const map = group.createMap();
290
+ map.core.makeTransaction([], "private", {
291
+ meta: true,
292
+ });
293
+
294
+ const localTransactionMeta = map.core.verified.decryptTransactionMeta(
295
+ client.node.currentSessionID,
296
+ 0,
297
+ map.core.getCurrentReadKey().secret!,
298
+ );
299
+
300
+ expect(localTransactionMeta).toEqual({ meta: true });
301
+
302
+ const newSession = client.spawnNewSession();
303
+
304
+ const mapOnNewSession = await loadCoValueOrFail(newSession.node, map.id);
305
+
306
+ const syncedTransactionMeta =
307
+ mapOnNewSession.core.verified.decryptTransactionMeta(
308
+ client.node.currentSessionID,
309
+ 0,
310
+ mapOnNewSession.core.getCurrentReadKey().secret!,
311
+ );
312
+
313
+ expect(syncedTransactionMeta).toEqual({ meta: true });
314
+ });
315
+
267
316
  test("getValidTransactions should skip private transactions with invalid JSON", () => {
268
317
  const [agent, sessionID] = agentAndSessionIDFromSecret(agentSecret);
269
318
  const node = new LocalNode(agent.agentSecret, sessionID, Crypto);
@@ -85,6 +85,7 @@ describe("SessionLog WASM", () => {
85
85
  key.id,
86
86
  key.secret,
87
87
  0,
88
+ undefined,
88
89
  );
89
90
 
90
91
  expect(signature).toMatch(/^signature_z[a-zA-Z0-9]+$/);
@@ -0,0 +1,109 @@
1
+ import { beforeEach, describe, expect, test } from "vitest";
2
+ import { setCoValueLoadingRetryDelay } from "../config";
3
+ import {
4
+ SyncMessagesLog,
5
+ TEST_NODE_CONFIG,
6
+ blockMessageTypeOnOutgoingPeer,
7
+ getSyncServerConnectedPeer,
8
+ loadCoValueOrFail,
9
+ setupTestAccount,
10
+ setupTestNode,
11
+ } from "./testUtils";
12
+
13
+ let jazzCloud: ReturnType<typeof setupTestNode>;
14
+
15
+ // Set a short timeout to make the tests on unavailable complete faster
16
+ setCoValueLoadingRetryDelay(100);
17
+
18
+ beforeEach(async () => {
19
+ // We want to simulate a real world communication that happens asynchronously
20
+ TEST_NODE_CONFIG.withAsyncPeers = true;
21
+
22
+ SyncMessagesLog.clear();
23
+ jazzCloud = setupTestNode({ isSyncServer: true });
24
+ });
25
+
26
+ describe("sending known coValues", () => {
27
+ test("dependencies are included when responding to a client", async () => {
28
+ const group = jazzCloud.node.createGroup();
29
+ const map = group.createMap();
30
+ map.set("hello", "world", "trusting");
31
+
32
+ const { node: client } = setupTestNode({
33
+ connected: true,
34
+ });
35
+
36
+ map.set("hello2", "world2", "trusting");
37
+ await map.core.waitForSync();
38
+
39
+ const mapOnClient = await loadCoValueOrFail(client, map.id);
40
+ expect(mapOnClient.get("hello")).toEqual("world");
41
+ expect(mapOnClient.get("hello2")).toEqual("world2");
42
+
43
+ expect(
44
+ SyncMessagesLog.getMessages({
45
+ Group: group.core,
46
+ Map: map.core,
47
+ }),
48
+ ).toMatchInlineSnapshot(`
49
+ [
50
+ "client -> server | LOAD Map sessions: empty",
51
+ "server -> client | CONTENT Group header: true new: After: 0 New: 3",
52
+ "server -> client | CONTENT Map header: true new: After: 0 New: 2",
53
+ "client -> server | KNOWN Group sessions: header/3",
54
+ "client -> server | KNOWN Map sessions: header/2",
55
+ ]
56
+ `);
57
+ });
58
+
59
+ test("dependencies are excluded when responding to a server", async () => {
60
+ // Create a disconnected client
61
+ const { node: client, accountID } = await setupTestAccount({
62
+ connected: false,
63
+ });
64
+ const account = client.expectCurrentAccount(accountID);
65
+
66
+ // Prepare a group -- this will be a non-account dependency of a forthcoming map.
67
+ const group = client.createGroup();
68
+ group.addMember("everyone", "writer");
69
+
70
+ // Let the queue drain
71
+ await new Promise((resolve) => setTimeout(resolve, 1));
72
+
73
+ // Disable transaction verification on the server so it doesn't ask for dependencies.
74
+ jazzCloud.node.syncManager.disableTransactionVerification();
75
+
76
+ // Connect the client, but don't setup syncing just yet...
77
+ const { peer } = getSyncServerConnectedPeer({
78
+ peerId: client.getCurrentAgent().id,
79
+ syncServer: jazzCloud.node,
80
+ });
81
+
82
+ // Disable reconciliation while we setup syncing because we don't want the
83
+ // server to know about our forthcoming map's dependencies (group + account).
84
+ const blocker = blockMessageTypeOnOutgoingPeer(peer, "load", {});
85
+ client.syncManager.addPeer(peer);
86
+ blocker.unblock();
87
+
88
+ // Create a map and set a value on it, this will trigger:
89
+ // - CONTENT from client to server
90
+ // - KNOWN from server to client
91
+ //
92
+ // We don't expect any more messages to be sent from client to server in this
93
+ // case because clients shouldn't greedily send dependencies to a server.
94
+ const map = group.createMap();
95
+ await map.core.waitForSync();
96
+
97
+ const syncMessages = SyncMessagesLog.getMessages({
98
+ Account: account.core,
99
+ Group: group.core,
100
+ Map: map.core,
101
+ });
102
+ expect(
103
+ syncMessages.some(
104
+ (msg) =>
105
+ msg.includes("CONTENT Account") || msg.includes("CONTENT Group"),
106
+ ),
107
+ ).toBe(false);
108
+ });
109
+ });