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/permissions.ts
CHANGED
|
@@ -2,9 +2,16 @@ 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 } from "./coValues/group.js";
|
|
5
|
+
import { EVERYONE, Everyone, RawGroup } from "./coValues/group.js";
|
|
6
6
|
import { KeyID } from "./crypto/crypto.js";
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
AgentID,
|
|
9
|
+
ParentGroupReference,
|
|
10
|
+
RawCoID,
|
|
11
|
+
SessionID,
|
|
12
|
+
TransactionID,
|
|
13
|
+
getParentGroupId,
|
|
14
|
+
} from "./ids.js";
|
|
8
15
|
import { parseJSON } from "./jsonStringify.js";
|
|
9
16
|
import { JsonValue } from "./jsonValue.js";
|
|
10
17
|
import { accountOrAgentIDfromSessionID } from "./typeUtils/accountOrAgentIDfromSessionID.js";
|
|
@@ -24,271 +31,350 @@ export type Role =
|
|
|
24
31
|
| "writerInvite"
|
|
25
32
|
| "readerInvite";
|
|
26
33
|
|
|
34
|
+
type ValidTransactionsResult = { txID: TransactionID; tx: Transaction };
|
|
35
|
+
type MemberState = { [agent: RawAccountID | AgentID]: Role; [EVERYONE]?: Role };
|
|
36
|
+
|
|
27
37
|
export function determineValidTransactions(
|
|
28
38
|
coValue: CoValueCore,
|
|
29
39
|
): { txID: TransactionID; tx: Transaction }[] {
|
|
30
40
|
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
|
-
|
|
49
41
|
const initialAdmin = coValue.header.ruleset.initialAdmin;
|
|
50
|
-
|
|
51
42
|
if (!initialAdmin) {
|
|
52
43
|
throw new Error("Group must have initialAdmin");
|
|
53
44
|
}
|
|
54
45
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
46
|
+
return determineValidTransactionsForGroup(coValue, initialAdmin)
|
|
47
|
+
.validTransactions;
|
|
48
|
+
} else if (coValue.header.ruleset.type === "ownedByGroup") {
|
|
49
|
+
const groupContent = expectGroup(
|
|
50
|
+
coValue.node
|
|
51
|
+
.expectCoValueLoaded(
|
|
52
|
+
coValue.header.ruleset.group,
|
|
53
|
+
"Determining valid transaction in owned object but its group wasn't loaded",
|
|
54
|
+
)
|
|
55
|
+
.getCurrentContent(),
|
|
56
|
+
);
|
|
61
57
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
58
|
+
if (groupContent.type !== "comap") {
|
|
59
|
+
throw new Error("Group must be a map");
|
|
60
|
+
}
|
|
65
61
|
|
|
66
|
-
|
|
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
|
-
}
|
|
62
|
+
const validTransactions: ValidTransactionsResult[] = [];
|
|
78
63
|
|
|
79
|
-
|
|
64
|
+
for (const [sessionID, sessionLog] of coValue.sessionLogs.entries()) {
|
|
65
|
+
const transactor = accountOrAgentIDfromSessionID(sessionID);
|
|
80
66
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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
|
-
),
|
|
67
|
+
sessionLog.transactions.forEach((tx, txIndex) => {
|
|
68
|
+
const groupAtTime = groupContent.atTime(tx.madeAt);
|
|
69
|
+
const effectiveTransactor = agentInAccountOrMemberInGroup(
|
|
70
|
+
transactor,
|
|
71
|
+
groupAtTime,
|
|
94
72
|
);
|
|
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
73
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
console.warn("Only admins can set readKeys");
|
|
115
|
-
continue;
|
|
74
|
+
if (!effectiveTransactor) {
|
|
75
|
+
return;
|
|
116
76
|
}
|
|
117
77
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
if (memberState[transactor] !== "admin") {
|
|
122
|
-
console.warn("Only admins can set profile");
|
|
123
|
-
continue;
|
|
124
|
-
}
|
|
78
|
+
const transactorRoleAtTxTime =
|
|
79
|
+
groupAtTime.roleOfInternal(effectiveTransactor)?.role ||
|
|
80
|
+
groupAtTime.roleOfInternal(EVERYONE)?.role;
|
|
125
81
|
|
|
126
|
-
validTransactions.push({ txID: { sessionID, txIndex }, tx });
|
|
127
|
-
continue;
|
|
128
|
-
} else if (
|
|
129
|
-
isKeyForKeyField(change.key) ||
|
|
130
|
-
isKeyForAccountField(change.key)
|
|
131
|
-
) {
|
|
132
82
|
if (
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
memberState[transactor] !== "writerInvite" &&
|
|
136
|
-
memberState[transactor] !== "readerInvite"
|
|
83
|
+
transactorRoleAtTxTime !== "admin" &&
|
|
84
|
+
transactorRoleAtTxTime !== "writer"
|
|
137
85
|
) {
|
|
138
|
-
|
|
139
|
-
continue;
|
|
86
|
+
return;
|
|
140
87
|
}
|
|
141
88
|
|
|
142
|
-
|
|
89
|
+
validTransactions.push({ txID: { sessionID, txIndex }, tx });
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return validTransactions;
|
|
94
|
+
} else if (coValue.header.ruleset.type === "unsafeAllowAll") {
|
|
95
|
+
const validTransactions: ValidTransactionsResult[] = [];
|
|
143
96
|
|
|
97
|
+
for (const [sessionID, sessionLog] of coValue.sessionLogs.entries()) {
|
|
98
|
+
sessionLog.transactions.forEach((tx, txIndex) => {
|
|
144
99
|
validTransactions.push({ txID: { sessionID, txIndex }, tx });
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
return validTransactions;
|
|
103
|
+
} else {
|
|
104
|
+
throw new Error(
|
|
105
|
+
"Unknown ruleset type " +
|
|
106
|
+
(coValue.header.ruleset as { type: string }).type,
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function isHigherRole(a: Role, b: Role | undefined) {
|
|
112
|
+
if (a === undefined) return false;
|
|
113
|
+
if (b === undefined) return true;
|
|
114
|
+
if (b === "admin") return false;
|
|
115
|
+
if (a === "admin") return true;
|
|
116
|
+
|
|
117
|
+
return a === "writer" && b === "reader";
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function resolveMemberStateFromParentReference(
|
|
121
|
+
coValue: CoValueCore,
|
|
122
|
+
memberState: MemberState,
|
|
123
|
+
parentReference: ParentGroupReference,
|
|
124
|
+
) {
|
|
125
|
+
const parentGroup = coValue.node.expectCoValueLoaded(
|
|
126
|
+
getParentGroupId(parentReference),
|
|
127
|
+
"Expected parent group to be loaded",
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
if (parentGroup.header.ruleset.type !== "group") {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const initialAdmin = parentGroup.header.ruleset.initialAdmin;
|
|
135
|
+
|
|
136
|
+
if (!initialAdmin) {
|
|
137
|
+
throw new Error("Group must have initialAdmin");
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const { memberState: parentGroupMemberState } =
|
|
141
|
+
determineValidTransactionsForGroup(parentGroup, initialAdmin);
|
|
142
|
+
|
|
143
|
+
for (const agent of Object.keys(parentGroupMemberState) as Array<
|
|
144
|
+
keyof MemberState
|
|
145
|
+
>) {
|
|
146
|
+
const parentRole = parentGroupMemberState[agent];
|
|
147
|
+
const currentRole = memberState[agent];
|
|
148
|
+
|
|
149
|
+
if (parentRole && isHigherRole(parentRole, currentRole)) {
|
|
150
|
+
memberState[agent] = parentRole;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function determineValidTransactionsForGroup(
|
|
156
|
+
coValue: CoValueCore,
|
|
157
|
+
initialAdmin: RawAccountID | AgentID,
|
|
158
|
+
): { validTransactions: ValidTransactionsResult[]; memberState: MemberState } {
|
|
159
|
+
const allTransactionsSorted = [...coValue.sessionLogs.entries()].flatMap(
|
|
160
|
+
([sessionID, sessionLog]) => {
|
|
161
|
+
return sessionLog.transactions.map((tx, txIndex) => ({
|
|
162
|
+
sessionID,
|
|
163
|
+
txIndex,
|
|
164
|
+
tx,
|
|
165
|
+
})) as {
|
|
166
|
+
sessionID: SessionID;
|
|
167
|
+
txIndex: number;
|
|
168
|
+
tx: Transaction;
|
|
169
|
+
}[];
|
|
170
|
+
},
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
allTransactionsSorted.sort((a, b) => {
|
|
174
|
+
return a.tx.madeAt - b.tx.madeAt;
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
const memberState: MemberState = {};
|
|
178
|
+
const validTransactions: ValidTransactionsResult[] = [];
|
|
179
|
+
|
|
180
|
+
for (const { sessionID, txIndex, tx } of allTransactionsSorted) {
|
|
181
|
+
// console.log("before", { memberState, validTransactions });
|
|
182
|
+
const transactor = accountOrAgentIDfromSessionID(sessionID);
|
|
183
|
+
|
|
184
|
+
if (tx.privacy === "private") {
|
|
185
|
+
if (memberState[transactor] === "admin") {
|
|
186
|
+
validTransactions.push({
|
|
187
|
+
txID: { sessionID, txIndex },
|
|
188
|
+
tx,
|
|
189
|
+
});
|
|
190
|
+
continue;
|
|
191
|
+
} else {
|
|
192
|
+
console.warn("Only admins can make private transactions in groups");
|
|
145
193
|
continue;
|
|
146
194
|
}
|
|
195
|
+
}
|
|
147
196
|
|
|
148
|
-
|
|
149
|
-
|
|
197
|
+
let changes;
|
|
198
|
+
|
|
199
|
+
try {
|
|
200
|
+
changes = parseJSON(tx.changes);
|
|
201
|
+
} catch (e) {
|
|
202
|
+
console.warn(
|
|
203
|
+
coValue.id,
|
|
204
|
+
"Invalid JSON in transaction",
|
|
205
|
+
e,
|
|
206
|
+
tx,
|
|
207
|
+
JSON.stringify(tx.changes, (k, v) =>
|
|
208
|
+
k === "changes" || k === "encryptedChanges"
|
|
209
|
+
? v.slice(0, 20) + "..."
|
|
210
|
+
: v,
|
|
211
|
+
),
|
|
212
|
+
);
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
150
215
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
)
|
|
160
|
-
|
|
216
|
+
const change = changes[0] as
|
|
217
|
+
| MapOpPayload<RawAccountID | AgentID | Everyone, Role>
|
|
218
|
+
| MapOpPayload<"readKey", JsonValue>
|
|
219
|
+
| MapOpPayload<"profile", CoID<RawProfile>>
|
|
220
|
+
| MapOpPayload<`parent_${CoID<RawGroup>}`, CoID<RawGroup>>
|
|
221
|
+
| MapOpPayload<`child_${CoID<RawGroup>}`, CoID<RawGroup>>;
|
|
222
|
+
|
|
223
|
+
if (changes.length !== 1) {
|
|
224
|
+
console.warn("Group transaction must have exactly one change");
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (change.op !== "set") {
|
|
229
|
+
console.warn("Group transaction must set a role or readKey");
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (change.key === "readKey") {
|
|
234
|
+
if (memberState[transactor] !== "admin") {
|
|
235
|
+
console.warn("Only admins can set readKeys");
|
|
161
236
|
continue;
|
|
162
237
|
}
|
|
163
238
|
|
|
239
|
+
validTransactions.push({ txID: { sessionID, txIndex }, tx });
|
|
240
|
+
continue;
|
|
241
|
+
} else if (change.key === "profile") {
|
|
242
|
+
if (memberState[transactor] !== "admin") {
|
|
243
|
+
console.warn("Only admins can set profile");
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
validTransactions.push({ txID: { sessionID, txIndex }, tx });
|
|
248
|
+
continue;
|
|
249
|
+
} else if (
|
|
250
|
+
isKeyForKeyField(change.key) ||
|
|
251
|
+
isKeyForAccountField(change.key)
|
|
252
|
+
) {
|
|
164
253
|
if (
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
change.value === "revoked"
|
|
170
|
-
)
|
|
254
|
+
memberState[transactor] !== "admin" &&
|
|
255
|
+
memberState[transactor] !== "adminInvite" &&
|
|
256
|
+
memberState[transactor] !== "writerInvite" &&
|
|
257
|
+
memberState[transactor] !== "readerInvite"
|
|
171
258
|
) {
|
|
172
|
-
console.warn("
|
|
259
|
+
console.warn("Only admins can reveal keys");
|
|
173
260
|
continue;
|
|
174
261
|
}
|
|
175
262
|
|
|
176
|
-
|
|
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
|
-
}
|
|
263
|
+
// TODO: check validity of agents who the key is revealed to?
|
|
215
264
|
|
|
216
|
-
memberState[affectedMember] = change.value;
|
|
217
265
|
validTransactions.push({ txID: { sessionID, txIndex }, tx });
|
|
266
|
+
continue;
|
|
267
|
+
} else if (isParentExtension(change.key)) {
|
|
268
|
+
if (memberState[transactor] !== "admin") {
|
|
269
|
+
console.warn("Only admins can set parent extensions");
|
|
270
|
+
continue;
|
|
271
|
+
}
|
|
272
|
+
resolveMemberStateFromParentReference(coValue, memberState, change.key);
|
|
273
|
+
validTransactions.push({ txID: { sessionID, txIndex }, tx });
|
|
274
|
+
continue;
|
|
275
|
+
} else if (isChildExtension(change.key)) {
|
|
276
|
+
if (memberState[transactor] !== "admin") {
|
|
277
|
+
console.warn("Only admins can set child extensions");
|
|
278
|
+
continue;
|
|
279
|
+
}
|
|
280
|
+
validTransactions.push({ txID: { sessionID, txIndex }, tx });
|
|
281
|
+
continue;
|
|
282
|
+
}
|
|
218
283
|
|
|
219
|
-
|
|
284
|
+
const affectedMember = change.key;
|
|
285
|
+
const assignedRole = change.value;
|
|
286
|
+
|
|
287
|
+
if (
|
|
288
|
+
change.value !== "admin" &&
|
|
289
|
+
change.value !== "writer" &&
|
|
290
|
+
change.value !== "reader" &&
|
|
291
|
+
change.value !== "revoked" &&
|
|
292
|
+
change.value !== "adminInvite" &&
|
|
293
|
+
change.value !== "writerInvite" &&
|
|
294
|
+
change.value !== "readerInvite"
|
|
295
|
+
) {
|
|
296
|
+
console.warn("Group transaction must set a valid role");
|
|
297
|
+
continue;
|
|
220
298
|
}
|
|
221
299
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
.
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
300
|
+
if (
|
|
301
|
+
affectedMember === EVERYONE &&
|
|
302
|
+
!(
|
|
303
|
+
change.value === "reader" ||
|
|
304
|
+
change.value === "writer" ||
|
|
305
|
+
change.value === "revoked"
|
|
306
|
+
)
|
|
307
|
+
) {
|
|
308
|
+
console.warn("Everyone can only be set to reader, writer or revoked");
|
|
309
|
+
continue;
|
|
310
|
+
}
|
|
232
311
|
|
|
233
|
-
|
|
234
|
-
|
|
312
|
+
const isFirstSelfAppointment =
|
|
313
|
+
!memberState[transactor] &&
|
|
314
|
+
transactor === initialAdmin &&
|
|
315
|
+
change.op === "set" &&
|
|
316
|
+
change.key === transactor &&
|
|
317
|
+
change.value === "admin";
|
|
318
|
+
|
|
319
|
+
if (!isFirstSelfAppointment) {
|
|
320
|
+
if (memberState[transactor] === "admin") {
|
|
321
|
+
if (
|
|
322
|
+
memberState[affectedMember] === "admin" &&
|
|
323
|
+
affectedMember !== transactor &&
|
|
324
|
+
assignedRole !== "admin"
|
|
325
|
+
) {
|
|
326
|
+
console.warn("Admins can only demote themselves.");
|
|
327
|
+
continue;
|
|
328
|
+
}
|
|
329
|
+
} else if (memberState[transactor] === "adminInvite") {
|
|
330
|
+
if (change.value !== "admin") {
|
|
331
|
+
console.warn("AdminInvites can only create admins.");
|
|
332
|
+
continue;
|
|
333
|
+
}
|
|
334
|
+
} else if (memberState[transactor] === "writerInvite") {
|
|
335
|
+
if (change.value !== "writer") {
|
|
336
|
+
console.warn("WriterInvites can only create writers.");
|
|
337
|
+
continue;
|
|
338
|
+
}
|
|
339
|
+
} else if (memberState[transactor] === "readerInvite") {
|
|
340
|
+
if (change.value !== "reader") {
|
|
341
|
+
console.warn("ReaderInvites can only create reader.");
|
|
342
|
+
continue;
|
|
343
|
+
}
|
|
344
|
+
} else {
|
|
345
|
+
console.warn(
|
|
346
|
+
"Group transaction must be made by current admin or invite",
|
|
347
|
+
);
|
|
348
|
+
continue;
|
|
349
|
+
}
|
|
235
350
|
}
|
|
236
351
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
if (!effectiveTransactor) {
|
|
260
|
-
return false;
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
const transactorRoleAtTxTime =
|
|
264
|
-
groupAtTime.get(effectiveTransactor) || groupAtTime.get(EVERYONE);
|
|
265
|
-
|
|
266
|
-
return (
|
|
267
|
-
transactorRoleAtTxTime === "admin" ||
|
|
268
|
-
transactorRoleAtTxTime === "writer"
|
|
269
|
-
);
|
|
270
|
-
})
|
|
271
|
-
.map((tx, txIndex) => ({
|
|
272
|
-
txID: { sessionID: sessionID, txIndex },
|
|
273
|
-
tx,
|
|
274
|
-
}));
|
|
275
|
-
},
|
|
276
|
-
);
|
|
277
|
-
} else if (coValue.header.ruleset.type === "unsafeAllowAll") {
|
|
278
|
-
return [...coValue.sessionLogs.entries()].flatMap(
|
|
279
|
-
([sessionID, sessionLog]) => {
|
|
280
|
-
return sessionLog.transactions.map((tx, txIndex) => ({
|
|
281
|
-
txID: { sessionID: sessionID, txIndex },
|
|
282
|
-
tx,
|
|
283
|
-
}));
|
|
352
|
+
memberState[affectedMember] = change.value;
|
|
353
|
+
validTransactions.push({ txID: { sessionID, txIndex }, tx });
|
|
354
|
+
|
|
355
|
+
// console.log("after", { memberState, validTransactions });
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
return { validTransactions, memberState };
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
function agentInAccountOrMemberInGroup(
|
|
362
|
+
transactor: RawAccountID | AgentID,
|
|
363
|
+
groupAtTime: RawGroup,
|
|
364
|
+
): RawAccountID | AgentID | undefined {
|
|
365
|
+
if (transactor === groupAtTime.id && groupAtTime instanceof RawAccount) {
|
|
366
|
+
return groupAtTime.currentAgentID().match(
|
|
367
|
+
(agentID) => agentID,
|
|
368
|
+
(e) => {
|
|
369
|
+
console.error(
|
|
370
|
+
"Error while determining current agent ID in valid transactions",
|
|
371
|
+
e,
|
|
372
|
+
);
|
|
373
|
+
return undefined;
|
|
284
374
|
},
|
|
285
375
|
);
|
|
286
|
-
} else {
|
|
287
|
-
throw new Error(
|
|
288
|
-
"Unknown ruleset type " +
|
|
289
|
-
(coValue.header.ruleset as { type: string }).type,
|
|
290
|
-
);
|
|
291
376
|
}
|
|
377
|
+
return transactor;
|
|
292
378
|
}
|
|
293
379
|
|
|
294
380
|
export function isKeyForKeyField(co: string): co is `${KeyID}_for_${KeyID}` {
|
|
@@ -304,3 +390,11 @@ export function isKeyForAccountField(
|
|
|
304
390
|
co.includes("_for_everyone")
|
|
305
391
|
);
|
|
306
392
|
}
|
|
393
|
+
|
|
394
|
+
function isParentExtension(key: string): key is `parent_${CoID<RawGroup>}` {
|
|
395
|
+
return key.startsWith("parent_");
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
function isChildExtension(key: string): key is `child_${CoID<RawGroup>}` {
|
|
399
|
+
return key.startsWith("child_");
|
|
400
|
+
}
|
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 dependedOnAccountsAndGroups = 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,16 +154,24 @@ 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
|
-
|
|
157
|
+
dependedOnAccountsAndGroups.add(change.key);
|
|
158
|
+
}
|
|
159
|
+
if (
|
|
160
|
+
change.op === "set" &&
|
|
161
|
+
change.key.startsWith("parent_co_")
|
|
162
|
+
) {
|
|
163
|
+
dependedOnAccountsAndGroups.add(
|
|
164
|
+
change.key.replace("parent_", ""),
|
|
165
|
+
);
|
|
158
166
|
}
|
|
159
167
|
}
|
|
160
168
|
}
|
|
161
169
|
}
|
|
162
170
|
}
|
|
163
171
|
}
|
|
164
|
-
for (const
|
|
172
|
+
for (const accountOrGroup of dependedOnAccountsAndGroups) {
|
|
165
173
|
await this.sendNewContent(
|
|
166
|
-
|
|
174
|
+
accountOrGroup as CoID<RawCoValue>,
|
|
167
175
|
undefined,
|
|
168
176
|
asDependencyOf || id,
|
|
169
177
|
);
|