cojson 0.18.27 → 0.18.29

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 (60) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +17 -0
  3. package/dist/coValueCore/branching.d.ts +2 -1
  4. package/dist/coValueCore/branching.d.ts.map +1 -1
  5. package/dist/coValueCore/branching.js +20 -2
  6. package/dist/coValueCore/branching.js.map +1 -1
  7. package/dist/coValueCore/coValueCore.d.ts +26 -23
  8. package/dist/coValueCore/coValueCore.d.ts.map +1 -1
  9. package/dist/coValueCore/coValueCore.js +161 -123
  10. package/dist/coValueCore/coValueCore.js.map +1 -1
  11. package/dist/coValueCore/decryptTransactionChangesAndMeta.d.ts +3 -0
  12. package/dist/coValueCore/decryptTransactionChangesAndMeta.d.ts.map +1 -0
  13. package/dist/coValueCore/decryptTransactionChangesAndMeta.js +34 -0
  14. package/dist/coValueCore/decryptTransactionChangesAndMeta.js.map +1 -0
  15. package/dist/localNode.js +1 -1
  16. package/dist/localNode.js.map +1 -1
  17. package/dist/permissions.d.ts.map +1 -1
  18. package/dist/permissions.js +18 -20
  19. package/dist/permissions.js.map +1 -1
  20. package/dist/sync.js +2 -2
  21. package/dist/sync.js.map +1 -1
  22. package/dist/tests/branching.test.js +237 -28
  23. package/dist/tests/branching.test.js.map +1 -1
  24. package/dist/tests/coValueCore.loadFromStorage.test.d.ts +2 -0
  25. package/dist/tests/coValueCore.loadFromStorage.test.d.ts.map +1 -0
  26. package/dist/tests/coValueCore.loadFromStorage.test.js +395 -0
  27. package/dist/tests/coValueCore.loadFromStorage.test.js.map +1 -0
  28. package/dist/tests/coValueCore.loadingState.test.d.ts +2 -0
  29. package/dist/tests/coValueCore.loadingState.test.d.ts.map +1 -0
  30. package/dist/tests/{coValueCoreLoadingState.test.js → coValueCore.loadingState.test.js} +4 -12
  31. package/dist/tests/coValueCore.loadingState.test.js.map +1 -0
  32. package/dist/tests/coValueCore.test.js +2 -5
  33. package/dist/tests/coValueCore.test.js.map +1 -1
  34. package/dist/tests/sync.mesh.test.js +4 -34
  35. package/dist/tests/sync.mesh.test.js.map +1 -1
  36. package/dist/tests/testUtils.d.ts +7 -1
  37. package/dist/tests/testUtils.d.ts.map +1 -1
  38. package/dist/tests/testUtils.js +12 -0
  39. package/dist/tests/testUtils.js.map +1 -1
  40. package/package.json +3 -3
  41. package/src/coValueCore/branching.ts +28 -3
  42. package/src/coValueCore/coValueCore.ts +215 -167
  43. package/src/coValueCore/decryptTransactionChangesAndMeta.ts +56 -0
  44. package/src/localNode.ts +1 -1
  45. package/src/permissions.ts +21 -19
  46. package/src/sync.ts +2 -2
  47. package/src/tests/branching.test.ts +392 -45
  48. package/src/tests/coValueCore.loadFromStorage.test.ts +540 -0
  49. package/src/tests/{coValueCoreLoadingState.test.ts → coValueCore.loadingState.test.ts} +3 -16
  50. package/src/tests/coValueCore.test.ts +2 -5
  51. package/src/tests/sync.mesh.test.ts +11 -38
  52. package/src/tests/testUtils.ts +21 -0
  53. package/dist/coValueCore/decodeTransactionChangesAndMeta.d.ts +0 -3
  54. package/dist/coValueCore/decodeTransactionChangesAndMeta.d.ts.map +0 -1
  55. package/dist/coValueCore/decodeTransactionChangesAndMeta.js +0 -59
  56. package/dist/coValueCore/decodeTransactionChangesAndMeta.js.map +0 -1
  57. package/dist/tests/coValueCoreLoadingState.test.d.ts +0 -2
  58. package/dist/tests/coValueCoreLoadingState.test.d.ts.map +0 -1
  59. package/dist/tests/coValueCoreLoadingState.test.js.map +0 -1
  60. package/src/coValueCore/decodeTransactionChangesAndMeta.ts +0 -81
@@ -42,13 +42,14 @@ import {
42
42
  BranchStartCommit,
43
43
  } from "./branching.js";
44
44
  import { type RawAccountID } from "../coValues/account.js";
45
- import { decodeTransactionChangesAndMeta } from "./decodeTransactionChangesAndMeta.js";
45
+ import { decryptTransactionChangesAndMeta } from "./decryptTransactionChangesAndMeta.js";
46
46
  import {
47
47
  combineKnownStateSessions,
48
48
  CoValueKnownState,
49
49
  emptyKnownState,
50
50
  KnownStateSessions,
51
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
  }
@@ -408,14 +437,14 @@ export class CoValueCore {
408
437
 
409
438
  markErrored(peerId: PeerID, error: TryAddTransactionsError) {
410
439
  const previousState = this.loadingState;
411
- this.peers.set(peerId, { type: "errored", error });
440
+ this.loadingStatuses.set(peerId, { type: "errored", error });
412
441
  this.updateCounter(previousState);
413
442
  this.scheduleNotifyUpdate();
414
443
  }
415
444
 
416
445
  markPending(peerId: PeerID) {
417
446
  const previousState = this.loadingState;
418
- this.peers.set(peerId, { type: "pending" });
447
+ this.loadingStatuses.set(peerId, { type: "pending" });
419
448
  this.updateCounter(previousState);
420
449
  this.scheduleNotifyUpdate();
421
450
  }
@@ -814,36 +843,19 @@ export class CoValueCore {
814
843
  continue;
815
844
  }
816
845
 
817
- const txID = isBranched
818
- ? {
819
- sessionID,
820
- txIndex,
821
- branch: this.id,
822
- }
823
- : {
824
- sessionID,
825
- txIndex,
826
- };
827
-
828
846
  const cache = this.parsingCache.get(tx);
829
847
  if (cache) {
830
848
  this.parsingCache.delete(tx);
831
849
  }
832
850
 
833
- const verifiedTransaction = {
834
- author: accountOrAgentIDfromSessionID(sessionID),
835
- txID,
836
- madeAt: tx.madeAt,
837
- isValidated: false,
838
- isValid: false,
839
- changes: cache?.changes,
840
- meta: cache?.meta,
841
- hasInvalidChanges: false,
842
- hasInvalidMeta: false,
843
- hasMetaBeenParsed: false,
851
+ const verifiedTransaction = new VerifiedTransaction(
852
+ sessionID,
853
+ txIndex,
844
854
  tx,
845
- previous: this.lastVerifiedTransactionBySessionID[sessionID],
846
- };
855
+ isBranched ? this.id : undefined,
856
+ cache,
857
+ this.lastVerifiedTransactionBySessionID[sessionID],
858
+ );
847
859
 
848
860
  if (verifiedTransaction.madeAt > this.latestTxMadeAt) {
849
861
  this.latestTxMadeAt = verifiedTransaction.madeAt;
@@ -919,19 +931,33 @@ export class CoValueCore {
919
931
  const meta = transaction.meta as MergedTransactionMetadata;
920
932
 
921
933
  // Check if the transaction is a merge commit
922
- const previousTransaction = transaction.previous?.txID;
923
- 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
+ }
924
950
 
925
951
  if (sessionID) {
926
- transaction.txID = {
952
+ transaction.sourceTxID = {
927
953
  sessionID,
928
954
  txIndex: meta.mi,
929
- branch: meta.b ?? previousTransaction?.branch,
955
+ branch: meta.b ?? previousTransaction?.txID.branch,
930
956
  };
931
957
  } else {
932
958
  logger.error("Merge commit without session ID", {
933
959
  txID: transaction.txID,
934
- prev: previousTransaction ?? null,
960
+ prevTxID: previousTransaction?.txID ?? null,
935
961
  });
936
962
  }
937
963
  }
@@ -952,11 +978,10 @@ export class CoValueCore {
952
978
  this.determineValidTransactions();
953
979
 
954
980
  for (const transaction of this.verifiedTransactions) {
955
- decodeTransactionChangesAndMeta(
956
- this,
957
- transaction,
958
- ignorePrivateTransactions,
959
- );
981
+ if (!ignorePrivateTransactions) {
982
+ decryptTransactionChangesAndMeta(this, transaction);
983
+ }
984
+
960
985
  this.parseMetaInformation(transaction);
961
986
  }
962
987
  }
@@ -987,7 +1012,7 @@ export class CoValueCore {
987
1012
  const source = getBranchSource(this);
988
1013
 
989
1014
  for (const transaction of this.verifiedTransactions) {
990
- if (!isValidTransactionWithChanges(transaction)) {
1015
+ if (!transaction.isValidTransactionWithChanges()) {
991
1016
  continue;
992
1017
  }
993
1018
 
@@ -997,7 +1022,8 @@ export class CoValueCore {
997
1022
 
998
1023
  options?.knownTransactions?.add(transaction.tx);
999
1024
 
1000
- 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;
1001
1027
 
1002
1028
  const from = options?.from?.[txID.sessionID] ?? -1;
1003
1029
 
@@ -1121,8 +1147,8 @@ export class CoValueCore {
1121
1147
  }
1122
1148
 
1123
1149
  compareTransactions(
1124
- a: Pick<DecryptedTransaction, "madeAt" | "txID">,
1125
- b: Pick<DecryptedTransaction, "madeAt" | "txID">,
1150
+ a: Pick<VerifiedTransaction, "madeAt" | "txID">,
1151
+ b: Pick<VerifiedTransaction, "madeAt" | "txID">,
1126
1152
  ) {
1127
1153
  if (a.madeAt !== b.madeAt) {
1128
1154
  return a.madeAt - b.madeAt;
@@ -1195,7 +1221,7 @@ export class CoValueCore {
1195
1221
  }
1196
1222
  }
1197
1223
 
1198
- getGroup(): RawGroup {
1224
+ safeGetGroup(): RawGroup | undefined {
1199
1225
  if (!this.verified) {
1200
1226
  throw new Error(
1201
1227
  "CoValueCore: getGroup called on coValue without verified state",
@@ -1203,7 +1229,7 @@ export class CoValueCore {
1203
1229
  }
1204
1230
 
1205
1231
  if (this.verified.header.ruleset.type !== "ownedByGroup") {
1206
- throw new Error("Only values owned by groups have groups");
1232
+ return undefined;
1207
1233
  }
1208
1234
 
1209
1235
  return expectGroup(
@@ -1213,6 +1239,16 @@ export class CoValueCore {
1213
1239
  );
1214
1240
  }
1215
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
+
1216
1252
  getTx(txID: TransactionID): Transaction | undefined {
1217
1253
  return this.verified?.sessions.get(txID.sessionID)?.transactions[
1218
1254
  txID.txIndex
@@ -1244,10 +1280,34 @@ export class CoValueCore {
1244
1280
  return;
1245
1281
  }
1246
1282
 
1247
- const currentState = this.peers.get("storage");
1283
+ const currentState = this.getLoadingStateForPeer("storage");
1248
1284
 
1249
- if (currentState && currentState.type !== "unknown") {
1250
- done?.(currentState.type === "available");
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");
1294
+
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");
1251
1311
  return;
1252
1312
  }
1253
1313
 
@@ -1273,7 +1333,7 @@ export class CoValueCore {
1273
1333
  }
1274
1334
 
1275
1335
  for (const peer of peers) {
1276
- const currentState = this.peers.get(peer.id)?.type ?? "unknown";
1336
+ const currentState = this.getLoadingStateForPeer(peer.id);
1277
1337
 
1278
1338
  if (currentState === "unknown" || currentState === "unavailable") {
1279
1339
  this.markPending(peer.id);
@@ -1300,40 +1360,34 @@ export class CoValueCore {
1300
1360
  peer.trackLoadRequestSent(this.id);
1301
1361
  }
1302
1362
 
1303
- return new Promise<void>((resolve) => {
1304
- const markNotFound = () => {
1305
- if (this.peers.get(peer.id)?.type === "pending") {
1306
- logger.warn("Timeout waiting for peer to load coValue", {
1307
- id: this.id,
1308
- peerID: peer.id,
1309
- });
1310
- this.markNotFoundInPeer(peer.id);
1311
- }
1312
- };
1313
-
1314
- const timeout = setTimeout(markNotFound, CO_VALUE_LOADING_CONFIG.TIMEOUT);
1315
- const removeCloseListener = peer.persistent
1316
- ? undefined
1317
- : peer.addCloseListener(markNotFound);
1318
-
1319
- const listener = (state: CoValueCore) => {
1320
- const peerState = state.peers.get(peer.id);
1321
- if (
1322
- state.isAvailable() || // might have become available from another peer e.g. through handleNewContent
1323
- peerState?.type === "available" ||
1324
- peerState?.type === "errored" ||
1325
- peerState?.type === "unavailable"
1326
- ) {
1327
- this.listeners.delete(listener);
1328
- removeCloseListener?.();
1329
- clearTimeout(timeout);
1330
- resolve();
1331
- }
1332
- };
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
+ };
1333
1372
 
1334
- this.listeners.add(listener);
1335
- listener(this);
1336
- });
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);
1337
1391
  }
1338
1392
  }
1339
1393
 
@@ -1369,9 +1423,3 @@ export type TryAddTransactionsError =
1369
1423
  | ResolveAccountAgentError
1370
1424
  | InvalidHashError
1371
1425
  | InvalidSignatureError;
1372
-
1373
- function isValidTransactionWithChanges(
1374
- transaction: VerifiedTransaction,
1375
- ): transaction is VerifiedTransaction & { changes: JsonValue[] } {
1376
- return Boolean(transaction.isValid && transaction.changes);
1377
- }
@@ -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
+ }
package/src/localNode.ts CHANGED
@@ -116,7 +116,7 @@ export class LocalNode {
116
116
  let entry = this.coValues.get(id);
117
117
 
118
118
  if (!entry) {
119
- entry = CoValueCore.fromID(id, this);
119
+ entry = new CoValueCore(id, this);
120
120
  this.coValues.set(id, entry);
121
121
  }
122
122