cojson 0.18.26 → 0.18.28

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 (161) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +17 -0
  3. package/dist/PeerKnownStates.d.ts +4 -3
  4. package/dist/PeerKnownStates.d.ts.map +1 -1
  5. package/dist/PeerKnownStates.js +27 -18
  6. package/dist/PeerKnownStates.js.map +1 -1
  7. package/dist/PeerState.d.ts +3 -2
  8. package/dist/PeerState.d.ts.map +1 -1
  9. package/dist/PeerState.js.map +1 -1
  10. package/dist/SyncStateManager.d.ts +2 -2
  11. package/dist/SyncStateManager.d.ts.map +1 -1
  12. package/dist/SyncStateManager.js +2 -10
  13. package/dist/SyncStateManager.js.map +1 -1
  14. package/dist/coValueContentMessage.d.ts +1 -1
  15. package/dist/coValueContentMessage.d.ts.map +1 -1
  16. package/dist/coValueContentMessage.js +1 -1
  17. package/dist/coValueContentMessage.js.map +1 -1
  18. package/dist/coValueCore/SessionMap.d.ts +6 -3
  19. package/dist/coValueCore/SessionMap.d.ts.map +1 -1
  20. package/dist/coValueCore/SessionMap.js +41 -8
  21. package/dist/coValueCore/SessionMap.js.map +1 -1
  22. package/dist/coValueCore/branching.d.ts +5 -4
  23. package/dist/coValueCore/branching.d.ts.map +1 -1
  24. package/dist/coValueCore/branching.js +22 -4
  25. package/dist/coValueCore/branching.js.map +1 -1
  26. package/dist/coValueCore/coValueCore.d.ts +29 -25
  27. package/dist/coValueCore/coValueCore.d.ts.map +1 -1
  28. package/dist/coValueCore/coValueCore.js +163 -126
  29. package/dist/coValueCore/coValueCore.js.map +1 -1
  30. package/dist/coValueCore/decryptTransactionChangesAndMeta.d.ts +3 -0
  31. package/dist/coValueCore/decryptTransactionChangesAndMeta.d.ts.map +1 -0
  32. package/dist/coValueCore/decryptTransactionChangesAndMeta.js +34 -0
  33. package/dist/coValueCore/decryptTransactionChangesAndMeta.js.map +1 -0
  34. package/dist/coValueCore/verifiedState.d.ts +12 -6
  35. package/dist/coValueCore/verifiedState.d.ts.map +1 -1
  36. package/dist/coValueCore/verifiedState.js +28 -56
  37. package/dist/coValueCore/verifiedState.js.map +1 -1
  38. package/dist/coValues/coMap.d.ts.map +1 -1
  39. package/dist/coValues/coMap.js.map +1 -1
  40. package/dist/coValues/coStream.d.ts.map +1 -1
  41. package/dist/coValues/coStream.js.map +1 -1
  42. package/dist/exports.d.ts +3 -2
  43. package/dist/exports.d.ts.map +1 -1
  44. package/dist/exports.js +2 -1
  45. package/dist/exports.js.map +1 -1
  46. package/dist/knownState.d.ts +58 -2
  47. package/dist/knownState.d.ts.map +1 -1
  48. package/dist/knownState.js +79 -5
  49. package/dist/knownState.js.map +1 -1
  50. package/dist/localNode.js +1 -1
  51. package/dist/localNode.js.map +1 -1
  52. package/dist/permissions.d.ts.map +1 -1
  53. package/dist/permissions.js +18 -20
  54. package/dist/permissions.js.map +1 -1
  55. package/dist/storage/knownState.d.ts +1 -1
  56. package/dist/storage/knownState.d.ts.map +1 -1
  57. package/dist/storage/knownState.js +2 -3
  58. package/dist/storage/knownState.js.map +1 -1
  59. package/dist/storage/storageAsync.d.ts +2 -1
  60. package/dist/storage/storageAsync.d.ts.map +1 -1
  61. package/dist/storage/storageAsync.js +5 -6
  62. package/dist/storage/storageAsync.js.map +1 -1
  63. package/dist/storage/storageSync.d.ts +2 -1
  64. package/dist/storage/storageSync.d.ts.map +1 -1
  65. package/dist/storage/storageSync.js +5 -5
  66. package/dist/storage/storageSync.js.map +1 -1
  67. package/dist/storage/types.d.ts +2 -1
  68. package/dist/storage/types.d.ts.map +1 -1
  69. package/dist/sync.d.ts +2 -12
  70. package/dist/sync.d.ts.map +1 -1
  71. package/dist/sync.js +9 -38
  72. package/dist/sync.js.map +1 -1
  73. package/dist/tests/PeerKnownStates.test.js +1 -1
  74. package/dist/tests/PeerKnownStates.test.js.map +1 -1
  75. package/dist/tests/PeerState.test.js +19 -0
  76. package/dist/tests/PeerState.test.js.map +1 -1
  77. package/dist/tests/PureJSCrypto.test.js.map +1 -1
  78. package/dist/tests/StorageApiAsync.test.js +1 -1
  79. package/dist/tests/StorageApiAsync.test.js.map +1 -1
  80. package/dist/tests/StorageApiSync.test.js +1 -2
  81. package/dist/tests/StorageApiSync.test.js.map +1 -1
  82. package/dist/tests/StoreQueue.test.js.map +1 -1
  83. package/dist/tests/SyncStateManager.test.js +1 -1
  84. package/dist/tests/SyncStateManager.test.js.map +1 -1
  85. package/dist/tests/branching.test.js +237 -28
  86. package/dist/tests/branching.test.js.map +1 -1
  87. package/dist/tests/coValueContentMessage.test.js +1 -1
  88. package/dist/tests/coValueContentMessage.test.js.map +1 -1
  89. package/dist/tests/coValueCore.loadFromStorage.test.d.ts +2 -0
  90. package/dist/tests/coValueCore.loadFromStorage.test.d.ts.map +1 -0
  91. package/dist/tests/coValueCore.loadFromStorage.test.js +395 -0
  92. package/dist/tests/coValueCore.loadFromStorage.test.js.map +1 -0
  93. package/dist/tests/coValueCore.loadingState.test.d.ts +2 -0
  94. package/dist/tests/coValueCore.loadingState.test.d.ts.map +1 -0
  95. package/dist/tests/{coValueCoreLoadingState.test.js → coValueCore.loadingState.test.js} +4 -12
  96. package/dist/tests/coValueCore.loadingState.test.js.map +1 -0
  97. package/dist/tests/coValueCore.test.js +30 -5
  98. package/dist/tests/coValueCore.test.js.map +1 -1
  99. package/dist/tests/knownState.test.d.ts +2 -0
  100. package/dist/tests/knownState.test.d.ts.map +1 -0
  101. package/dist/tests/knownState.test.js +510 -0
  102. package/dist/tests/knownState.test.js.map +1 -0
  103. package/dist/tests/messagesTestUtils.d.ts.map +1 -1
  104. package/dist/tests/messagesTestUtils.js.map +1 -1
  105. package/dist/tests/priority.test.js.map +1 -1
  106. package/dist/tests/sync.mesh.test.js +4 -34
  107. package/dist/tests/sync.mesh.test.js.map +1 -1
  108. package/dist/tests/sync.storage.test.js.map +1 -1
  109. package/dist/tests/sync.upload.test.js.map +1 -1
  110. package/dist/tests/testUtils.d.ts +7 -1
  111. package/dist/tests/testUtils.d.ts.map +1 -1
  112. package/dist/tests/testUtils.js +12 -0
  113. package/dist/tests/testUtils.js.map +1 -1
  114. package/package.json +3 -3
  115. package/src/PeerKnownStates.ts +36 -22
  116. package/src/PeerState.ts +2 -1
  117. package/src/SyncStateManager.ts +4 -17
  118. package/src/coValueContentMessage.ts +2 -1
  119. package/src/coValueCore/SessionMap.ts +66 -11
  120. package/src/coValueCore/branching.ts +37 -13
  121. package/src/coValueCore/coValueCore.ts +224 -177
  122. package/src/coValueCore/decryptTransactionChangesAndMeta.ts +56 -0
  123. package/src/coValueCore/verifiedState.ts +32 -64
  124. package/src/coValues/coMap.ts +1 -5
  125. package/src/coValues/coStream.ts +1 -5
  126. package/src/exports.ts +2 -2
  127. package/src/knownState.ts +118 -12
  128. package/src/localNode.ts +1 -1
  129. package/src/permissions.ts +21 -22
  130. package/src/storage/knownState.ts +9 -3
  131. package/src/storage/storageAsync.ts +15 -7
  132. package/src/storage/storageSync.ts +16 -8
  133. package/src/storage/types.ts +2 -1
  134. package/src/sync.ts +14 -60
  135. package/src/tests/PeerKnownStates.test.ts +1 -1
  136. package/src/tests/PeerState.test.ts +29 -3
  137. package/src/tests/PureJSCrypto.test.ts +0 -1
  138. package/src/tests/StorageApiAsync.test.ts +3 -6
  139. package/src/tests/StorageApiSync.test.ts +2 -6
  140. package/src/tests/StoreQueue.test.ts +3 -2
  141. package/src/tests/SyncStateManager.test.ts +1 -1
  142. package/src/tests/branching.test.ts +392 -45
  143. package/src/tests/coValueContentMessage.test.ts +2 -2
  144. package/src/tests/coValueCore.loadFromStorage.test.ts +540 -0
  145. package/src/tests/{coValueCoreLoadingState.test.ts → coValueCore.loadingState.test.ts} +3 -16
  146. package/src/tests/coValueCore.test.ts +40 -5
  147. package/src/tests/knownState.test.ts +665 -0
  148. package/src/tests/messagesTestUtils.ts +2 -1
  149. package/src/tests/priority.test.ts +0 -2
  150. package/src/tests/sync.mesh.test.ts +11 -38
  151. package/src/tests/sync.storage.test.ts +0 -1
  152. package/src/tests/sync.upload.test.ts +0 -1
  153. package/src/tests/testUtils.ts +22 -2
  154. package/dist/coValueCore/decodeTransactionChangesAndMeta.d.ts +0 -3
  155. package/dist/coValueCore/decodeTransactionChangesAndMeta.d.ts.map +0 -1
  156. package/dist/coValueCore/decodeTransactionChangesAndMeta.js +0 -59
  157. package/dist/coValueCore/decodeTransactionChangesAndMeta.js.map +0 -1
  158. package/dist/tests/coValueCoreLoadingState.test.d.ts +0 -2
  159. package/dist/tests/coValueCoreLoadingState.test.d.ts.map +0 -1
  160. package/dist/tests/coValueCoreLoadingState.test.js.map +0 -1
  161. package/src/coValueCore/decodeTransactionChangesAndMeta.ts +0 -81
@@ -20,12 +20,7 @@ import { JsonObject, JsonValue } from "../jsonValue.js";
20
20
  import { LocalNode, ResolveAccountAgentError } from "../localNode.js";
21
21
  import { logger } from "../logger.js";
22
22
  import { determineValidTransactions } from "../permissions.js";
23
- import {
24
- CoValueKnownState,
25
- NewContentMessage,
26
- PeerID,
27
- emptyKnownState,
28
- } from "../sync.js";
23
+ import { NewContentMessage, PeerID } from "../sync.js";
29
24
  import { accountOrAgentIDfromSessionID } from "../typeUtils/accountOrAgentIDfromSessionID.js";
30
25
  import { expectGroup } from "../typeUtils/expectGroup.js";
31
26
  import {
@@ -47,8 +42,14 @@ import {
47
42
  BranchStartCommit,
48
43
  } from "./branching.js";
49
44
  import { type RawAccountID } from "../coValues/account.js";
50
- import { decodeTransactionChangesAndMeta } from "./decodeTransactionChangesAndMeta.js";
51
- import { combineKnownStateSessions } from "../knownState.js";
45
+ import { decryptTransactionChangesAndMeta } from "./decryptTransactionChangesAndMeta.js";
46
+ import {
47
+ combineKnownStateSessions,
48
+ CoValueKnownState,
49
+ emptyKnownState,
50
+ KnownStateSessions,
51
+ } from "../knownState.js";
52
+ import { safeParseJSON } from "../jsonStringify.js";
52
53
 
53
54
  export function idforHeader(
54
55
  header: CoValueHeader,
@@ -58,41 +59,102 @@ export function idforHeader(
58
59
  return `co_z${hash.slice("shortHash_z".length)}`;
59
60
  }
60
61
 
61
- export type VerifiedTransaction = {
62
+ export class VerifiedTransaction {
62
63
  // The account or agent that made the transaction
63
64
  author: RawAccountID | AgentID;
64
65
  // An object containing the session ID and the transaction index
65
- txID: TransactionID;
66
+ currentTxID: TransactionID;
67
+ // If this is a merged transaction, the TxID of the transaction inside the original branch
68
+ sourceTxID: TransactionID | undefined;
66
69
  tx: Transaction;
67
70
  // The Unix time when the transaction was made
68
- madeAt: number;
69
- // Whether the transaction has been validated, used to track if determinedValidTransactions needs to be check this
70
- isValidated: boolean;
71
+ currentMadeAt: number;
72
+ // If this is a merged transaction, the madeAt of the transaction inside the original branch
73
+ sourceTxMadeAt: number | undefined;
71
74
  // The decoded changes of the transaction
72
75
  changes: JsonValue[] | undefined;
73
76
  // The decoded meta information of the transaction
74
77
  meta: JsonObject | undefined;
75
-
76
78
  // Whether the transaction is valid, as per membership rules
77
- isValid: boolean;
78
-
79
- // True if we encountered an error while decoding the changes
80
- hasInvalidChanges: boolean;
81
- // True if we encountered an error while parsing the meta
82
- hasInvalidMeta: boolean;
83
-
79
+ isValid: boolean = false;
80
+ // Whether the transaction has been validated, used to track if determinedValidTransactions needs to check this
81
+ isValidated: boolean = false;
82
+ // True if the transaction has been decrypted
83
+ isDecrypted: boolean = false;
84
84
  // True if the meta information has been parsed and loaded in the CoValueCore
85
- hasMetaBeenParsed: boolean;
86
-
85
+ hasMetaBeenParsed: boolean = false;
87
86
  // The previous verified transaction for the same session
88
87
  previous: VerifiedTransaction | undefined;
89
- };
90
88
 
91
- export type DecryptedTransaction = {
92
- txID: TransactionID;
89
+ constructor(
90
+ sessionID: SessionID,
91
+ txIndex: number,
92
+ tx: Transaction,
93
+ branchId: RawCoID | undefined,
94
+ parsingCache:
95
+ | { changes: JsonValue[]; meta: JsonObject | undefined }
96
+ | undefined,
97
+ previous: VerifiedTransaction | undefined,
98
+ ) {
99
+ this.author = accountOrAgentIDfromSessionID(sessionID);
100
+
101
+ const txID = branchId
102
+ ? {
103
+ sessionID,
104
+ txIndex,
105
+ branch: branchId,
106
+ }
107
+ : {
108
+ sessionID,
109
+ txIndex,
110
+ };
111
+
112
+ this.currentTxID = txID;
113
+ this.sourceTxID = undefined;
114
+ this.tx = tx;
115
+ this.currentMadeAt = tx.madeAt;
116
+ this.sourceTxMadeAt = undefined;
117
+ this.isValidated = false;
118
+
119
+ this.previous = previous;
120
+
121
+ if (parsingCache) {
122
+ this.changes = parsingCache.changes;
123
+ this.meta = parsingCache.meta;
124
+ } else {
125
+ // Decoding the trusting transactions here because they might be useful in the permissions checks
126
+ if (this.tx.privacy === "trusting") {
127
+ this.changes = safeParseJSON(this.tx.changes);
128
+
129
+ if (this.tx.meta) {
130
+ this.meta = safeParseJSON(this.tx.meta);
131
+ }
132
+ }
133
+ }
134
+ }
135
+
136
+ // The TxID that refers to the current position in the session map
137
+ // If this is a merged transaction, the txID is the TxID of the merged transaction
138
+ get txID() {
139
+ return this.sourceTxID ?? this.currentTxID;
140
+ }
141
+
142
+ // The madeAt that refers to the time when the transaction was made
143
+ // If this is a merged transaction, the madeAt is the time when the transaction has been made in the branch
144
+ get madeAt() {
145
+ return this.sourceTxMadeAt ?? this.currentMadeAt;
146
+ }
147
+
148
+ isValidTransactionWithChanges(): this is {
149
+ changes: JsonValue[];
150
+ isValid: true;
151
+ } {
152
+ return Boolean(this.isValid && this.changes);
153
+ }
154
+ }
155
+
156
+ export type DecryptedTransaction = Omit<VerifiedTransaction, "changes"> & {
93
157
  changes: JsonValue[];
94
- madeAt: number;
95
- tx: Transaction;
96
158
  };
97
159
 
98
160
  export type AvailableCoValueCore = CoValueCore & { verified: VerifiedState };
@@ -119,8 +181,9 @@ export class CoValueCore {
119
181
  get verified() {
120
182
  return this._verified;
121
183
  }
122
- private readonly peers = new Map<
123
- PeerID,
184
+
185
+ private readonly loadingStatuses = new Map<
186
+ PeerID | "storage",
124
187
  | {
125
188
  type: "unknown" | "pending" | "available" | "unavailable";
126
189
  }
@@ -136,18 +199,10 @@ export class CoValueCore {
136
199
  new Set();
137
200
  private counter: UpDownCounter;
138
201
 
139
- private constructor(
140
- init: { header: CoValueHeader } | { id: RawCoID },
141
- node: LocalNode,
142
- ) {
202
+ constructor(id: RawCoID, node: LocalNode) {
143
203
  this.crypto = node.crypto;
144
- if ("header" in init) {
145
- this.id = idforHeader(init.header, node.crypto);
146
- this._verified = new VerifiedState(this.id, node.crypto, init.header);
147
- } else {
148
- this.id = init.id;
149
- this._verified = null;
150
- }
204
+ this.id = id;
205
+ this._verified = null;
151
206
  this.node = node;
152
207
 
153
208
  this.counter = metrics
@@ -161,25 +216,14 @@ export class CoValueCore {
161
216
  this.updateCounter(null);
162
217
  }
163
218
 
164
- static fromID(id: RawCoID, node: LocalNode): CoValueCore {
165
- return new CoValueCore({ id }, node);
166
- }
167
-
168
- static fromHeader(
169
- header: CoValueHeader,
170
- node: LocalNode,
171
- ): AvailableCoValueCore {
172
- return new CoValueCore({ header }, node) as AvailableCoValueCore;
173
- }
174
-
175
219
  get loadingState() {
176
220
  if (this.verified) {
177
221
  return "available";
178
- } else if (this.peers.size === 0) {
222
+ } else if (this.loadingStatuses.size === 0) {
179
223
  return "unknown";
180
224
  }
181
225
 
182
- for (const peer of this.peers.values()) {
226
+ for (const peer of this.loadingStatuses.values()) {
183
227
  if (peer.type === "pending") {
184
228
  return "loading";
185
229
  } else if (peer.type === "unknown") {
@@ -203,53 +247,38 @@ export class CoValueCore {
203
247
  }
204
248
 
205
249
  isErroredInPeer(peerId: PeerID) {
206
- return this.peers.get(peerId)?.type === "errored";
250
+ return this.getLoadingStateForPeer(peerId) === "errored";
207
251
  }
208
252
 
209
- waitForAvailableOrUnavailable(): Promise<CoValueCore> {
253
+ waitFor(callback: (value: CoValueCore) => boolean) {
210
254
  return new Promise<CoValueCore>((resolve) => {
211
- const listener = (core: CoValueCore) => {
212
- if (core.isAvailable() || core.loadingState === "unavailable") {
255
+ this.subscribe((core, unsubscribe) => {
256
+ if (callback(core)) {
257
+ unsubscribe();
213
258
  resolve(core);
214
- this.listeners.delete(listener);
215
259
  }
216
- };
217
-
218
- this.listeners.add(listener);
219
- listener(this);
260
+ }, true);
220
261
  });
221
262
  }
222
263
 
223
- waitForAvailable(): Promise<CoValueCore> {
224
- return new Promise<CoValueCore>((resolve) => {
225
- const listener = (core: CoValueCore) => {
226
- if (core.isAvailable()) {
227
- resolve(core);
228
- this.listeners.delete(listener);
229
- }
230
- };
264
+ waitForAvailableOrUnavailable(): Promise<CoValueCore> {
265
+ return this.waitFor(
266
+ (core) => core.isAvailable() || core.loadingState === "unavailable",
267
+ );
268
+ }
231
269
 
232
- this.listeners.add(listener);
233
- listener(this);
234
- });
270
+ waitForAvailable(): Promise<CoValueCore> {
271
+ return this.waitFor((core) => core.isAvailable());
235
272
  }
236
273
 
237
274
  waitForFullStreaming(): Promise<CoValueCore> {
238
- return new Promise<CoValueCore>((resolve) => {
239
- const listener = (core: CoValueCore) => {
240
- if (core.isAvailable() && !core.verified.isStreaming()) {
241
- resolve(core);
242
- this.listeners.delete(listener);
243
- }
244
- };
245
-
246
- this.listeners.add(listener);
247
- listener(this);
248
- });
275
+ return this.waitFor(
276
+ (core) => core.isAvailable() && !core.verified.isStreaming(),
277
+ );
249
278
  }
250
279
 
251
- getStateForPeer(peerId: PeerID) {
252
- return this.peers.get(peerId);
280
+ getLoadingStateForPeer(peerId: PeerID) {
281
+ return this.loadingStatuses.get(peerId)?.type ?? "unknown";
253
282
  }
254
283
 
255
284
  private updateCounter(previousState: string | null) {
@@ -289,13 +318,13 @@ export class CoValueCore {
289
318
 
290
319
  markNotFoundInPeer(peerId: PeerID) {
291
320
  const previousState = this.loadingState;
292
- this.peers.set(peerId, { type: "unavailable" });
321
+ this.loadingStatuses.set(peerId, { type: "unavailable" });
293
322
  this.updateCounter(previousState);
294
323
  this.scheduleNotifyUpdate();
295
324
  }
296
325
 
297
326
  markFoundInPeer(peerId: PeerID, previousState: string) {
298
- this.peers.set(peerId, { type: "available" });
327
+ this.loadingStatuses.set(peerId, { type: "available" });
299
328
  this.updateCounter(previousState);
300
329
  this.scheduleNotifyUpdate();
301
330
  }
@@ -378,7 +407,7 @@ export class CoValueCore {
378
407
 
379
408
  provideHeader(
380
409
  header: CoValueHeader,
381
- streamingKnownState?: CoValueKnownState["sessions"],
410
+ streamingKnownState?: KnownStateSessions,
382
411
  skipVerify?: boolean,
383
412
  ) {
384
413
  if (!skipVerify) {
@@ -400,8 +429,7 @@ export class CoValueCore {
400
429
  this.id,
401
430
  this.node.crypto,
402
431
  header,
403
- new SessionMap(this.id, this.node.crypto),
404
- streamingKnownState,
432
+ new SessionMap(this.id, this.node.crypto, streamingKnownState),
405
433
  );
406
434
 
407
435
  return true;
@@ -409,14 +437,14 @@ export class CoValueCore {
409
437
 
410
438
  markErrored(peerId: PeerID, error: TryAddTransactionsError) {
411
439
  const previousState = this.loadingState;
412
- this.peers.set(peerId, { type: "errored", error });
440
+ this.loadingStatuses.set(peerId, { type: "errored", error });
413
441
  this.updateCounter(previousState);
414
442
  this.scheduleNotifyUpdate();
415
443
  }
416
444
 
417
445
  markPending(peerId: PeerID) {
418
446
  const previousState = this.loadingState;
419
- this.peers.set(peerId, { type: "pending" });
447
+ this.loadingStatuses.set(peerId, { type: "pending" });
420
448
  this.updateCounter(previousState);
421
449
  this.scheduleNotifyUpdate();
422
450
  }
@@ -815,36 +843,19 @@ export class CoValueCore {
815
843
  continue;
816
844
  }
817
845
 
818
- const txID = isBranched
819
- ? {
820
- sessionID,
821
- txIndex,
822
- branch: this.id,
823
- }
824
- : {
825
- sessionID,
826
- txIndex,
827
- };
828
-
829
846
  const cache = this.parsingCache.get(tx);
830
847
  if (cache) {
831
848
  this.parsingCache.delete(tx);
832
849
  }
833
850
 
834
- const verifiedTransaction = {
835
- author: accountOrAgentIDfromSessionID(sessionID),
836
- txID,
837
- madeAt: tx.madeAt,
838
- isValidated: false,
839
- isValid: false,
840
- changes: cache?.changes,
841
- meta: cache?.meta,
842
- hasInvalidChanges: false,
843
- hasInvalidMeta: false,
844
- hasMetaBeenParsed: false,
851
+ const verifiedTransaction = new VerifiedTransaction(
852
+ sessionID,
853
+ txIndex,
845
854
  tx,
846
- previous: this.lastVerifiedTransactionBySessionID[sessionID],
847
- };
855
+ isBranched ? this.id : undefined,
856
+ cache,
857
+ this.lastVerifiedTransactionBySessionID[sessionID],
858
+ );
848
859
 
849
860
  if (verifiedTransaction.madeAt > this.latestTxMadeAt) {
850
861
  this.latestTxMadeAt = verifiedTransaction.madeAt;
@@ -920,19 +931,33 @@ export class CoValueCore {
920
931
  const meta = transaction.meta as MergedTransactionMetadata;
921
932
 
922
933
  // Check if the transaction is a merge commit
923
- const previousTransaction = transaction.previous?.txID;
924
- const sessionID = meta.s ?? previousTransaction?.sessionID;
934
+ const previousTransaction = transaction.previous;
935
+ const sessionID = meta.s ?? previousTransaction?.txID.sessionID;
936
+
937
+ if (meta.t) {
938
+ transaction.sourceTxMadeAt = transaction.currentMadeAt - meta.t;
939
+ } else if (previousTransaction) {
940
+ transaction.sourceTxMadeAt = previousTransaction.madeAt;
941
+ }
942
+
943
+ // Check against tampering of the meta.t value to write transaction after the access revocation
944
+ if (
945
+ transaction.sourceTxMadeAt &&
946
+ transaction.sourceTxMadeAt > transaction.currentMadeAt
947
+ ) {
948
+ transaction.isValid = false;
949
+ }
925
950
 
926
951
  if (sessionID) {
927
- transaction.txID = {
952
+ transaction.sourceTxID = {
928
953
  sessionID,
929
954
  txIndex: meta.mi,
930
- branch: meta.b ?? previousTransaction?.branch,
955
+ branch: meta.b ?? previousTransaction?.txID.branch,
931
956
  };
932
957
  } else {
933
958
  logger.error("Merge commit without session ID", {
934
959
  txID: transaction.txID,
935
- prev: previousTransaction ?? null,
960
+ prevTxID: previousTransaction?.txID ?? null,
936
961
  });
937
962
  }
938
963
  }
@@ -953,11 +978,10 @@ export class CoValueCore {
953
978
  this.determineValidTransactions();
954
979
 
955
980
  for (const transaction of this.verifiedTransactions) {
956
- decodeTransactionChangesAndMeta(
957
- this,
958
- transaction,
959
- ignorePrivateTransactions,
960
- );
981
+ if (!ignorePrivateTransactions) {
982
+ decryptTransactionChangesAndMeta(this, transaction);
983
+ }
984
+
961
985
  this.parseMetaInformation(transaction);
962
986
  }
963
987
  }
@@ -988,7 +1012,7 @@ export class CoValueCore {
988
1012
  const source = getBranchSource(this);
989
1013
 
990
1014
  for (const transaction of this.verifiedTransactions) {
991
- if (!isValidTransactionWithChanges(transaction)) {
1015
+ if (!transaction.isValidTransactionWithChanges()) {
992
1016
  continue;
993
1017
  }
994
1018
 
@@ -998,7 +1022,8 @@ export class CoValueCore {
998
1022
 
999
1023
  options?.knownTransactions?.add(transaction.tx);
1000
1024
 
1001
- const { txID } = transaction;
1025
+ // Using the currentTxID to filter the transactions, because the TxID is modified by the merge meta
1026
+ const txID = transaction.currentTxID;
1002
1027
 
1003
1028
  const from = options?.from?.[txID.sessionID] ?? -1;
1004
1029
 
@@ -1122,8 +1147,8 @@ export class CoValueCore {
1122
1147
  }
1123
1148
 
1124
1149
  compareTransactions(
1125
- a: Pick<DecryptedTransaction, "madeAt" | "txID">,
1126
- b: Pick<DecryptedTransaction, "madeAt" | "txID">,
1150
+ a: Pick<VerifiedTransaction, "madeAt" | "txID">,
1151
+ b: Pick<VerifiedTransaction, "madeAt" | "txID">,
1127
1152
  ) {
1128
1153
  if (a.madeAt !== b.madeAt) {
1129
1154
  return a.madeAt - b.madeAt;
@@ -1196,7 +1221,7 @@ export class CoValueCore {
1196
1221
  }
1197
1222
  }
1198
1223
 
1199
- getGroup(): RawGroup {
1224
+ safeGetGroup(): RawGroup | undefined {
1200
1225
  if (!this.verified) {
1201
1226
  throw new Error(
1202
1227
  "CoValueCore: getGroup called on coValue without verified state",
@@ -1204,7 +1229,7 @@ export class CoValueCore {
1204
1229
  }
1205
1230
 
1206
1231
  if (this.verified.header.ruleset.type !== "ownedByGroup") {
1207
- throw new Error("Only values owned by groups have groups");
1232
+ return undefined;
1208
1233
  }
1209
1234
 
1210
1235
  return expectGroup(
@@ -1214,6 +1239,16 @@ export class CoValueCore {
1214
1239
  );
1215
1240
  }
1216
1241
 
1242
+ getGroup(): RawGroup {
1243
+ const group = this.safeGetGroup();
1244
+
1245
+ if (!group) {
1246
+ throw new Error("Only values owned by groups have groups");
1247
+ }
1248
+
1249
+ return group;
1250
+ }
1251
+
1217
1252
  getTx(txID: TransactionID): Transaction | undefined {
1218
1253
  return this.verified?.sessions.get(txID.sessionID)?.transactions[
1219
1254
  txID.txIndex
@@ -1245,10 +1280,34 @@ export class CoValueCore {
1245
1280
  return;
1246
1281
  }
1247
1282
 
1248
- const currentState = this.peers.get("storage");
1283
+ const currentState = this.getLoadingStateForPeer("storage");
1284
+
1285
+ if (currentState === "pending") {
1286
+ if (!done) {
1287
+ // We don't need to notify the result to anyone, so we can return early
1288
+ return;
1289
+ }
1290
+
1291
+ // Loading the value
1292
+ this.subscribe((state, unsubscribe) => {
1293
+ const updatedState = state.getLoadingStateForPeer("storage");
1249
1294
 
1250
- if (currentState && currentState.type !== "unknown") {
1251
- done?.(currentState.type === "available");
1295
+ if (updatedState === "available" || state.isAvailable()) {
1296
+ unsubscribe();
1297
+ done(true);
1298
+ } else if (
1299
+ updatedState === "errored" ||
1300
+ updatedState === "unavailable"
1301
+ ) {
1302
+ unsubscribe();
1303
+ done(false);
1304
+ }
1305
+ });
1306
+ return;
1307
+ }
1308
+
1309
+ if (currentState !== "unknown") {
1310
+ done?.(currentState === "available");
1252
1311
  return;
1253
1312
  }
1254
1313
 
@@ -1274,7 +1333,7 @@ export class CoValueCore {
1274
1333
  }
1275
1334
 
1276
1335
  for (const peer of peers) {
1277
- const currentState = this.peers.get(peer.id)?.type ?? "unknown";
1336
+ const currentState = this.getLoadingStateForPeer(peer.id);
1278
1337
 
1279
1338
  if (currentState === "unknown" || currentState === "unavailable") {
1280
1339
  this.markPending(peer.id);
@@ -1301,40 +1360,34 @@ export class CoValueCore {
1301
1360
  peer.trackLoadRequestSent(this.id);
1302
1361
  }
1303
1362
 
1304
- return new Promise<void>((resolve) => {
1305
- const markNotFound = () => {
1306
- if (this.peers.get(peer.id)?.type === "pending") {
1307
- logger.warn("Timeout waiting for peer to load coValue", {
1308
- id: this.id,
1309
- peerID: peer.id,
1310
- });
1311
- this.markNotFoundInPeer(peer.id);
1312
- }
1313
- };
1314
-
1315
- const timeout = setTimeout(markNotFound, CO_VALUE_LOADING_CONFIG.TIMEOUT);
1316
- const removeCloseListener = peer.persistent
1317
- ? undefined
1318
- : peer.addCloseListener(markNotFound);
1319
-
1320
- const listener = (state: CoValueCore) => {
1321
- const peerState = state.peers.get(peer.id);
1322
- if (
1323
- state.isAvailable() || // might have become available from another peer e.g. through handleNewContent
1324
- peerState?.type === "available" ||
1325
- peerState?.type === "errored" ||
1326
- peerState?.type === "unavailable"
1327
- ) {
1328
- this.listeners.delete(listener);
1329
- removeCloseListener?.();
1330
- clearTimeout(timeout);
1331
- resolve();
1332
- }
1333
- };
1363
+ const markNotFound = () => {
1364
+ if (this.getLoadingStateForPeer(peer.id) === "pending") {
1365
+ logger.warn("Timeout waiting for peer to load coValue", {
1366
+ id: this.id,
1367
+ peerID: peer.id,
1368
+ });
1369
+ this.markNotFoundInPeer(peer.id);
1370
+ }
1371
+ };
1334
1372
 
1335
- this.listeners.add(listener);
1336
- listener(this);
1337
- });
1373
+ const timeout = setTimeout(markNotFound, CO_VALUE_LOADING_CONFIG.TIMEOUT);
1374
+ const removeCloseListener = peer.persistent
1375
+ ? undefined
1376
+ : peer.addCloseListener(markNotFound);
1377
+
1378
+ this.subscribe((state, unsubscribe) => {
1379
+ const peerState = state.getLoadingStateForPeer(peer.id);
1380
+ if (
1381
+ state.isAvailable() || // might have become available from another peer e.g. through handleNewContent
1382
+ peerState === "available" ||
1383
+ peerState === "errored" ||
1384
+ peerState === "unavailable"
1385
+ ) {
1386
+ unsubscribe();
1387
+ removeCloseListener?.();
1388
+ clearTimeout(timeout);
1389
+ }
1390
+ }, true);
1338
1391
  }
1339
1392
  }
1340
1393
 
@@ -1370,9 +1423,3 @@ export type TryAddTransactionsError =
1370
1423
  | ResolveAccountAgentError
1371
1424
  | InvalidHashError
1372
1425
  | InvalidSignatureError;
1373
-
1374
- function isValidTransactionWithChanges(
1375
- transaction: VerifiedTransaction,
1376
- ): transaction is VerifiedTransaction & { changes: JsonValue[] } {
1377
- return Boolean(transaction.isValid && transaction.changes);
1378
- }
@@ -0,0 +1,56 @@
1
+ import { AvailableCoValueCore, VerifiedTransaction } from "./coValueCore.js";
2
+
3
+ export function decryptTransactionChangesAndMeta(
4
+ coValue: AvailableCoValueCore,
5
+ transaction: VerifiedTransaction,
6
+ ) {
7
+ if (
8
+ !transaction.isValid ||
9
+ transaction.isDecrypted ||
10
+ transaction.tx.privacy === "trusting" // Trusting transactions are already decrypted
11
+ ) {
12
+ return;
13
+ }
14
+
15
+ const needsChagesParsing = !transaction.changes;
16
+ const needsMetaParsing = !transaction.meta && transaction.tx.meta;
17
+
18
+ if (!needsChagesParsing && !needsMetaParsing) {
19
+ return;
20
+ }
21
+
22
+ const readKey = coValue.getReadKey(transaction.tx.keyUsed);
23
+
24
+ if (!readKey) {
25
+ return;
26
+ }
27
+
28
+ if (needsChagesParsing) {
29
+ const changes = coValue.verified.decryptTransaction(
30
+ transaction.txID.sessionID,
31
+ transaction.txID.txIndex,
32
+ readKey,
33
+ );
34
+
35
+ if (changes) {
36
+ transaction.changes = changes;
37
+ }
38
+ }
39
+
40
+ if (needsMetaParsing) {
41
+ const meta = coValue.verified.decryptTransactionMeta(
42
+ transaction.txID.sessionID,
43
+ transaction.txID.txIndex,
44
+ readKey,
45
+ );
46
+
47
+ if (meta) {
48
+ transaction.meta = meta;
49
+ }
50
+ }
51
+
52
+ // We mark the transaction as decrypted even if the changes or meta have failed to be decrypted
53
+ // This is because, if we successfully extracted the readKey and the decrypt failed once it will always fail
54
+ // so better to log the error (we already do that) and mark the transaction as decrypted
55
+ transaction.isDecrypted = true;
56
+ }