@vrtmrz/livesync-commonlib 0.1.19-rc.0 → 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
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { LiveSyncReplicatorEnv } from "../LiveSyncAbstractReplicator.js";
|
|
2
|
+
import { P2PRoomSession } from "./P2PRoomSession.js";
|
|
3
|
+
import type { ReplicatorHostEnv } from "./types.js";
|
|
4
|
+
import { type P2PConnectionProbeAdmissionResult, type P2PConnectionProbeSettings } from "./P2PConnectionProbeAdmission.js";
|
|
5
|
+
type P2PRoomSessionFactory = (env: ReplicatorHostEnv) => P2PRoomSession;
|
|
6
|
+
/** Session operations shared by the stable service and its compatibility facade. */
|
|
7
|
+
export interface P2PRoomSessionAccess {
|
|
8
|
+
readonly currentSession: P2PRoomSession | undefined;
|
|
9
|
+
readonly isConnected: boolean;
|
|
10
|
+
cancelActiveTransfers(): void;
|
|
11
|
+
open(): Promise<void>;
|
|
12
|
+
close(): Promise<void>;
|
|
13
|
+
}
|
|
14
|
+
/** Persistent reasons for which the service keeps a P2P room available. */
|
|
15
|
+
export type P2PPersistentRoomDemand = "explicit" | "automatic" | "rebuild-continuation";
|
|
16
|
+
/**
|
|
17
|
+
* Owns the one published P2P room session used by a stable P2P service.
|
|
18
|
+
*
|
|
19
|
+
* Compatibility facades may observe and operate on the published session, but
|
|
20
|
+
* they do not own its construction, replacement, or retirement.
|
|
21
|
+
*/
|
|
22
|
+
export declare class P2PRoomSessionOwner implements P2PRoomSessionAccess {
|
|
23
|
+
private readonly env;
|
|
24
|
+
private readonly createSession;
|
|
25
|
+
private current?;
|
|
26
|
+
private lifecycleOperation;
|
|
27
|
+
private readonly persistentDemands;
|
|
28
|
+
private readonly finiteDemands;
|
|
29
|
+
private readonly automationCoordinator;
|
|
30
|
+
private activeBinding?;
|
|
31
|
+
constructor(env: LiveSyncReplicatorEnv, createSession?: P2PRoomSessionFactory);
|
|
32
|
+
get currentSession(): P2PRoomSession | undefined;
|
|
33
|
+
get isConnected(): boolean;
|
|
34
|
+
cancelActiveTransfers(): void;
|
|
35
|
+
/** Retire automatic baseline history owned by the previous app lifecycle. */
|
|
36
|
+
beginAutomationLifecycle(): void;
|
|
37
|
+
open(): Promise<void>;
|
|
38
|
+
/** Reconcile one long-lived reason for keeping the room available. */
|
|
39
|
+
setPersistentDemand(demand: P2PPersistentRoomDemand, active: boolean): Promise<void>;
|
|
40
|
+
/**
|
|
41
|
+
* Keep the room available only for the lifetime of one finite operation.
|
|
42
|
+
*
|
|
43
|
+
* The token is internal to the owner. Releasing it cannot close a room
|
|
44
|
+
* retained by another finite operation or by a persistent policy demand.
|
|
45
|
+
*/
|
|
46
|
+
runWithFiniteDemand<T>(task: (session: P2PRoomSession) => T | PromiseLike<T>): Promise<T>;
|
|
47
|
+
/** Arbitrate a complete signalling probe on the room lifecycle queue. */
|
|
48
|
+
runConnectionProbe<T>(trialSettings: P2PConnectionProbeSettings, runOwnedTrial: () => Promise<T>): Promise<P2PConnectionProbeAdmissionResult<T>>;
|
|
49
|
+
private reconcileTransport;
|
|
50
|
+
close(): Promise<void>;
|
|
51
|
+
private hasRoomDemand;
|
|
52
|
+
private enqueueLifecycleOperation;
|
|
53
|
+
private closeTransport;
|
|
54
|
+
private bindingsMatch;
|
|
55
|
+
/** Capture immutable session inputs while keeping policy live. */
|
|
56
|
+
private getEffectiveBinding;
|
|
57
|
+
/** Apply policy which can change without retiring the room session. */
|
|
58
|
+
private reconcileCurrentSessionPolicy;
|
|
59
|
+
private buildSessionEnv;
|
|
60
|
+
}
|
|
61
|
+
export {};
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import { Logger } from "../../common/logger.js";
|
|
2
|
+
import {
|
|
3
|
+
LOG_LEVEL_NOTICE,
|
|
4
|
+
LOG_LEVEL_VERBOSE,
|
|
5
|
+
SETTING_KEY_P2P_DEVICE_NAME
|
|
6
|
+
} from "../../common/types.js";
|
|
7
|
+
import { P2PRoomSession } from "./P2PRoomSession.js";
|
|
8
|
+
import { P2PAutomationCoordinator } from "./P2PAutomationCoordinator.js";
|
|
9
|
+
import { PEER_REPLICATION_READINESS } from "../ReplicatorProvider.js";
|
|
10
|
+
import { getP2PReplicatorConfigurationIdentity } from "./p2pReplicatorConfigurationIdentity.js";
|
|
11
|
+
import {
|
|
12
|
+
ACTIVE_P2P_RELAY_BINDING_CONFLICT,
|
|
13
|
+
activeP2PRelayBindingCovers
|
|
14
|
+
} from "./P2PConnectionProbeAdmission.js";
|
|
15
|
+
class P2PRoomSessionOwner {
|
|
16
|
+
constructor(env, createSession = (sessionEnv) => new P2PRoomSession(sessionEnv)) {
|
|
17
|
+
this.env = env;
|
|
18
|
+
this.createSession = createSession;
|
|
19
|
+
}
|
|
20
|
+
env;
|
|
21
|
+
createSession;
|
|
22
|
+
current;
|
|
23
|
+
lifecycleOperation = Promise.resolve();
|
|
24
|
+
persistentDemands = /* @__PURE__ */ new Set();
|
|
25
|
+
finiteDemands = /* @__PURE__ */ new Set();
|
|
26
|
+
automationCoordinator = new P2PAutomationCoordinator();
|
|
27
|
+
activeBinding;
|
|
28
|
+
get currentSession() {
|
|
29
|
+
return this.current;
|
|
30
|
+
}
|
|
31
|
+
get isConnected() {
|
|
32
|
+
return this.current?.host.isServing ?? false;
|
|
33
|
+
}
|
|
34
|
+
cancelActiveTransfers() {
|
|
35
|
+
this.current?.cancelActiveTransfers();
|
|
36
|
+
}
|
|
37
|
+
/** Retire automatic baseline history owned by the previous app lifecycle. */
|
|
38
|
+
beginAutomationLifecycle() {
|
|
39
|
+
this.automationCoordinator.beginLifecycle();
|
|
40
|
+
}
|
|
41
|
+
async open() {
|
|
42
|
+
await this.setPersistentDemand("explicit", true);
|
|
43
|
+
}
|
|
44
|
+
/** Reconcile one long-lived reason for keeping the room available. */
|
|
45
|
+
async setPersistentDemand(demand, active) {
|
|
46
|
+
const enabled = this.env.services.setting.currentSettings().P2P_Enabled;
|
|
47
|
+
if (active && enabled) {
|
|
48
|
+
this.persistentDemands.add(demand);
|
|
49
|
+
} else {
|
|
50
|
+
this.persistentDemands.delete(demand);
|
|
51
|
+
}
|
|
52
|
+
if (active && !enabled) {
|
|
53
|
+
Logger(this.env.services.context.translate("P2P.NotEnabled"), LOG_LEVEL_NOTICE);
|
|
54
|
+
}
|
|
55
|
+
await this.reconcileTransport();
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Keep the room available only for the lifetime of one finite operation.
|
|
59
|
+
*
|
|
60
|
+
* The token is internal to the owner. Releasing it cannot close a room
|
|
61
|
+
* retained by another finite operation or by a persistent policy demand.
|
|
62
|
+
*/
|
|
63
|
+
async runWithFiniteDemand(task) {
|
|
64
|
+
if (!this.env.services.setting.currentSettings().P2P_Enabled) {
|
|
65
|
+
throw new Error("P2P is not enabled.");
|
|
66
|
+
}
|
|
67
|
+
const demand = /* @__PURE__ */ Symbol("p2p-finite-room-demand");
|
|
68
|
+
this.finiteDemands.add(demand);
|
|
69
|
+
try {
|
|
70
|
+
await this.reconcileTransport();
|
|
71
|
+
if (!this.finiteDemands.has(demand)) {
|
|
72
|
+
throw new Error("The P2P room demand was retired before the operation started.");
|
|
73
|
+
}
|
|
74
|
+
const session = this.current;
|
|
75
|
+
if (!session?.host.isServing) {
|
|
76
|
+
throw new Error("The P2P room could not be opened for the finite operation.");
|
|
77
|
+
}
|
|
78
|
+
return await task(session);
|
|
79
|
+
} finally {
|
|
80
|
+
if (this.finiteDemands.delete(demand)) {
|
|
81
|
+
await this.reconcileTransport();
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
/** Arbitrate a complete signalling probe on the room lifecycle queue. */
|
|
86
|
+
runConnectionProbe(trialSettings, runOwnedTrial) {
|
|
87
|
+
return this.enqueueLifecycleOperation(async () => {
|
|
88
|
+
const current = this.current;
|
|
89
|
+
const activeBinding = this.activeBinding;
|
|
90
|
+
if (current?.host.isServing && activeBinding) {
|
|
91
|
+
if (activeP2PRelayBindingCovers(activeBinding.settings, trialSettings)) {
|
|
92
|
+
return { status: "observed-active" };
|
|
93
|
+
}
|
|
94
|
+
return {
|
|
95
|
+
status: "blocked",
|
|
96
|
+
reason: ACTIVE_P2P_RELAY_BINDING_CONFLICT
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
if (current) {
|
|
100
|
+
await this.closeTransport();
|
|
101
|
+
}
|
|
102
|
+
return {
|
|
103
|
+
status: "trial",
|
|
104
|
+
result: await runOwnedTrial()
|
|
105
|
+
};
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
async reconcileTransport() {
|
|
109
|
+
await this.enqueueLifecycleOperation(async () => {
|
|
110
|
+
if (!this.env.services.setting.currentSettings().P2P_Enabled) {
|
|
111
|
+
this.persistentDemands.clear();
|
|
112
|
+
this.finiteDemands.clear();
|
|
113
|
+
}
|
|
114
|
+
if (!this.hasRoomDemand()) {
|
|
115
|
+
await this.closeTransport();
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
const binding = this.getEffectiveBinding();
|
|
119
|
+
if (this.current?.host.isServing && this.bindingsMatch(this.activeBinding, binding)) {
|
|
120
|
+
this.reconcileCurrentSessionPolicy(this.current);
|
|
121
|
+
Logger("P2P replicator is already open.");
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
if (this.current) {
|
|
125
|
+
await this.closeTransport();
|
|
126
|
+
}
|
|
127
|
+
let candidate;
|
|
128
|
+
try {
|
|
129
|
+
candidate = this.createSession(this.buildSessionEnv(binding));
|
|
130
|
+
await candidate.open();
|
|
131
|
+
if (!candidate.host.isServing) {
|
|
132
|
+
throw new Error("The P2P room did not start serving.");
|
|
133
|
+
}
|
|
134
|
+
const currentBinding = this.getEffectiveBinding();
|
|
135
|
+
if (!currentBinding.enabled || !this.hasRoomDemand() || !this.bindingsMatch(binding, currentBinding)) {
|
|
136
|
+
await candidate.retire();
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
this.current = candidate;
|
|
140
|
+
this.activeBinding = binding;
|
|
141
|
+
this.reconcileCurrentSessionPolicy(candidate);
|
|
142
|
+
} catch (error) {
|
|
143
|
+
await candidate?.retire(error).catch((retirementError) => {
|
|
144
|
+
Logger(retirementError, LOG_LEVEL_VERBOSE);
|
|
145
|
+
});
|
|
146
|
+
Logger(error instanceof Error ? error.message : "Error while opening P2P connection", LOG_LEVEL_NOTICE);
|
|
147
|
+
Logger(error, LOG_LEVEL_VERBOSE);
|
|
148
|
+
this.current = void 0;
|
|
149
|
+
this.activeBinding = void 0;
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
async close() {
|
|
154
|
+
this.persistentDemands.clear();
|
|
155
|
+
this.finiteDemands.clear();
|
|
156
|
+
await this.enqueueLifecycleOperation(async () => {
|
|
157
|
+
await this.closeTransport();
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
hasRoomDemand() {
|
|
161
|
+
return this.persistentDemands.size > 0 || this.finiteDemands.size > 0;
|
|
162
|
+
}
|
|
163
|
+
enqueueLifecycleOperation(operation) {
|
|
164
|
+
const queued = this.lifecycleOperation.catch(() => void 0).then(operation);
|
|
165
|
+
this.lifecycleOperation = queued.then(
|
|
166
|
+
() => void 0,
|
|
167
|
+
() => void 0
|
|
168
|
+
);
|
|
169
|
+
return queued;
|
|
170
|
+
}
|
|
171
|
+
async closeTransport() {
|
|
172
|
+
const session = this.current;
|
|
173
|
+
this.current = void 0;
|
|
174
|
+
this.activeBinding = void 0;
|
|
175
|
+
if (session) {
|
|
176
|
+
await session.retire();
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
bindingsMatch(current, candidate) {
|
|
180
|
+
return current?.database === candidate.database && current.signature === candidate.signature;
|
|
181
|
+
}
|
|
182
|
+
/** Capture immutable session inputs while keeping policy live. */
|
|
183
|
+
getEffectiveBinding() {
|
|
184
|
+
const settings = {
|
|
185
|
+
...this.env.services.setting.currentSettings()
|
|
186
|
+
};
|
|
187
|
+
const deviceName = this.env.services.config.getSmallConfig(SETTING_KEY_P2P_DEVICE_NAME) || this.env.services.vault.getVaultName();
|
|
188
|
+
const database = this.env.services.database.localDatabase.localDatabase;
|
|
189
|
+
this.automationCoordinator.reconcileIdentity(
|
|
190
|
+
JSON.stringify([
|
|
191
|
+
settings.P2P_AppID || "self-hosted-livesync",
|
|
192
|
+
settings.P2P_roomID,
|
|
193
|
+
settings.P2P_passphrase
|
|
194
|
+
]),
|
|
195
|
+
database
|
|
196
|
+
);
|
|
197
|
+
return {
|
|
198
|
+
database,
|
|
199
|
+
enabled: settings.P2P_Enabled,
|
|
200
|
+
settings,
|
|
201
|
+
deviceName,
|
|
202
|
+
signature: JSON.stringify([getP2PReplicatorConfigurationIdentity(settings), deviceName])
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
/** Apply policy which can change without retiring the room session. */
|
|
206
|
+
reconcileCurrentSessionPolicy(session) {
|
|
207
|
+
session.replicator.reconcileAutoBroadcast(this.env.services.setting.currentSettings().P2P_AutoBroadcast);
|
|
208
|
+
}
|
|
209
|
+
buildSessionEnv(binding) {
|
|
210
|
+
const services = this.env.services;
|
|
211
|
+
return {
|
|
212
|
+
events: services.context.events,
|
|
213
|
+
translate: services.context.translate,
|
|
214
|
+
settings: binding.settings,
|
|
215
|
+
currentSettings: () => services.setting.currentSettings(),
|
|
216
|
+
db: binding.database,
|
|
217
|
+
get simpleStore() {
|
|
218
|
+
return services.keyValueDB.openSimpleStore("p2p-sync");
|
|
219
|
+
},
|
|
220
|
+
deviceName: binding.deviceName,
|
|
221
|
+
get platform() {
|
|
222
|
+
return services.API.getPlatform();
|
|
223
|
+
},
|
|
224
|
+
get confirm() {
|
|
225
|
+
return services.API.confirm;
|
|
226
|
+
},
|
|
227
|
+
runFiniteReplicationActivity: (task, options) => services.replicator.runFiniteReplicationActivity(task, options),
|
|
228
|
+
canStartOrdinaryReplication: (showMessage = false) => services.replication.isReplicationReady(showMessage, PEER_REPLICATION_READINESS),
|
|
229
|
+
automationCoordinator: this.automationCoordinator,
|
|
230
|
+
processReplicatedDocs: async (docs) => {
|
|
231
|
+
const currentSettings = services.setting.currentSettings();
|
|
232
|
+
if (currentSettings.suspendParseReplicationResult) {
|
|
233
|
+
const docLength = docs.length;
|
|
234
|
+
if (docLength > 0) {
|
|
235
|
+
Logger(
|
|
236
|
+
`P2P sync, but parseReplicationResult is suspended. Ignoring ${docLength} documents.`,
|
|
237
|
+
LOG_LEVEL_VERBOSE
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
await services.replication.parseSynchroniseResult(docs);
|
|
243
|
+
}
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
export {
|
|
248
|
+
P2PRoomSessionOwner
|
|
249
|
+
};
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { type EntryDoc, type ObsidianLiveSyncSettings } from "../../common/types.js";
|
|
2
|
+
import { type LOG_LEVEL } from "octagonal-wheels/common/logger";
|
|
2
3
|
import { type ProgressInfo } from "../../pouchdb/ReplicatorShim.js";
|
|
3
4
|
import type { Confirm } from "../../interfaces/Confirm.js";
|
|
4
5
|
import { type Advertisement, type ReplicatorHostEnv } from "./types.js";
|
|
5
6
|
import { EVENT_P2P_REPLICATOR_PROGRESS, EVENT_P2P_REPLICATOR_STATUS, P2PHost } from "./TrysteroReplicatorP2PServer.js";
|
|
7
|
+
import type { P2PFiniteOperationOwner } from "./P2PRoomSession.js";
|
|
8
|
+
import { type P2PReplicationWireResult } from "./P2PReplicationWire.js";
|
|
6
9
|
export type P2PReplicatorStatus = {
|
|
7
10
|
isBroadcasting: boolean;
|
|
8
11
|
replicatingTo: string[];
|
|
@@ -39,6 +42,58 @@ export type P2PReplicationReport = {
|
|
|
39
42
|
isActive: boolean;
|
|
40
43
|
};
|
|
41
44
|
});
|
|
45
|
+
/**
|
|
46
|
+
* In-process outcome of one P2P replication request.
|
|
47
|
+
*
|
|
48
|
+
* The failed variant deliberately retains an `Error`-compatible value for
|
|
49
|
+
* local callers. RPC handlers must convert it to `P2PReplicationWireResult`,
|
|
50
|
+
* because native `Error` properties do not survive JSON serialisation.
|
|
51
|
+
*/
|
|
52
|
+
export type P2PReplicationResult = {
|
|
53
|
+
readonly status: "completed";
|
|
54
|
+
readonly ok: true;
|
|
55
|
+
readonly error?: never;
|
|
56
|
+
} | {
|
|
57
|
+
readonly status: "cancelled";
|
|
58
|
+
readonly ok?: false;
|
|
59
|
+
readonly error?: never;
|
|
60
|
+
} | {
|
|
61
|
+
readonly status: "failed";
|
|
62
|
+
readonly ok?: false;
|
|
63
|
+
readonly error: unknown;
|
|
64
|
+
};
|
|
65
|
+
export type P2PConfiguredTargetResult = {
|
|
66
|
+
readonly name: string;
|
|
67
|
+
readonly peerId?: string;
|
|
68
|
+
} & ({
|
|
69
|
+
readonly status: "completed";
|
|
70
|
+
} | {
|
|
71
|
+
readonly status: "cancelled";
|
|
72
|
+
} | {
|
|
73
|
+
readonly status: "missing";
|
|
74
|
+
} | {
|
|
75
|
+
readonly status: "rejected";
|
|
76
|
+
} | {
|
|
77
|
+
readonly status: "undecided";
|
|
78
|
+
} | {
|
|
79
|
+
readonly status: "failed";
|
|
80
|
+
readonly error: unknown;
|
|
81
|
+
});
|
|
82
|
+
/** Explicit result of configured, unattended P2P target synchronisation. */
|
|
83
|
+
export type P2PConfiguredReplicationResult = {
|
|
84
|
+
readonly status: "completed";
|
|
85
|
+
readonly targets: readonly P2PConfiguredTargetResult[];
|
|
86
|
+
} | {
|
|
87
|
+
readonly status: "cancelled";
|
|
88
|
+
readonly targets: readonly P2PConfiguredTargetResult[];
|
|
89
|
+
} | {
|
|
90
|
+
readonly status: "blocked";
|
|
91
|
+
readonly reason: "no-targets";
|
|
92
|
+
readonly targets: readonly [];
|
|
93
|
+
} | {
|
|
94
|
+
readonly status: "partial";
|
|
95
|
+
readonly targets: readonly P2PConfiguredTargetResult[];
|
|
96
|
+
};
|
|
42
97
|
declare global {
|
|
43
98
|
interface LSEvents {
|
|
44
99
|
[EVENT_P2P_REPLICATOR_STATUS]: P2PReplicatorStatus;
|
|
@@ -55,18 +110,27 @@ export type AllReplicationClientStatus = {
|
|
|
55
110
|
};
|
|
56
111
|
export declare class TrysteroReplicator {
|
|
57
112
|
_env: ReplicatorHostEnv;
|
|
113
|
+
private readonly finiteOperationOwner?;
|
|
114
|
+
private disposed;
|
|
115
|
+
private readonly fallbackAutomationCoordinator;
|
|
58
116
|
server?: P2PHost;
|
|
59
117
|
replicationStatus(): {};
|
|
60
118
|
get settings(): import("../../common/types.js").P2PSyncSetting;
|
|
119
|
+
/** Latest room-local policy settings supplied by the owning host. */
|
|
120
|
+
get currentSettings(): import("../../common/types.js").P2PSyncSetting;
|
|
61
121
|
get db(): PouchDB.Database<EntryDoc>;
|
|
62
122
|
get deviceName(): string;
|
|
63
123
|
get platform(): string;
|
|
64
124
|
get confirm(): Confirm;
|
|
65
125
|
get translate(): import("../../context.js").MessageTranslator;
|
|
126
|
+
private get automationCoordinator();
|
|
66
127
|
private runFiniteReplicationActivity;
|
|
67
128
|
private canStartOrdinaryReplication;
|
|
68
|
-
constructor(env: ReplicatorHostEnv, server?: P2PHost);
|
|
129
|
+
constructor(env: ReplicatorHostEnv, server?: P2PHost, finiteOperationOwner?: P2PFiniteOperationOwner);
|
|
130
|
+
private runSessionFiniteOperation;
|
|
69
131
|
close(): Promise<void>;
|
|
132
|
+
/** Close the transport and release resources owned for this object's lifetime. */
|
|
133
|
+
dispose(): Promise<void>;
|
|
70
134
|
open(): Promise<void>;
|
|
71
135
|
makeSureOpened(): Promise<void>;
|
|
72
136
|
get autoSyncPeers(): RegExp[];
|
|
@@ -77,31 +141,27 @@ export declare class TrysteroReplicator {
|
|
|
77
141
|
setOnSetup(): void;
|
|
78
142
|
clearOnSetup(): void;
|
|
79
143
|
getTweakSettings(fromPeerId: string): Promise<Partial<ObsidianLiveSyncSettings>>;
|
|
144
|
+
private handleSynchronisationRequest;
|
|
80
145
|
getCommands(): {
|
|
81
|
-
reqSync: (fromPeerId: string) => Promise<
|
|
82
|
-
error: any;
|
|
83
|
-
ok?: undefined;
|
|
84
|
-
} | {
|
|
85
|
-
ok: boolean;
|
|
86
|
-
error?: undefined;
|
|
87
|
-
}>;
|
|
146
|
+
reqSync: (fromPeerId: string) => Promise<P2PReplicationWireResult>;
|
|
88
147
|
"!reqAuth": (fromPeerId: string) => Promise<boolean>;
|
|
89
148
|
getTweakSettings: (fromPeerId: string) => Promise<Partial<ObsidianLiveSyncSettings>>;
|
|
90
149
|
onProgress: (fromPeerId: string) => Promise<{
|
|
91
|
-
error:
|
|
150
|
+
error: import("../../rpc/index.js").RpcErrorShape;
|
|
92
151
|
}>;
|
|
93
152
|
getAllConfig: (fromPeerId: string) => Promise<string | {
|
|
94
|
-
error:
|
|
153
|
+
error: import("../../rpc/index.js").RpcErrorShape;
|
|
95
154
|
}>;
|
|
96
155
|
onProgressAcknowledged: (fromPeerId: string, info: ProgressInfo) => Promise<void>;
|
|
97
156
|
getIsBroadcasting: () => Promise<boolean>;
|
|
98
157
|
requestBroadcasting: (peerId: string) => Promise<true | {
|
|
99
|
-
error:
|
|
158
|
+
error: import("../../rpc/index.js").RpcErrorShape;
|
|
100
159
|
}>;
|
|
101
160
|
};
|
|
102
|
-
requestAuthenticate(peerId: string): Promise<boolean>;
|
|
161
|
+
requestAuthenticate(peerId: string, signal?: AbortSignal): Promise<boolean>;
|
|
103
162
|
lastSeq: string | number;
|
|
104
|
-
requestSynchroniseToPeer(peerId: string): Promise<
|
|
163
|
+
requestSynchroniseToPeer(peerId: string, callerSignal?: AbortSignal): Promise<P2PReplicationResult>;
|
|
164
|
+
private requestSynchroniseToPeerWithinSession;
|
|
105
165
|
requestSynchroniseToAllAvailablePeers(): Promise<void>;
|
|
106
166
|
dispatchStatus(): void;
|
|
107
167
|
requestStatus(): void;
|
|
@@ -109,44 +169,36 @@ export declare class TrysteroReplicator {
|
|
|
109
169
|
_isBroadcasting: boolean;
|
|
110
170
|
disableBroadcastChanges(): void;
|
|
111
171
|
enableBroadcastChanges(): void;
|
|
172
|
+
/** Apply automatic broadcast policy without restarting this room. */
|
|
173
|
+
reconcileAutoBroadcast(enabled: boolean): void;
|
|
112
174
|
get knownAdvertisements(): Advertisement[];
|
|
113
175
|
availableReplicationPairs: Set<string>;
|
|
114
|
-
sync(remotePeer: string, showNotice?: boolean): Promise<
|
|
115
|
-
|
|
116
|
-
ok?: undefined;
|
|
117
|
-
} | {
|
|
118
|
-
ok: boolean;
|
|
119
|
-
error?: undefined;
|
|
120
|
-
}>;
|
|
176
|
+
sync(remotePeer: string, showNotice?: boolean, callerSignal?: AbortSignal): Promise<P2PReplicationResult>;
|
|
177
|
+
private syncWithinSession;
|
|
121
178
|
_replicateToPeers: Set<string>;
|
|
122
179
|
_replicateFromPeers: Set<string>;
|
|
123
180
|
dispatchReplicationProgress(peerId: string, info?: ProgressInfo): void;
|
|
124
181
|
onReplicationProgress(peerId: string, info?: ProgressInfo): boolean;
|
|
125
182
|
onProgressAcknowledged(peerId: string, info?: ProgressInfo): boolean;
|
|
126
183
|
acknowledgeProgress(remotePeerId: string, info?: ProgressInfo): void;
|
|
127
|
-
replicateFrom(remotePeer: string, showNotice?: boolean, fromStart?: boolean, skipOrdinaryReplicationPolicy?: boolean): Promise<
|
|
128
|
-
|
|
129
|
-
ok?: undefined;
|
|
130
|
-
} | {
|
|
131
|
-
ok: boolean;
|
|
132
|
-
error?: undefined;
|
|
133
|
-
}>;
|
|
184
|
+
replicateFrom(remotePeer: string, showNotice?: boolean, fromStart?: boolean, skipOrdinaryReplicationPolicy?: boolean, callerSignal?: AbortSignal): Promise<P2PReplicationResult>;
|
|
185
|
+
private replicateFromWithinSession;
|
|
134
186
|
notifyProgress(excludePeerId?: string): Promise<void>;
|
|
135
187
|
requestBroadcastChanges(peerId: string): Promise<unknown>;
|
|
136
188
|
getRemoteIsBroadcasting(peerId: string): Promise<unknown>;
|
|
137
189
|
_watchingPeers: Set<string>;
|
|
138
190
|
watchPeer(peerId: string): void;
|
|
139
191
|
unwatchPeer(peerId: string): void;
|
|
140
|
-
onUpdateDatabase(fromPeerId: string): Promise<false |
|
|
141
|
-
error: any;
|
|
142
|
-
ok?: undefined;
|
|
143
|
-
} | {
|
|
144
|
-
ok: boolean;
|
|
145
|
-
error?: undefined;
|
|
146
|
-
}>;
|
|
192
|
+
onUpdateDatabase(fromPeerId: string): Promise<false | P2PReplicationResult>;
|
|
147
193
|
getRemoteConfig(peerId: string): Promise<false | ObsidianLiveSyncSettings>;
|
|
148
|
-
|
|
149
|
-
|
|
194
|
+
/** Compare peer settings without exceeding the presentation authority of the initiating call. */
|
|
195
|
+
checkTweakValues(peerId: string, signal?: AbortSignal, logLevel?: LOG_LEVEL): Promise<boolean>;
|
|
196
|
+
private configuredPeerNames;
|
|
197
|
+
private findAdvertisement;
|
|
198
|
+
private waitForConfiguredAdvertisements;
|
|
199
|
+
private configuredTargetResult;
|
|
200
|
+
/** Synchronise configured peer targets without opening peer-selection UI. */
|
|
201
|
+
replicateFromCommand(showResult?: boolean, discoveryTimeoutMs?: number, callerSignal?: AbortSignal): Promise<P2PConfiguredReplicationResult>;
|
|
150
202
|
disconnectFromServer(): void;
|
|
151
203
|
pauseServe(): Promise<void>;
|
|
152
204
|
allowReconnection(): void;
|