cojson 0.18.6 → 0.18.7
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 +11 -0
- package/dist/coValueContentMessage.d.ts +2 -0
- package/dist/coValueContentMessage.d.ts.map +1 -1
- package/dist/coValueContentMessage.js +7 -0
- package/dist/coValueContentMessage.js.map +1 -1
- package/dist/coValueCore/SessionMap.d.ts +2 -2
- package/dist/coValueCore/SessionMap.d.ts.map +1 -1
- package/dist/coValueCore/SessionMap.js +2 -4
- package/dist/coValueCore/SessionMap.js.map +1 -1
- package/dist/coValueCore/branching.d.ts +31 -9
- package/dist/coValueCore/branching.d.ts.map +1 -1
- package/dist/coValueCore/branching.js +50 -100
- package/dist/coValueCore/branching.js.map +1 -1
- package/dist/coValueCore/coValueCore.d.ts +12 -8
- package/dist/coValueCore/coValueCore.d.ts.map +1 -1
- package/dist/coValueCore/coValueCore.js +93 -23
- package/dist/coValueCore/coValueCore.js.map +1 -1
- package/dist/coValueCore/verifiedState.d.ts +4 -2
- package/dist/coValueCore/verifiedState.d.ts.map +1 -1
- package/dist/coValueCore/verifiedState.js +6 -4
- package/dist/coValueCore/verifiedState.js.map +1 -1
- package/dist/coValues/coList.d.ts.map +1 -1
- package/dist/coValues/coList.js +10 -1
- package/dist/coValues/coList.js.map +1 -1
- package/dist/coValues/coMap.d.ts +2 -2
- package/dist/coValues/coMap.d.ts.map +1 -1
- package/dist/coValues/coMap.js +8 -8
- package/dist/coValues/coMap.js.map +1 -1
- package/dist/coValues/group.d.ts.map +1 -1
- package/dist/coValues/group.js +14 -1
- package/dist/coValues/group.js.map +1 -1
- package/dist/config.d.ts +6 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +8 -0
- package/dist/config.js.map +1 -1
- package/dist/crypto/PureJSCrypto.d.ts.map +1 -1
- package/dist/crypto/PureJSCrypto.js +14 -6
- package/dist/crypto/PureJSCrypto.js.map +1 -1
- package/dist/exports.d.ts +3 -2
- package/dist/exports.d.ts.map +1 -1
- package/dist/exports.js +2 -2
- package/dist/exports.js.map +1 -1
- package/dist/localNode.d.ts.map +1 -1
- package/dist/localNode.js +7 -2
- package/dist/localNode.js.map +1 -1
- package/dist/storage/storageAsync.d.ts.map +1 -1
- package/dist/storage/storageAsync.js.map +1 -1
- package/dist/sync.d.ts +1 -3
- package/dist/sync.d.ts.map +1 -1
- package/dist/sync.js +8 -18
- package/dist/sync.js.map +1 -1
- package/dist/tests/branching.test.js +107 -9
- package/dist/tests/branching.test.js.map +1 -1
- package/dist/tests/coValueCore.test.js +45 -1
- package/dist/tests/coValueCore.test.js.map +1 -1
- package/dist/tests/sync.content.test.d.ts +2 -0
- package/dist/tests/sync.content.test.d.ts.map +1 -0
- package/dist/tests/sync.content.test.js +120 -0
- package/dist/tests/sync.content.test.js.map +1 -0
- package/dist/tests/sync.load.test.js +2 -2
- package/dist/tests/sync.storage.test.js +1 -1
- package/dist/tests/sync.upload.test.js +2 -2
- package/package.json +2 -2
- package/src/coValueContentMessage.ts +13 -0
- package/src/coValueCore/SessionMap.ts +2 -2
- package/src/coValueCore/branching.ts +94 -149
- package/src/coValueCore/coValueCore.ts +121 -27
- package/src/coValueCore/verifiedState.ts +8 -0
- package/src/coValues/coList.ts +12 -1
- package/src/coValues/coMap.ts +10 -12
- package/src/coValues/group.ts +14 -1
- package/src/config.ts +9 -0
- package/src/crypto/PureJSCrypto.ts +25 -13
- package/src/exports.ts +7 -1
- package/src/localNode.ts +8 -2
- package/src/storage/storageAsync.ts +0 -1
- package/src/sync.ts +8 -32
- package/src/tests/branching.test.ts +158 -9
- package/src/tests/coValueCore.test.ts +62 -2
- package/src/tests/sync.content.test.ts +153 -0
- package/src/tests/sync.load.test.ts +2 -2
- package/src/tests/sync.storage.test.ts +1 -1
- package/src/tests/sync.upload.test.ts +2 -2
|
@@ -5,10 +5,10 @@ import type { RawCoValue } from "../coValue.js";
|
|
|
5
5
|
import type { ControlledAccountOrAgent } from "../coValues/account.js";
|
|
6
6
|
import type { RawGroup } from "../coValues/group.js";
|
|
7
7
|
import { CO_VALUE_LOADING_CONFIG } from "../config.js";
|
|
8
|
+
import { validateTxSizeLimitInBytes } from "../coValueContentMessage.js";
|
|
8
9
|
import { coreToCoValue } from "../coreToCoValue.js";
|
|
9
10
|
import {
|
|
10
11
|
CryptoProvider,
|
|
11
|
-
Encrypted,
|
|
12
12
|
Hash,
|
|
13
13
|
KeyID,
|
|
14
14
|
KeySecret,
|
|
@@ -17,7 +17,6 @@ import {
|
|
|
17
17
|
} from "../crypto/crypto.js";
|
|
18
18
|
import { AgentID, RawCoID, SessionID, TransactionID } from "../ids.js";
|
|
19
19
|
import { JsonObject, JsonValue } from "../jsonValue.js";
|
|
20
|
-
import { parseJSON, safeParseJSON } from "../jsonStringify.js";
|
|
21
20
|
import { LocalNode, ResolveAccountAgentError } from "../localNode.js";
|
|
22
21
|
import { logger } from "../logger.js";
|
|
23
22
|
import { determineValidTransactions } from "../permissions.js";
|
|
@@ -29,10 +28,14 @@ import { CoValueHeader, Transaction, VerifiedState } from "./verifiedState.js";
|
|
|
29
28
|
import { SessionMap } from "./SessionMap.js";
|
|
30
29
|
import {
|
|
31
30
|
MergeCommit,
|
|
31
|
+
BranchPointerCommit,
|
|
32
|
+
MergedTransactionMetadata,
|
|
32
33
|
createBranch,
|
|
33
34
|
getBranchId,
|
|
35
|
+
getBranchOwnerId,
|
|
34
36
|
getBranchSource,
|
|
35
37
|
mergeBranch,
|
|
38
|
+
BranchStartCommit,
|
|
36
39
|
} from "./branching.js";
|
|
37
40
|
import { type RawAccountID } from "../coValues/account.js";
|
|
38
41
|
import { decodeTransactionChangesAndMeta } from "./decodeTransactionChangesAndMeta.js";
|
|
@@ -70,6 +73,9 @@ export type VerifiedTransaction = {
|
|
|
70
73
|
|
|
71
74
|
// True if the meta information has been parsed and loaded in the CoValueCore
|
|
72
75
|
hasMetaBeenParsed: boolean;
|
|
76
|
+
|
|
77
|
+
// The previous verified transaction for the same session
|
|
78
|
+
previous: VerifiedTransaction | undefined;
|
|
73
79
|
};
|
|
74
80
|
|
|
75
81
|
export type DecryptedTransaction = {
|
|
@@ -600,6 +606,7 @@ export class CoValueCore {
|
|
|
600
606
|
changes: JsonValue[],
|
|
601
607
|
privacy: "private" | "trusting",
|
|
602
608
|
meta?: JsonObject,
|
|
609
|
+
madeAt?: number,
|
|
603
610
|
): boolean {
|
|
604
611
|
if (!this.verified) {
|
|
605
612
|
throw new Error(
|
|
@@ -607,6 +614,8 @@ export class CoValueCore {
|
|
|
607
614
|
);
|
|
608
615
|
}
|
|
609
616
|
|
|
617
|
+
validateTxSizeLimitInBytes(changes);
|
|
618
|
+
|
|
610
619
|
// This is an ugly hack to get a unique but stable session ID for editing the current account
|
|
611
620
|
const sessionID =
|
|
612
621
|
this.verified.header.meta?.type === "account"
|
|
@@ -634,6 +643,7 @@ export class CoValueCore {
|
|
|
634
643
|
keyID,
|
|
635
644
|
keySecret,
|
|
636
645
|
meta,
|
|
646
|
+
madeAt ?? Date.now(),
|
|
637
647
|
);
|
|
638
648
|
} else {
|
|
639
649
|
result = this.verified.makeNewTrustingTransaction(
|
|
@@ -641,6 +651,7 @@ export class CoValueCore {
|
|
|
641
651
|
signerAgent,
|
|
642
652
|
changes,
|
|
643
653
|
meta,
|
|
654
|
+
madeAt ?? Date.now(),
|
|
644
655
|
);
|
|
645
656
|
}
|
|
646
657
|
|
|
@@ -689,12 +700,13 @@ export class CoValueCore {
|
|
|
689
700
|
}
|
|
690
701
|
|
|
691
702
|
// The starting point of the branch, in case this CoValue is a branch
|
|
692
|
-
branchStart:
|
|
693
|
-
| { branch: CoValueKnownState["sessions"]; madeAt: number }
|
|
694
|
-
| undefined;
|
|
703
|
+
branchStart: { from: BranchStartCommit["from"]; madeAt: number } | undefined;
|
|
695
704
|
|
|
696
705
|
// The list of merge commits that have been made
|
|
697
|
-
mergeCommits:
|
|
706
|
+
mergeCommits: MergeCommit[] = [];
|
|
707
|
+
branches: BranchPointerCommit[] = [];
|
|
708
|
+
earliestTxMadeAt: number = Number.MAX_SAFE_INTEGER;
|
|
709
|
+
latestTxMadeAt: number = 0;
|
|
698
710
|
|
|
699
711
|
// Reset the parsed transactions and branches, to validate them again from scratch when the group is updated
|
|
700
712
|
resetParsedTransactions() {
|
|
@@ -710,6 +722,11 @@ export class CoValueCore {
|
|
|
710
722
|
verifiedTransactions: VerifiedTransaction[] = [];
|
|
711
723
|
private verifiedTransactionsKnownSessions: CoValueKnownState["sessions"] = {};
|
|
712
724
|
|
|
725
|
+
private lastVerifiedTransactionBySessionID: Record<
|
|
726
|
+
SessionID,
|
|
727
|
+
VerifiedTransaction
|
|
728
|
+
> = {};
|
|
729
|
+
|
|
713
730
|
/**
|
|
714
731
|
* Loads the new transaction from the SessionMap into verifiedTransactions as a VerifiedTransaction.
|
|
715
732
|
*
|
|
@@ -752,7 +769,7 @@ export class CoValueCore {
|
|
|
752
769
|
txIndex,
|
|
753
770
|
};
|
|
754
771
|
|
|
755
|
-
|
|
772
|
+
const verifiedTransaction = {
|
|
756
773
|
author: accountOrAgentIDfromSessionID(sessionID),
|
|
757
774
|
txID,
|
|
758
775
|
madeAt: tx.madeAt,
|
|
@@ -764,7 +781,20 @@ export class CoValueCore {
|
|
|
764
781
|
hasInvalidMeta: false,
|
|
765
782
|
hasMetaBeenParsed: false,
|
|
766
783
|
tx,
|
|
767
|
-
|
|
784
|
+
previous: this.lastVerifiedTransactionBySessionID[sessionID],
|
|
785
|
+
};
|
|
786
|
+
|
|
787
|
+
if (verifiedTransaction.madeAt > this.latestTxMadeAt) {
|
|
788
|
+
this.latestTxMadeAt = verifiedTransaction.madeAt;
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
if (verifiedTransaction.madeAt < this.earliestTxMadeAt) {
|
|
792
|
+
this.earliestTxMadeAt = verifiedTransaction.madeAt;
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
this.verifiedTransactions.push(verifiedTransaction);
|
|
796
|
+
this.lastVerifiedTransactionBySessionID[sessionID] =
|
|
797
|
+
verifiedTransaction;
|
|
768
798
|
});
|
|
769
799
|
|
|
770
800
|
this.verifiedTransactionsKnownSessions[sessionID] =
|
|
@@ -793,23 +823,54 @@ export class CoValueCore {
|
|
|
793
823
|
|
|
794
824
|
transaction.hasMetaBeenParsed = true;
|
|
795
825
|
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
826
|
+
// Branch related meta information
|
|
827
|
+
if (this.isBranch()) {
|
|
828
|
+
// Check if the transaction is a branch start
|
|
829
|
+
if ("from" in transaction.meta) {
|
|
830
|
+
if (!this.branchStart || transaction.madeAt < this.branchStart.madeAt) {
|
|
831
|
+
const commit = transaction.meta as BranchStartCommit;
|
|
832
|
+
|
|
833
|
+
this.branchStart = {
|
|
834
|
+
from: commit.from,
|
|
835
|
+
madeAt: transaction.madeAt,
|
|
836
|
+
};
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
// Check if the transaction is a merged checkpoint for a branch
|
|
841
|
+
if ("merged" in transaction.meta) {
|
|
842
|
+
const mergeCommit = transaction.meta as MergeCommit;
|
|
843
|
+
this.mergeCommits.push(mergeCommit);
|
|
844
|
+
}
|
|
804
845
|
}
|
|
805
846
|
|
|
806
|
-
if
|
|
807
|
-
|
|
847
|
+
// Check if the transaction is a branch pointer
|
|
848
|
+
if ("branch" in transaction.meta) {
|
|
849
|
+
const branch = transaction.meta as BranchPointerCommit;
|
|
808
850
|
|
|
809
|
-
this.
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
851
|
+
this.branches.push(branch);
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
// Check if the transaction has been merged from a branch
|
|
855
|
+
if ("mi" in transaction.meta) {
|
|
856
|
+
const meta = transaction.meta as MergedTransactionMetadata;
|
|
857
|
+
|
|
858
|
+
// Check if the transaction is a merge commit
|
|
859
|
+
const previousTransaction = transaction.previous?.txID;
|
|
860
|
+
const sessionID = meta.s ?? previousTransaction?.sessionID;
|
|
861
|
+
|
|
862
|
+
if (sessionID) {
|
|
863
|
+
transaction.txID = {
|
|
864
|
+
sessionID,
|
|
865
|
+
txIndex: meta.mi,
|
|
866
|
+
branch: meta.b ?? previousTransaction?.branch,
|
|
867
|
+
};
|
|
868
|
+
} else {
|
|
869
|
+
logger.error("Merge commit without session ID", {
|
|
870
|
+
txID: transaction.txID,
|
|
871
|
+
prev: previousTransaction ?? null,
|
|
872
|
+
});
|
|
873
|
+
}
|
|
813
874
|
}
|
|
814
875
|
}
|
|
815
876
|
|
|
@@ -876,7 +937,9 @@ export class CoValueCore {
|
|
|
876
937
|
const { txID } = transaction;
|
|
877
938
|
|
|
878
939
|
const from = options?.from?.[txID.sessionID] ?? -1;
|
|
879
|
-
|
|
940
|
+
|
|
941
|
+
// Load the to filter index. Sessions that are not in the to filter will be skipped
|
|
942
|
+
const to = options?.to ? (options.to[txID.sessionID] ?? -1) : Infinity;
|
|
880
943
|
|
|
881
944
|
// The txIndex starts at 0 and from/to are referring to the count of transactions
|
|
882
945
|
if (from > txID.txIndex || to < txID.txIndex) {
|
|
@@ -889,7 +952,7 @@ export class CoValueCore {
|
|
|
889
952
|
// If this is a branch, we load the valid transactions from the source
|
|
890
953
|
if (source && this.branchStart && !options?.skipBranchSource) {
|
|
891
954
|
const sourceTransactions = source.getValidTransactions({
|
|
892
|
-
to: this.branchStart.
|
|
955
|
+
to: this.branchStart.from,
|
|
893
956
|
ignorePrivateTransactions: options?.ignorePrivateTransactions ?? false,
|
|
894
957
|
knownTransactions: options?.knownTransactions,
|
|
895
958
|
});
|
|
@@ -915,15 +978,46 @@ export class CoValueCore {
|
|
|
915
978
|
}
|
|
916
979
|
|
|
917
980
|
getCurrentBranchName() {
|
|
918
|
-
return this.verified?.
|
|
981
|
+
return this.verified?.branchName;
|
|
919
982
|
}
|
|
920
983
|
|
|
921
984
|
getCurrentBranchSourceId() {
|
|
922
|
-
return this.verified?.
|
|
985
|
+
return this.verified?.branchSourceId;
|
|
923
986
|
}
|
|
924
987
|
|
|
925
988
|
isBranch() {
|
|
926
|
-
return Boolean(this.
|
|
989
|
+
return Boolean(this.verified?.branchSourceId);
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
hasBranch(name: string, ownerId?: RawCoID) {
|
|
993
|
+
// This function requires the meta information to be parsed, which might not be the case
|
|
994
|
+
// if the value content hasn't been loaded yet
|
|
995
|
+
this.parseNewTransactions(false);
|
|
996
|
+
|
|
997
|
+
const currentOwnerId = getBranchOwnerId(this);
|
|
998
|
+
return this.branches.some((item) => {
|
|
999
|
+
if (item.branch !== name) {
|
|
1000
|
+
return false;
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
if (item.ownerId === ownerId) {
|
|
1004
|
+
return true;
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
if (!ownerId) {
|
|
1008
|
+
return item.ownerId === currentOwnerId;
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
if (!item.ownerId) {
|
|
1012
|
+
return ownerId === currentOwnerId;
|
|
1013
|
+
}
|
|
1014
|
+
});
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
getMergeCommits() {
|
|
1018
|
+
this.parseNewTransactions(false);
|
|
1019
|
+
|
|
1020
|
+
return this.mergeCommits;
|
|
927
1021
|
}
|
|
928
1022
|
|
|
929
1023
|
getValidSortedTransactions(options?: {
|
|
@@ -59,6 +59,8 @@ export class VerifiedState {
|
|
|
59
59
|
private _cachedNewContentSinceEmpty: NewContentMessage[] | undefined;
|
|
60
60
|
private streamingKnownState?: CoValueKnownState["sessions"];
|
|
61
61
|
public lastAccessed: number | undefined;
|
|
62
|
+
public branchSourceId?: RawCoID;
|
|
63
|
+
public branchName?: string;
|
|
62
64
|
|
|
63
65
|
constructor(
|
|
64
66
|
id: RawCoID,
|
|
@@ -74,6 +76,8 @@ export class VerifiedState {
|
|
|
74
76
|
this.streamingKnownState = streamingKnownState
|
|
75
77
|
? { ...streamingKnownState }
|
|
76
78
|
: undefined;
|
|
79
|
+
this.branchSourceId = header.meta?.source as RawCoID | undefined;
|
|
80
|
+
this.branchName = header.meta?.branch as string | undefined;
|
|
77
81
|
}
|
|
78
82
|
|
|
79
83
|
clone(): VerifiedState {
|
|
@@ -114,12 +118,14 @@ export class VerifiedState {
|
|
|
114
118
|
signerAgent: ControlledAccountOrAgent,
|
|
115
119
|
changes: JsonValue[],
|
|
116
120
|
meta: JsonObject | undefined,
|
|
121
|
+
madeAt: number,
|
|
117
122
|
) {
|
|
118
123
|
const result = this.sessions.makeNewTrustingTransaction(
|
|
119
124
|
sessionID,
|
|
120
125
|
signerAgent,
|
|
121
126
|
changes,
|
|
122
127
|
meta,
|
|
128
|
+
madeAt,
|
|
123
129
|
);
|
|
124
130
|
|
|
125
131
|
this._cachedNewContentSinceEmpty = undefined;
|
|
@@ -135,6 +141,7 @@ export class VerifiedState {
|
|
|
135
141
|
keyID: KeyID,
|
|
136
142
|
keySecret: KeySecret,
|
|
137
143
|
meta: JsonObject | undefined,
|
|
144
|
+
madeAt: number,
|
|
138
145
|
) {
|
|
139
146
|
const result = this.sessions.makeNewPrivateTransaction(
|
|
140
147
|
sessionID,
|
|
@@ -143,6 +150,7 @@ export class VerifiedState {
|
|
|
143
150
|
keyID,
|
|
144
151
|
keySecret,
|
|
145
152
|
meta,
|
|
153
|
+
madeAt,
|
|
146
154
|
);
|
|
147
155
|
|
|
148
156
|
this._cachedNewContentSinceEmpty = undefined;
|
package/src/coValues/coList.ts
CHANGED
|
@@ -138,7 +138,13 @@ export class RawCoList<
|
|
|
138
138
|
sessionEntry[opID.txIndex] = txEntry;
|
|
139
139
|
}
|
|
140
140
|
|
|
141
|
+
// Check if the change index already exists, may be the case of double merges
|
|
142
|
+
if (txEntry[opID.changeIdx]) {
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
145
|
+
|
|
141
146
|
txEntry[opID.changeIdx] = value;
|
|
147
|
+
return true;
|
|
142
148
|
}
|
|
143
149
|
|
|
144
150
|
private isDeleted(opID: OpID) {
|
|
@@ -216,13 +222,18 @@ export class RawCoList<
|
|
|
216
222
|
};
|
|
217
223
|
|
|
218
224
|
if (change.op === "pre" || change.op === "app") {
|
|
219
|
-
this.createInsertionsEntry(opID, {
|
|
225
|
+
const created = this.createInsertionsEntry(opID, {
|
|
220
226
|
madeAt,
|
|
221
227
|
predecessors: [],
|
|
222
228
|
successors: [],
|
|
223
229
|
change,
|
|
224
230
|
});
|
|
225
231
|
|
|
232
|
+
// If the change index already exists, we don't need to process it again
|
|
233
|
+
if (!created) {
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
|
|
226
237
|
if (change.op === "pre") {
|
|
227
238
|
if (change.before === "end") {
|
|
228
239
|
this.beforeEnd.push(opID);
|
package/src/coValues/coMap.ts
CHANGED
|
@@ -49,10 +49,17 @@ export class RawCoMapView<
|
|
|
49
49
|
latest: {
|
|
50
50
|
[Key in keyof Shape & string]?: MapOp<Key, Shape[Key]>;
|
|
51
51
|
};
|
|
52
|
+
|
|
52
53
|
/** @internal */
|
|
53
|
-
latestTxMadeAt: number
|
|
54
|
+
get latestTxMadeAt(): number {
|
|
55
|
+
return this.core.latestTxMadeAt;
|
|
56
|
+
}
|
|
57
|
+
|
|
54
58
|
/** @internal */
|
|
55
|
-
earliestTxMadeAt: number
|
|
59
|
+
get earliestTxMadeAt(): number {
|
|
60
|
+
return this.core.earliestTxMadeAt;
|
|
61
|
+
}
|
|
62
|
+
|
|
56
63
|
/** @internal */
|
|
57
64
|
ops: {
|
|
58
65
|
[Key in keyof Shape & string]?: MapOp<Key, Shape[Key]>[];
|
|
@@ -80,8 +87,7 @@ export class RawCoMapView<
|
|
|
80
87
|
) {
|
|
81
88
|
this.id = core.id as CoID<this>;
|
|
82
89
|
this.core = core;
|
|
83
|
-
|
|
84
|
-
this.earliestTxMadeAt = null;
|
|
90
|
+
|
|
85
91
|
this.ignorePrivateTransactions =
|
|
86
92
|
options?.ignorePrivateTransactions ?? false;
|
|
87
93
|
this.ops = {};
|
|
@@ -105,10 +111,6 @@ export class RawCoMapView<
|
|
|
105
111
|
return;
|
|
106
112
|
}
|
|
107
113
|
|
|
108
|
-
if (this.earliestTxMadeAt === null && newValidTransactions[0]) {
|
|
109
|
-
this.earliestTxMadeAt = newValidTransactions[0].madeAt;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
114
|
const { ops } = this;
|
|
113
115
|
|
|
114
116
|
const changedEntries = new Map<
|
|
@@ -117,10 +119,6 @@ export class RawCoMapView<
|
|
|
117
119
|
>();
|
|
118
120
|
|
|
119
121
|
for (const { txID, changes, madeAt, tx } of newValidTransactions) {
|
|
120
|
-
if (madeAt > this.latestTxMadeAt) {
|
|
121
|
-
this.latestTxMadeAt = madeAt;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
122
|
for (let changeIdx = 0; changeIdx < changes.length; changeIdx++) {
|
|
125
123
|
const change = changes[changeIdx] as MapOpPayload<
|
|
126
124
|
keyof Shape & string,
|
package/src/coValues/group.ts
CHANGED
|
@@ -1070,6 +1070,9 @@ export class RawGroup<
|
|
|
1070
1070
|
|
|
1071
1071
|
if (init) {
|
|
1072
1072
|
map.assign(init, initPrivacy);
|
|
1073
|
+
} else if (!uniqueness.createdAt) {
|
|
1074
|
+
// If the createdAt is not set, we need to make a trusting transaction to set the createdAt
|
|
1075
|
+
map.core.makeTransaction([], "trusting");
|
|
1073
1076
|
}
|
|
1074
1077
|
|
|
1075
1078
|
return map;
|
|
@@ -1101,6 +1104,9 @@ export class RawGroup<
|
|
|
1101
1104
|
|
|
1102
1105
|
if (init?.length) {
|
|
1103
1106
|
list.appendItems(init, undefined, initPrivacy);
|
|
1107
|
+
} else if (!uniqueness.createdAt) {
|
|
1108
|
+
// If the createdAt is not set, we need to make a trusting transaction to set the createdAt
|
|
1109
|
+
list.core.makeTransaction([], "trusting");
|
|
1104
1110
|
}
|
|
1105
1111
|
|
|
1106
1112
|
return list;
|
|
@@ -1141,7 +1147,7 @@ export class RawGroup<
|
|
|
1141
1147
|
meta?: C["headerMeta"],
|
|
1142
1148
|
uniqueness: CoValueUniqueness = this.crypto.createdNowUnique(),
|
|
1143
1149
|
): C {
|
|
1144
|
-
|
|
1150
|
+
const stream = this.core.node
|
|
1145
1151
|
.createCoValue({
|
|
1146
1152
|
type: "costream",
|
|
1147
1153
|
ruleset: {
|
|
@@ -1152,6 +1158,13 @@ export class RawGroup<
|
|
|
1152
1158
|
...uniqueness,
|
|
1153
1159
|
})
|
|
1154
1160
|
.getCurrentContent() as C;
|
|
1161
|
+
|
|
1162
|
+
if (!uniqueness.createdAt) {
|
|
1163
|
+
// If the createdAt is not set, we need to make a trusting transaction to set the createdAt
|
|
1164
|
+
stream.core.makeTransaction([], "trusting");
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1167
|
+
return stream;
|
|
1155
1168
|
}
|
|
1156
1169
|
|
|
1157
1170
|
/** @category 3. Value creation */
|
package/src/config.ts
CHANGED
|
@@ -7,12 +7,21 @@
|
|
|
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,
|
|
10
15
|
};
|
|
11
16
|
|
|
12
17
|
export function setMaxRecommendedTxSize(size: number) {
|
|
13
18
|
TRANSACTION_CONFIG.MAX_RECOMMENDED_TX_SIZE = size;
|
|
14
19
|
}
|
|
15
20
|
|
|
21
|
+
export function setMaxTxSizeBytes(size: number) {
|
|
22
|
+
TRANSACTION_CONFIG.MAX_TX_SIZE_BYTES = size;
|
|
23
|
+
}
|
|
24
|
+
|
|
16
25
|
export const CO_VALUE_LOADING_CONFIG = {
|
|
17
26
|
MAX_RETRIES: 1,
|
|
18
27
|
TIMEOUT: 30_000,
|
|
@@ -32,6 +32,29 @@ import { ControlledAccountOrAgent } from "../coValues/account.js";
|
|
|
32
32
|
|
|
33
33
|
type Blake3State = ReturnType<typeof blake3.create>;
|
|
34
34
|
|
|
35
|
+
const x25519SharedSecretCache = new Map<string, Uint8Array>();
|
|
36
|
+
|
|
37
|
+
function getx25519SharedSecret(
|
|
38
|
+
privateKeyA: SealerSecret,
|
|
39
|
+
publicKeyB: SealerID,
|
|
40
|
+
): Uint8Array {
|
|
41
|
+
const cacheKey = `${privateKeyA}-${publicKeyB}`;
|
|
42
|
+
let sharedSecret = x25519SharedSecretCache.get(cacheKey);
|
|
43
|
+
|
|
44
|
+
if (!sharedSecret) {
|
|
45
|
+
const privateKeyABytes = base58.decode(
|
|
46
|
+
privateKeyA.substring("sealerSecret_z".length),
|
|
47
|
+
);
|
|
48
|
+
const publicKeyBBytes = base58.decode(
|
|
49
|
+
publicKeyB.substring("sealer_z".length),
|
|
50
|
+
);
|
|
51
|
+
sharedSecret = x25519.getSharedSecret(privateKeyABytes, publicKeyBBytes);
|
|
52
|
+
x25519SharedSecretCache.set(cacheKey, sharedSecret);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return sharedSecret;
|
|
56
|
+
}
|
|
57
|
+
|
|
35
58
|
/**
|
|
36
59
|
* Pure JavaScript implementation of the CryptoProvider interface using noble-curves and noble-ciphers libraries.
|
|
37
60
|
* This provides a fallback implementation that doesn't require WebAssembly, offering:
|
|
@@ -164,16 +187,10 @@ export class PureJSCrypto extends CryptoProvider<Blake3State> {
|
|
|
164
187
|
to: SealerID;
|
|
165
188
|
nOnceMaterial: { in: RawCoID; tx: TransactionID };
|
|
166
189
|
}): Sealed<T> {
|
|
190
|
+
const sharedSecret = getx25519SharedSecret(from, to);
|
|
167
191
|
const nOnce = this.generateJsonNonce(nOnceMaterial);
|
|
168
|
-
|
|
169
|
-
const sealerPub = base58.decode(to.substring("sealer_z".length));
|
|
170
|
-
|
|
171
|
-
const senderPriv = base58.decode(from.substring("sealerSecret_z".length));
|
|
172
|
-
|
|
173
192
|
const plaintext = textEncoder.encode(stableStringify(message));
|
|
174
193
|
|
|
175
|
-
const sharedSecret = x25519.getSharedSecret(senderPriv, sealerPub);
|
|
176
|
-
|
|
177
194
|
const sealedBytes = xsalsa20poly1305(sharedSecret, nOnce).encrypt(
|
|
178
195
|
plaintext,
|
|
179
196
|
);
|
|
@@ -189,14 +206,9 @@ export class PureJSCrypto extends CryptoProvider<Blake3State> {
|
|
|
189
206
|
): T | undefined {
|
|
190
207
|
const nOnce = this.generateJsonNonce(nOnceMaterial);
|
|
191
208
|
|
|
192
|
-
const
|
|
193
|
-
|
|
194
|
-
const senderPub = base58.decode(from.substring("sealer_z".length));
|
|
195
|
-
|
|
209
|
+
const sharedSecret = getx25519SharedSecret(sealer, from);
|
|
196
210
|
const sealedBytes = base64URLtoBytes(sealed.substring("sealed_U".length));
|
|
197
211
|
|
|
198
|
-
const sharedSecret = x25519.getSharedSecret(sealerPriv, senderPub);
|
|
199
|
-
|
|
200
212
|
const plaintext = xsalsa20poly1305(sharedSecret, nOnce).decrypt(
|
|
201
213
|
sealedBytes,
|
|
202
214
|
);
|
package/src/exports.ts
CHANGED
|
@@ -63,7 +63,12 @@ import type { JsonObject, JsonValue } from "./jsonValue.js";
|
|
|
63
63
|
import type * as Media from "./media.js";
|
|
64
64
|
import { disablePermissionErrors } from "./permissions.js";
|
|
65
65
|
import type { Peer, SyncMessage } from "./sync.js";
|
|
66
|
-
import {
|
|
66
|
+
import {
|
|
67
|
+
DisconnectedError,
|
|
68
|
+
SyncManager,
|
|
69
|
+
emptyKnownState,
|
|
70
|
+
hwrServerPeerSelector,
|
|
71
|
+
} from "./sync.js";
|
|
67
72
|
|
|
68
73
|
import {
|
|
69
74
|
getContentMessageSize,
|
|
@@ -163,6 +168,7 @@ export {
|
|
|
163
168
|
LogLevel,
|
|
164
169
|
base64URLtoBytes,
|
|
165
170
|
bytesToBase64url,
|
|
171
|
+
hwrServerPeerSelector,
|
|
166
172
|
};
|
|
167
173
|
|
|
168
174
|
export type {
|
package/src/localNode.ts
CHANGED
|
@@ -479,8 +479,14 @@ export class LocalNode {
|
|
|
479
479
|
return branch.getCurrentContent() as T;
|
|
480
480
|
}
|
|
481
481
|
|
|
482
|
-
//
|
|
483
|
-
|
|
482
|
+
// Do a synchronous check to see if the branch exists, if not we don't need to try to load the branch
|
|
483
|
+
if (!source.hasBranch(branchName, branchOwnerID)) {
|
|
484
|
+
return source
|
|
485
|
+
.createBranch(branchName, branchOwnerID)
|
|
486
|
+
.getCurrentContent() as T;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
await this.loadCoValueCore(branch.id);
|
|
484
490
|
|
|
485
491
|
if (!branch.isAvailable()) {
|
|
486
492
|
return source
|
package/src/sync.ts
CHANGED
|
@@ -219,28 +219,7 @@ export class SyncManager {
|
|
|
219
219
|
}
|
|
220
220
|
}
|
|
221
221
|
|
|
222
|
-
|
|
223
|
-
id: RawCoID,
|
|
224
|
-
peer: PeerState,
|
|
225
|
-
seen: Set<RawCoID> = new Set(),
|
|
226
|
-
) {
|
|
227
|
-
this.sendNewContent(id, peer, seen, true);
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
sendNewContentWithoutDependencies(
|
|
231
|
-
id: RawCoID,
|
|
232
|
-
peer: PeerState,
|
|
233
|
-
seen: Set<RawCoID> = new Set(),
|
|
234
|
-
) {
|
|
235
|
-
this.sendNewContent(id, peer, seen, false);
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
private sendNewContent(
|
|
239
|
-
id: RawCoID,
|
|
240
|
-
peer: PeerState,
|
|
241
|
-
seen: Set<RawCoID> = new Set(),
|
|
242
|
-
includeDependencies: boolean,
|
|
243
|
-
) {
|
|
222
|
+
sendNewContent(id: RawCoID, peer: PeerState, seen: Set<RawCoID> = new Set()) {
|
|
244
223
|
if (seen.has(id)) {
|
|
245
224
|
return;
|
|
246
225
|
}
|
|
@@ -253,9 +232,10 @@ export class SyncManager {
|
|
|
253
232
|
return;
|
|
254
233
|
}
|
|
255
234
|
|
|
235
|
+
const includeDependencies = peer.role !== "server";
|
|
256
236
|
if (includeDependencies) {
|
|
257
237
|
for (const dependency of coValue.getDependedOnCoValues()) {
|
|
258
|
-
this.
|
|
238
|
+
this.sendNewContent(dependency, peer, seen);
|
|
259
239
|
}
|
|
260
240
|
}
|
|
261
241
|
|
|
@@ -434,7 +414,7 @@ export class SyncManager {
|
|
|
434
414
|
const coValue = this.local.getCoValue(msg.id);
|
|
435
415
|
|
|
436
416
|
if (coValue.isAvailable()) {
|
|
437
|
-
this.
|
|
417
|
+
this.sendNewContent(msg.id, peer);
|
|
438
418
|
return;
|
|
439
419
|
}
|
|
440
420
|
|
|
@@ -444,7 +424,7 @@ export class SyncManager {
|
|
|
444
424
|
|
|
445
425
|
const handleLoadResult = () => {
|
|
446
426
|
if (coValue.isAvailable()) {
|
|
447
|
-
this.
|
|
427
|
+
this.sendNewContent(msg.id, peer);
|
|
448
428
|
return;
|
|
449
429
|
}
|
|
450
430
|
|
|
@@ -478,11 +458,7 @@ export class SyncManager {
|
|
|
478
458
|
}
|
|
479
459
|
|
|
480
460
|
if (coValue.isAvailable()) {
|
|
481
|
-
|
|
482
|
-
this.sendNewContentWithoutDependencies(msg.id, peer);
|
|
483
|
-
} else {
|
|
484
|
-
this.sendNewContentIncludingDependencies(msg.id, peer);
|
|
485
|
-
}
|
|
461
|
+
this.sendNewContent(msg.id, peer);
|
|
486
462
|
}
|
|
487
463
|
}
|
|
488
464
|
|
|
@@ -777,7 +753,7 @@ export class SyncManager {
|
|
|
777
753
|
|
|
778
754
|
// We directly forward the new content to peers that have an active subscription
|
|
779
755
|
if (peer.optimisticKnownStates.has(coValue.id)) {
|
|
780
|
-
this.
|
|
756
|
+
this.sendNewContent(coValue.id, peer);
|
|
781
757
|
syncedPeers.push(peer);
|
|
782
758
|
} else if (
|
|
783
759
|
peer.role === "server" &&
|
|
@@ -808,7 +784,7 @@ export class SyncManager {
|
|
|
808
784
|
handleCorrection(msg: KnownStateMessage, peer: PeerState) {
|
|
809
785
|
peer.setKnownState(msg.id, knownStateIn(msg));
|
|
810
786
|
|
|
811
|
-
return this.
|
|
787
|
+
return this.sendNewContent(msg.id, peer);
|
|
812
788
|
}
|
|
813
789
|
|
|
814
790
|
private syncQueue = new LocalTransactionsSyncQueue((content) =>
|