@vrtmrz/livesync-commonlib 0.1.17 → 0.1.19-rc.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/dist/API/DirectFileManipulatorV2.js +3 -1
  2. package/dist/common/ConnectionString.js +9 -1
  3. package/dist/common/models/fileaccess.type.d.ts +7 -0
  4. package/dist/common/models/setting.const.d.ts +18 -0
  5. package/dist/common/models/setting.const.defaults.js +10 -1
  6. package/dist/common/models/setting.const.js +12 -0
  7. package/dist/common/models/setting.const.qr.js +3 -1
  8. package/dist/common/models/setting.p2p.d.ts +21 -0
  9. package/dist/common/models/setting.p2p.js +35 -0
  10. package/dist/common/models/setting.type.d.ts +11 -1
  11. package/dist/common/models/shared.definition.configNames.js +8 -0
  12. package/dist/common/models/tweak.definition.d.ts +2 -0
  13. package/dist/common/settingConstants.d.ts +2 -0
  14. package/dist/common/types.d.ts +5 -3
  15. package/dist/common/types.js +17 -1
  16. package/dist/common/utils.js +4 -1
  17. package/dist/interfaces/DatabaseRebuilder.d.ts +4 -2
  18. package/dist/managers/StorageEventManager.d.ts +16 -5
  19. package/dist/managers/StorageEventManager.js +46 -8
  20. package/dist/pouchdb/LiveSyncLocalDB.d.ts +7 -1
  21. package/dist/pouchdb/LiveSyncLocalDB.js +120 -38
  22. package/dist/replication/trystero/LiveSyncTrysteroReplicator.d.ts +8 -0
  23. package/dist/replication/trystero/LiveSyncTrysteroReplicator.js +36 -8
  24. package/dist/replication/trystero/TrysteroReplicatorP2PServer.js +2 -2
  25. package/dist/rpc/transports/TrysteroTransport.d.ts +2 -0
  26. package/dist/rpc/transports/TrysteroTransport.js +18 -2
  27. package/dist/rpc/transports/trysteroUtils.js +12 -1
  28. package/dist/serviceFeatures/prepareDatabaseForUse.js +18 -15
  29. package/dist/serviceModules/Rebuilder.d.ts +27 -4
  30. package/dist/serviceModules/Rebuilder.js +113 -35
  31. package/dist/serviceModules/ServiceFileHandlerBase.d.ts +17 -0
  32. package/dist/serviceModules/ServiceFileHandlerBase.js +145 -9
  33. package/dist/services/base/DatabaseService.d.ts +11 -3
  34. package/dist/services/base/DatabaseService.js +63 -7
  35. package/dist/services/base/IService.d.ts +1 -0
  36. package/dist/services/base/ReplicationService.d.ts +16 -0
  37. package/dist/services/base/ReplicationService.js +36 -4
  38. package/dist/services/base/ReplicatorService.d.ts +1 -0
  39. package/dist/services/base/ReplicatorService.js +32 -13
  40. package/dist/worker/bgWorker.js +1 -1
  41. package/docs/development.md +2 -0
  42. package/docs/p2p-transport-lifecycle.md +4 -2
  43. package/docs/proven-in-use.md +5 -1
  44. package/docs/releasing.md +24 -0
  45. package/docs/remote-configurations.md +2 -0
  46. package/package.json +1 -1
@@ -37,16 +37,14 @@ class LiveSyncLocalDB {
37
37
  async _prepareHashFunctions() {
38
38
  await this._managers?.prepareHashFunction();
39
39
  }
40
- onunload() {
41
- void this.env.services.databaseEvents.onUnloadDatabase(this);
42
- this.localDatabase.removeAllListeners();
43
- }
44
40
  refreshSettings() {
45
41
  const settings = this.env.services.setting.currentSettings();
46
42
  this.settings = settings;
47
43
  void this._prepareHashFunctions();
48
44
  }
49
45
  offRemoteChunkFetchedHandler;
46
+ databaseCloseCleanup;
47
+ databaseUnloadNotification;
50
48
  constructor(dbname, env) {
51
49
  this.auth = {
52
50
  username: "",
@@ -57,14 +55,79 @@ class LiveSyncLocalDB {
57
55
  this._log = createInstanceLogFunction("LiveSyncLocalDB", this.env.services.API);
58
56
  this.refreshSettings();
59
57
  }
58
+ /** Notify dependent services at most once for each physical database lifecycle. */
59
+ notifyDatabaseUnload() {
60
+ if (!this.databaseUnloadNotification) {
61
+ this.databaseUnloadNotification = Promise.resolve().then(async () => {
62
+ await this.env.services.databaseEvents.onUnloadDatabase(this);
63
+ });
64
+ }
65
+ return this.databaseUnloadNotification;
66
+ }
67
+ onunload() {
68
+ void this.notifyDatabaseUnload();
69
+ }
60
70
  async close() {
61
71
  this._log("Database closed (by close)");
62
72
  this.isReady = false;
63
73
  this.offRemoteChunkFetchedHandler?.();
74
+ this.offRemoteChunkFetchedHandler = void 0;
64
75
  if (this.localDatabase != null) {
65
- await this.localDatabase.close();
76
+ const database = this.localDatabase;
77
+ await database.close();
78
+ if (this.databaseCloseCleanup?.database === database) {
79
+ await this.databaseCloseCleanup.promise;
80
+ }
81
+ if (this.localDatabase === database) {
82
+ this.localDatabase = null;
83
+ }
84
+ }
85
+ this._managers = void 0;
86
+ await this.notifyDatabaseUnload();
87
+ }
88
+ async teardownDatabaseDependencies(managers) {
89
+ try {
90
+ await Promise.resolve(this.env.services.replicator.getActiveReplicator()?.closeReplication());
91
+ } catch (error) {
92
+ this._log("Failed to close replication while tearing down the local database.", LOG_LEVEL_VERBOSE);
93
+ this._log(error, LOG_LEVEL_VERBOSE);
94
+ }
95
+ try {
96
+ await managers.teardownManagers();
97
+ } catch (error) {
98
+ this._log("Failed to tear down managers while closing the local database.", LOG_LEVEL_VERBOSE);
99
+ this._log(error, LOG_LEVEL_VERBOSE);
100
+ }
101
+ }
102
+ async rollbackFailedInitialisation(database, managers) {
103
+ this.isReady = false;
104
+ this.offRemoteChunkFetchedHandler?.();
105
+ this.offRemoteChunkFetchedHandler = void 0;
106
+ const closeCleanup = this.databaseCloseCleanup?.database === database ? this.databaseCloseCleanup : void 0;
107
+ if (closeCleanup) {
108
+ await closeCleanup.promise;
109
+ } else {
110
+ database.removeAllListeners();
111
+ await this.teardownDatabaseDependencies(managers);
112
+ try {
113
+ await database.close();
114
+ } catch (error) {
115
+ this._log("Failed to close the database after its initialisation was rejected.", LOG_LEVEL_VERBOSE);
116
+ this._log(error, LOG_LEVEL_VERBOSE);
117
+ }
118
+ }
119
+ if (this.localDatabase === database) {
120
+ this.localDatabase = null;
121
+ }
122
+ if (this._managers === managers) {
123
+ this._managers = void 0;
124
+ }
125
+ try {
126
+ await this.notifyDatabaseUnload();
127
+ } catch (error) {
128
+ this._log("A database unload handler failed after initialisation was rejected.", LOG_LEVEL_VERBOSE);
129
+ this._log(error, LOG_LEVEL_VERBOSE);
66
130
  }
67
- await this.env.services.databaseEvents.onUnloadDatabase(this);
68
131
  }
69
132
  onNewLeaf(chunk) {
70
133
  this.managers.chunkManager?.emitEvent(EVENT_CHUNK_FETCHED, chunk);
@@ -84,6 +147,7 @@ class LiveSyncLocalDB {
84
147
  deterministic_revs: true
85
148
  }
86
149
  );
150
+ this.databaseUnloadNotification = void 0;
87
151
  const manager = new LiveSyncManagers({
88
152
  database: this.localDatabase,
89
153
  APIService: this.env.services.API,
@@ -93,38 +157,53 @@ class LiveSyncLocalDB {
93
157
  databaseService: this.env.services.database
94
158
  });
95
159
  this._managers = manager;
96
- if (!await this.env.services.databaseEvents.onDatabaseInitialisation(this)) {
97
- this._log("Initializing Database has been failed on some module", LOG_LEVEL_NOTICE);
98
- }
99
- this._log("Opening Database...");
100
- this._log("Database info", LOG_LEVEL_VERBOSE);
101
- this._log(JSON.stringify(await this.localDatabase.info(), null, 2), LOG_LEVEL_VERBOSE);
102
- await this.managers.initialise();
103
- this.localDatabase.on("close", () => {
104
- this._log("Database closed.");
105
- this.isReady = false;
106
- this.localDatabase.removeAllListeners();
107
- this.env.services.replicator.getActiveReplicator()?.closeReplication();
108
- void this.managers.teardownManagers();
109
- });
110
- const _instance = new FallbackWeakRef(this);
111
- const unload = this.env.services.context.events.onEvent(REMOTE_CHUNK_FETCHED, (chunk) => {
112
- if (_instance.deref() == null) {
113
- unload();
160
+ const openedDatabase = this.localDatabase;
161
+ this.databaseCloseCleanup = void 0;
162
+ try {
163
+ if (!await this.env.services.databaseEvents.onDatabaseInitialisation(this)) {
164
+ await this.rollbackFailedInitialisation(openedDatabase, manager);
165
+ this._log("Database initialisation was rejected by a required module.", LOG_LEVEL_NOTICE);
166
+ return false;
114
167
  }
115
- _instance.deref()?.onNewLeaf(chunk);
116
- });
117
- this.offRemoteChunkFetchedHandler = unload;
118
- this.isReady = true;
119
- if (!await this.env.services.databaseEvents.onDatabaseHasReady()) {
120
- this._log(
121
- "Some module has prevented the database from being ready. The database is initialised but not ready for use.",
122
- LOG_LEVEL_NOTICE
123
- );
124
- return false;
168
+ this._log("Opening Database...");
169
+ this._log("Database info", LOG_LEVEL_VERBOSE);
170
+ this._log(JSON.stringify(await openedDatabase.info(), null, 2), LOG_LEVEL_VERBOSE);
171
+ await manager.initialise();
172
+ openedDatabase.on("close", () => {
173
+ this._log("Database closed.");
174
+ this.isReady = false;
175
+ openedDatabase.removeAllListeners();
176
+ if (this.databaseCloseCleanup?.database !== openedDatabase) {
177
+ this.databaseCloseCleanup = {
178
+ database: openedDatabase,
179
+ promise: this.teardownDatabaseDependencies(manager)
180
+ };
181
+ }
182
+ });
183
+ const _instance = new FallbackWeakRef(this);
184
+ const unload = this.env.services.context.events.onEvent(REMOTE_CHUNK_FETCHED, (chunk) => {
185
+ if (_instance.deref() == null) {
186
+ unload();
187
+ }
188
+ _instance.deref()?.onNewLeaf(chunk);
189
+ });
190
+ this.offRemoteChunkFetchedHandler = unload;
191
+ this.isReady = true;
192
+ const readyAccepted = await this.env.services.databaseEvents.onDatabaseHasReady();
193
+ if (!readyAccepted || this.localDatabase !== openedDatabase || !this.isReady) {
194
+ await this.rollbackFailedInitialisation(openedDatabase, manager);
195
+ this._log(
196
+ "Some module has prevented the database from being ready. The database is initialised but not ready for use.",
197
+ LOG_LEVEL_NOTICE
198
+ );
199
+ return false;
200
+ }
201
+ this._log("Database is now ready.");
202
+ return true;
203
+ } catch (error) {
204
+ await this.rollbackFailedInitialisation(openedDatabase, manager);
205
+ throw error;
125
206
  }
126
- this._log("Database is now ready.");
127
- return true;
128
207
  }
129
208
  /**
130
209
  * Retrieve all used and existing chunks in the database.
@@ -212,6 +291,7 @@ class LiveSyncLocalDB {
212
291
  return { used, existing };
213
292
  }
214
293
  async resetDatabase() {
294
+ this.isReady = false;
215
295
  await this.managers.teardownManagers();
216
296
  this.env.services.replicator.getActiveReplicator()?.closeReplication();
217
297
  if (!await this.env.services.databaseEvents.onResetDatabase(this)) {
@@ -219,10 +299,12 @@ class LiveSyncLocalDB {
219
299
  return false;
220
300
  }
221
301
  Logger("Database closed for reset Database.");
222
- this.isReady = false;
223
302
  await this.localDatabase.destroy();
224
303
  this.localDatabase = null;
225
- await this.initializeDatabase();
304
+ if (!await this.initializeDatabase()) {
305
+ this._log("Local Database could not be reinitialised after reset.", LOG_LEVEL_NOTICE);
306
+ return false;
307
+ }
226
308
  this._log("Local Database Reset", LOG_LEVEL_NOTICE);
227
309
  return true;
228
310
  }
@@ -20,6 +20,7 @@ export declare class LiveSyncTrysteroReplicator extends LiveSyncAbstractReplicat
20
20
  private _replicator?;
21
21
  private _lifecycleOperation;
22
22
  private _shouldBeOpen;
23
+ private _activeTransportCompatibilitySignature?;
23
24
  get openReplicationUI(): (showResult: boolean) => Promise<boolean | void>;
24
25
  get rawReplicator(): TrysteroReplicator;
25
26
  get rawHost(): P2PHost;
@@ -27,6 +28,13 @@ export declare class LiveSyncTrysteroReplicator extends LiveSyncAbstractReplicat
27
28
  terminateSync(): void;
28
29
  private _buildEnv;
29
30
  private _enqueueLifecycleOperation;
31
+ /**
32
+ * Capture settings which are fixed when the active P2P transport joins its room.
33
+ * A later open request can remain idempotent only while these effective values match.
34
+ */
35
+ private _getTransportCompatibilitySignature;
36
+ /** Close and forget the transport without changing the requested lifecycle state. */
37
+ private _closeTransport;
30
38
  open(): Promise<void>;
31
39
  close(): Promise<void>;
32
40
  closeReplication(): void;
@@ -16,11 +16,18 @@ import {
16
16
  P2PHost
17
17
  } from "./TrysteroReplicatorP2PServer.js";
18
18
  import { delay } from "octagonal-wheels/promises";
19
+ import {
20
+ hasValidP2PTurnServerUrl,
21
+ normaliseP2PConnectionPath,
22
+ normaliseP2PMaxWirePayloadBytes
23
+ } from "../../common/models/setting.p2p.js";
24
+ import { P2PConnectionPaths } from "../../common/models/setting.const.js";
19
25
  class LiveSyncTrysteroReplicator extends LiveSyncAbstractReplicator {
20
26
  _p2pHost;
21
27
  _replicator;
22
28
  _lifecycleOperation = Promise.resolve();
23
29
  _shouldBeOpen = false;
30
+ _activeTransportCompatibilitySignature;
24
31
  get openReplicationUI() {
25
32
  return this.env.openReplicationUI;
26
33
  }
@@ -81,6 +88,26 @@ class LiveSyncTrysteroReplicator extends LiveSyncAbstractReplicator {
81
88
  this._lifecycleOperation = queued.catch(() => void 0);
82
89
  return queued;
83
90
  }
91
+ /**
92
+ * Capture settings which are fixed when the active P2P transport joins its room.
93
+ * A later open request can remain idempotent only while these effective values match.
94
+ */
95
+ _getTransportCompatibilitySignature() {
96
+ const settings = this.env.services.setting.currentSettings();
97
+ const configuredPath = normaliseP2PConnectionPath(settings.P2P_connectionPath);
98
+ const effectivePath = configuredPath === P2PConnectionPaths.Relay && hasValidP2PTurnServerUrl(settings.P2P_turnServers ?? "") ? P2PConnectionPaths.Relay : P2PConnectionPaths.Automatic;
99
+ return `${normaliseP2PMaxWirePayloadBytes(settings.P2P_maxWirePayloadBytes)}:${effectivePath}`;
100
+ }
101
+ /** Close and forget the transport without changing the requested lifecycle state. */
102
+ async _closeTransport() {
103
+ if (this._replicator) {
104
+ this._replicator.disableBroadcastChanges();
105
+ await this._replicator.close();
106
+ this._replicator = void 0;
107
+ }
108
+ this._p2pHost = void 0;
109
+ this._activeTransportCompatibilitySignature = void 0;
110
+ }
84
111
  async open() {
85
112
  if (!this.env.services.setting.currentSettings().P2P_Enabled) {
86
113
  Logger(this.translate("P2P.NotEnabled"), LOG_LEVEL_NOTICE);
@@ -89,9 +116,13 @@ class LiveSyncTrysteroReplicator extends LiveSyncAbstractReplicator {
89
116
  this._shouldBeOpen = true;
90
117
  await this._enqueueLifecycleOperation(async () => {
91
118
  if (!this._shouldBeOpen) return;
119
+ const compatibilitySignature = this._getTransportCompatibilitySignature();
92
120
  if (this._replicator && this._p2pHost?.isServing) {
93
- Logger("P2P replicator is already open.");
94
- return;
121
+ if (this._activeTransportCompatibilitySignature === compatibilitySignature) {
122
+ Logger("P2P replicator is already open.");
123
+ return;
124
+ }
125
+ await this._closeTransport();
95
126
  }
96
127
  try {
97
128
  const env = this._buildEnv();
@@ -100,23 +131,20 @@ class LiveSyncTrysteroReplicator extends LiveSyncAbstractReplicator {
100
131
  this._p2pHost = host;
101
132
  this._replicator = replicator;
102
133
  await replicator.open();
134
+ this._activeTransportCompatibilitySignature = compatibilitySignature;
103
135
  } catch (e) {
104
136
  Logger(e instanceof Error ? e.message : "Error while opening P2P connection", LOG_LEVEL_NOTICE);
105
137
  Logger(e, LOG_LEVEL_VERBOSE);
106
138
  this._p2pHost = void 0;
107
139
  this._replicator = void 0;
140
+ this._activeTransportCompatibilitySignature = void 0;
108
141
  }
109
142
  });
110
143
  }
111
144
  async close() {
112
145
  this._shouldBeOpen = false;
113
146
  await this._enqueueLifecycleOperation(async () => {
114
- if (this._replicator) {
115
- this._replicator.disableBroadcastChanges();
116
- await this._replicator.close();
117
- this._replicator = void 0;
118
- }
119
- this._p2pHost = void 0;
147
+ await this._closeTransport();
120
148
  });
121
149
  }
122
150
  closeReplication() {
@@ -13,7 +13,7 @@ import { EVENT_PLATFORM_UNLOADED } from "../../events/coreEvents.js";
13
13
  import { shareRunningResult } from "octagonal-wheels/concurrency/lock_v2";
14
14
  import { Computed } from "octagonal-wheels/dataobject/Computed";
15
15
  import { RpcRoom } from "../../rpc/index.js";
16
- import { TRYSTERO_RPC_DEFAULTS } from "../../rpc/transports/TrysteroTransport.js";
16
+ import { resolveTrysteroRpcOptions } from "../../rpc/transports/TrysteroTransport.js";
17
17
  import { subscribeTrysteroPeerEvents } from "../../rpc/transports/trysteroRoomEvents.js";
18
18
  import { toRpcMethodName } from "./rpcCompat.js";
19
19
  import { generateJoinRoomOptions } from "../../rpc/transports/trysteroUtils.js";
@@ -379,7 +379,7 @@ You can chose as follows:
379
379
  };
380
380
  this._rpcRoom?.close();
381
381
  this._rpcRoom = new RpcRoom({
382
- ...TRYSTERO_RPC_DEFAULTS,
382
+ ...resolveTrysteroRpcOptions(this.settings),
383
383
  transport,
384
384
  canAcceptRequest: async (peerId, method) => {
385
385
  if (method === toRpcMethodName("!reqAuth")) return true;
@@ -68,6 +68,8 @@ export declare const TRYSTERO_RPC_DEFAULTS: {
68
68
  /** SCTP guarantees delivery; retransmission is a last-resort safety net. */
69
69
  readonly chunkMissingRetryMs: 150;
70
70
  };
71
+ /** Resolve the RpcRoom defaults contributed by one P2P connection profile. */
72
+ export declare function resolveTrysteroRpcOptions(settings: Pick<P2PConnectionInfo, "P2P_maxWirePayloadBytes">): Required<Pick<RpcRoomOptions, "maxWirePayloadBytes" | "chunkMissingRetryMs">>;
71
73
  /**
72
74
  * Wrap an already-joined Trystero `Room` as a `TransportAdapter` for `RpcRoom`.
73
75
  *
@@ -2,6 +2,7 @@ import { joinRoom, selfId } from "@trystero-p2p/nostr";
2
2
  import { RpcRoom } from "../RpcRoom.js";
3
3
  import { exposeDB } from "../pouchdb/RpcPouchDBServer.js";
4
4
  import { RpcPouchDBProxy } from "../pouchdb/RpcPouchDBProxy.js";
5
+ import { normaliseP2PMaxWirePayloadBytes } from "../../common/models/setting.p2p.js";
5
6
  import { ConnectionStringParser } from "../../common/ConnectionString.js";
6
7
  import { compatGlobal } from "../../common/coreEnvFunctions.js";
7
8
  import { generateJoinRoomOptions } from "./trysteroUtils.js";
@@ -46,6 +47,12 @@ const TRYSTERO_RPC_DEFAULTS = {
46
47
  /** SCTP guarantees delivery; retransmission is a last-resort safety net. */
47
48
  chunkMissingRetryMs: 150
48
49
  };
50
+ function resolveTrysteroRpcOptions(settings) {
51
+ return {
52
+ ...TRYSTERO_RPC_DEFAULTS,
53
+ maxWirePayloadBytes: normaliseP2PMaxWirePayloadBytes(settings.P2P_maxWirePayloadBytes)
54
+ };
55
+ }
49
56
  function wrapTrysteroRoom(room) {
50
57
  const rpcAction = room.makeAction(RPC_ACTION_NAME);
51
58
  return {
@@ -93,7 +100,11 @@ function joinTrysteroRoomFromUrl(url) {
93
100
  }
94
101
  function serveTrysteroDB(settings, db, ns = "pdb", options) {
95
102
  const roomHandle = joinTrysteroRoom(settings);
96
- const rpcRoom = new RpcRoom({ ...TRYSTERO_RPC_DEFAULTS, transport: roomHandle.transport, ...options });
103
+ const rpcRoom = new RpcRoom({
104
+ ...resolveTrysteroRpcOptions(settings),
105
+ transport: roomHandle.transport,
106
+ ...options
107
+ });
97
108
  exposeDB(rpcRoom, db, ns);
98
109
  return {
99
110
  peerId: roomHandle.peerId,
@@ -107,7 +118,11 @@ function serveTrysteroDB(settings, db, ns = "pdb", options) {
107
118
  }
108
119
  function connectTrysteroDBClient(settings, serverPeerId, dbName, ns = "pdb", options) {
109
120
  const roomHandle = joinTrysteroRoom(settings);
110
- const rpcRoom = new RpcRoom({ ...TRYSTERO_RPC_DEFAULTS, transport: roomHandle.transport, ...options });
121
+ const rpcRoom = new RpcRoom({
122
+ ...resolveTrysteroRpcOptions(settings),
123
+ transport: roomHandle.transport,
124
+ ...options
125
+ });
111
126
  const proxy = new RpcPouchDBProxy(rpcRoom.session(serverPeerId), dbName, ns);
112
127
  return {
113
128
  proxy,
@@ -136,6 +151,7 @@ export {
136
151
  connectTrysteroDBClient,
137
152
  joinTrysteroRoom,
138
153
  joinTrysteroRoomFromUrl,
154
+ resolveTrysteroRpcOptions,
139
155
  serveTrysteroDB,
140
156
  wrapTrysteroRoom
141
157
  };
@@ -1,3 +1,9 @@
1
+ import { P2PConnectionPaths } from "../../common/models/setting.const.js";
2
+ import {
3
+ hasValidP2PTurnServerUrl,
4
+ normaliseP2PConnectionPath,
5
+ splitP2PTurnServerUrls
6
+ } from "../../common/models/setting.p2p.js";
1
7
  import { mixedHash } from "octagonal-wheels/hash/purejs";
2
8
  import { compatGlobal } from "../../common/coreEnvFunctions.js";
3
9
  import { createDiagRTCPeerConnectionConstructor } from "./DiagRTCPeerConnections.js";
@@ -5,7 +11,7 @@ function generateJoinRoomOptions(settings) {
5
11
  const passphraseNumbers = mixedHash(settings.P2P_passphrase, 0);
6
12
  const passphrase = passphraseNumbers[0].toString(36) + passphraseNumbers[1].toString(36);
7
13
  const relays = settings.P2P_relays.split(",").map((e) => e.trim()).filter((e) => e.length > 0);
8
- const turnServers = settings.P2P_turnServers.split(",").map((e) => e.trim()).filter((e) => e.length > 0);
14
+ const turnServers = splitP2PTurnServerUrls(settings.P2P_turnServers);
9
15
  const relayConfig = {
10
16
  manualReconnection: true,
11
17
  urls: relays
@@ -30,6 +36,11 @@ function generateJoinRoomOptions(settings) {
30
36
  }
31
37
  ];
32
38
  }
39
+ if (normaliseP2PConnectionPath(settings.P2P_connectionPath) === P2PConnectionPaths.Relay && hasValidP2PTurnServerUrl(settings.P2P_turnServers)) {
40
+ options.rtcConfig = {
41
+ iceTransportPolicy: "relay"
42
+ };
43
+ }
33
44
  return options;
34
45
  }
35
46
  export {
@@ -4,26 +4,29 @@ import { createInstanceLogFunction } from "../services/lib/logUtils.js";
4
4
  async function prepareDatabaseForUse(host, log, errorManager, showingNotice = false, reopenDatabase = true, ignoreSuspending = false) {
5
5
  const appLifecycle = host.services.appLifecycle;
6
6
  appLifecycle.resetIsReady();
7
- if (!reopenDatabase || await host.services.database.openDatabase({
7
+ if (reopenDatabase && !await host.services.database.openDatabase({
8
8
  databaseEvents: host.services.databaseEvents,
9
9
  replicator: host.services.replicator
10
10
  })) {
11
- if (host.services.database.localDatabase.isReady) {
12
- await host.services.vault.scanVault(showingNotice, ignoreSuspending);
13
- }
14
- const ERR_INITIALISATION_FAILED = `Initializing database has been failed on some module!`;
15
- if (!await host.services.databaseEvents.onDatabaseInitialised(showingNotice)) {
16
- errorManager.showError(ERR_INITIALISATION_FAILED, LOG_LEVEL_NOTICE);
17
- return false;
18
- }
19
- errorManager.clearError(ERR_INITIALISATION_FAILED);
20
- appLifecycle.markIsReady();
21
- await host.services.fileProcessing.commitPendingFileEvents();
22
- return true;
23
- } else {
24
- appLifecycle.resetIsReady();
25
11
  return false;
26
12
  }
13
+ if (!host.services.database.isDatabaseReady()) {
14
+ return false;
15
+ }
16
+ if (!await host.services.vault.scanVault(showingNotice, ignoreSuspending)) {
17
+ return false;
18
+ }
19
+ const ERR_INITIALISATION_FAILED = `Initializing database has been failed on some module!`;
20
+ if (!await host.services.databaseEvents.onDatabaseInitialised(showingNotice)) {
21
+ errorManager.showError(ERR_INITIALISATION_FAILED, LOG_LEVEL_NOTICE);
22
+ return false;
23
+ }
24
+ errorManager.clearError(ERR_INITIALISATION_FAILED);
25
+ if (!await host.services.fileProcessing.commitPendingFileEvents()) {
26
+ return false;
27
+ }
28
+ appLifecycle.markIsReady();
29
+ return true;
27
30
  }
28
31
  function usePrepareDatabaseForUse(host) {
29
32
  const log = createInstanceLogFunction("SF:prepareDatabaseForUse", host.services.API);
@@ -14,6 +14,7 @@ import type { StorageAccess } from "../interfaces/StorageAccess.js";
14
14
  import type { LiveSyncEventHub } from "../hub/hub.js";
15
15
  import { ServiceModuleBase } from "./ServiceModuleBase.js";
16
16
  import type { ControlService } from "../services/base/ControlService.js";
17
+ import type { IFileProcessingService } from "../services/base/IService.js";
17
18
  export interface ServiceRebuilderDependencies {
18
19
  events: LiveSyncEventHub;
19
20
  appLifecycle: AppLifecycleService;
@@ -28,6 +29,7 @@ export interface ServiceRebuilderDependencies {
28
29
  replication: ReplicationService;
29
30
  database: DatabaseService;
30
31
  fileHandler: IFileHandler;
32
+ fileProcessing: IFileProcessingService;
31
33
  control: ControlService;
32
34
  }
33
35
  export declare class ServiceRebuilder extends ServiceModuleBase<ServiceRebuilderDependencies> implements Rebuilder {
@@ -44,6 +46,7 @@ export declare class ServiceRebuilder extends ServiceModuleBase<ServiceRebuilder
44
46
  private replication;
45
47
  private database;
46
48
  private fileHandler;
49
+ private fileProcessing;
47
50
  private control;
48
51
  constructor(services: ServiceRebuilderDependencies);
49
52
  $performRebuildDB(method: "localOnly" | "remoteOnly" | "rebuildBothByThisDevice" | "localOnlyWithChunks"): Promise<void>;
@@ -63,17 +66,37 @@ export declare class ServiceRebuilder extends ServiceModuleBase<ServiceRebuilder
63
66
  private _onResetLocalDatabase;
64
67
  suspendAllSync(): Promise<void>;
65
68
  suspendReflectingDatabase(ignoreMinIO?: boolean): Promise<void>;
66
- resumeReflectingDatabase(ignoreMinIO?: boolean): Promise<void>;
69
+ /**
70
+ * Stage reflection resumption and perform the final reconciliation checks.
71
+ *
72
+ * File watching is resumed in memory before the final scan so that changes
73
+ * made while the scan is running can enter the storage-event queue. This
74
+ * phase deliberately does not persist the resumed settings. The owning
75
+ * completion boundary persists them only after the scan, replication
76
+ * pre-check, and current batch-wait release have succeeded.
77
+ *
78
+ * @param ignoreMinIO Whether to resume reflection for a MinIO remote.
79
+ * @returns `true` when the applicable reconciliation checks succeeded.
80
+ */
81
+ resumeReflectingDatabase(ignoreMinIO?: boolean): Promise<boolean>;
82
+ private prepareLocalDatabaseForRebuild;
83
+ private completePreparedRebuild;
67
84
  fetchLocal(makeLocalChunkBeforeSync?: boolean, preventMakeLocalFilesBeforeSync?: boolean, autoResume?: boolean): Promise<void>;
68
85
  private performFetchLocal;
69
86
  fetchLocalDBFast(autoResume: boolean): Promise<void>;
70
87
  private performFetchLocalDBFast;
71
88
  /**
72
- * Finish rebuild process with resuming the reflection.
89
+ * Complete the final rebuild phases and establish application readiness.
90
+ *
91
+ * A failed completion restores the previous reflection settings in memory.
92
+ * Persistence atomicity remains a responsibility of the host setting store;
93
+ * Commonlib cannot roll back a store which writes successfully and then
94
+ * reports an error.
73
95
  *
74
- * @param ignoreMinIO Whether to ignore minio for resuming the reflection.
96
+ * @param ignoreMinIO Whether to resume reflection for a MinIO remote.
97
+ * @returns `true` when finalisation completed; otherwise, `false`.
75
98
  */
76
- finishRebuild(ignoreMinIO?: boolean): Promise<void>;
99
+ finishRebuild(ignoreMinIO?: boolean): Promise<boolean>;
77
100
  /**
78
101
  * Fetch local database with making all chunks.
79
102
  * This is a wrapper for {@link fetchLocal} with makeLocalChunkBeforeSync = true.