@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.
Files changed (46) hide show
  1. package/README.md +33 -8
  2. package/dist/API/DirectFileManipulatorV2.d.ts +7 -1
  3. package/dist/API/DirectFileManipulatorV2.js +46 -67
  4. package/dist/API/DirectFileWatchOwner.d.ts +24 -0
  5. package/dist/API/DirectFileWatchOwner.js +256 -0
  6. package/dist/API/processSetting.js +3 -3
  7. package/dist/common/models/setting.type.d.ts +1 -1
  8. package/dist/common/models/shared.definition.configNames.js +1 -2
  9. package/dist/common/models/tweak.definition.d.ts +1 -1
  10. package/dist/common/pathNormalization.d.ts +2 -0
  11. package/dist/common/pathNormalization.js +7 -2
  12. package/dist/managers/ChunkFetcher.d.ts +3 -0
  13. package/dist/managers/ChunkFetcher.js +20 -13
  14. package/dist/managers/LiveSyncManagers.d.ts +2 -0
  15. package/dist/managers/LiveSyncManagers.js +1 -0
  16. package/dist/managers/chunkFetchSource.d.ts +14 -0
  17. package/dist/managers/chunkFetchSource.js +12 -0
  18. package/dist/pouchdb/LiveSyncDBFunctions.js +5 -0
  19. package/dist/pouchdb/LiveSyncLocalDB.d.ts +7 -1
  20. package/dist/pouchdb/LiveSyncLocalDB.js +13 -10
  21. package/dist/pouchdb/pathIdentityCompatibility.d.ts +8 -0
  22. package/dist/pouchdb/pathIdentityCompatibility.js +19 -1
  23. package/dist/replication/RemoteResource.d.ts +3 -0
  24. package/dist/replication/index.d.ts +2 -0
  25. package/dist/replication/index.js +2 -0
  26. package/dist/replication/journal/JournalSyncCore.d.ts +1 -0
  27. package/dist/replication/journal/JournalSyncCore.js +35 -19
  28. package/dist/replication/journal/LiveSyncJournalReplicator.d.ts +11 -3
  29. package/dist/replication/journal/LiveSyncJournalReplicator.js +123 -45
  30. package/dist/replication/trystero/P2PRoomSessionOwner.js +14 -13
  31. package/dist/replication/trystero/TrysteroReplicator.d.ts +7 -5
  32. package/dist/replication/trystero/TrysteroReplicator.js +98 -70
  33. package/dist/replication/trystero/TrysteroReplicator.pathIdentity.d.ts +11 -0
  34. package/dist/replication/trystero/TrysteroReplicator.pathIdentity.js +72 -0
  35. package/dist/replication/trystero/p2pReplicatorConfigurationIdentity.js +2 -0
  36. package/dist/replication/trystero/useP2PReplicatorFeature.js +6 -2
  37. package/dist/serviceModules/Rebuilder.d.ts +1 -0
  38. package/dist/serviceModules/Rebuilder.js +33 -5
  39. package/dist/worker/bgWorker.js +1 -1
  40. package/docs/conflict-resolution.md +1 -1
  41. package/docs/development.md +4 -0
  42. package/docs/proven-in-use.md +30 -13
  43. package/docs/releasing.md +2 -0
  44. package/docs/service-feature-composition.md +7 -1
  45. package/docs/unicode-path-identity.md +64 -3
  46. 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
- Logger(`P2P Replicating from ${remotePeer}`, logLevel, "p2p-replicator");
576
- if (this._replicateFromPeers.has(remotePeer)) {
577
- Logger(`Replication from ${remotePeer} is already in progress`, logLevel, "p2p-replicator");
578
- return p2pReplicationFailed(new Error("Replication from this peer is already in progress"));
579
- }
580
- this._replicateFromPeers.add(remotePeer);
581
- this.dispatchStatus();
582
- try {
583
- if (!this.server) {
584
- throw new Error("Server is not available");
585
- }
586
- const connection = this.server.getConnection(remotePeer);
587
- const remoteDB = connection.getRemoteDB(signal);
588
- Logger(`P2P replicateFrom preparing remote DB info for ${remotePeer}`, LOG_LEVEL_VERBOSE, "p2p-replicator");
589
- const remoteDBInfo = await remoteDB.info();
590
- Logger(
591
- `P2P replicateFrom remote DB info for ${remotePeer}: ${remoteDBInfo.db_name} seq=${remoteDBInfo.update_seq}`,
592
- LOG_LEVEL_VERBOSE,
593
- "p2p-replicator"
594
- );
595
- const localDBInfo = await this.db.info();
596
- Logger(
597
- `P2P replicateFrom local DB info for ${remotePeer}: ${localDBInfo.db_name} seq=${localDBInfo.update_seq}`,
598
- LOG_LEVEL_VERBOSE,
599
- "p2p-replicator"
600
- );
601
- Logger(`P2P replicateFrom entering replicateShim for ${remotePeer}`, LOG_LEVEL_VERBOSE, "p2p-replicator");
602
- const outcome = await replicateShim(
603
- this.db,
604
- remoteDB,
605
- async (docs, info) => {
606
- await this._env.processReplicatedDocs(docs);
607
- void this.dispatchReplicationProgress(remotePeer, info);
608
- void this.acknowledgeProgress(remotePeer, info);
609
- void this.notifyProgress(remotePeer);
610
- Logger(
611
- `P2P Replication from ${remotePeer}
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
- logLevel,
614
- "p2p-replicator"
615
- );
616
- },
617
- {
618
- live: false,
619
- rewind: fromStart,
620
- signal
621
- /*, batch_size: batchSize */
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("Replication cancelled: The encryption or path obfuscation settings do not match this peer.", logLevel);
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 { getPathNormalizationVersion } from "../../common/pathNormalization.js";
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) => settings.remoteType === REMOTE_P2P && settings.P2P_Enabled && getPathNormalizationVersion(settings) === 0,
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.replication.markLocked();
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.replication.markLocked();
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();