cojson 0.18.32 → 0.18.34
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/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +18 -0
- package/dist/SyncStateManager.d.ts.map +1 -1
- package/dist/SyncStateManager.js +2 -2
- package/dist/SyncStateManager.js.map +1 -1
- package/dist/coValueCore/SessionMap.d.ts +1 -0
- package/dist/coValueCore/SessionMap.d.ts.map +1 -1
- package/dist/coValueCore/SessionMap.js +22 -12
- package/dist/coValueCore/SessionMap.js.map +1 -1
- package/dist/coValueCore/coValueCore.d.ts +14 -9
- package/dist/coValueCore/coValueCore.d.ts.map +1 -1
- package/dist/coValueCore/coValueCore.js +65 -51
- package/dist/coValueCore/coValueCore.js.map +1 -1
- package/dist/coValueCore/verifiedState.d.ts +5 -3
- package/dist/coValueCore/verifiedState.d.ts.map +1 -1
- package/dist/coValueCore/verifiedState.js +93 -76
- package/dist/coValueCore/verifiedState.js.map +1 -1
- package/dist/coValues/group.d.ts +1 -0
- package/dist/coValues/group.d.ts.map +1 -1
- package/dist/coValues/group.js +24 -4
- package/dist/coValues/group.js.map +1 -1
- package/dist/knownState.d.ts +9 -1
- package/dist/knownState.d.ts.map +1 -1
- package/dist/knownState.js +29 -3
- package/dist/knownState.js.map +1 -1
- package/dist/localNode.d.ts.map +1 -1
- package/dist/localNode.js +3 -3
- package/dist/localNode.js.map +1 -1
- package/dist/queue/LocalTransactionsSyncQueue.d.ts +10 -9
- package/dist/queue/LocalTransactionsSyncQueue.d.ts.map +1 -1
- package/dist/queue/LocalTransactionsSyncQueue.js +53 -32
- package/dist/queue/LocalTransactionsSyncQueue.js.map +1 -1
- package/dist/storage/knownState.js +2 -2
- package/dist/storage/knownState.js.map +1 -1
- package/dist/sync.d.ts +1 -2
- package/dist/sync.d.ts.map +1 -1
- package/dist/sync.js +8 -3
- package/dist/sync.js.map +1 -1
- package/dist/tests/PureJSCrypto.test.js +1 -1
- package/dist/tests/PureJSCrypto.test.js.map +1 -1
- package/dist/tests/StorageApiAsync.test.js +11 -11
- package/dist/tests/StorageApiAsync.test.js.map +1 -1
- package/dist/tests/StorageApiSync.test.js +3 -3
- package/dist/tests/StorageApiSync.test.js.map +1 -1
- package/dist/tests/WasmCrypto.test.js +1 -1
- package/dist/tests/WasmCrypto.test.js.map +1 -1
- package/dist/tests/coPlainText.test.js +13 -14
- package/dist/tests/coPlainText.test.js.map +1 -1
- package/dist/tests/coStream.test.js +12 -12
- package/dist/tests/coStream.test.js.map +1 -1
- package/dist/tests/coValueCore.isCompletelyDownloaded.test.d.ts +2 -0
- package/dist/tests/coValueCore.isCompletelyDownloaded.test.d.ts.map +1 -0
- package/dist/tests/coValueCore.isCompletelyDownloaded.test.js +422 -0
- package/dist/tests/coValueCore.isCompletelyDownloaded.test.js.map +1 -0
- package/dist/tests/coValueCore.isStreaming.test.d.ts +2 -0
- package/dist/tests/coValueCore.isStreaming.test.d.ts.map +1 -0
- package/dist/tests/coValueCore.isStreaming.test.js +232 -0
- package/dist/tests/coValueCore.isStreaming.test.js.map +1 -0
- package/dist/tests/coValueCore.newContentSince.test.d.ts +2 -0
- package/dist/tests/coValueCore.newContentSince.test.d.ts.map +1 -0
- package/dist/tests/coValueCore.newContentSince.test.js +808 -0
- package/dist/tests/coValueCore.newContentSince.test.js.map +1 -0
- package/dist/tests/coreWasm.test.js +2 -2
- package/dist/tests/coreWasm.test.js.map +1 -1
- package/dist/tests/group.childKeyRotation.test.d.ts +2 -0
- package/dist/tests/group.childKeyRotation.test.d.ts.map +1 -0
- package/dist/tests/group.childKeyRotation.test.js +261 -0
- package/dist/tests/group.childKeyRotation.test.js.map +1 -0
- package/dist/tests/group.removeMember.test.js +1 -114
- package/dist/tests/group.removeMember.test.js.map +1 -1
- package/dist/tests/knownState.test.js +83 -11
- package/dist/tests/knownState.test.js.map +1 -1
- package/dist/tests/sync.auth.test.js +6 -6
- package/dist/tests/sync.load.test.js +67 -4
- package/dist/tests/sync.load.test.js.map +1 -1
- package/dist/tests/sync.mesh.test.js +41 -40
- package/dist/tests/sync.mesh.test.js.map +1 -1
- package/dist/tests/sync.peerReconciliation.test.js +1 -1
- package/dist/tests/sync.storage.test.js +29 -28
- package/dist/tests/sync.storage.test.js.map +1 -1
- package/dist/tests/sync.storageAsync.test.js +26 -25
- package/dist/tests/sync.storageAsync.test.js.map +1 -1
- package/dist/tests/sync.upload.test.js +96 -40
- package/dist/tests/sync.upload.test.js.map +1 -1
- package/dist/tests/testUtils.d.ts +12 -8
- package/dist/tests/testUtils.d.ts.map +1 -1
- package/dist/tests/testUtils.js +39 -8
- package/dist/tests/testUtils.js.map +1 -1
- package/package.json +3 -3
- package/src/SyncStateManager.ts +5 -2
- package/src/coValueCore/SessionMap.ts +39 -12
- package/src/coValueCore/coValueCore.ts +81 -66
- package/src/coValueCore/verifiedState.ts +139 -109
- package/src/coValues/group.ts +27 -4
- package/src/knownState.ts +49 -5
- package/src/localNode.ts +7 -5
- package/src/queue/LocalTransactionsSyncQueue.ts +77 -68
- package/src/storage/knownState.ts +2 -2
- package/src/sync.ts +7 -3
- package/src/tests/PureJSCrypto.test.ts +1 -2
- package/src/tests/StorageApiAsync.test.ts +11 -11
- package/src/tests/StorageApiSync.test.ts +3 -3
- package/src/tests/WasmCrypto.test.ts +1 -2
- package/src/tests/coPlainText.test.ts +13 -14
- package/src/tests/coStream.test.ts +12 -12
- package/src/tests/coValueCore.isCompletelyDownloaded.test.ts +590 -0
- package/src/tests/coValueCore.isStreaming.test.ts +353 -0
- package/src/tests/coValueCore.newContentSince.test.ts +966 -0
- package/src/tests/coreWasm.test.ts +2 -2
- package/src/tests/group.childKeyRotation.test.ts +431 -0
- package/src/tests/group.removeMember.test.ts +1 -184
- package/src/tests/knownState.test.ts +108 -11
- package/src/tests/sync.auth.test.ts +6 -6
- package/src/tests/sync.load.test.ts +79 -4
- package/src/tests/sync.mesh.test.ts +41 -40
- package/src/tests/sync.peerReconciliation.test.ts +1 -1
- package/src/tests/sync.storage.test.ts +29 -28
- package/src/tests/sync.storageAsync.test.ts +26 -25
- package/src/tests/sync.upload.test.ts +106 -40
- package/src/tests/testUtils.ts +43 -9
|
@@ -254,63 +254,77 @@ export class CoValueCore {
|
|
|
254
254
|
return this.hasVerifiedContent();
|
|
255
255
|
}
|
|
256
256
|
|
|
257
|
-
|
|
258
|
-
* True if the coValue is completely downloaded:
|
|
259
|
-
* - the current coValue is available and not streaming
|
|
260
|
-
* - the group is available and not streaming
|
|
261
|
-
* - TODO: all the parent groups are available and not streaming
|
|
262
|
-
*/
|
|
263
|
-
isCompletelyDownloaded(): this is AvailableCoValueCore {
|
|
257
|
+
isCompletelyDownloaded(): boolean {
|
|
264
258
|
if (!this.hasVerifiedContent()) {
|
|
265
259
|
return false;
|
|
266
260
|
}
|
|
267
261
|
|
|
268
|
-
if (this.
|
|
262
|
+
if (this.isStreaming()) {
|
|
269
263
|
return false;
|
|
270
264
|
}
|
|
271
265
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
// TODO: Group coValues should be completely downloaded when all their parent groups are completely downloaded
|
|
275
|
-
if (!group) {
|
|
276
|
-
return true;
|
|
266
|
+
if (this.incompleteDependencies.size > 0) {
|
|
267
|
+
return false;
|
|
277
268
|
}
|
|
278
269
|
|
|
279
|
-
return
|
|
270
|
+
return true;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
isStreaming() {
|
|
274
|
+
return this.verified?.isStreaming() ?? false;
|
|
280
275
|
}
|
|
281
276
|
|
|
282
277
|
hasVerifiedContent(): this is AvailableCoValueCore {
|
|
283
278
|
return !!this.verified;
|
|
284
279
|
}
|
|
285
280
|
|
|
281
|
+
/**
|
|
282
|
+
* Returns the CoValue data as NewContentMessage objects, excluding the transactions that are part of the given known state.
|
|
283
|
+
*
|
|
284
|
+
* Used to serialize the CoValue data to send it to peers and storage.
|
|
285
|
+
*/
|
|
286
|
+
newContentSince(
|
|
287
|
+
knownState?: CoValueKnownState,
|
|
288
|
+
): NewContentMessage[] | undefined {
|
|
289
|
+
return this.verified?.newContentSince(knownState);
|
|
290
|
+
}
|
|
291
|
+
|
|
286
292
|
isErroredInPeer(peerId: PeerID) {
|
|
287
293
|
return this.getLoadingStateForPeer(peerId) === "errored";
|
|
288
294
|
}
|
|
289
295
|
|
|
290
|
-
waitFor(
|
|
296
|
+
waitFor(opts: {
|
|
297
|
+
predicate: (value: CoValueCore) => boolean;
|
|
298
|
+
onSuccess: (value: CoValueCore) => void;
|
|
299
|
+
}) {
|
|
300
|
+
const { predicate, onSuccess } = opts;
|
|
301
|
+
this.subscribe((core, unsubscribe) => {
|
|
302
|
+
if (predicate(core)) {
|
|
303
|
+
unsubscribe();
|
|
304
|
+
onSuccess(core);
|
|
305
|
+
}
|
|
306
|
+
}, true);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
waitForAsync(callback: (value: CoValueCore) => boolean) {
|
|
291
310
|
return new Promise<CoValueCore>((resolve) => {
|
|
292
|
-
this.
|
|
293
|
-
if (callback(core)) {
|
|
294
|
-
unsubscribe();
|
|
295
|
-
resolve(core);
|
|
296
|
-
}
|
|
297
|
-
}, true);
|
|
311
|
+
this.waitFor({ predicate: callback, onSuccess: resolve });
|
|
298
312
|
});
|
|
299
313
|
}
|
|
300
314
|
|
|
301
315
|
waitForAvailableOrUnavailable(): Promise<CoValueCore> {
|
|
302
|
-
return this.
|
|
316
|
+
return this.waitForAsync(
|
|
303
317
|
(core) => core.isAvailable() || core.loadingState === "unavailable",
|
|
304
318
|
);
|
|
305
319
|
}
|
|
306
320
|
|
|
307
321
|
waitForAvailable(): Promise<CoValueCore> {
|
|
308
|
-
return this.
|
|
322
|
+
return this.waitForAsync((core) => core.isAvailable());
|
|
309
323
|
}
|
|
310
324
|
|
|
311
325
|
waitForFullStreaming(): Promise<CoValueCore> {
|
|
312
|
-
return this.
|
|
313
|
-
(core) => core.isAvailable() && !core.
|
|
326
|
+
return this.waitForAsync(
|
|
327
|
+
(core) => core.isAvailable() && !core.isStreaming(),
|
|
314
328
|
);
|
|
315
329
|
}
|
|
316
330
|
|
|
@@ -368,7 +382,7 @@ export class CoValueCore {
|
|
|
368
382
|
|
|
369
383
|
missingDependencies = new Set<RawCoID>();
|
|
370
384
|
|
|
371
|
-
|
|
385
|
+
isCircularDependency(dependency: CoValueCore) {
|
|
372
386
|
const visited = new Set<RawCoID>();
|
|
373
387
|
const stack = [dependency];
|
|
374
388
|
|
|
@@ -381,7 +395,7 @@ export class CoValueCore {
|
|
|
381
395
|
|
|
382
396
|
visited.add(current.id);
|
|
383
397
|
|
|
384
|
-
for (const dependency of current.
|
|
398
|
+
for (const dependency of current.dependencies) {
|
|
385
399
|
if (dependency === this.id) {
|
|
386
400
|
return true;
|
|
387
401
|
}
|
|
@@ -395,14 +409,6 @@ export class CoValueCore {
|
|
|
395
409
|
return false;
|
|
396
410
|
}
|
|
397
411
|
|
|
398
|
-
markDependencyAvailable(dependency: RawCoID) {
|
|
399
|
-
this.missingDependencies.delete(dependency);
|
|
400
|
-
|
|
401
|
-
if (this.missingDependencies.size === 0) {
|
|
402
|
-
this.scheduleNotifyUpdate();
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
|
|
406
412
|
newContentQueue: {
|
|
407
413
|
msg: NewContentMessage;
|
|
408
414
|
from: PeerState | "storage" | "import";
|
|
@@ -422,17 +428,16 @@ export class CoValueCore {
|
|
|
422
428
|
return;
|
|
423
429
|
}
|
|
424
430
|
|
|
425
|
-
this.
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
431
|
+
this.waitFor({
|
|
432
|
+
predicate: (core) => !core.hasMissingDependencies(),
|
|
433
|
+
onSuccess: () => {
|
|
429
434
|
const enqueuedNewContent = this.newContentQueue;
|
|
430
435
|
this.newContentQueue = [];
|
|
431
436
|
|
|
432
437
|
for (const { msg, from } of enqueuedNewContent) {
|
|
433
438
|
this.node.syncManager.handleNewContent(msg, from);
|
|
434
439
|
}
|
|
435
|
-
}
|
|
440
|
+
},
|
|
436
441
|
});
|
|
437
442
|
}
|
|
438
443
|
|
|
@@ -764,6 +769,8 @@ export class CoValueCore {
|
|
|
764
769
|
|
|
765
770
|
let result: { signature: Signature; transaction: Transaction };
|
|
766
771
|
|
|
772
|
+
const knownStateBefore = this.knownState();
|
|
773
|
+
|
|
767
774
|
if (privacy === "private") {
|
|
768
775
|
const { secret: keySecret, id: keyID } = this.getCurrentReadKey();
|
|
769
776
|
|
|
@@ -790,7 +797,7 @@ export class CoValueCore {
|
|
|
790
797
|
);
|
|
791
798
|
}
|
|
792
799
|
|
|
793
|
-
const { transaction
|
|
800
|
+
const { transaction } = result;
|
|
794
801
|
|
|
795
802
|
// Assign pre-parsed meta and changes to skip the parse/decrypt operation when loading
|
|
796
803
|
// this transaction in the current content
|
|
@@ -798,9 +805,6 @@ export class CoValueCore {
|
|
|
798
805
|
|
|
799
806
|
this.node.syncManager.recordTransactionsSize([transaction], "local");
|
|
800
807
|
|
|
801
|
-
const session = this.verified.sessions.get(sessionID);
|
|
802
|
-
const txIdx = session ? session.transactions.length - 1 : 0;
|
|
803
|
-
|
|
804
808
|
this.resetKnownStateCache();
|
|
805
809
|
this.processNewTransactions();
|
|
806
810
|
this.addDependenciesFromNewTransaction(transaction);
|
|
@@ -808,13 +812,7 @@ export class CoValueCore {
|
|
|
808
812
|
// force immediate notification because local updates may come from the UI
|
|
809
813
|
// where we need synchronous updates
|
|
810
814
|
this.notifyUpdate();
|
|
811
|
-
this.node.syncManager.syncLocalTransaction(
|
|
812
|
-
this.verified,
|
|
813
|
-
transaction,
|
|
814
|
-
sessionID,
|
|
815
|
-
signature,
|
|
816
|
-
txIdx,
|
|
817
|
-
);
|
|
815
|
+
this.node.syncManager.syncLocalTransaction(this.verified, knownStateBefore);
|
|
818
816
|
|
|
819
817
|
return true;
|
|
820
818
|
}
|
|
@@ -1163,30 +1161,47 @@ export class CoValueCore {
|
|
|
1163
1161
|
}
|
|
1164
1162
|
|
|
1165
1163
|
dependencies: Set<RawCoID> = new Set();
|
|
1164
|
+
incompleteDependencies: Set<RawCoID> = new Set();
|
|
1166
1165
|
private addDependency(dependency: RawCoID) {
|
|
1167
|
-
if (this.dependencies.has(dependency)) {
|
|
1168
|
-
return true;
|
|
1169
|
-
}
|
|
1170
|
-
|
|
1171
|
-
this.dependencies.add(dependency);
|
|
1172
|
-
|
|
1173
1166
|
const dependencyCoValue = this.node.getCoValue(dependency);
|
|
1174
1167
|
|
|
1175
|
-
if (
|
|
1176
|
-
|
|
1168
|
+
if (
|
|
1169
|
+
this.isCircularDependency(dependencyCoValue) ||
|
|
1170
|
+
this.dependencies.has(dependency)
|
|
1171
|
+
) {
|
|
1172
|
+
return;
|
|
1177
1173
|
}
|
|
1178
1174
|
|
|
1175
|
+
this.dependencies.add(dependency);
|
|
1179
1176
|
dependencyCoValue.addDependant(this.id);
|
|
1180
1177
|
|
|
1178
|
+
if (!dependencyCoValue.isCompletelyDownloaded()) {
|
|
1179
|
+
this.incompleteDependencies.add(dependencyCoValue.id);
|
|
1180
|
+
dependencyCoValue.waitFor({
|
|
1181
|
+
predicate: (dependencyCoValue) =>
|
|
1182
|
+
dependencyCoValue.isCompletelyDownloaded(),
|
|
1183
|
+
onSuccess: () => {
|
|
1184
|
+
this.incompleteDependencies.delete(dependencyCoValue.id);
|
|
1185
|
+
if (this.incompleteDependencies.size === 0) {
|
|
1186
|
+
// We want this to propagate immediately in the dependency chain
|
|
1187
|
+
this.notifyUpdate();
|
|
1188
|
+
}
|
|
1189
|
+
},
|
|
1190
|
+
});
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1181
1193
|
if (!dependencyCoValue.isAvailable()) {
|
|
1182
|
-
this.missingDependencies.add(
|
|
1183
|
-
dependencyCoValue.
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
this.
|
|
1187
|
-
|
|
1194
|
+
this.missingDependencies.add(dependencyCoValue.id);
|
|
1195
|
+
dependencyCoValue.waitFor({
|
|
1196
|
+
predicate: (dependencyCoValue) => dependencyCoValue.isAvailable(),
|
|
1197
|
+
onSuccess: () => {
|
|
1198
|
+
this.missingDependencies.delete(dependencyCoValue.id);
|
|
1199
|
+
|
|
1200
|
+
if (this.missingDependencies.size === 0) {
|
|
1201
|
+
this.notifyUpdate(); // We want this to propagate immediately
|
|
1202
|
+
}
|
|
1203
|
+
},
|
|
1188
1204
|
});
|
|
1189
|
-
return false;
|
|
1190
1205
|
}
|
|
1191
1206
|
}
|
|
1192
1207
|
|
|
@@ -4,6 +4,8 @@ import {
|
|
|
4
4
|
createContentMessage,
|
|
5
5
|
exceedsRecommendedSize,
|
|
6
6
|
getTransactionSize,
|
|
7
|
+
addTransactionToContentMessage,
|
|
8
|
+
knownStateFromContent,
|
|
7
9
|
} from "../coValueContentMessage.js";
|
|
8
10
|
import {
|
|
9
11
|
CryptoProvider,
|
|
@@ -19,9 +21,13 @@ import { JsonObject, JsonValue } from "../jsonValue.js";
|
|
|
19
21
|
import { PermissionsDef as RulesetDef } from "../permissions.js";
|
|
20
22
|
import { NewContentMessage } from "../sync.js";
|
|
21
23
|
import { TryAddTransactionsError } from "./coValueCore.js";
|
|
22
|
-
import {
|
|
24
|
+
import { SessionMap } from "./SessionMap.js";
|
|
23
25
|
import { ControlledAccountOrAgent } from "../coValues/account.js";
|
|
24
|
-
import {
|
|
26
|
+
import {
|
|
27
|
+
CoValueKnownState,
|
|
28
|
+
getKnownStateToSend,
|
|
29
|
+
KnownStateSessions,
|
|
30
|
+
} from "../knownState.js";
|
|
25
31
|
|
|
26
32
|
export type CoValueHeader = {
|
|
27
33
|
type: AnyRawCoValue["type"];
|
|
@@ -55,7 +61,6 @@ export class VerifiedState {
|
|
|
55
61
|
readonly crypto: CryptoProvider;
|
|
56
62
|
readonly header: CoValueHeader;
|
|
57
63
|
readonly sessions: SessionMap;
|
|
58
|
-
private _cachedNewContentSinceEmpty: NewContentMessage[] | undefined;
|
|
59
64
|
public lastAccessed: number | undefined;
|
|
60
65
|
public branchSourceId?: RawCoID;
|
|
61
66
|
public branchName?: string;
|
|
@@ -98,10 +103,6 @@ export class VerifiedState {
|
|
|
98
103
|
skipVerify,
|
|
99
104
|
);
|
|
100
105
|
|
|
101
|
-
if (result.isOk()) {
|
|
102
|
-
this._cachedNewContentSinceEmpty = undefined;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
106
|
return result;
|
|
106
107
|
}
|
|
107
108
|
|
|
@@ -120,8 +121,6 @@ export class VerifiedState {
|
|
|
120
121
|
madeAt,
|
|
121
122
|
);
|
|
122
123
|
|
|
123
|
-
this._cachedNewContentSinceEmpty = undefined;
|
|
124
|
-
|
|
125
124
|
return result;
|
|
126
125
|
}
|
|
127
126
|
|
|
@@ -144,8 +143,6 @@ export class VerifiedState {
|
|
|
144
143
|
madeAt,
|
|
145
144
|
);
|
|
146
145
|
|
|
147
|
-
this._cachedNewContentSinceEmpty = undefined;
|
|
148
|
-
|
|
149
146
|
return result;
|
|
150
147
|
}
|
|
151
148
|
|
|
@@ -160,119 +157,157 @@ export class VerifiedState {
|
|
|
160
157
|
);
|
|
161
158
|
}
|
|
162
159
|
|
|
160
|
+
setStreamingKnownState(streamingKnownState: KnownStateSessions) {
|
|
161
|
+
this.sessions.setStreamingKnownState(streamingKnownState);
|
|
162
|
+
}
|
|
163
|
+
|
|
163
164
|
newContentSince(
|
|
164
165
|
knownState: CoValueKnownState | undefined,
|
|
166
|
+
opts?: {
|
|
167
|
+
skipExpectContentUntil?: boolean;
|
|
168
|
+
},
|
|
165
169
|
): NewContentMessage[] | undefined {
|
|
166
|
-
const isKnownStateEmpty = !knownState?.header && !knownState?.sessions;
|
|
167
|
-
|
|
168
|
-
if (isKnownStateEmpty && this._cachedNewContentSinceEmpty) {
|
|
169
|
-
return this._cachedNewContentSinceEmpty;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
170
|
let currentPiece: NewContentMessage = createContentMessage(
|
|
173
171
|
this.id,
|
|
174
172
|
this.header,
|
|
175
|
-
|
|
173
|
+
false,
|
|
176
174
|
);
|
|
177
|
-
|
|
178
|
-
const pieces = [currentPiece];
|
|
179
|
-
|
|
180
|
-
const sentState: CoValueKnownState["sessions"] = {};
|
|
181
|
-
|
|
175
|
+
const pieces: NewContentMessage[] = [currentPiece];
|
|
182
176
|
let pieceSize = 0;
|
|
183
177
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
178
|
+
const startNewPiece = () => {
|
|
179
|
+
currentPiece = createContentMessage(this.id, this.header, false);
|
|
180
|
+
pieces.push(currentPiece);
|
|
181
|
+
pieceSize = 0;
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
const moveSessionContentToNewPiece = (sessionID: SessionID) => {
|
|
185
|
+
const sessionContent = currentPiece.new[sessionID];
|
|
186
|
+
|
|
187
|
+
if (!sessionContent) {
|
|
188
|
+
throw new Error("Session content not found", {
|
|
189
|
+
cause: {
|
|
190
|
+
sessionID,
|
|
191
|
+
currentPiece,
|
|
192
|
+
},
|
|
193
|
+
});
|
|
189
194
|
}
|
|
190
|
-
const sessionsTodo = sessionsTodoAgain ?? this.sessions.keys();
|
|
191
|
-
|
|
192
|
-
for (const sessionIDKey of sessionsTodo) {
|
|
193
|
-
const sessionID = sessionIDKey as SessionID;
|
|
194
|
-
const log = this.sessions.get(sessionID)!;
|
|
195
|
-
const knownStateForSessionID = knownState?.sessions[sessionID];
|
|
196
|
-
const sentStateForSessionID = sentState[sessionID];
|
|
197
|
-
const nextKnownSignatureIdx = getNextKnownSignatureIdx(
|
|
198
|
-
log,
|
|
199
|
-
knownStateForSessionID,
|
|
200
|
-
sentStateForSessionID,
|
|
201
|
-
);
|
|
202
|
-
|
|
203
|
-
const firstNewTxIdx =
|
|
204
|
-
sentStateForSessionID ?? knownStateForSessionID ?? 0;
|
|
205
|
-
const afterLastNewTxIdx =
|
|
206
|
-
nextKnownSignatureIdx === undefined
|
|
207
|
-
? log.transactions.length
|
|
208
|
-
: nextKnownSignatureIdx + 1;
|
|
209
|
-
|
|
210
|
-
const nNewTx = Math.max(0, afterLastNewTxIdx - firstNewTxIdx);
|
|
211
|
-
|
|
212
|
-
if (nNewTx === 0) {
|
|
213
|
-
sessionsTodoAgain?.delete(sessionID);
|
|
214
|
-
continue;
|
|
215
|
-
}
|
|
216
195
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
196
|
+
delete currentPiece.new[sessionID];
|
|
197
|
+
|
|
198
|
+
const newPiece = createContentMessage(this.id, this.header, false);
|
|
199
|
+
newPiece.new[sessionID] = sessionContent;
|
|
200
|
+
|
|
201
|
+
// Insert the new piece before the current piece, to ensure that the order of the new transactions is preserved
|
|
202
|
+
pieces.splice(pieces.length - 1, 0, newPiece);
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
const sessionSent = knownState?.sessions;
|
|
206
|
+
|
|
207
|
+
for (const [sessionID, log] of this.sessions.sessions) {
|
|
208
|
+
const startFrom = sessionSent?.[sessionID] ?? 0;
|
|
209
|
+
|
|
210
|
+
let currentSessionSize = 0;
|
|
211
|
+
|
|
212
|
+
for (let txIdx = startFrom; txIdx < log.transactions.length; txIdx++) {
|
|
213
|
+
const isLastItem = txIdx === log.transactions.length - 1;
|
|
214
|
+
const tx = log.transactions[txIdx]!;
|
|
215
|
+
|
|
216
|
+
currentSessionSize += getTransactionSize(tx);
|
|
217
|
+
|
|
218
|
+
const signatureAfter = log.signatureAfter[txIdx];
|
|
219
|
+
|
|
220
|
+
if (signatureAfter) {
|
|
221
|
+
addTransactionToContentMessage(
|
|
222
|
+
currentPiece,
|
|
223
|
+
tx,
|
|
224
|
+
sessionID,
|
|
225
|
+
signatureAfter,
|
|
226
|
+
txIdx,
|
|
227
|
+
);
|
|
228
|
+
// When we meet a signatureAfter it means that the transaction log exceeds the recommended size
|
|
229
|
+
// so we move the session content to a dedicated piece, because it must be sent in a standalone piece
|
|
230
|
+
moveSessionContentToNewPiece(sessionID);
|
|
231
|
+
currentSessionSize = 0;
|
|
232
|
+
} else if (isLastItem) {
|
|
233
|
+
if (!log.lastSignature) {
|
|
234
|
+
throw new Error(
|
|
235
|
+
"All the SessionLogs sent must have a lastSignature",
|
|
236
|
+
{
|
|
237
|
+
cause: log,
|
|
238
|
+
},
|
|
239
|
+
);
|
|
220
240
|
}
|
|
221
|
-
sessionsTodoAgain.add(sessionID);
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
const oldPieceSize = pieceSize;
|
|
225
|
-
for (let txIdx = firstNewTxIdx; txIdx < afterLastNewTxIdx; txIdx++) {
|
|
226
|
-
const tx = log.transactions[txIdx]!;
|
|
227
|
-
pieceSize += getTransactionSize(tx);
|
|
228
|
-
}
|
|
229
241
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
242
|
+
addTransactionToContentMessage(
|
|
243
|
+
currentPiece,
|
|
244
|
+
tx,
|
|
245
|
+
sessionID,
|
|
246
|
+
log.lastSignature,
|
|
247
|
+
txIdx,
|
|
248
|
+
);
|
|
249
|
+
|
|
250
|
+
// If the current session size already exceeds the recommended size, we move the session content to a dedicated piece
|
|
251
|
+
if (exceedsRecommendedSize(currentSessionSize)) {
|
|
252
|
+
assertLastSignature(sessionID, currentPiece);
|
|
253
|
+
moveSessionContentToNewPiece(sessionID);
|
|
254
|
+
} else if (exceedsRecommendedSize(pieceSize, currentSessionSize)) {
|
|
255
|
+
assertLastSignature(sessionID, currentPiece);
|
|
256
|
+
startNewPiece();
|
|
257
|
+
} else {
|
|
258
|
+
pieceSize += currentSessionSize;
|
|
234
259
|
}
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
260
|
+
} else {
|
|
261
|
+
// Unsafely add the transaction to the content message, without a signature because we don't have one for this session
|
|
262
|
+
// Checks and assertions are enforced in this function to avoid that a content message gets out without a signature
|
|
263
|
+
const signature = undefined as Signature | undefined;
|
|
264
|
+
addTransactionToContentMessage(
|
|
265
|
+
currentPiece,
|
|
266
|
+
tx,
|
|
267
|
+
sessionID,
|
|
268
|
+
signature!,
|
|
269
|
+
txIdx,
|
|
270
|
+
);
|
|
239
271
|
}
|
|
272
|
+
}
|
|
240
273
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
sessionEntry = {
|
|
244
|
-
after: sentStateForSessionID ?? knownStateForSessionID ?? 0,
|
|
245
|
-
newTransactions: [],
|
|
246
|
-
lastSignature: "WILL_BE_REPLACED" as Signature,
|
|
247
|
-
};
|
|
248
|
-
currentPiece.new[sessionID] = sessionEntry;
|
|
249
|
-
}
|
|
274
|
+
assertLastSignature(sessionID, currentPiece);
|
|
275
|
+
}
|
|
250
276
|
|
|
251
|
-
|
|
252
|
-
const tx = log.transactions[txIdx]!;
|
|
253
|
-
sessionEntry.newTransactions.push(tx);
|
|
254
|
-
}
|
|
277
|
+
const firstPiece = pieces[0];
|
|
255
278
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
279
|
+
if (!firstPiece) {
|
|
280
|
+
throw new Error("First piece not found", {
|
|
281
|
+
cause: pieces,
|
|
282
|
+
});
|
|
283
|
+
}
|
|
260
284
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
285
|
+
const includeHeader = !knownState?.header;
|
|
286
|
+
|
|
287
|
+
if (includeHeader) {
|
|
288
|
+
firstPiece.header = this.header;
|
|
264
289
|
}
|
|
265
290
|
|
|
266
291
|
const piecesWithContent = pieces.filter(
|
|
267
|
-
(piece) => Object.keys(piece.new).length > 0
|
|
292
|
+
(piece) => piece.header || Object.keys(piece.new).length > 0,
|
|
268
293
|
);
|
|
269
294
|
|
|
270
|
-
if (piecesWithContent.length
|
|
271
|
-
|
|
295
|
+
if (piecesWithContent.length > 1 || this.isStreaming()) {
|
|
296
|
+
// Flag that more content is coming
|
|
297
|
+
if (knownState) {
|
|
298
|
+
firstPiece.expectContentUntil = getKnownStateToSend(
|
|
299
|
+
this.knownStateWithStreaming().sessions,
|
|
300
|
+
knownState.sessions,
|
|
301
|
+
);
|
|
302
|
+
} else {
|
|
303
|
+
firstPiece.expectContentUntil = {
|
|
304
|
+
...this.knownStateWithStreaming().sessions,
|
|
305
|
+
};
|
|
306
|
+
}
|
|
272
307
|
}
|
|
273
308
|
|
|
274
|
-
if (
|
|
275
|
-
|
|
309
|
+
if (piecesWithContent.length === 0) {
|
|
310
|
+
return undefined;
|
|
276
311
|
}
|
|
277
312
|
|
|
278
313
|
return piecesWithContent;
|
|
@@ -307,15 +342,10 @@ export class VerifiedState {
|
|
|
307
342
|
}
|
|
308
343
|
}
|
|
309
344
|
|
|
310
|
-
function
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
)
|
|
315
|
-
|
|
316
|
-
.map(Number)
|
|
317
|
-
.sort((a, b) => a - b)
|
|
318
|
-
.find(
|
|
319
|
-
(idx) => idx >= (sentStateForSessionID ?? knownStateForSessionID ?? -1),
|
|
320
|
-
);
|
|
345
|
+
function assertLastSignature(sessionID: SessionID, content: NewContentMessage) {
|
|
346
|
+
if (content.new[sessionID] && !content.new[sessionID].lastSignature) {
|
|
347
|
+
throw new Error("The SessionContent sent must have a lastSignature", {
|
|
348
|
+
cause: content.new[sessionID],
|
|
349
|
+
});
|
|
350
|
+
}
|
|
321
351
|
}
|
package/src/coValues/group.ts
CHANGED
|
@@ -123,6 +123,13 @@ function healMissingKeyForEveryone(group: RawGroup) {
|
|
|
123
123
|
}
|
|
124
124
|
|
|
125
125
|
function needsKeyRotation(group: RawGroup) {
|
|
126
|
+
const myRole = group.myRole();
|
|
127
|
+
|
|
128
|
+
// Checking only direct membership because inside the migrations we can't navigate the parent groups
|
|
129
|
+
if (myRole !== "admin" && myRole !== "manager") {
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
|
|
126
133
|
const currentReadKeyId = group.get("readKey");
|
|
127
134
|
|
|
128
135
|
if (!currentReadKeyId) {
|
|
@@ -190,11 +197,27 @@ export class RawGroup<
|
|
|
190
197
|
) {
|
|
191
198
|
super(core, options);
|
|
192
199
|
this.crypto = core.node.crypto;
|
|
200
|
+
this.migrate();
|
|
201
|
+
}
|
|
193
202
|
|
|
194
|
-
|
|
195
|
-
if (core.isGroup()
|
|
196
|
-
|
|
203
|
+
migrate() {
|
|
204
|
+
if (!this.core.isGroup()) {
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const runMigrations = () => {
|
|
209
|
+
rotateReadKeyIfNeeded(this);
|
|
197
210
|
healMissingKeyForEveryone(this);
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
// We need the group and their parents to be completely downloaded to correctly handle the migrations
|
|
214
|
+
if (!this.core.isCompletelyDownloaded()) {
|
|
215
|
+
this.core.waitFor({
|
|
216
|
+
predicate: (core) => core.isCompletelyDownloaded(),
|
|
217
|
+
onSuccess: runMigrations,
|
|
218
|
+
});
|
|
219
|
+
} else {
|
|
220
|
+
runMigrations();
|
|
198
221
|
}
|
|
199
222
|
}
|
|
200
223
|
|
|
@@ -318,7 +341,7 @@ export class RawGroup<
|
|
|
318
341
|
* @category 1. Role reading
|
|
319
342
|
*/
|
|
320
343
|
myRole(): Role | undefined {
|
|
321
|
-
return this.roleOfInternal(this.core.node.
|
|
344
|
+
return this.roleOfInternal(this.core.node.getCurrentAccountOrAgentID());
|
|
322
345
|
}
|
|
323
346
|
|
|
324
347
|
/**
|