@vrtmrz/livesync-commonlib 0.1.19-rc.0 → 0.1.20

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.
Files changed (114) hide show
  1. package/README.md +5 -1
  2. package/dist/common/models/setting.p2p.d.ts +2 -0
  3. package/dist/common/models/setting.p2p.js +5 -0
  4. package/dist/interfaces/ServiceModule.d.ts +13 -4
  5. package/dist/managers/ChunkFetcher.js +10 -0
  6. package/dist/p2p/P2PService.d.ts +123 -0
  7. package/dist/p2p/P2PService.js +271 -0
  8. package/dist/p2p/index.d.ts +8 -0
  9. package/dist/p2p/index.js +8 -0
  10. package/dist/pouchdb/LiveSyncLocalDB.d.ts +3 -0
  11. package/dist/pouchdb/LiveSyncLocalDB.js +23 -3
  12. package/dist/pouchdb/ReplicatorShim.d.ts +43 -4
  13. package/dist/pouchdb/ReplicatorShim.js +78 -5
  14. package/dist/replication/CentralCompatibility.d.ts +38 -0
  15. package/dist/replication/CentralCompatibility.js +39 -0
  16. package/dist/replication/CentralRemoteAdministration.d.ts +84 -0
  17. package/dist/replication/CentralRemoteAdministration.js +71 -0
  18. package/dist/replication/LiveSyncAbstractReplicator.d.ts +19 -6
  19. package/dist/replication/ProviderCapability.d.ts +32 -0
  20. package/dist/replication/ProviderCapability.js +27 -0
  21. package/dist/replication/RemoteResource.d.ts +85 -0
  22. package/dist/replication/RemoteResource.js +17 -0
  23. package/dist/replication/ReplicatorInstance.d.ts +23 -0
  24. package/dist/replication/ReplicatorProvider.d.ts +249 -0
  25. package/dist/replication/ReplicatorProvider.js +145 -0
  26. package/dist/replication/couchdb/LiveSyncReplicator.d.ts +54 -8
  27. package/dist/replication/couchdb/LiveSyncReplicator.js +411 -158
  28. package/dist/replication/index.d.ts +10 -0
  29. package/dist/replication/index.js +70 -0
  30. package/dist/replication/journal/JournalSyncCore.d.ts +2 -0
  31. package/dist/replication/journal/JournalSyncCore.js +18 -5
  32. package/dist/replication/journal/LiveSyncJournalReplicator.d.ts +38 -10
  33. package/dist/replication/journal/LiveSyncJournalReplicator.js +271 -63
  34. package/dist/replication/journal/LiveSyncJournalReplicatorEnv.d.ts +8 -0
  35. package/dist/replication/journal/objectstore/JournalStorageAdapter.d.ts +2 -0
  36. package/dist/replication/journal/objectstore/MinioStorageAdapter.d.ts +2 -0
  37. package/dist/replication/journal/objectstore/MinioStorageAdapter.js +10 -1
  38. package/dist/replication/trystero/LiveSyncTrysteroReplicator.d.ts +12 -40
  39. package/dist/replication/trystero/LiveSyncTrysteroReplicator.js +52 -152
  40. package/dist/replication/trystero/P2PAutomationCoordinator.d.ts +20 -0
  41. package/dist/replication/trystero/P2PAutomationCoordinator.js +43 -0
  42. package/dist/replication/trystero/P2PConnectionProbeAdmission.d.ts +32 -0
  43. package/dist/replication/trystero/P2PConnectionProbeAdmission.js +10 -0
  44. package/dist/replication/trystero/P2PReplicationWire.d.ts +31 -0
  45. package/dist/replication/trystero/P2PReplicationWire.js +50 -0
  46. package/dist/replication/trystero/P2PRoomSession.d.ts +32 -0
  47. package/dist/replication/trystero/P2PRoomSession.js +101 -0
  48. package/dist/replication/trystero/P2PRoomSessionOwner.d.ts +61 -0
  49. package/dist/replication/trystero/P2PRoomSessionOwner.js +249 -0
  50. package/dist/replication/trystero/TrysteroReplicator.d.ts +88 -36
  51. package/dist/replication/trystero/TrysteroReplicator.js +273 -113
  52. package/dist/replication/trystero/TrysteroReplicatorP2PClient.d.ts +7 -4
  53. package/dist/replication/trystero/TrysteroReplicatorP2PClient.js +21 -14
  54. package/dist/replication/trystero/TrysteroReplicatorP2PServer.d.ts +26 -3
  55. package/dist/replication/trystero/TrysteroReplicatorP2PServer.js +84 -39
  56. package/dist/replication/trystero/UseP2PReplicatorResult.d.ts +10 -6
  57. package/dist/replication/trystero/addP2PEventHandlers.d.ts +8 -12
  58. package/dist/replication/trystero/addP2PEventHandlers.js +19 -19
  59. package/dist/replication/trystero/p2pReplicatorConfigurationIdentity.d.ts +9 -0
  60. package/dist/replication/trystero/p2pReplicatorConfigurationIdentity.js +30 -0
  61. package/dist/replication/trystero/types.d.ts +18 -0
  62. package/dist/replication/trystero/types.js +5 -1
  63. package/dist/replication/trystero/useP2PReplicatorCommands.d.ts +3 -3
  64. package/dist/replication/trystero/useP2PReplicatorCommands.js +6 -8
  65. package/dist/replication/trystero/useP2PReplicatorFeature.d.ts +16 -7
  66. package/dist/replication/trystero/useP2PReplicatorFeature.js +72 -55
  67. package/dist/rpc/RpcRoom.d.ts +15 -1
  68. package/dist/rpc/RpcRoom.js +176 -41
  69. package/dist/rpc/RpcSession.d.ts +3 -1
  70. package/dist/rpc/RpcSession.js +2 -2
  71. package/dist/rpc/index.d.ts +1 -1
  72. package/dist/rpc/transports/trysteroUtils.js +2 -1
  73. package/dist/rpc/types.d.ts +26 -1
  74. package/dist/serviceFeatures/checkRemoteSize.js +5 -2
  75. package/dist/serviceModules/Rebuilder.js +66 -56
  76. package/dist/services/base/AppLifecycleService.d.ts +4 -0
  77. package/dist/services/base/AppLifecycleService.js +4 -0
  78. package/dist/services/base/ControlService.js +0 -4
  79. package/dist/services/base/DatabaseEventService.js +1 -1
  80. package/dist/services/base/IService.d.ts +105 -8
  81. package/dist/services/base/ReadinessEvaluator.d.ts +40 -0
  82. package/dist/services/base/ReadinessEvaluator.js +16 -0
  83. package/dist/services/base/RemoteConnection.d.ts +3 -0
  84. package/dist/services/base/RemoteService.js +2 -2
  85. package/dist/services/base/ReplicationService.d.ts +45 -10
  86. package/dist/services/base/ReplicationService.js +248 -91
  87. package/dist/services/base/ReplicationService.readiness.d.ts +52 -0
  88. package/dist/services/base/ReplicationService.readiness.js +87 -0
  89. package/dist/services/base/ReplicationService.typedReplication.d.ts +83 -0
  90. package/dist/services/base/ReplicationService.typedReplication.js +191 -0
  91. package/dist/services/base/ReplicatorService.activeReplicatorState.d.ts +47 -0
  92. package/dist/services/base/ReplicatorService.activeReplicatorState.js +107 -0
  93. package/dist/services/base/ReplicatorService.centralRemoteAdministration.d.ts +5 -0
  94. package/dist/services/base/ReplicatorService.centralRemoteAdministration.js +44 -0
  95. package/dist/services/base/ReplicatorService.d.ts +75 -14
  96. package/dist/services/base/ReplicatorService.js +232 -53
  97. package/dist/services/base/ReplicatorService.remoteResourceResolver.d.ts +4 -0
  98. package/dist/services/base/ReplicatorService.remoteResourceResolver.js +11 -0
  99. package/dist/services/base/TweakValueService.d.ts +3 -2
  100. package/dist/services/implements/injectable/InjectableTweakValueService.d.ts +1 -1
  101. package/dist/services/lib/HandlerUtils.d.ts +24 -0
  102. package/dist/services/lib/HandlerUtils.js +40 -0
  103. package/docs/development.md +40 -7
  104. package/docs/p2p-transport-lifecycle.md +135 -11
  105. package/docs/proven-in-use.md +5 -1
  106. package/docs/remote-configurations.md +4 -0
  107. package/docs/service-feature-composition.md +250 -0
  108. package/package.json +17 -1
  109. package/dist/replication/trystero/P2PReplicatorBase.d.ts +0 -19
  110. package/dist/replication/trystero/P2PReplicatorCore.d.ts +0 -8
  111. package/dist/replication/trystero/P2PReplicatorCore.js +0 -56
  112. package/dist/replication/trystero/TrysteroReplicatorP2PConnection.d.ts +0 -2
  113. package/dist/replication/trystero/TrysteroReplicatorP2PConnection.js +0 -4
  114. /package/dist/replication/{trystero/P2PReplicatorBase.js → ReplicatorInstance.js} +0 -0
@@ -42,6 +42,18 @@ import {
42
42
  SyncParamsUpdateError
43
43
  } from "../SyncParamsHandler.js";
44
44
  import { compatGlobal } from "../../common/coreEnvFunctions.js";
45
+ import {
46
+ CENTRAL_COMPATIBILITY_ACCEPTED,
47
+ CENTRAL_COMPATIBILITY_NOT_ASSESSED,
48
+ CENTRAL_COMPATIBILITY_REJECTION_REASONS,
49
+ centralCompatibilityRejected,
50
+ centralCompatibilityRecoveryHint
51
+ } from "../CentralCompatibility.js";
52
+ import {
53
+ outcomeFromFiniteOpenReplication,
54
+ replicationBlocked,
55
+ replicationFailed
56
+ } from "../ReplicatorProvider.js";
45
57
  const currentVersionRange = {
46
58
  min: 0,
47
59
  max: 2400,
@@ -49,6 +61,7 @@ const currentVersionRange = {
49
61
  };
50
62
  const selectorOnDemandPull = { selector: { type: { $ne: "leaf" } } };
51
63
  const DEFAULT_ONE_SHOT_CONNECTIVITY_TIMEOUT_MS = 6e4;
64
+ const ONE_SHOT_REPLICATION_ALREADY_RUNNING = /* @__PURE__ */ Symbol("one-shot-replication-already-running");
52
65
  class OneShotConnectivityPreflightTimeoutError extends Error {
53
66
  constructor(timeoutMs) {
54
67
  super(`Remote connectivity preflight exceeded ${timeoutMs} ms.`);
@@ -96,6 +109,13 @@ async function* genReplication(s, signal) {
96
109
  }
97
110
  }
98
111
  class LiveSyncCouchDBReplicator extends LiveSyncAbstractReplicator {
112
+ /**
113
+ * Keep Continuous ownership visible before CouchDB has created its live
114
+ * controller, so a stop request cannot be lost during catch-up.
115
+ */
116
+ continuousTask;
117
+ continuousStopWaiter;
118
+ continuousStopRequested = false;
99
119
  isMobile() {
100
120
  return this.env.services.API.isMobile();
101
121
  }
@@ -147,23 +167,69 @@ class LiveSyncCouchDBReplicator extends LiveSyncAbstractReplicator {
147
167
  Logger(`Database updated from ${from} to ${to}`, LOG_LEVEL_NOTICE);
148
168
  return Promise.resolve(true);
149
169
  }
170
+ /** Abort only the active controller; internal transitions must not await their own task. */
171
+ abortController() {
172
+ this.controller?.abort();
173
+ this.controller = void 0;
174
+ }
175
+ /**
176
+ * Request cancellation of the current transfer and share the tracked
177
+ * Continuous settlement when startup has not created a controller yet.
178
+ */
150
179
  terminateSync() {
151
- if (!this.controller) {
152
- return;
180
+ const task = this.continuousTask;
181
+ if (task) {
182
+ this.continuousStopRequested = true;
153
183
  }
154
- this.controller.abort();
155
- this.controller = void 0;
184
+ this.abortController();
185
+ if (!task) {
186
+ return Promise.resolve();
187
+ }
188
+ return this.continuousStopWaiter ??= task.then(
189
+ () => void 0,
190
+ () => void 0
191
+ );
156
192
  }
157
193
  async openReplication(setting, keepAlive, showResult, ignoreCleanLock) {
158
- if (!await this.initializeDatabaseForReplication()) {
159
- return false;
160
- }
194
+ if (!this.nodeid) return false;
161
195
  if (keepAlive) {
162
196
  void this.openContinuousReplication(setting, showResult, false);
163
197
  } else {
164
198
  return this.openOneShotReplication(setting, showResult, false, "sync", ignoreCleanLock);
165
199
  }
166
200
  }
201
+ /**
202
+ * Run one finite CouchDB attempt and retain only its own compatibility result.
203
+ *
204
+ * The decision is stack-local. A transport failure before assessment has no
205
+ * recovery hint, and a rejected assessment cannot leak into a later attempt.
206
+ */
207
+ async openOneShotReplicationWithOutcome(setting, showResult, ignoreCleanLock = false) {
208
+ return await this.runOneShotReplicationWithOutcome(setting, showResult, "sync", ignoreCleanLock);
209
+ }
210
+ /** Capture the compatibility decision made by this exact directional connection. */
211
+ async runOneShotReplicationWithOutcome(setting, showResult, syncMode, ignoreCleanLock = false) {
212
+ let decision = CENTRAL_COMPATIBILITY_NOT_ASSESSED;
213
+ const recordDecision = (next) => {
214
+ decision = next;
215
+ };
216
+ try {
217
+ const result = await this.runOneShotReplication(
218
+ setting,
219
+ showResult,
220
+ false,
221
+ syncMode,
222
+ ignoreCleanLock,
223
+ recordDecision
224
+ );
225
+ if (result === ONE_SHOT_REPLICATION_ALREADY_RUNNING) {
226
+ return replicationBlocked("replication-in-progress");
227
+ }
228
+ return outcomeFromFiniteOpenReplication(result, centralCompatibilityRecoveryHint(decision));
229
+ } catch (error) {
230
+ return replicationFailed(error, centralCompatibilityRecoveryHint(decision));
231
+ }
232
+ }
167
233
  replicationActivated(showResult) {
168
234
  this.syncStatus = "CONNECTED";
169
235
  this.updateInfo();
@@ -200,18 +266,18 @@ class LiveSyncCouchDBReplicator extends LiveSyncAbstractReplicator {
200
266
  this.syncStatus = "COMPLETED";
201
267
  this.updateInfo();
202
268
  Logger("Replication completed", showResult ? LOG_LEVEL_NOTICE : LOG_LEVEL_INFO, showResult ? "sync" : "");
203
- this.terminateSync();
269
+ this.abortController();
204
270
  }
205
271
  replicationDenied(e) {
206
272
  this.syncStatus = "ERRORED";
207
273
  this.updateInfo();
208
- this.terminateSync();
274
+ this.abortController();
209
275
  Logger("Replication denied", LOG_LEVEL_NOTICE, "sync");
210
276
  Logger(e, LOG_LEVEL_VERBOSE);
211
277
  }
212
278
  replicationErrored(e) {
213
279
  this.syncStatus = "ERRORED";
214
- this.terminateSync();
280
+ this.abortController();
215
281
  this.updateInfo();
216
282
  Logger("Replication error", LOG_LEVEL_NOTICE, "sync");
217
283
  Logger(e, LOG_LEVEL_VERBOSE);
@@ -315,7 +381,7 @@ class LiveSyncCouchDBReplicator extends LiveSyncAbstractReplicator {
315
381
  Logger(ex, LOG_LEVEL_VERBOSE);
316
382
  return "FAILED";
317
383
  } finally {
318
- this.terminateSync();
384
+ this.abortController();
319
385
  this.controller = void 0;
320
386
  }
321
387
  }
@@ -444,22 +510,18 @@ class LiveSyncCouchDBReplicator extends LiveSyncAbstractReplicator {
444
510
  );
445
511
  }
446
512
  }
447
- let bulkDocs = [];
448
- let bulkDocsSizeBytes = 0;
449
- let bulkDocsSizeCount = 0;
450
- let maxSeq = 0;
451
513
  const maxBatchSizeBytes = setting.sendChunksBulkMaxSize * 1024 * 1024;
452
514
  const maxBatchSizeCount = 200;
453
515
  const semaphore = Semaphore(4);
454
516
  let sendingDocs = 0;
455
517
  let sentDocsCount = 0;
456
- const sendChunks = async (bulkDocs2, bulkDocsSize, seq2) => {
518
+ const sendChunks = async (bulkDocs, bulkDocsSize, seq2) => {
457
519
  Logger(
458
- `Sending ${bulkDocs2.length} (${bulkDocsSize} => ${sizeToHumanReadable(bulkDocsSize)} in plain) chunks to remote database...`,
520
+ `Sending ${bulkDocs.length} (${bulkDocsSize} => ${sizeToHumanReadable(bulkDocsSize)} in plain) chunks to remote database...`,
459
521
  LOG_LEVEL_VERBOSE
460
522
  );
461
523
  const releaser = await semaphore.acquire(1);
462
- sendingDocs += bulkDocs2.length;
524
+ sendingDocs += bulkDocs.length;
463
525
  Logger(
464
526
  `\u2191 Uploading chunks
465
527
  ${sendingDocs}/(${sentDocsCount} done)`,
@@ -467,12 +529,12 @@ ${sendingDocs}/(${sentDocsCount} done)`,
467
529
  "send"
468
530
  );
469
531
  try {
470
- const uploadBulkDocTasks = bulkDocs2.map((e) => preprocessOutgoing(e));
532
+ const uploadBulkDocTasks = bulkDocs.map((e) => preprocessOutgoing(e));
471
533
  const uploadBulkDocs = await Promise.all(uploadBulkDocTasks);
472
534
  await remoteDB.bulkDocs(uploadBulkDocs, { new_edits: false });
473
535
  uploadBulkDocs.forEach((e) => sentMap[idSeqMap[e._id]] = true);
474
536
  await this.updateMaxTransferredSeqOnChunks(localDB, remoteID, sentMap);
475
- sentDocsCount += bulkDocs2.length;
537
+ sentDocsCount += bulkDocs.length;
476
538
  Logger(
477
539
  `\u2191 Uploading chunks
478
540
  ${sendingDocs}/(${sentDocsCount} done)`,
@@ -488,12 +550,16 @@ ${sendingDocs}/(${sentDocsCount} done)`,
488
550
  }
489
551
  return true;
490
552
  };
491
- const tasks = [];
492
553
  do {
493
554
  const nowSendChunks = await trench.dequeue("send-chunks");
494
555
  if (!nowSendChunks || nowSendChunks.length == 0) {
495
556
  break;
496
557
  }
558
+ let bulkDocs = [];
559
+ let bulkDocsSizeBytes = 0;
560
+ let bulkDocsSizeCount = 0;
561
+ let maxSeq = 0;
562
+ const tasks = [];
497
563
  for (const chunk of nowSendChunks) {
498
564
  const jsonLength = te.encode(JSON.stringify(chunk.doc)).byteLength + 32;
499
565
  if ((bulkDocsSizeBytes + jsonLength > maxBatchSizeBytes || bulkDocsSizeCount + 1 > maxBatchSizeCount) && bulkDocs.length > 0) {
@@ -513,7 +579,7 @@ ${sendingDocs}/(${sentDocsCount} done)`,
513
579
  const results = await Promise.all(
514
580
  tasks.map(async (e) => {
515
581
  try {
516
- await e;
582
+ return await e;
517
583
  } catch (ex) {
518
584
  Logger("Bulk sending failed.", showResult ? LOG_LEVEL_NOTICE : LOG_LEVEL_INFO, "send");
519
585
  Logger(ex, LOG_LEVEL_VERBOSE);
@@ -527,111 +593,196 @@ ${sendingDocs}/(${sentDocsCount} done)`,
527
593
  } while (true);
528
594
  return true;
529
595
  }
530
- async openOneShotReplication(setting, showResult, retrying, syncMode, ignoreCleanLock = false) {
531
- if (await this.ensurePBKDF2Salt(setting, showResult, !retrying) === false) {
596
+ /** Run one finite transfer through the legacy boolean settlement contract. */
597
+ async openOneShotReplication(setting, showResult, retrying, syncMode, ignoreCleanLock = false, recordCompatibilityDecision, cancellationRequested = () => false) {
598
+ const result = await this.runOneShotReplication(
599
+ setting,
600
+ showResult,
601
+ retrying,
602
+ syncMode,
603
+ ignoreCleanLock,
604
+ recordCompatibilityDecision,
605
+ cancellationRequested
606
+ );
607
+ return result === ONE_SHOT_REPLICATION_ALREADY_RUNNING ? false : result;
608
+ }
609
+ /**
610
+ * Run one finite CouchDB transfer and close its owned remote connection.
611
+ *
612
+ * The caller which enters the shared task owns Security Seed preparation,
613
+ * transfer, and any retry or resurrection continuation. A joining caller
614
+ * waits for the shared step only so it cannot race the owner, then receives
615
+ * the internal non-admission result without running that continuation.
616
+ *
617
+ * Continuous catch-up supplies `cancellationRequested` so a stop observed
618
+ * during awaited preparation cannot proceed to a later connection or
619
+ * controller. The admitted owner retains the established transfer behaviour.
620
+ */
621
+ async runOneShotReplication(setting, showResult, retrying, syncMode, ignoreCleanLock = false, recordCompatibilityDecision, cancellationRequested = () => false) {
622
+ if (cancellationRequested()) {
532
623
  return false;
533
624
  }
534
- const next = await shareRunningResult("oneShotReplication", async () => {
535
- if (this.controller) {
536
- Logger(
537
- this.translate("liveSyncReplicator.replicationInProgress"),
538
- showResult ? LOG_LEVEL_NOTICE : LOG_LEVEL_INFO,
539
- "sync"
540
- );
541
- return false;
542
- }
543
- const localDB = this.rawDatabase;
544
- Logger(this.translate("liveSyncReplicator.oneShotSyncBegin", { syncMode }));
545
- const ret = await this.checkOneShotReplicationConnectivity(setting, retrying, showResult, ignoreCleanLock);
546
- if (ret === false) {
547
- Logger(
548
- this.translate("liveSyncReplicator.couldNotConnectToServer"),
549
- showResult ? LOG_LEVEL_NOTICE : LOG_LEVEL_INFO,
550
- "sync"
551
- );
552
- return false;
553
- }
554
- const { db, syncOptionBase } = ret;
555
- try {
556
- this.maxPullSeq = Number(`${ret.info.update_seq}`.split("-")[0]);
557
- this.maxPushSeq = Number(`${(await localDB.info()).update_seq}`.split("-")[0]);
558
- if (showResult) {
559
- Logger(this.translate("liveSyncReplicator.checkingLastSyncPoint"), LOG_LEVEL_NOTICE, "sync");
560
- }
561
- this.syncStatus = "STARTED";
562
- this.updateInfo();
563
- const docArrivedOnStart = this.docArrived;
564
- const docSentOnStart = this.docSent;
565
- if (!retrying) {
566
- this.originalSetting = setting;
625
+ let ownsSharedAttempt = false;
626
+ let next;
627
+ try {
628
+ next = await shareRunningResult("oneShotReplication", async () => {
629
+ ownsSharedAttempt = true;
630
+ if (cancellationRequested()) {
631
+ return false;
567
632
  }
568
- this.terminateSync();
569
- const syncHandler = syncMode == "sync" ? localDB.sync(db, { ...syncOptionBase }) : syncMode == "pullOnly" ? localDB.replicate.from(db, {
570
- ...syncOptionBase,
571
- ...setting.readChunksOnline ? selectorOnDemandPull : {}
572
- }) : syncMode == "pushOnly" ? localDB.replicate.to(db, { ...syncOptionBase }) : void 0;
573
- const syncResult = await this.processSync(
574
- syncHandler,
575
- showResult,
576
- docSentOnStart,
577
- docArrivedOnStart,
578
- syncMode,
579
- retrying,
580
- false
581
- );
582
- if (syncResult == "DONE") {
583
- return true;
633
+ if (await this.ensurePBKDF2Salt(setting, showResult, !retrying) === false || cancellationRequested()) {
634
+ return false;
584
635
  }
585
- if (syncResult == "CANCELLED") {
636
+ if (this.controller) {
637
+ Logger(
638
+ this.translate("liveSyncReplicator.replicationInProgress"),
639
+ showResult ? LOG_LEVEL_NOTICE : LOG_LEVEL_INFO,
640
+ "sync"
641
+ );
586
642
  return false;
587
643
  }
588
- if (syncResult == "FAILED") {
644
+ const localDB = this.rawDatabase;
645
+ Logger(this.translate("liveSyncReplicator.oneShotSyncBegin", { syncMode }));
646
+ const ret = await this.checkOneShotReplicationConnectivity(
647
+ setting,
648
+ false,
649
+ showResult,
650
+ ignoreCleanLock,
651
+ recordCompatibilityDecision
652
+ );
653
+ if (ret === false) {
654
+ if (!cancellationRequested()) {
655
+ Logger(
656
+ this.translate("liveSyncReplicator.couldNotConnectToServer"),
657
+ showResult ? LOG_LEVEL_NOTICE : LOG_LEVEL_INFO,
658
+ "sync"
659
+ );
660
+ }
589
661
  return false;
590
662
  }
591
- if (syncResult == "NEED_RESURRECT") {
592
- this.terminateSync();
593
- return async () => await this.openOneShotReplication(
594
- this.originalSetting,
663
+ const { db, syncOptionBase } = ret;
664
+ try {
665
+ if (cancellationRequested()) {
666
+ return false;
667
+ }
668
+ this.maxPullSeq = Number(`${ret.info.update_seq}`.split("-")[0]);
669
+ this.maxPushSeq = Number(`${(await localDB.info()).update_seq}`.split("-")[0]);
670
+ if (cancellationRequested()) {
671
+ return false;
672
+ }
673
+ if (showResult) {
674
+ Logger(this.translate("liveSyncReplicator.checkingLastSyncPoint"), LOG_LEVEL_NOTICE, "sync");
675
+ }
676
+ this.syncStatus = "STARTED";
677
+ this.updateInfo();
678
+ const docArrivedOnStart = this.docArrived;
679
+ const docSentOnStart = this.docSent;
680
+ if (!retrying) {
681
+ this.originalSetting = setting;
682
+ }
683
+ this.abortController();
684
+ if (cancellationRequested()) {
685
+ return false;
686
+ }
687
+ const syncHandler = syncMode == "sync" ? localDB.sync(db, { ...syncOptionBase }) : syncMode == "pullOnly" ? localDB.replicate.from(db, {
688
+ ...syncOptionBase,
689
+ ...setting.readChunksOnline ? selectorOnDemandPull : {}
690
+ }) : syncMode == "pushOnly" ? localDB.replicate.to(db, { ...syncOptionBase }) : void 0;
691
+ const syncResult = await this.processSync(
692
+ syncHandler,
595
693
  showResult,
596
- false,
694
+ docSentOnStart,
695
+ docArrivedOnStart,
597
696
  syncMode,
598
- ignoreCleanLock
697
+ retrying,
698
+ false
599
699
  );
600
- }
601
- if (syncResult == "NEED_RETRY") {
602
- const tempSetting = JSON.parse(JSON.stringify(setting));
603
- tempSetting.batch_size = Math.ceil(tempSetting.batch_size / 2) + 2;
604
- tempSetting.batches_limit = Math.ceil(tempSetting.batches_limit / 2) + 2;
605
- if (tempSetting.batch_size <= 5 && tempSetting.batches_limit <= 5) {
606
- Logger(
607
- this.translate("liveSyncReplicator.cantReplicateLowerValue"),
608
- showResult ? LOG_LEVEL_NOTICE : LOG_LEVEL_INFO
609
- );
700
+ if (cancellationRequested()) {
610
701
  return false;
611
- } else {
612
- Logger(
613
- this.translate("liveSyncReplicator.retryLowerBatchSize", {
614
- batch_size: tempSetting.batch_size.toString(),
615
- batches_limit: tempSetting.batches_limit.toString()
616
- }),
617
- showResult ? LOG_LEVEL_NOTICE : LOG_LEVEL_INFO
702
+ }
703
+ if (syncResult == "DONE") {
704
+ return true;
705
+ }
706
+ if (syncResult == "CANCELLED") {
707
+ return false;
708
+ }
709
+ if (syncResult == "FAILED") {
710
+ return false;
711
+ }
712
+ if (syncResult == "NEED_RESURRECT") {
713
+ this.abortController();
714
+ return async () => await this.runOneShotReplication(
715
+ this.originalSetting,
716
+ showResult,
717
+ false,
718
+ syncMode,
719
+ ignoreCleanLock,
720
+ recordCompatibilityDecision,
721
+ cancellationRequested
618
722
  );
619
- return async () => await this.openOneShotReplication(tempSetting, showResult, true, syncMode, ignoreCleanLock);
620
723
  }
724
+ if (syncResult == "NEED_RETRY") {
725
+ const tempSetting = JSON.parse(JSON.stringify(setting));
726
+ tempSetting.batch_size = Math.ceil(tempSetting.batch_size / 2) + 2;
727
+ tempSetting.batches_limit = Math.ceil(tempSetting.batches_limit / 2) + 2;
728
+ if (tempSetting.batch_size <= 5 && tempSetting.batches_limit <= 5) {
729
+ Logger(
730
+ this.translate("liveSyncReplicator.cantReplicateLowerValue"),
731
+ showResult ? LOG_LEVEL_NOTICE : LOG_LEVEL_INFO
732
+ );
733
+ return false;
734
+ } else {
735
+ Logger(
736
+ this.translate("liveSyncReplicator.retryLowerBatchSize", {
737
+ batch_size: tempSetting.batch_size.toString(),
738
+ batches_limit: tempSetting.batches_limit.toString()
739
+ }),
740
+ showResult ? LOG_LEVEL_NOTICE : LOG_LEVEL_INFO
741
+ );
742
+ return async () => await this.runOneShotReplication(
743
+ tempSetting,
744
+ showResult,
745
+ true,
746
+ syncMode,
747
+ ignoreCleanLock,
748
+ recordCompatibilityDecision,
749
+ cancellationRequested
750
+ );
751
+ }
752
+ }
753
+ return false;
754
+ } finally {
755
+ await this.closeRemoteConnection(ret);
621
756
  }
622
- return false;
623
- } finally {
624
- await this.closeRemoteConnection(ret);
757
+ });
758
+ } catch (error) {
759
+ if (!ownsSharedAttempt) {
760
+ return ONE_SHOT_REPLICATION_ALREADY_RUNNING;
625
761
  }
626
- });
762
+ throw error;
763
+ }
764
+ if (!ownsSharedAttempt) {
765
+ return ONE_SHOT_REPLICATION_ALREADY_RUNNING;
766
+ }
627
767
  if (typeof next === "boolean") {
628
768
  return next;
629
769
  }
630
770
  return await next();
631
771
  }
632
- async checkOneShotReplicationConnectivity(setting, skipCheck, showResult, ignoreCleanLock) {
772
+ async checkOneShotReplicationConnectivity(setting, skipCheck, showResult, ignoreCleanLock, recordCompatibilityDecision) {
633
773
  if (setting.useRequestAPI) {
634
- return await this.checkReplicationConnectivity(setting, false, skipCheck, showResult, ignoreCleanLock);
774
+ if (!recordCompatibilityDecision) {
775
+ return await this.checkReplicationConnectivity(setting, false, skipCheck, showResult, ignoreCleanLock);
776
+ }
777
+ return await this.checkReplicationConnectivity(
778
+ setting,
779
+ false,
780
+ skipCheck,
781
+ showResult,
782
+ ignoreCleanLock,
783
+ void 0,
784
+ recordCompatibilityDecision
785
+ );
635
786
  }
636
787
  const timeoutMs = this.env.oneShotConnectivityTimeoutMs ?? DEFAULT_ONE_SHOT_CONNECTIVITY_TIMEOUT_MS;
637
788
  const controller = new AbortController();
@@ -640,10 +791,18 @@ ${sendingDocs}/(${sentDocsCount} done)`,
640
791
  timeoutMs
641
792
  );
642
793
  try {
643
- return await this.checkReplicationConnectivity(setting, false, skipCheck, showResult, ignoreCleanLock, {
644
- signal: controller.signal,
645
- allowNativeFallback: false
646
- });
794
+ return await this.checkReplicationConnectivity(
795
+ setting,
796
+ false,
797
+ skipCheck,
798
+ showResult,
799
+ ignoreCleanLock,
800
+ {
801
+ signal: controller.signal,
802
+ allowNativeFallback: false
803
+ },
804
+ recordCompatibilityDecision
805
+ );
647
806
  } finally {
648
807
  compatGlobal.clearTimeout(timeout);
649
808
  }
@@ -666,9 +825,15 @@ ${sendingDocs}/(${sentDocsCount} done)`,
666
825
  replicateAllToServer(setting, showingNotice) {
667
826
  return this.openOneShotReplication(setting, showingNotice ?? false, false, "pushOnly");
668
827
  }
828
+ replicateAllToServerWithOutcome(setting, showingNotice) {
829
+ return this.runOneShotReplicationWithOutcome(setting, showingNotice ?? false, "pushOnly");
830
+ }
669
831
  replicateAllFromServer(setting, showingNotice) {
670
832
  return this.openOneShotReplication(setting, showingNotice ?? false, false, "pullOnly");
671
833
  }
834
+ replicateAllFromServerWithOutcome(setting, showingNotice) {
835
+ return this.runOneShotReplicationWithOutcome(setting, showingNotice ?? false, "pullOnly");
836
+ }
672
837
  reportConnectivityPreflightTimeout(connectionOptions, showResult, error) {
673
838
  if (!connectionOptions?.signal?.aborted || !(connectionOptions.signal.reason instanceof OneShotConnectivityPreflightTimeoutError)) {
674
839
  return false;
@@ -679,7 +844,8 @@ ${sendingDocs}/(${sentDocsCount} done)`,
679
844
  }
680
845
  return true;
681
846
  }
682
- async checkReplicationConnectivity(setting, keepAlive, skipCheck, showResult, ignoreCleanLock = false, connectionOptions) {
847
+ async checkReplicationConnectivity(setting, keepAlive, skipCheck, showResult, ignoreCleanLock = false, connectionOptions, recordCompatibilityDecision) {
848
+ recordCompatibilityDecision?.(CENTRAL_COMPATIBILITY_NOT_ASSESSED);
683
849
  if (setting.versionUpFlash != "") {
684
850
  Logger(this.translate("Replicator.Message.VersionUpFlash"), LOG_LEVEL_NOTICE);
685
851
  return false;
@@ -717,15 +883,18 @@ ${sendingDocs}/(${sentDocsCount} done)`,
717
883
  let ownershipTransferred = false;
718
884
  try {
719
885
  if (!skipCheck) {
720
- if (!await checkRemoteVersion(dbRet.db, this.migrate.bind(this), VER)) {
721
- Logger(this.translate("liveSyncReplicator.remoteDbCorrupted"), LOG_LEVEL_NOTICE);
722
- return false;
723
- }
724
886
  this.remoteCleaned = false;
725
887
  this.remoteLocked = false;
726
888
  this.remoteLockedAndDeviceNotAccepted = false;
727
889
  this.tweakSettingsMismatched = false;
728
890
  this.preferredTweakValue = void 0;
891
+ if (!await checkRemoteVersion(dbRet.db, this.migrate.bind(this), VER)) {
892
+ recordCompatibilityDecision?.(
893
+ centralCompatibilityRejected(CENTRAL_COMPATIBILITY_REJECTION_REASONS.INCOMPATIBLE_VERSION)
894
+ );
895
+ Logger(this.translate("liveSyncReplicator.remoteDbCorrupted"), LOG_LEVEL_NOTICE);
896
+ return false;
897
+ }
729
898
  const progress = `${dbRet.info.update_seq}`;
730
899
  const info = {
731
900
  app_version: this.env.services.API.getAppVersion(),
@@ -742,12 +911,18 @@ ${sendingDocs}/(${sentDocsCount} done)`,
742
911
  info
743
912
  );
744
913
  if (ensure == "INCOMPATIBLE") {
914
+ recordCompatibilityDecision?.(
915
+ centralCompatibilityRejected(CENTRAL_COMPATIBILITY_REJECTION_REASONS.INCOMPATIBLE_VERSION)
916
+ );
745
917
  Logger(
746
918
  "The remote database has no compatibility with the running version. Please upgrade the plugin.",
747
919
  LOG_LEVEL_NOTICE
748
920
  );
749
921
  return false;
750
922
  } else if (ensure == "NODE_LOCKED") {
923
+ recordCompatibilityDecision?.(
924
+ centralCompatibilityRejected(CENTRAL_COMPATIBILITY_REJECTION_REASONS.NODE_LOCKED)
925
+ );
751
926
  Logger(
752
927
  "The remote database has been rebuilt or corrupted since we have synchronized last time. Fetch rebuilt DB, explicit unlocking or chunk clean-up is required.",
753
928
  LOG_LEVEL_NOTICE
@@ -761,6 +936,9 @@ ${sendingDocs}/(${sentDocsCount} done)`,
761
936
  if (ignoreCleanLock) {
762
937
  this.remoteLocked = true;
763
938
  } else {
939
+ recordCompatibilityDecision?.(
940
+ centralCompatibilityRejected(CENTRAL_COMPATIBILITY_REJECTION_REASONS.NODE_CLEANED)
941
+ );
764
942
  Logger(
765
943
  "The remote database has been cleaned up. Fetch rebuilt DB, explicit unlocking or chunk clean-up is required.",
766
944
  LOG_LEVEL_NOTICE
@@ -772,11 +950,15 @@ ${sendingDocs}/(${sentDocsCount} done)`,
772
950
  }
773
951
  } else if (ensure == "OK") {
774
952
  } else if (ensure[0] == "MISMATCHED") {
953
+ recordCompatibilityDecision?.(
954
+ centralCompatibilityRejected(CENTRAL_COMPATIBILITY_REJECTION_REASONS.TWEAK_MISMATCH, ensure[1])
955
+ );
775
956
  Logger(this.translate("liveSyncReplicator.mismatchedTweakDetected"), LOG_LEVEL_NOTICE);
776
957
  this.tweakSettingsMismatched = true;
777
958
  this.preferredTweakValue = ensure[1];
778
959
  return false;
779
960
  }
961
+ recordCompatibilityDecision?.(CENTRAL_COMPATIBILITY_ACCEPTED);
780
962
  }
781
963
  const syncOptionBase = {
782
964
  batches_limit: setting.batches_limit,
@@ -808,9 +990,37 @@ ${sendingDocs}/(${sentDocsCount} done)`,
808
990
  }
809
991
  }
810
992
  }
811
- async openContinuousReplication(setting, showResult, retrying) {
993
+ openContinuousReplication(setting, showResult, retrying) {
994
+ if (this.continuousTask) {
995
+ return this.continuousTask;
996
+ }
997
+ this.continuousStopRequested = false;
998
+ this.continuousStopWaiter = void 0;
999
+ let resolveTask;
1000
+ let rejectTask;
1001
+ const task = new Promise((resolve, reject) => {
1002
+ resolveTask = resolve;
1003
+ rejectTask = reject;
1004
+ });
1005
+ this.continuousTask = task;
1006
+ void task.then(
1007
+ () => this.clearContinuousTask(task),
1008
+ () => this.clearContinuousTask(task)
1009
+ );
1010
+ void this.runContinuousReplication(setting, showResult, retrying).then(resolveTask, rejectTask);
1011
+ return task;
1012
+ }
1013
+ clearContinuousTask(task) {
1014
+ if (this.continuousTask !== task) {
1015
+ return;
1016
+ }
1017
+ this.continuousTask = void 0;
1018
+ this.continuousStopWaiter = void 0;
1019
+ this.continuousStopRequested = false;
1020
+ }
1021
+ async runContinuousReplication(setting, showResult, retrying) {
812
1022
  const next = await shareRunningResult("continuousReplication", async () => {
813
- if (this.controller) {
1023
+ if (this.continuousStopRequested || this.controller) {
814
1024
  Logger(
815
1025
  this.translate("liveSyncReplicator.replicationInProgress"),
816
1026
  showResult ? LOG_LEVEL_NOTICE : LOG_LEVEL_INFO
@@ -820,17 +1030,30 @@ ${sendingDocs}/(${sentDocsCount} done)`,
820
1030
  const localDB = this.rawDatabase;
821
1031
  Logger(this.translate("liveSyncReplicator.beforeLiveSync"));
822
1032
  const caughtUp = await this.env.services.replicator.runFiniteReplicationActivity(
823
- () => this.openOneShotReplication(setting, showResult, false, "pullOnly"),
1033
+ () => this.openOneShotReplication(
1034
+ setting,
1035
+ showResult,
1036
+ false,
1037
+ "pullOnly",
1038
+ false,
1039
+ void 0,
1040
+ () => this.continuousStopRequested
1041
+ ),
824
1042
  { label: "replication" }
825
1043
  );
826
- if (caughtUp) {
1044
+ if (caughtUp && !this.continuousStopRequested) {
827
1045
  Logger(this.translate("liveSyncReplicator.liveSyncBegin"));
828
- const ret = await this.checkReplicationConnectivity(setting, true, true, showResult);
829
- if (ret === false) {
830
- Logger(
831
- this.translate("liveSyncReplicator.couldNotConnectToServer"),
832
- showResult ? LOG_LEVEL_NOTICE : LOG_LEVEL_INFO
833
- );
1046
+ const ret = await this.checkReplicationConnectivity(setting, true, false, showResult);
1047
+ if (ret === false || this.continuousStopRequested) {
1048
+ if (ret !== false) {
1049
+ await this.closeRemoteConnection(ret);
1050
+ }
1051
+ if (ret === false && !this.continuousStopRequested) {
1052
+ Logger(
1053
+ this.translate("liveSyncReplicator.couldNotConnectToServer"),
1054
+ showResult ? LOG_LEVEL_NOTICE : LOG_LEVEL_INFO
1055
+ );
1056
+ }
834
1057
  return false;
835
1058
  }
836
1059
  if (showResult) {
@@ -838,16 +1061,25 @@ ${sendingDocs}/(${sentDocsCount} done)`,
838
1061
  }
839
1062
  const { db, syncOption } = ret;
840
1063
  try {
1064
+ if (this.continuousStopRequested) {
1065
+ return false;
1066
+ }
841
1067
  this.syncStatus = "STARTED";
842
1068
  this.maxPullSeq = Number(`${ret.info.update_seq}`.split("-")[0]);
843
1069
  this.maxPushSeq = Number(`${(await localDB.info()).update_seq}`.split("-")[0]);
1070
+ if (this.continuousStopRequested) {
1071
+ return false;
1072
+ }
844
1073
  this.updateInfo();
845
1074
  const docArrivedOnStart = this.docArrived;
846
1075
  const docSentOnStart = this.docSent;
847
1076
  if (!retrying) {
848
1077
  this.originalSetting = setting;
849
1078
  }
850
- this.terminateSync();
1079
+ this.abortController();
1080
+ if (this.continuousStopRequested) {
1081
+ return false;
1082
+ }
851
1083
  const syncHandler = localDB.sync(db, {
852
1084
  ...syncOption
853
1085
  });
@@ -860,6 +1092,9 @@ ${sendingDocs}/(${sentDocsCount} done)`,
860
1092
  syncMode,
861
1093
  retrying
862
1094
  );
1095
+ if (this.continuousStopRequested) {
1096
+ return false;
1097
+ }
863
1098
  if (syncResult == "DONE") {
864
1099
  return true;
865
1100
  }
@@ -867,8 +1102,13 @@ ${sendingDocs}/(${sentDocsCount} done)`,
867
1102
  return false;
868
1103
  }
869
1104
  if (syncResult == "NEED_RESURRECT") {
870
- this.terminateSync();
871
- return async () => await this.openContinuousReplication(this.originalSetting, showResult, false);
1105
+ this.abortController();
1106
+ return async () => {
1107
+ if (this.continuousStopRequested) {
1108
+ return false;
1109
+ }
1110
+ return await this.runContinuousReplication(this.originalSetting, showResult, false);
1111
+ };
872
1112
  }
873
1113
  if (syncResult == "NEED_RETRY") {
874
1114
  const tempSetting = JSON.parse(JSON.stringify(setting));
@@ -888,7 +1128,12 @@ ${sendingDocs}/(${sentDocsCount} done)`,
888
1128
  }),
889
1129
  showResult ? LOG_LEVEL_NOTICE : LOG_LEVEL_INFO
890
1130
  );
891
- return async () => await this.openContinuousReplication(tempSetting, showResult, true);
1131
+ return async () => {
1132
+ if (this.continuousStopRequested) {
1133
+ return false;
1134
+ }
1135
+ return await this.runContinuousReplication(tempSetting, showResult, true);
1136
+ };
892
1137
  }
893
1138
  }
894
1139
  } finally {
@@ -903,41 +1148,45 @@ ${sendingDocs}/(${sentDocsCount} done)`,
903
1148
  return await next();
904
1149
  }
905
1150
  closeReplication() {
906
- if (!this.controller) {
907
- return;
1151
+ const hadController = !!this.controller;
1152
+ const stopped = this.terminateSync();
1153
+ if (!hadController) {
1154
+ return stopped;
908
1155
  }
909
- this.controller.abort();
910
- this.controller = void 0;
911
1156
  this.syncStatus = "CLOSED";
912
1157
  Logger(this.translate("liveSyncReplicator.replicationClosed"));
913
1158
  this.updateInfo();
1159
+ return stopped;
914
1160
  }
915
1161
  async tryResetRemoteDatabase(setting) {
916
- this.closeReplication();
1162
+ await this.closeReplication();
917
1163
  const con = await this.connectRemoteCouchDBWithSetting(setting, this.isMobile(), true);
918
- if (typeof con == "string") return;
1164
+ if (typeof con == "string") {
1165
+ throw new Error(con);
1166
+ }
919
1167
  try {
920
- try {
921
- await con.db.destroy();
922
- Logger(this.translate("liveSyncReplicator.remoteDbDestroyed"), LOG_LEVEL_NOTICE);
923
- await this.tryCreateRemoteDatabase(setting);
924
- } catch (ex) {
925
- Logger(this.translate("liveSyncReplicator.remoteDbDestroyError"), LOG_LEVEL_NOTICE);
926
- Logger(ex, LOG_LEVEL_NOTICE);
927
- }
1168
+ await con.db.destroy();
1169
+ Logger(this.translate("liveSyncReplicator.remoteDbDestroyed"), LOG_LEVEL_NOTICE);
1170
+ } catch (ex) {
1171
+ Logger(this.translate("liveSyncReplicator.remoteDbDestroyError"), LOG_LEVEL_NOTICE);
1172
+ Logger(ex, LOG_LEVEL_NOTICE);
1173
+ throw ex;
928
1174
  } finally {
929
1175
  await this.closeRemoteConnection(con);
930
1176
  }
931
- clearHandlers();
932
- await this.ensurePBKDF2Salt(setting, true, false);
1177
+ await this.tryCreateRemoteDatabase(setting);
933
1178
  }
934
1179
  async tryCreateRemoteDatabase(setting) {
935
- this.closeReplication();
1180
+ await this.closeReplication();
936
1181
  const con2 = await this.connectRemoteCouchDBWithSetting(setting, this.isMobile(), true);
937
- if (typeof con2 === "string") return;
1182
+ if (typeof con2 === "string") {
1183
+ throw new Error(con2);
1184
+ }
938
1185
  await this.closeRemoteConnection(con2);
939
1186
  clearHandlers();
940
- await this.ensurePBKDF2Salt(setting, true, false);
1187
+ if (!await this.ensurePBKDF2Salt(setting, true, false)) {
1188
+ throw new Error("Could not ensure PBKDF2 salt (Security Seed)");
1189
+ }
941
1190
  Logger(this.translate("liveSyncReplicator.remoteDbCreatedOrConnected"), LOG_LEVEL_NOTICE);
942
1191
  }
943
1192
  async markRemoteLocked(setting, locked, lockByClean) {
@@ -945,12 +1194,12 @@ ${sendingDocs}/(${sentDocsCount} done)`,
945
1194
  const dbRet = await this.connectRemoteCouchDBWithSetting(setting, this.isMobile(), true);
946
1195
  if (typeof dbRet === "string") {
947
1196
  Logger(this.translate("liveSyncReplicator.couldNotConnectToURI", { uri, dbRet }), LOG_LEVEL_NOTICE);
948
- return;
1197
+ throw new Error(dbRet);
949
1198
  }
950
1199
  await this.withRemoteConnection(dbRet, async (db) => {
951
1200
  if (!await checkRemoteVersion(db, this.migrate.bind(this), VER)) {
952
1201
  Logger(this.translate("liveSyncReplicator.remoteDbCorrupted"), LOG_LEVEL_NOTICE);
953
- return;
1202
+ throw new Error("The remote database version is not compatible");
954
1203
  }
955
1204
  const defInitPoint = {
956
1205
  _id: MILESTONE_DOCID,
@@ -976,7 +1225,10 @@ ${sendingDocs}/(${sentDocsCount} done)`,
976
1225
  } else {
977
1226
  Logger(this.translate("liveSyncReplicator.unlockRemoteDb"), LOG_LEVEL_NOTICE);
978
1227
  }
979
- await db.put(remoteMilestone);
1228
+ const result = await db.put(remoteMilestone);
1229
+ if (!result.ok) {
1230
+ throw new Error("Could not update the remote database lock state");
1231
+ }
980
1232
  });
981
1233
  }
982
1234
  async markRemoteResolved(setting) {
@@ -984,12 +1236,12 @@ ${sendingDocs}/(${sentDocsCount} done)`,
984
1236
  const dbRet = await this.connectRemoteCouchDBWithSetting(setting, this.isMobile(), true);
985
1237
  if (typeof dbRet === "string") {
986
1238
  Logger(this.translate("liveSyncReplicator.couldNotConnectToURI", { uri, dbRet }), LOG_LEVEL_NOTICE);
987
- return;
1239
+ throw new Error(dbRet);
988
1240
  }
989
1241
  await this.withRemoteConnection(dbRet, async (db) => {
990
1242
  if (!await checkRemoteVersion(db, this.migrate.bind(this), VER)) {
991
1243
  Logger(this.translate("liveSyncReplicator.remoteDbCorrupted"), LOG_LEVEL_NOTICE);
992
- return;
1244
+ throw new Error("The remote database version is not compatible");
993
1245
  }
994
1246
  const defInitPoint = {
995
1247
  _id: MILESTONE_DOCID,
@@ -1013,10 +1265,11 @@ ${sendingDocs}/(${sentDocsCount} done)`,
1013
1265
  Logger(this.translate("liveSyncReplicator.remoteDbMarkedResolved"), LOG_LEVEL_VERBOSE);
1014
1266
  } else {
1015
1267
  Logger(this.translate("liveSyncReplicator.couldNotMarkResolveRemoteDb"), LOG_LEVEL_NOTICE);
1268
+ throw new Error("Could not mark the remote database as resolved");
1016
1269
  }
1017
1270
  });
1018
1271
  }
1019
- connectRemoteCouchDBWithSetting(settings, isMobile, performSetup = false, skipInfo = false, connectionOptions) {
1272
+ async connectRemoteCouchDBWithSetting(settings, isMobile, performSetup = false, skipInfo = false, connectionOptions) {
1020
1273
  if (settings.encrypt && settings.passphrase == "" && !settings.permitEmptyPassphrase) {
1021
1274
  return "Empty passphrases cannot be used without explicit permission";
1022
1275
  }
@@ -1045,7 +1298,7 @@ ${sendingDocs}/(${sentDocsCount} done)`,
1045
1298
  customHeaders,
1046
1299
  settings.useRequestAPI,
1047
1300
  async () => await this.getReplicationPBKDF2Salt(settings),
1048
- connectionOptions
1301
+ { ...connectionOptions, encryptionAlgorithm: settings.E2EEAlgorithm }
1049
1302
  );
1050
1303
  }
1051
1304
  async openOwnedConnection(settings, performSetup = false) {
@@ -1108,8 +1361,8 @@ ${sendingDocs}/(${sentDocsCount} done)`,
1108
1361
  }
1109
1362
  }
1110
1363
  }
1111
- async fetchRemoteChunks(missingChunks, showResult) {
1112
- const ret = await this.connectRemoteCouchDBWithSetting(this.currentSettings, this.isMobile(), false, true);
1364
+ async fetchRemoteChunks(missingChunks, showResult, setting = this.currentSettings) {
1365
+ const ret = await this.connectRemoteCouchDBWithSetting(setting, this.isMobile(), false, true);
1113
1366
  if (typeof ret === "string") {
1114
1367
  Logger(
1115
1368
  `${this.translate("liveSyncReplicator.couldNotConnectToServer")} ${ret} `,
@@ -1182,12 +1435,12 @@ ${sendingDocs}/(${sentDocsCount} done)`,
1182
1435
  const dbRet = await this.connectRemoteCouchDBWithSetting(setting, this.isMobile(), true);
1183
1436
  if (typeof dbRet === "string") {
1184
1437
  Logger(this.translate("liveSyncReplicator.couldNotConnectToURI", { uri, dbRet }), LOG_LEVEL_NOTICE);
1185
- return;
1438
+ throw new Error(dbRet);
1186
1439
  }
1187
1440
  await this.withRemoteConnection(dbRet, async (db) => {
1188
1441
  if (!await checkRemoteVersion(db, this.migrate.bind(this), VER)) {
1189
1442
  Logger(this.translate("liveSyncReplicator.remoteDbCorrupted"), LOG_LEVEL_NOTICE);
1190
- return;
1443
+ throw new Error("The remote database version is not compatible");
1191
1444
  }
1192
1445
  try {
1193
1446
  const remoteMilestone = await db.get(MILESTONE_DOCID);