cojson 0.8.38 → 0.8.41

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 (60) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/dist/native/PeerState.js +11 -2
  3. package/dist/native/PeerState.js.map +1 -1
  4. package/dist/native/{SyncStateSubscriptionManager.js → SyncStateManager.js} +35 -24
  5. package/dist/native/SyncStateManager.js.map +1 -0
  6. package/dist/native/coValueCore.js +25 -5
  7. package/dist/native/coValueCore.js.map +1 -1
  8. package/dist/native/coValues/coMap.js +98 -103
  9. package/dist/native/coValues/coMap.js.map +1 -1
  10. package/dist/native/coValues/coStream.js +17 -6
  11. package/dist/native/coValues/coStream.js.map +1 -1
  12. package/dist/native/coValues/group.js +127 -39
  13. package/dist/native/coValues/group.js.map +1 -1
  14. package/dist/native/exports.js.map +1 -1
  15. package/dist/native/localNode.js +5 -2
  16. package/dist/native/localNode.js.map +1 -1
  17. package/dist/native/permissions.js +51 -3
  18. package/dist/native/permissions.js.map +1 -1
  19. package/dist/native/sync.js +34 -10
  20. package/dist/native/sync.js.map +1 -1
  21. package/dist/web/PeerState.js +11 -2
  22. package/dist/web/PeerState.js.map +1 -1
  23. package/dist/web/{SyncStateSubscriptionManager.js → SyncStateManager.js} +35 -24
  24. package/dist/web/SyncStateManager.js.map +1 -0
  25. package/dist/web/coValueCore.js +25 -5
  26. package/dist/web/coValueCore.js.map +1 -1
  27. package/dist/web/coValues/coMap.js +98 -103
  28. package/dist/web/coValues/coMap.js.map +1 -1
  29. package/dist/web/coValues/coStream.js +17 -6
  30. package/dist/web/coValues/coStream.js.map +1 -1
  31. package/dist/web/coValues/group.js +127 -39
  32. package/dist/web/coValues/group.js.map +1 -1
  33. package/dist/web/exports.js.map +1 -1
  34. package/dist/web/localNode.js +5 -2
  35. package/dist/web/localNode.js.map +1 -1
  36. package/dist/web/permissions.js +51 -3
  37. package/dist/web/permissions.js.map +1 -1
  38. package/dist/web/sync.js +34 -10
  39. package/dist/web/sync.js.map +1 -1
  40. package/package.json +3 -5
  41. package/src/PeerState.ts +12 -2
  42. package/src/{SyncStateSubscriptionManager.ts → SyncStateManager.ts} +48 -35
  43. package/src/coValueCore.ts +43 -9
  44. package/src/coValues/coMap.ts +126 -127
  45. package/src/coValues/coStream.ts +27 -10
  46. package/src/coValues/group.ts +218 -50
  47. package/src/exports.ts +2 -1
  48. package/src/localNode.ts +5 -2
  49. package/src/permissions.ts +71 -8
  50. package/src/sync.ts +57 -23
  51. package/src/tests/PeerState.test.ts +49 -0
  52. package/src/tests/PriorityBasedMessageQueue.test.ts +6 -73
  53. package/src/tests/{SyncStateSubscriptionManager.test.ts → SyncStateManager.test.ts} +109 -25
  54. package/src/tests/coMap.test.ts +2 -2
  55. package/src/tests/group.test.ts +338 -47
  56. package/src/tests/permissions.test.ts +324 -0
  57. package/src/tests/sync.test.ts +112 -71
  58. package/src/tests/testUtils.ts +126 -17
  59. package/dist/native/SyncStateSubscriptionManager.js.map +0 -1
  60. package/dist/web/SyncStateSubscriptionManager.js.map +0 -1
@@ -1,4 +1,4 @@
1
- import { expect, test } from "vitest";
1
+ import { describe, expect, test } from "vitest";
2
2
  import { RawCoList } from "../coValues/coList.js";
3
3
  import { RawCoMap } from "../coValues/coMap.js";
4
4
  import { RawCoStream } from "../coValues/coStream.js";
@@ -7,6 +7,7 @@ import { WasmCrypto } from "../crypto/WasmCrypto.js";
7
7
  import { LocalNode } from "../localNode.js";
8
8
  import {
9
9
  createThreeConnectedNodes,
10
+ createTwoConnectedNodes,
10
11
  loadCoValueOrFail,
11
12
  randomAnonymousAccountAndSessionID,
12
13
  } from "./testUtils.js";
@@ -59,146 +60,436 @@ test("Can create a FileStream in a group", () => {
59
60
  });
60
61
 
61
62
  test("Remove a member from a group where the admin role is inherited", async () => {
62
- const { node1, node2, node3, node1ToNode2Peer, node2ToNode3Peer } =
63
- createThreeConnectedNodes("server", "server", "server");
63
+ const { node1, node2, node3 } = await createThreeConnectedNodes(
64
+ "server",
65
+ "server",
66
+ "server",
67
+ );
64
68
 
65
- const group = node1.createGroup();
69
+ const group = node1.node.createGroup();
70
+
71
+ group.addMember(
72
+ await loadCoValueOrFail(node1.node, node2.accountID),
73
+ "admin",
74
+ );
75
+ group.addMember(
76
+ await loadCoValueOrFail(node1.node, node3.accountID),
77
+ "reader",
78
+ );
66
79
 
67
- group.addMember(node2.account, "admin");
68
- group.addMember(node3.account, "reader");
80
+ await group.core.waitForSync();
69
81
 
70
- const groupOnNode2 = await loadCoValueOrFail(node2, group.id);
82
+ const groupOnNode2 = await loadCoValueOrFail(node2.node, group.id);
71
83
 
72
84
  // The account of node2 create a child group and extend the initial group
73
85
  // This way the node1 account should become "admin" of the child group
74
86
  // by inheriting the admin role from the initial group
75
- const childGroup = node2.createGroup();
87
+ const childGroup = node2.node.createGroup();
76
88
  childGroup.extend(groupOnNode2);
77
89
 
78
90
  const map = childGroup.createMap();
79
91
  map.set("test", "Available to everyone");
80
92
 
81
- const mapOnNode3 = await loadCoValueOrFail(node3, map.id);
93
+ const mapOnNode3 = await loadCoValueOrFail(node3.node, map.id);
82
94
 
83
95
  // Check that the sync between node2 and node3 worked
84
96
  expect(mapOnNode3.get("test")).toEqual("Available to everyone");
85
97
 
86
98
  // The node1 account removes the reader from the group
87
99
  // The reader should be automatically kicked out of the child group
88
- await group.removeMember(node3.account);
100
+ await group.removeMember(node3.node.account);
89
101
 
90
- await node1.syncManager.waitForUploadIntoPeer(node1ToNode2Peer.id, group.id);
102
+ await group.core.waitForSync();
91
103
 
92
104
  // Update the map to check that node3 can't read updates anymore
93
105
  map.set("test", "Hidden to node3");
94
106
 
95
- await node2.syncManager.waitForUploadIntoPeer(node2ToNode3Peer.id, map.id);
107
+ await map.core.waitForSync();
96
108
 
97
109
  // Check that the value has not been updated on node3
98
110
  expect(mapOnNode3.get("test")).toEqual("Available to everyone");
99
111
 
100
- const mapOnNode1 = await loadCoValueOrFail(node1, map.id);
112
+ const mapOnNode1 = await loadCoValueOrFail(node1.node, map.id);
101
113
 
102
114
  expect(mapOnNode1.get("test")).toEqual("Hidden to node3");
103
115
  });
104
116
 
105
117
  test("An admin should be able to rotate the readKey on child groups and keep access to new coValues", async () => {
106
- const { node1, node2, node3, node1ToNode2Peer, node2ToNode1Peer } =
107
- createThreeConnectedNodes("server", "server", "server");
118
+ const { node1, node2, node3 } = await createThreeConnectedNodes(
119
+ "server",
120
+ "server",
121
+ "server",
122
+ );
108
123
 
109
- const group = node1.createGroup();
124
+ const group = node1.node.createGroup();
125
+
126
+ group.addMember(
127
+ await loadCoValueOrFail(node1.node, node2.accountID),
128
+ "admin",
129
+ );
130
+ group.addMember(
131
+ await loadCoValueOrFail(node1.node, node3.accountID),
132
+ "reader",
133
+ );
110
134
 
111
- group.addMember(node2.account, "admin");
112
- group.addMember(node3.account, "reader");
135
+ await group.core.waitForSync();
113
136
 
114
- const groupOnNode2 = await loadCoValueOrFail(node2, group.id);
137
+ const groupOnNode2 = await loadCoValueOrFail(node2.node, group.id);
115
138
 
116
139
  // The account of node2 create a child group and extend the initial group
117
140
  // This way the node1 account should become "admin" of the child group
118
141
  // by inheriting the admin role from the initial group
119
- const childGroup = node2.createGroup();
142
+ const childGroup = node2.node.createGroup();
120
143
  childGroup.extend(groupOnNode2);
121
144
 
122
- await node2.syncManager.waitForUploadIntoPeer(
123
- node2ToNode1Peer.id,
124
- childGroup.id,
125
- );
145
+ await childGroup.core.waitForSync();
126
146
 
127
147
  // The node1 account removes the reader from the group
128
148
  // In this case we want to ensure that node1 is still able to read new coValues
129
149
  // Even if some childs are not available when the readKey is rotated
130
- await group.removeMember(node3.account);
131
- await node1.syncManager.waitForUploadIntoPeer(node1ToNode2Peer.id, group.id);
150
+ await group.removeMember(node3.node.account);
151
+ await group.core.waitForSync();
132
152
 
133
153
  const map = childGroup.createMap();
134
154
  map.set("test", "Available to node1");
135
155
 
136
- const mapOnNode1 = await loadCoValueOrFail(node1, map.id);
156
+ const mapOnNode1 = await loadCoValueOrFail(node1.node, map.id);
137
157
  expect(mapOnNode1.get("test")).toEqual("Available to node1");
138
158
  });
139
159
 
140
160
  test("An admin should be able to rotate the readKey on child groups even if it was unavailable when kicking out a member from a parent group", async () => {
141
- const { node1, node2, node3, node1ToNode2Peer, node2ToNode1Peer } =
142
- createThreeConnectedNodes("server", "server", "server");
161
+ const { node1, node2, node3 } = await createThreeConnectedNodes(
162
+ "server",
163
+ "server",
164
+ "server",
165
+ );
143
166
 
144
- const group = node1.createGroup();
167
+ const group = node1.node.createGroup();
145
168
 
146
- group.addMember(node2.account, "admin");
147
- group.addMember(node3.account, "reader");
169
+ group.addMember(
170
+ await loadCoValueOrFail(node1.node, node2.accountID),
171
+ "admin",
172
+ );
173
+ group.addMember(
174
+ await loadCoValueOrFail(node1.node, node3.accountID),
175
+ "reader",
176
+ );
148
177
 
149
- const groupOnNode2 = await loadCoValueOrFail(node2, group.id);
178
+ await group.core.waitForSync();
179
+
180
+ const groupOnNode2 = await loadCoValueOrFail(node2.node, group.id);
150
181
 
151
182
  // The account of node2 create a child group and extend the initial group
152
183
  // This way the node1 account should become "admin" of the child group
153
184
  // by inheriting the admin role from the initial group
154
- const childGroup = node2.createGroup();
185
+ const childGroup = node2.node.createGroup();
155
186
  childGroup.extend(groupOnNode2);
156
187
 
157
188
  // The node1 account removes the reader from the group
158
189
  // In this case we want to ensure that node1 is still able to read new coValues
159
190
  // Even if some childs are not available when the readKey is rotated
160
- await group.removeMember(node3.account);
161
- await node1.syncManager.waitForUploadIntoPeer(node1ToNode2Peer.id, group.id);
191
+ await group.removeMember(node3.node.account);
192
+ await group.core.waitForSync();
162
193
 
163
194
  const map = childGroup.createMap();
164
195
  map.set("test", "Available to node1");
165
196
 
166
- const mapOnNode1 = await loadCoValueOrFail(node1, map.id);
197
+ const mapOnNode1 = await loadCoValueOrFail(node1.node, map.id);
167
198
  expect(mapOnNode1.get("test")).toEqual("Available to node1");
168
199
  });
169
200
 
170
201
  test("An admin should be able to rotate the readKey on child groups even if it was unavailable when kicking out a member from a parent group (grandChild)", async () => {
171
- const { node1, node2, node3, node1ToNode2Peer } = createThreeConnectedNodes(
202
+ const { node1, node2, node3 } = await createThreeConnectedNodes(
172
203
  "server",
173
204
  "server",
174
205
  "server",
175
206
  );
176
207
 
177
- const group = node1.createGroup();
208
+ const group = node1.node.createGroup();
209
+
210
+ group.addMember(
211
+ await loadCoValueOrFail(node1.node, node2.accountID),
212
+ "admin",
213
+ );
214
+ group.addMember(
215
+ await loadCoValueOrFail(node1.node, node3.accountID),
216
+ "reader",
217
+ );
178
218
 
179
- group.addMember(node2.account, "admin");
180
- group.addMember(node3.account, "reader");
219
+ await group.core.waitForSync();
181
220
 
182
- const groupOnNode2 = await loadCoValueOrFail(node2, group.id);
221
+ const groupOnNode2 = await loadCoValueOrFail(node2.node, group.id);
183
222
 
184
223
  // The account of node2 create a child group and extend the initial group
185
224
  // This way the node1 account should become "admin" of the child group
186
225
  // by inheriting the admin role from the initial group
187
- const childGroup = node2.createGroup();
226
+ const childGroup = node2.node.createGroup();
188
227
  childGroup.extend(groupOnNode2);
189
- const grandChildGroup = node2.createGroup();
228
+ const grandChildGroup = node2.node.createGroup();
190
229
  grandChildGroup.extend(childGroup);
191
230
 
192
231
  // The node1 account removes the reader from the group
193
232
  // In this case we want to ensure that node1 is still able to read new coValues
194
233
  // Even if some childs are not available when the readKey is rotated
195
- await group.removeMember(node3.account);
196
- await node1.syncManager.waitForUploadIntoPeer(node1ToNode2Peer.id, group.id);
234
+ await group.removeMember(node3.node.account);
235
+ await group.core.waitForSync();
197
236
 
198
237
  const map = childGroup.createMap();
199
238
  map.set("test", "Available to node1");
200
239
 
201
- const mapOnNode1 = await loadCoValueOrFail(node1, map.id);
240
+ const mapOnNode1 = await loadCoValueOrFail(node1.node, map.id);
202
241
 
203
242
  expect(mapOnNode1.get("test")).toEqual("Available to node1");
204
243
  });
244
+
245
+ test("A user add after a key rotation should have access to the old transactions", async () => {
246
+ const { node1, node2, node3 } = await createThreeConnectedNodes(
247
+ "server",
248
+ "server",
249
+ "server",
250
+ );
251
+
252
+ const group = node1.node.createGroup();
253
+
254
+ group.addMember(
255
+ await loadCoValueOrFail(node1.node, node2.accountID),
256
+ "writer",
257
+ );
258
+
259
+ await group.core.waitForSync();
260
+
261
+ const groupOnNode2 = await loadCoValueOrFail(node2.node, group.id);
262
+
263
+ const map = groupOnNode2.createMap();
264
+ map.set("test", "Written from node2");
265
+
266
+ await map.core.waitForSync();
267
+
268
+ await group.removeMember(node3.node.account);
269
+ group.addMember(
270
+ await loadCoValueOrFail(node1.node, node3.accountID),
271
+ "reader",
272
+ );
273
+
274
+ await group.core.waitForSync();
275
+
276
+ const mapOnNode3 = await loadCoValueOrFail(node3.node, map.id);
277
+ expect(mapOnNode3.get("test")).toEqual("Written from node2");
278
+ });
279
+
280
+ test("Invites should have access to the new keys", async () => {
281
+ const { node1, node2, node3 } = await createThreeConnectedNodes(
282
+ "server",
283
+ "server",
284
+ "server",
285
+ );
286
+
287
+ const group = node1.node.createGroup();
288
+ group.addMember(
289
+ await loadCoValueOrFail(node1.node, node3.accountID),
290
+ "reader",
291
+ );
292
+
293
+ const invite = group.createInvite("admin");
294
+
295
+ await group.removeMember(node3.node.account);
296
+
297
+ const map = group.createMap();
298
+ map.set("test", "Written from node1");
299
+
300
+ await map.core.waitForSync();
301
+
302
+ await node2.node.acceptInvite(group.id, invite);
303
+
304
+ const mapOnNode2 = await loadCoValueOrFail(node2.node, map.id);
305
+ expect(mapOnNode2.get("test")).toEqual("Written from node1");
306
+ });
307
+
308
+ describe("writeOnly", () => {
309
+ test("Admins can invite writeOnly members", async () => {
310
+ const { node1, node2 } = await createTwoConnectedNodes("server", "server");
311
+
312
+ const group = node1.node.createGroup();
313
+
314
+ const invite = group.createInvite("writeOnly");
315
+
316
+ await node2.node.acceptInvite(group.id, invite);
317
+
318
+ const groupOnNode2 = await loadCoValueOrFail(node2.node, group.id);
319
+ expect(groupOnNode2.myRole()).toEqual("writeOnly");
320
+ });
321
+
322
+ test("writeOnly roles are not inherited", async () => {
323
+ const { node1, node2 } = await createTwoConnectedNodes("server", "server");
324
+
325
+ const group = node1.node.createGroup();
326
+ group.addMember(
327
+ await loadCoValueOrFail(node1.node, node2.accountID),
328
+ "writeOnly",
329
+ );
330
+
331
+ const childGroup = node1.node.createGroup();
332
+ childGroup.extend(group);
333
+ expect(childGroup.roleOf(node2.accountID)).toEqual(undefined);
334
+ });
335
+
336
+ test("writeOnly roles are not overridded by reader roles", async () => {
337
+ const { node1, node2 } = await createTwoConnectedNodes("server", "server");
338
+
339
+ const group = node1.node.createGroup();
340
+ group.addMember(
341
+ await loadCoValueOrFail(node1.node, node2.accountID),
342
+ "reader",
343
+ );
344
+
345
+ const childGroup = node1.node.createGroup();
346
+ childGroup.extend(group);
347
+ childGroup.addMember(
348
+ await loadCoValueOrFail(node1.node, node2.accountID),
349
+ "writeOnly",
350
+ );
351
+
352
+ expect(childGroup.roleOf(node2.accountID)).toEqual("writeOnly");
353
+ });
354
+
355
+ test("writeOnly roles are overridded by writer roles", async () => {
356
+ const { node1, node2 } = await createTwoConnectedNodes("server", "server");
357
+
358
+ const group = node1.node.createGroup();
359
+ group.addMember(
360
+ await loadCoValueOrFail(node1.node, node2.accountID),
361
+ "writer",
362
+ );
363
+
364
+ const childGroup = node1.node.createGroup();
365
+ childGroup.extend(group);
366
+ childGroup.addMember(
367
+ await loadCoValueOrFail(node1.node, node2.accountID),
368
+ "writeOnly",
369
+ );
370
+
371
+ expect(childGroup.roleOf(node2.accountID)).toEqual("writer");
372
+ });
373
+
374
+ test("Edits by writeOnly members are visible to other members", async () => {
375
+ const { node1, node2, node3 } = await createThreeConnectedNodes(
376
+ "server",
377
+ "server",
378
+ "server",
379
+ );
380
+
381
+ const group = node1.node.createGroup();
382
+
383
+ group.addMember(
384
+ await loadCoValueOrFail(node1.node, node2.accountID),
385
+ "writeOnly",
386
+ );
387
+ group.addMember(
388
+ await loadCoValueOrFail(node1.node, node3.accountID),
389
+ "reader",
390
+ );
391
+
392
+ await group.core.waitForSync();
393
+
394
+ const groupOnNode2 = await loadCoValueOrFail(node2.node, group.id);
395
+ const map = groupOnNode2.createMap();
396
+
397
+ map.set("test", "Written from a writeOnly member");
398
+ expect(map.get("test")).toEqual("Written from a writeOnly member");
399
+
400
+ await map.core.waitForSync();
401
+
402
+ const mapOnNode1 = await loadCoValueOrFail(node1.node, map.id);
403
+ expect(mapOnNode1.get("test")).toEqual("Written from a writeOnly member");
404
+
405
+ const mapOnNode3 = await loadCoValueOrFail(node3.node, map.id);
406
+ expect(mapOnNode3.get("test")).toEqual("Written from a writeOnly member");
407
+ });
408
+
409
+ test("Edits by other members are not visible to writeOnly members", async () => {
410
+ const { node1, node2 } = await createTwoConnectedNodes("server", "server");
411
+
412
+ const group = node1.node.createGroup();
413
+
414
+ group.addMember(
415
+ await loadCoValueOrFail(node1.node, node2.accountID),
416
+ "writeOnly",
417
+ );
418
+ const map = group.createMap();
419
+ map.set("test", "Written from the admin");
420
+
421
+ await map.core.waitForSync();
422
+
423
+ const mapOnNode2 = await loadCoValueOrFail(node2.node, map.id);
424
+ expect(mapOnNode2.get("test")).toEqual(undefined);
425
+ });
426
+
427
+ test("Write only member keys are rotated when a member is kicked out", async () => {
428
+ const { node1, node2, node3 } = await createThreeConnectedNodes(
429
+ "server",
430
+ "server",
431
+ "server",
432
+ );
433
+
434
+ const group = node1.node.createGroup();
435
+
436
+ group.addMember(
437
+ await loadCoValueOrFail(node1.node, node2.accountID),
438
+ "writeOnly",
439
+ );
440
+ group.addMember(
441
+ await loadCoValueOrFail(node1.node, node3.accountID),
442
+ "reader",
443
+ );
444
+
445
+ await group.core.waitForSync();
446
+
447
+ const groupOnNode2 = await loadCoValueOrFail(node2.node, group.id);
448
+ const map = groupOnNode2.createMap();
449
+
450
+ map.set("test", "Written from a writeOnly member");
451
+
452
+ await map.core.waitForSync();
453
+
454
+ await group.removeMember(node3.node.account);
455
+
456
+ await group.core.waitForSync();
457
+
458
+ map.set("test", "Updated after key rotation");
459
+
460
+ await map.core.waitForSync();
461
+
462
+ const mapOnNode1 = await loadCoValueOrFail(node1.node, map.id);
463
+ expect(mapOnNode1.get("test")).toEqual("Updated after key rotation");
464
+
465
+ const mapOnNode3 = await loadCoValueOrFail(node3.node, map.id);
466
+ expect(mapOnNode3.get("test")).toEqual("Written from a writeOnly member");
467
+ });
468
+
469
+ test("inherited writer roles should work correctly", async () => {
470
+ const { node1, node2 } = await createTwoConnectedNodes("server", "server");
471
+
472
+ const group = node1.node.createGroup();
473
+ group.addMember(
474
+ await loadCoValueOrFail(node1.node, node2.accountID),
475
+ "writer",
476
+ );
477
+
478
+ const childGroup = node1.node.createGroup();
479
+ childGroup.extend(group);
480
+ childGroup.addMember(
481
+ await loadCoValueOrFail(node1.node, node2.accountID),
482
+ "writeOnly",
483
+ );
484
+
485
+ const map = childGroup.createMap();
486
+ map.set("test", "Written from the admin");
487
+
488
+ await map.core.waitForSync();
489
+
490
+ const mapOnNode2 = await loadCoValueOrFail(node2.node, map.id);
491
+
492
+ // The writer role should be able to see the edits from the admin
493
+ expect(mapOnNode2.get("test")).toEqual("Written from the admin");
494
+ });
495
+ });