mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2026-04-01 14:45:18 +00:00
Now, Self-hosted LiveSync has finally begun to be split into the Self-hosted LiveSync plugin for Obsidian, and a properly abstracted version of it. This may not offer much benefit to Obsidian plugin users, or might even cause a slight inconvenience, but I believe it will certainly help improve testability and make the ecosystem better. However, I do not see the point in putting something with little benefit into beta, so I am handling this on the alpha branch. I would actually preferred to create an R&D branch, but I was not keen on the ampersand, and I feel it will eventually become a proper beta anyway. ### Refactored - Separated `ObsidianLiveSyncPlugin` into `ObsidianLiveSyncPlugin` and `LiveSyncBaseCore`. - Now `LiveSyncCore` indicates the type specified version of `LiveSyncBaseCore`. - Referencing `plugin.xxx` has been rewritten to referencing the corresponding service or `core.xxx`. ### Internal API changes - Storage Access APIs are now yielding Promises. This is to allow more limited storage platforms to be supported. ### R&D - Browser-version of Self-hosted LiveSync is now in development. This is not intended for public use now, but I will eventually make it available for testing. - We can see the code in `src/apps/webapp` for the browser version.
279 lines
10 KiB
TypeScript
279 lines
10 KiB
TypeScript
import { P2PReplicatorPaneView, VIEW_TYPE_P2P } from "./P2PReplicator/P2PReplicatorPaneView.ts";
|
|
import {
|
|
AutoAccepting,
|
|
LOG_LEVEL_NOTICE,
|
|
P2P_DEFAULT_SETTINGS,
|
|
REMOTE_P2P,
|
|
type EntryDoc,
|
|
type P2PSyncSetting,
|
|
type RemoteDBSettings,
|
|
} from "../../lib/src/common/types.ts";
|
|
import { LiveSyncCommands } from "../LiveSyncCommands.ts";
|
|
import {
|
|
LiveSyncTrysteroReplicator,
|
|
setReplicatorFunc,
|
|
} from "../../lib/src/replication/trystero/LiveSyncTrysteroReplicator.ts";
|
|
import { EVENT_REQUEST_OPEN_P2P, eventHub } from "../../common/events.ts";
|
|
import type { LiveSyncAbstractReplicator } from "../../lib/src/replication/LiveSyncAbstractReplicator.ts";
|
|
import { LOG_LEVEL_INFO, LOG_LEVEL_VERBOSE, Logger } from "octagonal-wheels/common/logger";
|
|
import type { CommandShim } from "../../lib/src/replication/trystero/P2PReplicatorPaneCommon.ts";
|
|
import {
|
|
addP2PEventHandlers,
|
|
closeP2PReplicator,
|
|
openP2PReplicator,
|
|
P2PLogCollector,
|
|
removeP2PReplicatorInstance,
|
|
type P2PReplicatorBase,
|
|
} from "../../lib/src/replication/trystero/P2PReplicatorCore.ts";
|
|
import { reactiveSource } from "octagonal-wheels/dataobject/reactive_v2";
|
|
import type { Confirm } from "../../lib/src/interfaces/Confirm.ts";
|
|
import type ObsidianLiveSyncPlugin from "../../main.ts";
|
|
import type { SimpleStore } from "octagonal-wheels/databases/SimpleStoreBase";
|
|
// import { getPlatformName } from "../../lib/src/PlatformAPIs/obsidian/Environment.ts";
|
|
import type { LiveSyncCore } from "../../main.ts";
|
|
import { TrysteroReplicator } from "../../lib/src/replication/trystero/TrysteroReplicator.ts";
|
|
import { SETTING_KEY_P2P_DEVICE_NAME } from "../../lib/src/common/types.ts";
|
|
|
|
export class P2PReplicator extends LiveSyncCommands implements P2PReplicatorBase, CommandShim {
|
|
storeP2PStatusLine = reactiveSource("");
|
|
|
|
getSettings(): P2PSyncSetting {
|
|
return this.core.settings;
|
|
}
|
|
getDB() {
|
|
return this.core.localDatabase.localDatabase;
|
|
}
|
|
|
|
get confirm(): Confirm {
|
|
return this.core.confirm;
|
|
}
|
|
_simpleStore!: SimpleStore<any>;
|
|
|
|
simpleStore(): SimpleStore<any> {
|
|
return this._simpleStore;
|
|
}
|
|
|
|
constructor(plugin: ObsidianLiveSyncPlugin, core: LiveSyncCore) {
|
|
super(plugin, core);
|
|
setReplicatorFunc(() => this._replicatorInstance);
|
|
addP2PEventHandlers(this);
|
|
this.afterConstructor();
|
|
// onBindFunction is called in super class
|
|
// this.onBindFunction(plugin, plugin.services);
|
|
}
|
|
|
|
async handleReplicatedDocuments(docs: EntryDoc[]): Promise<boolean> {
|
|
// console.log("Processing Replicated Docs", docs);
|
|
return await this.services.replication.parseSynchroniseResult(
|
|
docs as PouchDB.Core.ExistingDocument<EntryDoc>[]
|
|
);
|
|
}
|
|
|
|
_anyNewReplicator(settingOverride: Partial<RemoteDBSettings> = {}): Promise<LiveSyncAbstractReplicator> {
|
|
const settings = { ...this.settings, ...settingOverride };
|
|
if (settings.remoteType == REMOTE_P2P) {
|
|
return Promise.resolve(new LiveSyncTrysteroReplicator(this.plugin.core));
|
|
}
|
|
return undefined!;
|
|
}
|
|
_replicatorInstance?: TrysteroReplicator;
|
|
p2pLogCollector = new P2PLogCollector();
|
|
|
|
afterConstructor() {
|
|
return;
|
|
}
|
|
|
|
async open() {
|
|
await openP2PReplicator(this);
|
|
}
|
|
async close() {
|
|
await closeP2PReplicator(this);
|
|
}
|
|
|
|
getConfig(key: string) {
|
|
return this.services.config.getSmallConfig(key);
|
|
}
|
|
setConfig(key: string, value: string) {
|
|
return this.services.config.setSmallConfig(key, value);
|
|
}
|
|
enableBroadcastCastings() {
|
|
return this?._replicatorInstance?.enableBroadcastChanges();
|
|
}
|
|
disableBroadcastCastings() {
|
|
return this?._replicatorInstance?.disableBroadcastChanges();
|
|
}
|
|
|
|
init() {
|
|
this._simpleStore = this.services.keyValueDB.openSimpleStore("p2p-sync");
|
|
return Promise.resolve(this);
|
|
}
|
|
|
|
async initialiseP2PReplicator(): Promise<TrysteroReplicator> {
|
|
await this.init();
|
|
try {
|
|
if (this._replicatorInstance) {
|
|
await this._replicatorInstance.close();
|
|
this._replicatorInstance = undefined;
|
|
}
|
|
|
|
if (!this.settings.P2P_AppID) {
|
|
this.settings.P2P_AppID = P2P_DEFAULT_SETTINGS.P2P_AppID;
|
|
}
|
|
const getInitialDeviceName = () =>
|
|
this.getConfig(SETTING_KEY_P2P_DEVICE_NAME) || this.services.vault.getVaultName();
|
|
|
|
const getSettings = () => this.settings;
|
|
const store = () => this.simpleStore();
|
|
const getDB = () => this.getDB();
|
|
|
|
const getConfirm = () => this.confirm;
|
|
const getPlatform = () => this.services.API.getPlatform();
|
|
const env = {
|
|
get db() {
|
|
return getDB();
|
|
},
|
|
get confirm() {
|
|
return getConfirm();
|
|
},
|
|
get deviceName() {
|
|
return getInitialDeviceName();
|
|
},
|
|
get platform() {
|
|
return getPlatform();
|
|
},
|
|
get settings() {
|
|
return getSettings();
|
|
},
|
|
processReplicatedDocs: async (docs: EntryDoc[]): Promise<void> => {
|
|
await this.handleReplicatedDocuments(docs);
|
|
// No op. This is a client and does not need to process the docs
|
|
},
|
|
get simpleStore() {
|
|
return store();
|
|
},
|
|
};
|
|
this._replicatorInstance = new TrysteroReplicator(env);
|
|
return this._replicatorInstance;
|
|
} catch (e) {
|
|
this._log(
|
|
e instanceof Error ? e.message : "Something occurred on Initialising P2P Replicator",
|
|
LOG_LEVEL_INFO
|
|
);
|
|
this._log(e, LOG_LEVEL_VERBOSE);
|
|
throw e;
|
|
}
|
|
}
|
|
|
|
onunload(): void {
|
|
removeP2PReplicatorInstance();
|
|
void this.close();
|
|
}
|
|
|
|
onload(): void | Promise<void> {
|
|
eventHub.onEvent(EVENT_REQUEST_OPEN_P2P, () => {
|
|
void this.openPane();
|
|
});
|
|
this.p2pLogCollector.p2pReplicationLine.onChanged((line) => {
|
|
this.storeP2PStatusLine.value = line.value;
|
|
});
|
|
}
|
|
async _everyOnInitializeDatabase(): Promise<boolean> {
|
|
await this.initialiseP2PReplicator();
|
|
return Promise.resolve(true);
|
|
}
|
|
|
|
private async _allSuspendExtraSync() {
|
|
this.plugin.core.settings.P2P_Enabled = false;
|
|
this.plugin.core.settings.P2P_AutoAccepting = AutoAccepting.NONE;
|
|
this.plugin.core.settings.P2P_AutoBroadcast = false;
|
|
this.plugin.core.settings.P2P_AutoStart = false;
|
|
this.plugin.core.settings.P2P_AutoSyncPeers = "";
|
|
this.plugin.core.settings.P2P_AutoWatchPeers = "";
|
|
return await Promise.resolve(true);
|
|
}
|
|
|
|
// async $everyOnLoadStart() {
|
|
// return await Promise.resolve();
|
|
// }
|
|
|
|
async openPane() {
|
|
await this.services.API.showWindow(VIEW_TYPE_P2P);
|
|
}
|
|
|
|
async _everyOnloadStart(): Promise<boolean> {
|
|
this.plugin.registerView(
|
|
VIEW_TYPE_P2P,
|
|
(leaf) => new P2PReplicatorPaneView(leaf, this.plugin.core, this.plugin)
|
|
);
|
|
this.plugin.addCommand({
|
|
id: "open-p2p-replicator",
|
|
name: "P2P Sync : Open P2P Replicator",
|
|
callback: async () => {
|
|
await this.openPane();
|
|
},
|
|
});
|
|
this.plugin.addCommand({
|
|
id: "p2p-establish-connection",
|
|
name: "P2P Sync : Connect to the Signalling Server",
|
|
checkCallback: (isChecking) => {
|
|
if (isChecking) {
|
|
return !(this._replicatorInstance?.server?.isServing ?? false);
|
|
}
|
|
void this.open();
|
|
},
|
|
});
|
|
this.plugin.addCommand({
|
|
id: "p2p-close-connection",
|
|
name: "P2P Sync : Disconnect from the Signalling Server",
|
|
checkCallback: (isChecking) => {
|
|
if (isChecking) {
|
|
return this._replicatorInstance?.server?.isServing ?? false;
|
|
}
|
|
Logger(`Closing P2P Connection`, LOG_LEVEL_NOTICE);
|
|
void this.close();
|
|
},
|
|
});
|
|
this.plugin.addCommand({
|
|
id: "replicate-now-by-p2p",
|
|
name: "Replicate now by P2P",
|
|
checkCallback: (isChecking) => {
|
|
if (isChecking) {
|
|
if (this.settings.remoteType == REMOTE_P2P) return false;
|
|
if (!this._replicatorInstance?.server?.isServing) return false;
|
|
return true;
|
|
}
|
|
void this._replicatorInstance?.replicateFromCommand(false);
|
|
},
|
|
});
|
|
this.plugin
|
|
.addRibbonIcon("waypoints", "P2P Replicator", async () => {
|
|
await this.openPane();
|
|
})
|
|
.addClass("livesync-ribbon-replicate-p2p");
|
|
|
|
return await Promise.resolve(true);
|
|
}
|
|
_everyAfterResumeProcess(): Promise<boolean> {
|
|
if (this.settings.P2P_Enabled && this.settings.P2P_AutoStart) {
|
|
setTimeout(() => void this.open(), 100);
|
|
}
|
|
const rep = this._replicatorInstance;
|
|
rep?.allowReconnection();
|
|
return Promise.resolve(true);
|
|
}
|
|
_everyBeforeSuspendProcess(): Promise<boolean> {
|
|
const rep = this._replicatorInstance;
|
|
rep?.disconnectFromServer();
|
|
return Promise.resolve(true);
|
|
}
|
|
|
|
override onBindFunction(core: LiveSyncCore, services: typeof core.services): void {
|
|
services.replicator.getNewReplicator.addHandler(this._anyNewReplicator.bind(this));
|
|
services.databaseEvents.onDatabaseInitialisation.addHandler(this._everyOnInitializeDatabase.bind(this));
|
|
services.appLifecycle.onInitialise.addHandler(this._everyOnloadStart.bind(this));
|
|
services.appLifecycle.onSuspending.addHandler(this._everyBeforeSuspendProcess.bind(this));
|
|
services.appLifecycle.onResumed.addHandler(this._everyAfterResumeProcess.bind(this));
|
|
services.setting.suspendExtraSync.addHandler(this._allSuspendExtraSync.bind(this));
|
|
}
|
|
}
|