@vrtmrz/livesync-commonlib 0.1.0-rc.0 → 0.1.0-rc.10
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 +101 -17
- package/dist/API/DirectFileManipulatorV2.d.ts +3 -0
- package/dist/common/configForDoc.d.ts +1 -0
- package/dist/common/configForDoc.js +24 -7
- package/dist/common/models/setting.const.defaults.d.ts +19 -0
- package/dist/common/models/setting.const.defaults.js +21 -2
- package/dist/common/models/setting.const.preferred.js +1 -2
- package/dist/common/models/setting.lifecycle.d.ts +33 -0
- package/dist/common/models/setting.lifecycle.js +121 -0
- package/dist/common/models/setting.type.d.ts +10 -4
- package/dist/common/models/shared.definition.configNames.js +3 -3
- package/dist/common/models/tweak.definition.d.ts +1 -1
- package/dist/common/models/tweak.definition.js +1 -6
- package/dist/common/settingConstants.d.ts +3 -2
- package/dist/common/settingConstants.js +6 -6
- package/dist/common/types.d.ts +4 -2
- package/dist/common/types.js +16 -1
- package/dist/context.d.ts +3 -1
- package/dist/context.js +4 -0
- package/dist/interfaces/Confirm.d.ts +10 -2
- package/dist/interfaces/DatabaseFileAccess.d.ts +8 -0
- package/dist/interfaces/DatabaseRebuilder.d.ts +12 -2
- package/dist/interfaces/FileReflectionProvenance.d.ts +32 -0
- package/dist/interfaces/FileReflectionProvenance.js +25 -0
- package/dist/managers/ConflictManager.js +16 -7
- package/dist/managers/EntryManager/EntryManager.d.ts +1 -0
- package/dist/managers/EntryManager/EntryManager.js +5 -1
- package/dist/managers/EntryManager/EntryManagerImpls.d.ts +1 -0
- package/dist/managers/EntryManager/EntryManagerImpls.js +29 -1
- package/dist/platform/node/index.d.ts +1 -0
- package/dist/platform/node/index.js +4 -0
- package/dist/platform/node/standardIo.d.ts +24 -0
- package/dist/platform/node/standardIo.js +39 -0
- package/dist/platform/node/storage.d.ts +10 -1
- package/dist/platform/node/storage.js +86 -25
- package/dist/platform/standardIo.d.ts +19 -0
- package/dist/platform/standardIo.js +0 -0
- package/dist/pouchdb/LiveSyncLocalDB.d.ts +1 -0
- package/dist/pouchdb/LiveSyncLocalDB.js +3 -0
- package/dist/remoteConfigurations.d.ts +9 -0
- package/dist/remoteConfigurations.js +14 -0
- package/dist/replication/LiveSyncAbstractReplicator.d.ts +3 -3
- package/dist/replication/couchdb/LiveSyncReplicator.d.ts +0 -1
- package/dist/replication/couchdb/LiveSyncReplicator.js +0 -3
- package/dist/replication/journal/LiveSyncJournalReplicator.d.ts +0 -1
- package/dist/replication/journal/LiveSyncJournalReplicator.js +0 -3
- package/dist/replication/trystero/LiveSyncTrysteroReplicator.d.ts +5 -3
- package/dist/replication/trystero/LiveSyncTrysteroReplicator.js +42 -30
- package/dist/replication/trystero/P2PReplicatorCore.d.ts +2 -6
- package/dist/replication/trystero/P2PReplicatorCore.js +16 -86
- package/dist/replication/trystero/TrysteroReplicator.d.ts +2 -1
- package/dist/replication/trystero/TrysteroReplicator.js +10 -4
- package/dist/replication/trystero/TrysteroReplicatorP2PServer.d.ts +4 -5
- package/dist/replication/trystero/TrysteroReplicatorP2PServer.js +22 -30
- package/dist/replication/trystero/UseP2PReplicatorResult.d.ts +10 -2
- package/dist/replication/trystero/addP2PEventHandlers.d.ts +4 -2
- package/dist/replication/trystero/addP2PEventHandlers.js +9 -10
- package/dist/replication/trystero/types.d.ts +2 -0
- package/dist/replication/trystero/useP2PReplicatorCommands.d.ts +1 -1
- package/dist/replication/trystero/useP2PReplicatorCommands.js +3 -1
- package/dist/replication/trystero/useP2PReplicatorFeature.js +25 -15
- package/dist/rpc/RpcRoom.d.ts +1 -1
- package/dist/rpc/RpcRoom.js +22 -16
- package/dist/rpc/transports/TrysteroTransport.d.ts +2 -0
- package/dist/rpc/transports/TrysteroTransport.js +24 -17
- package/dist/rpc/transports/trysteroRoomEvents.d.ts +14 -0
- package/dist/rpc/transports/trysteroRoomEvents.js +34 -0
- package/dist/serviceFeatures/checkRemoteSize.d.ts +5 -3
- package/dist/serviceFeatures/checkRemoteSize.js +51 -11
- package/dist/serviceFeatures/remoteConfig.d.ts +37 -1
- package/dist/serviceFeatures/remoteConfig.js +90 -0
- package/dist/serviceModules/Rebuilder.d.ts +3 -6
- package/dist/serviceModules/Rebuilder.js +31 -22
- package/dist/serviceModules/ServiceDatabaseFileAccessBase.d.ts +5 -0
- package/dist/serviceModules/ServiceDatabaseFileAccessBase.js +69 -13
- package/dist/serviceModules/ServiceFileHandlerBase.d.ts +20 -0
- package/dist/serviceModules/ServiceFileHandlerBase.js +224 -40
- package/dist/services/BrowserServices.d.ts +2 -0
- package/dist/services/BrowserServices.js +4 -1
- package/dist/services/HeadlessServices.d.ts +2 -0
- package/dist/services/HeadlessServices.js +2 -1
- package/dist/services/base/CommonlibMessages.d.ts +318 -0
- package/dist/services/base/CommonlibMessages.js +313 -0
- package/dist/services/base/IService.d.ts +13 -6
- package/dist/services/base/KeyValueDBService.d.ts +1 -1
- package/dist/services/base/KeyValueDBService.js +3 -1
- package/dist/services/base/MessageTranslator.d.ts +5 -2
- package/dist/services/base/MessageTranslator.js +21 -0
- package/dist/services/base/ReplicationService.d.ts +4 -2
- package/dist/services/base/ReplicationService.js +11 -16
- package/dist/services/base/ServiceBase.d.ts +1 -1
- package/dist/services/base/ServiceBase.js +2 -4
- package/dist/services/base/SettingService.d.ts +17 -9
- package/dist/services/base/SettingService.js +38 -19
- package/dist/services/implements/headless/HeadlessAPIService.d.ts +3 -3
- package/dist/services/implements/headless/HeadlessAPIService.js +2 -2
- package/dist/services/implements/injectable/InjectableTweakValueService.d.ts +3 -3
- package/dist/settings.d.ts +10 -0
- package/dist/settings.js +32 -0
- package/dist/worker/bgWorker.js +1 -1
- package/docs/conflict-resolution.md +153 -0
- package/docs/development.md +108 -8
- package/docs/p2p-transport-lifecycle.md +62 -0
- package/docs/platform-standard-io.md +44 -0
- package/docs/platform-storage.md +4 -0
- package/docs/proven-in-use.md +65 -0
- package/docs/releasing.md +26 -6
- package/docs/remote-configurations.md +63 -0
- package/docs/settings-lifecycle.md +85 -0
- package/package.json +17 -27
- package/dist/common/i18n.d.ts +0 -18
- package/dist/common/i18n.js +0 -7193
- package/dist/common/messages/combinedMessages.dev.d.ts +0 -7
- package/dist/common/messages/combinedMessages.dev.js +0 -44
- package/dist/common/messages/combinedMessages.prod.d.ts +0 -9264
- package/dist/common/messages/combinedMessages.prod.js +0 -9271
- package/dist/common/messages/de.d.ts +0 -297
- package/dist/common/messages/de.js +0 -7
- package/dist/common/messages/def.d.ts +0 -1119
- package/dist/common/messages/def.js +0 -7
- package/dist/common/messages/es.d.ts +0 -689
- package/dist/common/messages/es.js +0 -7
- package/dist/common/messages/fr.d.ts +0 -582
- package/dist/common/messages/fr.js +0 -7
- package/dist/common/messages/he.d.ts +0 -583
- package/dist/common/messages/he.js +0 -7
- package/dist/common/messages/ja.d.ts +0 -838
- package/dist/common/messages/ja.js +0 -7
- package/dist/common/messages/ko.d.ts +0 -800
- package/dist/common/messages/ko.js +0 -7
- package/dist/common/messages/ru.d.ts +0 -858
- package/dist/common/messages/ru.js +0 -7
- package/dist/common/messages/zh-tw.d.ts +0 -384
- package/dist/common/messages/zh-tw.js +0 -7
- package/dist/common/messages/zh.d.ts +0 -1093
- package/dist/common/messages/zh.js +0 -7
- package/dist/common/messagesJson/de.json +0 -295
- package/dist/common/messagesJson/en.json +0 -1117
- package/dist/common/messagesJson/es.json +0 -687
- package/dist/common/messagesJson/fr.json +0 -580
- package/dist/common/messagesJson/he.json +0 -581
- package/dist/common/messagesJson/ja.json +0 -836
- package/dist/common/messagesJson/ko.json +0 -798
- package/dist/common/messagesJson/ru.json +0 -856
- package/dist/common/messagesJson/zh-tw.json +0 -382
- package/dist/common/messagesJson/zh.json +0 -1091
- package/dist/common/rosetta.d.ts +0 -42
- package/dist/common/rosetta.js +0 -57
|
@@ -51,6 +51,9 @@ class TrysteroReplicator {
|
|
|
51
51
|
}
|
|
52
52
|
return await task();
|
|
53
53
|
}
|
|
54
|
+
async canStartOrdinaryReplication(showMessage = false) {
|
|
55
|
+
return this._env.canStartOrdinaryReplication ? await this._env.canStartOrdinaryReplication(showMessage) : true;
|
|
56
|
+
}
|
|
54
57
|
constructor(env, server) {
|
|
55
58
|
this._env = env;
|
|
56
59
|
if (server) {
|
|
@@ -265,6 +268,9 @@ class TrysteroReplicator {
|
|
|
265
268
|
// }
|
|
266
269
|
lastSeq = "";
|
|
267
270
|
async requestSynchroniseToPeer(peerId) {
|
|
271
|
+
if (!await this.canStartOrdinaryReplication(false)) {
|
|
272
|
+
return { error: new Error("Replication is not ready") };
|
|
273
|
+
}
|
|
268
274
|
await delay(25);
|
|
269
275
|
if (!this.server) throw new Error("Server is not available");
|
|
270
276
|
const conn = this.server.getConnection(peerId);
|
|
@@ -440,7 +446,10 @@ class TrysteroReplicator {
|
|
|
440
446
|
Logger(ex, LOG_LEVEL_VERBOSE);
|
|
441
447
|
});
|
|
442
448
|
}
|
|
443
|
-
async replicateFrom(remotePeer, showNotice = false, fromStart = false) {
|
|
449
|
+
async replicateFrom(remotePeer, showNotice = false, fromStart = false, skipOrdinaryReplicationPolicy = false) {
|
|
450
|
+
if (!skipOrdinaryReplicationPolicy && !await this.canStartOrdinaryReplication(showNotice)) {
|
|
451
|
+
return { error: new Error("Replication is not ready") };
|
|
452
|
+
}
|
|
444
453
|
const logLevel = showNotice ? LOG_LEVEL_NOTICE : LOG_LEVEL_INFO;
|
|
445
454
|
Logger(`P2P Requesting Authentication to ${remotePeer}`, logLevel, "p2p-replicator");
|
|
446
455
|
if (await this.requestAuthenticate(remotePeer) !== true) {
|
|
@@ -664,9 +673,6 @@ ${info.lastSeq} / ${info.maxSeqInBatch})`,
|
|
|
664
673
|
pauseRelayReconnection();
|
|
665
674
|
sockets.forEach(([, s]) => {
|
|
666
675
|
s.close();
|
|
667
|
-
s.onclose = () => {
|
|
668
|
-
void this.server?.dispatchConnectionStatus();
|
|
669
|
-
};
|
|
670
676
|
});
|
|
671
677
|
void this.pauseServe();
|
|
672
678
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type Room } from "@trystero-p2p/nostr";
|
|
2
2
|
import { type P2PSyncSetting } from "../../common/types.js";
|
|
3
3
|
import { type ReplicatorHostEnv, type FullFilledDeviceInfo, type Request, type Response, type Payload, type Advertisement, type BindableObject, type BindableFunction } from "./types.js";
|
|
4
4
|
import { StoredMapLike } from "../../dataobject/StoredMap.js";
|
|
@@ -56,7 +56,7 @@ export declare class TrysteroReplicatorP2PServer {
|
|
|
56
56
|
_room?: Room;
|
|
57
57
|
_serverPeerId: string;
|
|
58
58
|
_activeRoomId: string;
|
|
59
|
-
___send?:
|
|
59
|
+
___send?: (data: Payload, peerId?: string) => Promise<void>;
|
|
60
60
|
assignedFunctions: Map<string, BindableFunction>;
|
|
61
61
|
clients: Map<string, TrysteroReplicatorP2PClient>;
|
|
62
62
|
_bindingObjects: BindableObject[];
|
|
@@ -81,7 +81,7 @@ export declare class TrysteroReplicatorP2PServer {
|
|
|
81
81
|
get settings(): P2PSyncSetting;
|
|
82
82
|
get isEnabled(): boolean;
|
|
83
83
|
get deviceInfo(): FullFilledDeviceInfo;
|
|
84
|
-
_sendAdvertisement?:
|
|
84
|
+
_sendAdvertisement?: (data: Advertisement, peerId?: string) => Promise<void>;
|
|
85
85
|
sendAdvertisement(peerId?: string): void;
|
|
86
86
|
_knownAdvertisements: Map<string, Advertisement>;
|
|
87
87
|
get knownAdvertisements(): Advertisement[];
|
|
@@ -93,11 +93,10 @@ export declare class TrysteroReplicatorP2PServer {
|
|
|
93
93
|
_acceptablePeers: Computed<[settings: P2PSyncSetting], RegExp[]>;
|
|
94
94
|
_shouldDenyPeers: Computed<[settings: P2PSyncSetting], RegExp[]>;
|
|
95
95
|
isAcceptablePeer(peerId: string): Promise<boolean>;
|
|
96
|
-
__send(data: Payload, peerId: string): Promise<void
|
|
96
|
+
__send(data: Payload, peerId: string): Promise<void>;
|
|
97
97
|
processArrivedRPC(data: Payload, peerId: string): Promise<void>;
|
|
98
98
|
private _onPeerJoin;
|
|
99
99
|
private _onPeerLeave;
|
|
100
|
-
activePeer: Map<string, RTCPeerConnection>;
|
|
101
100
|
onAfterJoinRoom(): void;
|
|
102
101
|
startService(bindings?: BindableObject[]): Promise<void>;
|
|
103
102
|
start(bindings?: BindableObject[]): Promise<void>;
|
|
@@ -14,6 +14,7 @@ import { shareRunningResult } from "octagonal-wheels/concurrency/lock_v2";
|
|
|
14
14
|
import { Computed } from "octagonal-wheels/dataobject/Computed";
|
|
15
15
|
import { RpcRoom } from "../../rpc/index.js";
|
|
16
16
|
import { TRYSTERO_RPC_DEFAULTS } from "../../rpc/transports/TrysteroTransport.js";
|
|
17
|
+
import { subscribeTrysteroPeerEvents } from "../../rpc/transports/trysteroRoomEvents.js";
|
|
17
18
|
import { toRpcMethodName } from "./rpcCompat.js";
|
|
18
19
|
import { generateJoinRoomOptions } from "../../rpc/transports/trysteroUtils.js";
|
|
19
20
|
import { subscribeConnectionStatus, subscribeFailureDiagnosis } from "../../rpc/transports/DiagRTCPeerConnections.js";
|
|
@@ -333,52 +334,47 @@ You can chose as follows:
|
|
|
333
334
|
Logger(`Received peer join event from ${peerId}, but no active room. Ignoring.`, LOG_LEVEL_VERBOSE);
|
|
334
335
|
return;
|
|
335
336
|
}
|
|
336
|
-
const peers = this._room.getPeers();
|
|
337
|
-
const peer = peers[peerId];
|
|
338
337
|
Logger(`Peer joined: ${peerId}`, LOG_LEVEL_VERBOSE);
|
|
339
|
-
this.activePeer.set(peerId, peer);
|
|
340
338
|
this.sendAdvertisement(peerId);
|
|
341
339
|
}
|
|
342
340
|
_onPeerLeave(peerId) {
|
|
343
341
|
Logger(`Peer left: ${peerId}`, LOG_LEVEL_VERBOSE);
|
|
344
342
|
this._knownAdvertisements.delete(peerId);
|
|
345
|
-
const peerConn = this.activePeer.get(peerId);
|
|
346
|
-
if (peerConn) {
|
|
347
|
-
peerConn.close();
|
|
348
|
-
this.activePeer.delete(peerId);
|
|
349
|
-
}
|
|
350
343
|
void this._env.events.emitEvent(EVENT_DEVICE_LEAVED, peerId);
|
|
351
344
|
void this.dispatchConnectionStatus();
|
|
352
345
|
}
|
|
353
|
-
activePeer = /* @__PURE__ */ new Map();
|
|
354
346
|
onAfterJoinRoom() {
|
|
355
347
|
Logger(`Initializing...`, LOG_LEVEL_VERBOSE);
|
|
356
348
|
const room = this.room;
|
|
357
349
|
if (!room) throw new Error("This server has been already disconnected");
|
|
358
|
-
const
|
|
350
|
+
const rpcAction = room.makeAction("rpc2");
|
|
359
351
|
const transport = {
|
|
360
352
|
send: (message, peerId) => {
|
|
361
|
-
return
|
|
353
|
+
return rpcAction.send(message, { target: peerId });
|
|
362
354
|
},
|
|
363
355
|
onMessage: (handler) => {
|
|
364
|
-
|
|
356
|
+
rpcAction.onMessage = (data, { peerId }) => {
|
|
365
357
|
handler(data, peerId);
|
|
366
|
-
}
|
|
367
|
-
return () =>
|
|
358
|
+
};
|
|
359
|
+
return () => {
|
|
360
|
+
rpcAction.onMessage = null;
|
|
361
|
+
};
|
|
368
362
|
},
|
|
369
363
|
onPeerJoin: (handler) => {
|
|
370
|
-
room
|
|
371
|
-
|
|
372
|
-
|
|
364
|
+
return subscribeTrysteroPeerEvents(room, {
|
|
365
|
+
onJoin: (peerId) => {
|
|
366
|
+
this._onPeerJoin(peerId);
|
|
367
|
+
handler(peerId);
|
|
368
|
+
}
|
|
373
369
|
});
|
|
374
|
-
return () => void 0;
|
|
375
370
|
},
|
|
376
371
|
onPeerLeave: (handler) => {
|
|
377
|
-
room
|
|
378
|
-
|
|
379
|
-
|
|
372
|
+
return subscribeTrysteroPeerEvents(room, {
|
|
373
|
+
onLeave: (peerId) => {
|
|
374
|
+
this._onPeerLeave(peerId);
|
|
375
|
+
handler(peerId);
|
|
376
|
+
}
|
|
380
377
|
});
|
|
381
|
-
return () => void 0;
|
|
382
378
|
}
|
|
383
379
|
};
|
|
384
380
|
this._rpcRoom?.close();
|
|
@@ -393,11 +389,11 @@ You can chose as follows:
|
|
|
393
389
|
Logger(`RPC Protocol warning${peerId ? ` from ${peerId}` : ""}: ${message}`, LOG_LEVEL_VERBOSE);
|
|
394
390
|
}
|
|
395
391
|
});
|
|
396
|
-
const
|
|
397
|
-
this._sendAdvertisement =
|
|
398
|
-
|
|
392
|
+
const advertisementAction = room.makeAction("ad");
|
|
393
|
+
this._sendAdvertisement = (data, peerId) => advertisementAction.send(data, peerId ? { target: peerId } : void 0);
|
|
394
|
+
advertisementAction.onMessage = (data, { peerId }) => {
|
|
399
395
|
void this.onAdvertisement(data, peerId);
|
|
400
|
-
}
|
|
396
|
+
};
|
|
401
397
|
this._env.events.emitEvent(EVENT_P2P_CONNECTED);
|
|
402
398
|
void this.dispatchConnectionStatus();
|
|
403
399
|
}
|
|
@@ -494,14 +490,10 @@ You can chose as follows:
|
|
|
494
490
|
}
|
|
495
491
|
async close() {
|
|
496
492
|
this.assignedFunctions.clear();
|
|
497
|
-
const peers = this.room?.getPeers() ?? {};
|
|
498
493
|
this.clients.forEach((client) => client.close());
|
|
499
494
|
this.clients.clear();
|
|
500
495
|
this._rpcRoom?.close();
|
|
501
496
|
this._rpcRoom = void 0;
|
|
502
|
-
for (const [, peer] of Object.entries(peers)) {
|
|
503
|
-
peer.close();
|
|
504
|
-
}
|
|
505
497
|
await this.ensureLeaved();
|
|
506
498
|
this._activeRoomId = "";
|
|
507
499
|
this._knownAdvertisements.clear();
|
|
@@ -2,10 +2,18 @@ import type { ReactiveSource } from "octagonal-wheels/dataobject/reactive_v2";
|
|
|
2
2
|
import type { LiveSyncTrysteroReplicator } from "./LiveSyncTrysteroReplicator.js";
|
|
3
3
|
import type { P2PLogCollector } from "./P2PLogCollector.js";
|
|
4
4
|
export type UseP2PReplicatorResult = {
|
|
5
|
-
|
|
5
|
+
/**
|
|
6
|
+
* The current replicator selected by the host lifecycle.
|
|
7
|
+
*
|
|
8
|
+
* Read this property at the point of use. Retaining a destructured value
|
|
9
|
+
* across settings or database lifecycle events can target a closed,
|
|
10
|
+
* replaced instance.
|
|
11
|
+
*/
|
|
12
|
+
readonly replicator: LiveSyncTrysteroReplicator;
|
|
6
13
|
};
|
|
7
14
|
export type P2PPaneParams = {
|
|
8
|
-
replicator
|
|
15
|
+
/** Current replicator; preserve the result object so its getter remains live. */
|
|
16
|
+
readonly replicator: LiveSyncTrysteroReplicator;
|
|
9
17
|
p2pLogCollector: P2PLogCollector;
|
|
10
18
|
storeP2PStatusLine: ReactiveSource<string>;
|
|
11
19
|
};
|
|
@@ -17,11 +17,13 @@ export interface P2PReplicatorLike {
|
|
|
17
17
|
isServing?: boolean;
|
|
18
18
|
};
|
|
19
19
|
}
|
|
20
|
+
/** Resolves the replicator which currently owns P2P state. */
|
|
21
|
+
export type P2PReplicatorProvider = () => P2PReplicatorLike;
|
|
20
22
|
/**
|
|
21
23
|
* Add event handlers for P2P replication related events.
|
|
22
|
-
* @param instance
|
|
24
|
+
* @param source A fixed compatibility instance or a provider for a replaceable replicator.
|
|
23
25
|
*/
|
|
24
|
-
export declare function addP2PEventHandlers(
|
|
26
|
+
export declare function addP2PEventHandlers(source: P2PReplicatorLike | P2PReplicatorProvider, events: LiveSyncEventHub): void;
|
|
25
27
|
/**
|
|
26
28
|
* open P2P replicator if not opened yet.
|
|
27
29
|
* @param instance
|
|
@@ -1,30 +1,29 @@
|
|
|
1
1
|
import { EVENT_DATABASE_REBUILT, EVENT_PLATFORM_UNLOADED, EVENT_SETTING_SAVED } from "../../events/coreEvents.js";
|
|
2
2
|
import { EVENT_ADVERTISEMENT_RECEIVED, EVENT_DEVICE_LEAVED, EVENT_REQUEST_STATUS } from "./TrysteroReplicatorP2PServer.js";
|
|
3
|
-
function addP2PEventHandlers(
|
|
3
|
+
function addP2PEventHandlers(source, events) {
|
|
4
|
+
const current = () => typeof source === "function" ? source() : source;
|
|
4
5
|
events.onEvent(EVENT_ADVERTISEMENT_RECEIVED, (peer) => {
|
|
5
|
-
void
|
|
6
|
+
void current().onNewPeer(peer);
|
|
6
7
|
});
|
|
7
8
|
events.onEvent(EVENT_DEVICE_LEAVED, (peerId) => {
|
|
8
|
-
|
|
9
|
+
current().onPeerLeaved(peerId);
|
|
9
10
|
});
|
|
10
11
|
events.onEvent(EVENT_REQUEST_STATUS, () => {
|
|
11
|
-
|
|
12
|
+
current().requestStatus();
|
|
12
13
|
});
|
|
13
14
|
events.onEvent(EVENT_DATABASE_REBUILT, async () => {
|
|
14
|
-
await
|
|
15
|
+
await current().open();
|
|
15
16
|
});
|
|
16
17
|
events.onEvent(EVENT_PLATFORM_UNLOADED, () => {
|
|
17
|
-
void
|
|
18
|
+
void current().close();
|
|
18
19
|
});
|
|
19
20
|
events.onEvent(EVENT_SETTING_SAVED, async (settings) => {
|
|
20
|
-
const
|
|
21
|
+
const instance = current();
|
|
21
22
|
if (settings.P2P_Enabled && settings.P2P_AutoStart) {
|
|
22
23
|
await instance.open();
|
|
23
24
|
return;
|
|
24
25
|
}
|
|
25
|
-
|
|
26
|
-
await instance.close();
|
|
27
|
-
}
|
|
26
|
+
await instance.close();
|
|
28
27
|
});
|
|
29
28
|
}
|
|
30
29
|
async function openP2PReplicator(instance) {
|
|
@@ -85,6 +85,8 @@ export interface ReplicatorHostEnv extends ReplicatorHost {
|
|
|
85
85
|
db: PouchDB.Database<EntryDoc>;
|
|
86
86
|
simpleStore: SimpleStore<unknown>;
|
|
87
87
|
runFiniteReplicationActivity?: AsyncActivityRunner["run"];
|
|
88
|
+
/** Lightweight, repeatable host policy checked before ordinary P2P replication starts. */
|
|
89
|
+
canStartOrdinaryReplication?(showMessage?: boolean): Promise<boolean>;
|
|
88
90
|
processReplicatedDocs(docs: Array<PouchDB.Core.ExistingDocument<EntryDoc>>): void | Promise<void>;
|
|
89
91
|
}
|
|
90
92
|
export type Advertisement = {
|
|
@@ -4,4 +4,4 @@ import type { UseP2PReplicatorResult } from "./UseP2PReplicatorResult.js";
|
|
|
4
4
|
* ServiceFeature: Registers event handlers for P2P replication and manages the lifecycle of a LiveSyncTrysteroReplicator instance.
|
|
5
5
|
* @param host
|
|
6
6
|
*/
|
|
7
|
-
export declare function useP2PReplicatorCommands(host: NecessaryServices<"API" | "setting", never>,
|
|
7
|
+
export declare function useP2PReplicatorCommands(host: NecessaryServices<"API" | "setting", never>, result: UseP2PReplicatorResult): void;
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { Logger, LOG_LEVEL_NOTICE } from "octagonal-wheels/common/logger";
|
|
2
|
-
function useP2PReplicatorCommands(host,
|
|
2
|
+
function useP2PReplicatorCommands(host, result) {
|
|
3
3
|
host.services.API.addCommand({
|
|
4
4
|
id: "p2p-establish-connection",
|
|
5
5
|
name: "P2P Sync : Connect to the Signalling Server",
|
|
6
6
|
checkCallback: (isChecking) => {
|
|
7
|
+
const replicator = result.replicator;
|
|
7
8
|
if (!replicator) return false;
|
|
8
9
|
if (isChecking) return !(replicator.server?.isServing ?? false);
|
|
9
10
|
void replicator.open();
|
|
@@ -13,6 +14,7 @@ function useP2PReplicatorCommands(host, { replicator }) {
|
|
|
13
14
|
id: "p2p-close-connection",
|
|
14
15
|
name: "P2P Sync : Disconnect from the Signalling Server",
|
|
15
16
|
checkCallback: (isChecking) => {
|
|
17
|
+
const replicator = result.replicator;
|
|
16
18
|
if (!replicator) return false;
|
|
17
19
|
if (isChecking) return replicator.server?.isServing ?? false;
|
|
18
20
|
Logger("Closing P2P Connection", LOG_LEVEL_NOTICE);
|
|
@@ -8,6 +8,7 @@ function useP2PReplicatorFeature(host, openReplicationUIFactory, openRebuildUIFa
|
|
|
8
8
|
let replicator = new LiveSyncTrysteroReplicator({
|
|
9
9
|
services: host.services
|
|
10
10
|
});
|
|
11
|
+
let replacementPromise;
|
|
11
12
|
if (openReplicationUIFactory) {
|
|
12
13
|
replicator.env.openReplicationUI = openReplicationUIFactory(replicator);
|
|
13
14
|
}
|
|
@@ -19,27 +20,36 @@ function useP2PReplicatorFeature(host, openReplicationUIFactory, openRebuildUIFa
|
|
|
19
20
|
return replicator;
|
|
20
21
|
}
|
|
21
22
|
};
|
|
22
|
-
addP2PEventHandlers(activeReplicator.replicator, host.services.context.events);
|
|
23
|
+
addP2PEventHandlers(() => activeReplicator.replicator, host.services.context.events);
|
|
23
24
|
host.services.replicator.getNewReplicator.addHandler(
|
|
24
25
|
async (settingOverride = {}) => {
|
|
25
26
|
const settings = { ...host.services.setting.currentSettings(), ...settingOverride };
|
|
26
27
|
if (settings.remoteType == REMOTE_P2P) {
|
|
27
|
-
|
|
28
|
+
if (replacementPromise) return await replacementPromise;
|
|
29
|
+
const operation = (async () => {
|
|
30
|
+
const existingReplicator = replicator;
|
|
31
|
+
try {
|
|
32
|
+
await existingReplicator?.close();
|
|
33
|
+
} catch (e) {
|
|
34
|
+
Logger(`Error closing existing p2p replicator`);
|
|
35
|
+
Logger(e, LOG_LEVEL_VERBOSE);
|
|
36
|
+
}
|
|
37
|
+
const newReplicator = new LiveSyncTrysteroReplicator({ services: host.services });
|
|
38
|
+
if (openReplicationUIFactory) {
|
|
39
|
+
newReplicator.env.openReplicationUI = openReplicationUIFactory(newReplicator);
|
|
40
|
+
}
|
|
41
|
+
if (openRebuildUIFactory) {
|
|
42
|
+
newReplicator.env.openRebuildUI = openRebuildUIFactory(newReplicator);
|
|
43
|
+
}
|
|
44
|
+
replicator = newReplicator;
|
|
45
|
+
return replicator;
|
|
46
|
+
})();
|
|
47
|
+
replacementPromise = operation;
|
|
28
48
|
try {
|
|
29
|
-
await
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
Logger(e, LOG_LEVEL_VERBOSE);
|
|
49
|
+
return await operation;
|
|
50
|
+
} finally {
|
|
51
|
+
if (replacementPromise === operation) replacementPromise = void 0;
|
|
33
52
|
}
|
|
34
|
-
const newReplicator = new LiveSyncTrysteroReplicator({ services: host.services });
|
|
35
|
-
if (openReplicationUIFactory) {
|
|
36
|
-
newReplicator.env.openReplicationUI = openReplicationUIFactory(newReplicator);
|
|
37
|
-
}
|
|
38
|
-
if (openRebuildUIFactory) {
|
|
39
|
-
newReplicator.env.openRebuildUI = openRebuildUIFactory(newReplicator);
|
|
40
|
-
}
|
|
41
|
-
replicator = newReplicator;
|
|
42
|
-
return replicator;
|
|
43
53
|
}
|
|
44
54
|
return void 0;
|
|
45
55
|
}
|
package/dist/rpc/RpcRoom.d.ts
CHANGED
package/dist/rpc/RpcRoom.js
CHANGED
|
@@ -23,34 +23,40 @@ class RpcRoom {
|
|
|
23
23
|
incomingChunkMap = /* @__PURE__ */ new Map();
|
|
24
24
|
incomingChunkTimers = /* @__PURE__ */ new Map();
|
|
25
25
|
peerVersion = /* @__PURE__ */ new Map();
|
|
26
|
-
|
|
26
|
+
disposers = [];
|
|
27
27
|
constructor(options) {
|
|
28
28
|
this.options = {
|
|
29
29
|
maxWirePayloadBytes: options.maxWirePayloadBytes ?? 32 * 1024,
|
|
30
30
|
chunkMissingRetryMs: options.chunkMissingRetryMs ?? 350,
|
|
31
31
|
...options
|
|
32
32
|
};
|
|
33
|
-
this.
|
|
34
|
-
|
|
35
|
-
|
|
33
|
+
this.disposers.push(
|
|
34
|
+
this.options.transport.onMessage((msg, peerId) => {
|
|
35
|
+
void this.onWireMessage(msg, peerId);
|
|
36
|
+
})
|
|
37
|
+
);
|
|
36
38
|
if (this.options.transport.onPeerJoin) {
|
|
37
|
-
this.
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
39
|
+
this.disposers.push(
|
|
40
|
+
this.options.transport.onPeerJoin((peerId) => {
|
|
41
|
+
void this.sendEnvelope(peerId, {
|
|
42
|
+
kind: "handshake",
|
|
43
|
+
versionMajor: RPC_VERSION_MAJOR,
|
|
44
|
+
versionMinor: RPC_VERSION_MINOR
|
|
45
|
+
});
|
|
46
|
+
})
|
|
47
|
+
);
|
|
44
48
|
}
|
|
45
49
|
if (this.options.transport.onPeerLeave) {
|
|
46
|
-
this.
|
|
47
|
-
this.
|
|
48
|
-
|
|
49
|
-
|
|
50
|
+
this.disposers.push(
|
|
51
|
+
this.options.transport.onPeerLeave((peerId) => {
|
|
52
|
+
this.sessions.delete(peerId);
|
|
53
|
+
this.peerVersion.delete(peerId);
|
|
54
|
+
})
|
|
55
|
+
);
|
|
50
56
|
}
|
|
51
57
|
}
|
|
52
58
|
close() {
|
|
53
|
-
this.
|
|
59
|
+
this.disposers.splice(0).forEach((dispose) => dispose());
|
|
54
60
|
this.pending.forEach((pending) => pending.reject(new RpcError("NOT_CONNECTED", "Room closed")));
|
|
55
61
|
this.pending.clear();
|
|
56
62
|
this.inboundCalls.clear();
|
|
@@ -96,6 +96,8 @@ export type TrysteroRoomHandle = {
|
|
|
96
96
|
/**
|
|
97
97
|
* Returns the current WebRTC peer connections keyed by peer ID.
|
|
98
98
|
* Equivalent to the Trystero `room.getPeers()` call.
|
|
99
|
+
* Treat these as diagnostic handles. Their lifecycle belongs to Trystero;
|
|
100
|
+
* call {@link TrysteroRoomHandle.leave} instead of closing them directly.
|
|
99
101
|
*/
|
|
100
102
|
getPeers: () => Record<string, RTCPeerConnection>;
|
|
101
103
|
/**
|
|
@@ -5,30 +5,37 @@ import { RpcPouchDBProxy } from "../pouchdb/RpcPouchDBProxy.js";
|
|
|
5
5
|
import { ConnectionStringParser } from "../../common/ConnectionString.js";
|
|
6
6
|
import { compatGlobal } from "../../common/coreEnvFunctions.js";
|
|
7
7
|
import { generateJoinRoomOptions } from "./trysteroUtils.js";
|
|
8
|
+
import { subscribeTrysteroPeerEvents } from "./trysteroRoomEvents.js";
|
|
8
9
|
const RPC_ACTION_NAME = "rpc2";
|
|
9
10
|
const AD_ACTION_NAME = "ad";
|
|
10
11
|
function attachAdvertisement(room, localPeerId, name, platform = "unknown") {
|
|
11
12
|
const peerMap = /* @__PURE__ */ new Map();
|
|
12
13
|
const localAd = { peerId: localPeerId, name, platform };
|
|
13
|
-
const
|
|
14
|
-
const sendAdvertisement = (toPeerId) =>
|
|
15
|
-
room
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
14
|
+
const advertisementAction = room.makeAction(AD_ACTION_NAME);
|
|
15
|
+
const sendAdvertisement = (toPeerId) => advertisementAction.send(localAd, toPeerId ? { target: toPeerId } : void 0);
|
|
16
|
+
const disposePeerEvents = subscribeTrysteroPeerEvents(room, {
|
|
17
|
+
// When a new peer joins, introduce ourselves to them specifically.
|
|
18
|
+
onJoin: (peerId) => {
|
|
19
|
+
void sendAdvertisement(peerId);
|
|
20
|
+
},
|
|
21
|
+
// Remove stale entries when a peer disconnects.
|
|
22
|
+
onLeave: (peerId) => {
|
|
23
|
+
peerMap.delete(peerId);
|
|
24
|
+
}
|
|
20
25
|
});
|
|
21
|
-
|
|
26
|
+
advertisementAction.onMessage = (data, { peerId }) => {
|
|
22
27
|
if (data.peerId === localPeerId) return;
|
|
23
28
|
if (data.peerId !== peerId) return;
|
|
24
29
|
peerMap.set(peerId, data);
|
|
25
|
-
}
|
|
30
|
+
};
|
|
26
31
|
return {
|
|
27
32
|
get peers() {
|
|
28
33
|
return peerMap;
|
|
29
34
|
},
|
|
30
35
|
sendAdvertisement,
|
|
31
36
|
stop() {
|
|
37
|
+
disposePeerEvents();
|
|
38
|
+
advertisementAction.onMessage = null;
|
|
32
39
|
peerMap.clear();
|
|
33
40
|
}
|
|
34
41
|
};
|
|
@@ -40,22 +47,22 @@ const TRYSTERO_RPC_DEFAULTS = {
|
|
|
40
47
|
chunkMissingRetryMs: 150
|
|
41
48
|
};
|
|
42
49
|
function wrapTrysteroRoom(room) {
|
|
43
|
-
const
|
|
50
|
+
const rpcAction = room.makeAction(RPC_ACTION_NAME);
|
|
44
51
|
return {
|
|
45
52
|
send(message, peerId) {
|
|
46
|
-
return
|
|
53
|
+
return rpcAction.send(message, { target: peerId });
|
|
47
54
|
},
|
|
48
55
|
onMessage(handler) {
|
|
49
|
-
|
|
50
|
-
return () =>
|
|
56
|
+
rpcAction.onMessage = (data, { peerId }) => handler(data, peerId);
|
|
57
|
+
return () => {
|
|
58
|
+
rpcAction.onMessage = null;
|
|
59
|
+
};
|
|
51
60
|
},
|
|
52
61
|
onPeerJoin(handler) {
|
|
53
|
-
room
|
|
54
|
-
return () => void 0;
|
|
62
|
+
return subscribeTrysteroPeerEvents(room, { onJoin: handler });
|
|
55
63
|
},
|
|
56
64
|
onPeerLeave(handler) {
|
|
57
|
-
room
|
|
58
|
-
return () => void 0;
|
|
65
|
+
return subscribeTrysteroPeerEvents(room, { onLeave: handler });
|
|
59
66
|
}
|
|
60
67
|
};
|
|
61
68
|
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Room } from "@trystero-p2p/nostr";
|
|
2
|
+
type PeerHandler = (peerId: string) => void;
|
|
3
|
+
/**
|
|
4
|
+
* Subscribe multiple consumers to Trystero's single peer-event callback slots.
|
|
5
|
+
*
|
|
6
|
+
* Trystero 0.25 exposes `onPeerJoin` and `onPeerLeave` as nullable callback
|
|
7
|
+
* properties. This adapter preserves the library's multi-subscriber transport
|
|
8
|
+
* contract and returns a disposer for each subscription.
|
|
9
|
+
*/
|
|
10
|
+
export declare function subscribeTrysteroPeerEvents(room: Room, handlers: {
|
|
11
|
+
onJoin?: PeerHandler;
|
|
12
|
+
onLeave?: PeerHandler;
|
|
13
|
+
}): () => void;
|
|
14
|
+
export {};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
const subscribersByRoom = /* @__PURE__ */ new WeakMap();
|
|
2
|
+
function getSubscribers(room) {
|
|
3
|
+
const existing = subscribersByRoom.get(room);
|
|
4
|
+
if (existing) return existing;
|
|
5
|
+
const subscribers = {
|
|
6
|
+
join: /* @__PURE__ */ new Set(),
|
|
7
|
+
leave: /* @__PURE__ */ new Set()
|
|
8
|
+
};
|
|
9
|
+
subscribersByRoom.set(room, subscribers);
|
|
10
|
+
room.onPeerJoin = (peerId) => {
|
|
11
|
+
for (const handler of [...subscribers.join]) handler(peerId);
|
|
12
|
+
};
|
|
13
|
+
room.onPeerLeave = (peerId) => {
|
|
14
|
+
for (const handler of [...subscribers.leave]) handler(peerId);
|
|
15
|
+
};
|
|
16
|
+
return subscribers;
|
|
17
|
+
}
|
|
18
|
+
function subscribeTrysteroPeerEvents(room, handlers) {
|
|
19
|
+
const subscribers = getSubscribers(room);
|
|
20
|
+
if (handlers.onJoin) subscribers.join.add(handlers.onJoin);
|
|
21
|
+
if (handlers.onLeave) subscribers.leave.add(handlers.onLeave);
|
|
22
|
+
return () => {
|
|
23
|
+
if (handlers.onJoin) subscribers.join.delete(handlers.onJoin);
|
|
24
|
+
if (handlers.onLeave) subscribers.leave.delete(handlers.onLeave);
|
|
25
|
+
if (subscribers.join.size === 0 && subscribers.leave.size === 0) {
|
|
26
|
+
room.onPeerJoin = null;
|
|
27
|
+
room.onPeerLeave = null;
|
|
28
|
+
subscribersByRoom.delete(room);
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
export {
|
|
33
|
+
subscribeTrysteroPeerEvents
|
|
34
|
+
};
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { createInstanceLogFunction, type LogFunction } from "../services/lib/logUtils.js";
|
|
2
2
|
import type { NecessaryServices } from "../interfaces/ServiceModule.js";
|
|
3
|
+
type RemoteSizeReviewMode = "notice" | "dialogue";
|
|
3
4
|
/**
|
|
4
5
|
* Notify when checking remote storage size is not configured.
|
|
5
6
|
* @returns true if the check is passed or user has configured the notification, false to block subsequent processes. (always true).
|
|
6
7
|
*/
|
|
7
|
-
export declare function onNotifyRemoteSizeNotConfiguredFactory(host: NecessaryServices<"appLifecycle" | "API" | "setting", never>, log: ReturnType<typeof createInstanceLogFunction
|
|
8
|
+
export declare function onNotifyRemoteSizeNotConfiguredFactory(host: NecessaryServices<"appLifecycle" | "API" | "setting", never>, log: ReturnType<typeof createInstanceLogFunction>, mode?: RemoteSizeReviewMode): () => Promise<boolean>;
|
|
8
9
|
/**
|
|
9
10
|
* Notify when the remote storage size exceed the threshold.
|
|
10
11
|
* @returns true if the check is passed or user has chosen to ignore the warning, false to block subsequent processes.
|
|
@@ -12,7 +13,7 @@ export declare function onNotifyRemoteSizeNotConfiguredFactory(host: NecessarySe
|
|
|
12
13
|
* @param log
|
|
13
14
|
* @returns
|
|
14
15
|
*/
|
|
15
|
-
export declare function onNotifyRemoteSizeExceedFactory(host: NecessaryServices<"API" | "setting" | "replicator", "rebuilder">, log: ReturnType<typeof createInstanceLogFunction
|
|
16
|
+
export declare function onNotifyRemoteSizeExceedFactory(host: NecessaryServices<"API" | "setting" | "replicator", "rebuilder">, log: ReturnType<typeof createInstanceLogFunction>, mode?: RemoteSizeReviewMode): () => Promise<boolean>;
|
|
16
17
|
/**
|
|
17
18
|
* Scan the remote storage size and notify if it is not configured or exceed the threshold.
|
|
18
19
|
* @param host The necessary services required for the operation.
|
|
@@ -20,9 +21,10 @@ export declare function onNotifyRemoteSizeExceedFactory(host: NecessaryServices<
|
|
|
20
21
|
* @param resetThreshold Whether to reset the notification threshold before scanning. This is useful when you want to force the notification to show up again.
|
|
21
22
|
* @returns A promise that resolves to true if all checks pass or user has configured the notification.
|
|
22
23
|
*/
|
|
23
|
-
export declare function scanAllStat(host: NecessaryServices<"API" | "setting" | "replicator" | "appLifecycle", "rebuilder">, log: LogFunction, resetThreshold?: boolean): Promise<boolean>;
|
|
24
|
+
export declare function scanAllStat(host: NecessaryServices<"API" | "setting" | "replicator" | "appLifecycle", "rebuilder">, log: LogFunction, resetThreshold?: boolean, mode?: RemoteSizeReviewMode): Promise<boolean>;
|
|
24
25
|
/**
|
|
25
26
|
* Associate the remote storage size check feature with the app lifecycle events.
|
|
26
27
|
* @param host
|
|
27
28
|
*/
|
|
28
29
|
export declare function useCheckRemoteSize(host: NecessaryServices<"API" | "setting" | "replicator" | "appLifecycle", "rebuilder">): void;
|
|
30
|
+
export {};
|