cojson 0.7.35 → 0.8.3

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.
Files changed (222) hide show
  1. package/.turbo/turbo-build.log +2 -7
  2. package/.turbo/turbo-lint.log +4 -0
  3. package/CHANGELOG.md +13 -0
  4. package/dist/{PeerState.js → native/PeerState.js} +13 -5
  5. package/dist/native/PeerState.js.map +1 -0
  6. package/dist/native/PriorityBasedMessageQueue.js.map +1 -0
  7. package/dist/native/base64url.js.map +1 -0
  8. package/dist/native/base64url.test.js.map +1 -0
  9. package/dist/native/coValue.js.map +1 -0
  10. package/dist/{coValueCore.js → native/coValueCore.js} +0 -6
  11. package/dist/native/coValueCore.js.map +1 -0
  12. package/dist/{coValues → native/coValues}/account.js +2 -3
  13. package/dist/native/coValues/account.js.map +1 -0
  14. package/dist/native/coValues/coList.js.map +1 -0
  15. package/dist/native/coValues/coMap.js.map +1 -0
  16. package/dist/native/coValues/coStream.js.map +1 -0
  17. package/dist/{coValues → native/coValues}/group.js +8 -8
  18. package/dist/native/coValues/group.js.map +1 -0
  19. package/dist/native/coreToCoValue.js.map +1 -0
  20. package/dist/{crypto → native/crypto}/PureJSCrypto.js +4 -0
  21. package/dist/native/crypto/PureJSCrypto.js.map +1 -0
  22. package/dist/{crypto → native/crypto}/crypto.js +4 -1
  23. package/dist/native/crypto/crypto.js.map +1 -0
  24. package/dist/native/ids.js.map +1 -0
  25. package/dist/{index.js → native/index.native.js} +4 -6
  26. package/dist/native/index.native.js.map +1 -0
  27. package/dist/native/jsonStringify.js.map +1 -0
  28. package/dist/{jsonValue.js.map → native/jsonValue.js.map} +1 -1
  29. package/dist/{localNode.js → native/localNode.js} +44 -38
  30. package/dist/native/localNode.js.map +1 -0
  31. package/dist/native/media.js.map +1 -0
  32. package/dist/native/permissions.js.map +1 -0
  33. package/dist/native/priority.js.map +1 -0
  34. package/dist/native/storage/FileSystem.js.map +1 -0
  35. package/dist/{storage → native/storage}/chunksAndKnownStates.js +1 -1
  36. package/dist/native/storage/chunksAndKnownStates.js.map +1 -0
  37. package/dist/native/storage/index.js.map +1 -0
  38. package/dist/native/streamUtils.js.map +1 -0
  39. package/dist/native/sync.js.map +1 -0
  40. package/dist/native/typeUtils/accountOrAgentIDfromSessionID.js.map +1 -0
  41. package/dist/native/typeUtils/expectGroup.js.map +1 -0
  42. package/dist/native/typeUtils/isAccountID.js.map +1 -0
  43. package/dist/native/typeUtils/isCoValue.js.map +1 -0
  44. package/dist/web/PeerState.js +66 -0
  45. package/dist/web/PeerState.js.map +1 -0
  46. package/dist/web/PriorityBasedMessageQueue.js +51 -0
  47. package/dist/web/PriorityBasedMessageQueue.js.map +1 -0
  48. package/dist/web/base64url.js +59 -0
  49. package/dist/web/base64url.js.map +1 -0
  50. package/dist/web/base64url.test.js +25 -0
  51. package/dist/web/base64url.test.js.map +1 -0
  52. package/dist/web/coValue.js +19 -0
  53. package/dist/web/coValue.js.map +1 -0
  54. package/dist/web/coValueCore.js +693 -0
  55. package/dist/web/coValueCore.js.map +1 -0
  56. package/dist/web/coValues/account.js +96 -0
  57. package/dist/web/coValues/account.js.map +1 -0
  58. package/dist/web/coValues/coList.js +393 -0
  59. package/dist/web/coValues/coList.js.map +1 -0
  60. package/dist/web/coValues/coMap.js +197 -0
  61. package/dist/web/coValues/coMap.js.map +1 -0
  62. package/dist/web/coValues/coStream.js +220 -0
  63. package/dist/web/coValues/coStream.js.map +1 -0
  64. package/dist/web/coValues/group.js +250 -0
  65. package/dist/web/coValues/group.js.map +1 -0
  66. package/dist/web/coreToCoValue.js +42 -0
  67. package/dist/web/coreToCoValue.js.map +1 -0
  68. package/dist/web/crypto/PureJSCrypto.js +93 -0
  69. package/dist/web/crypto/PureJSCrypto.js.map +1 -0
  70. package/dist/{crypto → web/crypto}/WasmCrypto.js +3 -0
  71. package/dist/web/crypto/WasmCrypto.js.map +1 -0
  72. package/dist/web/crypto/crypto.js +154 -0
  73. package/dist/web/crypto/crypto.js.map +1 -0
  74. package/dist/web/ids.js +17 -0
  75. package/dist/web/ids.js.map +1 -0
  76. package/dist/web/index.web.js +40 -0
  77. package/dist/web/index.web.js.map +1 -0
  78. package/dist/web/jsonStringify.js +57 -0
  79. package/dist/web/jsonStringify.js.map +1 -0
  80. package/dist/web/jsonValue.js +2 -0
  81. package/dist/web/jsonValue.js.map +1 -0
  82. package/dist/web/localNode.js +442 -0
  83. package/dist/web/localNode.js.map +1 -0
  84. package/dist/web/media.js +2 -0
  85. package/dist/web/media.js.map +1 -0
  86. package/dist/web/permissions.js +206 -0
  87. package/dist/web/permissions.js.map +1 -0
  88. package/dist/web/priority.js +31 -0
  89. package/dist/web/priority.js.map +1 -0
  90. package/dist/web/storage/FileSystem.js +58 -0
  91. package/dist/web/storage/FileSystem.js.map +1 -0
  92. package/dist/web/storage/chunksAndKnownStates.js +100 -0
  93. package/dist/web/storage/chunksAndKnownStates.js.map +1 -0
  94. package/dist/web/storage/index.js +348 -0
  95. package/dist/web/storage/index.js.map +1 -0
  96. package/dist/web/streamUtils.js +41 -0
  97. package/dist/web/streamUtils.js.map +1 -0
  98. package/dist/web/sync.js +504 -0
  99. package/dist/web/sync.js.map +1 -0
  100. package/dist/web/typeUtils/accountOrAgentIDfromSessionID.js +5 -0
  101. package/dist/web/typeUtils/accountOrAgentIDfromSessionID.js.map +1 -0
  102. package/dist/web/typeUtils/expectGroup.js +13 -0
  103. package/dist/web/typeUtils/expectGroup.js.map +1 -0
  104. package/dist/web/typeUtils/isAccountID.js +4 -0
  105. package/dist/web/typeUtils/isAccountID.js.map +1 -0
  106. package/dist/web/typeUtils/isCoValue.js +11 -0
  107. package/dist/web/typeUtils/isCoValue.js.map +1 -0
  108. package/package.json +25 -8
  109. package/src/PeerState.ts +18 -7
  110. package/src/coValue.ts +1 -1
  111. package/src/coValueCore.ts +9 -14
  112. package/src/coValues/account.ts +12 -7
  113. package/src/coValues/coList.ts +4 -4
  114. package/src/coValues/coMap.ts +3 -3
  115. package/src/coValues/coStream.ts +8 -8
  116. package/src/coValues/group.ts +15 -11
  117. package/src/crypto/PureJSCrypto.ts +5 -0
  118. package/src/crypto/WasmCrypto.ts +4 -0
  119. package/src/crypto/crypto.ts +11 -1
  120. package/src/ids.ts +2 -2
  121. package/src/{index.ts → index.native.ts} +7 -8
  122. package/src/index.web.ts +152 -0
  123. package/src/localNode.ts +75 -67
  124. package/src/permissions.ts +5 -5
  125. package/src/storage/chunksAndKnownStates.ts +1 -1
  126. package/src/storage/index.ts +1 -1
  127. package/src/tests/account.test.ts +1 -2
  128. package/src/tests/coList.test.ts +1 -1
  129. package/src/tests/coMap.test.ts +1 -1
  130. package/src/tests/coStream.test.ts +2 -1
  131. package/src/tests/cryptoImpl.test.ts +24 -2
  132. package/src/tests/group.test.ts +6 -8
  133. package/src/tests/permissions.test.ts +90 -43
  134. package/src/tests/priority.test.ts +44 -15
  135. package/src/tests/sync.test.ts +8 -8
  136. package/src/tests/testUtils.ts +1 -2
  137. package/src/typeUtils/accountOrAgentIDfromSessionID.ts +3 -3
  138. package/src/typeUtils/isAccountID.ts +2 -2
  139. package/tsconfig.json +5 -4
  140. package/tsconfig.native.json +12 -0
  141. package/tsconfig.web.json +11 -0
  142. package/dist/PeerState.js.map +0 -1
  143. package/dist/PriorityBasedMessageQueue.js.map +0 -1
  144. package/dist/base64url.js.map +0 -1
  145. package/dist/base64url.test.js.map +0 -1
  146. package/dist/coValue.js.map +0 -1
  147. package/dist/coValueCore.js.map +0 -1
  148. package/dist/coValues/account.js.map +0 -1
  149. package/dist/coValues/coList.js.map +0 -1
  150. package/dist/coValues/coMap.js.map +0 -1
  151. package/dist/coValues/coStream.js.map +0 -1
  152. package/dist/coValues/group.js.map +0 -1
  153. package/dist/coreToCoValue.js.map +0 -1
  154. package/dist/crypto/PureJSCrypto.js.map +0 -1
  155. package/dist/crypto/WasmCrypto.js.map +0 -1
  156. package/dist/crypto/crypto.js.map +0 -1
  157. package/dist/ids.js.map +0 -1
  158. package/dist/index.js.map +0 -1
  159. package/dist/jsonStringify.js.map +0 -1
  160. package/dist/localNode.js.map +0 -1
  161. package/dist/media.js.map +0 -1
  162. package/dist/permissions.js.map +0 -1
  163. package/dist/priority.js.map +0 -1
  164. package/dist/storage/FileSystem.js.map +0 -1
  165. package/dist/storage/chunksAndKnownStates.js.map +0 -1
  166. package/dist/storage/index.js.map +0 -1
  167. package/dist/streamUtils.js.map +0 -1
  168. package/dist/sync.js.map +0 -1
  169. package/dist/tests/PeerState.test.js +0 -80
  170. package/dist/tests/PeerState.test.js.map +0 -1
  171. package/dist/tests/PriorityBasedMessageQueue.test.js +0 -97
  172. package/dist/tests/PriorityBasedMessageQueue.test.js.map +0 -1
  173. package/dist/tests/account.test.js +0 -59
  174. package/dist/tests/account.test.js.map +0 -1
  175. package/dist/tests/coList.test.js +0 -76
  176. package/dist/tests/coList.test.js.map +0 -1
  177. package/dist/tests/coMap.test.js +0 -136
  178. package/dist/tests/coMap.test.js.map +0 -1
  179. package/dist/tests/coStream.test.js +0 -205
  180. package/dist/tests/coStream.test.js.map +0 -1
  181. package/dist/tests/coValueCore.test.js +0 -124
  182. package/dist/tests/coValueCore.test.js.map +0 -1
  183. package/dist/tests/crypto.test.js +0 -118
  184. package/dist/tests/crypto.test.js.map +0 -1
  185. package/dist/tests/cryptoImpl.test.js +0 -113
  186. package/dist/tests/cryptoImpl.test.js.map +0 -1
  187. package/dist/tests/group.test.js +0 -34
  188. package/dist/tests/group.test.js.map +0 -1
  189. package/dist/tests/permissions.test.js +0 -1060
  190. package/dist/tests/permissions.test.js.map +0 -1
  191. package/dist/tests/priority.test.js +0 -61
  192. package/dist/tests/priority.test.js.map +0 -1
  193. package/dist/tests/sync.test.js +0 -1187
  194. package/dist/tests/sync.test.js.map +0 -1
  195. package/dist/tests/testUtils.js +0 -60
  196. package/dist/tests/testUtils.js.map +0 -1
  197. package/dist/typeUtils/accountOrAgentIDfromSessionID.js.map +0 -1
  198. package/dist/typeUtils/expectGroup.js.map +0 -1
  199. package/dist/typeUtils/isAccountID.js.map +0 -1
  200. package/dist/typeUtils/isCoValue.js.map +0 -1
  201. /package/dist/{PriorityBasedMessageQueue.js → native/PriorityBasedMessageQueue.js} +0 -0
  202. /package/dist/{base64url.js → native/base64url.js} +0 -0
  203. /package/dist/{base64url.test.js → native/base64url.test.js} +0 -0
  204. /package/dist/{coValue.js → native/coValue.js} +0 -0
  205. /package/dist/{coValues → native/coValues}/coList.js +0 -0
  206. /package/dist/{coValues → native/coValues}/coMap.js +0 -0
  207. /package/dist/{coValues → native/coValues}/coStream.js +0 -0
  208. /package/dist/{coreToCoValue.js → native/coreToCoValue.js} +0 -0
  209. /package/dist/{ids.js → native/ids.js} +0 -0
  210. /package/dist/{jsonStringify.js → native/jsonStringify.js} +0 -0
  211. /package/dist/{jsonValue.js → native/jsonValue.js} +0 -0
  212. /package/dist/{media.js → native/media.js} +0 -0
  213. /package/dist/{permissions.js → native/permissions.js} +0 -0
  214. /package/dist/{priority.js → native/priority.js} +0 -0
  215. /package/dist/{storage → native/storage}/FileSystem.js +0 -0
  216. /package/dist/{storage → native/storage}/index.js +0 -0
  217. /package/dist/{streamUtils.js → native/streamUtils.js} +0 -0
  218. /package/dist/{sync.js → native/sync.js} +0 -0
  219. /package/dist/{typeUtils → native/typeUtils}/accountOrAgentIDfromSessionID.js +0 -0
  220. /package/dist/{typeUtils → native/typeUtils}/expectGroup.js +0 -0
  221. /package/dist/{typeUtils → native/typeUtils}/isAccountID.js +0 -0
  222. /package/dist/{typeUtils → native/typeUtils}/isCoValue.js +0 -0
@@ -0,0 +1,693 @@
1
+ import { StreamingHash, } from "./crypto/crypto.js";
2
+ import { determineValidTransactions, isKeyForKeyField, } from "./permissions.js";
3
+ import { parseJSON, stableStringify } from "./jsonStringify.js";
4
+ import { coreToCoValue } from "./coreToCoValue.js";
5
+ import { expectGroup } from "./typeUtils/expectGroup.js";
6
+ import { isAccountID } from "./typeUtils/isAccountID.js";
7
+ import { accountOrAgentIDfromSessionID } from "./typeUtils/accountOrAgentIDfromSessionID.js";
8
+ import { err, ok } from "neverthrow";
9
+ import { getPriorityFromHeader } from "./priority.js";
10
+ /**
11
+ In order to not block other concurrently syncing CoValues we introduce a maximum size of transactions,
12
+ since they are the smallest unit of progress that can be synced within a CoValue.
13
+ This is particularly important for storing binary data in CoValues, since they are likely to be at least on the order of megabytes.
14
+ This also means that we want to keep signatures roughly after each MAX_RECOMMENDED_TX size chunk,
15
+ to be able to verify partially loaded CoValues or CoValues that are still being created (like a video live stream).
16
+ **/
17
+ export const MAX_RECOMMENDED_TX_SIZE = 100 * 1024;
18
+ export function idforHeader(header, crypto) {
19
+ const hash = crypto.shortHash(header);
20
+ return `co_z${hash.slice("shortHash_z".length)}`;
21
+ }
22
+ const readKeyCache = new WeakMap();
23
+ export class CoValueCore {
24
+ constructor(header, node, internalInitSessions = new Map()) {
25
+ this.listeners = new Set();
26
+ this._decryptionCache = {};
27
+ this.deferredUpdates = 0;
28
+ this.crypto = node.crypto;
29
+ this.id = idforHeader(header, node.crypto);
30
+ this.header = header;
31
+ this._sessionLogs = internalInitSessions;
32
+ this.node = node;
33
+ if (header.ruleset.type == "ownedByGroup") {
34
+ this.node
35
+ .expectCoValueLoaded(header.ruleset.group)
36
+ .subscribe((_groupUpdate) => {
37
+ this._cachedContent = undefined;
38
+ const newContent = this.getCurrentContent();
39
+ for (const listener of this.listeners) {
40
+ listener(newContent);
41
+ }
42
+ });
43
+ }
44
+ }
45
+ get sessionLogs() {
46
+ return this._sessionLogs;
47
+ }
48
+ testWithDifferentAccount(account, currentSessionID) {
49
+ const newNode = this.node.testWithDifferentAccount(account, currentSessionID);
50
+ return newNode.expectCoValueLoaded(this.id);
51
+ }
52
+ knownState() {
53
+ if (this._cachedKnownState) {
54
+ return this._cachedKnownState;
55
+ }
56
+ else {
57
+ const knownState = this.knownStateUncached();
58
+ this._cachedKnownState = knownState;
59
+ return knownState;
60
+ }
61
+ }
62
+ /** @internal */
63
+ knownStateUncached() {
64
+ return {
65
+ id: this.id,
66
+ header: true,
67
+ sessions: Object.fromEntries([...this.sessionLogs.entries()].map(([k, v]) => [
68
+ k,
69
+ v.transactions.length,
70
+ ])),
71
+ };
72
+ }
73
+ get meta() {
74
+ return this.header?.meta ?? null;
75
+ }
76
+ nextTransactionID() {
77
+ // This is an ugly hack to get a unique but stable session ID for editing the current account
78
+ const sessionID = this.header.meta?.type === "account"
79
+ ? this.node.currentSessionID.replace(this.node.account.id, this.node.account
80
+ .currentAgentID()
81
+ ._unsafeUnwrap({ withStackTrace: true }))
82
+ : this.node.currentSessionID;
83
+ return {
84
+ sessionID,
85
+ txIndex: this.sessionLogs.get(sessionID)?.transactions.length || 0,
86
+ };
87
+ }
88
+ tryAddTransactions(sessionID, newTransactions, givenExpectedNewHash, newSignature) {
89
+ return this.node
90
+ .resolveAccountAgent(accountOrAgentIDfromSessionID(sessionID), "Expected to know signer of transaction")
91
+ .andThen((agent) => {
92
+ const signerID = this.crypto.getAgentSignerID(agent);
93
+ // const beforeHash = performance.now();
94
+ const { expectedNewHash, newStreamingHash } = this.expectedNewHashAfter(sessionID, newTransactions);
95
+ // const afterHash = performance.now();
96
+ // console.log(
97
+ // "Hashing took",
98
+ // afterHash - beforeHash
99
+ // );
100
+ if (givenExpectedNewHash &&
101
+ givenExpectedNewHash !== expectedNewHash) {
102
+ return err({
103
+ type: "InvalidHash",
104
+ id: this.id,
105
+ expectedNewHash,
106
+ givenExpectedNewHash,
107
+ });
108
+ }
109
+ // const beforeVerify = performance.now();
110
+ if (!this.crypto.verify(newSignature, expectedNewHash, signerID)) {
111
+ return err({
112
+ type: "InvalidSignature",
113
+ id: this.id,
114
+ newSignature,
115
+ sessionID,
116
+ signerID,
117
+ });
118
+ }
119
+ // const afterVerify = performance.now();
120
+ // console.log(
121
+ // "Verify took",
122
+ // afterVerify - beforeVerify
123
+ // );
124
+ this.doAddTransactions(sessionID, newTransactions, newSignature, expectedNewHash, newStreamingHash, "immediate");
125
+ return ok(true);
126
+ });
127
+ }
128
+ /*tryAddTransactionsAsync(
129
+ sessionID: SessionID,
130
+ newTransactions: Transaction[],
131
+ givenExpectedNewHash: Hash | undefined,
132
+ newSignature: Signature,
133
+ ): ResultAsync<true, TryAddTransactionsError> {
134
+ const currentAsyncAddTransaction = this._currentAsyncAddTransaction;
135
+ let maybeAwaitPrevious:
136
+ | ResultAsync<void, TryAddTransactionsError>
137
+ | undefined;
138
+ let thisDone = () => {};
139
+
140
+ if (currentAsyncAddTransaction) {
141
+ // eslint-disable-next-line neverthrow/must-use-result
142
+ maybeAwaitPrevious = ResultAsync.fromSafePromise(
143
+ currentAsyncAddTransaction,
144
+ );
145
+ } else {
146
+ // eslint-disable-next-line neverthrow/must-use-result
147
+ maybeAwaitPrevious = ResultAsync.fromSafePromise(Promise.resolve());
148
+ this._currentAsyncAddTransaction = new Promise((resolve) => {
149
+ thisDone = resolve;
150
+ });
151
+ }
152
+
153
+ return maybeAwaitPrevious
154
+ .andThen((_previousDone) =>
155
+ this.node
156
+ .resolveAccountAgentAsync(
157
+ accountOrAgentIDfromSessionID(sessionID),
158
+ "Expected to know signer of transaction",
159
+ )
160
+ .andThen((agent) => {
161
+ const signerID = this.crypto.getAgentSignerID(agent);
162
+
163
+ const nTxBefore =
164
+ this.sessionLogs.get(sessionID)?.transactions
165
+ .length ?? 0;
166
+
167
+ // const beforeHash = performance.now();
168
+ return ResultAsync.fromSafePromise(
169
+ this.expectedNewHashAfterAsync(
170
+ sessionID,
171
+ newTransactions,
172
+ ),
173
+ ).andThen(({ expectedNewHash, newStreamingHash }) => {
174
+ // const afterHash = performance.now();
175
+ // console.log(
176
+ // "Hashing took",
177
+ // afterHash - beforeHash
178
+ // );
179
+
180
+ const nTxAfter =
181
+ this.sessionLogs.get(sessionID)?.transactions
182
+ .length ?? 0;
183
+
184
+ if (nTxAfter !== nTxBefore) {
185
+ const newTransactionLengthBefore =
186
+ newTransactions.length;
187
+ newTransactions = newTransactions.slice(
188
+ nTxAfter - nTxBefore,
189
+ );
190
+ console.warn(
191
+ "Transactions changed while async hashing",
192
+ {
193
+ nTxBefore,
194
+ nTxAfter,
195
+ newTransactionLengthBefore,
196
+ remainingNewTransactions:
197
+ newTransactions.length,
198
+ },
199
+ );
200
+ }
201
+
202
+ if (
203
+ givenExpectedNewHash &&
204
+ givenExpectedNewHash !== expectedNewHash
205
+ ) {
206
+ return err({
207
+ type: "InvalidHash",
208
+ id: this.id,
209
+ expectedNewHash,
210
+ givenExpectedNewHash,
211
+ } satisfies InvalidHashError);
212
+ }
213
+
214
+ performance.mark("verifyStart" + this.id);
215
+ if (
216
+ !this.crypto.verify(
217
+ newSignature,
218
+ expectedNewHash,
219
+ signerID,
220
+ )
221
+ ) {
222
+ return err({
223
+ type: "InvalidSignature",
224
+ id: this.id,
225
+ newSignature,
226
+ sessionID,
227
+ signerID,
228
+ } satisfies InvalidSignatureError);
229
+ }
230
+ performance.mark("verifyEnd" + this.id);
231
+ performance.measure(
232
+ "verify" + this.id,
233
+ "verifyStart" + this.id,
234
+ "verifyEnd" + this.id,
235
+ );
236
+
237
+ this.doAddTransactions(
238
+ sessionID,
239
+ newTransactions,
240
+ newSignature,
241
+ expectedNewHash,
242
+ newStreamingHash,
243
+ "deferred",
244
+ );
245
+
246
+ return ok(true as const);
247
+ });
248
+ }),
249
+ )
250
+ .map((trueResult) => {
251
+ thisDone();
252
+ return trueResult;
253
+ })
254
+ .mapErr((err) => {
255
+ thisDone();
256
+ return err;
257
+ });
258
+ }*/
259
+ doAddTransactions(sessionID, newTransactions, newSignature, expectedNewHash, newStreamingHash, notifyMode) {
260
+ if (this.node.crashed) {
261
+ throw new Error("Trying to add transactions after node is crashed");
262
+ }
263
+ const transactions = this.sessionLogs.get(sessionID)?.transactions ?? [];
264
+ transactions.push(...newTransactions);
265
+ const signatureAfter = this.sessionLogs.get(sessionID)?.signatureAfter ?? {};
266
+ const lastInbetweenSignatureIdx = Object.keys(signatureAfter).reduce((max, idx) => (parseInt(idx) > max ? parseInt(idx) : max), -1);
267
+ const sizeOfTxsSinceLastInbetweenSignature = transactions
268
+ .slice(lastInbetweenSignatureIdx + 1)
269
+ .reduce((sum, tx) => sum +
270
+ (tx.privacy === "private"
271
+ ? tx.encryptedChanges.length
272
+ : tx.changes.length), 0);
273
+ if (sizeOfTxsSinceLastInbetweenSignature > MAX_RECOMMENDED_TX_SIZE) {
274
+ // console.log(
275
+ // "Saving inbetween signature for tx ",
276
+ // sessionID,
277
+ // transactions.length - 1,
278
+ // sizeOfTxsSinceLastInbetweenSignature
279
+ // );
280
+ signatureAfter[transactions.length - 1] = newSignature;
281
+ }
282
+ this._sessionLogs.set(sessionID, {
283
+ transactions,
284
+ lastHash: expectedNewHash,
285
+ streamingHash: newStreamingHash,
286
+ lastSignature: newSignature,
287
+ signatureAfter: signatureAfter,
288
+ });
289
+ this._cachedContent = undefined;
290
+ this._cachedKnownState = undefined;
291
+ this._cachedDependentOn = undefined;
292
+ this._cachedNewContentSinceEmpty = undefined;
293
+ if (this.listeners.size > 0) {
294
+ if (notifyMode === "immediate") {
295
+ const content = this.getCurrentContent();
296
+ for (const listener of this.listeners) {
297
+ listener(content);
298
+ }
299
+ }
300
+ else {
301
+ if (!this.nextDeferredNotify) {
302
+ this.nextDeferredNotify = new Promise((resolve) => {
303
+ setTimeout(() => {
304
+ this.nextDeferredNotify = undefined;
305
+ this.deferredUpdates = 0;
306
+ const content = this.getCurrentContent();
307
+ for (const listener of this.listeners) {
308
+ listener(content);
309
+ }
310
+ resolve();
311
+ }, 0);
312
+ });
313
+ }
314
+ this.deferredUpdates++;
315
+ }
316
+ }
317
+ }
318
+ subscribe(listener) {
319
+ this.listeners.add(listener);
320
+ listener(this.getCurrentContent());
321
+ return () => {
322
+ this.listeners.delete(listener);
323
+ };
324
+ }
325
+ expectedNewHashAfter(sessionID, newTransactions) {
326
+ const streamingHash = this.sessionLogs.get(sessionID)?.streamingHash.clone() ??
327
+ new StreamingHash(this.crypto);
328
+ for (const transaction of newTransactions) {
329
+ streamingHash.update(transaction);
330
+ }
331
+ const newStreamingHash = streamingHash.clone();
332
+ return {
333
+ expectedNewHash: streamingHash.digest(),
334
+ newStreamingHash,
335
+ };
336
+ }
337
+ async expectedNewHashAfterAsync(sessionID, newTransactions) {
338
+ const streamingHash = this.sessionLogs.get(sessionID)?.streamingHash.clone() ??
339
+ new StreamingHash(this.crypto);
340
+ let before = performance.now();
341
+ for (const transaction of newTransactions) {
342
+ streamingHash.update(transaction);
343
+ const after = performance.now();
344
+ if (after - before > 1) {
345
+ // console.log("Hashing blocked for", after - before);
346
+ await new Promise((resolve) => setTimeout(resolve, 0));
347
+ before = performance.now();
348
+ }
349
+ }
350
+ const newStreamingHash = streamingHash.clone();
351
+ return {
352
+ expectedNewHash: streamingHash.digest(),
353
+ newStreamingHash,
354
+ };
355
+ }
356
+ makeTransaction(changes, privacy) {
357
+ const madeAt = Date.now();
358
+ let transaction;
359
+ if (privacy === "private") {
360
+ const { secret: keySecret, id: keyID } = this.getCurrentReadKey();
361
+ if (!keySecret) {
362
+ throw new Error("Can't make transaction without read key secret");
363
+ }
364
+ const encrypted = this.crypto.encryptForTransaction(changes, keySecret, {
365
+ in: this.id,
366
+ tx: this.nextTransactionID(),
367
+ });
368
+ this._decryptionCache[encrypted] = changes;
369
+ transaction = {
370
+ privacy: "private",
371
+ madeAt,
372
+ keyUsed: keyID,
373
+ encryptedChanges: encrypted,
374
+ };
375
+ }
376
+ else {
377
+ transaction = {
378
+ privacy: "trusting",
379
+ madeAt,
380
+ changes: stableStringify(changes),
381
+ };
382
+ }
383
+ // This is an ugly hack to get a unique but stable session ID for editing the current account
384
+ const sessionID = this.header.meta?.type === "account"
385
+ ? this.node.currentSessionID.replace(this.node.account.id, this.node.account
386
+ .currentAgentID()
387
+ ._unsafeUnwrap({ withStackTrace: true }))
388
+ : this.node.currentSessionID;
389
+ const { expectedNewHash } = this.expectedNewHashAfter(sessionID, [
390
+ transaction,
391
+ ]);
392
+ const signature = this.crypto.sign(this.node.account.currentSignerSecret(), expectedNewHash);
393
+ const success = this.tryAddTransactions(sessionID, [transaction], expectedNewHash, signature)._unsafeUnwrap({ withStackTrace: true });
394
+ if (success) {
395
+ void this.node.syncManager.syncCoValue(this);
396
+ }
397
+ return success;
398
+ }
399
+ getCurrentContent(options) {
400
+ if (!options?.ignorePrivateTransactions && this._cachedContent) {
401
+ return this._cachedContent;
402
+ }
403
+ const newContent = coreToCoValue(this, options);
404
+ if (!options?.ignorePrivateTransactions) {
405
+ this._cachedContent = newContent;
406
+ }
407
+ return newContent;
408
+ }
409
+ getValidSortedTransactions(options) {
410
+ const validTransactions = determineValidTransactions(this);
411
+ const allTransactions = validTransactions
412
+ .flatMap(({ txID, tx }) => {
413
+ if (tx.privacy === "trusting") {
414
+ return {
415
+ txID,
416
+ madeAt: tx.madeAt,
417
+ changes: parseJSON(tx.changes),
418
+ };
419
+ }
420
+ else {
421
+ if (options?.ignorePrivateTransactions) {
422
+ return undefined;
423
+ }
424
+ const readKey = this.getReadKey(tx.keyUsed);
425
+ if (!readKey) {
426
+ return undefined;
427
+ }
428
+ else {
429
+ let decrytedChanges = this._decryptionCache[tx.encryptedChanges];
430
+ if (!decrytedChanges) {
431
+ const decryptedString = this.crypto.decryptRawForTransaction(tx.encryptedChanges, readKey, {
432
+ in: this.id,
433
+ tx: txID,
434
+ });
435
+ decrytedChanges =
436
+ decryptedString && parseJSON(decryptedString);
437
+ this._decryptionCache[tx.encryptedChanges] =
438
+ decrytedChanges;
439
+ }
440
+ if (!decrytedChanges) {
441
+ console.error("Failed to decrypt transaction despite having key");
442
+ return undefined;
443
+ }
444
+ return {
445
+ txID,
446
+ madeAt: tx.madeAt,
447
+ changes: decrytedChanges,
448
+ };
449
+ }
450
+ }
451
+ })
452
+ .filter((x) => !!x);
453
+ allTransactions.sort((a, b) => a.madeAt - b.madeAt ||
454
+ (a.txID.sessionID < b.txID.sessionID ? -1 : 1) ||
455
+ a.txID.txIndex - b.txID.txIndex);
456
+ return allTransactions;
457
+ }
458
+ getCurrentReadKey() {
459
+ if (this.header.ruleset.type === "group") {
460
+ const content = expectGroup(this.getCurrentContent());
461
+ const currentKeyId = content.get("readKey");
462
+ if (!currentKeyId) {
463
+ throw new Error("No readKey set");
464
+ }
465
+ const secret = this.getReadKey(currentKeyId);
466
+ return {
467
+ secret: secret,
468
+ id: currentKeyId,
469
+ };
470
+ }
471
+ else if (this.header.ruleset.type === "ownedByGroup") {
472
+ return this.node
473
+ .expectCoValueLoaded(this.header.ruleset.group)
474
+ .getCurrentReadKey();
475
+ }
476
+ else {
477
+ throw new Error("Only groups or values owned by groups have read secrets");
478
+ }
479
+ }
480
+ getReadKey(keyID) {
481
+ let key = readKeyCache.get(this)?.[keyID];
482
+ if (!key) {
483
+ key = this.getUncachedReadKey(keyID);
484
+ if (key) {
485
+ let cache = readKeyCache.get(this);
486
+ if (!cache) {
487
+ cache = {};
488
+ readKeyCache.set(this, cache);
489
+ }
490
+ cache[keyID] = key;
491
+ }
492
+ }
493
+ return key;
494
+ }
495
+ getUncachedReadKey(keyID) {
496
+ if (this.header.ruleset.type === "group") {
497
+ const content = expectGroup(this.getCurrentContent({ ignorePrivateTransactions: true }));
498
+ const keyForEveryone = content.get(`${keyID}_for_everyone`);
499
+ if (keyForEveryone)
500
+ return keyForEveryone;
501
+ // Try to find key revelation for us
502
+ const lookupAccountOrAgentID = this.header.meta?.type === "account"
503
+ ? this.node.account
504
+ .currentAgentID()
505
+ ._unsafeUnwrap({ withStackTrace: true })
506
+ : this.node.account.id;
507
+ const lastReadyKeyEdit = content.lastEditAt(`${keyID}_for_${lookupAccountOrAgentID}`);
508
+ if (lastReadyKeyEdit?.value) {
509
+ const revealer = lastReadyKeyEdit.by;
510
+ const revealerAgent = this.node
511
+ .resolveAccountAgent(revealer, "Expected to know revealer")
512
+ ._unsafeUnwrap({ withStackTrace: true });
513
+ const secret = this.crypto.unseal(lastReadyKeyEdit.value, this.node.account.currentSealerSecret(), this.crypto.getAgentSealerID(revealerAgent), {
514
+ in: this.id,
515
+ tx: lastReadyKeyEdit.tx,
516
+ });
517
+ if (secret) {
518
+ return secret;
519
+ }
520
+ }
521
+ // Try to find indirect revelation through previousKeys
522
+ for (const co of content.keys()) {
523
+ if (isKeyForKeyField(co) && co.startsWith(keyID)) {
524
+ const encryptingKeyID = co.split("_for_")[1];
525
+ const encryptingKeySecret = this.getReadKey(encryptingKeyID);
526
+ if (!encryptingKeySecret) {
527
+ continue;
528
+ }
529
+ const encryptedPreviousKey = content.get(co);
530
+ const secret = this.crypto.decryptKeySecret({
531
+ encryptedID: keyID,
532
+ encryptingID: encryptingKeyID,
533
+ encrypted: encryptedPreviousKey,
534
+ }, encryptingKeySecret);
535
+ if (secret) {
536
+ return secret;
537
+ }
538
+ else {
539
+ console.error(`Encrypting ${encryptingKeyID} key didn't decrypt ${keyID}`);
540
+ }
541
+ }
542
+ }
543
+ return undefined;
544
+ }
545
+ else if (this.header.ruleset.type === "ownedByGroup") {
546
+ return this.node
547
+ .expectCoValueLoaded(this.header.ruleset.group)
548
+ .getReadKey(keyID);
549
+ }
550
+ else {
551
+ throw new Error("Only groups or values owned by groups have read secrets");
552
+ }
553
+ }
554
+ getGroup() {
555
+ if (this.header.ruleset.type !== "ownedByGroup") {
556
+ throw new Error("Only values owned by groups have groups");
557
+ }
558
+ return expectGroup(this.node
559
+ .expectCoValueLoaded(this.header.ruleset.group)
560
+ .getCurrentContent());
561
+ }
562
+ getTx(txID) {
563
+ return this.sessionLogs.get(txID.sessionID)?.transactions[txID.txIndex];
564
+ }
565
+ newContentSince(knownState) {
566
+ const isKnownStateEmpty = !knownState?.header && !knownState?.sessions;
567
+ if (isKnownStateEmpty && this._cachedNewContentSinceEmpty) {
568
+ return this._cachedNewContentSinceEmpty;
569
+ }
570
+ let currentPiece = {
571
+ action: "content",
572
+ id: this.id,
573
+ header: knownState?.header ? undefined : this.header,
574
+ priority: getPriorityFromHeader(this.header),
575
+ new: {},
576
+ };
577
+ const pieces = [currentPiece];
578
+ const sentState = {};
579
+ let pieceSize = 0;
580
+ let sessionsTodoAgain = "first";
581
+ while (sessionsTodoAgain === "first" ||
582
+ sessionsTodoAgain?.size ||
583
+ 0 > 0) {
584
+ if (sessionsTodoAgain === "first") {
585
+ sessionsTodoAgain = undefined;
586
+ }
587
+ const sessionsTodo = sessionsTodoAgain ?? this.sessionLogs.keys();
588
+ for (const sessionIDKey of sessionsTodo) {
589
+ const sessionID = sessionIDKey;
590
+ const log = this.sessionLogs.get(sessionID);
591
+ const knownStateForSessionID = knownState?.sessions[sessionID];
592
+ const sentStateForSessionID = sentState[sessionID];
593
+ const nextKnownSignatureIdx = getNextKnownSignatureIdx(log, knownStateForSessionID, sentStateForSessionID);
594
+ const firstNewTxIdx = sentStateForSessionID ?? knownStateForSessionID ?? 0;
595
+ const afterLastNewTxIdx = nextKnownSignatureIdx === undefined
596
+ ? log.transactions.length
597
+ : nextKnownSignatureIdx + 1;
598
+ const nNewTx = Math.max(0, afterLastNewTxIdx - firstNewTxIdx);
599
+ if (nNewTx === 0) {
600
+ sessionsTodoAgain?.delete(sessionID);
601
+ continue;
602
+ }
603
+ if (afterLastNewTxIdx < log.transactions.length) {
604
+ if (!sessionsTodoAgain) {
605
+ sessionsTodoAgain = new Set();
606
+ }
607
+ sessionsTodoAgain.add(sessionID);
608
+ }
609
+ const oldPieceSize = pieceSize;
610
+ for (let txIdx = firstNewTxIdx; txIdx < afterLastNewTxIdx; txIdx++) {
611
+ const tx = log.transactions[txIdx];
612
+ pieceSize +=
613
+ tx.privacy === "private"
614
+ ? tx.encryptedChanges.length
615
+ : tx.changes.length;
616
+ }
617
+ if (pieceSize >= MAX_RECOMMENDED_TX_SIZE) {
618
+ currentPiece = {
619
+ action: "content",
620
+ id: this.id,
621
+ header: undefined,
622
+ new: {},
623
+ priority: getPriorityFromHeader(this.header),
624
+ };
625
+ pieces.push(currentPiece);
626
+ pieceSize = pieceSize - oldPieceSize;
627
+ }
628
+ let sessionEntry = currentPiece.new[sessionID];
629
+ if (!sessionEntry) {
630
+ sessionEntry = {
631
+ after: sentStateForSessionID ??
632
+ knownStateForSessionID ??
633
+ 0,
634
+ newTransactions: [],
635
+ lastSignature: "WILL_BE_REPLACED",
636
+ };
637
+ currentPiece.new[sessionID] = sessionEntry;
638
+ }
639
+ for (let txIdx = firstNewTxIdx; txIdx < afterLastNewTxIdx; txIdx++) {
640
+ const tx = log.transactions[txIdx];
641
+ sessionEntry.newTransactions.push(tx);
642
+ }
643
+ sessionEntry.lastSignature =
644
+ nextKnownSignatureIdx === undefined
645
+ ? log.lastSignature
646
+ : log.signatureAfter[nextKnownSignatureIdx];
647
+ sentState[sessionID] =
648
+ (sentStateForSessionID ?? knownStateForSessionID ?? 0) +
649
+ nNewTx;
650
+ }
651
+ }
652
+ const piecesWithContent = pieces.filter((piece) => Object.keys(piece.new).length > 0 || piece.header);
653
+ if (piecesWithContent.length === 0) {
654
+ return undefined;
655
+ }
656
+ if (isKnownStateEmpty) {
657
+ this._cachedNewContentSinceEmpty = piecesWithContent;
658
+ }
659
+ return piecesWithContent;
660
+ }
661
+ getDependedOnCoValues() {
662
+ if (this._cachedDependentOn) {
663
+ return this._cachedDependentOn;
664
+ }
665
+ else {
666
+ const dependentOn = this.getDependedOnCoValuesUncached();
667
+ this._cachedDependentOn = dependentOn;
668
+ return dependentOn;
669
+ }
670
+ }
671
+ /** @internal */
672
+ getDependedOnCoValuesUncached() {
673
+ return this.header.ruleset.type === "group"
674
+ ? expectGroup(this.getCurrentContent())
675
+ .keys()
676
+ .filter((k) => k.startsWith("co_"))
677
+ : this.header.ruleset.type === "ownedByGroup"
678
+ ? [
679
+ this.header.ruleset.group,
680
+ ...new Set([...this.sessionLogs.keys()]
681
+ .map((sessionID) => accountOrAgentIDfromSessionID(sessionID))
682
+ .filter((session) => isAccountID(session) && session !== this.id)),
683
+ ]
684
+ : [];
685
+ }
686
+ }
687
+ function getNextKnownSignatureIdx(log, knownStateForSessionID, sentStateForSessionID) {
688
+ return Object.keys(log.signatureAfter)
689
+ .map(Number)
690
+ .sort((a, b) => a - b)
691
+ .find((idx) => idx >= (sentStateForSessionID ?? knownStateForSessionID ?? -1));
692
+ }
693
+ //# sourceMappingURL=coValueCore.js.map