cojson 0.8.19-group-inheritance.0 → 0.8.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -2
- package/dist/native/PeerState.js +5 -0
- package/dist/native/PeerState.js.map +1 -1
- package/dist/native/coValueCore.js +3 -34
- package/dist/native/coValueCore.js.map +1 -1
- package/dist/native/coValues/group.js +3 -89
- package/dist/native/coValues/group.js.map +1 -1
- package/dist/native/permissions.js +145 -174
- package/dist/native/permissions.js.map +1 -1
- package/dist/native/storage/index.js +4 -8
- package/dist/native/storage/index.js.map +1 -1
- package/dist/native/sync.js +17 -11
- package/dist/native/sync.js.map +1 -1
- package/dist/web/PeerState.js +5 -0
- package/dist/web/PeerState.js.map +1 -1
- package/dist/web/coValueCore.js +3 -34
- package/dist/web/coValueCore.js.map +1 -1
- package/dist/web/coValues/group.js +3 -89
- package/dist/web/coValues/group.js.map +1 -1
- package/dist/web/permissions.js +145 -174
- package/dist/web/permissions.js.map +1 -1
- package/dist/web/storage/index.js +4 -8
- package/dist/web/storage/index.js.map +1 -1
- package/dist/web/sync.js +17 -11
- package/dist/web/sync.js.map +1 -1
- package/package.json +1 -1
- package/src/PeerKnownStates.ts +1 -1
- package/src/PeerState.ts +9 -1
- package/src/coValueCore.ts +4 -50
- package/src/coValues/group.ts +4 -159
- package/src/permissions.ts +203 -244
- package/src/storage/index.ts +4 -12
- package/src/sync.ts +20 -12
- package/src/tests/PeerState.test.ts +44 -1
- package/src/tests/permissions.test.ts +0 -748
- package/src/tests/sync.test.ts +17 -10
- package/.turbo/turbo-build.log +0 -12
- package/.turbo/turbo-lint.log +0 -4
- package/.turbo/turbo-test.log +0 -1001
package/src/permissions.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { CoID } from "./coValue.js";
|
|
|
2
2
|
import { CoValueCore, Transaction } from "./coValueCore.js";
|
|
3
3
|
import { RawAccount, RawAccountID, RawProfile } from "./coValues/account.js";
|
|
4
4
|
import { MapOpPayload } from "./coValues/coMap.js";
|
|
5
|
-
import { EVERYONE, Everyone
|
|
5
|
+
import { EVERYONE, Everyone } from "./coValues/group.js";
|
|
6
6
|
import { KeyID } from "./crypto/crypto.js";
|
|
7
7
|
import { AgentID, RawCoID, SessionID, TransactionID } from "./ids.js";
|
|
8
8
|
import { parseJSON } from "./jsonStringify.js";
|
|
@@ -28,12 +28,198 @@ export function determineValidTransactions(
|
|
|
28
28
|
coValue: CoValueCore,
|
|
29
29
|
): { txID: TransactionID; tx: Transaction }[] {
|
|
30
30
|
if (coValue.header.ruleset.type === "group") {
|
|
31
|
+
const allTransactionsSorted = [...coValue.sessionLogs.entries()].flatMap(
|
|
32
|
+
([sessionID, sessionLog]) => {
|
|
33
|
+
return sessionLog.transactions.map((tx, txIndex) => ({
|
|
34
|
+
sessionID,
|
|
35
|
+
txIndex,
|
|
36
|
+
tx,
|
|
37
|
+
})) as {
|
|
38
|
+
sessionID: SessionID;
|
|
39
|
+
txIndex: number;
|
|
40
|
+
tx: Transaction;
|
|
41
|
+
}[];
|
|
42
|
+
},
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
allTransactionsSorted.sort((a, b) => {
|
|
46
|
+
return a.tx.madeAt - b.tx.madeAt;
|
|
47
|
+
});
|
|
48
|
+
|
|
31
49
|
const initialAdmin = coValue.header.ruleset.initialAdmin;
|
|
50
|
+
|
|
32
51
|
if (!initialAdmin) {
|
|
33
52
|
throw new Error("Group must have initialAdmin");
|
|
34
53
|
}
|
|
35
54
|
|
|
36
|
-
|
|
55
|
+
const memberState: {
|
|
56
|
+
[agent: RawAccountID | AgentID]: Role;
|
|
57
|
+
[EVERYONE]?: Role;
|
|
58
|
+
} = {};
|
|
59
|
+
|
|
60
|
+
const validTransactions: { txID: TransactionID; tx: Transaction }[] = [];
|
|
61
|
+
|
|
62
|
+
for (const { sessionID, txIndex, tx } of allTransactionsSorted) {
|
|
63
|
+
// console.log("before", { memberState, validTransactions });
|
|
64
|
+
const transactor = accountOrAgentIDfromSessionID(sessionID);
|
|
65
|
+
|
|
66
|
+
if (tx.privacy === "private") {
|
|
67
|
+
if (memberState[transactor] === "admin") {
|
|
68
|
+
validTransactions.push({
|
|
69
|
+
txID: { sessionID, txIndex },
|
|
70
|
+
tx,
|
|
71
|
+
});
|
|
72
|
+
continue;
|
|
73
|
+
} else {
|
|
74
|
+
console.warn("Only admins can make private transactions in groups");
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
let changes;
|
|
80
|
+
|
|
81
|
+
try {
|
|
82
|
+
changes = parseJSON(tx.changes);
|
|
83
|
+
} catch (e) {
|
|
84
|
+
console.warn(
|
|
85
|
+
coValue.id,
|
|
86
|
+
"Invalid JSON in transaction",
|
|
87
|
+
e,
|
|
88
|
+
tx,
|
|
89
|
+
JSON.stringify(tx.changes, (k, v) =>
|
|
90
|
+
k === "changes" || k === "encryptedChanges"
|
|
91
|
+
? v.slice(0, 20) + "..."
|
|
92
|
+
: v,
|
|
93
|
+
),
|
|
94
|
+
);
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const change = changes[0] as
|
|
99
|
+
| MapOpPayload<RawAccountID | AgentID | Everyone, Role>
|
|
100
|
+
| MapOpPayload<"readKey", JsonValue>
|
|
101
|
+
| MapOpPayload<"profile", CoID<RawProfile>>;
|
|
102
|
+
if (changes.length !== 1) {
|
|
103
|
+
console.warn("Group transaction must have exactly one change");
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (change.op !== "set") {
|
|
108
|
+
console.warn("Group transaction must set a role or readKey");
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (change.key === "readKey") {
|
|
113
|
+
if (memberState[transactor] !== "admin") {
|
|
114
|
+
console.warn("Only admins can set readKeys");
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
validTransactions.push({ txID: { sessionID, txIndex }, tx });
|
|
119
|
+
continue;
|
|
120
|
+
} else if (change.key === "profile") {
|
|
121
|
+
if (memberState[transactor] !== "admin") {
|
|
122
|
+
console.warn("Only admins can set profile");
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
validTransactions.push({ txID: { sessionID, txIndex }, tx });
|
|
127
|
+
continue;
|
|
128
|
+
} else if (
|
|
129
|
+
isKeyForKeyField(change.key) ||
|
|
130
|
+
isKeyForAccountField(change.key)
|
|
131
|
+
) {
|
|
132
|
+
if (
|
|
133
|
+
memberState[transactor] !== "admin" &&
|
|
134
|
+
memberState[transactor] !== "adminInvite" &&
|
|
135
|
+
memberState[transactor] !== "writerInvite" &&
|
|
136
|
+
memberState[transactor] !== "readerInvite"
|
|
137
|
+
) {
|
|
138
|
+
console.warn("Only admins can reveal keys");
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// TODO: check validity of agents who the key is revealed to?
|
|
143
|
+
|
|
144
|
+
validTransactions.push({ txID: { sessionID, txIndex }, tx });
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const affectedMember = change.key;
|
|
149
|
+
const assignedRole = change.value;
|
|
150
|
+
|
|
151
|
+
if (
|
|
152
|
+
change.value !== "admin" &&
|
|
153
|
+
change.value !== "writer" &&
|
|
154
|
+
change.value !== "reader" &&
|
|
155
|
+
change.value !== "revoked" &&
|
|
156
|
+
change.value !== "adminInvite" &&
|
|
157
|
+
change.value !== "writerInvite" &&
|
|
158
|
+
change.value !== "readerInvite"
|
|
159
|
+
) {
|
|
160
|
+
console.warn("Group transaction must set a valid role");
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (
|
|
165
|
+
affectedMember === EVERYONE &&
|
|
166
|
+
!(
|
|
167
|
+
change.value === "reader" ||
|
|
168
|
+
change.value === "writer" ||
|
|
169
|
+
change.value === "revoked"
|
|
170
|
+
)
|
|
171
|
+
) {
|
|
172
|
+
console.warn("Everyone can only be set to reader, writer or revoked");
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const isFirstSelfAppointment =
|
|
177
|
+
!memberState[transactor] &&
|
|
178
|
+
transactor === initialAdmin &&
|
|
179
|
+
change.op === "set" &&
|
|
180
|
+
change.key === transactor &&
|
|
181
|
+
change.value === "admin";
|
|
182
|
+
|
|
183
|
+
if (!isFirstSelfAppointment) {
|
|
184
|
+
if (memberState[transactor] === "admin") {
|
|
185
|
+
if (
|
|
186
|
+
memberState[affectedMember] === "admin" &&
|
|
187
|
+
affectedMember !== transactor &&
|
|
188
|
+
assignedRole !== "admin"
|
|
189
|
+
) {
|
|
190
|
+
console.warn("Admins can only demote themselves.");
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
} else if (memberState[transactor] === "adminInvite") {
|
|
194
|
+
if (change.value !== "admin") {
|
|
195
|
+
console.warn("AdminInvites can only create admins.");
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
} else if (memberState[transactor] === "writerInvite") {
|
|
199
|
+
if (change.value !== "writer") {
|
|
200
|
+
console.warn("WriterInvites can only create writers.");
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
} else if (memberState[transactor] === "readerInvite") {
|
|
204
|
+
if (change.value !== "reader") {
|
|
205
|
+
console.warn("ReaderInvites can only create reader.");
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
} else {
|
|
209
|
+
console.warn(
|
|
210
|
+
"Group transaction must be made by current admin or invite",
|
|
211
|
+
);
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
memberState[affectedMember] = change.value;
|
|
217
|
+
validTransactions.push({ txID: { sessionID, txIndex }, tx });
|
|
218
|
+
|
|
219
|
+
// console.log("after", { memberState, validTransactions });
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return validTransactions;
|
|
37
223
|
} else if (coValue.header.ruleset.type === "ownedByGroup") {
|
|
38
224
|
const groupContent = expectGroup(
|
|
39
225
|
coValue.node
|
|
@@ -55,18 +241,27 @@ export function determineValidTransactions(
|
|
|
55
241
|
return sessionLog.transactions
|
|
56
242
|
.filter((tx) => {
|
|
57
243
|
const groupAtTime = groupContent.atTime(tx.madeAt);
|
|
58
|
-
const effectiveTransactor =
|
|
59
|
-
transactor
|
|
60
|
-
groupAtTime
|
|
61
|
-
|
|
244
|
+
const effectiveTransactor =
|
|
245
|
+
transactor === groupContent.id &&
|
|
246
|
+
groupAtTime instanceof RawAccount
|
|
247
|
+
? groupAtTime.currentAgentID().match(
|
|
248
|
+
(agentID) => agentID,
|
|
249
|
+
(e) => {
|
|
250
|
+
console.error(
|
|
251
|
+
"Error while determining current agent ID in valid transactions",
|
|
252
|
+
e,
|
|
253
|
+
);
|
|
254
|
+
return undefined;
|
|
255
|
+
},
|
|
256
|
+
)
|
|
257
|
+
: transactor;
|
|
62
258
|
|
|
63
259
|
if (!effectiveTransactor) {
|
|
64
260
|
return false;
|
|
65
261
|
}
|
|
66
262
|
|
|
67
263
|
const transactorRoleAtTxTime =
|
|
68
|
-
groupAtTime.
|
|
69
|
-
groupAtTime.roleOfInternal(EVERYONE)?.role;
|
|
264
|
+
groupAtTime.get(effectiveTransactor) || groupAtTime.get(EVERYONE);
|
|
70
265
|
|
|
71
266
|
return (
|
|
72
267
|
transactorRoleAtTxTime === "admin" ||
|
|
@@ -96,234 +291,6 @@ export function determineValidTransactions(
|
|
|
96
291
|
}
|
|
97
292
|
}
|
|
98
293
|
|
|
99
|
-
function determineValidTransactionsForGroup(
|
|
100
|
-
coValue: CoValueCore,
|
|
101
|
-
initialAdmin: RawAccountID | AgentID,
|
|
102
|
-
): { txID: TransactionID; tx: Transaction }[] {
|
|
103
|
-
const allTransactionsSorted = [...coValue.sessionLogs.entries()].flatMap(
|
|
104
|
-
([sessionID, sessionLog]) => {
|
|
105
|
-
return sessionLog.transactions.map((tx, txIndex) => ({
|
|
106
|
-
sessionID,
|
|
107
|
-
txIndex,
|
|
108
|
-
tx,
|
|
109
|
-
})) as {
|
|
110
|
-
sessionID: SessionID;
|
|
111
|
-
txIndex: number;
|
|
112
|
-
tx: Transaction;
|
|
113
|
-
}[];
|
|
114
|
-
},
|
|
115
|
-
);
|
|
116
|
-
|
|
117
|
-
allTransactionsSorted.sort((a, b) => {
|
|
118
|
-
return a.tx.madeAt - b.tx.madeAt;
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
const memberState: {
|
|
122
|
-
[agent: RawAccountID | AgentID]: Role;
|
|
123
|
-
[EVERYONE]?: Role;
|
|
124
|
-
} = {};
|
|
125
|
-
|
|
126
|
-
const validTransactions: { txID: TransactionID; tx: Transaction }[] = [];
|
|
127
|
-
|
|
128
|
-
for (const { sessionID, txIndex, tx } of allTransactionsSorted) {
|
|
129
|
-
// console.log("before", { memberState, validTransactions });
|
|
130
|
-
const transactor = accountOrAgentIDfromSessionID(sessionID);
|
|
131
|
-
|
|
132
|
-
if (tx.privacy === "private") {
|
|
133
|
-
if (memberState[transactor] === "admin") {
|
|
134
|
-
validTransactions.push({
|
|
135
|
-
txID: { sessionID, txIndex },
|
|
136
|
-
tx,
|
|
137
|
-
});
|
|
138
|
-
continue;
|
|
139
|
-
} else {
|
|
140
|
-
console.warn("Only admins can make private transactions in groups");
|
|
141
|
-
continue;
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
let changes;
|
|
146
|
-
|
|
147
|
-
try {
|
|
148
|
-
changes = parseJSON(tx.changes);
|
|
149
|
-
} catch (e) {
|
|
150
|
-
console.warn(
|
|
151
|
-
coValue.id,
|
|
152
|
-
"Invalid JSON in transaction",
|
|
153
|
-
e,
|
|
154
|
-
tx,
|
|
155
|
-
JSON.stringify(tx.changes, (k, v) =>
|
|
156
|
-
k === "changes" || k === "encryptedChanges"
|
|
157
|
-
? v.slice(0, 20) + "..."
|
|
158
|
-
: v,
|
|
159
|
-
),
|
|
160
|
-
);
|
|
161
|
-
continue;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
const change = changes[0] as
|
|
165
|
-
| MapOpPayload<RawAccountID | AgentID | Everyone, Role>
|
|
166
|
-
| MapOpPayload<"readKey", JsonValue>
|
|
167
|
-
| MapOpPayload<"profile", CoID<RawProfile>>
|
|
168
|
-
| MapOpPayload<`parent_${CoID<RawGroup>}`, CoID<RawGroup>>
|
|
169
|
-
| MapOpPayload<`child_${CoID<RawGroup>}`, CoID<RawGroup>>;
|
|
170
|
-
|
|
171
|
-
if (changes.length !== 1) {
|
|
172
|
-
console.warn("Group transaction must have exactly one change");
|
|
173
|
-
continue;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
if (change.op !== "set") {
|
|
177
|
-
console.warn("Group transaction must set a role or readKey");
|
|
178
|
-
continue;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
if (change.key === "readKey") {
|
|
182
|
-
if (memberState[transactor] !== "admin") {
|
|
183
|
-
console.warn("Only admins can set readKeys");
|
|
184
|
-
continue;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
validTransactions.push({ txID: { sessionID, txIndex }, tx });
|
|
188
|
-
continue;
|
|
189
|
-
} else if (change.key === "profile") {
|
|
190
|
-
if (memberState[transactor] !== "admin") {
|
|
191
|
-
console.warn("Only admins can set profile");
|
|
192
|
-
continue;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
validTransactions.push({ txID: { sessionID, txIndex }, tx });
|
|
196
|
-
continue;
|
|
197
|
-
} else if (
|
|
198
|
-
isKeyForKeyField(change.key) ||
|
|
199
|
-
isKeyForAccountField(change.key)
|
|
200
|
-
) {
|
|
201
|
-
if (
|
|
202
|
-
memberState[transactor] !== "admin" &&
|
|
203
|
-
memberState[transactor] !== "adminInvite" &&
|
|
204
|
-
memberState[transactor] !== "writerInvite" &&
|
|
205
|
-
memberState[transactor] !== "readerInvite"
|
|
206
|
-
) {
|
|
207
|
-
console.warn("Only admins can reveal keys");
|
|
208
|
-
continue;
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
// TODO: check validity of agents who the key is revealed to?
|
|
212
|
-
|
|
213
|
-
validTransactions.push({ txID: { sessionID, txIndex }, tx });
|
|
214
|
-
continue;
|
|
215
|
-
} else if (isParentExtension(change.key)) {
|
|
216
|
-
if (memberState[transactor] !== "admin") {
|
|
217
|
-
console.warn("Only admins can set parent extensions");
|
|
218
|
-
continue;
|
|
219
|
-
}
|
|
220
|
-
validTransactions.push({ txID: { sessionID, txIndex }, tx });
|
|
221
|
-
continue;
|
|
222
|
-
} else if (isChildExtension(change.key)) {
|
|
223
|
-
if (memberState[transactor] !== "admin") {
|
|
224
|
-
console.warn("Only admins can set child extensions");
|
|
225
|
-
continue;
|
|
226
|
-
}
|
|
227
|
-
validTransactions.push({ txID: { sessionID, txIndex }, tx });
|
|
228
|
-
continue;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
const affectedMember = change.key;
|
|
232
|
-
const assignedRole = change.value;
|
|
233
|
-
|
|
234
|
-
if (
|
|
235
|
-
change.value !== "admin" &&
|
|
236
|
-
change.value !== "writer" &&
|
|
237
|
-
change.value !== "reader" &&
|
|
238
|
-
change.value !== "revoked" &&
|
|
239
|
-
change.value !== "adminInvite" &&
|
|
240
|
-
change.value !== "writerInvite" &&
|
|
241
|
-
change.value !== "readerInvite"
|
|
242
|
-
) {
|
|
243
|
-
console.warn("Group transaction must set a valid role");
|
|
244
|
-
continue;
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
if (
|
|
248
|
-
affectedMember === EVERYONE &&
|
|
249
|
-
!(
|
|
250
|
-
change.value === "reader" ||
|
|
251
|
-
change.value === "writer" ||
|
|
252
|
-
change.value === "revoked"
|
|
253
|
-
)
|
|
254
|
-
) {
|
|
255
|
-
console.warn("Everyone can only be set to reader, writer or revoked");
|
|
256
|
-
continue;
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
const isFirstSelfAppointment =
|
|
260
|
-
!memberState[transactor] &&
|
|
261
|
-
transactor === initialAdmin &&
|
|
262
|
-
change.op === "set" &&
|
|
263
|
-
change.key === transactor &&
|
|
264
|
-
change.value === "admin";
|
|
265
|
-
|
|
266
|
-
if (!isFirstSelfAppointment) {
|
|
267
|
-
if (memberState[transactor] === "admin") {
|
|
268
|
-
if (
|
|
269
|
-
memberState[affectedMember] === "admin" &&
|
|
270
|
-
affectedMember !== transactor &&
|
|
271
|
-
assignedRole !== "admin"
|
|
272
|
-
) {
|
|
273
|
-
console.warn("Admins can only demote themselves.");
|
|
274
|
-
continue;
|
|
275
|
-
}
|
|
276
|
-
} else if (memberState[transactor] === "adminInvite") {
|
|
277
|
-
if (change.value !== "admin") {
|
|
278
|
-
console.warn("AdminInvites can only create admins.");
|
|
279
|
-
continue;
|
|
280
|
-
}
|
|
281
|
-
} else if (memberState[transactor] === "writerInvite") {
|
|
282
|
-
if (change.value !== "writer") {
|
|
283
|
-
console.warn("WriterInvites can only create writers.");
|
|
284
|
-
continue;
|
|
285
|
-
}
|
|
286
|
-
} else if (memberState[transactor] === "readerInvite") {
|
|
287
|
-
if (change.value !== "reader") {
|
|
288
|
-
console.warn("ReaderInvites can only create reader.");
|
|
289
|
-
continue;
|
|
290
|
-
}
|
|
291
|
-
} else {
|
|
292
|
-
console.warn(
|
|
293
|
-
"Group transaction must be made by current admin or invite",
|
|
294
|
-
);
|
|
295
|
-
continue;
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
memberState[affectedMember] = change.value;
|
|
300
|
-
validTransactions.push({ txID: { sessionID, txIndex }, tx });
|
|
301
|
-
|
|
302
|
-
// console.log("after", { memberState, validTransactions });
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
return validTransactions;
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
function agentInAccountOrMemberInGroup(
|
|
309
|
-
transactor: RawAccountID | AgentID,
|
|
310
|
-
groupAtTime: RawGroup,
|
|
311
|
-
): RawAccountID | AgentID | undefined {
|
|
312
|
-
if (transactor === groupAtTime.id && groupAtTime instanceof RawAccount) {
|
|
313
|
-
return groupAtTime.currentAgentID().match(
|
|
314
|
-
(agentID) => agentID,
|
|
315
|
-
(e) => {
|
|
316
|
-
console.error(
|
|
317
|
-
"Error while determining current agent ID in valid transactions",
|
|
318
|
-
e,
|
|
319
|
-
);
|
|
320
|
-
return undefined;
|
|
321
|
-
},
|
|
322
|
-
);
|
|
323
|
-
}
|
|
324
|
-
return transactor;
|
|
325
|
-
}
|
|
326
|
-
|
|
327
294
|
export function isKeyForKeyField(co: string): co is `${KeyID}_for_${KeyID}` {
|
|
328
295
|
return co.startsWith("key_") && co.includes("_for_key");
|
|
329
296
|
}
|
|
@@ -337,11 +304,3 @@ export function isKeyForAccountField(
|
|
|
337
304
|
co.includes("_for_everyone")
|
|
338
305
|
);
|
|
339
306
|
}
|
|
340
|
-
|
|
341
|
-
function isParentExtension(key: string): key is `parent_${CoID<RawGroup>}` {
|
|
342
|
-
return key.startsWith("parent_");
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
function isChildExtension(key: string): key is `child_${CoID<RawGroup>}` {
|
|
346
|
-
return key.startsWith("child_");
|
|
347
|
-
}
|
package/src/storage/index.ts
CHANGED
|
@@ -146,7 +146,7 @@ export class LSMStorage<WH, RH, FS extends FileSystem<WH, RH>> {
|
|
|
146
146
|
asDependencyOf || id,
|
|
147
147
|
);
|
|
148
148
|
} else if (!known?.header && coValue.header?.ruleset.type === "group") {
|
|
149
|
-
const
|
|
149
|
+
const dependedOnAccounts = new Set();
|
|
150
150
|
for (const session of Object.values(coValue.sessionEntries)) {
|
|
151
151
|
for (const entry of session) {
|
|
152
152
|
for (const tx of entry.transactions) {
|
|
@@ -154,24 +154,16 @@ export class LSMStorage<WH, RH, FS extends FileSystem<WH, RH>> {
|
|
|
154
154
|
const parsedChanges = JSON.parse(tx.changes);
|
|
155
155
|
for (const change of parsedChanges) {
|
|
156
156
|
if (change.op === "set" && change.key.startsWith("co_")) {
|
|
157
|
-
|
|
158
|
-
}
|
|
159
|
-
if (
|
|
160
|
-
change.op === "set" &&
|
|
161
|
-
change.key.startsWith("parent_co_")
|
|
162
|
-
) {
|
|
163
|
-
dependedOnAccountsAndGroups.add(
|
|
164
|
-
change.key.replace("parent_", ""),
|
|
165
|
-
);
|
|
157
|
+
dependedOnAccounts.add(change.key);
|
|
166
158
|
}
|
|
167
159
|
}
|
|
168
160
|
}
|
|
169
161
|
}
|
|
170
162
|
}
|
|
171
163
|
}
|
|
172
|
-
for (const
|
|
164
|
+
for (const account of dependedOnAccounts) {
|
|
173
165
|
await this.sendNewContent(
|
|
174
|
-
|
|
166
|
+
account as CoID<RawCoValue>,
|
|
175
167
|
undefined,
|
|
176
168
|
asDependencyOf || id,
|
|
177
169
|
);
|
package/src/sync.ts
CHANGED
|
@@ -143,6 +143,12 @@ export class SyncManager {
|
|
|
143
143
|
const coValueEntry = this.local.coValues[id];
|
|
144
144
|
|
|
145
145
|
for (const peer of eligiblePeers) {
|
|
146
|
+
if (peer.erroredCoValues.has(id)) {
|
|
147
|
+
console.error(
|
|
148
|
+
`Skipping load on errored coValue ${id} from peer ${peer.id}`,
|
|
149
|
+
);
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
146
152
|
await peer.pushOutgoingMessage({
|
|
147
153
|
action: "load",
|
|
148
154
|
id: id,
|
|
@@ -157,6 +163,12 @@ export class SyncManager {
|
|
|
157
163
|
}
|
|
158
164
|
|
|
159
165
|
async handleSyncMessage(msg: SyncMessage, peer: PeerState) {
|
|
166
|
+
if (peer.erroredCoValues.has(msg.id)) {
|
|
167
|
+
console.error(
|
|
168
|
+
`Skipping message ${msg.action} on errored coValue ${msg.id} from peer ${peer.id}`,
|
|
169
|
+
);
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
160
172
|
// TODO: validate
|
|
161
173
|
switch (msg.action) {
|
|
162
174
|
case "load":
|
|
@@ -388,7 +400,7 @@ export class SyncManager {
|
|
|
388
400
|
}
|
|
389
401
|
|
|
390
402
|
async handleLoad(msg: LoadMessage, peer: PeerState) {
|
|
391
|
-
peer.
|
|
403
|
+
peer.dispatchToKnownStates({
|
|
392
404
|
type: "SET",
|
|
393
405
|
id: msg.id,
|
|
394
406
|
value: knownStateIn(msg),
|
|
@@ -442,7 +454,7 @@ export class SyncManager {
|
|
|
442
454
|
const loaded = await entry.state.ready;
|
|
443
455
|
|
|
444
456
|
if (loaded === "unavailable") {
|
|
445
|
-
peer.
|
|
457
|
+
peer.dispatchToKnownStates({
|
|
446
458
|
type: "SET",
|
|
447
459
|
id: msg.id,
|
|
448
460
|
value: knownStateIn(msg),
|
|
@@ -469,13 +481,7 @@ export class SyncManager {
|
|
|
469
481
|
async handleKnownState(msg: KnownStateMessage, peer: PeerState) {
|
|
470
482
|
let entry = this.local.coValues[msg.id];
|
|
471
483
|
|
|
472
|
-
peer.
|
|
473
|
-
type: "COMBINE_WITH",
|
|
474
|
-
id: msg.id,
|
|
475
|
-
value: knownStateIn(msg),
|
|
476
|
-
});
|
|
477
|
-
|
|
478
|
-
peer.knownStates.dispatch({
|
|
484
|
+
peer.dispatchToKnownStates({
|
|
479
485
|
type: "COMBINE_WITH",
|
|
480
486
|
id: msg.id,
|
|
481
487
|
value: knownStateIn(msg),
|
|
@@ -540,7 +546,7 @@ export class SyncManager {
|
|
|
540
546
|
return;
|
|
541
547
|
}
|
|
542
548
|
|
|
543
|
-
peer.
|
|
549
|
+
peer.dispatchToKnownStates({
|
|
544
550
|
type: "UPDATE_HEADER",
|
|
545
551
|
id: msg.id,
|
|
546
552
|
header: true,
|
|
@@ -629,10 +635,11 @@ export class SyncManager {
|
|
|
629
635
|
"our last known tx idx now: " +
|
|
630
636
|
coValue.sessionLogs.get(sessionID)?.transactions.length,
|
|
631
637
|
);
|
|
638
|
+
peer.erroredCoValues.set(msg.id, result.error);
|
|
632
639
|
continue;
|
|
633
640
|
}
|
|
634
641
|
|
|
635
|
-
peer.
|
|
642
|
+
peer.dispatchToKnownStates({
|
|
636
643
|
type: "UPDATE_SESSION_COUNTER",
|
|
637
644
|
id: msg.id,
|
|
638
645
|
sessionId: sessionID,
|
|
@@ -675,7 +682,7 @@ export class SyncManager {
|
|
|
675
682
|
}
|
|
676
683
|
|
|
677
684
|
async handleCorrection(msg: KnownStateMessage, peer: PeerState) {
|
|
678
|
-
peer.
|
|
685
|
+
peer.dispatchToKnownStates({
|
|
679
686
|
type: "SET",
|
|
680
687
|
id: msg.id,
|
|
681
688
|
value: knownStateIn(msg),
|
|
@@ -716,6 +723,7 @@ export class SyncManager {
|
|
|
716
723
|
// let blockingSince = performance.now();
|
|
717
724
|
for (const peer of this.peersInPriorityOrder()) {
|
|
718
725
|
if (peer.closed) continue;
|
|
726
|
+
if (peer.erroredCoValues.has(coValue.id)) continue;
|
|
719
727
|
// if (performance.now() - blockingSince > 5) {
|
|
720
728
|
// await new Promise<void>((resolve) => {
|
|
721
729
|
// setTimeout(resolve, 0);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { describe, expect, test, vi } from "vitest";
|
|
2
|
+
import { PeerKnownStateActions } from "../PeerKnownStates.js";
|
|
2
3
|
import { PeerState } from "../PeerState.js";
|
|
3
4
|
import { CO_VALUE_PRIORITY } from "../priority.js";
|
|
4
5
|
import { Peer, SyncMessage } from "../sync.js";
|
|
@@ -15,7 +16,7 @@ function setup() {
|
|
|
15
16
|
close: vi.fn(),
|
|
16
17
|
},
|
|
17
18
|
};
|
|
18
|
-
const peerState = new PeerState(mockPeer);
|
|
19
|
+
const peerState = new PeerState(mockPeer, undefined);
|
|
19
20
|
return { mockPeer, peerState };
|
|
20
21
|
}
|
|
21
22
|
|
|
@@ -115,4 +116,46 @@ describe("PeerState", () => {
|
|
|
115
116
|
contentMessageMid,
|
|
116
117
|
);
|
|
117
118
|
});
|
|
119
|
+
|
|
120
|
+
test("should clone the knownStates into optimisticKnownStates and knownStates when passed as argument", () => {
|
|
121
|
+
const { peerState, mockPeer } = setup();
|
|
122
|
+
const action: PeerKnownStateActions = {
|
|
123
|
+
type: "SET",
|
|
124
|
+
id: "co_z1",
|
|
125
|
+
value: {
|
|
126
|
+
id: "co_z1",
|
|
127
|
+
header: false,
|
|
128
|
+
sessions: {},
|
|
129
|
+
},
|
|
130
|
+
};
|
|
131
|
+
peerState.dispatchToKnownStates(action);
|
|
132
|
+
|
|
133
|
+
const newPeerState = new PeerState(mockPeer, peerState.knownStates);
|
|
134
|
+
|
|
135
|
+
expect(newPeerState.knownStates).toEqual(peerState.knownStates);
|
|
136
|
+
expect(newPeerState.optimisticKnownStates).toEqual(peerState.knownStates);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test("should dispatch to both states", () => {
|
|
140
|
+
const { peerState } = setup();
|
|
141
|
+
const knownStatesSpy = vi.spyOn(peerState.knownStates, "dispatch");
|
|
142
|
+
const optimisticKnownStatesSpy = vi.spyOn(
|
|
143
|
+
peerState.optimisticKnownStates,
|
|
144
|
+
"dispatch",
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
const action: PeerKnownStateActions = {
|
|
148
|
+
type: "SET",
|
|
149
|
+
id: "co_z1",
|
|
150
|
+
value: {
|
|
151
|
+
id: "co_z1",
|
|
152
|
+
header: false,
|
|
153
|
+
sessions: {},
|
|
154
|
+
},
|
|
155
|
+
};
|
|
156
|
+
peerState.dispatchToKnownStates(action);
|
|
157
|
+
|
|
158
|
+
expect(knownStatesSpy).toHaveBeenCalledWith(action);
|
|
159
|
+
expect(optimisticKnownStatesSpy).toHaveBeenCalledWith(action);
|
|
160
|
+
});
|
|
118
161
|
});
|