cojson 0.4.8 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -8,7 +8,6 @@ import { Group } from "./group.js";
8
8
  import { AgentID, SessionID, TransactionID } from "../ids.js";
9
9
  import { base64URLtoBytes, bytesToBase64url } from "../base64url.js";
10
10
  import { AccountID } from "./account.js";
11
- import { parseJSON } from "../jsonStringify.js";
12
11
 
13
12
  export type BinaryStreamInfo = {
14
13
  mimeType: string;
@@ -84,7 +83,7 @@ export class CoStreamView<
84
83
  madeAt,
85
84
  changes,
86
85
  } of this.core.getValidSortedTransactions()) {
87
- for (const changeUntyped of parseJSON(changes)) {
86
+ for (const changeUntyped of changes) {
88
87
  const change = changeUntyped as Item;
89
88
  let entries = this.items[txID.sessionID];
90
89
  if (!entries) {
@@ -294,7 +293,7 @@ export class BinaryCoStreamView<
294
293
  implements CoValue
295
294
  {
296
295
  getBinaryChunks(
297
- allowUnfinished?: boolean
296
+ allowUnfinished?: boolean,
298
297
  ):
299
298
  | (BinaryStreamInfo & { chunks: Uint8Array[]; finished: boolean })
300
299
  | undefined {
@@ -319,6 +318,8 @@ export class BinaryCoStreamView<
319
318
  let finished = false;
320
319
  // let totalLength = 0;
321
320
 
321
+ let lastProgressUpdate = Date.now();
322
+
322
323
  for (const item of items.slice(1)) {
323
324
  if (item.type === "end") {
324
325
  finished = true;
@@ -335,6 +336,10 @@ export class BinaryCoStreamView<
335
336
  );
336
337
  // totalLength += chunk.length;
337
338
  chunks.push(chunk);
339
+
340
+ if (Date.now() - lastProgressUpdate > 100) {
341
+ lastProgressUpdate = Date.now();
342
+ }
338
343
  }
339
344
 
340
345
  // const after = performance.now();
@@ -362,36 +367,55 @@ export class BinaryCoStream<
362
367
  /** @internal */
363
368
  push(
364
369
  item: BinaryStreamItem,
365
- privacy: "private" | "trusting" = "private"
366
- ): this {
370
+ privacy?: "private" | "trusting",
371
+ ): this
372
+ push(
373
+ item: BinaryStreamItem,
374
+ privacy: "private" | "trusting",
375
+ returnNewStream: true
376
+ ): this
377
+ push(
378
+ item: BinaryStreamItem,
379
+ privacy: "private" | "trusting",
380
+ returnNewStream: false
381
+ ): void
382
+ push(
383
+ item: BinaryStreamItem,
384
+ privacy: "private" | "trusting" = "private",
385
+ returnNewStream: boolean = true
386
+ ): this | void {
367
387
  this.core.makeTransaction([item], privacy);
368
- return new BinaryCoStream(this.core) as this;
388
+ if (returnNewStream) {
389
+ return new BinaryCoStream(this.core) as this;
390
+ }
369
391
  }
370
392
 
371
393
  startBinaryStream(
372
394
  settings: BinaryStreamInfo,
373
395
  privacy: "private" | "trusting" = "private"
374
- ): this {
396
+ ): void {
375
397
  return this.push(
376
398
  {
377
399
  type: "start",
378
400
  ...settings,
379
401
  } satisfies BinaryStreamStart,
380
- privacy
402
+ privacy,
403
+ false
381
404
  );
382
405
  }
383
406
 
384
407
  pushBinaryStreamChunk(
385
408
  chunk: Uint8Array,
386
409
  privacy: "private" | "trusting" = "private"
387
- ): this {
410
+ ): void {
388
411
  // const before = performance.now();
389
412
  return this.push(
390
413
  {
391
414
  type: "chunk",
392
415
  chunk: `binary_U${bytesToBase64url(chunk)}`,
393
416
  } satisfies BinaryStreamChunk,
394
- privacy
417
+ privacy,
418
+ false
395
419
  );
396
420
  // const after = performance.now();
397
421
  // console.log(
@@ -405,7 +429,8 @@ export class BinaryCoStream<
405
429
  {
406
430
  type: "end",
407
431
  } satisfies BinaryStreamEnd,
408
- privacy
432
+ privacy,
433
+ true
409
434
  );
410
435
  }
411
436
 
package/src/crypto.ts CHANGED
@@ -1,4 +1,12 @@
1
- import { ed25519, x25519 } from "@noble/curves/ed25519";
1
+ import {
2
+ initBundledOnce,
3
+ Ed25519SigningKey,
4
+ Ed25519VerifyingKey,
5
+ X25519StaticSecret,
6
+ Memory,
7
+ Ed25519Signature,
8
+ X25519PublicKey,
9
+ } from "@hazae41/berith";
2
10
  import { xsalsa20_poly1305, xsalsa20 } from "@noble/ciphers/salsa";
3
11
  import { JsonValue } from "./jsonValue.js";
4
12
  import { base58 } from "@scure/base";
@@ -10,7 +18,13 @@ import { createBLAKE3 } from "hash-wasm";
10
18
  import { Stringified, parseJSON, stableStringify } from "./jsonStringify.js";
11
19
 
12
20
  let blake3Instance: Awaited<ReturnType<typeof createBLAKE3>>;
13
- let blake3HashOnce: (data: Uint8Array) => Uint8Array;
21
+ let blake3HashOnce: (data: Uint8Array) => Uint8Array = () => {
22
+ throw new Error(
23
+ "cojson WASM dependencies not yet loaded; Make sure to import `cojsonReady` from `cojson` and await it before using any cojson functionality:\n\n" +
24
+ 'import { cojsonReady } from "cojson";\n' +
25
+ "await cojsonReady;\n\n"
26
+ );
27
+ };
14
28
  let blake3HashOnceWithContext: (
15
29
  data: Uint8Array,
16
30
  { context }: { context: Uint8Array }
@@ -21,29 +35,36 @@ let blake3incrementalUpdateSLOW_WITH_DEVTOOLS: (
21
35
  ) => Uint8Array;
22
36
  let blake3digestForState: (state: Uint8Array) => Uint8Array;
23
37
 
24
- export const cryptoReady = new Promise<void>((resolve) => {
25
- createBLAKE3()
26
- .then((bl3) => {
27
- blake3Instance = bl3;
28
- blake3HashOnce = (data) => {
29
- return bl3.init().update(data).digest("binary");
30
- };
31
- blake3HashOnceWithContext = (data, { context }) => {
32
- return bl3.init().update(context).update(data).digest("binary");
33
- };
34
- blake3incrementalUpdateSLOW_WITH_DEVTOOLS = (state, data) => {
35
- bl3.load(state).update(data);
36
- return bl3.save();
37
- };
38
- blake3digestForState = (state) => {
39
- return bl3.load(state).digest("binary");
40
- };
41
- resolve();
42
- })
43
- .catch((e) =>
44
- console.error("Failed to load cryptography dependencies", e)
45
- );
46
- });
38
+ export const cryptoReady = Promise.all([
39
+ new Promise<void>((resolve) => {
40
+ createBLAKE3()
41
+ .then((bl3) => {
42
+ blake3Instance = bl3;
43
+ blake3HashOnce = (data) => {
44
+ return bl3.init().update(data).digest("binary");
45
+ };
46
+ blake3HashOnceWithContext = (data, { context }) => {
47
+ return bl3
48
+ .init()
49
+ .update(context)
50
+ .update(data)
51
+ .digest("binary");
52
+ };
53
+ blake3incrementalUpdateSLOW_WITH_DEVTOOLS = (state, data) => {
54
+ bl3.load(state).update(data);
55
+ return bl3.save();
56
+ };
57
+ blake3digestForState = (state) => {
58
+ return bl3.load(state).digest("binary");
59
+ };
60
+ resolve();
61
+ })
62
+ .catch((e) =>
63
+ console.error("Failed to load cryptography dependencies", e)
64
+ );
65
+ }),
66
+ initBundledOnce(),
67
+ ]);
47
68
 
48
69
  export type SignerSecret = `signerSecret_z${string}`;
49
70
  export type SignerID = `signer_z${string}`;
@@ -59,7 +80,9 @@ const textEncoder = new TextEncoder();
59
80
  const textDecoder = new TextDecoder();
60
81
 
61
82
  export function newRandomSigner(): SignerSecret {
62
- return `signerSecret_z${base58.encode(ed25519.utils.randomPrivateKey())}`;
83
+ return `signerSecret_z${base58.encode(
84
+ new Ed25519SigningKey().to_bytes().copyAndDispose()
85
+ )}`;
63
86
  }
64
87
 
65
88
  export function signerSecretToBytes(secret: SignerSecret): Uint8Array {
@@ -72,17 +95,22 @@ export function signerSecretFromBytes(bytes: Uint8Array): SignerSecret {
72
95
 
73
96
  export function getSignerID(secret: SignerSecret): SignerID {
74
97
  return `signer_z${base58.encode(
75
- ed25519.getPublicKey(
76
- base58.decode(secret.substring("signerSecret_z".length))
98
+ Ed25519SigningKey.from_bytes(
99
+ new Memory(base58.decode(secret.substring("signerSecret_z".length)))
77
100
  )
101
+ .public()
102
+ .to_bytes()
103
+ .copyAndDispose()
78
104
  )}`;
79
105
  }
80
106
 
81
107
  export function sign(secret: SignerSecret, message: JsonValue): Signature {
82
- const signature = ed25519.sign(
83
- textEncoder.encode(stableStringify(message)),
84
- base58.decode(secret.substring("signerSecret_z".length))
85
- );
108
+ const signature = Ed25519SigningKey.from_bytes(
109
+ new Memory(base58.decode(secret.substring("signerSecret_z".length)))
110
+ )
111
+ .sign(new Memory(textEncoder.encode(stableStringify(message))))
112
+ .to_bytes()
113
+ .copyAndDispose();
86
114
  return `signature_z${base58.encode(signature)}`;
87
115
  }
88
116
 
@@ -91,15 +119,20 @@ export function verify(
91
119
  message: JsonValue,
92
120
  id: SignerID
93
121
  ): boolean {
94
- return ed25519.verify(
95
- base58.decode(signature.substring("signature_z".length)),
96
- textEncoder.encode(stableStringify(message)),
97
- base58.decode(id.substring("signer_z".length))
122
+ return new Ed25519VerifyingKey(
123
+ new Memory(base58.decode(id.substring("signer_z".length)))
124
+ ).verify(
125
+ new Memory(textEncoder.encode(stableStringify(message))),
126
+ new Ed25519Signature(
127
+ new Memory(base58.decode(signature.substring("signature_z".length)))
128
+ )
98
129
  );
99
130
  }
100
131
 
101
132
  export function newRandomSealer(): SealerSecret {
102
- return `sealerSecret_z${base58.encode(x25519.utils.randomPrivateKey())}`;
133
+ return `sealerSecret_z${base58.encode(
134
+ new X25519StaticSecret().to_bytes().copyAndDispose()
135
+ )}`;
103
136
  }
104
137
 
105
138
  export function sealerSecretToBytes(secret: SealerSecret): Uint8Array {
@@ -112,9 +145,12 @@ export function sealerSecretFromBytes(bytes: Uint8Array): SealerSecret {
112
145
 
113
146
  export function getSealerID(secret: SealerSecret): SealerID {
114
147
  return `sealer_z${base58.encode(
115
- x25519.getPublicKey(
116
- base58.decode(secret.substring("sealerSecret_z".length))
148
+ X25519StaticSecret.from_bytes(
149
+ new Memory(base58.decode(secret.substring("sealerSecret_z".length)))
117
150
  )
151
+ .to_public()
152
+ .to_bytes()
153
+ .copyAndDispose()
118
154
  )}`;
119
155
  }
120
156
 
@@ -180,7 +216,10 @@ export function seal<T extends JsonValue>({
180
216
 
181
217
  const plaintext = textEncoder.encode(stableStringify(message));
182
218
 
183
- const sharedSecret = x25519.getSharedSecret(senderPriv, sealerPub);
219
+ const sharedSecret = X25519StaticSecret.from_bytes(new Memory(senderPriv))
220
+ .diffie_hellman(X25519PublicKey.from_bytes(new Memory(sealerPub)))
221
+ .to_bytes()
222
+ .copyAndDispose();
184
223
 
185
224
  const sealedBytes = xsalsa20_poly1305(sharedSecret, nOnce).encrypt(
186
225
  plaintext
@@ -205,7 +244,10 @@ export function unseal<T extends JsonValue>(
205
244
 
206
245
  const sealedBytes = base64URLtoBytes(sealed.substring("sealed_U".length));
207
246
 
208
- const sharedSecret = x25519.getSharedSecret(sealerPriv, senderPub);
247
+ const sharedSecret = X25519StaticSecret.from_bytes(new Memory(sealerPriv))
248
+ .diffie_hellman(X25519PublicKey.from_bytes(new Memory(senderPub)))
249
+ .to_bytes()
250
+ .copyAndDispose();
209
251
 
210
252
  const plaintext = xsalsa20_poly1305(sharedSecret, nOnce).decrypt(
211
253
  sealedBytes
package/src/localNode.ts CHANGED
@@ -19,7 +19,7 @@ import {
19
19
  Group,
20
20
  secretSeedFromInviteSecret,
21
21
  } from "./coValues/group.js";
22
- import { Peer, SyncManager } from "./sync.js";
22
+ import { Peer, PeerID, SyncManager } from "./sync.js";
23
23
  import { AgentID, RawCoID, SessionID, isAgentID } from "./ids.js";
24
24
  import { CoID } from "./coValue.js";
25
25
  import {
@@ -153,6 +153,11 @@ export class LocalNode {
153
153
  }
154
154
 
155
155
  const account = await accountPromise;
156
+
157
+ if (account === "unavailable") {
158
+ throw new Error("Account unavailable from all peers");
159
+ }
160
+
156
161
  const controlledAccount = new ControlledAccount(
157
162
  account.core,
158
163
  accountSecret
@@ -199,14 +204,36 @@ export class LocalNode {
199
204
  }
200
205
 
201
206
  /** @internal */
202
- loadCoValue(id: RawCoID): Promise<CoValueCore> {
207
+ async loadCoValueCore(
208
+ id: RawCoID,
209
+ options: {
210
+ dontLoadFrom?: PeerID;
211
+ dontWaitFor?: PeerID;
212
+ onProgress?: (progress: number) => void;
213
+ } = {}
214
+ ): Promise<CoValueCore | "unavailable"> {
203
215
  let entry = this.coValues[id];
204
216
  if (!entry) {
205
- entry = newLoadingState();
217
+ const peersToWaitFor = new Set(
218
+ Object.values(this.syncManager.peers)
219
+ .filter((peer) => peer.role === "server")
220
+ .map((peer) => peer.id)
221
+ );
222
+ if (options.dontWaitFor) peersToWaitFor.delete(options.dontWaitFor);
223
+ entry = newLoadingState(peersToWaitFor, options.onProgress);
206
224
 
207
225
  this.coValues[id] = entry;
208
226
 
209
- this.syncManager.loadFromPeers(id);
227
+ this.syncManager
228
+ .loadFromPeers(id, options.dontLoadFrom)
229
+ .catch((e) => {
230
+ console.error(
231
+ "Error loading from peers",
232
+ id,
233
+
234
+ e
235
+ );
236
+ });
210
237
  }
211
238
  if (entry.state === "loaded") {
212
239
  return Promise.resolve(entry.coValue);
@@ -221,25 +248,38 @@ export class LocalNode {
221
248
  *
222
249
  * @category 3. Low-level
223
250
  */
224
- async load<T extends CoValue>(id: CoID<T>): Promise<T> {
225
- return (await this.loadCoValue(id)).getCurrentContent() as T;
251
+ async load<T extends CoValue>(
252
+ id: CoID<T>,
253
+ onProgress?: (progress: number) => void
254
+ ): Promise<T | "unavailable"> {
255
+ const core = await this.loadCoValueCore(id, { onProgress });
256
+
257
+ if (core === "unavailable") {
258
+ return "unavailable";
259
+ }
260
+
261
+ return core.getCurrentContent() as T;
226
262
  }
227
263
 
228
264
  /** @category 3. Low-level */
229
265
  subscribe<T extends CoValue>(
230
266
  id: CoID<T>,
231
- callback: (update: T) => void
267
+ callback: (update: T | "unavailable") => void
232
268
  ): () => void {
233
269
  let stopped = false;
234
270
  let unsubscribe!: () => void;
235
271
 
236
- console.log("Subscribing to " + id);
272
+ // console.log("Subscribing to " + id);
237
273
 
238
274
  this.load(id)
239
275
  .then((coValue) => {
240
276
  if (stopped) {
241
277
  return;
242
278
  }
279
+ if (coValue === "unavailable") {
280
+ callback("unavailable");
281
+ return;
282
+ }
243
283
  unsubscribe = coValue.subscribe(callback);
244
284
  })
245
285
  .catch((e) => {
@@ -260,6 +300,12 @@ export class LocalNode {
260
300
  ): Promise<void> {
261
301
  const groupOrOwnedValue = await this.load(groupOrOwnedValueID);
262
302
 
303
+ if (groupOrOwnedValue === "unavailable") {
304
+ throw new Error(
305
+ "Trying to accept invite: Group/owned value unavailable from all peers"
306
+ );
307
+ }
308
+
263
309
  if (groupOrOwnedValue.core.header.ruleset.type === "ownedByGroup") {
264
310
  return this.acceptInvite(
265
311
  groupOrOwnedValue.core.header.ruleset.group as CoID<Group>,
@@ -325,7 +371,7 @@ export class LocalNode {
325
371
  : "reader"
326
372
  );
327
373
 
328
- group.core._sessions = groupAsInvite.core.sessions;
374
+ group.core._sessionLogs = groupAsInvite.core.sessionLogs;
329
375
  group.core._cachedContent = undefined;
330
376
 
331
377
  for (const groupListener of group.core.listeners) {
@@ -400,17 +446,6 @@ export class LocalNode {
400
446
  },
401
447
  });
402
448
 
403
- console.log(
404
- "Creating read key",
405
- getAgentSealerSecret(agentSecret),
406
- getAgentSealerID(accountAgentID),
407
- account.id,
408
- account.core.nextTransactionID(),
409
- "in session",
410
- account.core.node.currentSessionID,
411
- "=",
412
- sealed
413
- );
414
449
  editable.set(
415
450
  `${readKey.id}_for_${accountAgentID}`,
416
451
  sealed,
@@ -432,16 +467,13 @@ export class LocalNode {
432
467
 
433
468
  const accountOnThisNode = this.expectCoValueLoaded(account.id);
434
469
 
435
- accountOnThisNode._sessions = {
436
- ...account.core.sessions,
437
- };
470
+ accountOnThisNode._sessionLogs = new Map(account.core.sessionLogs);
471
+
438
472
  accountOnThisNode._cachedContent = undefined;
439
473
 
440
474
  const profileOnThisNode = this.createCoValue(profile.core.header);
441
475
 
442
- profileOnThisNode._sessions = {
443
- ...profile.core.sessions,
444
- };
476
+ profileOnThisNode._sessionLogs = new Map(profile.core.sessionLogs);
445
477
  profileOnThisNode._cachedContent = undefined;
446
478
 
447
479
  return new ControlledAccount(accountOnThisNode, agentSecret);
@@ -475,6 +507,41 @@ export class LocalNode {
475
507
  return new Account(coValue).getCurrentAgentID();
476
508
  }
477
509
 
510
+ async resolveAccountAgentAsync(
511
+ id: AccountID | AgentID,
512
+ expectation?: string
513
+ ): Promise<AgentID> {
514
+ if (isAgentID(id)) {
515
+ return id;
516
+ }
517
+
518
+ const coValue = await this.loadCoValueCore(id);
519
+
520
+ if (coValue === "unavailable") {
521
+ throw new Error(
522
+ `${
523
+ expectation ? expectation + ": " : ""
524
+ }Account ${id} is unavailable from all peers`
525
+ );
526
+ }
527
+
528
+ if (
529
+ coValue.header.type !== "comap" ||
530
+ coValue.header.ruleset.type !== "group" ||
531
+ !coValue.header.meta ||
532
+ !("type" in coValue.header.meta) ||
533
+ coValue.header.meta.type !== "account"
534
+ ) {
535
+ throw new Error(
536
+ `${
537
+ expectation ? expectation + ": " : ""
538
+ }CoValue ${id} is not an account`
539
+ );
540
+ }
541
+
542
+ return new Account(coValue).getCurrentAgentID();
543
+ }
544
+
478
545
  /**
479
546
  * @deprecated use Account.createGroup() instead
480
547
  */
@@ -543,7 +610,7 @@ export class LocalNode {
543
610
  const newCoValue = new CoValueCore(
544
611
  entry.coValue.header,
545
612
  newNode,
546
- { ...entry.coValue.sessions }
613
+ new Map(entry.coValue.sessionLogs)
547
614
  );
548
615
 
549
616
  newNode.coValues[coValueID as RawCoID] = {
@@ -575,16 +642,34 @@ export class LocalNode {
575
642
  type CoValueState =
576
643
  | {
577
644
  state: "loading";
578
- done: Promise<CoValueCore>;
579
- resolve: (coValue: CoValueCore) => void;
645
+ done: Promise<CoValueCore | "unavailable">;
646
+ resolve: (coValue: CoValueCore | "unavailable") => void;
647
+ onProgress?: (progress: number) => void;
648
+ firstPeerState: {
649
+ [peerID: string]:
650
+ | {
651
+ type: "waiting";
652
+ done: Promise<void>;
653
+ resolve: () => void;
654
+ }
655
+ | { type: "available" }
656
+ | { type: "unavailable" };
657
+ };
580
658
  }
581
- | { state: "loaded"; coValue: CoValueCore };
659
+ | {
660
+ state: "loaded";
661
+ coValue: CoValueCore;
662
+ onProgress?: (progress: number) => void;
663
+ };
582
664
 
583
665
  /** @internal */
584
- export function newLoadingState(): CoValueState {
585
- let resolve: (coValue: CoValueCore) => void;
666
+ export function newLoadingState(
667
+ currentPeerIds: Set<PeerID>,
668
+ onProgress?: (progress: number) => void
669
+ ): CoValueState {
670
+ let resolve: (coValue: CoValueCore | "unavailable") => void;
586
671
 
587
- const promise = new Promise<CoValueCore>((r) => {
672
+ const promise = new Promise<CoValueCore | "unavailable">((r) => {
588
673
  resolve = r;
589
674
  });
590
675
 
@@ -592,5 +677,15 @@ export function newLoadingState(): CoValueState {
592
677
  state: "loading",
593
678
  done: promise,
594
679
  resolve: resolve!,
680
+ onProgress,
681
+ firstPeerState: Object.fromEntries(
682
+ [...currentPeerIds].map((id) => {
683
+ let resolve: () => void;
684
+ const done = new Promise<void>((r) => {
685
+ resolve = r;
686
+ });
687
+ return [id, { type: "waiting", done, resolve: resolve! }];
688
+ })
689
+ ),
595
690
  };
596
691
  }
@@ -2,10 +2,7 @@ import { CoID } from "./coValue.js";
2
2
  import { MapOpPayload } from "./coValues/coMap.js";
3
3
  import { JsonValue } from "./jsonValue.js";
4
4
  import { KeyID } from "./crypto.js";
5
- import {
6
- CoValueCore,
7
- Transaction,
8
- } from "./coValueCore.js";
5
+ import { CoValueCore, Transaction } from "./coValueCore.js";
9
6
  import { accountOrAgentIDfromSessionID } from "./typeUtils/accountOrAgentIDfromSessionID.js";
10
7
  import { AgentID, RawCoID, SessionID, TransactionID } from "./ids.js";
11
8
  import { Account, AccountID, Profile } from "./coValues/account.js";
@@ -31,19 +28,19 @@ export function determineValidTransactions(
31
28
  coValue: CoValueCore
32
29
  ): { txID: TransactionID; tx: Transaction }[] {
33
30
  if (coValue.header.ruleset.type === "group") {
34
- const allTransactionsSorted = Object.entries(coValue.sessions).flatMap(
35
- ([sessionID, sessionLog]) => {
36
- return sessionLog.transactions.map((tx, txIndex) => ({
37
- sessionID,
38
- txIndex,
39
- tx,
40
- })) as {
41
- sessionID: SessionID;
42
- txIndex: number;
43
- tx: Transaction;
44
- }[];
45
- }
46
- );
31
+ const allTransactionsSorted = [
32
+ ...coValue.sessionLogs.entries(),
33
+ ].flatMap(([sessionID, sessionLog]) => {
34
+ return sessionLog.transactions.map((tx, txIndex) => ({
35
+ sessionID,
36
+ txIndex,
37
+ tx,
38
+ })) as {
39
+ sessionID: SessionID;
40
+ txIndex: number;
41
+ tx: Transaction;
42
+ }[];
43
+ });
47
44
 
48
45
  allTransactionsSorted.sort((a, b) => {
49
46
  return a.tx.madeAt - b.tx.madeAt;
@@ -242,11 +239,9 @@ export function determineValidTransactions(
242
239
  throw new Error("Group must be a map");
243
240
  }
244
241
 
245
- return Object.entries(coValue.sessions).flatMap(
242
+ return [...coValue.sessionLogs.entries()].flatMap(
246
243
  ([sessionID, sessionLog]) => {
247
- const transactor = accountOrAgentIDfromSessionID(
248
- sessionID as SessionID
249
- );
244
+ const transactor = accountOrAgentIDfromSessionID(sessionID);
250
245
 
251
246
  return sessionLog.transactions
252
247
  .filter((tx) => {
@@ -266,16 +261,16 @@ export function determineValidTransactions(
266
261
  );
267
262
  })
268
263
  .map((tx, txIndex) => ({
269
- txID: { sessionID: sessionID as SessionID, txIndex },
264
+ txID: { sessionID: sessionID, txIndex },
270
265
  tx,
271
266
  }));
272
267
  }
273
268
  );
274
269
  } else if (coValue.header.ruleset.type === "unsafeAllowAll") {
275
- return Object.entries(coValue.sessions).flatMap(
270
+ return [...coValue.sessionLogs.entries()].flatMap(
276
271
  ([sessionID, sessionLog]) => {
277
272
  return sessionLog.transactions.map((tx, txIndex) => ({
278
- txID: { sessionID: sessionID as SessionID, txIndex },
273
+ txID: { sessionID: sessionID, txIndex },
279
274
  tx,
280
275
  }));
281
276
  }