@vrtmrz/livesync-commonlib 0.2.2 → 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 (81) hide show
  1. package/README.md +86 -1
  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/ContentSplitter/ContentSplitterRabinKarp.js +2 -17
  8. package/dist/ContentSplitter/ContentSplitterV1.js +9 -19
  9. package/dist/ContentSplitter/ContentSplitterV2.js +9 -19
  10. package/dist/common/models/setting.type.d.ts +1 -1
  11. package/dist/common/models/shared.definition.configNames.js +1 -2
  12. package/dist/common/models/tweak.definition.d.ts +1 -1
  13. package/dist/common/pathNormalization.d.ts +2 -0
  14. package/dist/common/pathNormalization.js +7 -2
  15. package/dist/common/utils.d.ts +33 -3
  16. package/dist/common/utils.js +12 -44
  17. package/dist/common/utils.object.js +2 -1
  18. package/dist/concurrency/keyedPendingWork.d.ts +13 -0
  19. package/dist/concurrency/keyedPendingWork.js +27 -0
  20. package/dist/conflicts/conflictCheckQueue.d.ts +45 -0
  21. package/dist/conflicts/conflictCheckQueue.js +108 -0
  22. package/dist/conflicts.d.ts +3 -0
  23. package/dist/conflicts.js +4 -0
  24. package/dist/diagnostics/integrity.js +11 -12
  25. package/dist/interfaces/ServiceModule.d.ts +42 -24
  26. package/dist/interfaces/ServiceModule.js +4 -3
  27. package/dist/managers/ChunkFetcher.d.ts +3 -0
  28. package/dist/managers/ChunkFetcher.js +20 -13
  29. package/dist/managers/LiveSyncManagers.d.ts +2 -0
  30. package/dist/managers/LiveSyncManagers.js +1 -0
  31. package/dist/managers/StorageEventManager.d.ts +3 -3
  32. package/dist/managers/StorageEventManager.js +15 -6
  33. package/dist/managers/chunkFetchSource.d.ts +14 -0
  34. package/dist/managers/chunkFetchSource.js +12 -0
  35. package/dist/pouchdb/LiveSyncDBFunctions.js +5 -0
  36. package/dist/pouchdb/LiveSyncLocalDB.d.ts +7 -1
  37. package/dist/pouchdb/LiveSyncLocalDB.js +14 -10
  38. package/dist/pouchdb/chunks.d.ts +16 -0
  39. package/dist/pouchdb/pathIdentityCompatibility.d.ts +8 -0
  40. package/dist/pouchdb/pathIdentityCompatibility.js +19 -1
  41. package/dist/pouchdb/pouchdb-browser.js +2 -84
  42. package/dist/pouchdb/pouchdb-http.js +2 -84
  43. package/dist/pouchdb/purgeMulti.d.ts +4 -0
  44. package/dist/pouchdb/purgeMulti.js +89 -0
  45. package/dist/replication/RemoteResource.d.ts +3 -0
  46. package/dist/replication/index.d.ts +2 -0
  47. package/dist/replication/index.js +2 -0
  48. package/dist/replication/journal/JournalSyncCore.d.ts +1 -0
  49. package/dist/replication/journal/JournalSyncCore.js +41 -20
  50. package/dist/replication/journal/LiveSyncJournalReplicator.d.ts +11 -3
  51. package/dist/replication/journal/LiveSyncJournalReplicator.js +123 -45
  52. package/dist/replication/trystero/P2PRoomSessionOwner.js +14 -13
  53. package/dist/replication/trystero/TrysteroReplicator.d.ts +7 -5
  54. package/dist/replication/trystero/TrysteroReplicator.js +101 -74
  55. package/dist/replication/trystero/TrysteroReplicator.pathIdentity.d.ts +11 -0
  56. package/dist/replication/trystero/TrysteroReplicator.pathIdentity.js +72 -0
  57. package/dist/replication/trystero/TrysteroReplicatorP2PServer.js +3 -2
  58. package/dist/replication/trystero/p2pReplicatorConfigurationIdentity.js +2 -0
  59. package/dist/replication/trystero/peerPatterns.d.ts +1 -0
  60. package/dist/replication/trystero/peerPatterns.js +6 -0
  61. package/dist/replication/trystero/useP2PReplicatorFeature.js +6 -2
  62. package/dist/rpc/RpcRoom.d.ts +5 -1
  63. package/dist/rpc/RpcRoom.js +80 -105
  64. package/dist/serviceFeatures/offlineScanner.d.ts +4 -4
  65. package/dist/serviceFeatures/offlineScanner.js +14 -15
  66. package/dist/serviceFeatures/prepareDatabaseForUse.d.ts +2 -2
  67. package/dist/serviceFeatures/remoteConfig.d.ts +10 -5
  68. package/dist/serviceModules/Rebuilder.d.ts +1 -0
  69. package/dist/serviceModules/Rebuilder.js +33 -5
  70. package/dist/serviceModules/ServiceFileHandlerBase.js +2 -7
  71. package/dist/services/implements/injectable/InjectableConflictService.d.ts +5 -5
  72. package/dist/services/lib/HandlerUtils.d.ts +8 -6
  73. package/dist/services/lib/HandlerUtils.js +14 -0
  74. package/dist/worker/bgWorker.js +1 -1
  75. package/docs/conflict-resolution.md +30 -0
  76. package/docs/development.md +50 -0
  77. package/docs/proven-in-use.md +80 -0
  78. package/docs/releasing.md +4 -0
  79. package/docs/service-feature-composition.md +63 -10
  80. package/docs/unicode-path-identity.md +64 -3
  81. package/package.json +11 -3
@@ -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>;
@@ -1,3 +1,4 @@
1
+ import { parsePeerPatterns } from "./peerPatterns.js";
1
2
  import {
2
3
  TweakValuesShouldMatchedTemplate,
3
4
  omitP2PRuntimeSettings
@@ -27,6 +28,9 @@ import { compatGlobal } from "../../common/coreEnvFunctions.js";
27
28
  import { computeKeyedId, configuredIdKey } from "../../common/idDerivation.js";
28
29
  import { getWebCrypto } from "../../mods.js";
29
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";
30
34
  import {
31
35
  fromP2PReplicationWireResult,
32
36
  toP2PReplicationWireResult,
@@ -171,12 +175,10 @@ class TrysteroReplicator {
171
175
  }
172
176
  }
173
177
  get autoSyncPeers() {
174
- const peers = this.currentSettings.P2P_AutoSyncPeers.split(",").map((e) => e.trim()).filter((e) => e.length > 0).map((e) => e.startsWith("~") ? new RegExp(e.substring(1), "i") : new RegExp(`^${e}$`, "i"));
175
- return peers;
178
+ return parsePeerPatterns(this.currentSettings.P2P_AutoSyncPeers);
176
179
  }
177
180
  get autoWatchPeers() {
178
- const peers = this.currentSettings.P2P_AutoWatchPeers.split(",").map((e) => e.trim()).filter((e) => e.length > 0).map((e) => e.startsWith("~") ? new RegExp(e.substring(1), "i") : new RegExp(`^${e}$`, "i"));
179
- return peers;
181
+ return parsePeerPatterns(this.currentSettings.P2P_AutoWatchPeers);
180
182
  }
181
183
  async onNewPeer(peer) {
182
184
  const peerName = peer.name;
@@ -206,6 +208,11 @@ class TrysteroReplicator {
206
208
  this._onSetup = false;
207
209
  }
208
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
+ }
209
216
  const allSettings = JSON.parse(JSON.stringify(this.settings));
210
217
  for (const key in allSettings) {
211
218
  if (key == "encrypt") {
@@ -224,6 +231,7 @@ class TrysteroReplicator {
224
231
  const idKey = configuredIdKey({ idDerivationVersion, idDerivationKey: idSettings.idDerivationKey ?? "" });
225
232
  return {
226
233
  ...allSettings,
234
+ pathNormalizationVersion,
227
235
  idDerivationVersion,
228
236
  idDerivationProof: idKey && idSettings.encrypt && idSettings.usePathObfuscation ? await computeKeyedId(idKey, "peer-agreement", fromPeerId) : ""
229
237
  };
@@ -554,6 +562,28 @@ class TrysteroReplicator {
554
562
  }
555
563
  if (signal?.aborted) return P2P_REPLICATION_CANCELLED;
556
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) {
557
587
  Logger(`P2P Requesting Authentication to ${remotePeer}`, logLevel, "p2p-replicator");
558
588
  const authenticated = signal ? await this.requestAuthenticate(remotePeer, signal) : await this.requestAuthenticate(remotePeer);
559
589
  if (authenticated !== true) {
@@ -573,71 +603,62 @@ class TrysteroReplicator {
573
603
  Logger("Tweak values are not matched", logLevel, "p2p-replicator");
574
604
  return p2pReplicationFailed(new Error("Tweak values are not matched"));
575
605
  }
576
- Logger(`P2P Replicating from ${remotePeer}`, logLevel, "p2p-replicator");
577
- if (this._replicateFromPeers.has(remotePeer)) {
578
- Logger(`Replication from ${remotePeer} is already in progress`, logLevel, "p2p-replicator");
579
- return p2pReplicationFailed(new Error("Replication from this peer is already in progress"));
580
- }
581
- this._replicateFromPeers.add(remotePeer);
582
- this.dispatchStatus();
583
- try {
584
- if (!this.server) {
585
- throw new Error("Server is not available");
586
- }
587
- const connection = this.server.getConnection(remotePeer);
588
- const remoteDB = connection.getRemoteDB(signal);
589
- Logger(`P2P replicateFrom preparing remote DB info for ${remotePeer}`, LOG_LEVEL_VERBOSE, "p2p-replicator");
590
- const remoteDBInfo = await remoteDB.info();
591
- Logger(
592
- `P2P replicateFrom remote DB info for ${remotePeer}: ${remoteDBInfo.db_name} seq=${remoteDBInfo.update_seq}`,
593
- LOG_LEVEL_VERBOSE,
594
- "p2p-replicator"
595
- );
596
- const localDBInfo = await this.db.info();
597
- Logger(
598
- `P2P replicateFrom local DB info for ${remotePeer}: ${localDBInfo.db_name} seq=${localDBInfo.update_seq}`,
599
- LOG_LEVEL_VERBOSE,
600
- "p2p-replicator"
601
- );
602
- Logger(`P2P replicateFrom entering replicateShim for ${remotePeer}`, LOG_LEVEL_VERBOSE, "p2p-replicator");
603
- const outcome = await replicateShim(
604
- this.db,
605
- remoteDB,
606
- async (docs, info) => {
607
- await this._env.processReplicatedDocs(docs);
608
- void this.dispatchReplicationProgress(remotePeer, info);
609
- void this.acknowledgeProgress(remotePeer, info);
610
- void this.notifyProgress(remotePeer);
611
- Logger(
612
- `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}
613
648
  ${info.lastSeq} / ${info.maxSeqInBatch})`,
614
- logLevel,
615
- "p2p-replicator"
616
- );
617
- },
618
- {
619
- live: false,
620
- rewind: fromStart,
621
- signal
622
- /*, batch_size: batchSize */
623
- }
624
- );
625
- Logger(`P2P replicateFrom replicateShim returned for ${remotePeer}`, LOG_LEVEL_VERBOSE, "p2p-replicator");
626
- void this.acknowledgeProgress(remotePeer, void 0);
627
- if (outcome.status === "cancelled") {
628
- Logger(`P2P Replication from ${remotePeer} has been cancelled`, logLevel, "p2p-replicator");
629
- return P2P_REPLICATION_CANCELLED;
630
- }
631
- Logger(`P2P Replication from ${remotePeer} has been completed`, logLevel, "p2p-replicator");
632
- } catch (e) {
633
- if (signal?.aborted) return P2P_REPLICATION_CANCELLED;
634
- Logger("Error while P2P replicating", logLevel, "p2p-replicator");
635
- Logger(e, LOG_LEVEL_VERBOSE);
636
- return p2pReplicationFailed(e);
637
- } finally {
638
- this._replicateFromPeers.delete(remotePeer);
639
- 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;
640
660
  }
661
+ Logger(`P2P Replication from ${remotePeer} has been completed`, logLevel, "p2p-replicator");
641
662
  return P2P_REPLICATION_COMPLETED;
642
663
  }
643
664
  notifyProgress(excludePeerId) {
@@ -740,12 +761,6 @@ ${info.lastSeq} / ${info.maxSeqInBatch})`,
740
761
  Logger("Peer is not found", logLevel);
741
762
  return false;
742
763
  }
743
- if (this.platform === "pseudo-replicator") {
744
- return true;
745
- }
746
- if (peerPlatform === "pseudo-replicator") {
747
- return true;
748
- }
749
764
  const connection = this.server.getConnection(peerId);
750
765
  const challengeBytes = new Uint8Array(16);
751
766
  (await getWebCrypto()).getRandomValues(challengeBytes);
@@ -753,6 +768,15 @@ ${info.lastSeq} / ${info.maxSeqInBatch})`,
753
768
  const tweakValues = await connection.invokeRemoteObjectFunction("getTweakSettings", [challenge], 5e3, signal);
754
769
  if (signal?.aborted) return false;
755
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
+ }
756
780
  if (thisTweakValues.passphrase !== tweakValues.passphrase) {
757
781
  Logger(
758
782
  "Replication cancelled: Passphrase is not matched\nCannot replicate to a remote database until the problem is resolved.",
@@ -761,7 +785,10 @@ ${info.lastSeq} / ${info.maxSeqInBatch})`,
761
785
  return false;
762
786
  }
763
787
  if (thisTweakValues.encrypt !== tweakValues.encrypt || thisTweakValues.usePathObfuscation !== tweakValues.usePathObfuscation) {
764
- 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
+ );
765
792
  return false;
766
793
  }
767
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
+ };
@@ -1,3 +1,4 @@
1
+ import { parsePeerPatterns } from "./peerPatterns.js";
1
2
  import { selfId, joinRoom } from "@trystero-p2p/nostr";
2
3
  import { AutoAccepting, LOG_LEVEL_INFO, LOG_LEVEL_NOTICE } from "../../common/types.js";
3
4
  import { LOG_LEVEL_VERBOSE, Logger } from "../../common/logger.js";
@@ -281,12 +282,12 @@ You can chose as follows:
281
282
  }
282
283
  _acceptablePeers = new Computed({
283
284
  evaluation: (settings) => {
284
- return `${settings?.P2P_AutoAcceptingPeers ?? ""}`.split(",").map((e) => e.trim()).filter((e) => !!e).map((e) => e.startsWith("~") ? new RegExp(e.substring(1), "i") : new RegExp(`^${e}$`, "i"));
285
+ return parsePeerPatterns(`${settings?.P2P_AutoAcceptingPeers ?? ""}`);
285
286
  }
286
287
  });
287
288
  _shouldDenyPeers = new Computed({
288
289
  evaluation: (settings) => {
289
- return `${settings?.P2P_AutoDenyingPeers ?? ""}`.split(",").map((e) => e.trim()).filter((e) => !!e).map((e) => e.startsWith("~") ? new RegExp(e.substring(1), "i") : new RegExp(`^${e}$`, "i"));
290
+ return parsePeerPatterns(`${settings?.P2P_AutoDenyingPeers ?? ""}`);
290
291
  }
291
292
  });
292
293
  /**
@@ -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
  }
@@ -0,0 +1 @@
1
+ export declare function parsePeerPatterns(value: string): RegExp[];
@@ -0,0 +1,6 @@
1
+ function parsePeerPatterns(value) {
2
+ return value.split(",").map((entry) => entry.trim()).filter((entry) => entry.length > 0).map((entry) => entry.startsWith("~") ? new RegExp(entry.substring(1), "i") : new RegExp(`^${entry}$`, "i"));
3
+ }
4
+ export {
5
+ parsePeerPatterns
6
+ };
@@ -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,
@@ -8,7 +8,7 @@ export declare class RpcRoom {
8
8
  private sessions;
9
9
  private outgoingChunkMap;
10
10
  private incomingChunkMap;
11
- private incomingChunkTimers;
11
+ private incomingChunkWaits;
12
12
  private peerVersion;
13
13
  private disposers;
14
14
  private closed;
@@ -32,7 +32,11 @@ export declare class RpcRoom {
32
32
  private notifyRemoteCancellation;
33
33
  private abortInboundCall;
34
34
  private sendEnvelope;
35
+ private sendChunk;
36
+ private sendErrorResponse;
35
37
  private scheduleMissingAck;
36
38
  private onWireMessage;
37
39
  private onEnvelopePayload;
40
+ private onRequest;
41
+ private runMethod;
38
42
  }