cojson 0.18.37 → 0.19.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/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +16 -0
- package/dist/coValueCore/SessionMap.d.ts +5 -3
- package/dist/coValueCore/SessionMap.d.ts.map +1 -1
- package/dist/coValueCore/SessionMap.js +19 -9
- package/dist/coValueCore/SessionMap.js.map +1 -1
- package/dist/coValueCore/coValueCore.d.ts +5 -2
- package/dist/coValueCore/coValueCore.d.ts.map +1 -1
- package/dist/coValueCore/coValueCore.js +24 -4
- package/dist/coValueCore/coValueCore.js.map +1 -1
- package/dist/coValueCore/verifiedState.d.ts +1 -3
- package/dist/coValueCore/verifiedState.d.ts.map +1 -1
- package/dist/coValueCore/verifiedState.js +10 -14
- package/dist/coValueCore/verifiedState.js.map +1 -1
- package/dist/exports.d.ts +3 -3
- package/dist/exports.d.ts.map +1 -1
- package/dist/exports.js +3 -3
- package/dist/exports.js.map +1 -1
- package/dist/permissions.d.ts +0 -1
- package/dist/permissions.d.ts.map +1 -1
- package/dist/permissions.js +34 -58
- package/dist/permissions.js.map +1 -1
- package/dist/queue/LocalTransactionsSyncQueue.d.ts.map +1 -1
- package/dist/queue/LocalTransactionsSyncQueue.js +1 -18
- package/dist/queue/LocalTransactionsSyncQueue.js.map +1 -1
- package/dist/sync.d.ts.map +1 -1
- package/dist/sync.js +8 -0
- package/dist/sync.js.map +1 -1
- package/dist/tests/StorageApiAsync.test.js +8 -8
- package/dist/tests/coValueCore.isStreaming.test.js +30 -40
- package/dist/tests/coValueCore.isStreaming.test.js.map +1 -1
- package/dist/tests/coValueCore.newContentSince.test.js +242 -132
- package/dist/tests/coValueCore.newContentSince.test.js.map +1 -1
- package/dist/tests/sync.auth.test.js +6 -6
- package/dist/tests/sync.load.test.js +11 -75
- package/dist/tests/sync.load.test.js.map +1 -1
- package/dist/tests/sync.mesh.test.js +2 -2
- package/dist/tests/sync.storage.test.js +22 -48
- package/dist/tests/sync.storage.test.js.map +1 -1
- package/dist/tests/sync.storageAsync.test.js +121 -71
- package/dist/tests/sync.storageAsync.test.js.map +1 -1
- package/dist/tests/sync.upload.test.js +11 -75
- package/dist/tests/sync.upload.test.js.map +1 -1
- package/dist/tests/testUtils.d.ts +4 -1
- package/dist/tests/testUtils.d.ts.map +1 -1
- package/dist/tests/testUtils.js +11 -0
- package/dist/tests/testUtils.js.map +1 -1
- package/package.json +3 -3
- package/src/coValueCore/SessionMap.ts +25 -15
- package/src/coValueCore/coValueCore.ts +32 -2
- package/src/coValueCore/verifiedState.ts +11 -20
- package/src/exports.ts +3 -2
- package/src/permissions.ts +34 -66
- package/src/queue/LocalTransactionsSyncQueue.ts +1 -20
- package/src/sync.ts +9 -0
- package/src/tests/StorageApiAsync.test.ts +8 -8
- package/src/tests/coValueCore.isStreaming.test.ts +84 -91
- package/src/tests/coValueCore.newContentSince.test.ts +246 -141
- package/src/tests/sync.auth.test.ts +6 -6
- package/src/tests/sync.load.test.ts +11 -79
- package/src/tests/sync.mesh.test.ts +2 -2
- package/src/tests/sync.storage.test.ts +22 -51
- package/src/tests/sync.storageAsync.test.ts +159 -76
- package/src/tests/sync.upload.test.ts +11 -78
- package/src/tests/testUtils.ts +16 -0
package/src/permissions.ts
CHANGED
|
@@ -18,7 +18,6 @@ import {
|
|
|
18
18
|
getParentGroupId,
|
|
19
19
|
} from "./ids.js";
|
|
20
20
|
import { JsonValue } from "./jsonValue.js";
|
|
21
|
-
import { logger } from "./logger.js";
|
|
22
21
|
import { expectGroup } from "./typeUtils/expectGroup.js";
|
|
23
22
|
|
|
24
23
|
export type PermissionsDef =
|
|
@@ -71,23 +70,6 @@ function canAdmin(role: Role | undefined): boolean {
|
|
|
71
70
|
return role === "admin" || role === "manager";
|
|
72
71
|
}
|
|
73
72
|
|
|
74
|
-
let logPermissionErrors = true;
|
|
75
|
-
|
|
76
|
-
export function disablePermissionErrors() {
|
|
77
|
-
logPermissionErrors = false;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
function logPermissionError(
|
|
81
|
-
message: string,
|
|
82
|
-
attributes?: Record<string, JsonValue>,
|
|
83
|
-
) {
|
|
84
|
-
if (logPermissionErrors === false) {
|
|
85
|
-
return;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
logger.debug("Permission error: " + message, attributes);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
73
|
export function determineValidTransactions(coValue: CoValueCore): void {
|
|
92
74
|
if (!coValue.isAvailable()) {
|
|
93
75
|
throw new Error("determineValidTransactions CoValue is not available");
|
|
@@ -129,7 +111,10 @@ export function determineValidTransactions(coValue: CoValueCore): void {
|
|
|
129
111
|
);
|
|
130
112
|
|
|
131
113
|
if (!effectiveTransactor) {
|
|
132
|
-
tx.markInvalid(
|
|
114
|
+
tx.markInvalid("Transactor not found in group", {
|
|
115
|
+
transactor: tx.author,
|
|
116
|
+
group: groupAtTime.toJSON(),
|
|
117
|
+
});
|
|
133
118
|
continue;
|
|
134
119
|
}
|
|
135
120
|
|
|
@@ -157,7 +142,10 @@ export function determineValidTransactions(coValue: CoValueCore): void {
|
|
|
157
142
|
transactorRoleAtTxTime !== "writer" &&
|
|
158
143
|
transactorRoleAtTxTime !== "writeOnly"
|
|
159
144
|
) {
|
|
160
|
-
tx.markInvalid(
|
|
145
|
+
tx.markInvalid("Transactor has no write permissions", {
|
|
146
|
+
transactor: tx.author,
|
|
147
|
+
transactorRole: transactorRoleAtTxTime ?? "undefined",
|
|
148
|
+
});
|
|
161
149
|
continue;
|
|
162
150
|
}
|
|
163
151
|
|
|
@@ -263,10 +251,9 @@ function determineValidTransactionsForGroup(
|
|
|
263
251
|
transaction.markValid();
|
|
264
252
|
continue;
|
|
265
253
|
} else {
|
|
266
|
-
|
|
254
|
+
transaction.markInvalid(
|
|
267
255
|
"Only admins can make private transactions in groups",
|
|
268
256
|
);
|
|
269
|
-
transaction.markInvalid();
|
|
270
257
|
continue;
|
|
271
258
|
}
|
|
272
259
|
}
|
|
@@ -286,21 +273,18 @@ function determineValidTransactionsForGroup(
|
|
|
286
273
|
| MapOpPayload<`child_${CoID<RawGroup>}`, CoID<RawGroup>>;
|
|
287
274
|
|
|
288
275
|
if (changes.length !== 1) {
|
|
289
|
-
|
|
290
|
-
transaction.markInvalid();
|
|
276
|
+
transaction.markInvalid("Group transaction must have exactly one change");
|
|
291
277
|
continue;
|
|
292
278
|
}
|
|
293
279
|
|
|
294
280
|
if (change.op !== "set") {
|
|
295
|
-
|
|
296
|
-
transaction.markInvalid();
|
|
281
|
+
transaction.markInvalid("Group transaction must set a role or readKey");
|
|
297
282
|
continue;
|
|
298
283
|
}
|
|
299
284
|
|
|
300
285
|
if (change.key === "readKey") {
|
|
301
286
|
if (!canAdmin(transactorRole)) {
|
|
302
|
-
|
|
303
|
-
transaction.markInvalid();
|
|
287
|
+
transaction.markInvalid("Only admins can set readKeys");
|
|
304
288
|
continue;
|
|
305
289
|
}
|
|
306
290
|
|
|
@@ -308,8 +292,7 @@ function determineValidTransactionsForGroup(
|
|
|
308
292
|
continue;
|
|
309
293
|
} else if (change.key === "profile") {
|
|
310
294
|
if (!canAdmin(transactorRole)) {
|
|
311
|
-
|
|
312
|
-
transaction.markInvalid();
|
|
295
|
+
transaction.markInvalid("Only admins can set profile");
|
|
313
296
|
continue;
|
|
314
297
|
}
|
|
315
298
|
|
|
@@ -317,7 +300,7 @@ function determineValidTransactionsForGroup(
|
|
|
317
300
|
continue;
|
|
318
301
|
} else if (change.key === "root") {
|
|
319
302
|
if (!canAdmin(transactorRole)) {
|
|
320
|
-
|
|
303
|
+
transaction.markInvalid("Only admins can set root");
|
|
321
304
|
continue;
|
|
322
305
|
}
|
|
323
306
|
|
|
@@ -337,8 +320,7 @@ function determineValidTransactionsForGroup(
|
|
|
337
320
|
transactorRole !== "writeOnlyInvite" &&
|
|
338
321
|
!isOwnWriteKeyRevelation(change.key, transactor, writeOnlyKeys)
|
|
339
322
|
) {
|
|
340
|
-
|
|
341
|
-
transaction.markInvalid();
|
|
323
|
+
transaction.markInvalid("Only admins and managers can reveal keys");
|
|
342
324
|
continue;
|
|
343
325
|
}
|
|
344
326
|
|
|
@@ -346,10 +328,9 @@ function determineValidTransactionsForGroup(
|
|
|
346
328
|
continue;
|
|
347
329
|
} else if (isParentExtension(change.key)) {
|
|
348
330
|
if (!canAdmin(transactorRole)) {
|
|
349
|
-
|
|
331
|
+
transaction.markInvalid(
|
|
350
332
|
"Only admins and managers can set parent extensions",
|
|
351
333
|
);
|
|
352
|
-
transaction.markInvalid();
|
|
353
334
|
continue;
|
|
354
335
|
}
|
|
355
336
|
|
|
@@ -361,16 +342,14 @@ function determineValidTransactionsForGroup(
|
|
|
361
342
|
);
|
|
362
343
|
|
|
363
344
|
if (!parentGroupCore.isGroup()) {
|
|
364
|
-
|
|
365
|
-
transaction.markInvalid();
|
|
345
|
+
transaction.markInvalid("Parent group is not a group");
|
|
366
346
|
continue;
|
|
367
347
|
}
|
|
368
348
|
|
|
369
349
|
const parentGroup = expectGroup(parentGroupCore.getCurrentContent());
|
|
370
350
|
|
|
371
351
|
if (isSelfExtension(coValue, parentGroup)) {
|
|
372
|
-
|
|
373
|
-
transaction.markInvalid();
|
|
352
|
+
transaction.markInvalid("Parent group is a circular dependency");
|
|
374
353
|
continue;
|
|
375
354
|
}
|
|
376
355
|
|
|
@@ -385,8 +364,7 @@ function determineValidTransactionsForGroup(
|
|
|
385
364
|
transaction.markValid();
|
|
386
365
|
continue;
|
|
387
366
|
} else if (isChildExtension(change.key)) {
|
|
388
|
-
|
|
389
|
-
transaction.markInvalid();
|
|
367
|
+
transaction.markInvalid("Child extensions are not allowed anymore");
|
|
390
368
|
continue;
|
|
391
369
|
} else if (isWriteKeyForMember(change.key)) {
|
|
392
370
|
const memberKey = getAccountOrAgentFromWriteKeyForMember(change.key);
|
|
@@ -397,8 +375,7 @@ function determineValidTransactionsForGroup(
|
|
|
397
375
|
transactorRole !== "writeOnlyInvite" &&
|
|
398
376
|
memberKey !== transactor
|
|
399
377
|
) {
|
|
400
|
-
|
|
401
|
-
transaction.markInvalid();
|
|
378
|
+
transaction.markInvalid("Only admins and managers can set writeKeys");
|
|
402
379
|
continue;
|
|
403
380
|
}
|
|
404
381
|
|
|
@@ -413,10 +390,9 @@ function determineValidTransactionsForGroup(
|
|
|
413
390
|
* blocking them from accessing the group.ß
|
|
414
391
|
*/
|
|
415
392
|
if (writeKeys.has(change.key) && !canAdmin(transactorRole)) {
|
|
416
|
-
|
|
393
|
+
transaction.markInvalid(
|
|
417
394
|
"Write key already exists and can't be overridden by invite",
|
|
418
395
|
);
|
|
419
|
-
transaction.markInvalid();
|
|
420
396
|
continue;
|
|
421
397
|
}
|
|
422
398
|
|
|
@@ -442,8 +418,7 @@ function determineValidTransactionsForGroup(
|
|
|
442
418
|
assignedRole !== "readerInvite" &&
|
|
443
419
|
assignedRole !== "writeOnlyInvite"
|
|
444
420
|
) {
|
|
445
|
-
|
|
446
|
-
transaction.markInvalid();
|
|
421
|
+
transaction.markInvalid("Group transaction must set a valid role");
|
|
447
422
|
continue;
|
|
448
423
|
}
|
|
449
424
|
|
|
@@ -456,10 +431,9 @@ function determineValidTransactionsForGroup(
|
|
|
456
431
|
assignedRole === "revoked"
|
|
457
432
|
)
|
|
458
433
|
) {
|
|
459
|
-
|
|
434
|
+
transaction.markInvalid(
|
|
460
435
|
"Everyone can only be set to reader, writer, writeOnly or revoked",
|
|
461
436
|
);
|
|
462
|
-
transaction.markInvalid();
|
|
463
437
|
continue;
|
|
464
438
|
}
|
|
465
439
|
|
|
@@ -474,11 +448,6 @@ function determineValidTransactionsForGroup(
|
|
|
474
448
|
transaction.markValid();
|
|
475
449
|
}
|
|
476
450
|
|
|
477
|
-
function markTransactionAsInvalid(message: string) {
|
|
478
|
-
logPermissionError(message);
|
|
479
|
-
transaction.markInvalid();
|
|
480
|
-
}
|
|
481
|
-
|
|
482
451
|
// is first self promotion to admin
|
|
483
452
|
if (
|
|
484
453
|
transactorRole === undefined &&
|
|
@@ -511,7 +480,7 @@ function determineValidTransactionsForGroup(
|
|
|
511
480
|
assignedRole !== "admin" &&
|
|
512
481
|
affectedMember !== transactor
|
|
513
482
|
) {
|
|
514
|
-
|
|
483
|
+
transaction.markInvalid("Admins can't demote admins.");
|
|
515
484
|
continue;
|
|
516
485
|
}
|
|
517
486
|
|
|
@@ -527,20 +496,20 @@ function determineValidTransactionsForGroup(
|
|
|
527
496
|
*/
|
|
528
497
|
if (transactorRole === "manager") {
|
|
529
498
|
if (affectedMemberRole === "admin") {
|
|
530
|
-
|
|
499
|
+
transaction.markInvalid("Managers can't demote admins.");
|
|
531
500
|
continue;
|
|
532
501
|
}
|
|
533
502
|
if (change.value === "admin") {
|
|
534
|
-
|
|
503
|
+
transaction.markInvalid("Managers can't promote to admin.");
|
|
535
504
|
continue;
|
|
536
505
|
}
|
|
537
506
|
|
|
538
507
|
if (change.value === "adminInvite") {
|
|
539
|
-
|
|
508
|
+
transaction.markInvalid("Managers can't invite admins.");
|
|
540
509
|
continue;
|
|
541
510
|
}
|
|
542
511
|
if (change.value === "managerInvite") {
|
|
543
|
-
|
|
512
|
+
transaction.markInvalid("Managers can't invite managers.");
|
|
544
513
|
continue;
|
|
545
514
|
}
|
|
546
515
|
|
|
@@ -550,32 +519,31 @@ function determineValidTransactionsForGroup(
|
|
|
550
519
|
|
|
551
520
|
if (transactorRole === "adminInvite") {
|
|
552
521
|
if (change.value !== "admin") {
|
|
553
|
-
|
|
554
|
-
transaction.markInvalid();
|
|
522
|
+
transaction.markInvalid("AdminInvites can only create admins.");
|
|
555
523
|
continue;
|
|
556
524
|
}
|
|
557
525
|
} else if (transactorRole === "managerInvite") {
|
|
558
526
|
if (change.value !== "manager") {
|
|
559
|
-
|
|
527
|
+
transaction.markInvalid("managerInvite can only create managers.");
|
|
560
528
|
continue;
|
|
561
529
|
}
|
|
562
530
|
} else if (transactorRole === "writerInvite") {
|
|
563
531
|
if (change.value !== "writer") {
|
|
564
|
-
|
|
532
|
+
transaction.markInvalid("WriterInvites can only create writers.");
|
|
565
533
|
continue;
|
|
566
534
|
}
|
|
567
535
|
} else if (transactorRole === "readerInvite") {
|
|
568
536
|
if (change.value !== "reader") {
|
|
569
|
-
|
|
537
|
+
transaction.markInvalid("ReaderInvites can only create reader.");
|
|
570
538
|
continue;
|
|
571
539
|
}
|
|
572
540
|
} else if (transactorRole === "writeOnlyInvite") {
|
|
573
541
|
if (change.value !== "writeOnly") {
|
|
574
|
-
|
|
542
|
+
transaction.markInvalid("WriteOnlyInvites can only create writeOnly.");
|
|
575
543
|
continue;
|
|
576
544
|
}
|
|
577
545
|
} else {
|
|
578
|
-
|
|
546
|
+
transaction.markInvalid(
|
|
579
547
|
"Group transaction must be made by current admin, manager, or invite",
|
|
580
548
|
);
|
|
581
549
|
continue;
|
|
@@ -50,33 +50,14 @@ export class LocalTransactionsSyncQueue {
|
|
|
50
50
|
coValue: VerifiedState,
|
|
51
51
|
knownStateBefore: CoValueKnownState,
|
|
52
52
|
) {
|
|
53
|
-
const content = coValue.newContentSince(knownStateBefore
|
|
54
|
-
skipExpectContentUntil: true, // we need to calculate the streaming header considering the current batch
|
|
55
|
-
});
|
|
53
|
+
const content = coValue.newContentSince(knownStateBefore);
|
|
56
54
|
|
|
57
55
|
if (!content) {
|
|
58
56
|
return;
|
|
59
57
|
}
|
|
60
58
|
|
|
61
|
-
let firstChunk = this.firstChunks.get(coValue.id);
|
|
62
|
-
|
|
63
59
|
for (const piece of content) {
|
|
64
60
|
this.batch.push(piece);
|
|
65
|
-
|
|
66
|
-
// Check if the local content updates are in streaming, if so we need to add the info to the first chunk
|
|
67
|
-
if (firstChunk) {
|
|
68
|
-
if (!firstChunk.expectContentUntil) {
|
|
69
|
-
firstChunk.expectContentUntil =
|
|
70
|
-
knownStateFromContent(firstChunk).sessions;
|
|
71
|
-
}
|
|
72
|
-
combineKnownStateSessions(
|
|
73
|
-
firstChunk.expectContentUntil,
|
|
74
|
-
knownStateFromContent(piece).sessions,
|
|
75
|
-
);
|
|
76
|
-
} else {
|
|
77
|
-
firstChunk = piece;
|
|
78
|
-
this.firstChunks.set(coValue.id, firstChunk);
|
|
79
|
-
}
|
|
80
61
|
}
|
|
81
62
|
}
|
|
82
63
|
|
package/src/sync.ts
CHANGED
|
@@ -484,6 +484,15 @@ export class SyncManager {
|
|
|
484
484
|
? "import"
|
|
485
485
|
: peer?.role;
|
|
486
486
|
|
|
487
|
+
// TODO: We can't handle client-to-client streaming until we
|
|
488
|
+
// handle the streaming state reset on disconnection
|
|
489
|
+
if (peer?.role === "client" && msg.expectContentUntil) {
|
|
490
|
+
msg = {
|
|
491
|
+
...msg,
|
|
492
|
+
expectContentUntil: undefined,
|
|
493
|
+
};
|
|
494
|
+
}
|
|
495
|
+
|
|
487
496
|
coValue.addDependenciesFromContentMessage(msg);
|
|
488
497
|
|
|
489
498
|
// If some of the dependencies are missing, we wait for them to be available
|
|
@@ -539,16 +539,16 @@ describe("StorageApiAsync", () => {
|
|
|
539
539
|
}),
|
|
540
540
|
).toMatchInlineSnapshot(`
|
|
541
541
|
[
|
|
542
|
-
"test -> test-storage | CONTENT Core header: false new: After: 1 New: 1
|
|
543
|
-
"test -> test-storage | CONTENT Core2 header: false new: After: 1 New: 1
|
|
542
|
+
"test -> test-storage | CONTENT Core header: false new: After: 1 New: 1",
|
|
543
|
+
"test -> test-storage | CONTENT Core2 header: false new: After: 1 New: 1",
|
|
544
544
|
"test -> test-storage | CONTENT Core header: false new: After: 2 New: 1",
|
|
545
545
|
"test -> test-storage | CONTENT Core2 header: false new: After: 2 New: 1",
|
|
546
546
|
"test-storage -> test | KNOWN CORRECTION Core sessions: empty",
|
|
547
547
|
"test -> test-storage | CONTENT Core header: true new: After: 0 New: 3",
|
|
548
548
|
"test-storage -> test | KNOWN CORRECTION Core2 sessions: empty",
|
|
549
549
|
"test -> test-storage | CONTENT Core2 header: true new: After: 0 New: 3",
|
|
550
|
-
"test -> test-storage | CONTENT Core header: false new: After: 3 New: 1
|
|
551
|
-
"test -> test-storage | CONTENT Core2 header: false new: After: 3 New: 1
|
|
550
|
+
"test -> test-storage | CONTENT Core header: false new: After: 3 New: 1",
|
|
551
|
+
"test -> test-storage | CONTENT Core2 header: false new: After: 3 New: 1",
|
|
552
552
|
"test -> test-storage | CONTENT Core header: false new: After: 4 New: 1",
|
|
553
553
|
"test -> test-storage | CONTENT Core2 header: false new: After: 4 New: 1",
|
|
554
554
|
]
|
|
@@ -606,14 +606,14 @@ describe("StorageApiAsync", () => {
|
|
|
606
606
|
}),
|
|
607
607
|
).toMatchInlineSnapshot(`
|
|
608
608
|
[
|
|
609
|
-
"test -> test-storage | CONTENT Core header: false new: After: 1 New: 1
|
|
610
|
-
"test -> test-storage | CONTENT Core2 header: false new: After: 1 New: 1
|
|
609
|
+
"test -> test-storage | CONTENT Core header: false new: After: 1 New: 1",
|
|
610
|
+
"test -> test-storage | CONTENT Core2 header: false new: After: 1 New: 1",
|
|
611
611
|
"test -> test-storage | CONTENT Core header: false new: After: 2 New: 1",
|
|
612
612
|
"test -> test-storage | CONTENT Core2 header: false new: After: 2 New: 1",
|
|
613
613
|
"test-storage -> test | KNOWN CORRECTION Core sessions: empty",
|
|
614
614
|
"test -> test-storage | CONTENT Core header: true new: After: 0 New: 3",
|
|
615
|
-
"test -> test-storage | CONTENT Core header: false new: After: 3 New: 1
|
|
616
|
-
"test -> test-storage | CONTENT Core2 header: false new: After: 3 New: 1
|
|
615
|
+
"test -> test-storage | CONTENT Core header: false new: After: 3 New: 1",
|
|
616
|
+
"test -> test-storage | CONTENT Core2 header: false new: After: 3 New: 1",
|
|
617
617
|
"test -> test-storage | CONTENT Core header: false new: After: 4 New: 1",
|
|
618
618
|
"test -> test-storage | CONTENT Core2 header: false new: After: 4 New: 1",
|
|
619
619
|
]
|
|
@@ -2,6 +2,7 @@ import { assert, beforeEach, describe, expect, test } from "vitest";
|
|
|
2
2
|
import {
|
|
3
3
|
SyncMessagesLog,
|
|
4
4
|
TEST_NODE_CONFIG,
|
|
5
|
+
fillCoMapWithLargeData,
|
|
5
6
|
loadCoValueOrFail,
|
|
6
7
|
setupTestAccount,
|
|
7
8
|
setupTestNode,
|
|
@@ -49,19 +50,7 @@ describe("isStreaming", () => {
|
|
|
49
50
|
await group.core.waitForSync();
|
|
50
51
|
client.disconnect();
|
|
51
52
|
|
|
52
|
-
const map = group.createMap();
|
|
53
|
-
|
|
54
|
-
// Generate a large amount of data that requires multiple chunks
|
|
55
|
-
const dataSize = 1 * 1024 * 100;
|
|
56
|
-
const chunkSize = 1024; // 1KB chunks
|
|
57
|
-
const chunks = dataSize / chunkSize;
|
|
58
|
-
|
|
59
|
-
const value = Buffer.alloc(chunkSize, `value$`).toString("base64");
|
|
60
|
-
|
|
61
|
-
for (let i = 0; i < chunks; i++) {
|
|
62
|
-
const key = `key${i}`;
|
|
63
|
-
map.set(key, value, "trusting");
|
|
64
|
-
}
|
|
53
|
+
const map = fillCoMapWithLargeData(group.createMap());
|
|
65
54
|
|
|
66
55
|
const newSession = client.spawnNewSession();
|
|
67
56
|
|
|
@@ -101,16 +90,7 @@ describe("isStreaming", () => {
|
|
|
101
90
|
client.disconnect();
|
|
102
91
|
|
|
103
92
|
// Generate a large amount of data that requires multiple chunks
|
|
104
|
-
|
|
105
|
-
const chunkSize = 1024; // 1KB chunks
|
|
106
|
-
const chunks = dataSize / chunkSize;
|
|
107
|
-
|
|
108
|
-
const value = Buffer.alloc(chunkSize, `value$`).toString("base64");
|
|
109
|
-
|
|
110
|
-
for (let i = 0; i < chunks; i++) {
|
|
111
|
-
const key = `key${i}`;
|
|
112
|
-
map.set(key, value, "trusting");
|
|
113
|
-
}
|
|
93
|
+
fillCoMapWithLargeData(map);
|
|
114
94
|
|
|
115
95
|
const newSession = client.spawnNewSession();
|
|
116
96
|
|
|
@@ -134,41 +114,81 @@ describe("isStreaming", () => {
|
|
|
134
114
|
expect(mapInNewSession.core.isStreaming()).toBe(false);
|
|
135
115
|
});
|
|
136
116
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
117
|
+
// TODO: We can't handle client-to-client streaming until we
|
|
118
|
+
// handle the streaming state reset on disconnection
|
|
119
|
+
// Otherwise the other client might wait for a content that will never be sent
|
|
120
|
+
test.fails(
|
|
121
|
+
"streaming a large value between two clients should be streaming until all chunks are sent",
|
|
122
|
+
async () => {
|
|
123
|
+
const client = setupTestNode();
|
|
124
|
+
client.connectToSyncServer({
|
|
125
|
+
ourName: "initialClient",
|
|
126
|
+
});
|
|
127
|
+
const streamingClient = client.spawnNewSession();
|
|
128
|
+
streamingClient.connectToSyncServer({
|
|
129
|
+
ourName: "streamingClient",
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
const group = client.node.createGroup();
|
|
133
|
+
|
|
134
|
+
await group.core.waitForSync();
|
|
135
|
+
client.disconnect();
|
|
136
|
+
|
|
137
|
+
const map = fillCoMapWithLargeData(group.createMap());
|
|
138
|
+
|
|
139
|
+
const loadingClient = client.spawnNewSession();
|
|
140
|
+
loadingClient.connectToSyncServer({
|
|
141
|
+
ourName: "loadingClient",
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
await loadCoValueOrFail(loadingClient.node, group.id);
|
|
145
|
+
|
|
146
|
+
const content = map.core.verified.newContentSince(undefined);
|
|
147
|
+
assert(content);
|
|
148
|
+
const lastChunk = content.pop();
|
|
149
|
+
assert(lastChunk);
|
|
150
|
+
|
|
151
|
+
for (const chunk of content) {
|
|
152
|
+
streamingClient.node.syncManager.handleNewContent(chunk, "import");
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
await streamingClient.node.syncManager.waitForAllCoValuesSync();
|
|
156
|
+
|
|
157
|
+
const mapInLoadingClient = await loadCoValueOrFail(
|
|
158
|
+
loadingClient.node,
|
|
159
|
+
map.id,
|
|
160
|
+
);
|
|
146
161
|
|
|
147
|
-
|
|
162
|
+
expect(mapInLoadingClient.core.isStreaming()).toBe(true);
|
|
148
163
|
|
|
149
|
-
|
|
150
|
-
client.disconnect();
|
|
164
|
+
streamingClient.node.syncManager.handleNewContent(lastChunk, "import");
|
|
151
165
|
|
|
152
|
-
|
|
166
|
+
await waitFor(() => {
|
|
167
|
+
expect(mapInLoadingClient.core.knownState()).toEqual(
|
|
168
|
+
map.core.knownState(),
|
|
169
|
+
);
|
|
170
|
+
});
|
|
153
171
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
const chunks = dataSize / chunkSize;
|
|
172
|
+
expect(mapInLoadingClient.core.isStreaming()).toBe(false);
|
|
173
|
+
},
|
|
174
|
+
);
|
|
158
175
|
|
|
159
|
-
|
|
176
|
+
test("should be false when getting streaming content that is already in the known state", async () => {
|
|
177
|
+
const client = setupTestNode({
|
|
178
|
+
connected: true,
|
|
179
|
+
});
|
|
160
180
|
|
|
161
|
-
|
|
162
|
-
const key = `key${i}`;
|
|
163
|
-
map.set(key, value, "trusting");
|
|
164
|
-
}
|
|
181
|
+
const group = client.node.createGroup();
|
|
165
182
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
});
|
|
183
|
+
await group.core.waitForSync();
|
|
184
|
+
|
|
185
|
+
const map = fillCoMapWithLargeData(group.createMap());
|
|
170
186
|
|
|
171
|
-
await
|
|
187
|
+
await map.core.waitForSync();
|
|
188
|
+
const newSession = client.spawnNewSession();
|
|
189
|
+
|
|
190
|
+
const mapInNewSession1 = await loadCoValueOrFail(newSession.node, map.id);
|
|
191
|
+
await mapInNewSession1.core.waitForFullStreaming();
|
|
172
192
|
|
|
173
193
|
const content = map.core.verified.newContentSince(undefined);
|
|
174
194
|
assert(content);
|
|
@@ -176,57 +196,28 @@ describe("isStreaming", () => {
|
|
|
176
196
|
assert(lastChunk);
|
|
177
197
|
|
|
178
198
|
for (const chunk of content) {
|
|
179
|
-
|
|
199
|
+
newSession.node.syncManager.handleNewContent(chunk, "import");
|
|
180
200
|
}
|
|
181
201
|
|
|
182
|
-
await
|
|
183
|
-
|
|
184
|
-
const mapInLoadingClient = await loadCoValueOrFail(
|
|
185
|
-
loadingClient.node,
|
|
186
|
-
map.id,
|
|
187
|
-
);
|
|
188
|
-
|
|
189
|
-
expect(mapInLoadingClient.core.isStreaming()).toBe(true);
|
|
190
|
-
|
|
191
|
-
streamingClient.node.syncManager.handleNewContent(lastChunk, "import");
|
|
192
|
-
|
|
193
|
-
await waitFor(() => {
|
|
194
|
-
expect(mapInLoadingClient.core.knownState()).toEqual(
|
|
195
|
-
map.core.knownState(),
|
|
196
|
-
);
|
|
197
|
-
});
|
|
202
|
+
const mapInNewSession = await loadCoValueOrFail(newSession.node, map.id);
|
|
198
203
|
|
|
199
|
-
expect(
|
|
204
|
+
expect(mapInNewSession.core.isStreaming()).toBe(false);
|
|
200
205
|
});
|
|
201
206
|
|
|
202
|
-
test("
|
|
203
|
-
const client = setupTestNode(
|
|
204
|
-
connected: true,
|
|
205
|
-
});
|
|
207
|
+
test("streaming state from clients should be ignored", async () => {
|
|
208
|
+
const client = setupTestNode();
|
|
206
209
|
|
|
207
210
|
const group = client.node.createGroup();
|
|
208
211
|
|
|
209
212
|
await group.core.waitForSync();
|
|
210
213
|
|
|
211
|
-
const map = group.createMap();
|
|
212
|
-
|
|
213
|
-
// Generate a large amount of data that requires multiple chunks
|
|
214
|
-
const dataSize = 1 * 1024 * 100;
|
|
215
|
-
const chunkSize = 1024; // 1KB chunks
|
|
216
|
-
const chunks = dataSize / chunkSize;
|
|
217
|
-
|
|
218
|
-
const value = Buffer.alloc(chunkSize, `value$`).toString("base64");
|
|
219
|
-
|
|
220
|
-
for (let i = 0; i < chunks; i++) {
|
|
221
|
-
const key = `key${i}`;
|
|
222
|
-
map.set(key, value, "trusting");
|
|
223
|
-
}
|
|
214
|
+
const map = fillCoMapWithLargeData(group.createMap());
|
|
224
215
|
|
|
225
216
|
await map.core.waitForSync();
|
|
226
217
|
const newSession = client.spawnNewSession();
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
218
|
+
newSession.connectToSyncServer({
|
|
219
|
+
ourName: "streamingClient",
|
|
220
|
+
});
|
|
230
221
|
|
|
231
222
|
const content = map.core.verified.newContentSince(undefined);
|
|
232
223
|
assert(content);
|
|
@@ -237,9 +228,11 @@ describe("isStreaming", () => {
|
|
|
237
228
|
newSession.node.syncManager.handleNewContent(chunk, "import");
|
|
238
229
|
}
|
|
239
230
|
|
|
240
|
-
const
|
|
241
|
-
|
|
242
|
-
expect(
|
|
231
|
+
const mapOnServer = jazzCloud.node.getCoValue(map.id);
|
|
232
|
+
expect(mapOnServer.isStreaming()).toBe(false);
|
|
233
|
+
expect(mapOnServer.knownState()).toEqual(
|
|
234
|
+
mapOnServer.knownStateWithStreaming(),
|
|
235
|
+
);
|
|
243
236
|
});
|
|
244
237
|
|
|
245
238
|
test("should be false when getting streaming content that's not really streaming", async () => {
|