mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2026-03-26 11:45:16 +00:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
67568ea886 | ||
|
|
cc29b4058d |
@@ -7,3 +7,5 @@ rollup.config.js
|
||||
src/lib/test
|
||||
src/lib/src/cli
|
||||
main.js
|
||||
src/lib/apps/webpeer/dist
|
||||
src/lib/apps/webpeer/svelte.config.js
|
||||
@@ -18,6 +18,10 @@ Note: This plugin cannot synchronise with the official "Obsidian Sync".
|
||||
- Supporting End-to-end encryption.
|
||||
- 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)
|
||||
- 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.
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "obsidian-livesync",
|
||||
"name": "Self-hosted LiveSync",
|
||||
"version": "0.24.11",
|
||||
"version": "0.24.12",
|
||||
"minAppVersion": "0.9.12",
|
||||
"description": "Community implementation of self-hosted livesync. Reflect your vault changes to some other devices immediately. Please make sure to disable other synchronize solutions to avoid content corruption or duplication.",
|
||||
"author": "vorotamoroz",
|
||||
|
||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "obsidian-livesync",
|
||||
"version": "0.24.11",
|
||||
"version": "0.24.12",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "obsidian-livesync",
|
||||
"version": "0.24.11",
|
||||
"version": "0.24.12",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-s3": "^3.645.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "obsidian-livesync",
|
||||
"version": "0.24.11",
|
||||
"version": "0.24.12",
|
||||
"description": "Reflect your vault changes to some other devices immediately. Please make sure to disable other synchronize solutions to avoid content corruption or duplication.",
|
||||
"main": "main.js",
|
||||
"type": "module",
|
||||
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
EVENT_DATABASE_REBUILT,
|
||||
EVENT_PLUGIN_UNLOADED,
|
||||
EVENT_REQUEST_OPEN_P2P,
|
||||
EVENT_SETTING_SAVED,
|
||||
eventHub,
|
||||
} from "../../common/events.ts";
|
||||
import {
|
||||
@@ -31,8 +32,9 @@ import {
|
||||
import type { LiveSyncAbstractReplicator } from "../../lib/src/replication/LiveSyncAbstractReplicator.ts";
|
||||
import { Logger } from "octagonal-wheels/common/logger";
|
||||
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> {
|
||||
const settings = { ...this.settings, ...settingOverride };
|
||||
if (settings.remoteType == REMOTE_P2P) {
|
||||
@@ -66,6 +68,9 @@ export class P2PReplicator extends LiveSyncCommands implements IObsidianModule {
|
||||
eventHub.onEvent(EVENT_PLUGIN_UNLOADED, () => {
|
||||
void this.close();
|
||||
});
|
||||
eventHub.onEvent(EVENT_SETTING_SAVED, async () => {
|
||||
await this.initialiseP2PReplicator();
|
||||
});
|
||||
// throw new Error("Method not implemented.");
|
||||
}
|
||||
async $everyOnInitializeDatabase(): Promise<boolean> {
|
||||
@@ -154,8 +159,13 @@ export class P2PReplicator extends LiveSyncCommands implements IObsidianModule {
|
||||
|
||||
if (!this._replicatorInstance) {
|
||||
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() {
|
||||
await this._replicatorInstance?.close();
|
||||
@@ -208,7 +218,9 @@ export class P2PReplicator extends LiveSyncCommands implements IObsidianModule {
|
||||
simpleStore: getPlugin().$$getSimpleStore("p2p-sync"),
|
||||
};
|
||||
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;
|
||||
} catch (e) {
|
||||
this._log(
|
||||
@@ -219,4 +231,10 @@ export class P2PReplicator extends LiveSyncCommands implements IObsidianModule {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
enableBroadcastCastings() {
|
||||
return this?._replicatorInstance?.enableBroadcastChanges();
|
||||
}
|
||||
disableBroadcastCastings() {
|
||||
return this?._replicatorInstance?.disableBroadcastChanges();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
<script lang="ts">
|
||||
import { onMount, setContext } from "svelte";
|
||||
import { AutoAccepting, DEFAULT_SETTINGS, type P2PSyncSetting } from "../../../lib/src/common/types";
|
||||
import {
|
||||
AutoAccepting,
|
||||
DEFAULT_SETTINGS,
|
||||
type ObsidianLiveSyncSettings,
|
||||
type P2PSyncSetting,
|
||||
} from "../../../lib/src/common/types";
|
||||
import { type P2PReplicator } from "../CmdP2PSync";
|
||||
import { AcceptedStatus, ConnectionStatus, type PeerStatus } from "./P2PReplicatorPaneView";
|
||||
AcceptedStatus,
|
||||
ConnectionStatus,
|
||||
type CommandShim,
|
||||
type PeerStatus,
|
||||
type PluginShim,
|
||||
} from "./P2PReplicatorPaneCommon";
|
||||
import PeerStatusRow from "../P2PReplicator/PeerStatusRow.svelte";
|
||||
import { EVENT_LAYOUT_READY, eventHub } from "../../../common/events";
|
||||
import type ObsidianLiveSyncPlugin from "../../../main";
|
||||
import {
|
||||
type PeerInfo,
|
||||
type P2PServerInfo,
|
||||
@@ -22,11 +21,12 @@
|
||||
import { $msg as _msg } from "../../../lib/src/common/i18n";
|
||||
|
||||
interface Props {
|
||||
plugin: ObsidianLiveSyncPlugin;
|
||||
plugin: PluginShim;
|
||||
cmdSync: CommandShim;
|
||||
}
|
||||
|
||||
let { plugin }: Props = $props();
|
||||
const cmdSync = plugin.getAddOn<P2PReplicator>("P2PReplicator")!;
|
||||
let { plugin, cmdSync }: Props = $props();
|
||||
// const cmdSync = plugin.getAddOn<P2PReplicator>("P2PReplicator")!;
|
||||
setContext("getReplicator", () => cmdSync);
|
||||
|
||||
const initialSettings = { ...plugin.settings };
|
||||
@@ -34,6 +34,7 @@
|
||||
let settings = $state<P2PSyncSetting>(initialSettings);
|
||||
// const vaultName = plugin.$$getVaultName();
|
||||
// const dbKey = `${vaultName}-p2p-device-name`;
|
||||
|
||||
const initialDeviceName = cmdSync.getConfig("p2p_device_name") ?? plugin.$$getVaultName();
|
||||
let deviceName = $state<string>(initialDeviceName);
|
||||
|
||||
@@ -100,7 +101,7 @@
|
||||
|
||||
let serverInfo = $state<P2PServerInfo | 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;
|
||||
if (force || !isP2PEnabledModified) eP2PEnabled = d.P2P_Enabled;
|
||||
if (force || !isRelayModified) eRelay = P2P_relays;
|
||||
@@ -221,10 +222,10 @@
|
||||
await cmdSync.close();
|
||||
}
|
||||
function startBroadcasting() {
|
||||
cmdSync._replicatorInstance?.enableBroadcastChanges();
|
||||
void cmdSync.enableBroadcastCastings();
|
||||
}
|
||||
function stopBroadcasting() {
|
||||
cmdSync._replicatorInstance?.disableBroadcastChanges();
|
||||
void cmdSync.disableBroadcastCastings();
|
||||
}
|
||||
|
||||
const initialDialogStatusKey = `p2p-dialog-status`;
|
||||
@@ -283,6 +284,7 @@
|
||||
type="text"
|
||||
placeholder="wss://exp-relay.vrtmrz.net, wss://xxxxx"
|
||||
bind:value={eRelay}
|
||||
autocomplete="off"
|
||||
/>
|
||||
<button onclick={() => useDefaultRelay()}> Use vrtmrz's relay </button>
|
||||
</label>
|
||||
@@ -292,7 +294,7 @@
|
||||
<th> Room ID </th>
|
||||
<td>
|
||||
<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>
|
||||
</label>
|
||||
<span>
|
||||
@@ -318,7 +320,8 @@
|
||||
<th> This device name </th>
|
||||
<td>
|
||||
<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>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
@@ -1,35 +1,33 @@
|
||||
import { WorkspaceLeaf } from "obsidian";
|
||||
import { Menu, WorkspaceLeaf } from "obsidian";
|
||||
import ReplicatorPaneComponent from "./P2PReplicatorPane.svelte";
|
||||
import type ObsidianLiveSyncPlugin from "../../../main.ts";
|
||||
import { mount } from "svelte";
|
||||
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 enum AcceptedStatus {
|
||||
UNKNOWN = "Unknown",
|
||||
ACCEPTED = "Accepted",
|
||||
DENIED = "Denied",
|
||||
ACCEPTED_IN_SESSION = "Accepted in session",
|
||||
DENIED_IN_SESSION = "Denied in session",
|
||||
function addToList(item: string, list: string) {
|
||||
return unique(
|
||||
list
|
||||
.split(",")
|
||||
.map((e) => e.trim())
|
||||
.concat(item)
|
||||
.filter((p) => p)
|
||||
).join(",");
|
||||
}
|
||||
|
||||
export enum ConnectionStatus {
|
||||
CONNECTED = "Connected",
|
||||
CONNECTED_LIVE = "Connected(live)",
|
||||
DISCONNECTED = "Disconnected",
|
||||
function removeFromList(item: string, list: string) {
|
||||
return list
|
||||
.split(",")
|
||||
.map((e) => e.trim())
|
||||
.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 {
|
||||
plugin: ObsidianLiveSyncPlugin;
|
||||
@@ -40,10 +38,130 @@ export class P2PReplicatorPaneView extends SvelteItemView {
|
||||
getIcon(): string {
|
||||
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) {
|
||||
super(leaf);
|
||||
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() {
|
||||
@@ -54,11 +172,22 @@ export class P2PReplicatorPaneView extends SvelteItemView {
|
||||
return "Peer-to-Peer Replicator";
|
||||
}
|
||||
|
||||
override async onClose(): Promise<void> {
|
||||
await super.onClose();
|
||||
if (this.m) {
|
||||
this.m.hide();
|
||||
}
|
||||
}
|
||||
instantiateComponent(target: HTMLElement) {
|
||||
const cmdSync = this.plugin.getAddOn<P2PReplicator>(P2PReplicator.name);
|
||||
if (!cmdSync) {
|
||||
throw new Error("Replicator not found");
|
||||
}
|
||||
return mount(ReplicatorPaneComponent, {
|
||||
target: target,
|
||||
props: {
|
||||
plugin: this.plugin,
|
||||
plugin: cmdSync.plugin,
|
||||
cmdSync: cmdSync,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
<script lang="ts">
|
||||
import { getContext } from "svelte";
|
||||
import { AcceptedStatus, ConnectionStatus, type PeerStatus } from "./P2PReplicatorPaneView";
|
||||
import { Menu, Setting } from "obsidian";
|
||||
import { LOG_LEVEL_INFO, LOG_LEVEL_NOTICE, Logger } from "octagonal-wheels/common/logger";
|
||||
import { AcceptedStatus, type PeerStatus } from "./P2PReplicatorPaneCommon";
|
||||
import type { P2PReplicator } from "../CmdP2PSync";
|
||||
import { unique } from "../../../lib/src/common/utils";
|
||||
import { REMOTE_P2P } from "src/lib/src/common/types";
|
||||
import { eventHub } from "../../../common/events";
|
||||
import { EVENT_P2P_PEER_SHOW_EXTRA_MENU } from "./P2PReplicatorPaneCommon";
|
||||
|
||||
interface Props {
|
||||
peerStatus: PeerStatus;
|
||||
@@ -94,154 +92,13 @@
|
||||
function stopWatching() {
|
||||
replicator.unwatchPeer(peer.peerId);
|
||||
}
|
||||
function replicateFrom() {
|
||||
replicator.replicateFrom(peer.peerId);
|
||||
}
|
||||
function replicateTo() {
|
||||
replicator.requestSynchroniseToPeer(peer.peerId);
|
||||
}
|
||||
|
||||
function sync() {
|
||||
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) {
|
||||
const m = new Menu()
|
||||
.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 });
|
||||
eventHub.emitEvent(EVENT_P2P_PEER_SHOW_EXTRA_MENU, { peer, event: evt });
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
2
src/lib
2
src/lib
Submodule src/lib updated: b8b05a2146...9f71ed12ad
@@ -14,7 +14,7 @@ import { cancelAllPeriodicTask, cancelAllTasks } from "octagonal-wheels/concurre
|
||||
import { stopAllRunningProcessors } from "octagonal-wheels/concurrency/processor";
|
||||
import { AbstractModule } from "../AbstractModule.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 {
|
||||
async $$onLiveSyncReady() {
|
||||
|
||||
17
updates.md
17
updates.md
@@ -1,6 +1,5 @@
|
||||
## 0.24.11
|
||||
|
||||
|
||||
Peer-to-peer synchronisation has been implemented!
|
||||
|
||||
Until now, I have not provided a synchronisation server. More people may not even know that I have shut down the test server. I confess that this is a bit repetitive, but I confess it is a cautionary tale. This is out of a sense of self-discipline that someone has occurred who could see your data. Even if the 'someone' is me. I should not be unaware of its superiority, even though well-meaning and am a servant of all. (Half joking, but also serious).
|
||||
@@ -10,7 +9,21 @@ Also, this signalling server is just a Nostr relay, not my implementation. You c
|
||||
Nevertheless, that being said, to be more honest, I still have not decided what to do with this signalling server if too much traffic comes in.
|
||||
|
||||
Note: Already you have noticed this, but let me mention it again, this is a significantly large update. If you have noticed anything, please let me know. I will try to fix it as soon as possible (Some address is on my [profile](https://github.com/vrtmrz)).
|
||||
---
|
||||
|
||||
## 0.24.12
|
||||
|
||||
I created a SPA called [webpeer](https://github.com/vrtmrz/livesync-commonlib/tree/main/apps/webpeer) (well, right... I will think of a name again), which replaces the server when using Peer-to-Peer synchronisation. This is a pseudo-client that appears to other devices as if it were one of the clients. . As with the client, it receives and sends data without storing it as a file.
|
||||
And, this is just a single web page, without any server-side code. It is a static web page that can be hosted on any static web server, such as GitHub Pages, Netlify, or Vercel. All you have to do is to open the page and enter several items, and leave it open.
|
||||
|
||||
### Fixed
|
||||
|
||||
- No longer unnecessary acknowledgements are sent when starting peer-to-peer synchronisation.
|
||||
|
||||
### Refactored
|
||||
|
||||
- Platform impedance-matching-layer has been improved.
|
||||
- And you can see the actual usage of this on [webpeer](https://github.com/vrtmrz/livesync-commonlib/tree/main/apps/webpeer) that a pseudo client for peer-to-peer synchronisation.
|
||||
- Some UIs have been got isomorphic among Obsidian and web applications (for `webpeer`).
|
||||
|
||||
## 0.24.11
|
||||
|
||||
|
||||
Reference in New Issue
Block a user