cojson 0.1.10 → 0.1.12

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.
@@ -100,6 +100,7 @@ export class CoValueCore {
100
100
  _sessions: { [key: SessionID]: SessionLog };
101
101
  _cachedContent?: CoValueImpl;
102
102
  listeners: Set<(content?: CoValueImpl) => void> = new Set();
103
+ _decryptionCache: {[key: Encrypted<JsonValue[], JsonValue>]: JsonValue[] | undefined} = {}
103
104
 
104
105
  constructor(
105
106
  header: CoValueHeader,
@@ -186,10 +187,16 @@ export class CoValueCore {
186
187
  return false;
187
188
  }
188
189
 
190
+ // const beforeHash = performance.now();
189
191
  const { expectedNewHash, newStreamingHash } = this.expectedNewHashAfter(
190
192
  sessionID,
191
193
  newTransactions
192
194
  );
195
+ // const afterHash = performance.now();
196
+ // console.log(
197
+ // "Hashing took",
198
+ // afterHash - beforeHash
199
+ // );
193
200
 
194
201
  if (givenExpectedNewHash && givenExpectedNewHash !== expectedNewHash) {
195
202
  console.warn("Invalid hash", {
@@ -199,6 +206,7 @@ export class CoValueCore {
199
206
  return false;
200
207
  }
201
208
 
209
+ // const beforeVerify = performance.now();
202
210
  if (!verify(newSignature, expectedNewHash, signerID)) {
203
211
  console.warn(
204
212
  "Invalid signature",
@@ -208,6 +216,11 @@ export class CoValueCore {
208
216
  );
209
217
  return false;
210
218
  }
219
+ // const afterVerify = performance.now();
220
+ // console.log(
221
+ // "Verify took",
222
+ // afterVerify - beforeVerify
223
+ // );
211
224
 
212
225
  const transactions = this.sessions[sessionID]?.transactions ?? [];
213
226
 
@@ -222,10 +235,105 @@ export class CoValueCore {
222
235
 
223
236
  this._cachedContent = undefined;
224
237
 
225
- const content = this.getCurrentContent();
238
+ if (this.listeners.size > 0) {
239
+ const content = this.getCurrentContent();
240
+ for (const listener of this.listeners) {
241
+ listener(content);
242
+ }
243
+ }
244
+
245
+ return true;
246
+ }
226
247
 
227
- for (const listener of this.listeners) {
228
- listener(content);
248
+ async tryAddTransactionsAsync(
249
+ sessionID: SessionID,
250
+ newTransactions: Transaction[],
251
+ givenExpectedNewHash: Hash | undefined,
252
+ newSignature: Signature
253
+ ): Promise<boolean> {
254
+ const signerID = getAgentSignerID(
255
+ this.node.resolveAccountAgent(
256
+ accountOrAgentIDfromSessionID(sessionID),
257
+ "Expected to know signer of transaction"
258
+ )
259
+ );
260
+
261
+ if (!signerID) {
262
+ console.warn(
263
+ "Unknown agent",
264
+ accountOrAgentIDfromSessionID(sessionID)
265
+ );
266
+ return false;
267
+ }
268
+
269
+ const nTxBefore = this.sessions[sessionID]?.transactions.length ?? 0;
270
+
271
+ // const beforeHash = performance.now();
272
+ const { expectedNewHash, newStreamingHash } = await this.expectedNewHashAfterAsync(
273
+ sessionID,
274
+ newTransactions
275
+ );
276
+ // const afterHash = performance.now();
277
+ // console.log(
278
+ // "Hashing took",
279
+ // afterHash - beforeHash
280
+ // );
281
+
282
+ const nTxAfter = this.sessions[sessionID]?.transactions.length ?? 0;
283
+
284
+ if (nTxAfter !== nTxBefore) {
285
+ const newTransactionLengthBefore = newTransactions.length;
286
+ newTransactions = newTransactions.slice((nTxAfter - nTxBefore));
287
+ console.warn("Transactions changed while async hashing", {
288
+ nTxBefore,
289
+ nTxAfter,
290
+ newTransactionLengthBefore,
291
+ remainingNewTransactions: newTransactions.length,
292
+ });
293
+ }
294
+
295
+ if (givenExpectedNewHash && givenExpectedNewHash !== expectedNewHash) {
296
+ console.warn("Invalid hash", {
297
+ expectedNewHash,
298
+ givenExpectedNewHash,
299
+ });
300
+ return false;
301
+ }
302
+
303
+ // const beforeVerify = performance.now();
304
+ if (!verify(newSignature, expectedNewHash, signerID)) {
305
+ console.warn(
306
+ "Invalid signature",
307
+ newSignature,
308
+ expectedNewHash,
309
+ signerID
310
+ );
311
+ return false;
312
+ }
313
+ // const afterVerify = performance.now();
314
+ // console.log(
315
+ // "Verify took",
316
+ // afterVerify - beforeVerify
317
+ // );
318
+
319
+ const transactions = this.sessions[sessionID]?.transactions ?? [];
320
+
321
+ transactions.push(...newTransactions);
322
+
323
+ this._sessions[sessionID] = {
324
+ transactions,
325
+ lastHash: expectedNewHash,
326
+ streamingHash: newStreamingHash,
327
+ lastSignature: newSignature,
328
+ };
329
+
330
+ this._cachedContent = undefined;
331
+
332
+ if (this.listeners.size > 0) {
333
+ const content = this.getCurrentContent();
334
+ for (const listener of this.listeners) {
335
+ listener(content);
336
+ }
229
337
  }
230
338
 
231
339
  return true;
@@ -259,6 +367,32 @@ export class CoValueCore {
259
367
  };
260
368
  }
261
369
 
370
+ async expectedNewHashAfterAsync(
371
+ sessionID: SessionID,
372
+ newTransactions: Transaction[]
373
+ ): Promise<{ expectedNewHash: Hash; newStreamingHash: StreamingHash }> {
374
+ const streamingHash =
375
+ this.sessions[sessionID]?.streamingHash.clone() ??
376
+ new StreamingHash();
377
+ let before = performance.now();
378
+ for (const transaction of newTransactions) {
379
+ streamingHash.update(transaction)
380
+ const after = performance.now();
381
+ if (after - before > 1) {
382
+ console.log("Hashing blocked for", after - before);
383
+ await new Promise((resolve) => setTimeout(resolve, 0));
384
+ before = performance.now();
385
+ }
386
+ }
387
+
388
+ const newStreamingHash = streamingHash.clone();
389
+
390
+ return {
391
+ expectedNewHash: streamingHash.digest(),
392
+ newStreamingHash,
393
+ };
394
+ }
395
+
262
396
  makeTransaction(
263
397
  changes: JsonValue[],
264
398
  privacy: "private" | "trusting"
@@ -276,14 +410,18 @@ export class CoValueCore {
276
410
  );
277
411
  }
278
412
 
413
+ const encrypted = encryptForTransaction(changes, keySecret, {
414
+ in: this.id,
415
+ tx: this.nextTransactionID(),
416
+ });
417
+
418
+ this._decryptionCache[encrypted] = changes;
419
+
279
420
  transaction = {
280
421
  privacy: "private",
281
422
  madeAt,
282
423
  keyUsed: keyID,
283
- encryptedChanges: encryptForTransaction(changes, keySecret, {
284
- in: this.id,
285
- tx: this.nextTransactionID(),
286
- }),
424
+ encryptedChanges: encrypted,
287
425
  };
288
426
  } else {
289
427
  transaction = {
@@ -359,14 +497,19 @@ export class CoValueCore {
359
497
  if (!readKey) {
360
498
  return undefined;
361
499
  } else {
362
- const decrytedChanges = decryptForTransaction(
363
- tx.encryptedChanges,
364
- readKey,
365
- {
366
- in: this.id,
367
- tx: txID,
368
- }
369
- );
500
+ let decrytedChanges = this._decryptionCache[tx.encryptedChanges];
501
+
502
+ if (!decrytedChanges) {
503
+ decrytedChanges = decryptForTransaction(
504
+ tx.encryptedChanges,
505
+ readKey,
506
+ {
507
+ in: this.id,
508
+ tx: txID,
509
+ }
510
+ );
511
+ this._decryptionCache[tx.encryptedChanges] = decrytedChanges;
512
+ }
370
513
 
371
514
  if (!decrytedChanges) {
372
515
  console.error(
@@ -3,7 +3,7 @@ import { CoID, ReadableCoValue, WriteableCoValue } from "../coValue.js";
3
3
  import { CoValueCore } from "../coValueCore.js";
4
4
  import { Group } from "../group.js";
5
5
  import { SessionID } from "../ids.js";
6
- import { base64url } from "@scure/base";
6
+ import { base64URLtoBytes, bytesToBase64url } from "../base64url.js";
7
7
 
8
8
  export type BinaryChunkInfo = {
9
9
  mimeType: string;
@@ -17,7 +17,7 @@ export type BinaryStreamStart = {
17
17
 
18
18
  export type BinaryStreamChunk = {
19
19
  type: "chunk";
20
- chunk: `U${string}`;
20
+ chunk: `binary_U${string}`;
21
21
  };
22
22
 
23
23
  export type BinaryStreamEnd = {
@@ -47,6 +47,7 @@ export class CoStream<
47
47
  this.id = core.id as CoID<CoStream<T, Meta>>;
48
48
  this.core = core;
49
49
  this.items = {};
50
+ this.fillFromCoValue();
50
51
  }
51
52
 
52
53
  get meta(): Meta {
@@ -110,6 +111,8 @@ export class CoStream<
110
111
  }
111
112
  }
112
113
 
114
+ const binary_U_prefixLength = 8; // "binary_U".length;
115
+
113
116
  export class BinaryCoStream<
114
117
  Meta extends BinaryCoStreamMeta = { type: "binary" }
115
118
  >
@@ -121,6 +124,7 @@ export class BinaryCoStream<
121
124
  getBinaryChunks():
122
125
  | (BinaryChunkInfo & { chunks: Uint8Array[]; finished: boolean })
123
126
  | undefined {
127
+ const before = performance.now();
124
128
  const items = this.getSingleStream();
125
129
 
126
130
  if (!items) return;
@@ -134,15 +138,13 @@ export class BinaryCoStream<
134
138
 
135
139
  const chunks: Uint8Array[] = [];
136
140
 
141
+ let finished = false;
142
+ let totalLength = 0;
143
+
137
144
  for (const item of items.slice(1)) {
138
145
  if (item.type === "end") {
139
- return {
140
- mimeType: start.mimeType,
141
- fileName: start.fileName,
142
- totalSizeBytes: start.totalSizeBytes,
143
- chunks,
144
- finished: true,
145
- };
146
+ finished = true;
147
+ break;
146
148
  }
147
149
 
148
150
  if (item.type !== "chunk") {
@@ -150,15 +152,25 @@ export class BinaryCoStream<
150
152
  return undefined;
151
153
  }
152
154
 
153
- chunks.push(base64url.decode(item.chunk.slice(1)));
155
+ const chunk = base64URLtoBytes(
156
+ item.chunk.slice(binary_U_prefixLength)
157
+ );
158
+ totalLength += chunk.length;
159
+ chunks.push(chunk);
154
160
  }
155
161
 
162
+ const after = performance.now();
163
+ console.log(
164
+ "getBinaryChunks bandwidth in MB/s",
165
+ (1000 * totalLength) / (after - before) / (1024 * 1024)
166
+ );
167
+
156
168
  return {
157
169
  mimeType: start.mimeType,
158
170
  fileName: start.fileName,
159
171
  totalSizeBytes: start.totalSizeBytes,
160
172
  chunks,
161
- finished: false,
173
+ finished,
162
174
  };
163
175
  }
164
176
 
@@ -205,10 +217,7 @@ export class WriteableBinaryCoStream<
205
217
  }
206
218
 
207
219
  /** @internal */
208
- push(
209
- item: BinaryStreamItem,
210
- privacy: "private" | "trusting" = "private"
211
- ) {
220
+ push(item: BinaryStreamItem, privacy: "private" | "trusting" = "private") {
212
221
  WriteableCoStream.prototype.push.call(this, item, privacy);
213
222
  }
214
223
 
@@ -229,13 +238,19 @@ export class WriteableBinaryCoStream<
229
238
  chunk: Uint8Array,
230
239
  privacy: "private" | "trusting" = "private"
231
240
  ) {
241
+ const before = performance.now();
232
242
  this.push(
233
243
  {
234
244
  type: "chunk",
235
- chunk: `U${base64url.encode(chunk)}`,
245
+ chunk: `binary_U${bytesToBase64url(chunk)}`,
236
246
  } satisfies BinaryStreamChunk,
237
247
  privacy
238
248
  );
249
+ const after = performance.now();
250
+ console.log(
251
+ "pushBinaryStreamChunk bandwidth in MB/s",
252
+ (1000 * chunk.length) / (after - before) / (1024 * 1024)
253
+ );
239
254
  }
240
255
 
241
256
  endBinaryStream(privacy: "private" | "trusting" = "private") {
@@ -21,6 +21,11 @@ import { xsalsa20_poly1305 } from "@noble/ciphers/salsa";
21
21
  import { blake3 } from "@noble/hashes/blake3";
22
22
  import stableStringify from "fast-json-stable-stringify";
23
23
  import { SessionID } from './ids.js';
24
+ import { cojsonReady } from './index.js';
25
+
26
+ beforeEach(async () => {
27
+ await cojsonReady;
28
+ });
24
29
 
25
30
  test("Signatures round-trip and use stable stringify", () => {
26
31
  const data = { b: "world", a: "hello" };
package/src/crypto.ts CHANGED
@@ -1,11 +1,39 @@
1
1
  import { ed25519, x25519 } from "@noble/curves/ed25519";
2
2
  import { xsalsa20_poly1305, xsalsa20 } from "@noble/ciphers/salsa";
3
3
  import { JsonValue } from "./jsonValue.js";
4
- import { base58, base64url } from "@scure/base";
5
- import stableStringify from "fast-json-stable-stringify";
6
- import { blake3 } from "@noble/hashes/blake3";
4
+ import { base58 } from "@scure/base";
7
5
  import { randomBytes } from "@noble/ciphers/webcrypto/utils";
8
6
  import { AgentID, RawCoID, TransactionID } from "./ids.js";
7
+ import { base64URLtoBytes, bytesToBase64url } from "./base64url.js";
8
+
9
+ import { createBLAKE3 } from 'hash-wasm';
10
+ import { stableStringify } from "./fastJsonStableStringify.js";
11
+
12
+ let blake3Instance: Awaited<ReturnType<typeof createBLAKE3>>;
13
+ let blake3HashOnce: (data: Uint8Array) => Uint8Array;
14
+ let blake3HashOnceWithContext: (data: Uint8Array, {context}: {context: Uint8Array}) => Uint8Array;
15
+ let blake3incrementalUpdateSLOW_WITH_DEVTOOLS: (state: Uint8Array, data: Uint8Array) => Uint8Array;
16
+ let blake3digestForState: (state: Uint8Array) => Uint8Array;
17
+
18
+ export const cryptoReady = new Promise<void>((resolve) => {
19
+ createBLAKE3().then(bl3 => {
20
+ blake3Instance = bl3;
21
+ blake3HashOnce = (data) => {
22
+ return bl3.init().update(data).digest('binary');
23
+ }
24
+ blake3HashOnceWithContext = (data, {context}) => {
25
+ return bl3.init().update(context).update(data).digest('binary');
26
+ }
27
+ blake3incrementalUpdateSLOW_WITH_DEVTOOLS = (state, data) => {
28
+ bl3.load(state).update(data);
29
+ return bl3.save();
30
+ }
31
+ blake3digestForState = (state) => {
32
+ return bl3.load(state).digest('binary');
33
+ }
34
+ resolve();
35
+ })
36
+ });
9
37
 
10
38
  export type SignerSecret = `signerSecret_z${string}`;
11
39
  export type SignerID = `signer_z${string}`;
@@ -127,7 +155,7 @@ export function seal<T extends JsonValue>(
127
155
  to: SealerID,
128
156
  nOnceMaterial: { in: RawCoID; tx: TransactionID }
129
157
  ): Sealed<T> {
130
- const nOnce = blake3(
158
+ const nOnce = blake3HashOnce(
131
159
  textEncoder.encode(stableStringify(nOnceMaterial))
132
160
  ).slice(0, 24);
133
161
 
@@ -143,7 +171,7 @@ export function seal<T extends JsonValue>(
143
171
  plaintext
144
172
  );
145
173
 
146
- return `sealed_U${base64url.encode(sealedBytes)}` as Sealed<T>;
174
+ return `sealed_U${bytesToBase64url(sealedBytes)}` as Sealed<T>;
147
175
  }
148
176
 
149
177
  export function unseal<T extends JsonValue>(
@@ -152,7 +180,7 @@ export function unseal<T extends JsonValue>(
152
180
  from: SealerID,
153
181
  nOnceMaterial: { in: RawCoID; tx: TransactionID }
154
182
  ): T | undefined {
155
- const nOnce = blake3(
183
+ const nOnce = blake3HashOnce(
156
184
  textEncoder.encode(stableStringify(nOnceMaterial))
157
185
  ).slice(0, 24);
158
186
 
@@ -160,7 +188,7 @@ export function unseal<T extends JsonValue>(
160
188
 
161
189
  const senderPub = base58.decode(from.substring("sealer_z".length));
162
190
 
163
- const sealedBytes = base64url.decode(sealed.substring("sealed_U".length));
191
+ const sealedBytes = base64URLtoBytes(sealed.substring("sealed_U".length));
164
192
 
165
193
  const sharedSecret = x25519.getSharedSecret(sealerPriv, senderPub);
166
194
 
@@ -180,28 +208,32 @@ export type Hash = `hash_z${string}`;
180
208
 
181
209
  export function secureHash(value: JsonValue): Hash {
182
210
  return `hash_z${base58.encode(
183
- blake3(textEncoder.encode(stableStringify(value)))
211
+ blake3HashOnce(textEncoder.encode(stableStringify(value)))
184
212
  )}`;
185
213
  }
186
214
 
187
215
  export class StreamingHash {
188
- state: ReturnType<typeof blake3.create>;
216
+ state: Uint8Array;
189
217
 
190
- constructor(fromClone?: ReturnType<typeof blake3.create>) {
191
- this.state = fromClone || blake3.create({});
218
+ constructor(fromClone?: Uint8Array) {
219
+ this.state = fromClone || blake3Instance.init().save();
192
220
  }
193
221
 
194
222
  update(value: JsonValue) {
195
- this.state.update(textEncoder.encode(stableStringify(value)));
223
+ const encoded = textEncoder.encode(stableStringify(value))
224
+ // const before = performance.now();
225
+ this.state = blake3incrementalUpdateSLOW_WITH_DEVTOOLS(this.state, encoded);
226
+ // const after = performance.now();
227
+ // console.log(`Hashing throughput in MB/s`, 1000 * (encoded.length / (after - before)) / (1024 * 1024));
196
228
  }
197
229
 
198
230
  digest(): Hash {
199
- const hash = this.state.digest();
231
+ const hash = blake3digestForState(this.state);
200
232
  return `hash_z${base58.encode(hash)}`;
201
233
  }
202
234
 
203
235
  clone(): StreamingHash {
204
- return new StreamingHash(this.state.clone());
236
+ return new StreamingHash(new Uint8Array(this.state));
205
237
  }
206
238
  }
207
239
 
@@ -210,7 +242,10 @@ export const shortHashLength = 19;
210
242
 
211
243
  export function shortHash(value: JsonValue): ShortHash {
212
244
  return `shortHash_z${base58.encode(
213
- blake3(textEncoder.encode(stableStringify(value))).slice(0, shortHashLength)
245
+ blake3HashOnce(textEncoder.encode(stableStringify(value))).slice(
246
+ 0,
247
+ shortHashLength
248
+ )
214
249
  )}`;
215
250
  }
216
251
 
@@ -237,13 +272,13 @@ function encrypt<T extends JsonValue, N extends JsonValue>(
237
272
  const keySecretBytes = base58.decode(
238
273
  keySecret.substring("keySecret_z".length)
239
274
  );
240
- const nOnce = blake3(
275
+ const nOnce = blake3HashOnce(
241
276
  textEncoder.encode(stableStringify(nOnceMaterial))
242
277
  ).slice(0, 24);
243
278
 
244
279
  const plaintext = textEncoder.encode(stableStringify(value));
245
280
  const ciphertext = xsalsa20(keySecretBytes, nOnce, plaintext);
246
- return `encrypted_U${base64url.encode(ciphertext)}` as Encrypted<T, N>;
281
+ return `encrypted_U${bytesToBase64url(ciphertext)}` as Encrypted<T, N>;
247
282
  }
248
283
 
249
284
  export function encryptForTransaction<T extends JsonValue>(
@@ -289,11 +324,11 @@ function decrypt<T extends JsonValue, N extends JsonValue>(
289
324
  const keySecretBytes = base58.decode(
290
325
  keySecret.substring("keySecret_z".length)
291
326
  );
292
- const nOnce = blake3(
327
+ const nOnce = blake3HashOnce(
293
328
  textEncoder.encode(stableStringify(nOnceMaterial))
294
329
  ).slice(0, 24);
295
330
 
296
- const ciphertext = base64url.decode(
331
+ const ciphertext = base64URLtoBytes(
297
332
  encrypted.substring("encrypted_U".length)
298
333
  );
299
334
  const plaintext = xsalsa20(keySecretBytes, nOnce, ciphertext);
@@ -355,15 +390,17 @@ export function newRandomSecretSeed(): Uint8Array {
355
390
 
356
391
  export function agentSecretFromSecretSeed(secretSeed: Uint8Array): AgentSecret {
357
392
  if (secretSeed.length !== secretSeedLength) {
358
- throw new Error(`Secret seed needs to be ${secretSeedLength} bytes long`);
393
+ throw new Error(
394
+ `Secret seed needs to be ${secretSeedLength} bytes long`
395
+ );
359
396
  }
360
397
 
361
398
  return `sealerSecret_z${base58.encode(
362
- blake3(secretSeed, {
399
+ blake3HashOnceWithContext(secretSeed, {
363
400
  context: textEncoder.encode("seal"),
364
401
  })
365
402
  )}/signerSecret_z${base58.encode(
366
- blake3(secretSeed, {
403
+ blake3HashOnceWithContext(secretSeed, {
367
404
  context: textEncoder.encode("sign"),
368
405
  })
369
406
  )}`;
@@ -0,0 +1,54 @@
1
+ // adapted from fast-json-stable-stringify (https://github.com/epoberezkin/fast-json-stable-stringify)
2
+
3
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
4
+ export function stableStringify(data: any): string | undefined {
5
+ const cycles = false;
6
+
7
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
8
+ const seen: any[] = [];
9
+ let node = data;
10
+
11
+ if (node && node.toJSON && typeof node.toJSON === "function") {
12
+ node = node.toJSON();
13
+ }
14
+
15
+ if (node === undefined) return;
16
+ if (typeof node == "number") return isFinite(node) ? "" + node : "null";
17
+ if (typeof node !== "object") {
18
+ if (typeof node === "string" && (node.startsWith("encrypted_U") || node.startsWith("binary_U"))) {
19
+ return `"${node}"`;
20
+ }
21
+ return JSON.stringify(node);
22
+ }
23
+
24
+ let i, out;
25
+ if (Array.isArray(node)) {
26
+ out = "[";
27
+ for (i = 0; i < node.length; i++) {
28
+ if (i) out += ",";
29
+ out += stableStringify(node[i]) || "null";
30
+ }
31
+ return out + "]";
32
+ }
33
+
34
+ if (node === null) return "null";
35
+
36
+ if (seen.indexOf(node) !== -1) {
37
+ if (cycles) return JSON.stringify("__cycle__");
38
+ throw new TypeError("Converting circular structure to JSON");
39
+ }
40
+
41
+ const seenIndex = seen.push(node) - 1;
42
+ const keys = Object.keys(node).sort();
43
+ out = "";
44
+ for (i = 0; i < keys.length; i++) {
45
+ const key = keys[i]!;
46
+ const value = stableStringify(node[key]);
47
+
48
+ if (!value) continue;
49
+ if (out) out += ",";
50
+ out += JSON.stringify(key) + ":" + value;
51
+ }
52
+ seen.splice(seenIndex, 1);
53
+ return "{" + out + "}";
54
+ }
package/src/group.test.ts CHANGED
@@ -1,6 +1,10 @@
1
- import { LocalNode, CoMap, CoList, CoStream, BinaryCoStream } from "./index";
1
+ import { LocalNode, CoMap, CoList, CoStream, BinaryCoStream, cojsonReady } from "./index";
2
2
  import { randomAnonymousAccountAndSessionID } from "./testUtils";
3
3
 
4
+ beforeEach(async () => {
5
+ await cojsonReady;
6
+ });
7
+
4
8
  test("Can create a CoMap in a group", () => {
5
9
  const node = new LocalNode(...randomAnonymousAccountAndSessionID());
6
10
 
package/src/index.ts CHANGED
@@ -18,11 +18,13 @@ import {
18
18
  agentSecretFromSecretSeed,
19
19
  secretSeedLength,
20
20
  shortHashLength,
21
+ cryptoReady
21
22
  } from "./crypto.js";
22
23
  import { connectedPeers } from "./streamUtils.js";
23
24
  import { AnonymousControlledAccount, ControlledAccount } from "./account.js";
24
25
  import { rawCoIDtoBytes, rawCoIDfromBytes } from "./ids.js";
25
26
  import { Group, expectGroupContent } from "./group.js";
27
+ import { base64URLtoBytes, bytesToBase64url } from "./base64url.js";
26
28
 
27
29
  import type { SessionID, AgentID } from "./ids.js";
28
30
  import type { CoID, CoValueImpl } from "./coValue.js";
@@ -50,6 +52,8 @@ export const cojsonInternals = {
50
52
  secretSeedLength,
51
53
  shortHashLength,
52
54
  expectGroupContent,
55
+ base64URLtoBytes,
56
+ bytesToBase64url
53
57
  };
54
58
 
55
59
  export {
@@ -66,6 +70,7 @@ export {
66
70
  CoValueCore,
67
71
  AnonymousControlledAccount,
68
72
  ControlledAccount,
73
+ cryptoReady as cojsonReady,
69
74
  };
70
75
 
71
76
  export type {
@@ -17,7 +17,11 @@ import {
17
17
  groupWithTwoAdmins,
18
18
  groupWithTwoAdminsHighLevel,
19
19
  } from "./testUtils.js";
20
- import { AnonymousControlledAccount } from "./index.js";
20
+ import { AnonymousControlledAccount, cojsonReady } from "./index.js";
21
+
22
+ beforeEach(async () => {
23
+ await cojsonReady;
24
+ });
21
25
 
22
26
  test("Initial admin can add another admin to a group", () => {
23
27
  groupWithTwoAdmins();
package/src/sync.test.ts CHANGED
@@ -1,14 +1,9 @@
1
1
  import { newRandomSessionID } from "./coValueCore.js";
2
2
  import { LocalNode } from "./node.js";
3
- import { Peer, PeerID, SyncMessage } from "./sync.js";
3
+ import { SyncMessage } from "./sync.js";
4
4
  import { expectMap } from "./coValue.js";
5
5
  import { MapOpPayload } from "./coValues/coMap.js";
6
6
  import { Group } from "./group.js";
7
- import {
8
- ReadableStream,
9
- WritableStream,
10
- TransformStream,
11
- } from "isomorphic-streams";
12
7
  import {
13
8
  randomAnonymousAccountAndSessionID,
14
9
  shouldNotResolve,
@@ -18,6 +13,11 @@ import {
18
13
  newStreamPair
19
14
  } from "./streamUtils.js";
20
15
  import { AccountID } from "./account.js";
16
+ import { cojsonReady } from "./index.js";
17
+
18
+ beforeEach(async () => {
19
+ await cojsonReady;
20
+ });
21
21
 
22
22
  test("Node replies with initial tx and header to empty subscribe", async () => {
23
23
  const [admin, session] = randomAnonymousAccountAndSessionID();