@vrtmrz/livesync-commonlib 0.1.19 → 0.1.21
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/README.md +5 -1
- package/dist/common/models/setting.p2p.d.ts +2 -0
- package/dist/common/models/setting.p2p.js +5 -0
- package/dist/interfaces/ServiceModule.d.ts +13 -4
- package/dist/managers/ChunkFetcher.js +10 -0
- package/dist/p2p/P2PService.d.ts +123 -0
- package/dist/p2p/P2PService.js +271 -0
- package/dist/p2p/index.d.ts +8 -0
- package/dist/p2p/index.js +8 -0
- package/dist/pouchdb/LiveSyncLocalDB.d.ts +3 -0
- package/dist/pouchdb/LiveSyncLocalDB.js +23 -3
- package/dist/pouchdb/ReplicatorShim.d.ts +43 -4
- package/dist/pouchdb/ReplicatorShim.js +78 -5
- package/dist/replication/CentralCompatibility.d.ts +38 -0
- package/dist/replication/CentralCompatibility.js +39 -0
- package/dist/replication/CentralRemoteAdministration.d.ts +84 -0
- package/dist/replication/CentralRemoteAdministration.js +71 -0
- package/dist/replication/LiveSyncAbstractReplicator.d.ts +19 -6
- package/dist/replication/ProviderCapability.d.ts +32 -0
- package/dist/replication/ProviderCapability.js +27 -0
- package/dist/replication/RemoteResource.d.ts +85 -0
- package/dist/replication/RemoteResource.js +17 -0
- package/dist/replication/ReplicatorInstance.d.ts +23 -0
- package/dist/replication/ReplicatorProvider.d.ts +262 -0
- package/dist/replication/ReplicatorProvider.js +150 -0
- package/dist/replication/couchdb/LiveSyncReplicator.d.ts +54 -8
- package/dist/replication/couchdb/LiveSyncReplicator.js +411 -158
- package/dist/replication/index.d.ts +10 -0
- package/dist/replication/index.js +72 -0
- package/dist/replication/journal/JournalSyncCore.d.ts +2 -0
- package/dist/replication/journal/JournalSyncCore.js +18 -5
- package/dist/replication/journal/LiveSyncJournalReplicator.d.ts +38 -10
- package/dist/replication/journal/LiveSyncJournalReplicator.js +271 -63
- package/dist/replication/journal/LiveSyncJournalReplicatorEnv.d.ts +8 -0
- package/dist/replication/journal/objectstore/JournalStorageAdapter.d.ts +2 -0
- package/dist/replication/journal/objectstore/MinioStorageAdapter.d.ts +2 -0
- package/dist/replication/journal/objectstore/MinioStorageAdapter.js +10 -1
- package/dist/replication/trystero/LiveSyncTrysteroReplicator.d.ts +12 -40
- package/dist/replication/trystero/LiveSyncTrysteroReplicator.js +52 -152
- package/dist/replication/trystero/P2PAutomationCoordinator.d.ts +20 -0
- package/dist/replication/trystero/P2PAutomationCoordinator.js +43 -0
- package/dist/replication/trystero/P2PConnectionProbeAdmission.d.ts +32 -0
- package/dist/replication/trystero/P2PConnectionProbeAdmission.js +10 -0
- package/dist/replication/trystero/P2PReplicationWire.d.ts +31 -0
- package/dist/replication/trystero/P2PReplicationWire.js +50 -0
- package/dist/replication/trystero/P2PRoomSession.d.ts +32 -0
- package/dist/replication/trystero/P2PRoomSession.js +101 -0
- package/dist/replication/trystero/P2PRoomSessionOwner.d.ts +61 -0
- package/dist/replication/trystero/P2PRoomSessionOwner.js +249 -0
- package/dist/replication/trystero/TrysteroReplicator.d.ts +88 -36
- package/dist/replication/trystero/TrysteroReplicator.js +273 -113
- package/dist/replication/trystero/TrysteroReplicatorP2PClient.d.ts +7 -4
- package/dist/replication/trystero/TrysteroReplicatorP2PClient.js +21 -14
- package/dist/replication/trystero/TrysteroReplicatorP2PServer.d.ts +26 -3
- package/dist/replication/trystero/TrysteroReplicatorP2PServer.js +84 -39
- package/dist/replication/trystero/UseP2PReplicatorResult.d.ts +10 -6
- package/dist/replication/trystero/addP2PEventHandlers.d.ts +8 -12
- package/dist/replication/trystero/addP2PEventHandlers.js +19 -19
- package/dist/replication/trystero/p2pReplicatorConfigurationIdentity.d.ts +9 -0
- package/dist/replication/trystero/p2pReplicatorConfigurationIdentity.js +30 -0
- package/dist/replication/trystero/types.d.ts +18 -0
- package/dist/replication/trystero/types.js +5 -1
- package/dist/replication/trystero/useP2PReplicatorCommands.d.ts +3 -3
- package/dist/replication/trystero/useP2PReplicatorCommands.js +6 -8
- package/dist/replication/trystero/useP2PReplicatorFeature.d.ts +16 -7
- package/dist/replication/trystero/useP2PReplicatorFeature.js +73 -55
- package/dist/rpc/RpcRoom.d.ts +15 -1
- package/dist/rpc/RpcRoom.js +176 -41
- package/dist/rpc/RpcSession.d.ts +3 -1
- package/dist/rpc/RpcSession.js +2 -2
- package/dist/rpc/index.d.ts +1 -1
- package/dist/rpc/transports/trysteroUtils.js +2 -1
- package/dist/rpc/types.d.ts +26 -1
- package/dist/serviceFeatures/checkRemoteSize.js +5 -2
- package/dist/serviceModules/Rebuilder.js +66 -56
- package/dist/services/base/AppLifecycleService.d.ts +4 -0
- package/dist/services/base/AppLifecycleService.js +4 -0
- package/dist/services/base/ControlService.js +0 -4
- package/dist/services/base/DatabaseEventService.js +1 -1
- package/dist/services/base/IService.d.ts +105 -8
- package/dist/services/base/ReadinessEvaluator.d.ts +40 -0
- package/dist/services/base/ReadinessEvaluator.js +16 -0
- package/dist/services/base/RemoteConnection.d.ts +3 -0
- package/dist/services/base/RemoteService.js +2 -2
- package/dist/services/base/ReplicationService.d.ts +45 -10
- package/dist/services/base/ReplicationService.js +250 -91
- package/dist/services/base/ReplicationService.readiness.d.ts +52 -0
- package/dist/services/base/ReplicationService.readiness.js +87 -0
- package/dist/services/base/ReplicationService.typedReplication.d.ts +93 -0
- package/dist/services/base/ReplicationService.typedReplication.js +214 -0
- package/dist/services/base/ReplicatorService.activeReplicatorState.d.ts +47 -0
- package/dist/services/base/ReplicatorService.activeReplicatorState.js +107 -0
- package/dist/services/base/ReplicatorService.centralRemoteAdministration.d.ts +5 -0
- package/dist/services/base/ReplicatorService.centralRemoteAdministration.js +44 -0
- package/dist/services/base/ReplicatorService.d.ts +75 -14
- package/dist/services/base/ReplicatorService.js +232 -53
- package/dist/services/base/ReplicatorService.remoteResourceResolver.d.ts +4 -0
- package/dist/services/base/ReplicatorService.remoteResourceResolver.js +11 -0
- package/dist/services/base/TweakValueService.d.ts +3 -2
- package/dist/services/implements/injectable/InjectableTweakValueService.d.ts +1 -1
- package/dist/services/lib/HandlerUtils.d.ts +24 -0
- package/dist/services/lib/HandlerUtils.js +40 -0
- package/docs/development.md +40 -7
- package/docs/p2p-transport-lifecycle.md +135 -11
- package/docs/proven-in-use.md +5 -1
- package/docs/remote-configurations.md +4 -0
- package/docs/service-feature-composition.md +250 -0
- package/package.json +17 -1
- package/dist/replication/trystero/P2PReplicatorBase.d.ts +0 -19
- package/dist/replication/trystero/P2PReplicatorCore.d.ts +0 -8
- package/dist/replication/trystero/P2PReplicatorCore.js +0 -56
- package/dist/replication/trystero/TrysteroReplicatorP2PConnection.d.ts +0 -2
- package/dist/replication/trystero/TrysteroReplicatorP2PConnection.js +0 -4
- /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
|
-
|
|
152
|
-
|
|
180
|
+
const task = this.continuousTask;
|
|
181
|
+
if (task) {
|
|
182
|
+
this.continuousStopRequested = true;
|
|
153
183
|
}
|
|
154
|
-
this.
|
|
155
|
-
|
|
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 (!
|
|
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.
|
|
269
|
+
this.abortController();
|
|
204
270
|
}
|
|
205
271
|
replicationDenied(e) {
|
|
206
272
|
this.syncStatus = "ERRORED";
|
|
207
273
|
this.updateInfo();
|
|
208
|
-
this.
|
|
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.
|
|
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.
|
|
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 (
|
|
518
|
+
const sendChunks = async (bulkDocs, bulkDocsSize, seq2) => {
|
|
457
519
|
Logger(
|
|
458
|
-
`Sending ${
|
|
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 +=
|
|
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 =
|
|
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 +=
|
|
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
|
-
|
|
531
|
-
|
|
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
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
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.
|
|
569
|
-
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
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
|
-
|
|
694
|
+
docSentOnStart,
|
|
695
|
+
docArrivedOnStart,
|
|
597
696
|
syncMode,
|
|
598
|
-
|
|
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
|
-
}
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
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
|
-
|
|
623
|
-
|
|
624
|
-
|
|
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
|
-
|
|
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(
|
|
644
|
-
|
|
645
|
-
|
|
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
|
-
|
|
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(
|
|
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,
|
|
829
|
-
if (ret === false) {
|
|
830
|
-
|
|
831
|
-
this.
|
|
832
|
-
|
|
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.
|
|
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.
|
|
871
|
-
return async () =>
|
|
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 () =>
|
|
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
|
-
|
|
907
|
-
|
|
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")
|
|
1164
|
+
if (typeof con == "string") {
|
|
1165
|
+
throw new Error(con);
|
|
1166
|
+
}
|
|
919
1167
|
try {
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
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
|
-
|
|
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")
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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);
|