Fixed
-  No longer unnecessary acknowledgements are sent when starting peer-to-peer synchronisation.

Refactored
- Platform impedance-matching-layer has been improved.
- Some UIs have been get isomorphic among Obsidian and web applications (for `webpeer`).
This commit is contained in:
vorotamoroz
2025-02-14 11:15:22 +00:00
parent 4e8243b3d5
commit cc29b4058d
9 changed files with 266 additions and 195 deletions

View File

@@ -7,3 +7,5 @@ rollup.config.js
src/lib/test src/lib/test
src/lib/src/cli src/lib/src/cli
main.js main.js
src/lib/apps/webpeer/dist
src/lib/apps/webpeer/svelte.config.js

View File

@@ -18,6 +18,10 @@ Note: This plugin cannot synchronise with the official "Obsidian Sync".
- Supporting End-to-end encryption. - Supporting End-to-end encryption.
- Synchronisation of settings, snippets, themes, and plug-ins, via [Customization sync(Beta)](#customization-sync) or [Hidden File Sync](#hiddenfilesync) - Synchronisation of settings, snippets, themes, and plug-ins, via [Customization sync(Beta)](#customization-sync) or [Hidden File Sync](#hiddenfilesync)
- WebClip from [obsidian-livesync-webclip](https://chrome.google.com/webstore/detail/obsidian-livesync-webclip/jfpaflmpckblieefkegjncjoceapakdf) - WebClip from [obsidian-livesync-webclip](https://chrome.google.com/webstore/detail/obsidian-livesync-webclip/jfpaflmpckblieefkegjncjoceapakdf)
- WebRTC peer-to-peer synchronisation without the need any `host` is now possible. (Experimental)
- This feature is still in the experimental stage. Please be careful when using it.
- Instead of using server, you can use [webpeer](https://github.com/vrtmrz/livesync-commonlib/tree/main/apps/webpeer) the pseudo client for receiving and sending between devices.
This plug-in might be useful for researchers, engineers, and developers with a need to keep their notes fully self-hosted for security reasons. Or just anyone who would like the peace of mind of knowing that their notes are fully private. This plug-in might be useful for researchers, engineers, and developers with a need to keep their notes fully self-hosted for security reasons. Or just anyone who would like the peace of mind of knowing that their notes are fully private.

View File

@@ -20,6 +20,7 @@ import {
EVENT_DATABASE_REBUILT, EVENT_DATABASE_REBUILT,
EVENT_PLUGIN_UNLOADED, EVENT_PLUGIN_UNLOADED,
EVENT_REQUEST_OPEN_P2P, EVENT_REQUEST_OPEN_P2P,
EVENT_SETTING_SAVED,
eventHub, eventHub,
} from "../../common/events.ts"; } from "../../common/events.ts";
import { import {
@@ -31,8 +32,9 @@ import {
import type { LiveSyncAbstractReplicator } from "../../lib/src/replication/LiveSyncAbstractReplicator.ts"; import type { LiveSyncAbstractReplicator } from "../../lib/src/replication/LiveSyncAbstractReplicator.ts";
import { Logger } from "octagonal-wheels/common/logger"; import { Logger } from "octagonal-wheels/common/logger";
import { $msg } from "../../lib/src/common/i18n.ts"; import { $msg } from "../../lib/src/common/i18n.ts";
import type { CommandShim } from "./P2PReplicator/P2PReplicatorPaneCommon.ts";
export class P2PReplicator extends LiveSyncCommands implements IObsidianModule { export class P2PReplicator extends LiveSyncCommands implements IObsidianModule, CommandShim {
$anyNewReplicator(settingOverride: Partial<RemoteDBSettings> = {}): Promise<LiveSyncAbstractReplicator> { $anyNewReplicator(settingOverride: Partial<RemoteDBSettings> = {}): Promise<LiveSyncAbstractReplicator> {
const settings = { ...this.settings, ...settingOverride }; const settings = { ...this.settings, ...settingOverride };
if (settings.remoteType == REMOTE_P2P) { if (settings.remoteType == REMOTE_P2P) {
@@ -66,6 +68,9 @@ export class P2PReplicator extends LiveSyncCommands implements IObsidianModule {
eventHub.onEvent(EVENT_PLUGIN_UNLOADED, () => { eventHub.onEvent(EVENT_PLUGIN_UNLOADED, () => {
void this.close(); void this.close();
}); });
eventHub.onEvent(EVENT_SETTING_SAVED, async () => {
await this.initialiseP2PReplicator();
});
// throw new Error("Method not implemented."); // throw new Error("Method not implemented.");
} }
async $everyOnInitializeDatabase(): Promise<boolean> { async $everyOnInitializeDatabase(): Promise<boolean> {
@@ -154,8 +159,13 @@ export class P2PReplicator extends LiveSyncCommands implements IObsidianModule {
if (!this._replicatorInstance) { if (!this._replicatorInstance) {
await this.initialiseP2PReplicator(); await this.initialiseP2PReplicator();
if (!this.settings.P2P_AutoStart) {
// While auto start is enabled, we don't need to open the connection (Literally, it's already opened automatically)
await this._replicatorInstance!.open();
}
} else {
await this._replicatorInstance?.open();
} }
await this._replicatorInstance?.open();
} }
async close() { async close() {
await this._replicatorInstance?.close(); await this._replicatorInstance?.close();
@@ -208,7 +218,9 @@ export class P2PReplicator extends LiveSyncCommands implements IObsidianModule {
simpleStore: getPlugin().$$getSimpleStore("p2p-sync"), simpleStore: getPlugin().$$getSimpleStore("p2p-sync"),
}; };
this._replicatorInstance = new TrysteroReplicator(env); this._replicatorInstance = new TrysteroReplicator(env);
// p2p_replicator.set(this.p2pReplicator); if (this.settings.P2P_AutoStart && this.settings.P2P_Enabled) {
await this.open();
}
return this._replicatorInstance; return this._replicatorInstance;
} catch (e) { } catch (e) {
this._log( this._log(
@@ -219,4 +231,10 @@ export class P2PReplicator extends LiveSyncCommands implements IObsidianModule {
throw e; throw e;
} }
} }
enableBroadcastCastings() {
return this?._replicatorInstance?.enableBroadcastChanges();
}
disableBroadcastCastings() {
return this?._replicatorInstance?.disableBroadcastChanges();
}
} }

View File

@@ -1,16 +1,15 @@
<script lang="ts"> <script lang="ts">
import { onMount, setContext } from "svelte"; import { onMount, setContext } from "svelte";
import { AutoAccepting, DEFAULT_SETTINGS, type P2PSyncSetting } from "../../../lib/src/common/types";
import { import {
AutoAccepting, AcceptedStatus,
DEFAULT_SETTINGS, ConnectionStatus,
type ObsidianLiveSyncSettings, type CommandShim,
type P2PSyncSetting, type PeerStatus,
} from "../../../lib/src/common/types"; type PluginShim,
import { type P2PReplicator } from "../CmdP2PSync"; } from "./P2PReplicatorPaneCommon";
import { AcceptedStatus, ConnectionStatus, type PeerStatus } from "./P2PReplicatorPaneView";
import PeerStatusRow from "../P2PReplicator/PeerStatusRow.svelte"; import PeerStatusRow from "../P2PReplicator/PeerStatusRow.svelte";
import { EVENT_LAYOUT_READY, eventHub } from "../../../common/events"; import { EVENT_LAYOUT_READY, eventHub } from "../../../common/events";
import type ObsidianLiveSyncPlugin from "../../../main";
import { import {
type PeerInfo, type PeerInfo,
type P2PServerInfo, type P2PServerInfo,
@@ -22,11 +21,12 @@
import { $msg as _msg } from "../../../lib/src/common/i18n"; import { $msg as _msg } from "../../../lib/src/common/i18n";
interface Props { interface Props {
plugin: ObsidianLiveSyncPlugin; plugin: PluginShim;
cmdSync: CommandShim;
} }
let { plugin }: Props = $props(); let { plugin, cmdSync }: Props = $props();
const cmdSync = plugin.getAddOn<P2PReplicator>("P2PReplicator")!; // const cmdSync = plugin.getAddOn<P2PReplicator>("P2PReplicator")!;
setContext("getReplicator", () => cmdSync); setContext("getReplicator", () => cmdSync);
const initialSettings = { ...plugin.settings }; const initialSettings = { ...plugin.settings };
@@ -34,6 +34,7 @@
let settings = $state<P2PSyncSetting>(initialSettings); let settings = $state<P2PSyncSetting>(initialSettings);
// const vaultName = plugin.$$getVaultName(); // const vaultName = plugin.$$getVaultName();
// const dbKey = `${vaultName}-p2p-device-name`; // const dbKey = `${vaultName}-p2p-device-name`;
const initialDeviceName = cmdSync.getConfig("p2p_device_name") ?? plugin.$$getVaultName(); const initialDeviceName = cmdSync.getConfig("p2p_device_name") ?? plugin.$$getVaultName();
let deviceName = $state<string>(initialDeviceName); let deviceName = $state<string>(initialDeviceName);
@@ -100,7 +101,7 @@
let serverInfo = $state<P2PServerInfo | undefined>(undefined); let serverInfo = $state<P2PServerInfo | undefined>(undefined);
let replicatorInfo = $state<P2PReplicatorStatus | undefined>(undefined); let replicatorInfo = $state<P2PReplicatorStatus | undefined>(undefined);
const applyLoadSettings = (d: ObsidianLiveSyncSettings, force: boolean) => { const applyLoadSettings = (d: P2PSyncSetting, force: boolean) => {
const { P2P_relays, P2P_roomID, P2P_passphrase, P2P_AppID, P2P_AutoAccepting } = d; const { P2P_relays, P2P_roomID, P2P_passphrase, P2P_AppID, P2P_AutoAccepting } = d;
if (force || !isP2PEnabledModified) eP2PEnabled = d.P2P_Enabled; if (force || !isP2PEnabledModified) eP2PEnabled = d.P2P_Enabled;
if (force || !isRelayModified) eRelay = P2P_relays; if (force || !isRelayModified) eRelay = P2P_relays;
@@ -221,10 +222,10 @@
await cmdSync.close(); await cmdSync.close();
} }
function startBroadcasting() { function startBroadcasting() {
cmdSync._replicatorInstance?.enableBroadcastChanges(); void cmdSync.enableBroadcastCastings();
} }
function stopBroadcasting() { function stopBroadcasting() {
cmdSync._replicatorInstance?.disableBroadcastChanges(); void cmdSync.disableBroadcastCastings();
} }
const initialDialogStatusKey = `p2p-dialog-status`; const initialDialogStatusKey = `p2p-dialog-status`;
@@ -283,6 +284,7 @@
type="text" type="text"
placeholder="wss://exp-relay.vrtmrz.net, wss://xxxxx" placeholder="wss://exp-relay.vrtmrz.net, wss://xxxxx"
bind:value={eRelay} bind:value={eRelay}
autocomplete="off"
/> />
<button onclick={() => useDefaultRelay()}> Use vrtmrz's relay </button> <button onclick={() => useDefaultRelay()}> Use vrtmrz's relay </button>
</label> </label>
@@ -292,7 +294,7 @@
<th> Room ID </th> <th> Room ID </th>
<td> <td>
<label class={{ "is-dirty": isRoomIdModified }}> <label class={{ "is-dirty": isRoomIdModified }}>
<input type="text" placeholder="anything-you-like" bind:value={eRoomId} /> <input type="text" placeholder="anything-you-like" bind:value={eRoomId} autocomplete="off"/>
<button onclick={() => chooseRandom()}> Use Random Number </button> <button onclick={() => chooseRandom()}> Use Random Number </button>
</label> </label>
<span> <span>
@@ -318,7 +320,8 @@
<th> This device name </th> <th> This device name </th>
<td> <td>
<label class={{ "is-dirty": isDeviceNameModified }}> <label class={{ "is-dirty": isDeviceNameModified }}>
<input type="text" placeholder="iphone-16" bind:value={eDeviceName} /> <input type="text" placeholder="iphone-16" bind:value={eDeviceName}
autocomplete="off" />
</label> </label>
</td> </td>
</tr> </tr>

View File

@@ -0,0 +1,58 @@
import type { P2PSyncSetting } from "../../../lib/src/common/types";
export const EVENT_P2P_PEER_SHOW_EXTRA_MENU = "p2p-peer-show-extra-menu";
export enum AcceptedStatus {
UNKNOWN = "Unknown",
ACCEPTED = "Accepted",
DENIED = "Denied",
ACCEPTED_IN_SESSION = "Accepted in session",
DENIED_IN_SESSION = "Denied in session",
}
export type PeerExtraMenuEvent = {
peer: PeerStatus;
event: MouseEvent;
};
export enum ConnectionStatus {
CONNECTED = "Connected",
CONNECTED_LIVE = "Connected(live)",
DISCONNECTED = "Disconnected",
}
export type PeerStatus = {
name: string;
peerId: string;
syncOnConnect: boolean;
watchOnConnect: boolean;
syncOnReplicationCommand: boolean;
accepted: AcceptedStatus;
status: ConnectionStatus;
isFetching: boolean;
isSending: boolean;
isWatching: boolean;
};
declare global {
interface LSEvents {
[EVENT_P2P_PEER_SHOW_EXTRA_MENU]: PeerExtraMenuEvent;
// [EVENT_P2P_REPLICATOR_PROGRESS]: P2PReplicationReport;
}
}
export interface PluginShim {
saveSettings: () => Promise<void>;
settings: P2PSyncSetting;
rebuilder: any;
$$scheduleAppReload: () => void;
$$getVaultName: () => string;
// confirm: any;
}
export interface CommandShim {
getConfig(key: string): string | null;
setConfig(key: string, value: string): void;
open(): Promise<void>;
close(): Promise<void>;
enableBroadcastCastings(): void; // cmdSync._replicatorInstance?.enableBroadcastChanges();
disableBroadcastCastings(): void; ///cmdSync._replicatorInstance?.disableBroadcastChanges();
}

View File

@@ -1,35 +1,33 @@
import { WorkspaceLeaf } from "obsidian"; import { Menu, WorkspaceLeaf } from "obsidian";
import ReplicatorPaneComponent from "./P2PReplicatorPane.svelte"; import ReplicatorPaneComponent from "./P2PReplicatorPane.svelte";
import type ObsidianLiveSyncPlugin from "../../../main.ts"; import type ObsidianLiveSyncPlugin from "../../../main.ts";
import { mount } from "svelte"; import { mount } from "svelte";
import { SvelteItemView } from "../../../common/SvelteItemView.ts"; import { SvelteItemView } from "../../../common/SvelteItemView.ts";
import { eventHub } from "../../../common/events.ts";
import { EVENT_P2P_PEER_SHOW_EXTRA_MENU, type PeerStatus } from "./P2PReplicatorPaneCommon.ts";
import { unique } from "octagonal-wheels/collection";
import { LOG_LEVEL_NOTICE, REMOTE_P2P } from "../../../lib/src/common/types.ts";
import { Logger } from "../../../lib/src/common/logger.ts";
import { P2PReplicator } from "../CmdP2PSync.ts";
export const VIEW_TYPE_P2P = "p2p-replicator"; export const VIEW_TYPE_P2P = "p2p-replicator";
export enum AcceptedStatus { function addToList(item: string, list: string) {
UNKNOWN = "Unknown", return unique(
ACCEPTED = "Accepted", list
DENIED = "Denied", .split(",")
ACCEPTED_IN_SESSION = "Accepted in session", .map((e) => e.trim())
DENIED_IN_SESSION = "Denied in session", .concat(item)
.filter((p) => p)
).join(",");
} }
function removeFromList(item: string, list: string) {
export enum ConnectionStatus { return list
CONNECTED = "Connected", .split(",")
CONNECTED_LIVE = "Connected(live)", .map((e) => e.trim())
DISCONNECTED = "Disconnected", .filter((p) => p !== item)
.filter((p) => p)
.join(",");
} }
export type PeerStatus = {
name: string;
peerId: string;
syncOnConnect: boolean;
watchOnConnect: boolean;
syncOnReplicationCommand: boolean;
accepted: AcceptedStatus;
status: ConnectionStatus;
isFetching: boolean;
isSending: boolean;
isWatching: boolean;
};
export class P2PReplicatorPaneView extends SvelteItemView { export class P2PReplicatorPaneView extends SvelteItemView {
plugin: ObsidianLiveSyncPlugin; plugin: ObsidianLiveSyncPlugin;
@@ -40,10 +38,130 @@ export class P2PReplicatorPaneView extends SvelteItemView {
getIcon(): string { getIcon(): string {
return "waypoints"; return "waypoints";
} }
get replicator() {
const r = this.plugin.getAddOn<P2PReplicator>(P2PReplicator.name);
if (!r || !r._replicatorInstance) {
throw new Error("Replicator not found");
}
return r._replicatorInstance;
}
async replicateFrom(peer: PeerStatus) {
await this.replicator.replicateFrom(peer.peerId);
}
async replicateTo(peer: PeerStatus) {
await this.replicator.requestSynchroniseToPeer(peer.peerId);
}
async getRemoteConfig(peer: PeerStatus) {
Logger(
`Requesting remote config for ${peer.name}. Please input the passphrase on the remote device`,
LOG_LEVEL_NOTICE
);
const remoteConfig = await this.replicator.getRemoteConfig(peer.peerId);
if (remoteConfig) {
Logger(`Remote config for ${peer.name} is retrieved successfully`);
const DROP = "Yes, and drop local database";
const KEEP = "Yes, but keep local database";
const CANCEL = "No, cancel";
const yn = await this.plugin.confirm.askSelectStringDialogue(
`Do you really want to apply the remote config? This will overwrite your current config immediately and restart.
And you can also drop the local database to rebuild from the remote device.`,
[DROP, KEEP, CANCEL] as const,
{
defaultAction: CANCEL,
title: "Apply Remote Config ",
}
);
if (yn === DROP || yn === KEEP) {
if (yn === DROP) {
if (remoteConfig.remoteType !== REMOTE_P2P) {
const yn2 = await this.plugin.confirm.askYesNoDialog(
`Do you want to set the remote type to "P2P Sync" to rebuild by "P2P replication"?`,
{
title: "Rebuild from remote device",
}
);
if (yn2 === "yes") {
remoteConfig.remoteType = REMOTE_P2P;
remoteConfig.P2P_RebuildFrom = peer.name;
}
}
}
this.plugin.settings = remoteConfig;
await this.plugin.saveSettings();
if (yn === DROP) {
await this.plugin.rebuilder.scheduleFetch();
} else {
await this.plugin.$$scheduleAppReload();
}
} else {
Logger(`Cancelled\nRemote config for ${peer.name} is not applied`, LOG_LEVEL_NOTICE);
}
} else {
Logger(`Cannot retrieve remote config for ${peer.peerId}`);
}
}
async toggleProp(peer: PeerStatus, prop: "syncOnConnect" | "watchOnConnect" | "syncOnReplicationCommand") {
const settingMap = {
syncOnConnect: "P2P_AutoSyncPeers",
watchOnConnect: "P2P_AutoWatchPeers",
syncOnReplicationCommand: "P2P_SyncOnReplication",
} as const;
const targetSetting = settingMap[prop];
if (peer[prop]) {
this.plugin.settings[targetSetting] = removeFromList(peer.name, this.plugin.settings[targetSetting]);
await this.plugin.saveSettings();
} else {
this.plugin.settings[targetSetting] = addToList(peer.name, this.plugin.settings[targetSetting]);
await this.plugin.saveSettings();
}
await this.plugin.saveSettings();
}
m?: Menu;
constructor(leaf: WorkspaceLeaf, plugin: ObsidianLiveSyncPlugin) { constructor(leaf: WorkspaceLeaf, plugin: ObsidianLiveSyncPlugin) {
super(leaf); super(leaf);
this.plugin = plugin; this.plugin = plugin;
eventHub.onEvent(EVENT_P2P_PEER_SHOW_EXTRA_MENU, ({ peer, event }) => {
if (this.m) {
this.m.hide();
}
this.m = new Menu()
.addItem((item) => item.setTitle("📥 Only Fetch").onClick(() => this.replicateFrom(peer)))
.addItem((item) => item.setTitle("📤 Only Send").onClick(() => this.replicateTo(peer)))
.addSeparator()
.addItem((item) => {
item.setTitle("🔧 Get Configuration").onClick(async () => {
await this.getRemoteConfig(peer);
});
})
.addSeparator()
.addItem((item) => {
const mark = peer.syncOnConnect ? "checkmark" : null;
item.setTitle("Toggle Sync on connect")
.onClick(async () => {
await this.toggleProp(peer, "syncOnConnect");
})
.setIcon(mark);
})
.addItem((item) => {
const mark = peer.watchOnConnect ? "checkmark" : null;
item.setTitle("Toggle Watch on connect")
.onClick(async () => {
await this.toggleProp(peer, "watchOnConnect");
})
.setIcon(mark);
})
.addItem((item) => {
const mark = peer.syncOnReplicationCommand ? "checkmark" : null;
item.setTitle("Toggle Sync on `Replicate now` command")
.onClick(async () => {
await this.toggleProp(peer, "syncOnReplicationCommand");
})
.setIcon(mark);
});
this.m.showAtPosition({ x: event.x, y: event.y });
});
} }
getViewType() { getViewType() {
@@ -54,11 +172,22 @@ export class P2PReplicatorPaneView extends SvelteItemView {
return "Peer-to-Peer Replicator"; return "Peer-to-Peer Replicator";
} }
override async onClose(): Promise<void> {
await super.onClose();
if (this.m) {
this.m.hide();
}
}
instantiateComponent(target: HTMLElement) { instantiateComponent(target: HTMLElement) {
const cmdSync = this.plugin.getAddOn<P2PReplicator>(P2PReplicator.name);
if (!cmdSync) {
throw new Error("Replicator not found");
}
return mount(ReplicatorPaneComponent, { return mount(ReplicatorPaneComponent, {
target: target, target: target,
props: { props: {
plugin: this.plugin, plugin: cmdSync.plugin,
cmdSync: cmdSync,
}, },
}); });
} }

View File

@@ -1,11 +1,9 @@
<script lang="ts"> <script lang="ts">
import { getContext } from "svelte"; import { getContext } from "svelte";
import { AcceptedStatus, ConnectionStatus, type PeerStatus } from "./P2PReplicatorPaneView"; import { AcceptedStatus, type PeerStatus } from "./P2PReplicatorPaneCommon";
import { Menu, Setting } from "obsidian";
import { LOG_LEVEL_INFO, LOG_LEVEL_NOTICE, Logger } from "octagonal-wheels/common/logger";
import type { P2PReplicator } from "../CmdP2PSync"; import type { P2PReplicator } from "../CmdP2PSync";
import { unique } from "../../../lib/src/common/utils"; import { eventHub } from "../../../common/events";
import { REMOTE_P2P } from "src/lib/src/common/types"; import { EVENT_P2P_PEER_SHOW_EXTRA_MENU } from "./P2PReplicatorPaneCommon";
interface Props { interface Props {
peerStatus: PeerStatus; peerStatus: PeerStatus;
@@ -94,154 +92,13 @@
function stopWatching() { function stopWatching() {
replicator.unwatchPeer(peer.peerId); replicator.unwatchPeer(peer.peerId);
} }
function replicateFrom() {
replicator.replicateFrom(peer.peerId);
}
function replicateTo() {
replicator.requestSynchroniseToPeer(peer.peerId);
}
function sync() { function sync() {
replicator.sync(peer.peerId, false); replicator.sync(peer.peerId, false);
} }
function addToList(item: string, list: string) {
return unique(
list
.split(",")
.map((e) => e.trim())
.concat(item)
.filter((p) => p)
).join(",");
}
function removeFromList(item: string, list: string) {
return list
.split(",")
.map((e) => e.trim())
.filter((p) => p !== item)
.filter((p) => p)
.join(",");
}
function moreMenu(evt: MouseEvent) { function moreMenu(evt: MouseEvent) {
const m = new Menu() eventHub.emitEvent(EVENT_P2P_PEER_SHOW_EXTRA_MENU, { peer, event: evt });
.addItem((item) => item.setTitle("📥 Only Fetch").onClick(() => replicateFrom()))
.addItem((item) => item.setTitle("📤 Only Send").onClick(() => replicateTo()))
.addSeparator()
.addItem((item) => {
item.setTitle("🔧 Get Configuration").onClick(async () => {
Logger(
`Requesting remote config for ${peer.name}. Please input the passphrase on the remote device`,
LOG_LEVEL_NOTICE
);
const remoteConfig = await replicator.getRemoteConfig(peer.peerId);
if (remoteConfig) {
Logger(`Remote config for ${peer.name} is retrieved successfully`);
const DROP = "Yes, and drop local database";
const KEEP = "Yes, but keep local database";
const CANCEL = "No, cancel";
const yn = await replicator.confirm.askSelectStringDialogue(
`Do you really want to apply the remote config? This will overwrite your current config immediately and restart.
And you can also drop the local database to rebuild from the remote device.`,
[DROP, KEEP, CANCEL] as const,
{
defaultAction: CANCEL,
title: "Apply Remote Config ",
}
);
if (yn === DROP || yn === KEEP) {
if (yn === DROP) {
if (remoteConfig.remoteType !== REMOTE_P2P) {
const yn2 = await replicator.confirm.askYesNoDialog(
`Do you want to set the remote type to "P2P Sync" to rebuild by "P2P replication"?`,
{
title: "Rebuild from remote device",
}
);
if (yn2 === "yes") {
remoteConfig.remoteType = REMOTE_P2P;
remoteConfig.P2P_RebuildFrom = peer.name;
}
}
}
cmdReplicator.plugin.settings = remoteConfig;
await cmdReplicator.plugin.saveSettings();
// await cmdReplicator.setConfig("rebuildFrom", peer.name);
if (yn === DROP) {
await cmdReplicator.plugin.rebuilder.scheduleFetch();
} else {
await cmdReplicator.plugin.$$scheduleAppReload();
}
} else {
Logger(`Cancelled\nRemote config for ${peer.name} is not applied`, LOG_LEVEL_NOTICE);
}
} else {
Logger(`Cannot retrieve remote config for ${peer.peerId}`);
}
});
})
.addSeparator()
.addItem((item) => {
const mark = peer.syncOnConnect ? "checkmark" : null;
item.setTitle("Toggle Sync on connect")
.onClick(async () => {
// TODO: Fix to prevent writing to settings directly
if (peer.syncOnConnect) {
cmdReplicator.settings.P2P_AutoSyncPeers = removeFromList(
peer.name,
cmdReplicator.settings.P2P_AutoSyncPeers
);
await cmdReplicator.plugin.saveSettings();
} else {
cmdReplicator.settings.P2P_AutoSyncPeers = addToList(
peer.name,
cmdReplicator.settings.P2P_AutoSyncPeers
);
await cmdReplicator.plugin.saveSettings();
}
})
.setIcon(mark);
})
.addItem((item) => {
const mark = peer.watchOnConnect ? "checkmark" : null;
item.setTitle("Toggle Watch on connect")
.onClick(async () => {
// TODO: Fix to prevent writing to settings directly
if (peer.watchOnConnect) {
cmdReplicator.settings.P2P_AutoWatchPeers = removeFromList(
peer.name,
cmdReplicator.settings.P2P_AutoWatchPeers
);
await cmdReplicator.plugin.saveSettings();
} else {
cmdReplicator.settings.P2P_AutoWatchPeers = addToList(
peer.name,
cmdReplicator.settings.P2P_AutoWatchPeers
);
await cmdReplicator.plugin.saveSettings();
}
})
.setIcon(mark);
})
.addItem((item) => {
const mark = peer.syncOnReplicationCommand ? "checkmark" : null;
item.setTitle("Toggle Sync on `Replicate now` command")
.onClick(async () => {
// TODO: Fix to prevent writing to settings directly
if (peer.syncOnReplicationCommand) {
cmdReplicator.settings.P2P_SyncOnReplication = removeFromList(
peer.name,
cmdReplicator.settings.P2P_SyncOnReplication
);
await cmdReplicator.plugin.saveSettings();
} else {
cmdReplicator.settings.P2P_SyncOnReplication = addToList(
peer.name,
cmdReplicator.settings.P2P_SyncOnReplication
);
await cmdReplicator.plugin.saveSettings();
}
})
.setIcon(mark);
});
m.showAtPosition({ x: evt.x, y: evt.y });
} }
</script> </script>

Submodule src/lib updated: b8b05a2146...9f71ed12ad

View File

@@ -14,7 +14,7 @@ import { cancelAllPeriodicTask, cancelAllTasks } from "octagonal-wheels/concurre
import { stopAllRunningProcessors } from "octagonal-wheels/concurrency/processor"; import { stopAllRunningProcessors } from "octagonal-wheels/concurrency/processor";
import { AbstractModule } from "../AbstractModule.ts"; import { AbstractModule } from "../AbstractModule.ts";
import type { ICoreModule } from "../ModuleTypes.ts"; import type { ICoreModule } from "../ModuleTypes.ts";
import { EVENT_PLATFORM_UNLOADED } from "../../lib/src/PlatformAPIs/APIBase.ts"; import { EVENT_PLATFORM_UNLOADED } from "../../lib/src/PlatformAPIs/base/APIBase.ts";
export class ModuleLiveSyncMain extends AbstractModule implements ICoreModule { export class ModuleLiveSyncMain extends AbstractModule implements ICoreModule {
async $$onLiveSyncReady() { async $$onLiveSyncReady() {