@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.
- package/dist/API/DirectFileManipulatorV2.js +3 -1
- package/dist/common/ConnectionString.js +9 -1
- package/dist/common/models/fileaccess.type.d.ts +7 -0
- package/dist/common/models/setting.const.d.ts +18 -0
- package/dist/common/models/setting.const.defaults.js +10 -1
- package/dist/common/models/setting.const.js +12 -0
- package/dist/common/models/setting.const.qr.js +3 -1
- package/dist/common/models/setting.p2p.d.ts +21 -0
- package/dist/common/models/setting.p2p.js +35 -0
- package/dist/common/models/setting.type.d.ts +11 -1
- package/dist/common/models/shared.definition.configNames.js +8 -0
- package/dist/common/models/tweak.definition.d.ts +2 -0
- package/dist/common/settingConstants.d.ts +2 -0
- package/dist/common/types.d.ts +5 -3
- package/dist/common/types.js +17 -1
- package/dist/common/utils.js +4 -1
- package/dist/interfaces/DatabaseRebuilder.d.ts +4 -2
- package/dist/managers/StorageEventManager.d.ts +16 -5
- package/dist/managers/StorageEventManager.js +46 -8
- package/dist/pouchdb/LiveSyncLocalDB.d.ts +7 -1
- package/dist/pouchdb/LiveSyncLocalDB.js +120 -38
- package/dist/replication/trystero/LiveSyncTrysteroReplicator.d.ts +8 -0
- package/dist/replication/trystero/LiveSyncTrysteroReplicator.js +36 -8
- package/dist/replication/trystero/TrysteroReplicatorP2PServer.js +2 -2
- package/dist/rpc/transports/TrysteroTransport.d.ts +2 -0
- package/dist/rpc/transports/TrysteroTransport.js +18 -2
- package/dist/rpc/transports/trysteroUtils.js +12 -1
- package/dist/serviceFeatures/prepareDatabaseForUse.js +18 -15
- package/dist/serviceModules/Rebuilder.d.ts +27 -4
- package/dist/serviceModules/Rebuilder.js +113 -35
- package/dist/serviceModules/ServiceFileHandlerBase.d.ts +17 -0
- package/dist/serviceModules/ServiceFileHandlerBase.js +145 -9
- package/dist/services/base/DatabaseService.d.ts +11 -3
- package/dist/services/base/DatabaseService.js +63 -7
- package/dist/services/base/IService.d.ts +1 -0
- package/dist/services/base/ReplicationService.d.ts +16 -0
- package/dist/services/base/ReplicationService.js +36 -4
- package/dist/services/base/ReplicatorService.d.ts +1 -0
- package/dist/services/base/ReplicatorService.js +32 -13
- package/dist/worker/bgWorker.js +1 -1
- package/docs/development.md +2 -0
- package/docs/p2p-transport-lifecycle.md +4 -2
- package/docs/proven-in-use.md +5 -1
- package/docs/releasing.md +24 -0
- package/docs/remote-configurations.md +2 -0
- 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
|
-
|
|
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
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
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
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
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
|
-
|
|
94
|
-
|
|
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
|
-
|
|
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 {
|
|
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
|
-
...
|
|
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({
|
|
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({
|
|
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
|
|
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 (
|
|
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
|
-
|
|
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
|
-
*
|
|
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
|
|
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<
|
|
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.
|