@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
@@ -12,7 +12,7 @@ import { MinioStorageAdapter } from "./objectstore/MinioStorageAdapter.js";
12
12
  import { LiveSyncAbstractReplicator } from "../LiveSyncAbstractReplicator.js";
13
13
  import { ensureRemoteIsCompatible } from "../../pouchdb/LiveSyncDBFunctions.js";
14
14
  import { getEffectiveTweakValues } from "../../pouchdb/LiveSyncDBFunctions.js";
15
- import { getPathNormalizationVersion } from "../../common/pathNormalization.js";
15
+ import { assertSupportedPathNormalizationVersion, getPathNormalizationVersion } from "../../common/pathNormalization.js";
16
16
  import { JournalStorageReadStatuses } from "./objectstore/JournalStorageAdapter.js";
17
17
  import { computeKeyedId, configuredIdKey } from "../../common/idDerivation.js";
18
18
  import { decryptString, encryptString } from "../../encryption/stringEncryption.js";
@@ -44,8 +44,17 @@ const currentVersionRange = {
44
44
  current: 2
45
45
  };
46
46
  function journalVersionRange(setting) {
47
+ if (getPathNormalizationVersion(setting) === 1) return { min: 4, max: 4, current: 4 };
47
48
  return configuredIdKey(setting) && setting.encrypt && setting.usePathObfuscation ? { min: 3, max: 3, current: 3 } : currentVersionRange;
48
49
  }
50
+ function pathIdentityFromJournalRange(range) {
51
+ if (range?.min === 4 && range?.max === 4) return 1;
52
+ if (!range) return 0;
53
+ if (Number.isSafeInteger(range.min) && Number.isSafeInteger(range.max) && range.min >= 0 && range.max <= 3 && range.min <= range.max) {
54
+ return 0;
55
+ }
56
+ throw new Error("The Journal declares an unsupported path identity range.");
57
+ }
49
58
  class LiveSyncJournalReplicator extends LiveSyncAbstractReplicator {
50
59
  get client() {
51
60
  return this.setupJournalSyncClient();
@@ -66,7 +75,7 @@ class LiveSyncJournalReplicator extends LiveSyncAbstractReplicator {
66
75
  return await this.setupJournalSyncClient(setting).getReplicationPBKDF2Salt(refresh);
67
76
  }
68
77
  setupJournalSyncClient(settings = this.currentSettings) {
69
- this.assertLegacyPathIdentity(settings);
78
+ assertSupportedPathNormalizationVersion(settings.pathNormalizationVersion);
70
79
  if (this._client) {
71
80
  this._client.applyNewConfig(settings, this.simpleStore, this.env);
72
81
  } else {
@@ -79,33 +88,105 @@ class LiveSyncJournalReplicator extends LiveSyncAbstractReplicator {
79
88
  }
80
89
  return this._client;
81
90
  }
82
- async ensureBucketIsCompatible(deviceNodeID, currentVersionRange2, setting = this.currentSettings, client = this.setupJournalSyncClient(setting), recordTweakAssessment) {
83
- const milestoneResult = await client.downloadJsonWithResult(MILSTONE_DOCID);
84
- if (milestoneResult.status === JournalStorageReadStatuses.UNAVAILABLE) {
85
- throw milestoneResult.error;
91
+ async isBucketPathIdentityCompatible(setting, milestone, client) {
92
+ const pathVersion = getPathNormalizationVersion(setting);
93
+ const ranges = milestone?.node_chunk_info ?? {};
94
+ const nodes = /* @__PURE__ */ new Set([...milestone?.accepted_nodes ?? [], ...Object.keys(ranges)]);
95
+ for (const node of nodes) {
96
+ const range = ranges[node];
97
+ const usesNFC = range?.min === 4 && range?.max === 4;
98
+ if (usesNFC !== (pathVersion === 1)) return false;
99
+ }
100
+ const preferred = milestone?.tweak_values?.[DEVICE_ID_PREFERRED];
101
+ if (preferred && getPathNormalizationVersion(preferred) !== pathVersion) return false;
102
+ if (nodes.size === 0 && await client.hasRemoteJournals()) {
103
+ throw new Error(
104
+ "The Journal compatibility milestone is missing. Rebuild from the authoritative Vault before synchronising."
105
+ );
86
106
  }
87
- const downloadedMilestone = milestoneResult.status === JournalStorageReadStatuses.AVAILABLE ? milestoneResult.value : false;
107
+ return true;
108
+ }
109
+ async isBucketIdDerivationCompatible(setting, milestone) {
88
110
  const idKey = configuredIdKey(setting);
89
- let encryptedProof;
90
- const preferredTweak = downloadedMilestone && downloadedMilestone.tweak_values?.[DEVICE_ID_PREFERRED];
91
- if (preferredTweak?.idDerivationVersion === 1 && preferredTweak.usePathObfuscation === true && (!idKey || !setting.encrypt || !setting.usePathObfuscation)) {
92
- return "ID_KEY_MISMATCH";
111
+ const usesKeyedDocumentIds = !!idKey && setting.encrypt && setting.usePathObfuscation;
112
+ const preferred = milestone?.tweak_values?.[DEVICE_ID_PREFERRED];
113
+ const stored = milestone?.encrypted_id_derivation_proof;
114
+ if (!usesKeyedDocumentIds) {
115
+ return !stored && !(preferred?.idDerivationVersion === 1 && preferred.usePathObfuscation === true);
93
116
  }
94
- if (idKey && setting.encrypt && setting.usePathObfuscation) {
95
- const expected = await computeKeyedId(idKey, "remote-agreement", "journal-milestone-v1");
96
- const stored = downloadedMilestone && downloadedMilestone.encrypted_id_derivation_proof;
97
- if (stored) {
98
- try {
99
- if (await decryptString(stored, setting.passphrase) !== expected) return "ID_KEY_MISMATCH";
100
- } catch {
101
- return "ID_KEY_MISMATCH";
102
- }
103
- } else if (downloadedMilestone) {
104
- return "ID_KEY_MISMATCH";
105
- } else {
106
- encryptedProof = await encryptString(expected, setting.passphrase);
107
- }
117
+ if (!milestone) return true;
118
+ if (!stored) return false;
119
+ const expected = await computeKeyedId(idKey, "remote-agreement", "journal-milestone-v1");
120
+ try {
121
+ return await decryptString(stored, setting.passphrase) === expected;
122
+ } catch {
123
+ return false;
124
+ }
125
+ }
126
+ async assertBucketIdentity(setting, milestone, client) {
127
+ if (!await this.isBucketPathIdentityCompatible(setting, milestone, client)) {
128
+ throw new Error(
129
+ "The Journal and settings use different path identity versions. Rebuild from the authoritative Vault or Fetch with matching settings before synchronising."
130
+ );
108
131
  }
132
+ if (!await this.isBucketIdDerivationCompatible(setting, milestone)) {
133
+ throw new Error(
134
+ "The Journal and settings use different document ID derivation modes or keys. Rebuild from the authoritative Vault or Fetch with matching settings before synchronising."
135
+ );
136
+ }
137
+ }
138
+ /** Inspect identity without publishing this node, preparing a seed, or changing checkpoints. */
139
+ async assertRemotePathIdentity(setting) {
140
+ const client = this.setupJournalSyncClient(setting);
141
+ await this.assertBucketIdentity(setting, await readRemoteMilestone(client), client);
142
+ }
143
+ /** Read path identity for initial setup without publishing a node or preparing a seed. */
144
+ async inspectRemotePathIdentity(setting) {
145
+ const client = this.setupJournalSyncClient(setting);
146
+ const milestone = await readRemoteMilestone(client);
147
+ const versions = /* @__PURE__ */ new Set();
148
+ const ranges = milestone?.node_chunk_info ?? {};
149
+ const nodes = /* @__PURE__ */ new Set([...milestone?.accepted_nodes ?? [], ...Object.keys(ranges)]);
150
+ for (const node of nodes) {
151
+ versions.add(pathIdentityFromJournalRange(ranges[node]));
152
+ }
153
+ const preferred = milestone?.tweak_values?.[DEVICE_ID_PREFERRED];
154
+ if (preferred) versions.add(getPathNormalizationVersion(preferred));
155
+ if (versions.size > 1) throw new Error("The Journal has conflicting path identity declarations.");
156
+ if (nodes.size === 0 && await client.hasRemoteJournals()) {
157
+ throw new Error(
158
+ "The Journal path identity declaration is missing. Use matching settings or Rebuild from the authoritative Vault."
159
+ );
160
+ }
161
+ const version = [...versions][0];
162
+ return version === void 0 ? { status: "empty" } : { status: "configured", pathNormalizationVersion: version };
163
+ }
164
+ /** Lock existing journals for replacement without declaring the replacement's identity. */
165
+ async markRemoteLockedForRebuild(setting) {
166
+ assertSupportedPathNormalizationVersion(setting.pathNormalizationVersion);
167
+ const client = this.setupJournalSyncClient(setting);
168
+ const milestone = await readRemoteMilestone(client);
169
+ if (!milestone) return;
170
+ const previousRange = Object.values(milestone.node_chunk_info ?? {}).sort((a, b) => b.min - a.min)[0];
171
+ const lockedMilestone = {
172
+ ...milestone,
173
+ locked: true,
174
+ accepted_nodes: previousRange ? [this.nodeid] : [],
175
+ node_chunk_info: previousRange ? { ...milestone.node_chunk_info, [this.nodeid]: { ...previousRange } } : milestone.node_chunk_info
176
+ };
177
+ if (!await client.uploadJson(MILSTONE_DOCID, lockedMilestone)) {
178
+ throw new Error("Could not upload remote milestone");
179
+ }
180
+ }
181
+ async ensureBucketIsCompatible(deviceNodeID, currentVersionRange2, setting = this.currentSettings, client = this.setupJournalSyncClient(setting), recordTweakAssessment) {
182
+ const downloadedMilestone = await readRemoteMilestone(client) ?? false;
183
+ if (!await this.isBucketPathIdentityCompatible(setting, downloadedMilestone || void 0, client)) {
184
+ return "INCOMPATIBLE";
185
+ }
186
+ if (!await this.isBucketIdDerivationCompatible(setting, downloadedMilestone || void 0)) {
187
+ return "ID_KEY_MISMATCH";
188
+ }
189
+ const encryptedProof = downloadedMilestone ? void 0 : await this.createInitialIdDerivationProof(setting);
109
190
  const cPointInfo = await client.getCheckpointInfo();
110
191
  const progress = [...cPointInfo?.receivedFiles || []].sort().pop() || "";
111
192
  return await ensureRemoteIsCompatible(
@@ -291,7 +372,7 @@ class LiveSyncJournalReplicator extends LiveSyncAbstractReplicator {
291
372
  }
292
373
  async checkReplicationConnectivity(skipCheck, ignoreCleanLock = false, showMessage = false, setting = this.currentSettings, client, recordCompatibilityDecision) {
293
374
  recordCompatibilityDecision?.(CENTRAL_COMPATIBILITY_NOT_ASSESSED);
294
- if (!this.supportsLegacyPathIdentity(setting)) return false;
375
+ assertSupportedPathNormalizationVersion(setting.pathNormalizationVersion);
295
376
  const activeClient = client ?? this.setupJournalSyncClient(setting);
296
377
  if (!await activeClient.isAvailable()) {
297
378
  return false;
@@ -388,7 +469,7 @@ class LiveSyncJournalReplicator extends LiveSyncAbstractReplicator {
388
469
  this.updateInfo();
389
470
  }
390
471
  async tryResetRemoteDatabase(setting) {
391
- this.assertLegacyPathIdentity(setting);
472
+ assertSupportedPathNormalizationVersion(setting.pathNormalizationVersion);
392
473
  this.closeReplication();
393
474
  try {
394
475
  if (!await this.client.resetBucket()) {
@@ -403,7 +484,7 @@ class LiveSyncJournalReplicator extends LiveSyncAbstractReplicator {
403
484
  }
404
485
  }
405
486
  async tryCreateRemoteDatabase(setting) {
406
- this.assertLegacyPathIdentity(setting);
487
+ assertSupportedPathNormalizationVersion(setting.pathNormalizationVersion);
407
488
  this.closeReplication();
408
489
  Logger("Remote Database Created or Connected", LOG_LEVEL_NOTICE);
409
490
  if (!await this.ensurePBKDF2Salt(setting, true, false)) {
@@ -418,7 +499,7 @@ class LiveSyncJournalReplicator extends LiveSyncAbstractReplicator {
418
499
  return await encryptString(proof, setting.passphrase);
419
500
  }
420
501
  async markRemoteLocked(setting, locked, lockByClean) {
421
- this.assertLegacyPathIdentity(setting);
502
+ assertSupportedPathNormalizationVersion(setting.pathNormalizationVersion);
422
503
  const defInitPoint = {
423
504
  _id: MILSTONE_DOCID,
424
505
  type: "milestoneinfo",
@@ -432,6 +513,7 @@ class LiveSyncJournalReplicator extends LiveSyncAbstractReplicator {
432
513
  };
433
514
  const client = this.setupJournalSyncClient(setting);
434
515
  const existingMilestone = await readRemoteMilestone(client);
516
+ await this.assertBucketIdentity(setting, existingMilestone, client);
435
517
  const initialProof = existingMilestone ? void 0 : await this.createInitialIdDerivationProof(setting);
436
518
  const remoteMilestone = {
437
519
  ...defInitPoint,
@@ -452,7 +534,7 @@ class LiveSyncJournalReplicator extends LiveSyncAbstractReplicator {
452
534
  }
453
535
  }
454
536
  async markRemoteResolved(setting) {
455
- this.assertLegacyPathIdentity(setting);
537
+ assertSupportedPathNormalizationVersion(setting.pathNormalizationVersion);
456
538
  const defInitPoint = {
457
539
  _id: MILSTONE_DOCID,
458
540
  type: "milestoneinfo",
@@ -465,6 +547,7 @@ class LiveSyncJournalReplicator extends LiveSyncAbstractReplicator {
465
547
  };
466
548
  const client = this.setupJournalSyncClient(setting);
467
549
  const existingMilestone = await readRemoteMilestone(client);
550
+ await this.assertBucketIdentity(setting, existingMilestone, client);
468
551
  const initialProof = existingMilestone ? void 0 : await this.createInitialIdDerivationProof(setting);
469
552
  const remoteMilestone = {
470
553
  ...defInitPoint,
@@ -482,7 +565,7 @@ class LiveSyncJournalReplicator extends LiveSyncAbstractReplicator {
482
565
  const endpoint = setting.endpoint;
483
566
  let testClient;
484
567
  try {
485
- this.assertLegacyPathIdentity(setting);
568
+ assertSupportedPathNormalizationVersion(setting.pathNormalizationVersion);
486
569
  testClient = new MinioStorageAdapter(setting, this.env);
487
570
  await testClient.listFiles("", 1);
488
571
  Logger(`Connected to ${endpoint} successfully!`, LOG_LEVEL_NOTICE);
@@ -497,12 +580,13 @@ ${ex.message}`, LOG_LEVEL_NOTICE);
497
580
  }
498
581
  }
499
582
  async resetRemoteTweakSettings(setting) {
500
- this.assertLegacyPathIdentity(setting);
583
+ assertSupportedPathNormalizationVersion(setting.pathNormalizationVersion);
501
584
  try {
502
585
  const remoteMilestone = await this.client.downloadJson(MILSTONE_DOCID);
503
586
  if (!remoteMilestone) {
504
587
  throw new Error("Missing remote milestone");
505
588
  }
589
+ await this.assertBucketIdentity(setting, remoteMilestone, this.client);
506
590
  remoteMilestone.tweak_values = {};
507
591
  Logger(`tweak values on the remote database have been cleared`, LOG_LEVEL_VERBOSE);
508
592
  await this.client.uploadJson(MILSTONE_DOCID, remoteMilestone);
@@ -512,7 +596,7 @@ ${ex.message}`, LOG_LEVEL_NOTICE);
512
596
  }
513
597
  }
514
598
  async setPreferredRemoteTweakSettings(setting) {
515
- this.assertLegacyPathIdentity(setting);
599
+ assertSupportedPathNormalizationVersion(setting.pathNormalizationVersion);
516
600
  const trialClient = new JournalSyncCore(
517
601
  setting,
518
602
  this.simpleStore,
@@ -524,6 +608,7 @@ ${ex.message}`, LOG_LEVEL_NOTICE);
524
608
  if (!remoteMilestone) {
525
609
  throw new Error("Missing remote milestone");
526
610
  }
611
+ await this.assertBucketIdentity(setting, remoteMilestone, trialClient);
527
612
  remoteMilestone.tweak_values[DEVICE_ID_PREFERRED] = getEffectiveTweakValues(setting);
528
613
  Logger(`Preferred tweak values have been registered`, LOG_LEVEL_VERBOSE);
529
614
  if (!await trialClient.uploadJson(MILSTONE_DOCID, remoteMilestone)) {
@@ -537,7 +622,7 @@ ${ex.message}`, LOG_LEVEL_NOTICE);
537
622
  }
538
623
  }
539
624
  async getRemotePreferredTweakValues(setting) {
540
- this.assertLegacyPathIdentity(setting);
625
+ assertSupportedPathNormalizationVersion(setting.pathNormalizationVersion);
541
626
  const trialClient = new JournalSyncCore(
542
627
  setting,
543
628
  this.simpleStore,
@@ -574,7 +659,7 @@ ${ex.message}`, LOG_LEVEL_NOTICE);
574
659
  }
575
660
  }
576
661
  async getRemoteStatus(setting) {
577
- this.assertLegacyPathIdentity(setting);
662
+ assertSupportedPathNormalizationVersion(setting.pathNormalizationVersion);
578
663
  const testClient = new MinioStorageAdapter(setting, this.env);
579
664
  try {
580
665
  return await testClient.getUsage();
@@ -582,14 +667,6 @@ ${ex.message}`, LOG_LEVEL_NOTICE);
582
667
  testClient.dispose();
583
668
  }
584
669
  }
585
- supportsLegacyPathIdentity(setting) {
586
- return getPathNormalizationVersion(setting) === 0;
587
- }
588
- assertLegacyPathIdentity(setting) {
589
- if (!this.supportsLegacyPathIdentity(setting)) {
590
- throw new Error("Object Storage does not support NFC path identity.");
591
- }
592
- }
593
670
  async getOwnJoinedDeviceId() {
594
671
  const nodeInfo = await this.rawDatabase.get(NODEINFO_DOCID);
595
672
  const nodeId = nodeInfo?.nodeid;
@@ -599,7 +676,7 @@ ${ex.message}`, LOG_LEVEL_NOTICE);
599
676
  return nodeId;
600
677
  }
601
678
  async getJoinedDevices(setting) {
602
- this.assertLegacyPathIdentity(setting);
679
+ assertSupportedPathNormalizationVersion(setting.pathNormalizationVersion);
603
680
  const trialClient = new JournalSyncCore(
604
681
  setting,
605
682
  this.simpleStore,
@@ -616,7 +693,7 @@ ${ex.message}`, LOG_LEVEL_NOTICE);
616
693
  }
617
694
  }
618
695
  async forgetJoinedDevice(setting, deviceId) {
619
- this.assertLegacyPathIdentity(setting);
696
+ assertSupportedPathNormalizationVersion(setting.pathNormalizationVersion);
620
697
  const ownDeviceId = await this.getOwnJoinedDeviceId();
621
698
  const trialClient = new JournalSyncCore(
622
699
  setting,
@@ -630,6 +707,7 @@ ${ex.message}`, LOG_LEVEL_NOTICE);
630
707
  const result = await trialClient.downloadJsonWithResult(MILSTONE_DOCID);
631
708
  if (result.status === JournalStorageReadStatuses.NOT_FOUND) return void 0;
632
709
  if (result.status === JournalStorageReadStatuses.UNAVAILABLE) throw result.error;
710
+ await this.assertBucketIdentity(setting, result.value, trialClient);
633
711
  return { milestone: result.value, revision: result.revision };
634
712
  },
635
713
  write: async (milestone, revision) => await trialClient.uploadJsonIfUnchanged(MILSTONE_DOCID, milestone, revision)
@@ -12,7 +12,7 @@ import { P2PRoomSession } from "./P2PRoomSession.js";
12
12
  import { P2PAutomationCoordinator } from "./P2PAutomationCoordinator.js";
13
13
  import { PEER_REPLICATION_READINESS } from "../ReplicatorProvider.js";
14
14
  import { getP2PReplicatorConfigurationIdentity } from "./p2pReplicatorConfigurationIdentity.js";
15
- import { getPathNormalizationVersion } from "../../common/pathNormalization.js";
15
+ import { assertSupportedPathNormalizationVersion, getPathNormalizationVersion } from "../../common/pathNormalization.js";
16
16
  import {
17
17
  ACTIVE_P2P_RELAY_BINDING_CONFLICT,
18
18
  activeP2PRelayBindingCovers
@@ -58,8 +58,8 @@ class P2PRoomSessionOwner {
58
58
  /** Reconcile one long-lived reason for keeping the room available. */
59
59
  async setPersistentDemand(demand, active) {
60
60
  const settings = this.env.services.setting.currentSettings();
61
- const supportsPathIdentity = getPathNormalizationVersion(settings) === 0;
62
- const enabled = settings.P2P_Enabled && supportsPathIdentity;
61
+ assertSupportedPathNormalizationVersion(settings.pathNormalizationVersion);
62
+ const enabled = settings.P2P_Enabled;
63
63
  if (active && enabled) {
64
64
  this.persistentDemands.add(demand);
65
65
  } else {
@@ -67,8 +67,6 @@ class P2PRoomSessionOwner {
67
67
  }
68
68
  if (active && !settings.P2P_Enabled) {
69
69
  Logger(this.env.services.context.translate("P2P.NotEnabled"), LOG_LEVEL_NOTICE);
70
- } else if (active && !supportsPathIdentity) {
71
- Logger("P2P does not support NFC path identity.", LOG_LEVEL_NOTICE);
72
70
  }
73
71
  await this.fencePreparationForCurrentSettings(enabled);
74
72
  await this.reconcileTransport();
@@ -81,12 +79,10 @@ class P2PRoomSessionOwner {
81
79
  */
82
80
  async runWithFiniteDemand(task) {
83
81
  const settings = this.env.services.setting.currentSettings();
82
+ assertSupportedPathNormalizationVersion(settings.pathNormalizationVersion);
84
83
  if (!settings.P2P_Enabled) {
85
84
  throw new Error("P2P is not enabled.");
86
85
  }
87
- if (getPathNormalizationVersion(settings) !== 0) {
88
- throw new Error("P2P does not support NFC path identity.");
89
- }
90
86
  const demand = /* @__PURE__ */ Symbol("p2p-finite-room-demand");
91
87
  this.finiteDemands.add(demand);
92
88
  try {
@@ -108,8 +104,10 @@ class P2PRoomSessionOwner {
108
104
  }
109
105
  /** Arbitrate a complete signalling probe on the room lifecycle queue. */
110
106
  runConnectionProbe(trialSettings, runOwnedTrial) {
111
- if (getPathNormalizationVersion(this.env.services.setting.currentSettings()) !== 0) {
112
- return Promise.reject(new Error("P2P does not support NFC path identity."));
107
+ try {
108
+ assertSupportedPathNormalizationVersion(this.env.services.setting.currentSettings().pathNormalizationVersion);
109
+ } catch (error) {
110
+ return Promise.reject(error);
113
111
  }
114
112
  return this.enqueueLifecycleOperation(async () => {
115
113
  const current = this.current;
@@ -135,7 +133,8 @@ class P2PRoomSessionOwner {
135
133
  async reconcileTransport() {
136
134
  await this.enqueueLifecycleOperation(async () => {
137
135
  const settings = this.env.services.setting.currentSettings();
138
- if (!settings.P2P_Enabled || getPathNormalizationVersion(settings) !== 0) {
136
+ assertSupportedPathNormalizationVersion(settings.pathNormalizationVersion);
137
+ if (!settings.P2P_Enabled) {
139
138
  this.persistentDemands.clear();
140
139
  this.finiteDemands.clear();
141
140
  }
@@ -390,17 +389,19 @@ class P2PRoomSessionOwner {
390
389
  ...this.env.services.setting.currentSettings()
391
390
  });
392
391
  const database = this.env.services.database.localDatabase.localDatabase;
392
+ const pathNormalizationVersion = getPathNormalizationVersion(settings);
393
393
  this.automationCoordinator.reconcileIdentity(
394
394
  JSON.stringify([
395
395
  settings.P2P_AppID || "self-hosted-livesync",
396
396
  settings.P2P_roomID,
397
- settings.P2P_passphrase
397
+ settings.P2P_passphrase,
398
+ pathNormalizationVersion
398
399
  ]),
399
400
  database
400
401
  );
401
402
  return {
402
403
  database,
403
- enabled: settings.P2P_Enabled && getPathNormalizationVersion(settings) === 0,
404
+ enabled: settings.P2P_Enabled,
404
405
  settings,
405
406
  deviceName,
406
407
  signature: JSON.stringify([getP2PReplicatorConfigurationIdentity(settings), deviceName])
@@ -141,6 +141,7 @@ export declare class TrysteroReplicator {
141
141
  setOnSetup(): void;
142
142
  clearOnSetup(): void;
143
143
  getTweakSettings(fromPeerId: string): Promise<{
144
+ pathNormalizationVersion: import("../../settings.js").PathNormalizationVersion;
144
145
  idDerivationVersion: 0 | 1;
145
146
  idDerivationProof: string;
146
147
  liveSync?: boolean;
@@ -251,7 +252,6 @@ export declare class TrysteroReplicator {
251
252
  bucketPrefix?: string;
252
253
  forcePathStyle?: boolean;
253
254
  remoteType?: import("../../common/types.js").RemoteType;
254
- pathNormalizationVersion?: import("../../settings.js").PathNormalizationVersion;
255
255
  encrypt?: boolean;
256
256
  passphrase?: string;
257
257
  idDerivationKey?: string;
@@ -331,6 +331,7 @@ export declare class TrysteroReplicator {
331
331
  reqSync: (fromPeerId: string) => Promise<P2PReplicationWireResult>;
332
332
  "!reqAuth": (fromPeerId: string) => Promise<boolean>;
333
333
  getTweakSettings: (fromPeerId: string) => Promise<{
334
+ pathNormalizationVersion: import("../../settings.js").PathNormalizationVersion;
334
335
  idDerivationVersion: 0 | 1;
335
336
  idDerivationProof: string;
336
337
  liveSync?: boolean;
@@ -441,7 +442,6 @@ export declare class TrysteroReplicator {
441
442
  bucketPrefix?: string;
442
443
  forcePathStyle?: boolean;
443
444
  remoteType?: import("../../common/types.js").RemoteType;
444
- pathNormalizationVersion?: import("../../settings.js").PathNormalizationVersion;
445
445
  encrypt?: boolean;
446
446
  passphrase?: string;
447
447
  idDerivationKey?: string;
@@ -517,15 +517,15 @@ export declare class TrysteroReplicator {
517
517
  P2P_useDiagRTC?: boolean;
518
518
  }>;
519
519
  onProgress: (fromPeerId: string) => Promise<{
520
- error: import("../../rpc/index.js").RpcErrorShape;
520
+ error: import("../../rpc/types.js").RpcErrorShape;
521
521
  }>;
522
522
  getAllConfig: (fromPeerId: string) => Promise<string | {
523
- error: import("../../rpc/index.js").RpcErrorShape;
523
+ error: import("../../rpc/types.js").RpcErrorShape;
524
524
  }>;
525
525
  onProgressAcknowledged: (fromPeerId: string, info: ProgressInfo) => Promise<void>;
526
526
  getIsBroadcasting: () => Promise<boolean>;
527
527
  requestBroadcasting: (peerId: string) => Promise<true | {
528
- error: import("../../rpc/index.js").RpcErrorShape;
528
+ error: import("../../rpc/types.js").RpcErrorShape;
529
529
  }>;
530
530
  };
531
531
  requestAuthenticate(peerId: string, signal?: AbortSignal): Promise<boolean>;
@@ -553,6 +553,8 @@ export declare class TrysteroReplicator {
553
553
  acknowledgeProgress(remotePeerId: string, info?: ProgressInfo): void;
554
554
  replicateFrom(remotePeer: string, showNotice?: boolean, fromStart?: boolean, skipOrdinaryReplicationPolicy?: boolean, callerSignal?: AbortSignal): Promise<P2PReplicationResult>;
555
555
  private replicateFromWithinSession;
556
+ private checkPeerForReplication;
557
+ private transferFromPeer;
556
558
  notifyProgress(excludePeerId?: string): Promise<void>;
557
559
  requestBroadcastChanges(peerId: string): Promise<unknown>;
558
560
  getRemoteIsBroadcasting(peerId: string): Promise<unknown>;