cojson 0.4.13 → 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.
- package/CHANGELOG.md +7 -0
- package/dist/coValueCore.js +108 -42
- package/dist/coValueCore.js.map +1 -1
- package/dist/coValues/coList.js +21 -4
- package/dist/coValues/coList.js.map +1 -1
- package/dist/crypto.js +57 -31
- package/dist/crypto.js.map +1 -1
- package/dist/localNode.js +58 -16
- package/dist/localNode.js.map +1 -1
- package/dist/permissions.js +5 -3
- package/dist/permissions.js.map +1 -1
- package/dist/streamUtils.js +34 -8
- package/dist/streamUtils.js.map +1 -1
- package/dist/sync.js +162 -40
- package/dist/sync.js.map +1 -1
- package/dist/typeUtils/accountOrAgentIDfromSessionID.js +2 -1
- package/dist/typeUtils/accountOrAgentIDfromSessionID.js.map +1 -1
- package/package.json +4 -3
- package/src/coValueCore.ts +140 -63
- package/src/coValues/coList.ts +33 -8
- package/src/crypto.ts +83 -41
- package/src/localNode.ts +127 -34
- package/src/permissions.ts +19 -24
- package/src/streamUtils.ts +41 -8
- package/src/sync.ts +206 -59
- package/src/tests/coValue.test.ts +3 -3
- package/src/tests/permissions.test.ts +124 -83
- package/src/tests/sync.test.ts +29 -20
- package/src/typeUtils/accountOrAgentIDfromSessionID.ts +2 -1
package/src/crypto.ts
CHANGED
|
@@ -1,4 +1,12 @@
|
|
|
1
|
-
import {
|
|
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 =
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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(
|
|
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
|
-
|
|
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 =
|
|
83
|
-
|
|
84
|
-
|
|
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
|
|
95
|
-
base58.decode(
|
|
96
|
-
|
|
97
|
-
|
|
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(
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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>(
|
|
225
|
-
|
|
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.
|
|
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.
|
|
436
|
-
|
|
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.
|
|
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
|
-
|
|
613
|
+
new Map(entry.coValue.sessionLogs)
|
|
547
614
|
);
|
|
548
615
|
|
|
549
616
|
newNode.coValues[coValueID as RawCoID] = {
|
|
@@ -575,17 +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;
|
|
580
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
|
+
};
|
|
581
658
|
}
|
|
582
|
-
| {
|
|
659
|
+
| {
|
|
660
|
+
state: "loaded";
|
|
661
|
+
coValue: CoValueCore;
|
|
662
|
+
onProgress?: (progress: number) => void;
|
|
663
|
+
};
|
|
583
664
|
|
|
584
665
|
/** @internal */
|
|
585
|
-
export function newLoadingState(
|
|
586
|
-
|
|
666
|
+
export function newLoadingState(
|
|
667
|
+
currentPeerIds: Set<PeerID>,
|
|
668
|
+
onProgress?: (progress: number) => void
|
|
669
|
+
): CoValueState {
|
|
670
|
+
let resolve: (coValue: CoValueCore | "unavailable") => void;
|
|
587
671
|
|
|
588
|
-
const promise = new Promise<CoValueCore>((r) => {
|
|
672
|
+
const promise = new Promise<CoValueCore | "unavailable">((r) => {
|
|
589
673
|
resolve = r;
|
|
590
674
|
});
|
|
591
675
|
|
|
@@ -593,6 +677,15 @@ export function newLoadingState(onProgress?: (progress: number) => void): CoValu
|
|
|
593
677
|
state: "loading",
|
|
594
678
|
done: promise,
|
|
595
679
|
resolve: resolve!,
|
|
596
|
-
onProgress
|
|
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
|
+
),
|
|
597
690
|
};
|
|
598
691
|
}
|
package/src/permissions.ts
CHANGED
|
@@ -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 =
|
|
35
|
-
(
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
|
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
|
|
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
|
|
270
|
+
return [...coValue.sessionLogs.entries()].flatMap(
|
|
276
271
|
([sessionID, sessionLog]) => {
|
|
277
272
|
return sessionLog.transactions.map((tx, txIndex) => ({
|
|
278
|
-
txID: { sessionID: sessionID
|
|
273
|
+
txID: { sessionID: sessionID, txIndex },
|
|
279
274
|
tx,
|
|
280
275
|
}));
|
|
281
276
|
}
|
package/src/streamUtils.ts
CHANGED
|
@@ -18,11 +18,11 @@ export function connectedPeers(
|
|
|
18
18
|
peer2role?: Peer["role"];
|
|
19
19
|
} = {}
|
|
20
20
|
): [Peer, Peer] {
|
|
21
|
-
const [inRx1, inTx1] = newStreamPair<SyncMessage>();
|
|
22
|
-
const [outRx1, outTx1] = newStreamPair<SyncMessage>();
|
|
21
|
+
const [inRx1, inTx1] = newStreamPair<SyncMessage>(peer1id + "_in");
|
|
22
|
+
const [outRx1, outTx1] = newStreamPair<SyncMessage>(peer1id + "_out");
|
|
23
23
|
|
|
24
|
-
const [inRx2, inTx2] = newStreamPair<SyncMessage>();
|
|
25
|
-
const [outRx2, outTx2] = newStreamPair<SyncMessage>();
|
|
24
|
+
const [inRx2, inTx2] = newStreamPair<SyncMessage>(peer2id + "_in");
|
|
25
|
+
const [outRx2, outTx2] = newStreamPair<SyncMessage>(peer2id + "_out");
|
|
26
26
|
|
|
27
27
|
void outRx2
|
|
28
28
|
.pipeThrough(
|
|
@@ -37,7 +37,7 @@ export function connectedPeers(
|
|
|
37
37
|
JSON.stringify(
|
|
38
38
|
chunk,
|
|
39
39
|
(k, v) =>
|
|
40
|
-
|
|
40
|
+
k === "changes" || k === "encryptedChanges"
|
|
41
41
|
? v.slice(0, 20) + "..."
|
|
42
42
|
: v,
|
|
43
43
|
2
|
|
@@ -62,7 +62,7 @@ export function connectedPeers(
|
|
|
62
62
|
JSON.stringify(
|
|
63
63
|
chunk,
|
|
64
64
|
(k, v) =>
|
|
65
|
-
|
|
65
|
+
k === "changes" || k === "encryptedChanges"
|
|
66
66
|
? v.slice(0, 20) + "..."
|
|
67
67
|
: v,
|
|
68
68
|
2
|
|
@@ -91,7 +91,10 @@ export function connectedPeers(
|
|
|
91
91
|
return [peer1AsPeer, peer2AsPeer];
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
-
export function newStreamPair<T>(
|
|
94
|
+
export function newStreamPair<T>(
|
|
95
|
+
pairName?: string
|
|
96
|
+
): [ReadableStream<T>, WritableStream<T>] {
|
|
97
|
+
let queueLength = 0;
|
|
95
98
|
let readerClosed = false;
|
|
96
99
|
|
|
97
100
|
let resolveEnqueue: (enqueue: (item: T) => void) => void;
|
|
@@ -104,6 +107,22 @@ export function newStreamPair<T>(): [ReadableStream<T>, WritableStream<T>] {
|
|
|
104
107
|
resolveClose = resolve;
|
|
105
108
|
});
|
|
106
109
|
|
|
110
|
+
let queueWasOverflowing = false;
|
|
111
|
+
|
|
112
|
+
function maybeReportQueueLength() {
|
|
113
|
+
if (queueLength >= 100) {
|
|
114
|
+
queueWasOverflowing = true;
|
|
115
|
+
if (queueLength % 100 === 0) {
|
|
116
|
+
console.warn(pairName, "overflowing queue length", queueLength);
|
|
117
|
+
}
|
|
118
|
+
} else {
|
|
119
|
+
if (queueWasOverflowing) {
|
|
120
|
+
console.debug(pairName, "ok queue length", queueLength);
|
|
121
|
+
queueWasOverflowing = false;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
107
126
|
const readable = new ReadableStream<T>({
|
|
108
127
|
async start(controller) {
|
|
109
128
|
resolveEnqueue(controller.enqueue.bind(controller));
|
|
@@ -114,12 +133,26 @@ export function newStreamPair<T>(): [ReadableStream<T>, WritableStream<T>] {
|
|
|
114
133
|
console.log("Manually closing reader");
|
|
115
134
|
readerClosed = true;
|
|
116
135
|
},
|
|
117
|
-
})
|
|
136
|
+
}).pipeThrough(
|
|
137
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
138
|
+
new TransformStream<any, any>({
|
|
139
|
+
transform(
|
|
140
|
+
chunk: SyncMessage,
|
|
141
|
+
controller: { enqueue: (msg: SyncMessage) => void }
|
|
142
|
+
) {
|
|
143
|
+
queueLength -= 1;
|
|
144
|
+
maybeReportQueueLength();
|
|
145
|
+
controller.enqueue(chunk);
|
|
146
|
+
},
|
|
147
|
+
})
|
|
148
|
+
) as ReadableStream<T>;
|
|
118
149
|
|
|
119
150
|
let lastWritePromise = Promise.resolve();
|
|
120
151
|
|
|
121
152
|
const writable = new WritableStream<T>({
|
|
122
153
|
async write(chunk) {
|
|
154
|
+
queueLength += 1;
|
|
155
|
+
maybeReportQueueLength();
|
|
123
156
|
const enqueue = await enqueuePromise;
|
|
124
157
|
if (readerClosed) {
|
|
125
158
|
throw new Error("Reader closed");
|