cojson 0.8.34 → 0.8.36
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/CHANGELOG.md +15 -0
- package/dist/native/coValueCore.js +73 -37
- package/dist/native/coValueCore.js.map +1 -1
- package/dist/native/coValues/coMap.js +82 -33
- package/dist/native/coValues/coMap.js.map +1 -1
- package/dist/native/coValues/group.js +132 -5
- package/dist/native/coValues/group.js.map +1 -1
- package/dist/native/exports.js +5 -2
- package/dist/native/exports.js.map +1 -1
- package/dist/native/ids.js +33 -0
- package/dist/native/ids.js.map +1 -1
- package/dist/native/permissions.js +216 -154
- package/dist/native/permissions.js.map +1 -1
- package/dist/native/storage/index.js +8 -4
- package/dist/native/storage/index.js.map +1 -1
- package/dist/native/sync.js +41 -25
- package/dist/native/sync.js.map +1 -1
- package/dist/web/coValueCore.js +73 -37
- package/dist/web/coValueCore.js.map +1 -1
- package/dist/web/coValues/coMap.js +82 -33
- package/dist/web/coValues/coMap.js.map +1 -1
- package/dist/web/coValues/group.js +132 -5
- package/dist/web/coValues/group.js.map +1 -1
- package/dist/web/exports.js +5 -2
- package/dist/web/exports.js.map +1 -1
- package/dist/web/ids.js +33 -0
- package/dist/web/ids.js.map +1 -1
- package/dist/web/permissions.js +216 -154
- package/dist/web/permissions.js.map +1 -1
- package/dist/web/storage/index.js +8 -4
- package/dist/web/storage/index.js.map +1 -1
- package/dist/web/sync.js +41 -25
- package/dist/web/sync.js.map +1 -1
- package/package.json +1 -1
- package/src/coValueCore.ts +120 -47
- package/src/coValues/coMap.ts +116 -43
- package/src/coValues/group.ts +219 -6
- package/src/exports.ts +18 -3
- package/src/ids.ts +48 -0
- package/src/permissions.ts +314 -220
- package/src/storage/index.ts +12 -4
- package/src/sync.ts +43 -26
- package/src/tests/group.test.ts +152 -1
- package/src/tests/permissions.test.ts +785 -2
- package/src/tests/sync.test.ts +29 -0
- package/src/tests/testUtils.ts +102 -1
package/src/sync.ts
CHANGED
|
@@ -304,14 +304,19 @@ export class SyncManager {
|
|
|
304
304
|
|
|
305
305
|
if (peerState.isServerOrStoragePeer()) {
|
|
306
306
|
const initialSync = async () => {
|
|
307
|
-
for (const
|
|
308
|
-
|
|
309
|
-
await this.subscribeToIncludingDependencies(id, peerState);
|
|
307
|
+
for (const entry of this.local.coValuesStore.getValues()) {
|
|
308
|
+
await this.subscribeToIncludingDependencies(entry.id, peerState);
|
|
310
309
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
310
|
+
if (entry.state.type === "available") {
|
|
311
|
+
await this.sendNewContentIncludingDependencies(entry.id, peerState);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
if (!peerState.optimisticKnownStates.has(entry.id)) {
|
|
315
|
+
peerState.optimisticKnownStates.dispatch({
|
|
316
|
+
type: "SET_AS_EMPTY",
|
|
317
|
+
id: entry.id,
|
|
318
|
+
});
|
|
319
|
+
}
|
|
315
320
|
}
|
|
316
321
|
};
|
|
317
322
|
void initialSync();
|
|
@@ -403,27 +408,39 @@ export class SyncManager {
|
|
|
403
408
|
}
|
|
404
409
|
|
|
405
410
|
if (entry.state.type === "loading") {
|
|
406
|
-
|
|
411
|
+
// We need to return from handleLoad immediately and wait for the CoValue to be loaded
|
|
412
|
+
// in a new task, otherwise we might block further incoming content messages that would
|
|
413
|
+
// resolve the CoValue as available. This can happen when we receive fresh
|
|
414
|
+
// content from a client, but we are a server with our own upstream server(s)
|
|
415
|
+
entry
|
|
416
|
+
.getCoValue()
|
|
417
|
+
.then(async (value) => {
|
|
418
|
+
if (value === "unavailable") {
|
|
419
|
+
peer.dispatchToKnownStates({
|
|
420
|
+
type: "SET",
|
|
421
|
+
id: msg.id,
|
|
422
|
+
value: knownStateIn(msg),
|
|
423
|
+
});
|
|
424
|
+
peer.toldKnownState.add(msg.id);
|
|
425
|
+
|
|
426
|
+
this.trySendToPeer(peer, {
|
|
427
|
+
action: "known",
|
|
428
|
+
id: msg.id,
|
|
429
|
+
header: false,
|
|
430
|
+
sessions: {},
|
|
431
|
+
}).catch((e) => {
|
|
432
|
+
console.error("Error sending known state back", e);
|
|
433
|
+
});
|
|
407
434
|
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
type: "SET",
|
|
411
|
-
id: msg.id,
|
|
412
|
-
value: knownStateIn(msg),
|
|
413
|
-
});
|
|
414
|
-
peer.toldKnownState.add(msg.id);
|
|
415
|
-
|
|
416
|
-
this.trySendToPeer(peer, {
|
|
417
|
-
action: "known",
|
|
418
|
-
id: msg.id,
|
|
419
|
-
header: false,
|
|
420
|
-
sessions: {},
|
|
421
|
-
}).catch((e) => {
|
|
422
|
-
console.error("Error sending known state back", e);
|
|
423
|
-
});
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
424
437
|
|
|
425
|
-
|
|
426
|
-
|
|
438
|
+
await this.tellUntoldKnownStateIncludingDependencies(msg.id, peer);
|
|
439
|
+
await this.sendNewContentIncludingDependencies(msg.id, peer);
|
|
440
|
+
})
|
|
441
|
+
.catch((e) => {
|
|
442
|
+
console.error("Error loading coValue in handleLoad loading state", e);
|
|
443
|
+
});
|
|
427
444
|
}
|
|
428
445
|
|
|
429
446
|
if (entry.state.type === "available") {
|
package/src/tests/group.test.ts
CHANGED
|
@@ -5,7 +5,13 @@ import { RawCoStream } from "../coValues/coStream.js";
|
|
|
5
5
|
import { RawBinaryCoStream } from "../coValues/coStream.js";
|
|
6
6
|
import { WasmCrypto } from "../crypto/WasmCrypto.js";
|
|
7
7
|
import { LocalNode } from "../localNode.js";
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
createThreeConnectedNodes,
|
|
10
|
+
createTwoConnectedNodes,
|
|
11
|
+
loadCoValueOrFail,
|
|
12
|
+
randomAnonymousAccountAndSessionID,
|
|
13
|
+
waitFor,
|
|
14
|
+
} from "./testUtils.js";
|
|
9
15
|
|
|
10
16
|
const Crypto = await WasmCrypto.create();
|
|
11
17
|
|
|
@@ -53,3 +59,148 @@ test("Can create a FileStream in a group", () => {
|
|
|
53
59
|
expect(stream.headerMeta.type).toEqual("binary");
|
|
54
60
|
expect(stream instanceof RawBinaryCoStream).toEqual(true);
|
|
55
61
|
});
|
|
62
|
+
|
|
63
|
+
test("Remove a member from a group where the admin role is inherited", async () => {
|
|
64
|
+
const { node1, node2, node3, node1ToNode2Peer, node2ToNode3Peer } =
|
|
65
|
+
createThreeConnectedNodes("server", "server", "server");
|
|
66
|
+
|
|
67
|
+
const group = node1.createGroup();
|
|
68
|
+
|
|
69
|
+
group.addMember(node2.account, "admin");
|
|
70
|
+
group.addMember(node3.account, "reader");
|
|
71
|
+
|
|
72
|
+
const groupOnNode2 = await loadCoValueOrFail(node2, group.id);
|
|
73
|
+
|
|
74
|
+
// The account of node2 create a child group and extend the initial group
|
|
75
|
+
// This way the node1 account should become "admin" of the child group
|
|
76
|
+
// by inheriting the admin role from the initial group
|
|
77
|
+
const childGroup = node2.createGroup();
|
|
78
|
+
childGroup.extend(groupOnNode2);
|
|
79
|
+
|
|
80
|
+
const map = childGroup.createMap();
|
|
81
|
+
map.set("test", "Available to everyone");
|
|
82
|
+
|
|
83
|
+
const mapOnNode3 = await loadCoValueOrFail(node3, map.id);
|
|
84
|
+
|
|
85
|
+
// Check that the sync between node2 and node3 worked
|
|
86
|
+
expect(mapOnNode3.get("test")).toEqual("Available to everyone");
|
|
87
|
+
|
|
88
|
+
// The node1 account removes the reader from the group
|
|
89
|
+
// The reader should be automatically kicked out of the child group
|
|
90
|
+
await group.removeMember(node3.account);
|
|
91
|
+
|
|
92
|
+
await node1.syncManager.waitForUploadIntoPeer(node1ToNode2Peer.id, group.id);
|
|
93
|
+
|
|
94
|
+
// Update the map to check that node3 can't read updates anymore
|
|
95
|
+
map.set("test", "Hidden to node3");
|
|
96
|
+
|
|
97
|
+
await node2.syncManager.waitForUploadIntoPeer(node2ToNode3Peer.id, map.id);
|
|
98
|
+
|
|
99
|
+
// Check that the value has not been updated on node3
|
|
100
|
+
expect(mapOnNode3.get("test")).toEqual("Available to everyone");
|
|
101
|
+
|
|
102
|
+
const mapOnNode1 = await loadCoValueOrFail(node1, map.id);
|
|
103
|
+
|
|
104
|
+
expect(mapOnNode1.get("test")).toEqual("Hidden to node3");
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test("An admin should be able to rotate the readKey on child groups and keep access to new coValues", async () => {
|
|
108
|
+
const { node1, node2, node3, node1ToNode2Peer, node2ToNode1Peer } =
|
|
109
|
+
createThreeConnectedNodes("server", "server", "server");
|
|
110
|
+
|
|
111
|
+
const group = node1.createGroup();
|
|
112
|
+
|
|
113
|
+
group.addMember(node2.account, "admin");
|
|
114
|
+
group.addMember(node3.account, "reader");
|
|
115
|
+
|
|
116
|
+
const groupOnNode2 = await loadCoValueOrFail(node2, group.id);
|
|
117
|
+
|
|
118
|
+
// The account of node2 create a child group and extend the initial group
|
|
119
|
+
// This way the node1 account should become "admin" of the child group
|
|
120
|
+
// by inheriting the admin role from the initial group
|
|
121
|
+
const childGroup = node2.createGroup();
|
|
122
|
+
childGroup.extend(groupOnNode2);
|
|
123
|
+
|
|
124
|
+
await node2.syncManager.waitForUploadIntoPeer(
|
|
125
|
+
node2ToNode1Peer.id,
|
|
126
|
+
childGroup.id,
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
// The node1 account removes the reader from the group
|
|
130
|
+
// In this case we want to ensure that node1 is still able to read new coValues
|
|
131
|
+
// Even if some childs are not available when the readKey is rotated
|
|
132
|
+
await group.removeMember(node3.account);
|
|
133
|
+
await node1.syncManager.waitForUploadIntoPeer(node1ToNode2Peer.id, group.id);
|
|
134
|
+
|
|
135
|
+
const map = childGroup.createMap();
|
|
136
|
+
map.set("test", "Available to node1");
|
|
137
|
+
|
|
138
|
+
const mapOnNode1 = await loadCoValueOrFail(node1, map.id);
|
|
139
|
+
expect(mapOnNode1.get("test")).toEqual("Available to node1");
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
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 () => {
|
|
143
|
+
const { node1, node2, node3, node1ToNode2Peer, node2ToNode1Peer } =
|
|
144
|
+
createThreeConnectedNodes("server", "server", "server");
|
|
145
|
+
|
|
146
|
+
const group = node1.createGroup();
|
|
147
|
+
|
|
148
|
+
group.addMember(node2.account, "admin");
|
|
149
|
+
group.addMember(node3.account, "reader");
|
|
150
|
+
|
|
151
|
+
const groupOnNode2 = await loadCoValueOrFail(node2, group.id);
|
|
152
|
+
|
|
153
|
+
// The account of node2 create a child group and extend the initial group
|
|
154
|
+
// This way the node1 account should become "admin" of the child group
|
|
155
|
+
// by inheriting the admin role from the initial group
|
|
156
|
+
const childGroup = node2.createGroup();
|
|
157
|
+
childGroup.extend(groupOnNode2);
|
|
158
|
+
|
|
159
|
+
// The node1 account removes the reader from the group
|
|
160
|
+
// In this case we want to ensure that node1 is still able to read new coValues
|
|
161
|
+
// Even if some childs are not available when the readKey is rotated
|
|
162
|
+
await group.removeMember(node3.account);
|
|
163
|
+
await node1.syncManager.waitForUploadIntoPeer(node1ToNode2Peer.id, group.id);
|
|
164
|
+
|
|
165
|
+
const map = childGroup.createMap();
|
|
166
|
+
map.set("test", "Available to node1");
|
|
167
|
+
|
|
168
|
+
const mapOnNode1 = await loadCoValueOrFail(node1, map.id);
|
|
169
|
+
expect(mapOnNode1.get("test")).toEqual("Available to node1");
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
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 () => {
|
|
173
|
+
const { node1, node2, node3, node1ToNode2Peer } = createThreeConnectedNodes(
|
|
174
|
+
"server",
|
|
175
|
+
"server",
|
|
176
|
+
"server",
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
const group = node1.createGroup();
|
|
180
|
+
|
|
181
|
+
group.addMember(node2.account, "admin");
|
|
182
|
+
group.addMember(node3.account, "reader");
|
|
183
|
+
|
|
184
|
+
const groupOnNode2 = await loadCoValueOrFail(node2, group.id);
|
|
185
|
+
|
|
186
|
+
// The account of node2 create a child group and extend the initial group
|
|
187
|
+
// This way the node1 account should become "admin" of the child group
|
|
188
|
+
// by inheriting the admin role from the initial group
|
|
189
|
+
const childGroup = node2.createGroup();
|
|
190
|
+
childGroup.extend(groupOnNode2);
|
|
191
|
+
const grandChildGroup = node2.createGroup();
|
|
192
|
+
grandChildGroup.extend(childGroup);
|
|
193
|
+
|
|
194
|
+
// The node1 account removes the reader from the group
|
|
195
|
+
// In this case we want to ensure that node1 is still able to read new coValues
|
|
196
|
+
// Even if some childs are not available when the readKey is rotated
|
|
197
|
+
await group.removeMember(node3.account);
|
|
198
|
+
await node1.syncManager.waitForUploadIntoPeer(node1ToNode2Peer.id, group.id);
|
|
199
|
+
|
|
200
|
+
const map = childGroup.createMap();
|
|
201
|
+
map.set("test", "Available to node1");
|
|
202
|
+
|
|
203
|
+
const mapOnNode1 = await loadCoValueOrFail(node1, map.id);
|
|
204
|
+
|
|
205
|
+
expect(mapOnNode1.get("test")).toEqual("Available to node1");
|
|
206
|
+
});
|