@vrtmrz/livesync-commonlib 0.1.19 → 0.1.21
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 +262 -0
- package/dist/replication/ReplicatorProvider.js +150 -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 +72 -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 +73 -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 +250 -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 +93 -0
- package/dist/services/base/ReplicationService.typedReplication.js +214 -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
|
@@ -1,21 +1,45 @@
|
|
|
1
1
|
import { TweakValuesShouldMatchedTemplate } from "../../common/types.js";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
LOG_LEVEL_INFO,
|
|
4
|
+
LOG_LEVEL_NOTICE,
|
|
5
|
+
LOG_LEVEL_VERBOSE,
|
|
6
|
+
Logger
|
|
7
|
+
} from "octagonal-wheels/common/logger";
|
|
3
8
|
import { replicateShim } from "../../pouchdb/ReplicatorShim.js";
|
|
4
|
-
import {} from "./types.js";
|
|
5
|
-
import { TrysteroConnection } from "./TrysteroReplicatorP2PConnection.js";
|
|
9
|
+
import { resolveCurrentP2PSettings } from "./types.js";
|
|
6
10
|
import { scheduleOnceIfDuplicated, serialized, skipIfDuplicated } from "octagonal-wheels/concurrency/lock_v2";
|
|
7
11
|
import { delay, fireAndForget } from "octagonal-wheels/promises";
|
|
8
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
EVENT_ADVERTISEMENT_RECEIVED,
|
|
14
|
+
EVENT_P2P_REPLICATOR_PROGRESS,
|
|
15
|
+
EVENT_P2P_REPLICATOR_STATUS,
|
|
16
|
+
P2PHost
|
|
17
|
+
} from "./TrysteroReplicatorP2PServer.js";
|
|
9
18
|
import { encryptWithEphemeralSalt, decryptWithEphemeralSalt } from "octagonal-wheels/encryption/hkdf";
|
|
10
19
|
import { sha1 } from "octagonal-wheels/hash/purejs";
|
|
11
20
|
import { isObjectDifferent } from "octagonal-wheels/object";
|
|
12
21
|
import { getRelaySockets, pauseRelayReconnection, resumeRelayReconnection } from "@trystero-p2p/nostr";
|
|
22
|
+
import { P2PAutomationCoordinator } from "./P2PAutomationCoordinator.js";
|
|
23
|
+
import { compatGlobal } from "../../common/coreEnvFunctions.js";
|
|
24
|
+
import {
|
|
25
|
+
fromP2PReplicationWireResult,
|
|
26
|
+
toP2PReplicationWireResult,
|
|
27
|
+
toP2PWireError
|
|
28
|
+
} from "./P2PReplicationWire.js";
|
|
13
29
|
async function encrypt(data, passphrase) {
|
|
14
30
|
return await encryptWithEphemeralSalt(data, passphrase, true);
|
|
15
31
|
}
|
|
16
32
|
async function decrypt(encryptedData, passphrase) {
|
|
17
33
|
return await decryptWithEphemeralSalt(encryptedData, passphrase);
|
|
18
34
|
}
|
|
35
|
+
const P2P_REPLICATION_COMPLETED = Object.freeze({ status: "completed", ok: true });
|
|
36
|
+
const P2P_REPLICATION_CANCELLED = Object.freeze({ status: "cancelled" });
|
|
37
|
+
function p2pReplicationFailed(error) {
|
|
38
|
+
return { status: "failed", error };
|
|
39
|
+
}
|
|
40
|
+
function isP2PReplicationCancelled(result) {
|
|
41
|
+
return result.status === "cancelled";
|
|
42
|
+
}
|
|
19
43
|
async function getHashedStringWithCurrentTime(source) {
|
|
20
44
|
const salt = (~~((/* @__PURE__ */ new Date()).getTime() / 1e3 / 180)).toString(36);
|
|
21
45
|
const salt2 = await sha1(salt);
|
|
@@ -23,6 +47,9 @@ async function getHashedStringWithCurrentTime(source) {
|
|
|
23
47
|
}
|
|
24
48
|
class TrysteroReplicator {
|
|
25
49
|
_env;
|
|
50
|
+
finiteOperationOwner;
|
|
51
|
+
disposed = false;
|
|
52
|
+
fallbackAutomationCoordinator = new P2PAutomationCoordinator();
|
|
26
53
|
server;
|
|
27
54
|
replicationStatus() {
|
|
28
55
|
return {};
|
|
@@ -30,6 +57,10 @@ class TrysteroReplicator {
|
|
|
30
57
|
get settings() {
|
|
31
58
|
return this._env.settings;
|
|
32
59
|
}
|
|
60
|
+
/** Latest room-local policy settings supplied by the owning host. */
|
|
61
|
+
get currentSettings() {
|
|
62
|
+
return resolveCurrentP2PSettings(this._env);
|
|
63
|
+
}
|
|
33
64
|
get db() {
|
|
34
65
|
return this._env.db;
|
|
35
66
|
}
|
|
@@ -45,6 +76,9 @@ class TrysteroReplicator {
|
|
|
45
76
|
get translate() {
|
|
46
77
|
return this._env.translate;
|
|
47
78
|
}
|
|
79
|
+
get automationCoordinator() {
|
|
80
|
+
return this._env.automationCoordinator ?? this.fallbackAutomationCoordinator;
|
|
81
|
+
}
|
|
48
82
|
async runFiniteReplicationActivity(task) {
|
|
49
83
|
if (this._env.runFiniteReplicationActivity) {
|
|
50
84
|
return await this._env.runFiniteReplicationActivity(task, { label: "replication" });
|
|
@@ -54,8 +88,9 @@ class TrysteroReplicator {
|
|
|
54
88
|
async canStartOrdinaryReplication(showMessage = false) {
|
|
55
89
|
return this._env.canStartOrdinaryReplication ? await this._env.canStartOrdinaryReplication(showMessage) : true;
|
|
56
90
|
}
|
|
57
|
-
constructor(env, server) {
|
|
91
|
+
constructor(env, server, finiteOperationOwner) {
|
|
58
92
|
this._env = env;
|
|
93
|
+
this.finiteOperationOwner = finiteOperationOwner;
|
|
59
94
|
if (server) {
|
|
60
95
|
this.server = server;
|
|
61
96
|
return;
|
|
@@ -76,13 +111,19 @@ class TrysteroReplicator {
|
|
|
76
111
|
if (!this.settings.P2P_relays || this.settings.P2P_relays.length === 0) {
|
|
77
112
|
throw new Error("No relay URIs provided. We need them to establish the P2P connection");
|
|
78
113
|
}
|
|
79
|
-
this.server = new
|
|
114
|
+
this.server = new P2PHost(env);
|
|
80
115
|
} catch (e) {
|
|
81
116
|
Logger(e instanceof Error ? e.message : "Error while creating TrysteroReplicator", LOG_LEVEL_NOTICE);
|
|
82
117
|
Logger(e, LOG_LEVEL_VERBOSE);
|
|
83
118
|
throw e;
|
|
84
119
|
}
|
|
85
120
|
}
|
|
121
|
+
async runSessionFiniteOperation(task, callerSignal) {
|
|
122
|
+
if (this.finiteOperationOwner) {
|
|
123
|
+
return await this.finiteOperationOwner.runFiniteOperation(task, callerSignal);
|
|
124
|
+
}
|
|
125
|
+
return await task(callerSignal);
|
|
126
|
+
}
|
|
86
127
|
async close() {
|
|
87
128
|
this.requestStatus();
|
|
88
129
|
await this.server?.shutdown();
|
|
@@ -92,13 +133,31 @@ class TrysteroReplicator {
|
|
|
92
133
|
this.requestStatus();
|
|
93
134
|
this.disconnectFromServer();
|
|
94
135
|
}
|
|
136
|
+
/** Close the transport and release resources owned for this object's lifetime. */
|
|
137
|
+
async dispose() {
|
|
138
|
+
if (this.disposed) return;
|
|
139
|
+
this.disposed = true;
|
|
140
|
+
try {
|
|
141
|
+
await this.close();
|
|
142
|
+
} finally {
|
|
143
|
+
this.server?.dispose();
|
|
144
|
+
}
|
|
145
|
+
}
|
|
95
146
|
async open() {
|
|
147
|
+
if (this.disposed) {
|
|
148
|
+
throw new Error("The P2P Replicator has been disposed.");
|
|
149
|
+
}
|
|
96
150
|
this.allowReconnection();
|
|
97
|
-
|
|
151
|
+
const commands = this.getCommands();
|
|
152
|
+
await this.server?.start(
|
|
153
|
+
[commands],
|
|
154
|
+
() => this.server?.serveCancellationAwareFunction(
|
|
155
|
+
"reqSync",
|
|
156
|
+
async ({ signal }, _peerId, fromPeerId) => toP2PReplicationWireResult(await this.handleSynchronisationRequest(fromPeerId, signal))
|
|
157
|
+
)
|
|
158
|
+
);
|
|
98
159
|
this.dispatchStatus();
|
|
99
|
-
|
|
100
|
-
this.enableBroadcastChanges();
|
|
101
|
-
}
|
|
160
|
+
this.reconcileAutoBroadcast(this.currentSettings.P2P_AutoBroadcast);
|
|
102
161
|
}
|
|
103
162
|
async makeSureOpened() {
|
|
104
163
|
if (!this.server?.isServing) {
|
|
@@ -106,19 +165,27 @@ class TrysteroReplicator {
|
|
|
106
165
|
}
|
|
107
166
|
}
|
|
108
167
|
get autoSyncPeers() {
|
|
109
|
-
const peers = this.
|
|
168
|
+
const peers = this.currentSettings.P2P_AutoSyncPeers.split(",").map((e) => e.trim()).filter((e) => e.length > 0).map((e) => e.startsWith("~") ? new RegExp(e.substring(1), "i") : new RegExp(`^${e}$`, "i"));
|
|
110
169
|
return peers;
|
|
111
170
|
}
|
|
112
171
|
get autoWatchPeers() {
|
|
113
|
-
const peers = this.
|
|
172
|
+
const peers = this.currentSettings.P2P_AutoWatchPeers.split(",").map((e) => e.trim()).filter((e) => e.length > 0).map((e) => e.startsWith("~") ? new RegExp(e.substring(1), "i") : new RegExp(`^${e}$`, "i"));
|
|
114
173
|
return peers;
|
|
115
174
|
}
|
|
116
175
|
async onNewPeer(peer) {
|
|
117
176
|
const peerName = peer.name;
|
|
118
|
-
|
|
119
|
-
|
|
177
|
+
const shouldAutoSync = this.autoSyncPeers.some((e) => e.test(peerName));
|
|
178
|
+
const shouldAutoWatch = this.autoWatchPeers.some((e) => e.test(peerName));
|
|
179
|
+
if (!shouldAutoSync && !shouldAutoWatch) return;
|
|
180
|
+
const acceptance = await this.server?.evaluatePeerAcceptance(peer.peerId);
|
|
181
|
+
if (acceptance !== "accepted") return;
|
|
182
|
+
if (shouldAutoSync) {
|
|
183
|
+
await this.automationCoordinator.runBaseline(
|
|
184
|
+
peerName,
|
|
185
|
+
() => this.runFiniteReplicationActivity(() => this.sync(peer.peerId))
|
|
186
|
+
);
|
|
120
187
|
}
|
|
121
|
-
if (this.
|
|
188
|
+
if (shouldAutoWatch && await this.getRemoteIsBroadcasting(peer.peerId) === true) {
|
|
122
189
|
this.watchPeer(peer.peerId);
|
|
123
190
|
}
|
|
124
191
|
}
|
|
@@ -148,15 +215,19 @@ class TrysteroReplicator {
|
|
|
148
215
|
}
|
|
149
216
|
return allSettings;
|
|
150
217
|
}
|
|
218
|
+
async handleSynchronisationRequest(fromPeerId, signal) {
|
|
219
|
+
if (this._onSetup) {
|
|
220
|
+
return p2pReplicationFailed(new Error("The setup is in progress"));
|
|
221
|
+
}
|
|
222
|
+
return await this.runFiniteReplicationActivity(
|
|
223
|
+
() => signal ? this.replicateFrom(fromPeerId, false, false, false, signal) : this.replicateFrom(fromPeerId)
|
|
224
|
+
);
|
|
225
|
+
}
|
|
151
226
|
getCommands() {
|
|
152
227
|
return {
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
}
|
|
157
|
-
const result = await this.runFiniteReplicationActivity(() => this.replicateFrom(fromPeerId));
|
|
158
|
-
return result;
|
|
159
|
-
},
|
|
228
|
+
// The wire-visible command deliberately accepts only serialisable
|
|
229
|
+
// arguments. RpcRoom supplies cancellation context separately.
|
|
230
|
+
reqSync: async (fromPeerId) => toP2PReplicationWireResult(await this.handleSynchronisationRequest(fromPeerId)),
|
|
160
231
|
"!reqAuth": async (fromPeerId) => {
|
|
161
232
|
return await this.server?.isAcceptablePeer(fromPeerId);
|
|
162
233
|
},
|
|
@@ -165,13 +236,13 @@ class TrysteroReplicator {
|
|
|
165
236
|
},
|
|
166
237
|
onProgress: async (fromPeerId) => {
|
|
167
238
|
if (this._onSetup) {
|
|
168
|
-
return { error: new Error("The setup is in progress") };
|
|
239
|
+
return { error: toP2PWireError(new Error("The setup is in progress")) };
|
|
169
240
|
}
|
|
170
241
|
await this.onUpdateDatabase(fromPeerId);
|
|
171
242
|
},
|
|
172
243
|
getAllConfig: async (fromPeerId) => {
|
|
173
244
|
if (this._onSetup) {
|
|
174
|
-
return { error: new Error("The setup is in progress") };
|
|
245
|
+
return { error: toP2PWireError(new Error("The setup is in progress")) };
|
|
175
246
|
}
|
|
176
247
|
const passphrase = await skipIfDuplicated(`getAllConfig-${fromPeerId}`, async () => {
|
|
177
248
|
return await this.confirm.askString(
|
|
@@ -218,7 +289,7 @@ class TrysteroReplicator {
|
|
|
218
289
|
},
|
|
219
290
|
requestBroadcasting: async (peerId) => {
|
|
220
291
|
if (this._onSetup) {
|
|
221
|
-
return { error: new Error("The setup is in progress") };
|
|
292
|
+
return { error: toP2PWireError(new Error("The setup is in progress")) };
|
|
222
293
|
}
|
|
223
294
|
if (this._isBroadcasting) {
|
|
224
295
|
return true;
|
|
@@ -235,7 +306,7 @@ class TrysteroReplicator {
|
|
|
235
306
|
}
|
|
236
307
|
};
|
|
237
308
|
}
|
|
238
|
-
async requestAuthenticate(peerId) {
|
|
309
|
+
async requestAuthenticate(peerId, signal) {
|
|
239
310
|
if (!this.server) return false;
|
|
240
311
|
try {
|
|
241
312
|
const connection = this.server.getConnection(peerId);
|
|
@@ -243,7 +314,8 @@ class TrysteroReplicator {
|
|
|
243
314
|
const r = await connection.invokeRemoteObjectFunction(
|
|
244
315
|
"!reqAuth",
|
|
245
316
|
[selfPeerId],
|
|
246
|
-
2e4
|
|
317
|
+
2e4,
|
|
318
|
+
signal
|
|
247
319
|
);
|
|
248
320
|
return r;
|
|
249
321
|
} catch (e) {
|
|
@@ -252,30 +324,34 @@ class TrysteroReplicator {
|
|
|
252
324
|
return false;
|
|
253
325
|
}
|
|
254
326
|
}
|
|
255
|
-
// async selectPeer() {
|
|
256
|
-
// if (!this.server) return false;
|
|
257
|
-
// const knownPeers = this.server.knownAdvertisements;
|
|
258
|
-
// if (knownPeers.length === 0) {
|
|
259
|
-
// Logger("No known peers", LOG_LEVEL_VERBOSE);
|
|
260
|
-
// return false;
|
|
261
|
-
// }
|
|
262
|
-
// const peers = [...Object.entries(knownPeers)].map(([peerId, info]) => {
|
|
263
|
-
// return `${info.peerId}\u2001: (${info.name})`;
|
|
264
|
-
// });
|
|
265
|
-
// const selectedPeer = await this.confirm.askSelectString("Select a peer to replicate", peers);
|
|
266
|
-
// if (selectedPeer) return selectedPeer.split("\u2001")[0];
|
|
267
|
-
// return false;
|
|
268
|
-
// }
|
|
269
327
|
lastSeq = "";
|
|
270
|
-
async requestSynchroniseToPeer(peerId) {
|
|
328
|
+
async requestSynchroniseToPeer(peerId, callerSignal) {
|
|
329
|
+
return await this.runSessionFiniteOperation(
|
|
330
|
+
async (signal) => await this.requestSynchroniseToPeerWithinSession(peerId, signal),
|
|
331
|
+
callerSignal
|
|
332
|
+
);
|
|
333
|
+
}
|
|
334
|
+
async requestSynchroniseToPeerWithinSession(peerId, signal) {
|
|
271
335
|
if (!await this.canStartOrdinaryReplication(false)) {
|
|
272
|
-
return
|
|
336
|
+
return p2pReplicationFailed(new Error("Replication is not ready"));
|
|
273
337
|
}
|
|
338
|
+
if (signal?.aborted) return P2P_REPLICATION_CANCELLED;
|
|
274
339
|
await delay(25);
|
|
340
|
+
if (signal?.aborted) return P2P_REPLICATION_CANCELLED;
|
|
275
341
|
if (!this.server) throw new Error("Server is not available");
|
|
276
342
|
const conn = this.server.getConnection(peerId);
|
|
277
|
-
|
|
278
|
-
|
|
343
|
+
try {
|
|
344
|
+
const result = await conn.invokeRemoteFunction(
|
|
345
|
+
"reqSync",
|
|
346
|
+
[this.server.serverPeerId],
|
|
347
|
+
0,
|
|
348
|
+
signal
|
|
349
|
+
);
|
|
350
|
+
return fromP2PReplicationWireResult(result);
|
|
351
|
+
} catch (error) {
|
|
352
|
+
if (signal?.aborted) return P2P_REPLICATION_CANCELLED;
|
|
353
|
+
throw error;
|
|
354
|
+
}
|
|
279
355
|
}
|
|
280
356
|
async requestSynchroniseToAllAvailablePeers() {
|
|
281
357
|
await scheduleOnceIfDuplicated("requestSynchroniseToAllAvailablePeers", async () => {
|
|
@@ -287,6 +363,7 @@ class TrysteroReplicator {
|
|
|
287
363
|
});
|
|
288
364
|
}
|
|
289
365
|
dispatchStatus() {
|
|
366
|
+
if (this.disposed) return;
|
|
290
367
|
this._env.events.emitEvent(EVENT_P2P_REPLICATOR_STATUS, {
|
|
291
368
|
isBroadcasting: this._isBroadcasting,
|
|
292
369
|
replicatingTo: [...this._replicateToPeers],
|
|
@@ -347,20 +424,37 @@ class TrysteroReplicator {
|
|
|
347
424
|
void this.changes.on("complete", closeChanges);
|
|
348
425
|
fireAndForget(async () => await this.notifyProgress());
|
|
349
426
|
}
|
|
427
|
+
/** Apply automatic broadcast policy without restarting this room. */
|
|
428
|
+
reconcileAutoBroadcast(enabled) {
|
|
429
|
+
if (enabled === this._isBroadcasting) return;
|
|
430
|
+
if (enabled) {
|
|
431
|
+
this.enableBroadcastChanges();
|
|
432
|
+
} else {
|
|
433
|
+
this.disableBroadcastChanges();
|
|
434
|
+
}
|
|
435
|
+
}
|
|
350
436
|
get knownAdvertisements() {
|
|
351
437
|
return this.server?.knownAdvertisements ?? [];
|
|
352
438
|
}
|
|
353
439
|
availableReplicationPairs = /* @__PURE__ */ new Set();
|
|
354
|
-
async sync(remotePeer, showNotice = false) {
|
|
355
|
-
|
|
356
|
-
|
|
440
|
+
async sync(remotePeer, showNotice = false, callerSignal) {
|
|
441
|
+
return await this.runSessionFiniteOperation(
|
|
442
|
+
async (signal) => await this.syncWithinSession(remotePeer, showNotice, signal),
|
|
443
|
+
callerSignal
|
|
444
|
+
);
|
|
445
|
+
}
|
|
446
|
+
async syncWithinSession(remotePeer, showNotice, signal) {
|
|
447
|
+
const from = await this.replicateFrom(remotePeer, showNotice, false, false, signal);
|
|
448
|
+
if (isP2PReplicationCancelled(from)) return from;
|
|
449
|
+
if (from.error) {
|
|
357
450
|
Logger("Error while replicating from the remote", LOG_LEVEL_VERBOSE);
|
|
358
451
|
Logger(from.error, LOG_LEVEL_VERBOSE);
|
|
359
452
|
return from;
|
|
360
453
|
}
|
|
454
|
+
if (signal?.aborted) return P2P_REPLICATION_CANCELLED;
|
|
361
455
|
const logLevel = showNotice ? LOG_LEVEL_NOTICE : LOG_LEVEL_INFO;
|
|
362
456
|
Logger(`P2P Replication has been requested to ${remotePeer}`, logLevel, "p2p-replicator");
|
|
363
|
-
const res = await this.requestSynchroniseToPeer(remotePeer);
|
|
457
|
+
const res = await this.requestSynchroniseToPeer(remotePeer, signal);
|
|
364
458
|
if (res.ok) {
|
|
365
459
|
Logger("P2P Replication has been done", logLevel, "p2p-replicator");
|
|
366
460
|
}
|
|
@@ -368,29 +462,9 @@ class TrysteroReplicator {
|
|
|
368
462
|
Logger("Error while syncing from the remote", logLevel, "p2p-replicator");
|
|
369
463
|
Logger(res.error, LOG_LEVEL_VERBOSE);
|
|
370
464
|
}
|
|
465
|
+
return res;
|
|
371
466
|
}
|
|
372
467
|
_replicateToPeers = /* @__PURE__ */ new Set();
|
|
373
|
-
// async replicateTo() {
|
|
374
|
-
// await this.makeSureOpened();
|
|
375
|
-
// const remotePeer = await this.selectPeer();
|
|
376
|
-
// if (!remotePeer) {
|
|
377
|
-
// Logger("No peer selected", LOG_LEVEL_VERBOSE);
|
|
378
|
-
// return;
|
|
379
|
-
// }
|
|
380
|
-
// Logger(`P2P Replicating to ${remotePeer}`, LOG_LEVEL_INFO);
|
|
381
|
-
// try {
|
|
382
|
-
// if (this._replicateToPeers.has(remotePeer)) {
|
|
383
|
-
// Logger(`Replication to ${remotePeer} is already in progress`, LOG_LEVEL_VERBOSE);
|
|
384
|
-
// return;
|
|
385
|
-
// }
|
|
386
|
-
// this._replicateToPeers.add(remotePeer);
|
|
387
|
-
// this.dispatchStatus();
|
|
388
|
-
// return await this.requestSynchroniseToPeer(remotePeer);
|
|
389
|
-
// } finally {
|
|
390
|
-
// this._replicateToPeers.delete(remotePeer);
|
|
391
|
-
// this.dispatchStatus();
|
|
392
|
-
// }
|
|
393
|
-
// }
|
|
394
468
|
_replicateFromPeers = /* @__PURE__ */ new Set();
|
|
395
469
|
dispatchReplicationProgress(peerId, info) {
|
|
396
470
|
this.onReplicationProgress(peerId, info);
|
|
@@ -446,24 +520,47 @@ class TrysteroReplicator {
|
|
|
446
520
|
Logger(ex, LOG_LEVEL_VERBOSE);
|
|
447
521
|
});
|
|
448
522
|
}
|
|
449
|
-
async replicateFrom(remotePeer, showNotice = false, fromStart = false, skipOrdinaryReplicationPolicy = false) {
|
|
523
|
+
async replicateFrom(remotePeer, showNotice = false, fromStart = false, skipOrdinaryReplicationPolicy = false, callerSignal) {
|
|
524
|
+
return await this.runSessionFiniteOperation(
|
|
525
|
+
async (signal) => await this.replicateFromWithinSession(
|
|
526
|
+
remotePeer,
|
|
527
|
+
showNotice,
|
|
528
|
+
fromStart,
|
|
529
|
+
skipOrdinaryReplicationPolicy,
|
|
530
|
+
signal
|
|
531
|
+
),
|
|
532
|
+
callerSignal
|
|
533
|
+
);
|
|
534
|
+
}
|
|
535
|
+
async replicateFromWithinSession(remotePeer, showNotice, fromStart, skipOrdinaryReplicationPolicy, signal) {
|
|
450
536
|
if (!skipOrdinaryReplicationPolicy && !await this.canStartOrdinaryReplication(showNotice)) {
|
|
451
|
-
return
|
|
537
|
+
return p2pReplicationFailed(new Error("Replication is not ready"));
|
|
452
538
|
}
|
|
539
|
+
if (signal?.aborted) return P2P_REPLICATION_CANCELLED;
|
|
453
540
|
const logLevel = showNotice ? LOG_LEVEL_NOTICE : LOG_LEVEL_INFO;
|
|
454
541
|
Logger(`P2P Requesting Authentication to ${remotePeer}`, logLevel, "p2p-replicator");
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
542
|
+
const authenticated = signal ? await this.requestAuthenticate(remotePeer, signal) : await this.requestAuthenticate(remotePeer);
|
|
543
|
+
if (authenticated !== true) {
|
|
544
|
+
if (signal?.aborted) return P2P_REPLICATION_CANCELLED;
|
|
545
|
+
Logger("Peer rejected the connection", logLevel, "p2p-replicator");
|
|
546
|
+
return p2pReplicationFailed(new Error("Peer rejected the connection"));
|
|
547
|
+
}
|
|
548
|
+
let tweaksMatched;
|
|
549
|
+
try {
|
|
550
|
+
tweaksMatched = signal ? await this.checkTweakValues(remotePeer, signal, logLevel) : await this.checkTweakValues(remotePeer, void 0, logLevel);
|
|
551
|
+
} catch (error) {
|
|
552
|
+
if (signal?.aborted) return P2P_REPLICATION_CANCELLED;
|
|
553
|
+
throw error;
|
|
458
554
|
}
|
|
459
|
-
if (
|
|
460
|
-
|
|
461
|
-
|
|
555
|
+
if (tweaksMatched !== true) {
|
|
556
|
+
if (signal?.aborted) return P2P_REPLICATION_CANCELLED;
|
|
557
|
+
Logger("Tweak values are not matched", logLevel, "p2p-replicator");
|
|
558
|
+
return p2pReplicationFailed(new Error("Tweak values are not matched"));
|
|
462
559
|
}
|
|
463
560
|
Logger(`P2P Replicating from ${remotePeer}`, logLevel, "p2p-replicator");
|
|
464
561
|
if (this._replicateFromPeers.has(remotePeer)) {
|
|
465
|
-
Logger(`Replication from ${remotePeer} is already in progress`,
|
|
466
|
-
return
|
|
562
|
+
Logger(`Replication from ${remotePeer} is already in progress`, logLevel, "p2p-replicator");
|
|
563
|
+
return p2pReplicationFailed(new Error("Replication from this peer is already in progress"));
|
|
467
564
|
}
|
|
468
565
|
this._replicateFromPeers.add(remotePeer);
|
|
469
566
|
this.dispatchStatus();
|
|
@@ -472,7 +569,7 @@ class TrysteroReplicator {
|
|
|
472
569
|
throw new Error("Server is not available");
|
|
473
570
|
}
|
|
474
571
|
const connection = this.server.getConnection(remotePeer);
|
|
475
|
-
const remoteDB = connection.
|
|
572
|
+
const remoteDB = connection.getRemoteDB(signal);
|
|
476
573
|
Logger(`P2P replicateFrom preparing remote DB info for ${remotePeer}`, LOG_LEVEL_VERBOSE, "p2p-replicator");
|
|
477
574
|
const remoteDBInfo = await remoteDB.info();
|
|
478
575
|
Logger(
|
|
@@ -487,7 +584,7 @@ class TrysteroReplicator {
|
|
|
487
584
|
"p2p-replicator"
|
|
488
585
|
);
|
|
489
586
|
Logger(`P2P replicateFrom entering replicateShim for ${remotePeer}`, LOG_LEVEL_VERBOSE, "p2p-replicator");
|
|
490
|
-
await replicateShim(
|
|
587
|
+
const outcome = await replicateShim(
|
|
491
588
|
this.db,
|
|
492
589
|
remoteDB,
|
|
493
590
|
async (docs, info) => {
|
|
@@ -504,22 +601,28 @@ ${info.lastSeq} / ${info.maxSeqInBatch})`,
|
|
|
504
601
|
},
|
|
505
602
|
{
|
|
506
603
|
live: false,
|
|
507
|
-
rewind: fromStart
|
|
604
|
+
rewind: fromStart,
|
|
605
|
+
signal
|
|
508
606
|
/*, batch_size: batchSize */
|
|
509
607
|
}
|
|
510
608
|
);
|
|
511
609
|
Logger(`P2P replicateFrom replicateShim returned for ${remotePeer}`, LOG_LEVEL_VERBOSE, "p2p-replicator");
|
|
512
610
|
void this.acknowledgeProgress(remotePeer, void 0);
|
|
611
|
+
if (outcome.status === "cancelled") {
|
|
612
|
+
Logger(`P2P Replication from ${remotePeer} has been cancelled`, logLevel, "p2p-replicator");
|
|
613
|
+
return P2P_REPLICATION_CANCELLED;
|
|
614
|
+
}
|
|
513
615
|
Logger(`P2P Replication from ${remotePeer} has been completed`, logLevel, "p2p-replicator");
|
|
514
616
|
} catch (e) {
|
|
617
|
+
if (signal?.aborted) return P2P_REPLICATION_CANCELLED;
|
|
515
618
|
Logger("Error while P2P replicating", logLevel, "p2p-replicator");
|
|
516
619
|
Logger(e, LOG_LEVEL_VERBOSE);
|
|
517
|
-
return
|
|
620
|
+
return p2pReplicationFailed(e);
|
|
518
621
|
} finally {
|
|
519
622
|
this._replicateFromPeers.delete(remotePeer);
|
|
520
623
|
this.dispatchStatus();
|
|
521
624
|
}
|
|
522
|
-
return
|
|
625
|
+
return P2P_REPLICATION_COMPLETED;
|
|
523
626
|
}
|
|
524
627
|
notifyProgress(excludePeerId) {
|
|
525
628
|
if (!this._isBroadcasting) return;
|
|
@@ -604,14 +707,15 @@ ${info.lastSeq} / ${info.maxSeqInBatch})`,
|
|
|
604
707
|
return false;
|
|
605
708
|
}
|
|
606
709
|
}
|
|
607
|
-
|
|
710
|
+
/** Compare peer settings without exceeding the presentation authority of the initiating call. */
|
|
711
|
+
async checkTweakValues(peerId, signal, logLevel = LOG_LEVEL_NOTICE) {
|
|
608
712
|
if (!this.server) {
|
|
609
|
-
Logger("Server is not available",
|
|
713
|
+
Logger("Server is not available", logLevel);
|
|
610
714
|
return false;
|
|
611
715
|
}
|
|
612
716
|
const peerPlatform = this.server.knownAdvertisements.find((e) => e.peerId == peerId)?.platform;
|
|
613
717
|
if (peerPlatform == null) {
|
|
614
|
-
Logger("Peer is not found",
|
|
718
|
+
Logger("Peer is not found", logLevel);
|
|
615
719
|
return false;
|
|
616
720
|
}
|
|
617
721
|
if (this.platform === "pseudo-replicator") {
|
|
@@ -621,7 +725,8 @@ ${info.lastSeq} / ${info.maxSeqInBatch})`,
|
|
|
621
725
|
return true;
|
|
622
726
|
}
|
|
623
727
|
const connection = this.server.getConnection(peerId);
|
|
624
|
-
const tweakValues = await connection.invokeRemoteObjectFunction("getTweakSettings", [this.server.serverPeerId], 5e3);
|
|
728
|
+
const tweakValues = await connection.invokeRemoteObjectFunction("getTweakSettings", [this.server.serverPeerId], 5e3, signal);
|
|
729
|
+
if (signal?.aborted) return false;
|
|
625
730
|
const thisTweakValues = await this.getTweakSettings("");
|
|
626
731
|
if (!isObjectDifferent(thisTweakValues, tweakValues)) {
|
|
627
732
|
return true;
|
|
@@ -629,43 +734,98 @@ ${info.lastSeq} / ${info.maxSeqInBatch})`,
|
|
|
629
734
|
if (thisTweakValues.passphrase !== tweakValues.passphrase) {
|
|
630
735
|
Logger(
|
|
631
736
|
"Replication cancelled: Passphrase is not matched\nCannot replicate to a remote database until the problem is resolved.",
|
|
632
|
-
|
|
737
|
+
logLevel
|
|
633
738
|
);
|
|
634
739
|
return false;
|
|
635
740
|
}
|
|
636
741
|
Logger(
|
|
637
742
|
"Some mismatched configuration have been detected... Please check settings for efficient replication.",
|
|
638
|
-
|
|
743
|
+
logLevel
|
|
639
744
|
);
|
|
640
745
|
return true;
|
|
641
746
|
}
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
747
|
+
configuredPeerNames() {
|
|
748
|
+
return [...new Set(this.currentSettings.P2P_SyncOnReplication.split(",").map((name) => name.trim()))].filter(
|
|
749
|
+
Boolean
|
|
750
|
+
);
|
|
751
|
+
}
|
|
752
|
+
findAdvertisement(peerName) {
|
|
753
|
+
return this.knownAdvertisements.find((advertisement) => advertisement.name === peerName);
|
|
754
|
+
}
|
|
755
|
+
async waitForConfiguredAdvertisements(peerNames, timeoutMs, signal) {
|
|
756
|
+
const allPresent = () => peerNames.every((name) => this.findAdvertisement(name) !== void 0);
|
|
757
|
+
if (allPresent() || timeoutMs <= 0 || signal?.aborted) return;
|
|
758
|
+
await new Promise((resolve) => {
|
|
759
|
+
let timeout;
|
|
760
|
+
let detachAdvertisement = () => void 0;
|
|
761
|
+
const finish = () => {
|
|
762
|
+
detachAdvertisement();
|
|
763
|
+
signal?.removeEventListener("abort", finish);
|
|
764
|
+
if (timeout !== void 0) compatGlobal.clearTimeout(timeout);
|
|
765
|
+
resolve();
|
|
766
|
+
};
|
|
767
|
+
detachAdvertisement = this._env.events.onEvent(EVENT_ADVERTISEMENT_RECEIVED, () => {
|
|
768
|
+
if (allPresent()) finish();
|
|
769
|
+
});
|
|
770
|
+
signal?.addEventListener("abort", finish, { once: true });
|
|
771
|
+
timeout = compatGlobal.setTimeout(finish, timeoutMs);
|
|
772
|
+
if (allPresent() || signal?.aborted) finish();
|
|
773
|
+
});
|
|
774
|
+
}
|
|
775
|
+
configuredTargetResult(name, peerId, result) {
|
|
776
|
+
if (result.status === "completed") return { name, peerId, status: "completed" };
|
|
777
|
+
if (result.status === "cancelled") return { name, peerId, status: "cancelled" };
|
|
778
|
+
return { name, peerId, status: "failed", error: result.error };
|
|
779
|
+
}
|
|
780
|
+
/** Synchronise configured peer targets without opening peer-selection UI. */
|
|
781
|
+
async replicateFromCommand(showResult = false, discoveryTimeoutMs = 5e3, callerSignal) {
|
|
782
|
+
const logLevel = showResult ? LOG_LEVEL_NOTICE : LOG_LEVEL_INFO;
|
|
783
|
+
if (!this.currentSettings.P2P_Enabled) {
|
|
784
|
+
Logger(this.translate("P2P.NotEnabled"), logLevel);
|
|
785
|
+
return { status: "blocked", reason: "no-targets", targets: [] };
|
|
786
|
+
}
|
|
787
|
+
const peerNames = this.configuredPeerNames();
|
|
788
|
+
if (peerNames.length === 0) {
|
|
789
|
+
Logger(this.translate("P2P.NoAutoSyncPeers"), logLevel);
|
|
790
|
+
return { status: "blocked", reason: "no-targets", targets: [] };
|
|
791
|
+
}
|
|
792
|
+
await this.waitForConfiguredAdvertisements(peerNames, discoveryTimeoutMs, callerSignal);
|
|
793
|
+
const targets = [];
|
|
794
|
+
for (const peerName of peerNames) {
|
|
795
|
+
if (callerSignal?.aborted) {
|
|
796
|
+
targets.push({ name: peerName, status: "cancelled" });
|
|
797
|
+
continue;
|
|
648
798
|
}
|
|
649
|
-
const
|
|
650
|
-
if (
|
|
651
|
-
Logger(this.translate(
|
|
652
|
-
|
|
799
|
+
const advertisement = this.findAdvertisement(peerName);
|
|
800
|
+
if (!advertisement) {
|
|
801
|
+
Logger(this.translate(`P2P.SeemsOffline`, { name: peerName }), logLevel);
|
|
802
|
+
targets.push({ name: peerName, status: "missing" });
|
|
803
|
+
continue;
|
|
653
804
|
}
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
805
|
+
const acceptance = await this.server?.evaluatePeerAcceptance(advertisement.peerId) ?? "unknown";
|
|
806
|
+
if (acceptance !== "accepted") {
|
|
807
|
+
targets.push({
|
|
808
|
+
name: peerName,
|
|
809
|
+
peerId: advertisement.peerId,
|
|
810
|
+
status: acceptance === "rejected" ? "rejected" : acceptance === "undecided" ? "undecided" : "missing"
|
|
811
|
+
});
|
|
812
|
+
continue;
|
|
662
813
|
}
|
|
814
|
+
Logger(this.translate(`P2P.SyncStartedWith`, { name: peerName }), logLevel);
|
|
815
|
+
const result = await this.automationCoordinator.runBaseline(
|
|
816
|
+
peerName,
|
|
817
|
+
() => this.sync(advertisement.peerId, showResult, callerSignal)
|
|
818
|
+
);
|
|
819
|
+
targets.push(this.configuredTargetResult(peerName, advertisement.peerId, result));
|
|
820
|
+
}
|
|
821
|
+
if (targets.every((target) => target.status === "completed")) {
|
|
663
822
|
Logger(this.translate("P2P.SyncCompleted"), logLevel);
|
|
664
|
-
return
|
|
665
|
-
}
|
|
666
|
-
if (
|
|
667
|
-
|
|
823
|
+
return { status: "completed", targets };
|
|
824
|
+
}
|
|
825
|
+
if (targets.every((target) => target.status === "cancelled")) {
|
|
826
|
+
return { status: "cancelled", targets };
|
|
668
827
|
}
|
|
828
|
+
return { status: "partial", targets };
|
|
669
829
|
}
|
|
670
830
|
disconnectFromServer() {
|
|
671
831
|
const connections = getRelaySockets();
|
|
@@ -7,13 +7,16 @@ export declare class TrysteroReplicatorP2PClient {
|
|
|
7
7
|
_connectedPeerId: string;
|
|
8
8
|
_remoteDB: PouchDBShim<SomeDocument<object>>;
|
|
9
9
|
get remoteDB(): PouchDBShim<SomeDocument<object>>;
|
|
10
|
+
/** Bind the reverse database RPC calls to one finite-operation signal. */
|
|
11
|
+
getRemoteDB(signal?: AbortSignal): PouchDBShim<SomeDocument<object>>;
|
|
10
12
|
constructor(server: TrysteroReplicatorP2PServer, connectedPeerId: string);
|
|
11
|
-
_bindRemoteDB(): PouchDBShim<SomeDocument<object>>;
|
|
13
|
+
_bindRemoteDB(signal?: AbortSignal): PouchDBShim<SomeDocument<object>>;
|
|
14
|
+
private getCallOptions;
|
|
12
15
|
_sendRPC(type: string, args: JsonLike[], timeout?: number): Promise<JsonLike>;
|
|
13
16
|
__onResponse(_data: Response): void;
|
|
14
|
-
bindRemoteFunction<T extends unknown[], U>(type: string, timeout?: number): (...args: T) => Promise<U>;
|
|
15
|
-
invokeRemoteFunction<T extends unknown[], U>(type: string, args: T, timeout?: number): Promise<U>;
|
|
17
|
+
bindRemoteFunction<T extends unknown[], U>(type: string, timeout?: number, signal?: AbortSignal): (...args: T) => Promise<U>;
|
|
18
|
+
invokeRemoteFunction<T extends unknown[], U>(type: string, args: T, timeout?: number, signal?: AbortSignal): Promise<U>;
|
|
16
19
|
bindRemoteObjectFunctions<T extends BindableObject<any>, U extends keyof T>(key: U, timeout?: number): (...args: Parameters<T[U]>) => Promise<Awaited<ReturnType<T[U]>>>;
|
|
17
|
-
invokeRemoteObjectFunction<T extends BindableObject<any>, U extends NonPrivateMethodKeys<T>>(key: U, args: Parameters<T[U]>, timeout?: number): Promise<Awaited<ReturnType<T[U]>>>;
|
|
20
|
+
invokeRemoteObjectFunction<T extends BindableObject<any>, U extends NonPrivateMethodKeys<T>>(key: U, args: Parameters<T[U]>, timeout?: number, signal?: AbortSignal): Promise<Awaited<ReturnType<T[U]>>>;
|
|
18
21
|
close(): void;
|
|
19
22
|
}
|