cojson 0.19.22 → 0.20.0
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/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +54 -0
- package/dist/coValueContentMessage.d.ts +0 -2
- package/dist/coValueContentMessage.d.ts.map +1 -1
- package/dist/coValueContentMessage.js +0 -8
- package/dist/coValueContentMessage.js.map +1 -1
- package/dist/coValueCore/SessionMap.d.ts +4 -2
- package/dist/coValueCore/SessionMap.d.ts.map +1 -1
- package/dist/coValueCore/SessionMap.js +30 -0
- package/dist/coValueCore/SessionMap.js.map +1 -1
- package/dist/coValueCore/coValueCore.d.ts +67 -3
- package/dist/coValueCore/coValueCore.d.ts.map +1 -1
- package/dist/coValueCore/coValueCore.js +289 -12
- package/dist/coValueCore/coValueCore.js.map +1 -1
- package/dist/coValueCore/verifiedState.d.ts +6 -1
- package/dist/coValueCore/verifiedState.d.ts.map +1 -1
- package/dist/coValueCore/verifiedState.js +9 -0
- package/dist/coValueCore/verifiedState.js.map +1 -1
- package/dist/coValues/coList.d.ts +3 -2
- package/dist/coValues/coList.d.ts.map +1 -1
- package/dist/coValues/coList.js.map +1 -1
- package/dist/coValues/group.d.ts.map +1 -1
- package/dist/coValues/group.js +3 -6
- package/dist/coValues/group.js.map +1 -1
- package/dist/config.d.ts +0 -6
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +0 -8
- package/dist/config.js.map +1 -1
- package/dist/crypto/NapiCrypto.d.ts +1 -2
- package/dist/crypto/NapiCrypto.d.ts.map +1 -1
- package/dist/crypto/NapiCrypto.js +19 -4
- package/dist/crypto/NapiCrypto.js.map +1 -1
- package/dist/crypto/RNCrypto.d.ts.map +1 -1
- package/dist/crypto/RNCrypto.js +19 -4
- package/dist/crypto/RNCrypto.js.map +1 -1
- package/dist/crypto/WasmCrypto.d.ts +11 -4
- package/dist/crypto/WasmCrypto.d.ts.map +1 -1
- package/dist/crypto/WasmCrypto.js +52 -10
- package/dist/crypto/WasmCrypto.js.map +1 -1
- package/dist/crypto/WasmCryptoEdge.d.ts +1 -0
- package/dist/crypto/WasmCryptoEdge.d.ts.map +1 -1
- package/dist/crypto/WasmCryptoEdge.js +4 -1
- package/dist/crypto/WasmCryptoEdge.js.map +1 -1
- package/dist/crypto/crypto.d.ts +3 -3
- package/dist/crypto/crypto.d.ts.map +1 -1
- package/dist/crypto/crypto.js +6 -1
- package/dist/crypto/crypto.js.map +1 -1
- package/dist/exports.d.ts +2 -2
- package/dist/exports.d.ts.map +1 -1
- package/dist/exports.js +2 -1
- package/dist/exports.js.map +1 -1
- package/dist/ids.d.ts +4 -1
- package/dist/ids.d.ts.map +1 -1
- package/dist/ids.js +4 -0
- package/dist/ids.js.map +1 -1
- package/dist/knownState.d.ts +2 -0
- package/dist/knownState.d.ts.map +1 -1
- package/dist/localNode.d.ts +12 -0
- package/dist/localNode.d.ts.map +1 -1
- package/dist/localNode.js +14 -0
- package/dist/localNode.js.map +1 -1
- package/dist/platformUtils.d.ts +3 -0
- package/dist/platformUtils.d.ts.map +1 -0
- package/dist/platformUtils.js +24 -0
- package/dist/platformUtils.js.map +1 -0
- package/dist/storage/DeletedCoValuesEraserScheduler.d.ts +30 -0
- package/dist/storage/DeletedCoValuesEraserScheduler.d.ts.map +1 -0
- package/dist/storage/DeletedCoValuesEraserScheduler.js +84 -0
- package/dist/storage/DeletedCoValuesEraserScheduler.js.map +1 -0
- package/dist/storage/sqlite/client.d.ts +3 -0
- package/dist/storage/sqlite/client.d.ts.map +1 -1
- package/dist/storage/sqlite/client.js +44 -0
- package/dist/storage/sqlite/client.js.map +1 -1
- package/dist/storage/sqlite/sqliteMigrations.d.ts.map +1 -1
- package/dist/storage/sqlite/sqliteMigrations.js +7 -0
- package/dist/storage/sqlite/sqliteMigrations.js.map +1 -1
- package/dist/storage/sqliteAsync/client.d.ts +3 -0
- package/dist/storage/sqliteAsync/client.d.ts.map +1 -1
- package/dist/storage/sqliteAsync/client.js +42 -0
- package/dist/storage/sqliteAsync/client.js.map +1 -1
- package/dist/storage/storageAsync.d.ts +7 -0
- package/dist/storage/storageAsync.d.ts.map +1 -1
- package/dist/storage/storageAsync.js +48 -0
- package/dist/storage/storageAsync.js.map +1 -1
- package/dist/storage/storageSync.d.ts +6 -0
- package/dist/storage/storageSync.d.ts.map +1 -1
- package/dist/storage/storageSync.js +42 -0
- package/dist/storage/storageSync.js.map +1 -1
- package/dist/storage/types.d.ts +59 -0
- package/dist/storage/types.d.ts.map +1 -1
- package/dist/storage/types.js +12 -1
- package/dist/storage/types.js.map +1 -1
- package/dist/sync.d.ts.map +1 -1
- package/dist/sync.js +44 -11
- package/dist/sync.js.map +1 -1
- package/dist/tests/DeletedCoValuesEraserScheduler.test.d.ts +2 -0
- package/dist/tests/DeletedCoValuesEraserScheduler.test.d.ts.map +1 -0
- package/dist/tests/DeletedCoValuesEraserScheduler.test.js +149 -0
- package/dist/tests/DeletedCoValuesEraserScheduler.test.js.map +1 -0
- package/dist/tests/GarbageCollector.test.js +5 -6
- package/dist/tests/GarbageCollector.test.js.map +1 -1
- package/dist/tests/StorageApiAsync.test.js +484 -152
- package/dist/tests/StorageApiAsync.test.js.map +1 -1
- package/dist/tests/StorageApiSync.test.js +505 -136
- package/dist/tests/StorageApiSync.test.js.map +1 -1
- package/dist/tests/WasmCrypto.test.js +6 -3
- package/dist/tests/WasmCrypto.test.js.map +1 -1
- package/dist/tests/coValueCore.loadFromStorage.test.js +3 -0
- package/dist/tests/coValueCore.loadFromStorage.test.js.map +1 -1
- package/dist/tests/coValueCore.test.js +34 -13
- package/dist/tests/coValueCore.test.js.map +1 -1
- package/dist/tests/coreWasm.test.js +127 -4
- package/dist/tests/coreWasm.test.js.map +1 -1
- package/dist/tests/crypto.test.js +89 -93
- package/dist/tests/crypto.test.js.map +1 -1
- package/dist/tests/deleteCoValue.test.d.ts +2 -0
- package/dist/tests/deleteCoValue.test.d.ts.map +1 -0
- package/dist/tests/deleteCoValue.test.js +313 -0
- package/dist/tests/deleteCoValue.test.js.map +1 -0
- package/dist/tests/group.removeMember.test.js +18 -30
- package/dist/tests/group.removeMember.test.js.map +1 -1
- package/dist/tests/knownState.lazyLoading.test.js +3 -0
- package/dist/tests/knownState.lazyLoading.test.js.map +1 -1
- package/dist/tests/sync.deleted.test.d.ts +2 -0
- package/dist/tests/sync.deleted.test.d.ts.map +1 -0
- package/dist/tests/sync.deleted.test.js +214 -0
- package/dist/tests/sync.deleted.test.js.map +1 -0
- package/dist/tests/sync.mesh.test.js +3 -2
- package/dist/tests/sync.mesh.test.js.map +1 -1
- package/dist/tests/sync.storage.test.js +3 -2
- package/dist/tests/sync.storage.test.js.map +1 -1
- package/dist/tests/sync.test.js +3 -2
- package/dist/tests/sync.test.js.map +1 -1
- package/dist/tests/testStorage.d.ts +3 -0
- package/dist/tests/testStorage.d.ts.map +1 -1
- package/dist/tests/testStorage.js +14 -0
- package/dist/tests/testStorage.js.map +1 -1
- package/dist/tests/testUtils.d.ts +6 -3
- package/dist/tests/testUtils.d.ts.map +1 -1
- package/dist/tests/testUtils.js +17 -3
- package/dist/tests/testUtils.js.map +1 -1
- package/package.json +6 -16
- package/src/coValueContentMessage.ts +0 -14
- package/src/coValueCore/SessionMap.ts +43 -1
- package/src/coValueCore/coValueCore.ts +400 -8
- package/src/coValueCore/verifiedState.ts +26 -3
- package/src/coValues/coList.ts +5 -3
- package/src/coValues/group.ts +5 -6
- package/src/config.ts +0 -9
- package/src/crypto/NapiCrypto.ts +29 -13
- package/src/crypto/RNCrypto.ts +29 -11
- package/src/crypto/WasmCrypto.ts +67 -20
- package/src/crypto/WasmCryptoEdge.ts +5 -1
- package/src/crypto/crypto.ts +16 -4
- package/src/exports.ts +2 -0
- package/src/ids.ts +11 -1
- package/src/localNode.ts +15 -0
- package/src/platformUtils.ts +26 -0
- package/src/storage/DeletedCoValuesEraserScheduler.ts +124 -0
- package/src/storage/sqlite/client.ts +77 -0
- package/src/storage/sqlite/sqliteMigrations.ts +7 -0
- package/src/storage/sqliteAsync/client.ts +75 -0
- package/src/storage/storageAsync.ts +62 -0
- package/src/storage/storageSync.ts +58 -0
- package/src/storage/types.ts +69 -0
- package/src/sync.ts +51 -11
- package/src/tests/DeletedCoValuesEraserScheduler.test.ts +185 -0
- package/src/tests/GarbageCollector.test.ts +6 -10
- package/src/tests/StorageApiAsync.test.ts +572 -162
- package/src/tests/StorageApiSync.test.ts +580 -143
- package/src/tests/WasmCrypto.test.ts +8 -3
- package/src/tests/coValueCore.loadFromStorage.test.ts +6 -0
- package/src/tests/coValueCore.test.ts +49 -14
- package/src/tests/coreWasm.test.ts +319 -10
- package/src/tests/crypto.test.ts +141 -150
- package/src/tests/deleteCoValue.test.ts +528 -0
- package/src/tests/group.removeMember.test.ts +35 -35
- package/src/tests/knownState.lazyLoading.test.ts +6 -0
- package/src/tests/sync.deleted.test.ts +294 -0
- package/src/tests/sync.mesh.test.ts +5 -2
- package/src/tests/sync.storage.test.ts +5 -2
- package/src/tests/sync.test.ts +5 -2
- package/src/tests/testStorage.ts +28 -1
- package/src/tests/testUtils.ts +28 -9
- package/dist/crypto/PureJSCrypto.d.ts +0 -77
- package/dist/crypto/PureJSCrypto.d.ts.map +0 -1
- package/dist/crypto/PureJSCrypto.js +0 -236
- package/dist/crypto/PureJSCrypto.js.map +0 -1
- package/dist/tests/PureJSCrypto.test.d.ts +0 -2
- package/dist/tests/PureJSCrypto.test.d.ts.map +0 -1
- package/dist/tests/PureJSCrypto.test.js +0 -145
- package/dist/tests/PureJSCrypto.test.js.map +0 -1
- package/src/crypto/PureJSCrypto.ts +0 -429
- package/src/tests/PureJSCrypto.test.ts +0 -217
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { UpDownCounter, ValueType, metrics } from "@opentelemetry/api";
|
|
2
2
|
import type { PeerState } from "../PeerState.js";
|
|
3
3
|
import type { RawCoValue } from "../coValue.js";
|
|
4
|
-
import
|
|
4
|
+
import {
|
|
5
|
+
RawAccount,
|
|
6
|
+
type ControlledAccountOrAgent,
|
|
7
|
+
} from "../coValues/account.js";
|
|
5
8
|
import type { RawGroup } from "../coValues/group.js";
|
|
6
9
|
import { CO_VALUE_LOADING_CONFIG } from "../config.js";
|
|
7
|
-
import { validateTxSizeLimitInBytes } from "../coValueContentMessage.js";
|
|
8
10
|
import { coreToCoValue } from "../coreToCoValue.js";
|
|
9
11
|
import {
|
|
10
12
|
CryptoProvider,
|
|
@@ -14,12 +16,18 @@ import {
|
|
|
14
16
|
Signature,
|
|
15
17
|
SignerID,
|
|
16
18
|
} from "../crypto/crypto.js";
|
|
17
|
-
import {
|
|
19
|
+
import {
|
|
20
|
+
AgentID,
|
|
21
|
+
isDeleteSessionID,
|
|
22
|
+
RawCoID,
|
|
23
|
+
SessionID,
|
|
24
|
+
TransactionID,
|
|
25
|
+
} from "../ids.js";
|
|
18
26
|
import { JsonObject, JsonValue } from "../jsonValue.js";
|
|
19
27
|
import { LocalNode, ResolveAccountAgentError } from "../localNode.js";
|
|
20
28
|
import { logger } from "../logger.js";
|
|
21
29
|
import { determineValidTransactions } from "../permissions.js";
|
|
22
|
-
import { NewContentMessage, PeerID } from "../sync.js";
|
|
30
|
+
import { KnownStateMessage, NewContentMessage, PeerID } from "../sync.js";
|
|
23
31
|
import { accountOrAgentIDfromSessionID } from "../typeUtils/accountOrAgentIDfromSessionID.js";
|
|
24
32
|
import { expectGroup } from "../typeUtils/expectGroup.js";
|
|
25
33
|
import {
|
|
@@ -27,7 +35,12 @@ import {
|
|
|
27
35
|
getDependenciesFromGroupRawTransactions,
|
|
28
36
|
getDependenciesFromHeader,
|
|
29
37
|
} from "./utils.js";
|
|
30
|
-
import {
|
|
38
|
+
import {
|
|
39
|
+
CoValueHeader,
|
|
40
|
+
Transaction,
|
|
41
|
+
Uniqueness,
|
|
42
|
+
VerifiedState,
|
|
43
|
+
} from "./verifiedState.js";
|
|
31
44
|
import { SessionMap } from "./SessionMap.js";
|
|
32
45
|
import {
|
|
33
46
|
MergeCommit,
|
|
@@ -51,6 +64,45 @@ import {
|
|
|
51
64
|
} from "../knownState.js";
|
|
52
65
|
import { safeParseJSON } from "../jsonStringify.js";
|
|
53
66
|
|
|
67
|
+
export type ValidationValue =
|
|
68
|
+
| { isOk: true }
|
|
69
|
+
| {
|
|
70
|
+
isOk: false;
|
|
71
|
+
message: string;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
function validateUniqueness(uniqueness: Uniqueness): ValidationValue {
|
|
75
|
+
if (typeof uniqueness === "number" && !Number.isInteger(uniqueness)) {
|
|
76
|
+
return {
|
|
77
|
+
isOk: false,
|
|
78
|
+
message: "Uniqueness cannot be a non-integer number, got " + uniqueness,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (Array.isArray(uniqueness)) {
|
|
83
|
+
return {
|
|
84
|
+
isOk: false,
|
|
85
|
+
message: "Uniqueness cannot be an array, got " + uniqueness,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (typeof uniqueness === "object" && uniqueness !== null) {
|
|
90
|
+
for (let [key, value] of Object.entries(uniqueness)) {
|
|
91
|
+
if (typeof value !== "string") {
|
|
92
|
+
return {
|
|
93
|
+
isOk: false,
|
|
94
|
+
message:
|
|
95
|
+
"Uniqueness object values must be a string, got " +
|
|
96
|
+
value +
|
|
97
|
+
" for key " +
|
|
98
|
+
key,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return { isOk: true };
|
|
104
|
+
}
|
|
105
|
+
|
|
54
106
|
export function idforHeader(
|
|
55
107
|
header: CoValueHeader,
|
|
56
108
|
crypto: CryptoProvider,
|
|
@@ -197,6 +249,8 @@ export class CoValueCore {
|
|
|
197
249
|
// context
|
|
198
250
|
readonly node: LocalNode;
|
|
199
251
|
private readonly crypto: CryptoProvider;
|
|
252
|
+
// Whether the coValue is deleted
|
|
253
|
+
public isDeleted: boolean = false;
|
|
200
254
|
|
|
201
255
|
// state
|
|
202
256
|
id: RawCoID;
|
|
@@ -492,6 +546,15 @@ export class CoValueCore {
|
|
|
492
546
|
skipVerify?: boolean,
|
|
493
547
|
) {
|
|
494
548
|
if (!skipVerify) {
|
|
549
|
+
const validation = validateUniqueness(header.uniqueness);
|
|
550
|
+
if (!validation.isOk) {
|
|
551
|
+
logger.error("Invalid uniqueness", {
|
|
552
|
+
header,
|
|
553
|
+
errorMessage: validation.message,
|
|
554
|
+
});
|
|
555
|
+
return false;
|
|
556
|
+
}
|
|
557
|
+
|
|
495
558
|
const expectedId = idforHeader(header, this.node.crypto);
|
|
496
559
|
|
|
497
560
|
if (this.id !== expectedId) {
|
|
@@ -559,6 +622,33 @@ export class CoValueCore {
|
|
|
559
622
|
return this.verified?.immutableKnownState() ?? emptyKnownState(this.id);
|
|
560
623
|
}
|
|
561
624
|
|
|
625
|
+
/**
|
|
626
|
+
* Returns a known state message to signal to the peer that the coValue doesn't need to be synced anymore
|
|
627
|
+
*
|
|
628
|
+
* Implemented to be backward compatible with clients that don't support deleted coValues
|
|
629
|
+
*/
|
|
630
|
+
stopSyncingKnownStateMessage(
|
|
631
|
+
peerKnownState: CoValueKnownState | undefined,
|
|
632
|
+
): KnownStateMessage {
|
|
633
|
+
if (!peerKnownState) {
|
|
634
|
+
return {
|
|
635
|
+
action: "known",
|
|
636
|
+
...this.knownState(),
|
|
637
|
+
};
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
const knownState = cloneKnownState(this.knownState());
|
|
641
|
+
|
|
642
|
+
// We combine everything for backward compatibility with clients that don't support deleted coValues
|
|
643
|
+
// This way they won't try to sync their own content that we have discarded because the coValue is deleted
|
|
644
|
+
combineKnownStateSessions(knownState.sessions, peerKnownState.sessions);
|
|
645
|
+
|
|
646
|
+
return {
|
|
647
|
+
action: "known",
|
|
648
|
+
...knownState,
|
|
649
|
+
};
|
|
650
|
+
}
|
|
651
|
+
|
|
562
652
|
get meta(): JsonValue {
|
|
563
653
|
return this.verified?.header.meta ?? null;
|
|
564
654
|
}
|
|
@@ -593,6 +683,107 @@ export class CoValueCore {
|
|
|
593
683
|
}
|
|
594
684
|
}
|
|
595
685
|
|
|
686
|
+
#isDeleteTransaction(
|
|
687
|
+
sessionID: SessionID,
|
|
688
|
+
newTransactions: Transaction[],
|
|
689
|
+
skipVerify: boolean,
|
|
690
|
+
) {
|
|
691
|
+
if (!this.verified) {
|
|
692
|
+
return {
|
|
693
|
+
value: false,
|
|
694
|
+
};
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
// Detect + validate delete transactions during ingestion
|
|
698
|
+
// Delete transactions are:
|
|
699
|
+
// - in a delete session (sessionID ends with `$`)
|
|
700
|
+
// - trusting (unencrypted)
|
|
701
|
+
// - have meta `{ deleted: true }`
|
|
702
|
+
let deleteTransaction: Transaction | undefined = undefined;
|
|
703
|
+
|
|
704
|
+
if (isDeleteSessionID(sessionID)) {
|
|
705
|
+
const txCount =
|
|
706
|
+
this.verified.sessions.get(sessionID)?.transactions.length ?? 0;
|
|
707
|
+
if (txCount > 0 || newTransactions.length > 1) {
|
|
708
|
+
return {
|
|
709
|
+
value: true,
|
|
710
|
+
err: {
|
|
711
|
+
type: "DeleteTransactionRejected",
|
|
712
|
+
id: this.id,
|
|
713
|
+
sessionID,
|
|
714
|
+
reason: "InvalidDeleteTransaction",
|
|
715
|
+
error: new Error(
|
|
716
|
+
"Delete transaction must be the only transaction in the session",
|
|
717
|
+
),
|
|
718
|
+
} as const,
|
|
719
|
+
};
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
const firstTransaction = newTransactions[0];
|
|
723
|
+
const deleteMarker =
|
|
724
|
+
firstTransaction && this.#getDeleteMarker(firstTransaction);
|
|
725
|
+
|
|
726
|
+
if (deleteMarker) {
|
|
727
|
+
deleteTransaction = firstTransaction;
|
|
728
|
+
|
|
729
|
+
if (deleteMarker.deleted !== this.id) {
|
|
730
|
+
return {
|
|
731
|
+
value: true,
|
|
732
|
+
err: {
|
|
733
|
+
type: "DeleteTransactionRejected",
|
|
734
|
+
id: this.id,
|
|
735
|
+
sessionID,
|
|
736
|
+
reason: "InvalidDeleteTransaction",
|
|
737
|
+
error: new Error(
|
|
738
|
+
`Delete transaction ID mismatch: expected ${this.id}, got ${deleteMarker.deleted}`,
|
|
739
|
+
),
|
|
740
|
+
} as const,
|
|
741
|
+
};
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
if (this.isGroupOrAccount()) {
|
|
746
|
+
return {
|
|
747
|
+
value: true,
|
|
748
|
+
err: {
|
|
749
|
+
type: "DeleteTransactionRejected",
|
|
750
|
+
id: this.id,
|
|
751
|
+
sessionID,
|
|
752
|
+
reason: "CoValueNotDeletable",
|
|
753
|
+
error: new Error("Cannot delete Group or Account coValues"),
|
|
754
|
+
},
|
|
755
|
+
} as const;
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
if (!skipVerify && deleteTransaction) {
|
|
760
|
+
const author = accountOrAgentIDfromSessionID(sessionID);
|
|
761
|
+
|
|
762
|
+
const permission = this.#canAuthorDeleteCoValueAtTime(
|
|
763
|
+
author,
|
|
764
|
+
deleteTransaction.madeAt,
|
|
765
|
+
);
|
|
766
|
+
|
|
767
|
+
if (!permission.ok) {
|
|
768
|
+
return {
|
|
769
|
+
value: true,
|
|
770
|
+
err: {
|
|
771
|
+
type: "DeleteTransactionRejected",
|
|
772
|
+
id: this.id,
|
|
773
|
+
sessionID,
|
|
774
|
+
author,
|
|
775
|
+
reason: permission.reason,
|
|
776
|
+
error: new Error(permission.message),
|
|
777
|
+
},
|
|
778
|
+
} as const;
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
return {
|
|
783
|
+
value: Boolean(deleteTransaction),
|
|
784
|
+
};
|
|
785
|
+
}
|
|
786
|
+
|
|
596
787
|
/**
|
|
597
788
|
* Apply new transactions that were not generated by the current node to the CoValue
|
|
598
789
|
*/
|
|
@@ -602,8 +793,22 @@ export class CoValueCore {
|
|
|
602
793
|
newSignature: Signature,
|
|
603
794
|
skipVerify: boolean = false,
|
|
604
795
|
) {
|
|
796
|
+
if (newTransactions.length === 0) {
|
|
797
|
+
return;
|
|
798
|
+
}
|
|
799
|
+
|
|
605
800
|
let signerID: SignerID | undefined;
|
|
606
801
|
|
|
802
|
+
// sync should never try to add transactions to a deleted coValue
|
|
803
|
+
// this can only happen if `tryAddTransactions` is called directly, without going through `handleNewContent`
|
|
804
|
+
if (this.isDeleted && !isDeleteSessionID(sessionID)) {
|
|
805
|
+
return {
|
|
806
|
+
type: "CoValueDeleted",
|
|
807
|
+
id: this.id,
|
|
808
|
+
error: new Error("Cannot add transactions to a deleted coValue"),
|
|
809
|
+
} as const;
|
|
810
|
+
}
|
|
811
|
+
|
|
607
812
|
if (!skipVerify) {
|
|
608
813
|
const result = this.node.resolveAccountAgent(
|
|
609
814
|
accountOrAgentIDfromSessionID(sessionID),
|
|
@@ -629,6 +834,16 @@ export class CoValueCore {
|
|
|
629
834
|
};
|
|
630
835
|
}
|
|
631
836
|
|
|
837
|
+
const isDeleteTransaction = this.#isDeleteTransaction(
|
|
838
|
+
sessionID,
|
|
839
|
+
newTransactions,
|
|
840
|
+
skipVerify,
|
|
841
|
+
);
|
|
842
|
+
|
|
843
|
+
if (isDeleteTransaction.err) {
|
|
844
|
+
return isDeleteTransaction.err;
|
|
845
|
+
}
|
|
846
|
+
|
|
632
847
|
try {
|
|
633
848
|
this.verified.tryAddTransactions(
|
|
634
849
|
sessionID,
|
|
@@ -638,6 +853,13 @@ export class CoValueCore {
|
|
|
638
853
|
skipVerify,
|
|
639
854
|
);
|
|
640
855
|
|
|
856
|
+
// Mark deleted state when a delete marker transaction is accepted.
|
|
857
|
+
// - In skipVerify mode (storage shards), we accept + mark without permission checks.
|
|
858
|
+
// - In verify mode, we only reach here if the delete permission check passed.
|
|
859
|
+
if (isDeleteTransaction.value) {
|
|
860
|
+
this.#markAsDeleted();
|
|
861
|
+
}
|
|
862
|
+
|
|
641
863
|
this.processNewTransactions();
|
|
642
864
|
this.scheduleNotifyUpdate();
|
|
643
865
|
this.invalidateDependants();
|
|
@@ -646,6 +868,78 @@ export class CoValueCore {
|
|
|
646
868
|
}
|
|
647
869
|
}
|
|
648
870
|
|
|
871
|
+
#markAsDeleted() {
|
|
872
|
+
this.isDeleted = true;
|
|
873
|
+
this.verified?.markAsDeleted();
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
#getDeleteMarker(tx: Transaction): { deleted: RawCoID } | undefined {
|
|
877
|
+
if (tx.privacy !== "trusting") {
|
|
878
|
+
return;
|
|
879
|
+
}
|
|
880
|
+
if (!tx.meta) {
|
|
881
|
+
return;
|
|
882
|
+
}
|
|
883
|
+
const meta = safeParseJSON(tx.meta);
|
|
884
|
+
|
|
885
|
+
return meta && typeof meta.deleted === "string"
|
|
886
|
+
? (meta as { deleted: RawCoID })
|
|
887
|
+
: undefined;
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
#canAuthorDeleteCoValueAtTime(
|
|
891
|
+
author: RawAccountID | AgentID,
|
|
892
|
+
madeAt: number,
|
|
893
|
+
):
|
|
894
|
+
| { ok: true }
|
|
895
|
+
| {
|
|
896
|
+
ok: false;
|
|
897
|
+
reason: DeleteTransactionRejectedError["reason"];
|
|
898
|
+
message: string;
|
|
899
|
+
} {
|
|
900
|
+
if (!this.verified) {
|
|
901
|
+
return {
|
|
902
|
+
ok: false,
|
|
903
|
+
reason: "CannotVerifyPermissions",
|
|
904
|
+
message: "Cannot verify delete permissions without verified state",
|
|
905
|
+
};
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
if (this.isGroupOrAccount()) {
|
|
909
|
+
return {
|
|
910
|
+
ok: false,
|
|
911
|
+
reason: "CoValueNotDeletable",
|
|
912
|
+
message: "Cannot delete Group or Account coValues",
|
|
913
|
+
};
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
const group = this.safeGetGroup();
|
|
917
|
+
|
|
918
|
+
// Today, delete permission is defined in terms of group-admin on the owning group.
|
|
919
|
+
// If we cannot derive that (non-owned coValues), we reject the delete when verification is required.
|
|
920
|
+
if (!group) {
|
|
921
|
+
return {
|
|
922
|
+
ok: false,
|
|
923
|
+
reason: "CannotVerifyPermissions",
|
|
924
|
+
message:
|
|
925
|
+
"Cannot verify delete permissions for coValues not owned by a group",
|
|
926
|
+
};
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
const groupAtTime = group.atTime(madeAt);
|
|
930
|
+
const role = groupAtTime.roleOfInternal(author);
|
|
931
|
+
|
|
932
|
+
if (role !== "admin") {
|
|
933
|
+
return {
|
|
934
|
+
ok: false,
|
|
935
|
+
reason: "NotAdmin",
|
|
936
|
+
message: "Delete transaction rejected: author is not an admin",
|
|
937
|
+
};
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
return { ok: true };
|
|
941
|
+
}
|
|
942
|
+
|
|
649
943
|
notifyDependants() {
|
|
650
944
|
if (!this.isGroup()) {
|
|
651
945
|
return;
|
|
@@ -743,6 +1037,70 @@ export class CoValueCore {
|
|
|
743
1037
|
};
|
|
744
1038
|
}
|
|
745
1039
|
|
|
1040
|
+
validateDeletePermissions() {
|
|
1041
|
+
if (!this.verified) {
|
|
1042
|
+
return {
|
|
1043
|
+
ok: false,
|
|
1044
|
+
reason: "CannotVerifyPermissions",
|
|
1045
|
+
message: "Cannot verify delete permissions without verified state",
|
|
1046
|
+
};
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
if (this.isGroupOrAccount()) {
|
|
1050
|
+
return {
|
|
1051
|
+
ok: false,
|
|
1052
|
+
reason: "CoValueNotDeletable",
|
|
1053
|
+
message: "Cannot delete Group or Account coValues",
|
|
1054
|
+
};
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
const group = this.safeGetGroup();
|
|
1058
|
+
if (!group) {
|
|
1059
|
+
return {
|
|
1060
|
+
ok: false,
|
|
1061
|
+
reason: "CannotVerifyPermissions",
|
|
1062
|
+
message:
|
|
1063
|
+
"Cannot verify delete permissions for coValues not owned by a group",
|
|
1064
|
+
};
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
const role = group.myRole();
|
|
1068
|
+
if (role !== "admin") {
|
|
1069
|
+
return {
|
|
1070
|
+
ok: false,
|
|
1071
|
+
reason: "NotAdmin",
|
|
1072
|
+
message:
|
|
1073
|
+
"The current account lacks admin permissions to delete this coValue",
|
|
1074
|
+
};
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
return { ok: true };
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
/**
|
|
1081
|
+
* Creates a delete marker transaction for this CoValue and sets the coValue as deleted
|
|
1082
|
+
*
|
|
1083
|
+
* Constraints:
|
|
1084
|
+
* - Account and Group CoValues cannot be deleted.
|
|
1085
|
+
* - Only admins can delete a coValue.
|
|
1086
|
+
*/
|
|
1087
|
+
deleteCoValue() {
|
|
1088
|
+
if (this.isDeleted) {
|
|
1089
|
+
return;
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
const result = this.validateDeletePermissions();
|
|
1093
|
+
if (!result.ok) {
|
|
1094
|
+
throw new Error(result.message);
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
this.makeTransaction(
|
|
1098
|
+
[], // Empty changes array
|
|
1099
|
+
"trusting", // Unencrypted
|
|
1100
|
+
{ deleted: this.id }, // Delete metadata
|
|
1101
|
+
);
|
|
1102
|
+
}
|
|
1103
|
+
|
|
746
1104
|
/**
|
|
747
1105
|
* Creates a new transaction with local changes and syncs it to all peers
|
|
748
1106
|
*/
|
|
@@ -757,11 +1115,17 @@ export class CoValueCore {
|
|
|
757
1115
|
"CoValueCore: makeTransaction called on coValue without verified state",
|
|
758
1116
|
);
|
|
759
1117
|
}
|
|
1118
|
+
const isDeleteTransaction = Boolean(meta?.deleted);
|
|
760
1119
|
|
|
761
|
-
|
|
1120
|
+
if (this.isDeleted && !isDeleteTransaction) {
|
|
1121
|
+
logger.error("Cannot make transaction on a deleted coValue", {
|
|
1122
|
+
id: this.id,
|
|
1123
|
+
});
|
|
1124
|
+
return false;
|
|
1125
|
+
}
|
|
762
1126
|
|
|
763
1127
|
// This is an ugly hack to get a unique but stable session ID for editing the current account
|
|
764
|
-
|
|
1128
|
+
let sessionID =
|
|
765
1129
|
this.verified.header.meta?.type === "account"
|
|
766
1130
|
? (this.node.currentSessionID.replace(
|
|
767
1131
|
this.node.getCurrentAgent().id,
|
|
@@ -769,6 +1133,12 @@ export class CoValueCore {
|
|
|
769
1133
|
) as SessionID)
|
|
770
1134
|
: this.node.currentSessionID;
|
|
771
1135
|
|
|
1136
|
+
if (isDeleteTransaction) {
|
|
1137
|
+
sessionID = this.crypto.newDeleteSessionID(
|
|
1138
|
+
this.node.getCurrentAccountOrAgentID(),
|
|
1139
|
+
);
|
|
1140
|
+
}
|
|
1141
|
+
|
|
772
1142
|
const signerAgent = this.node.getCurrentAgent();
|
|
773
1143
|
|
|
774
1144
|
let result: { signature: Signature; transaction: Transaction };
|
|
@@ -801,6 +1171,10 @@ export class CoValueCore {
|
|
|
801
1171
|
);
|
|
802
1172
|
}
|
|
803
1173
|
|
|
1174
|
+
if (isDeleteTransaction) {
|
|
1175
|
+
this.#markAsDeleted();
|
|
1176
|
+
}
|
|
1177
|
+
|
|
804
1178
|
const { transaction } = result;
|
|
805
1179
|
|
|
806
1180
|
// Assign pre-parsed meta and changes to skip the parse/decrypt operation when loading
|
|
@@ -1266,6 +1640,14 @@ export class CoValueCore {
|
|
|
1266
1640
|
this.dependant.add(dependant);
|
|
1267
1641
|
}
|
|
1268
1642
|
|
|
1643
|
+
isGroupOrAccount() {
|
|
1644
|
+
if (!this.verified) {
|
|
1645
|
+
return false;
|
|
1646
|
+
}
|
|
1647
|
+
|
|
1648
|
+
return this.verified.header.ruleset.type === "group";
|
|
1649
|
+
}
|
|
1650
|
+
|
|
1269
1651
|
isGroup() {
|
|
1270
1652
|
if (!this.verified) {
|
|
1271
1653
|
return false;
|
|
@@ -1645,9 +2027,19 @@ export type TriedToAddTransactionsWithoutSignerIDError = {
|
|
|
1645
2027
|
sessionID: SessionID;
|
|
1646
2028
|
};
|
|
1647
2029
|
|
|
2030
|
+
export type DeleteTransactionRejectedError = {
|
|
2031
|
+
type: "DeleteTransactionRejected";
|
|
2032
|
+
id: RawCoID;
|
|
2033
|
+
sessionID: SessionID;
|
|
2034
|
+
author: RawAccountID | AgentID;
|
|
2035
|
+
reason: "NotAdmin" | "CoValueNotDeletable" | "CannotVerifyPermissions";
|
|
2036
|
+
error: Error;
|
|
2037
|
+
};
|
|
2038
|
+
|
|
1648
2039
|
export type TryAddTransactionsError =
|
|
1649
2040
|
| TriedToAddTransactionsWithoutVerifiedStateErrpr
|
|
1650
2041
|
| TriedToAddTransactionsWithoutSignerIDError
|
|
1651
2042
|
| ResolveAccountAgentError
|
|
1652
2043
|
| InvalidHashError
|
|
1653
|
-
| InvalidSignatureError
|
|
2044
|
+
| InvalidSignatureError
|
|
2045
|
+
| DeleteTransactionRejectedError;
|
|
@@ -14,12 +14,16 @@ import {
|
|
|
14
14
|
Signature,
|
|
15
15
|
SignerID,
|
|
16
16
|
} from "../crypto/crypto.js";
|
|
17
|
-
import {
|
|
17
|
+
import {
|
|
18
|
+
isDeleteSessionID,
|
|
19
|
+
RawCoID,
|
|
20
|
+
SessionID,
|
|
21
|
+
TransactionID,
|
|
22
|
+
} from "../ids.js";
|
|
18
23
|
import { Stringified } from "../jsonStringify.js";
|
|
19
24
|
import { JsonObject, JsonValue } from "../jsonValue.js";
|
|
20
25
|
import { PermissionsDef as RulesetDef } from "../permissions.js";
|
|
21
26
|
import { NewContentMessage } from "../sync.js";
|
|
22
|
-
import { TryAddTransactionsError } from "./coValueCore.js";
|
|
23
27
|
import { SessionMap } from "./SessionMap.js";
|
|
24
28
|
import { ControlledAccountOrAgent } from "../coValues/account.js";
|
|
25
29
|
import {
|
|
@@ -35,10 +39,19 @@ export type CoValueHeader = {
|
|
|
35
39
|
} & CoValueUniqueness;
|
|
36
40
|
|
|
37
41
|
export type CoValueUniqueness = {
|
|
38
|
-
uniqueness:
|
|
42
|
+
uniqueness: Uniqueness;
|
|
39
43
|
createdAt?: `2${string}` | null;
|
|
40
44
|
};
|
|
41
45
|
|
|
46
|
+
export type Uniqueness =
|
|
47
|
+
| string
|
|
48
|
+
| boolean
|
|
49
|
+
| null
|
|
50
|
+
| undefined
|
|
51
|
+
| {
|
|
52
|
+
[key: string]: string;
|
|
53
|
+
};
|
|
54
|
+
|
|
42
55
|
export type PrivateTransaction = {
|
|
43
56
|
privacy: "private";
|
|
44
57
|
madeAt: number;
|
|
@@ -63,6 +76,7 @@ export class VerifiedState {
|
|
|
63
76
|
public lastAccessed: number | undefined;
|
|
64
77
|
public branchSourceId?: RawCoID;
|
|
65
78
|
public branchName?: string;
|
|
79
|
+
private isDeleted: boolean = false;
|
|
66
80
|
|
|
67
81
|
constructor(
|
|
68
82
|
id: RawCoID,
|
|
@@ -87,6 +101,11 @@ export class VerifiedState {
|
|
|
87
101
|
);
|
|
88
102
|
}
|
|
89
103
|
|
|
104
|
+
markAsDeleted() {
|
|
105
|
+
this.isDeleted = true;
|
|
106
|
+
this.sessions.markAsDeleted();
|
|
107
|
+
}
|
|
108
|
+
|
|
90
109
|
tryAddTransactions(
|
|
91
110
|
sessionID: SessionID,
|
|
92
111
|
signerID: SignerID | undefined,
|
|
@@ -199,6 +218,10 @@ export class VerifiedState {
|
|
|
199
218
|
const sessionSent = knownState?.sessions;
|
|
200
219
|
|
|
201
220
|
for (const [sessionID, log] of this.sessions.sessions) {
|
|
221
|
+
if (this.isDeleted && !isDeleteSessionID(sessionID)) {
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
|
|
202
225
|
const startFrom = sessionSent?.[sessionID] ?? 0;
|
|
203
226
|
|
|
204
227
|
let currentSessionSize = 0;
|
package/src/coValues/coList.ts
CHANGED
|
@@ -59,14 +59,14 @@ export class RawCoList<
|
|
|
59
59
|
afterStart: OpID[] = [];
|
|
60
60
|
beforeEnd: OpID[] = [];
|
|
61
61
|
insertions: {
|
|
62
|
-
[sessionID:
|
|
62
|
+
[sessionID: SessionIndex]: {
|
|
63
63
|
[txIdx: number]: {
|
|
64
64
|
[changeIdx: number]: InsertionEntry<Item>;
|
|
65
65
|
};
|
|
66
66
|
};
|
|
67
67
|
} = {};
|
|
68
68
|
deletionsByInsertion: {
|
|
69
|
-
[deletedSessionID:
|
|
69
|
+
[deletedSessionID: SessionIndex]: {
|
|
70
70
|
[deletedTxIdx: number]: {
|
|
71
71
|
[deletedChangeIdx: number]: DeletionEntry[];
|
|
72
72
|
};
|
|
@@ -700,7 +700,9 @@ export class RawCoList<
|
|
|
700
700
|
}
|
|
701
701
|
}
|
|
702
702
|
|
|
703
|
-
|
|
703
|
+
type SessionIndex = SessionID | `${SessionID}_branch_${RawCoID}`;
|
|
704
|
+
|
|
705
|
+
function getSessionIndex(txID: TransactionID): SessionIndex {
|
|
704
706
|
if (txID.branch) {
|
|
705
707
|
return `${txID.sessionID}_branch_${txID.branch}`;
|
|
706
708
|
}
|
package/src/coValues/group.ts
CHANGED
|
@@ -1203,12 +1203,11 @@ export class RawGroup<
|
|
|
1203
1203
|
|
|
1204
1204
|
this.set(memberKey, "revoked", "trusting");
|
|
1205
1205
|
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
// }
|
|
1206
|
+
if (this.get(memberKey) !== "revoked") {
|
|
1207
|
+
throw new Error(
|
|
1208
|
+
`Failed to revoke role to ${memberKey} (role of current account is ${this.myRole()})`,
|
|
1209
|
+
);
|
|
1210
|
+
}
|
|
1212
1211
|
}
|
|
1213
1212
|
|
|
1214
1213
|
/**
|
package/src/config.ts
CHANGED
|
@@ -7,21 +7,12 @@
|
|
|
7
7
|
**/
|
|
8
8
|
export const TRANSACTION_CONFIG = {
|
|
9
9
|
MAX_RECOMMENDED_TX_SIZE: 100 * 1024,
|
|
10
|
-
/**
|
|
11
|
-
* Messages larger than this will be rejected when creating a transaction.
|
|
12
|
-
* The current limit is set at 1MB because that's the limit imposed by Cloudflare to Websocket messages.
|
|
13
|
-
*/
|
|
14
|
-
MAX_TX_SIZE_BYTES: 1 * 1024 * 1024,
|
|
15
10
|
};
|
|
16
11
|
|
|
17
12
|
export function setMaxRecommendedTxSize(size: number) {
|
|
18
13
|
TRANSACTION_CONFIG.MAX_RECOMMENDED_TX_SIZE = size;
|
|
19
14
|
}
|
|
20
15
|
|
|
21
|
-
export function setMaxTxSizeBytes(size: number) {
|
|
22
|
-
TRANSACTION_CONFIG.MAX_TX_SIZE_BYTES = size;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
16
|
export const CO_VALUE_LOADING_CONFIG = {
|
|
26
17
|
MAX_RETRIES: 1,
|
|
27
18
|
TIMEOUT: 30_000,
|