@vrtmrz/livesync-commonlib 0.1.19 → 0.1.20
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/README.md +5 -1
- package/dist/common/models/setting.p2p.d.ts +2 -0
- package/dist/common/models/setting.p2p.js +5 -0
- package/dist/interfaces/ServiceModule.d.ts +13 -4
- package/dist/managers/ChunkFetcher.js +10 -0
- package/dist/p2p/P2PService.d.ts +123 -0
- package/dist/p2p/P2PService.js +271 -0
- package/dist/p2p/index.d.ts +8 -0
- package/dist/p2p/index.js +8 -0
- package/dist/pouchdb/LiveSyncLocalDB.d.ts +3 -0
- package/dist/pouchdb/LiveSyncLocalDB.js +23 -3
- package/dist/pouchdb/ReplicatorShim.d.ts +43 -4
- package/dist/pouchdb/ReplicatorShim.js +78 -5
- package/dist/replication/CentralCompatibility.d.ts +38 -0
- package/dist/replication/CentralCompatibility.js +39 -0
- package/dist/replication/CentralRemoteAdministration.d.ts +84 -0
- package/dist/replication/CentralRemoteAdministration.js +71 -0
- package/dist/replication/LiveSyncAbstractReplicator.d.ts +19 -6
- package/dist/replication/ProviderCapability.d.ts +32 -0
- package/dist/replication/ProviderCapability.js +27 -0
- package/dist/replication/RemoteResource.d.ts +85 -0
- package/dist/replication/RemoteResource.js +17 -0
- package/dist/replication/ReplicatorInstance.d.ts +23 -0
- package/dist/replication/ReplicatorProvider.d.ts +249 -0
- package/dist/replication/ReplicatorProvider.js +145 -0
- package/dist/replication/couchdb/LiveSyncReplicator.d.ts +54 -8
- package/dist/replication/couchdb/LiveSyncReplicator.js +411 -158
- package/dist/replication/index.d.ts +10 -0
- package/dist/replication/index.js +70 -0
- package/dist/replication/journal/JournalSyncCore.d.ts +2 -0
- package/dist/replication/journal/JournalSyncCore.js +18 -5
- package/dist/replication/journal/LiveSyncJournalReplicator.d.ts +38 -10
- package/dist/replication/journal/LiveSyncJournalReplicator.js +271 -63
- package/dist/replication/journal/LiveSyncJournalReplicatorEnv.d.ts +8 -0
- package/dist/replication/journal/objectstore/JournalStorageAdapter.d.ts +2 -0
- package/dist/replication/journal/objectstore/MinioStorageAdapter.d.ts +2 -0
- package/dist/replication/journal/objectstore/MinioStorageAdapter.js +10 -1
- package/dist/replication/trystero/LiveSyncTrysteroReplicator.d.ts +12 -40
- package/dist/replication/trystero/LiveSyncTrysteroReplicator.js +52 -152
- package/dist/replication/trystero/P2PAutomationCoordinator.d.ts +20 -0
- package/dist/replication/trystero/P2PAutomationCoordinator.js +43 -0
- package/dist/replication/trystero/P2PConnectionProbeAdmission.d.ts +32 -0
- package/dist/replication/trystero/P2PConnectionProbeAdmission.js +10 -0
- package/dist/replication/trystero/P2PReplicationWire.d.ts +31 -0
- package/dist/replication/trystero/P2PReplicationWire.js +50 -0
- package/dist/replication/trystero/P2PRoomSession.d.ts +32 -0
- package/dist/replication/trystero/P2PRoomSession.js +101 -0
- package/dist/replication/trystero/P2PRoomSessionOwner.d.ts +61 -0
- package/dist/replication/trystero/P2PRoomSessionOwner.js +249 -0
- package/dist/replication/trystero/TrysteroReplicator.d.ts +88 -36
- package/dist/replication/trystero/TrysteroReplicator.js +273 -113
- package/dist/replication/trystero/TrysteroReplicatorP2PClient.d.ts +7 -4
- package/dist/replication/trystero/TrysteroReplicatorP2PClient.js +21 -14
- package/dist/replication/trystero/TrysteroReplicatorP2PServer.d.ts +26 -3
- package/dist/replication/trystero/TrysteroReplicatorP2PServer.js +84 -39
- package/dist/replication/trystero/UseP2PReplicatorResult.d.ts +10 -6
- package/dist/replication/trystero/addP2PEventHandlers.d.ts +8 -12
- package/dist/replication/trystero/addP2PEventHandlers.js +19 -19
- package/dist/replication/trystero/p2pReplicatorConfigurationIdentity.d.ts +9 -0
- package/dist/replication/trystero/p2pReplicatorConfigurationIdentity.js +30 -0
- package/dist/replication/trystero/types.d.ts +18 -0
- package/dist/replication/trystero/types.js +5 -1
- package/dist/replication/trystero/useP2PReplicatorCommands.d.ts +3 -3
- package/dist/replication/trystero/useP2PReplicatorCommands.js +6 -8
- package/dist/replication/trystero/useP2PReplicatorFeature.d.ts +16 -7
- package/dist/replication/trystero/useP2PReplicatorFeature.js +72 -55
- package/dist/rpc/RpcRoom.d.ts +15 -1
- package/dist/rpc/RpcRoom.js +176 -41
- package/dist/rpc/RpcSession.d.ts +3 -1
- package/dist/rpc/RpcSession.js +2 -2
- package/dist/rpc/index.d.ts +1 -1
- package/dist/rpc/transports/trysteroUtils.js +2 -1
- package/dist/rpc/types.d.ts +26 -1
- package/dist/serviceFeatures/checkRemoteSize.js +5 -2
- package/dist/serviceModules/Rebuilder.js +66 -56
- package/dist/services/base/AppLifecycleService.d.ts +4 -0
- package/dist/services/base/AppLifecycleService.js +4 -0
- package/dist/services/base/ControlService.js +0 -4
- package/dist/services/base/DatabaseEventService.js +1 -1
- package/dist/services/base/IService.d.ts +105 -8
- package/dist/services/base/ReadinessEvaluator.d.ts +40 -0
- package/dist/services/base/ReadinessEvaluator.js +16 -0
- package/dist/services/base/RemoteConnection.d.ts +3 -0
- package/dist/services/base/RemoteService.js +2 -2
- package/dist/services/base/ReplicationService.d.ts +45 -10
- package/dist/services/base/ReplicationService.js +248 -91
- package/dist/services/base/ReplicationService.readiness.d.ts +52 -0
- package/dist/services/base/ReplicationService.readiness.js +87 -0
- package/dist/services/base/ReplicationService.typedReplication.d.ts +83 -0
- package/dist/services/base/ReplicationService.typedReplication.js +191 -0
- package/dist/services/base/ReplicatorService.activeReplicatorState.d.ts +47 -0
- package/dist/services/base/ReplicatorService.activeReplicatorState.js +107 -0
- package/dist/services/base/ReplicatorService.centralRemoteAdministration.d.ts +5 -0
- package/dist/services/base/ReplicatorService.centralRemoteAdministration.js +44 -0
- package/dist/services/base/ReplicatorService.d.ts +75 -14
- package/dist/services/base/ReplicatorService.js +232 -53
- package/dist/services/base/ReplicatorService.remoteResourceResolver.d.ts +4 -0
- package/dist/services/base/ReplicatorService.remoteResourceResolver.js +11 -0
- package/dist/services/base/TweakValueService.d.ts +3 -2
- package/dist/services/implements/injectable/InjectableTweakValueService.d.ts +1 -1
- package/dist/services/lib/HandlerUtils.d.ts +24 -0
- package/dist/services/lib/HandlerUtils.js +40 -0
- package/docs/development.md +40 -7
- package/docs/p2p-transport-lifecycle.md +135 -11
- package/docs/proven-in-use.md +5 -1
- package/docs/remote-configurations.md +4 -0
- package/docs/service-feature-composition.md +250 -0
- package/package.json +17 -1
- package/dist/replication/trystero/P2PReplicatorBase.d.ts +0 -19
- package/dist/replication/trystero/P2PReplicatorCore.d.ts +0 -8
- package/dist/replication/trystero/P2PReplicatorCore.js +0 -56
- package/dist/replication/trystero/TrysteroReplicatorP2PConnection.d.ts +0 -2
- package/dist/replication/trystero/TrysteroReplicatorP2PConnection.js +0 -4
- /package/dist/replication/{trystero/P2PReplicatorBase.js → ReplicatorInstance.js} +0 -0
|
@@ -14,7 +14,24 @@ import { ensureRemoteIsCompatible } from "../../pouchdb/LiveSyncDBFunctions.js";
|
|
|
14
14
|
import { extractObject } from "../../common/utils.js";
|
|
15
15
|
import { clearHandlers } from "../SyncParamsHandler.js";
|
|
16
16
|
import { JournalStorageReadStatuses } from "./objectstore/JournalStorageAdapter.js";
|
|
17
|
+
import {
|
|
18
|
+
CENTRAL_COMPATIBILITY_ACCEPTED,
|
|
19
|
+
CENTRAL_COMPATIBILITY_NOT_ASSESSED,
|
|
20
|
+
CENTRAL_COMPATIBILITY_REJECTION_REASONS,
|
|
21
|
+
centralCompatibilityRejected,
|
|
22
|
+
centralCompatibilityRecoveryHint
|
|
23
|
+
} from "../CentralCompatibility.js";
|
|
24
|
+
import {
|
|
25
|
+
outcomeFromFiniteOpenReplication,
|
|
26
|
+
replicationFailed
|
|
27
|
+
} from "../ReplicatorProvider.js";
|
|
17
28
|
const MILSTONE_DOCID = "_00000000-milestone.json";
|
|
29
|
+
async function readRemoteMilestone(client) {
|
|
30
|
+
const result = await client.downloadJsonWithResult(MILSTONE_DOCID);
|
|
31
|
+
if (result.status === JournalStorageReadStatuses.AVAILABLE) return result.value;
|
|
32
|
+
if (result.status === JournalStorageReadStatuses.NOT_FOUND) return void 0;
|
|
33
|
+
throw result.error;
|
|
34
|
+
}
|
|
18
35
|
const currentVersionRange = {
|
|
19
36
|
min: 0,
|
|
20
37
|
max: 2,
|
|
@@ -28,11 +45,18 @@ class LiveSyncJournalReplicator extends LiveSyncAbstractReplicator {
|
|
|
28
45
|
return this.env.services.keyValueDB.simpleStore;
|
|
29
46
|
}
|
|
30
47
|
_client;
|
|
48
|
+
/** Whether this instance has entered a Journal transfer since its last close. */
|
|
49
|
+
hasEnteredReplication = false;
|
|
50
|
+
/** Transfers whose settlement must be observed by an admitted Stop request. */
|
|
51
|
+
activeJournalTransfers;
|
|
52
|
+
/** Shared settlement for repeated Stop requests at the same boundary. */
|
|
53
|
+
journalTransferStopSettlement;
|
|
54
|
+
/** Monotonic fence which prevents a preflight crossing a later Stop boundary. */
|
|
55
|
+
journalTransferStopGeneration = 0;
|
|
31
56
|
async getReplicationPBKDF2Salt(setting, refresh) {
|
|
32
|
-
return await this.
|
|
57
|
+
return await this.setupJournalSyncClient(setting).getReplicationPBKDF2Salt(refresh);
|
|
33
58
|
}
|
|
34
|
-
setupJournalSyncClient() {
|
|
35
|
-
const settings = this.currentSettings;
|
|
59
|
+
setupJournalSyncClient(settings = this.currentSettings) {
|
|
36
60
|
if (this._client) {
|
|
37
61
|
this._client.applyNewConfig(settings, this.simpleStore, this.env);
|
|
38
62
|
} else {
|
|
@@ -45,13 +69,17 @@ class LiveSyncJournalReplicator extends LiveSyncAbstractReplicator {
|
|
|
45
69
|
}
|
|
46
70
|
return this._client;
|
|
47
71
|
}
|
|
48
|
-
async ensureBucketIsCompatible(deviceNodeID, currentVersionRange2) {
|
|
49
|
-
const
|
|
50
|
-
|
|
72
|
+
async ensureBucketIsCompatible(deviceNodeID, currentVersionRange2, setting = this.currentSettings, client = this.setupJournalSyncClient(setting)) {
|
|
73
|
+
const milestoneResult = await client.downloadJsonWithResult(MILSTONE_DOCID);
|
|
74
|
+
if (milestoneResult.status === JournalStorageReadStatuses.UNAVAILABLE) {
|
|
75
|
+
throw milestoneResult.error;
|
|
76
|
+
}
|
|
77
|
+
const downloadedMilestone = milestoneResult.status === JournalStorageReadStatuses.AVAILABLE ? milestoneResult.value : false;
|
|
78
|
+
const cPointInfo = await client.getCheckpointInfo();
|
|
51
79
|
const progress = [...cPointInfo?.receivedFiles || []].sort().pop() || "";
|
|
52
80
|
return await ensureRemoteIsCompatible(
|
|
53
81
|
downloadedMilestone,
|
|
54
|
-
|
|
82
|
+
setting,
|
|
55
83
|
deviceNodeID,
|
|
56
84
|
currentVersionRange2,
|
|
57
85
|
{
|
|
@@ -62,7 +90,9 @@ class LiveSyncJournalReplicator extends LiveSyncAbstractReplicator {
|
|
|
62
90
|
progress
|
|
63
91
|
},
|
|
64
92
|
async (info) => {
|
|
65
|
-
await
|
|
93
|
+
if (!await client.uploadJson(MILSTONE_DOCID, info)) {
|
|
94
|
+
throw new Error("Could not upload remote milestone");
|
|
95
|
+
}
|
|
66
96
|
}
|
|
67
97
|
);
|
|
68
98
|
}
|
|
@@ -74,40 +104,175 @@ class LiveSyncJournalReplicator extends LiveSyncAbstractReplicator {
|
|
|
74
104
|
Logger(`Database updated from ${from} to ${to}`, LOG_LEVEL_NOTICE);
|
|
75
105
|
return Promise.resolve(true);
|
|
76
106
|
}
|
|
107
|
+
/**
|
|
108
|
+
* Run one Replicator-owned Journal transfer after any earlier Stop boundary.
|
|
109
|
+
*
|
|
110
|
+
* The client owns transfer mechanics, while this Replicator owns the
|
|
111
|
+
* settlement required by its active-transfer cancellation contract.
|
|
112
|
+
*/
|
|
113
|
+
runJournalTransfer(run) {
|
|
114
|
+
const stopGeneration = this.journalTransferStopGeneration;
|
|
115
|
+
let resolveTask;
|
|
116
|
+
let rejectTask;
|
|
117
|
+
const task = new Promise((resolve, reject) => {
|
|
118
|
+
resolveTask = resolve;
|
|
119
|
+
rejectTask = reject;
|
|
120
|
+
});
|
|
121
|
+
const activeTransfers = this.activeJournalTransfers ??= /* @__PURE__ */ new Set();
|
|
122
|
+
activeTransfers.add(task);
|
|
123
|
+
const release = () => activeTransfers.delete(task);
|
|
124
|
+
void task.then(release, release);
|
|
125
|
+
void (async () => {
|
|
126
|
+
const stopping = this.journalTransferStopSettlement;
|
|
127
|
+
if (stopping) {
|
|
128
|
+
await stopping;
|
|
129
|
+
}
|
|
130
|
+
return await run(stopGeneration);
|
|
131
|
+
})().then(resolveTask, rejectTask);
|
|
132
|
+
return task;
|
|
133
|
+
}
|
|
134
|
+
/** Request client cancellation and await the transfers admitted before this Stop boundary. */
|
|
77
135
|
terminateSync() {
|
|
78
|
-
this.
|
|
136
|
+
this.journalTransferStopGeneration += 1;
|
|
137
|
+
const client = this._client;
|
|
138
|
+
client?.requestStop();
|
|
139
|
+
if (this.journalTransferStopSettlement) {
|
|
140
|
+
return this.journalTransferStopSettlement;
|
|
141
|
+
}
|
|
142
|
+
const activeTransfers = [...this.activeJournalTransfers ?? []];
|
|
143
|
+
if (activeTransfers.length === 0) return Promise.resolve();
|
|
144
|
+
let settlement;
|
|
145
|
+
settlement = Promise.allSettled(activeTransfers).then(() => {
|
|
146
|
+
if (this.journalTransferStopSettlement === settlement) {
|
|
147
|
+
this.journalTransferStopSettlement = void 0;
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
this.journalTransferStopSettlement = settlement;
|
|
151
|
+
return settlement;
|
|
79
152
|
}
|
|
80
|
-
async openReplication(setting, _, showResult, ignoreCleanLock = false) {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
153
|
+
async openReplication(setting, _, showResult, ignoreCleanLock = false, recordCompatibilityDecision) {
|
|
154
|
+
return await this.runJournalTransfer(async (stopGeneration) => {
|
|
155
|
+
const client = this.setupJournalSyncClient(setting);
|
|
156
|
+
stopGeneration = this.journalTransferStopGeneration;
|
|
157
|
+
if (!await this.checkReplicationConnectivity(
|
|
158
|
+
false,
|
|
159
|
+
ignoreCleanLock,
|
|
160
|
+
showResult,
|
|
161
|
+
setting,
|
|
162
|
+
client,
|
|
163
|
+
recordCompatibilityDecision
|
|
164
|
+
)) {
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
167
|
+
if (stopGeneration !== this.journalTransferStopGeneration) return false;
|
|
168
|
+
this.hasEnteredReplication = true;
|
|
169
|
+
return await client.sync(showResult);
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Run one finite Journal attempt against one settings-bound borrowed client.
|
|
174
|
+
*
|
|
175
|
+
* Only a rejection observed by this attempt is projected into recovery;
|
|
176
|
+
* unavailable storage and later transport failures do not reuse old state.
|
|
177
|
+
*/
|
|
178
|
+
async openOneShotReplicationWithOutcome(setting, showResult, ignoreCleanLock = false) {
|
|
179
|
+
let decision = CENTRAL_COMPATIBILITY_NOT_ASSESSED;
|
|
180
|
+
const recordDecision = (next) => {
|
|
181
|
+
decision = next;
|
|
182
|
+
};
|
|
183
|
+
try {
|
|
184
|
+
const result = await this.openReplication(setting, false, showResult, ignoreCleanLock, recordDecision);
|
|
185
|
+
return outcomeFromFiniteOpenReplication(result, centralCompatibilityRecoveryHint(decision));
|
|
186
|
+
} catch (error) {
|
|
187
|
+
return replicationFailed(error, centralCompatibilityRecoveryHint(decision));
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
async runDirectionalReplication(setting, showingNotice, transfer, recordCompatibilityDecision) {
|
|
191
|
+
return await this.runJournalTransfer(async (stopGeneration) => {
|
|
192
|
+
const client = this.setupJournalSyncClient(setting);
|
|
193
|
+
stopGeneration = this.journalTransferStopGeneration;
|
|
194
|
+
if (!await this.checkReplicationConnectivity(
|
|
195
|
+
false,
|
|
196
|
+
false,
|
|
197
|
+
!!showingNotice,
|
|
198
|
+
setting,
|
|
199
|
+
client,
|
|
200
|
+
recordCompatibilityDecision
|
|
201
|
+
)) {
|
|
202
|
+
return false;
|
|
203
|
+
}
|
|
204
|
+
if (stopGeneration !== this.journalTransferStopGeneration) return false;
|
|
205
|
+
this.hasEnteredReplication = true;
|
|
206
|
+
return await transfer(client);
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
/** Capture only the compatibility decision made by this exact borrowed client. */
|
|
210
|
+
async runDirectionalReplicationWithOutcome(setting, showingNotice, transfer) {
|
|
211
|
+
let decision = CENTRAL_COMPATIBILITY_NOT_ASSESSED;
|
|
212
|
+
const recordDecision = (next) => {
|
|
213
|
+
decision = next;
|
|
214
|
+
};
|
|
215
|
+
try {
|
|
216
|
+
const result = await this.runDirectionalReplication(setting, showingNotice, transfer, recordDecision);
|
|
217
|
+
return outcomeFromFiniteOpenReplication(result, centralCompatibilityRecoveryHint(decision));
|
|
218
|
+
} catch (error) {
|
|
219
|
+
return replicationFailed(error, centralCompatibilityRecoveryHint(decision));
|
|
220
|
+
}
|
|
84
221
|
}
|
|
85
222
|
async replicateAllToServer(setting, showingNotice) {
|
|
86
|
-
|
|
87
|
-
|
|
223
|
+
return await this.runDirectionalReplication(
|
|
224
|
+
setting,
|
|
225
|
+
showingNotice,
|
|
226
|
+
(client) => client.sendLocalJournal(showingNotice)
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
async replicateAllToServerWithOutcome(setting, showingNotice) {
|
|
230
|
+
return await this.runDirectionalReplicationWithOutcome(
|
|
231
|
+
setting,
|
|
232
|
+
showingNotice,
|
|
233
|
+
(client) => client.sendLocalJournal(showingNotice)
|
|
234
|
+
);
|
|
88
235
|
}
|
|
89
236
|
async replicateAllFromServer(setting, showingNotice) {
|
|
90
|
-
|
|
91
|
-
|
|
237
|
+
return await this.runDirectionalReplication(
|
|
238
|
+
setting,
|
|
239
|
+
showingNotice,
|
|
240
|
+
(client) => client.receiveRemoteJournal(showingNotice)
|
|
241
|
+
);
|
|
92
242
|
}
|
|
93
|
-
async
|
|
94
|
-
|
|
243
|
+
async replicateAllFromServerWithOutcome(setting, showingNotice) {
|
|
244
|
+
return await this.runDirectionalReplicationWithOutcome(
|
|
245
|
+
setting,
|
|
246
|
+
showingNotice,
|
|
247
|
+
(client) => client.receiveRemoteJournal(showingNotice)
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
async checkReplicationConnectivity(skipCheck, ignoreCleanLock = false, showMessage = false, setting = this.currentSettings, client = this.setupJournalSyncClient(setting), recordCompatibilityDecision) {
|
|
251
|
+
recordCompatibilityDecision?.(CENTRAL_COMPATIBILITY_NOT_ASSESSED);
|
|
252
|
+
if (!await client.isAvailable()) {
|
|
95
253
|
return false;
|
|
96
254
|
}
|
|
97
255
|
if (!skipCheck) {
|
|
98
|
-
await
|
|
256
|
+
await client.ensureCheckpointCachesAreFresh();
|
|
99
257
|
this.remoteCleaned = false;
|
|
100
258
|
this.remoteLocked = false;
|
|
101
259
|
this.remoteLockedAndDeviceNotAccepted = false;
|
|
102
260
|
this.tweakSettingsMismatched = false;
|
|
103
|
-
|
|
261
|
+
this.preferredTweakValue = void 0;
|
|
262
|
+
const ensure = await this.ensureBucketIsCompatible(this.nodeid, currentVersionRange, setting, client);
|
|
104
263
|
if (ensure == "INCOMPATIBLE") {
|
|
264
|
+
recordCompatibilityDecision?.(
|
|
265
|
+
centralCompatibilityRejected(CENTRAL_COMPATIBILITY_REJECTION_REASONS.INCOMPATIBLE_VERSION)
|
|
266
|
+
);
|
|
105
267
|
Logger(
|
|
106
268
|
"The remote database has no compatibility with the running version. Please upgrade the plugin.",
|
|
107
269
|
LOG_LEVEL_NOTICE
|
|
108
270
|
);
|
|
109
271
|
return false;
|
|
110
272
|
} else if (ensure == "NODE_LOCKED") {
|
|
273
|
+
recordCompatibilityDecision?.(
|
|
274
|
+
centralCompatibilityRejected(CENTRAL_COMPATIBILITY_REJECTION_REASONS.NODE_LOCKED)
|
|
275
|
+
);
|
|
111
276
|
Logger(
|
|
112
277
|
"The remote database has been rebuilt or corrupted since we have synchronized last time. Fetch rebuilt DB, explicit unlocking or chunk clean-up is required.",
|
|
113
278
|
LOG_LEVEL_NOTICE
|
|
@@ -121,6 +286,9 @@ class LiveSyncJournalReplicator extends LiveSyncAbstractReplicator {
|
|
|
121
286
|
if (ignoreCleanLock) {
|
|
122
287
|
this.remoteLocked = true;
|
|
123
288
|
} else {
|
|
289
|
+
recordCompatibilityDecision?.(
|
|
290
|
+
centralCompatibilityRejected(CENTRAL_COMPATIBILITY_REJECTION_REASONS.NODE_CLEANED)
|
|
291
|
+
);
|
|
124
292
|
Logger(
|
|
125
293
|
"The remote database has been cleaned up. Fetch rebuilt DB, explicit unlocking or chunk clean-up is required.",
|
|
126
294
|
LOG_LEVEL_NOTICE
|
|
@@ -132,40 +300,52 @@ class LiveSyncJournalReplicator extends LiveSyncAbstractReplicator {
|
|
|
132
300
|
}
|
|
133
301
|
} else if (ensure == "OK") {
|
|
134
302
|
} else if (ensure[0] == "MISMATCHED") {
|
|
303
|
+
recordCompatibilityDecision?.(
|
|
304
|
+
centralCompatibilityRejected(CENTRAL_COMPATIBILITY_REJECTION_REASONS.TWEAK_MISMATCH, ensure[1])
|
|
305
|
+
);
|
|
135
306
|
Logger(this.translate("liveSyncReplicator.mismatchedTweakDetected"), LOG_LEVEL_NOTICE);
|
|
136
307
|
this.tweakSettingsMismatched = true;
|
|
137
308
|
this.preferredTweakValue = ensure[1];
|
|
138
309
|
return false;
|
|
139
310
|
}
|
|
311
|
+
recordCompatibilityDecision?.(CENTRAL_COMPATIBILITY_ACCEPTED);
|
|
140
312
|
}
|
|
141
313
|
return true;
|
|
142
314
|
}
|
|
143
|
-
async fetchRemoteChunks(missingChunks, showResult) {
|
|
144
|
-
return Promise.resolve([]);
|
|
145
|
-
}
|
|
146
315
|
closeReplication() {
|
|
147
|
-
this.
|
|
316
|
+
const reportClosure = this.hasEnteredReplication;
|
|
317
|
+
this.hasEnteredReplication = false;
|
|
318
|
+
const client = this._client;
|
|
319
|
+
this._client = void 0;
|
|
320
|
+
client?.dispose();
|
|
148
321
|
this.syncStatus = "CLOSED";
|
|
149
|
-
|
|
322
|
+
if (reportClosure) {
|
|
323
|
+
Logger("Replication closed");
|
|
324
|
+
}
|
|
150
325
|
this.updateInfo();
|
|
151
326
|
}
|
|
152
327
|
async tryResetRemoteDatabase(setting) {
|
|
153
328
|
this.closeReplication();
|
|
154
329
|
try {
|
|
155
|
-
await this.client.resetBucket()
|
|
330
|
+
if (!await this.client.resetBucket()) {
|
|
331
|
+
throw new Error("Could not reset remote bucket");
|
|
332
|
+
}
|
|
156
333
|
clearHandlers();
|
|
157
334
|
Logger("Remote Bucket Cleared", LOG_LEVEL_NOTICE);
|
|
158
335
|
await this.tryCreateRemoteDatabase(setting);
|
|
159
336
|
} catch (ex) {
|
|
160
337
|
Logger("Something happened on Remote Bucket Clear", LOG_LEVEL_NOTICE);
|
|
161
338
|
Logger(ex, LOG_LEVEL_NOTICE);
|
|
339
|
+
throw ex;
|
|
162
340
|
}
|
|
163
341
|
}
|
|
164
342
|
async tryCreateRemoteDatabase(setting) {
|
|
165
343
|
this.closeReplication();
|
|
166
344
|
Logger("Remote Database Created or Connected", LOG_LEVEL_NOTICE);
|
|
167
345
|
clearHandlers();
|
|
168
|
-
await this.ensurePBKDF2Salt(setting, true, false)
|
|
346
|
+
if (!await this.ensurePBKDF2Salt(setting, true, false)) {
|
|
347
|
+
throw new Error("Could not ensure PBKDF2 salt (Security Seed)");
|
|
348
|
+
}
|
|
169
349
|
return await Promise.resolve();
|
|
170
350
|
}
|
|
171
351
|
async markRemoteLocked(setting, locked, lockByClean) {
|
|
@@ -180,9 +360,10 @@ class LiveSyncJournalReplicator extends LiveSyncAbstractReplicator {
|
|
|
180
360
|
node_info: {},
|
|
181
361
|
tweak_values: {}
|
|
182
362
|
};
|
|
363
|
+
const client = this.setupJournalSyncClient(setting);
|
|
183
364
|
const remoteMilestone = {
|
|
184
365
|
...defInitPoint,
|
|
185
|
-
...await
|
|
366
|
+
...await readRemoteMilestone(client) ?? {}
|
|
186
367
|
};
|
|
187
368
|
remoteMilestone.node_chunk_info = { ...defInitPoint.node_chunk_info, ...remoteMilestone.node_chunk_info };
|
|
188
369
|
remoteMilestone.accepted_nodes = [this.nodeid];
|
|
@@ -193,7 +374,9 @@ class LiveSyncJournalReplicator extends LiveSyncAbstractReplicator {
|
|
|
193
374
|
} else {
|
|
194
375
|
Logger("Unlock remote bucket to prevent data corruption", LOG_LEVEL_NOTICE);
|
|
195
376
|
}
|
|
196
|
-
await
|
|
377
|
+
if (!await client.uploadJson(MILSTONE_DOCID, remoteMilestone)) {
|
|
378
|
+
throw new Error("Could not upload remote milestone");
|
|
379
|
+
}
|
|
197
380
|
}
|
|
198
381
|
async markRemoteResolved(setting) {
|
|
199
382
|
const defInitPoint = {
|
|
@@ -206,14 +389,17 @@ class LiveSyncJournalReplicator extends LiveSyncAbstractReplicator {
|
|
|
206
389
|
node_info: {},
|
|
207
390
|
tweak_values: {}
|
|
208
391
|
};
|
|
392
|
+
const client = this.setupJournalSyncClient(setting);
|
|
209
393
|
const remoteMilestone = {
|
|
210
394
|
...defInitPoint,
|
|
211
|
-
...await
|
|
395
|
+
...await readRemoteMilestone(client) ?? {}
|
|
212
396
|
};
|
|
213
397
|
remoteMilestone.node_chunk_info = { ...defInitPoint.node_chunk_info, ...remoteMilestone.node_chunk_info };
|
|
214
398
|
remoteMilestone.accepted_nodes = Array.from(/* @__PURE__ */ new Set([...remoteMilestone.accepted_nodes, this.nodeid]));
|
|
215
399
|
Logger("Mark this device as 'resolved'.", LOG_LEVEL_NOTICE);
|
|
216
|
-
await
|
|
400
|
+
if (!await client.uploadJson(MILSTONE_DOCID, remoteMilestone)) {
|
|
401
|
+
throw new Error("Could not upload remote milestone");
|
|
402
|
+
}
|
|
217
403
|
}
|
|
218
404
|
async tryConnectRemote(setting, showResult = true) {
|
|
219
405
|
const endpoint = setting.endpoint;
|
|
@@ -227,6 +413,8 @@ class LiveSyncJournalReplicator extends LiveSyncAbstractReplicator {
|
|
|
227
413
|
${ex.message}`, LOG_LEVEL_NOTICE);
|
|
228
414
|
Logger(ex, LOG_LEVEL_NOTICE);
|
|
229
415
|
return false;
|
|
416
|
+
} finally {
|
|
417
|
+
testClient.dispose();
|
|
230
418
|
}
|
|
231
419
|
}
|
|
232
420
|
async resetRemoteTweakSettings(setting) {
|
|
@@ -244,54 +432,74 @@ ${ex.message}`, LOG_LEVEL_NOTICE);
|
|
|
244
432
|
}
|
|
245
433
|
}
|
|
246
434
|
async setPreferredRemoteTweakSettings(setting) {
|
|
435
|
+
const trialClient = new JournalSyncCore(
|
|
436
|
+
setting,
|
|
437
|
+
this.simpleStore,
|
|
438
|
+
this.env,
|
|
439
|
+
new MinioStorageAdapter(setting, this.env)
|
|
440
|
+
);
|
|
247
441
|
try {
|
|
248
|
-
const remoteMilestone = await
|
|
442
|
+
const remoteMilestone = await trialClient.downloadJson(MILSTONE_DOCID);
|
|
249
443
|
if (!remoteMilestone) {
|
|
250
444
|
throw new Error("Missing remote milestone");
|
|
251
445
|
}
|
|
252
446
|
remoteMilestone.tweak_values[DEVICE_ID_PREFERRED] = extractObject(TweakValuesTemplate, {
|
|
253
447
|
...setting
|
|
254
448
|
});
|
|
255
|
-
Logger(`tweak values
|
|
256
|
-
await
|
|
449
|
+
Logger(`Preferred tweak values have been registered`, LOG_LEVEL_VERBOSE);
|
|
450
|
+
if (!await trialClient.uploadJson(MILSTONE_DOCID, remoteMilestone)) {
|
|
451
|
+
throw new Error("Could not upload remote milestone");
|
|
452
|
+
}
|
|
257
453
|
} catch (ex) {
|
|
258
|
-
Logger(`Could not
|
|
454
|
+
Logger(`Could not update remote preferred tweak values`, LOG_LEVEL_NOTICE);
|
|
259
455
|
throw ex;
|
|
456
|
+
} finally {
|
|
457
|
+
trialClient.dispose();
|
|
260
458
|
}
|
|
261
459
|
}
|
|
262
|
-
async getRemotePreferredTweakValues(
|
|
263
|
-
const
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
460
|
+
async getRemotePreferredTweakValues(setting) {
|
|
461
|
+
const trialClient = new JournalSyncCore(
|
|
462
|
+
setting,
|
|
463
|
+
this.simpleStore,
|
|
464
|
+
this.env,
|
|
465
|
+
new MinioStorageAdapter(setting, this.env)
|
|
466
|
+
);
|
|
467
|
+
try {
|
|
468
|
+
const result = await trialClient.downloadJsonWithResult(MILSTONE_DOCID);
|
|
469
|
+
if (result.status === JournalStorageReadStatuses.NOT_FOUND) {
|
|
470
|
+
return {
|
|
471
|
+
status: RemotePreferredTweakStatuses.NOT_CONFIGURED,
|
|
472
|
+
reason: RemotePreferredTweakNotConfiguredReasons.MILESTONE_MISSING
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
if (result.status === JournalStorageReadStatuses.UNAVAILABLE) {
|
|
476
|
+
return {
|
|
477
|
+
status: RemotePreferredTweakStatuses.UNAVAILABLE,
|
|
478
|
+
error: result.error
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
const preferred = result.value.tweak_values?.[DEVICE_ID_PREFERRED];
|
|
482
|
+
if (!preferred) {
|
|
483
|
+
return {
|
|
484
|
+
status: RemotePreferredTweakStatuses.NOT_CONFIGURED,
|
|
485
|
+
reason: RemotePreferredTweakNotConfiguredReasons.PREFERRED_VALUES_MISSING
|
|
486
|
+
};
|
|
487
|
+
}
|
|
278
488
|
return {
|
|
279
|
-
status: RemotePreferredTweakStatuses.
|
|
280
|
-
|
|
489
|
+
status: RemotePreferredTweakStatuses.AVAILABLE,
|
|
490
|
+
values: preferred
|
|
281
491
|
};
|
|
492
|
+
} finally {
|
|
493
|
+
trialClient.dispose();
|
|
282
494
|
}
|
|
283
|
-
return {
|
|
284
|
-
status: RemotePreferredTweakStatuses.AVAILABLE,
|
|
285
|
-
values: preferred
|
|
286
|
-
};
|
|
287
495
|
}
|
|
288
496
|
async getRemoteStatus(setting) {
|
|
289
497
|
const testClient = new MinioStorageAdapter(setting, this.env);
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
498
|
+
try {
|
|
499
|
+
return await testClient.getUsage();
|
|
500
|
+
} finally {
|
|
501
|
+
testClient.dispose();
|
|
502
|
+
}
|
|
295
503
|
}
|
|
296
504
|
getConnectedDeviceList(setting) {
|
|
297
505
|
return Promise.resolve(false);
|
|
@@ -1,3 +1,11 @@
|
|
|
1
1
|
import type { LiveSyncReplicatorEnv } from "../LiveSyncAbstractReplicator.js";
|
|
2
|
+
/**
|
|
3
|
+
* Compatibility name for the Journal Replicator constructor environment.
|
|
4
|
+
*
|
|
5
|
+
* Journal currently adds no requirements to {@link LiveSyncReplicatorEnv}.
|
|
6
|
+
* The distinct exported interface preserves the existing constructor contract
|
|
7
|
+
* and an extension point; shared host composition should depend on the base
|
|
8
|
+
* environment unless it specifically constructs a Journal Replicator.
|
|
9
|
+
*/
|
|
2
10
|
export interface LiveSyncJournalReplicatorEnv extends LiveSyncReplicatorEnv {
|
|
3
11
|
}
|
|
@@ -15,6 +15,8 @@ export type JournalStorageReadResult<T> = {
|
|
|
15
15
|
error: unknown;
|
|
16
16
|
};
|
|
17
17
|
export interface IJournalStorage {
|
|
18
|
+
/** Release transport resources retained by this adapter, when any. */
|
|
19
|
+
dispose?(): void;
|
|
18
20
|
upload(key: string, data: Uint8Array, mime: string): Promise<boolean>;
|
|
19
21
|
download(key: string, ignoreCache?: boolean): Promise<Uint8Array | false>;
|
|
20
22
|
downloadWithResult(key: string, ignoreCache?: boolean): Promise<JournalStorageReadResult<Uint8Array>>;
|
|
@@ -10,6 +10,8 @@ export declare class MinioStorageAdapter implements IJournalStorage {
|
|
|
10
10
|
constructor(settings: BucketSyncSetting, env: LiveSyncJournalReplicatorEnv);
|
|
11
11
|
private runTrackedRequest;
|
|
12
12
|
applyNewConfig(settings: BucketSyncSetting): void;
|
|
13
|
+
/** Release the SDK client owned by this adapter. Repeated calls are safe. */
|
|
14
|
+
dispose(): void;
|
|
13
15
|
get customHeaders(): [string, string][];
|
|
14
16
|
_getClient(): S3;
|
|
15
17
|
upload(key: string, data: Uint8Array, mime: string): Promise<boolean>;
|
|
@@ -28,8 +28,14 @@ class MinioStorageAdapter {
|
|
|
28
28
|
return await runWithTrackedPhysicalRequest(this._env.services.API, task);
|
|
29
29
|
}
|
|
30
30
|
applyNewConfig(settings) {
|
|
31
|
+
this.dispose();
|
|
31
32
|
this._settings = settings;
|
|
33
|
+
}
|
|
34
|
+
/** Release the SDK client owned by this adapter. Repeated calls are safe. */
|
|
35
|
+
dispose() {
|
|
36
|
+
const instance = this._instance;
|
|
32
37
|
this._instance = void 0;
|
|
38
|
+
instance?.destroy();
|
|
33
39
|
}
|
|
34
40
|
get customHeaders() {
|
|
35
41
|
return this._settings.bucketCustomHeaders.length == 0 ? [] : Object.entries(parseHeaderValues(this._settings.bucketCustomHeaders));
|
|
@@ -118,7 +124,10 @@ class MinioStorageAdapter {
|
|
|
118
124
|
const cmd = new GetObjectCommand({
|
|
119
125
|
Bucket: this._settings.bucket,
|
|
120
126
|
Key: `${this._settings.bucketPrefix}${key}`,
|
|
121
|
-
|
|
127
|
+
// The SDK serialises this response override as a signed query parameter.
|
|
128
|
+
// Control-object reads use `no-store`: `no-cache` may retain the old
|
|
129
|
+
// response and serve it after a successful PUT in the same renderer.
|
|
130
|
+
...ignoreCache ? { ResponseCacheControl: "no-store" } : {}
|
|
122
131
|
});
|
|
123
132
|
try {
|
|
124
133
|
return await this.runTrackedRequest(async () => {
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { type RemoteDBSettings, type EntryLeaf, type RemotePreferredTweakResult, type LOG_LEVEL, type NodeData } from "../../common/types.js";
|
|
2
2
|
import { LiveSyncAbstractReplicator, type LiveSyncReplicatorEnv, type RemoteDBStatus } from "../LiveSyncAbstractReplicator.js";
|
|
3
3
|
import { TrysteroReplicator } from "./TrysteroReplicator.js";
|
|
4
|
-
import {
|
|
4
|
+
import { type AcceptanceDecision, type RevokeAcceptanceDecision } from "./TrysteroReplicatorP2PServer.js";
|
|
5
5
|
import type { Advertisement } from "./types.js";
|
|
6
|
+
import { type P2PRoomSessionAccess } from "./P2PRoomSessionOwner.js";
|
|
6
7
|
export interface LiveSyncTrysteroReplicatorEnv extends LiveSyncReplicatorEnv {
|
|
7
8
|
/**
|
|
8
9
|
* Injected by the host platform (e.g. Obsidian) to show a UI for peer selection.
|
|
@@ -16,60 +17,31 @@ export interface LiveSyncTrysteroReplicatorEnv extends LiveSyncReplicatorEnv {
|
|
|
16
17
|
openRebuildUI?: (showResult: boolean) => Promise<boolean | void>;
|
|
17
18
|
}
|
|
18
19
|
export declare class LiveSyncTrysteroReplicator extends LiveSyncAbstractReplicator {
|
|
19
|
-
private
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
private _shouldBeOpen;
|
|
23
|
-
private _activeTransportCompatibilitySignature?;
|
|
20
|
+
private readonly sessionOwner;
|
|
21
|
+
/** Peer lifecycle is fenced inside the room session rather than this facade. */
|
|
22
|
+
readonly handlesPeerEventsWithinSession = true;
|
|
24
23
|
get openReplicationUI(): (showResult: boolean) => Promise<boolean | void>;
|
|
25
24
|
get rawReplicator(): TrysteroReplicator;
|
|
26
|
-
get rawHost():
|
|
25
|
+
get rawHost(): import("./TrysteroReplicatorP2PServer.js").TrysteroReplicatorP2PServer;
|
|
27
26
|
getReplicationPBKDF2Salt(_setting: RemoteDBSettings, _refresh?: boolean): Promise<Uint8Array<ArrayBuffer>>;
|
|
28
27
|
terminateSync(): void;
|
|
29
|
-
private _buildEnv;
|
|
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;
|
|
38
28
|
open(): Promise<void>;
|
|
39
29
|
close(): Promise<void>;
|
|
40
30
|
closeReplication(): void;
|
|
41
|
-
get server():
|
|
31
|
+
get server(): import("./TrysteroReplicatorP2PServer.js").TrysteroReplicatorP2PServer;
|
|
42
32
|
get knownAdvertisements(): Advertisement[];
|
|
43
33
|
enableBroadcastChanges(): void;
|
|
44
34
|
disableBroadcastChanges(): void;
|
|
45
35
|
requestStatus(): void;
|
|
46
36
|
onNewPeer(peer: Advertisement): Promise<void>;
|
|
47
37
|
onPeerLeaved(peerId: string): void;
|
|
48
|
-
replicateFromCommand(showResult?: boolean): Promise<
|
|
49
|
-
replicateFrom(peerId: string, showNotice?: boolean, skipOrdinaryReplicationPolicy?: boolean): Promise<
|
|
50
|
-
|
|
51
|
-
ok?: undefined;
|
|
52
|
-
} | {
|
|
53
|
-
ok: boolean;
|
|
54
|
-
error?: undefined;
|
|
55
|
-
}>;
|
|
56
|
-
requestSynchroniseToPeer(peerId: string): Promise<{
|
|
57
|
-
error: any;
|
|
58
|
-
ok?: undefined;
|
|
59
|
-
} | {
|
|
60
|
-
ok: boolean;
|
|
61
|
-
error?: undefined;
|
|
62
|
-
}>;
|
|
38
|
+
replicateFromCommand(showResult?: boolean): Promise<boolean>;
|
|
39
|
+
replicateFrom(peerId: string, showNotice?: boolean, skipOrdinaryReplicationPolicy?: boolean): Promise<import("./TrysteroReplicator.js").P2PReplicationResult>;
|
|
40
|
+
requestSynchroniseToPeer(peerId: string): Promise<import("./TrysteroReplicator.js").P2PReplicationResult>;
|
|
63
41
|
getRemoteConfig(peerId: string): Promise<false | import("../../common/types.js").ObsidianLiveSyncSettings>;
|
|
64
42
|
watchPeer(peerId: string): void;
|
|
65
43
|
unwatchPeer(peerId: string): void;
|
|
66
|
-
sync(peerId: string, showNotice?: boolean): Promise<
|
|
67
|
-
error: any;
|
|
68
|
-
ok?: undefined;
|
|
69
|
-
} | {
|
|
70
|
-
ok: boolean;
|
|
71
|
-
error?: undefined;
|
|
72
|
-
}>;
|
|
44
|
+
sync(peerId: string, showNotice?: boolean): Promise<import("./TrysteroReplicator.js").P2PReplicationResult>;
|
|
73
45
|
setOnSetup(): void;
|
|
74
46
|
clearOnSetup(): void;
|
|
75
47
|
makeDecision(decision: AcceptanceDecision): Promise<void>;
|
|
@@ -96,5 +68,5 @@ export declare class LiveSyncTrysteroReplicator extends LiveSyncAbstractReplicat
|
|
|
96
68
|
accepted_nodes: string[];
|
|
97
69
|
}>;
|
|
98
70
|
env: LiveSyncTrysteroReplicatorEnv;
|
|
99
|
-
constructor(env: LiveSyncTrysteroReplicatorEnv);
|
|
71
|
+
constructor(env: LiveSyncTrysteroReplicatorEnv, sessionOwner?: P2PRoomSessionAccess);
|
|
100
72
|
}
|