@vrtmrz/livesync-commonlib 0.2.3 → 0.3.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/README.md +33 -8
- package/dist/API/DirectFileManipulatorV2.d.ts +7 -1
- package/dist/API/DirectFileManipulatorV2.js +46 -67
- package/dist/API/DirectFileWatchOwner.d.ts +24 -0
- package/dist/API/DirectFileWatchOwner.js +256 -0
- package/dist/API/processSetting.js +3 -3
- package/dist/common/models/setting.type.d.ts +1 -1
- package/dist/common/models/shared.definition.configNames.js +1 -2
- package/dist/common/models/tweak.definition.d.ts +1 -1
- package/dist/common/pathNormalization.d.ts +2 -0
- package/dist/common/pathNormalization.js +7 -2
- package/dist/managers/ChunkFetcher.d.ts +3 -0
- package/dist/managers/ChunkFetcher.js +20 -13
- package/dist/managers/LiveSyncManagers.d.ts +2 -0
- package/dist/managers/LiveSyncManagers.js +1 -0
- package/dist/managers/chunkFetchSource.d.ts +14 -0
- package/dist/managers/chunkFetchSource.js +12 -0
- package/dist/pouchdb/LiveSyncDBFunctions.js +5 -0
- package/dist/pouchdb/LiveSyncLocalDB.d.ts +7 -1
- package/dist/pouchdb/LiveSyncLocalDB.js +13 -10
- package/dist/pouchdb/pathIdentityCompatibility.d.ts +8 -0
- package/dist/pouchdb/pathIdentityCompatibility.js +19 -1
- package/dist/replication/RemoteResource.d.ts +3 -0
- package/dist/replication/index.d.ts +2 -0
- package/dist/replication/index.js +2 -0
- package/dist/replication/journal/JournalSyncCore.d.ts +1 -0
- package/dist/replication/journal/JournalSyncCore.js +35 -19
- package/dist/replication/journal/LiveSyncJournalReplicator.d.ts +11 -3
- package/dist/replication/journal/LiveSyncJournalReplicator.js +123 -45
- package/dist/replication/trystero/P2PRoomSessionOwner.js +14 -13
- package/dist/replication/trystero/TrysteroReplicator.d.ts +7 -5
- package/dist/replication/trystero/TrysteroReplicator.js +98 -70
- package/dist/replication/trystero/TrysteroReplicator.pathIdentity.d.ts +11 -0
- package/dist/replication/trystero/TrysteroReplicator.pathIdentity.js +72 -0
- package/dist/replication/trystero/p2pReplicatorConfigurationIdentity.js +2 -0
- package/dist/replication/trystero/useP2PReplicatorFeature.js +6 -2
- package/dist/serviceModules/Rebuilder.d.ts +1 -0
- package/dist/serviceModules/Rebuilder.js +33 -5
- package/dist/worker/bgWorker.js +1 -1
- package/docs/conflict-resolution.md +1 -1
- package/docs/development.md +4 -0
- package/docs/proven-in-use.md +30 -13
- package/docs/releasing.md +2 -0
- package/docs/service-feature-composition.md +7 -1
- package/docs/unicode-path-identity.md +64 -3
- package/package.json +2 -2
|
@@ -28,6 +28,9 @@ import { compatGlobal } from "../../common/coreEnvFunctions.js";
|
|
|
28
28
|
import { computeKeyedId, configuredIdKey } from "../../common/idDerivation.js";
|
|
29
29
|
import { getWebCrypto } from "../../mods.js";
|
|
30
30
|
import { uint8ArrayToHexString } from "../../string_and_binary/convert.js";
|
|
31
|
+
import { getPathNormalizationVersion } from "../../common/pathNormalization.js";
|
|
32
|
+
import { readLocalP2PPathIdentity, readRemoteP2PPathIdentity } from "./TrysteroReplicator.pathIdentity.js";
|
|
33
|
+
import { assertDatabasePathIdentity } from "../../pouchdb/pathIdentityCompatibility.js";
|
|
31
34
|
import {
|
|
32
35
|
fromP2PReplicationWireResult,
|
|
33
36
|
toP2PReplicationWireResult,
|
|
@@ -205,6 +208,11 @@ class TrysteroReplicator {
|
|
|
205
208
|
this._onSetup = false;
|
|
206
209
|
}
|
|
207
210
|
async getTweakSettings(fromPeerId) {
|
|
211
|
+
const currentSettings = this.currentSettings;
|
|
212
|
+
const pathNormalizationVersion = getPathNormalizationVersion(currentSettings);
|
|
213
|
+
if (pathNormalizationVersion === 1) {
|
|
214
|
+
await assertDatabasePathIdentity(this.db, currentSettings, true);
|
|
215
|
+
}
|
|
208
216
|
const allSettings = JSON.parse(JSON.stringify(this.settings));
|
|
209
217
|
for (const key in allSettings) {
|
|
210
218
|
if (key == "encrypt") {
|
|
@@ -223,6 +231,7 @@ class TrysteroReplicator {
|
|
|
223
231
|
const idKey = configuredIdKey({ idDerivationVersion, idDerivationKey: idSettings.idDerivationKey ?? "" });
|
|
224
232
|
return {
|
|
225
233
|
...allSettings,
|
|
234
|
+
pathNormalizationVersion,
|
|
226
235
|
idDerivationVersion,
|
|
227
236
|
idDerivationProof: idKey && idSettings.encrypt && idSettings.usePathObfuscation ? await computeKeyedId(idKey, "peer-agreement", fromPeerId) : ""
|
|
228
237
|
};
|
|
@@ -553,6 +562,28 @@ class TrysteroReplicator {
|
|
|
553
562
|
}
|
|
554
563
|
if (signal?.aborted) return P2P_REPLICATION_CANCELLED;
|
|
555
564
|
const logLevel = showNotice ? LOG_LEVEL_NOTICE : LOG_LEVEL_INFO;
|
|
565
|
+
const rejection = await this.checkPeerForReplication(remotePeer, logLevel, signal);
|
|
566
|
+
if (rejection) return rejection;
|
|
567
|
+
Logger(`P2P Replicating from ${remotePeer}`, logLevel, "p2p-replicator");
|
|
568
|
+
if (this._replicateFromPeers.has(remotePeer)) {
|
|
569
|
+
Logger(`Replication from ${remotePeer} is already in progress`, logLevel, "p2p-replicator");
|
|
570
|
+
return p2pReplicationFailed(new Error("Replication from this peer is already in progress"));
|
|
571
|
+
}
|
|
572
|
+
this._replicateFromPeers.add(remotePeer);
|
|
573
|
+
this.dispatchStatus();
|
|
574
|
+
try {
|
|
575
|
+
return await this.transferFromPeer(remotePeer, fromStart, logLevel, signal);
|
|
576
|
+
} catch (e) {
|
|
577
|
+
if (signal?.aborted) return P2P_REPLICATION_CANCELLED;
|
|
578
|
+
Logger("Error while P2P replicating", logLevel, "p2p-replicator");
|
|
579
|
+
Logger(e, LOG_LEVEL_VERBOSE);
|
|
580
|
+
return p2pReplicationFailed(e);
|
|
581
|
+
} finally {
|
|
582
|
+
this._replicateFromPeers.delete(remotePeer);
|
|
583
|
+
this.dispatchStatus();
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
async checkPeerForReplication(remotePeer, logLevel, signal) {
|
|
556
587
|
Logger(`P2P Requesting Authentication to ${remotePeer}`, logLevel, "p2p-replicator");
|
|
557
588
|
const authenticated = signal ? await this.requestAuthenticate(remotePeer, signal) : await this.requestAuthenticate(remotePeer);
|
|
558
589
|
if (authenticated !== true) {
|
|
@@ -572,71 +603,62 @@ class TrysteroReplicator {
|
|
|
572
603
|
Logger("Tweak values are not matched", logLevel, "p2p-replicator");
|
|
573
604
|
return p2pReplicationFailed(new Error("Tweak values are not matched"));
|
|
574
605
|
}
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
this.
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
const
|
|
590
|
-
Logger(
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
606
|
+
return void 0;
|
|
607
|
+
}
|
|
608
|
+
async transferFromPeer(remotePeer, fromStart, logLevel, signal) {
|
|
609
|
+
if (!this.server) {
|
|
610
|
+
throw new Error("Server is not available");
|
|
611
|
+
}
|
|
612
|
+
const connection = this.server.getConnection(remotePeer);
|
|
613
|
+
const remoteDB = connection.getRemoteDB(signal);
|
|
614
|
+
const pathNormalizationVersion = getPathNormalizationVersion(this.currentSettings);
|
|
615
|
+
const [localPathIdentity, remotePathIdentity] = await Promise.all([
|
|
616
|
+
readLocalP2PPathIdentity(this.db, pathNormalizationVersion),
|
|
617
|
+
readRemoteP2PPathIdentity(remoteDB, pathNormalizationVersion)
|
|
618
|
+
]);
|
|
619
|
+
if (localPathIdentity !== pathNormalizationVersion || remotePathIdentity !== pathNormalizationVersion) {
|
|
620
|
+
const error = new Error("P2P database path identity does not match the configured path identity.");
|
|
621
|
+
Logger(error.message, logLevel, "p2p-replicator");
|
|
622
|
+
return p2pReplicationFailed(error);
|
|
623
|
+
}
|
|
624
|
+
Logger(`P2P replicateFrom preparing remote DB info for ${remotePeer}`, LOG_LEVEL_VERBOSE, "p2p-replicator");
|
|
625
|
+
const remoteDBInfo = await remoteDB.info();
|
|
626
|
+
Logger(
|
|
627
|
+
`P2P replicateFrom remote DB info for ${remotePeer}: ${remoteDBInfo.db_name} seq=${remoteDBInfo.update_seq}`,
|
|
628
|
+
LOG_LEVEL_VERBOSE,
|
|
629
|
+
"p2p-replicator"
|
|
630
|
+
);
|
|
631
|
+
const localDBInfo = await this.db.info();
|
|
632
|
+
Logger(
|
|
633
|
+
`P2P replicateFrom local DB info for ${remotePeer}: ${localDBInfo.db_name} seq=${localDBInfo.update_seq}`,
|
|
634
|
+
LOG_LEVEL_VERBOSE,
|
|
635
|
+
"p2p-replicator"
|
|
636
|
+
);
|
|
637
|
+
Logger(`P2P replicateFrom entering replicateShim for ${remotePeer}`, LOG_LEVEL_VERBOSE, "p2p-replicator");
|
|
638
|
+
const outcome = await replicateShim(
|
|
639
|
+
this.db,
|
|
640
|
+
remoteDB,
|
|
641
|
+
async (docs, info) => {
|
|
642
|
+
await this._env.processReplicatedDocs(docs);
|
|
643
|
+
void this.dispatchReplicationProgress(remotePeer, info);
|
|
644
|
+
void this.acknowledgeProgress(remotePeer, info);
|
|
645
|
+
void this.notifyProgress(remotePeer);
|
|
646
|
+
Logger(
|
|
647
|
+
`P2P Replication from ${remotePeer}
|
|
612
648
|
${info.lastSeq} / ${info.maxSeqInBatch})`,
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
Logger(`P2P replicateFrom replicateShim returned for ${remotePeer}`, LOG_LEVEL_VERBOSE, "p2p-replicator");
|
|
625
|
-
void this.acknowledgeProgress(remotePeer, void 0);
|
|
626
|
-
if (outcome.status === "cancelled") {
|
|
627
|
-
Logger(`P2P Replication from ${remotePeer} has been cancelled`, logLevel, "p2p-replicator");
|
|
628
|
-
return P2P_REPLICATION_CANCELLED;
|
|
629
|
-
}
|
|
630
|
-
Logger(`P2P Replication from ${remotePeer} has been completed`, logLevel, "p2p-replicator");
|
|
631
|
-
} catch (e) {
|
|
632
|
-
if (signal?.aborted) return P2P_REPLICATION_CANCELLED;
|
|
633
|
-
Logger("Error while P2P replicating", logLevel, "p2p-replicator");
|
|
634
|
-
Logger(e, LOG_LEVEL_VERBOSE);
|
|
635
|
-
return p2pReplicationFailed(e);
|
|
636
|
-
} finally {
|
|
637
|
-
this._replicateFromPeers.delete(remotePeer);
|
|
638
|
-
this.dispatchStatus();
|
|
649
|
+
logLevel,
|
|
650
|
+
"p2p-replicator"
|
|
651
|
+
);
|
|
652
|
+
},
|
|
653
|
+
{ live: false, rewind: fromStart, signal }
|
|
654
|
+
);
|
|
655
|
+
Logger(`P2P replicateFrom replicateShim returned for ${remotePeer}`, LOG_LEVEL_VERBOSE, "p2p-replicator");
|
|
656
|
+
void this.acknowledgeProgress(remotePeer, void 0);
|
|
657
|
+
if (outcome.status === "cancelled") {
|
|
658
|
+
Logger(`P2P Replication from ${remotePeer} has been cancelled`, logLevel, "p2p-replicator");
|
|
659
|
+
return P2P_REPLICATION_CANCELLED;
|
|
639
660
|
}
|
|
661
|
+
Logger(`P2P Replication from ${remotePeer} has been completed`, logLevel, "p2p-replicator");
|
|
640
662
|
return P2P_REPLICATION_COMPLETED;
|
|
641
663
|
}
|
|
642
664
|
notifyProgress(excludePeerId) {
|
|
@@ -739,12 +761,6 @@ ${info.lastSeq} / ${info.maxSeqInBatch})`,
|
|
|
739
761
|
Logger("Peer is not found", logLevel);
|
|
740
762
|
return false;
|
|
741
763
|
}
|
|
742
|
-
if (this.platform === "pseudo-replicator") {
|
|
743
|
-
return true;
|
|
744
|
-
}
|
|
745
|
-
if (peerPlatform === "pseudo-replicator") {
|
|
746
|
-
return true;
|
|
747
|
-
}
|
|
748
764
|
const connection = this.server.getConnection(peerId);
|
|
749
765
|
const challengeBytes = new Uint8Array(16);
|
|
750
766
|
(await getWebCrypto()).getRandomValues(challengeBytes);
|
|
@@ -752,6 +768,15 @@ ${info.lastSeq} / ${info.maxSeqInBatch})`,
|
|
|
752
768
|
const tweakValues = await connection.invokeRemoteObjectFunction("getTweakSettings", [challenge], 5e3, signal);
|
|
753
769
|
if (signal?.aborted) return false;
|
|
754
770
|
const thisTweakValues = await this.getTweakSettings(challenge);
|
|
771
|
+
const localPathNormalizationVersion = getPathNormalizationVersion(thisTweakValues);
|
|
772
|
+
const peerPathNormalizationVersion = getPathNormalizationVersion(tweakValues);
|
|
773
|
+
if (localPathNormalizationVersion !== peerPathNormalizationVersion) {
|
|
774
|
+
Logger("Replication cancelled: The path identity mode does not match this peer.", logLevel);
|
|
775
|
+
return false;
|
|
776
|
+
}
|
|
777
|
+
if (this.platform === "pseudo-replicator" || peerPlatform === "pseudo-replicator") {
|
|
778
|
+
return true;
|
|
779
|
+
}
|
|
755
780
|
if (thisTweakValues.passphrase !== tweakValues.passphrase) {
|
|
756
781
|
Logger(
|
|
757
782
|
"Replication cancelled: Passphrase is not matched\nCannot replicate to a remote database until the problem is resolved.",
|
|
@@ -760,7 +785,10 @@ ${info.lastSeq} / ${info.maxSeqInBatch})`,
|
|
|
760
785
|
return false;
|
|
761
786
|
}
|
|
762
787
|
if (thisTweakValues.encrypt !== tweakValues.encrypt || thisTweakValues.usePathObfuscation !== tweakValues.usePathObfuscation) {
|
|
763
|
-
Logger(
|
|
788
|
+
Logger(
|
|
789
|
+
"Replication cancelled: The encryption or path obfuscation settings do not match this peer.",
|
|
790
|
+
logLevel
|
|
791
|
+
);
|
|
764
792
|
return false;
|
|
765
793
|
}
|
|
766
794
|
if (thisTweakValues.encrypt && thisTweakValues.usePathObfuscation && ((thisTweakValues.idDerivationVersion ?? 0) !== (tweakValues.idDerivationVersion ?? 0) || (thisTweakValues.idDerivationProof ?? "") !== (tweakValues.idDerivationProof ?? ""))) {
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { PathNormalizationVersion } from "../../common/models/setting.type.js";
|
|
2
|
+
export declare function readLocalP2PPathIdentity(database: {
|
|
3
|
+
get(id: string): Promise<unknown>;
|
|
4
|
+
}, configuredVersion: PathNormalizationVersion): Promise<PathNormalizationVersion>;
|
|
5
|
+
export declare function readRemoteP2PPathIdentity(database: {
|
|
6
|
+
bulkGet(options: {
|
|
7
|
+
docs: {
|
|
8
|
+
id: string;
|
|
9
|
+
}[];
|
|
10
|
+
}): Promise<unknown>;
|
|
11
|
+
}, configuredVersion: PathNormalizationVersion): Promise<PathNormalizationVersion>;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { VERSIONING_DOCID } from "../../common/models/db.const.js";
|
|
2
|
+
import {
|
|
3
|
+
assessRemoteFeatureDocument,
|
|
4
|
+
describeRemoteFeatureRejection,
|
|
5
|
+
NFC_PATH_IDENTITY_FEATURE
|
|
6
|
+
} from "../../pouchdb/remoteFeatureCompatibility.js";
|
|
7
|
+
import { isErrorOfMissingDoc } from "../../pouchdb/utils_couchdb.js";
|
|
8
|
+
function isRecord(value) {
|
|
9
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
10
|
+
}
|
|
11
|
+
function hasOwnField(value, key) {
|
|
12
|
+
return Object.hasOwn(value, key);
|
|
13
|
+
}
|
|
14
|
+
function isVersionInfoResponse(value) {
|
|
15
|
+
if (!isRecord(value) || !Array.isArray(value.results) || value.results.length !== 1) return false;
|
|
16
|
+
const [result] = value.results;
|
|
17
|
+
return isRecord(result) && result.id === VERSIONING_DOCID && Array.isArray(result.docs) && result.docs.length === 1 && isRecord(result.docs[0]);
|
|
18
|
+
}
|
|
19
|
+
function isMissingDocumentResult(result) {
|
|
20
|
+
if (!hasOwnField(result, "error")) return false;
|
|
21
|
+
const error = result.error;
|
|
22
|
+
const errorDetails = isRecord(error) ? error : void 0;
|
|
23
|
+
const status = errorDetails?.status ?? result.status;
|
|
24
|
+
const errorName = errorDetails?.name ?? result.name;
|
|
25
|
+
const errorCode = typeof error === "string" ? error : errorDetails?.error;
|
|
26
|
+
return status === 404 || status === void 0 && (errorName === "not_found" || errorCode === "not_found");
|
|
27
|
+
}
|
|
28
|
+
function parseRemotePathIdentityDocument(response) {
|
|
29
|
+
if (!isVersionInfoResponse(response)) {
|
|
30
|
+
throw new Error("P2P remote versioninfo response is malformed.");
|
|
31
|
+
}
|
|
32
|
+
const [documentResult] = response.results[0].docs;
|
|
33
|
+
if (hasOwnField(documentResult, "ok")) {
|
|
34
|
+
const document = documentResult.ok;
|
|
35
|
+
if (!isRecord(document) || document._id !== VERSIONING_DOCID) {
|
|
36
|
+
throw new Error("P2P remote versioninfo response is malformed.");
|
|
37
|
+
}
|
|
38
|
+
return { kind: "found", document };
|
|
39
|
+
}
|
|
40
|
+
if (isMissingDocumentResult(documentResult)) return { kind: "missing" };
|
|
41
|
+
throw new Error("P2P remote versioninfo could not be read.");
|
|
42
|
+
}
|
|
43
|
+
function resolvePathIdentity(result, configuredVersion) {
|
|
44
|
+
if (result.kind === "missing") {
|
|
45
|
+
if (configuredVersion === 0) return 0;
|
|
46
|
+
throw new Error("P2P NFC database is missing path identity version information.");
|
|
47
|
+
}
|
|
48
|
+
const assessment = assessRemoteFeatureDocument(result.document);
|
|
49
|
+
if (assessment.status === "older-generation" && configuredVersion === 0) return 0;
|
|
50
|
+
if (assessment.status !== "supported") {
|
|
51
|
+
throw new Error(describeRemoteFeatureRejection(assessment));
|
|
52
|
+
}
|
|
53
|
+
return assessment.usedFeatures.includes(NFC_PATH_IDENTITY_FEATURE) ? 1 : 0;
|
|
54
|
+
}
|
|
55
|
+
async function readLocalP2PPathIdentity(database, configuredVersion) {
|
|
56
|
+
let result;
|
|
57
|
+
try {
|
|
58
|
+
result = { kind: "found", document: await database.get(VERSIONING_DOCID) };
|
|
59
|
+
} catch (error) {
|
|
60
|
+
if (!isErrorOfMissingDoc(error)) throw error;
|
|
61
|
+
result = { kind: "missing" };
|
|
62
|
+
}
|
|
63
|
+
return resolvePathIdentity(result, configuredVersion);
|
|
64
|
+
}
|
|
65
|
+
async function readRemoteP2PPathIdentity(database, configuredVersion) {
|
|
66
|
+
const response = await database.bulkGet({ docs: [{ id: VERSIONING_DOCID }] });
|
|
67
|
+
return resolvePathIdentity(parseRemotePathIdentityDocument(response), configuredVersion);
|
|
68
|
+
}
|
|
69
|
+
export {
|
|
70
|
+
readLocalP2PPathIdentity,
|
|
71
|
+
readRemoteP2PPathIdentity
|
|
72
|
+
};
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
splitP2PRelayUrls,
|
|
8
8
|
splitP2PTurnServerUrls
|
|
9
9
|
} from "../../common/models/setting.p2p.js";
|
|
10
|
+
import { getPathNormalizationVersion } from "../../common/pathNormalization.js";
|
|
10
11
|
function getP2PReplicatorConfigurationIdentity(settings) {
|
|
11
12
|
const managedTurnSelected = hasManagedP2PTurnConfiguration(settings);
|
|
12
13
|
const turnServerValue = settings.P2P_turnServers ?? "";
|
|
@@ -25,6 +26,7 @@ function getP2PReplicatorConfigurationIdentity(settings) {
|
|
|
25
26
|
managedTurnSelected ? [settings.P2P_managedType, settings.P2P_managedId, settings.P2P_managedToken] : void 0,
|
|
26
27
|
normaliseP2PMaxWirePayloadBytes(settings.P2P_maxWirePayloadBytes),
|
|
27
28
|
effectivePath,
|
|
29
|
+
getPathNormalizationVersion(settings),
|
|
28
30
|
settings.P2P_useDiagRTC ?? false
|
|
29
31
|
]);
|
|
30
32
|
}
|
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
supportedStopActiveTransfer
|
|
16
16
|
} from "../index.js";
|
|
17
17
|
import { getP2PReplicatorConfigurationIdentity } from "./p2pReplicatorConfigurationIdentity.js";
|
|
18
|
-
import {
|
|
18
|
+
import { assertSupportedPathNormalizationVersion } from "../../common/pathNormalization.js";
|
|
19
19
|
function useP2PReplicatorFeature(host, openReplicationUIFactory, openRebuildUIFactory, options = {}) {
|
|
20
20
|
const service = createP2PService(
|
|
21
21
|
{
|
|
@@ -63,7 +63,11 @@ function useP2PReplicatorFeature(host, openReplicationUIFactory, openRebuildUIFa
|
|
|
63
63
|
kind: REMOTE_P2P,
|
|
64
64
|
diagnosticName: "P2P",
|
|
65
65
|
readiness: PEER_REPLICATION_READINESS,
|
|
66
|
-
isConfigured: (settings) =>
|
|
66
|
+
isConfigured: (settings) => {
|
|
67
|
+
if (settings.remoteType !== REMOTE_P2P || !settings.P2P_Enabled) return false;
|
|
68
|
+
assertSupportedPathNormalizationVersion(settings.pathNormalizationVersion);
|
|
69
|
+
return true;
|
|
70
|
+
},
|
|
67
71
|
configurationIdentity: getP2PReplicatorConfigurationIdentity,
|
|
68
72
|
create: createP2PReplicator,
|
|
69
73
|
remoteResources: NO_REMOTE_RESOURCE_CAPABILITIES,
|
|
@@ -66,6 +66,7 @@ export declare class ServiceRebuilder extends ServiceModuleBase<ServiceRebuilder
|
|
|
66
66
|
private scheduleInitialisation;
|
|
67
67
|
scheduleRebuild(prepareBeforeRestart?: () => Promise<void>): Promise<boolean>;
|
|
68
68
|
scheduleFetch(prepareBeforeRestart?: () => Promise<void>): Promise<boolean>;
|
|
69
|
+
private markRemoteLockedForRebuild;
|
|
69
70
|
private _tryResetRemoteDatabase;
|
|
70
71
|
private _onResetLocalDatabase;
|
|
71
72
|
suspendAllSync(): Promise<void>;
|
|
@@ -26,6 +26,12 @@ const FAST_FETCH_RETRY_DELAYS = [2e3, 5e3, 1e4, 2e4];
|
|
|
26
26
|
function canResetCentralRemote(replicator) {
|
|
27
27
|
return "tryResetRemoteDatabase" in replicator && typeof replicator.tryResetRemoteDatabase === "function";
|
|
28
28
|
}
|
|
29
|
+
function canCheckJournalPathIdentity(replicator) {
|
|
30
|
+
return "assertRemotePathIdentity" in replicator && typeof replicator.assertRemotePathIdentity === "function";
|
|
31
|
+
}
|
|
32
|
+
function canLockJournalForRebuild(replicator) {
|
|
33
|
+
return canResetCentralRemote(replicator) && "markRemoteLockedForRebuild" in replicator && typeof replicator.markRemoteLockedForRebuild === "function";
|
|
34
|
+
}
|
|
29
35
|
class ServiceRebuilder extends ServiceModuleBase {
|
|
30
36
|
events;
|
|
31
37
|
appLifecycle;
|
|
@@ -100,7 +106,7 @@ Please enable them from the settings screen after setup is complete.`,
|
|
|
100
106
|
notifyThresholdOfRemoteStorageSize: DEFAULT_SETTINGS.notifyThresholdOfRemoteStorageSize
|
|
101
107
|
});
|
|
102
108
|
await this.control.applySettings();
|
|
103
|
-
await this.
|
|
109
|
+
await this.markRemoteLockedForRebuild();
|
|
104
110
|
await this._tryResetRemoteDatabase();
|
|
105
111
|
await this.replication.markLocked();
|
|
106
112
|
await delay(500);
|
|
@@ -145,7 +151,7 @@ Please enable them from the settings screen after setup is complete.`,
|
|
|
145
151
|
}
|
|
146
152
|
return;
|
|
147
153
|
}
|
|
148
|
-
await this.
|
|
154
|
+
await this.markRemoteLockedForRebuild();
|
|
149
155
|
await this._tryResetRemoteDatabase();
|
|
150
156
|
await this.replication.markLocked();
|
|
151
157
|
await delay(500);
|
|
@@ -170,9 +176,6 @@ Please enable them from the settings screen after setup is complete.`,
|
|
|
170
176
|
await assertDatabasePathIdentity(this.database.localDatabase.localDatabase, settings);
|
|
171
177
|
}
|
|
172
178
|
if (getPathNormalizationVersion(settings) === 0) return;
|
|
173
|
-
if (settings.remoteType !== REMOTE_COUCHDB) {
|
|
174
|
-
throw new Error("NFC path identity is supported only with CouchDB. No database has been reset.");
|
|
175
|
-
}
|
|
176
179
|
const paths = await this.storageAccess.getFileNames();
|
|
177
180
|
const selected = [];
|
|
178
181
|
const databaseReady = this.database.isDatabaseReady();
|
|
@@ -191,6 +194,19 @@ Please enable them from the settings screen after setup is complete.`,
|
|
|
191
194
|
}
|
|
192
195
|
async checkRemotePathIdentityBeforeFetch() {
|
|
193
196
|
const settings = this.setting.currentSettings();
|
|
197
|
+
if (settings.remoteType === REMOTE_MINIO) {
|
|
198
|
+
const trial = await this.replicator.getNewReplicator(settings);
|
|
199
|
+
if (!trial) throw new Error("Could not create a Journal replicator for the Fetch identity check.");
|
|
200
|
+
try {
|
|
201
|
+
if (!canCheckJournalPathIdentity(trial)) {
|
|
202
|
+
throw new Error("The Journal replicator does not support the Fetch identity check.");
|
|
203
|
+
}
|
|
204
|
+
await trial.assertRemotePathIdentity(settings);
|
|
205
|
+
} finally {
|
|
206
|
+
await trial.closeReplication();
|
|
207
|
+
}
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
194
210
|
if (settings.remoteType !== REMOTE_COUCHDB) return;
|
|
195
211
|
const remote = settings.couchDB_URI.replace(/\/+$/, "") + (settings.couchDB_DBNAME === "" ? "" : "/" + settings.couchDB_DBNAME);
|
|
196
212
|
const connection = await this.remote.connect(
|
|
@@ -262,6 +278,18 @@ Please enable them from the settings screen after setup is complete.`,
|
|
|
262
278
|
scheduleFetch(prepareBeforeRestart) {
|
|
263
279
|
return this.scheduleInitialisation(FlagFilesHumanReadable.FETCH_ALL, prepareBeforeRestart);
|
|
264
280
|
}
|
|
281
|
+
async markRemoteLockedForRebuild() {
|
|
282
|
+
const settings = this.setting.currentSettings();
|
|
283
|
+
if (settings.remoteType !== REMOTE_MINIO) {
|
|
284
|
+
await this.replication.markLocked();
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
const replicator = this.replicator.getActiveReplicator();
|
|
288
|
+
if (!replicator || !canLockJournalForRebuild(replicator)) {
|
|
289
|
+
throw new Error("The active Journal replicator does not support safe reconstruction.");
|
|
290
|
+
}
|
|
291
|
+
await replicator.markRemoteLockedForRebuild(settings);
|
|
292
|
+
}
|
|
265
293
|
async _tryResetRemoteDatabase() {
|
|
266
294
|
const currentReplicator = this.replicator.getActiveReplicator();
|
|
267
295
|
const settings = this.setting.currentSettings();
|