From 405624b51bf1d4688aa7c8cbeb9dc0ea846c7bf4 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Mon, 17 Feb 2025 11:33:35 +0000 Subject: [PATCH] ## 0.24.13 ### Fixed #### General Replication - No longer unexpected errors occur when the replication is stopped during for some reason (e.g., network disconnection). #### Peer-to-Peer Synchronisation - Set-up process will not receive data from unexpected sources. - No longer resource leaks while enabling the `broadcasting changes` - Logs are less verbose. - Received data is now correctly dispatched to other devices. - `Timeout` error now more informative. - No longer timeout error occurs for reporting the progress to other devices. - Decision dialogues for the same thing are not shown multiply at the same time anymore. - Disconnection of the peer-to-peer synchronisation is now more robust and less error-prone. #### Webpeer - Now we can toggle Peers' configuration. ### Refactored - Cross-platform compatibility layer has been improved. - Common events are moved to the common library. - Displaying replication status of the peer-to-peer synchronisation is separated from the main-log-logic. - Some file names have been changed to be more consistent. --- src/common/events.ts | 31 +-- src/features/P2PSync/CmdP2PReplicator.ts | 175 +++++++++++++ src/features/P2PSync/CmdP2PSync.ts | 240 ------------------ .../P2PReplicator/P2PReplicatorPane.svelte | 12 +- .../P2PReplicator/P2PReplicatorPaneCommon.ts | 58 ----- .../P2PReplicator/P2PReplicatorPaneView.ts | 8 +- .../P2PReplicator/PeerStatusRow.svelte | 6 +- src/lib | 2 +- src/main.ts | 2 +- src/modules/features/ModuleLog.ts | 94 +------ 10 files changed, 205 insertions(+), 423 deletions(-) create mode 100644 src/features/P2PSync/CmdP2PReplicator.ts delete mode 100644 src/features/P2PSync/CmdP2PSync.ts delete mode 100644 src/features/P2PSync/P2PReplicator/P2PReplicatorPaneCommon.ts diff --git a/src/common/events.ts b/src/common/events.ts index 6610166..a871dab 100644 --- a/src/common/events.ts +++ b/src/common/events.ts @@ -1,19 +1,11 @@ -import type { FilePathWithPrefix, ObsidianLiveSyncSettings } from "../lib/src/common/types"; import { eventHub } from "../lib/src/hub/hub"; import type ObsidianLiveSyncPlugin from "../main"; -export const EVENT_LAYOUT_READY = "layout-ready"; export const EVENT_PLUGIN_LOADED = "plugin-loaded"; export const EVENT_PLUGIN_UNLOADED = "plugin-unloaded"; -export const EVENT_SETTING_SAVED = "setting-saved"; -export const EVENT_FILE_RENAMED = "file-renamed"; export const EVENT_FILE_SAVED = "file-saved"; export const EVENT_LEAF_ACTIVE_CHANGED = "leaf-active-changed"; -export const EVENT_DATABASE_REBUILT = "database-rebuilt"; - -export const EVENT_LOG_ADDED = "log-added"; - export const EVENT_REQUEST_OPEN_SETTINGS = "request-open-settings"; export const EVENT_REQUEST_OPEN_SETTING_WIZARD = "request-open-setting-wizard"; export const EVENT_REQUEST_OPEN_SETUP_URI = "request-open-setup-uri"; @@ -30,28 +22,19 @@ export const EVENT_REQUEST_CLOSE_P2P = "request-close-p2p"; declare global { interface LSEvents { - [EVENT_REQUEST_OPEN_PLUGIN_SYNC_DIALOG]: undefined; - [EVENT_FILE_SAVED]: undefined; - [EVENT_REQUEST_OPEN_SETUP_URI]: undefined; - [EVENT_REQUEST_COPY_SETUP_URI]: undefined; - [EVENT_REQUEST_RELOAD_SETTING_TAB]: undefined; - [EVENT_PLUGIN_UNLOADED]: undefined; - [EVENT_SETTING_SAVED]: ObsidianLiveSyncSettings; [EVENT_PLUGIN_LOADED]: ObsidianLiveSyncPlugin; - [EVENT_LAYOUT_READY]: undefined; - "event-file-changed": { file: FilePathWithPrefix; automated: boolean }; - "document-stub-created": { - toc: Set; - stub: { [key: string]: { [key: string]: Map> } }; - }; + [EVENT_PLUGIN_UNLOADED]: undefined; + [EVENT_REQUEST_OPEN_PLUGIN_SYNC_DIALOG]: undefined; [EVENT_REQUEST_OPEN_SETTINGS]: undefined; [EVENT_REQUEST_OPEN_SETTING_WIZARD]: undefined; - [EVENT_FILE_RENAMED]: { newPath: FilePathWithPrefix; old: FilePathWithPrefix }; + [EVENT_REQUEST_RELOAD_SETTING_TAB]: undefined; [EVENT_LEAF_ACTIVE_CHANGED]: undefined; - [EVENT_REQUEST_OPEN_P2P]: undefined; [EVENT_REQUEST_CLOSE_P2P]: undefined; - [EVENT_DATABASE_REBUILT]: undefined; + [EVENT_REQUEST_OPEN_P2P]: undefined; + [EVENT_REQUEST_OPEN_SETUP_URI]: undefined; + [EVENT_REQUEST_COPY_SETUP_URI]: undefined; } } +export * from "../lib/src/events/coreEvents.ts"; export { eventHub }; diff --git a/src/features/P2PSync/CmdP2PReplicator.ts b/src/features/P2PSync/CmdP2PReplicator.ts new file mode 100644 index 0000000..22bd45e --- /dev/null +++ b/src/features/P2PSync/CmdP2PReplicator.ts @@ -0,0 +1,175 @@ +import type { IObsidianModule } from "../../modules/AbstractObsidianModule"; +import { P2PReplicatorPaneView, VIEW_TYPE_P2P } from "./P2PReplicator/P2PReplicatorPaneView.ts"; +import { + AutoAccepting, + LOG_LEVEL_NOTICE, + REMOTE_P2P, + type EntryDoc, + type P2PSyncSetting, + type RemoteDBSettings, +} from "../../lib/src/common/types.ts"; +import { LiveSyncCommands } from "../LiveSyncCommands.ts"; +import { LiveSyncTrysteroReplicator } 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 { Logger } from "octagonal-wheels/common/logger"; +import type { CommandShim } from "../../lib/src/replication/trystero/P2PReplicatorPaneCommon.ts"; +import { + P2PReplicatorMixIn, + 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"; + +class P2PReplicatorCommandBase extends LiveSyncCommands implements P2PReplicatorBase { + storeP2PStatusLine = reactiveSource(""); + + getSettings(): P2PSyncSetting { + return this.plugin.settings; + } + get settings() { + return this.plugin.settings; + } + getDB() { + return this.plugin.localDatabase.localDatabase; + } + + get confirm(): Confirm { + return this.plugin.confirm; + } + _simpleStore!: SimpleStore; + + simpleStore(): SimpleStore { + return this._simpleStore; + } + + constructor(plugin: ObsidianLiveSyncPlugin) { + super(plugin); + } + + async handleReplicatedDocuments(docs: EntryDoc[]): Promise { + // console.log("Processing Replicated Docs", docs); + return await this.plugin.$$parseReplicationResult(docs as PouchDB.Core.ExistingDocument[]); + } + onunload(): void { + throw new Error("Method not implemented."); + } + onload(): void | Promise { + throw new Error("Method not implemented."); + } + + init() { + this._simpleStore = this.plugin.$$getSimpleStore("p2p-sync"); + return Promise.resolve(this); + } +} + +export class P2PReplicator + extends P2PReplicatorMixIn(P2PReplicatorCommandBase) + implements IObsidianModule, CommandShim +{ + storeP2PStatusLine = reactiveSource(""); + $anyNewReplicator(settingOverride: Partial = {}): Promise { + const settings = { ...this.settings, ...settingOverride }; + if (settings.remoteType == REMOTE_P2P) { + return Promise.resolve(new LiveSyncTrysteroReplicator(this.plugin)); + } + return undefined!; + } + + override onunload(): void { + removeP2PReplicatorInstance(); + void this.close(); + } + + override onload(): void | Promise { + eventHub.onEvent(EVENT_REQUEST_OPEN_P2P, () => { + void this.openPane(); + }); + this.p2pLogCollector.p2pReplicationLine.onChanged((line) => { + this.storeP2PStatusLine.value = line.value; + }); + } + async $everyOnInitializeDatabase(): Promise { + await this.initialiseP2PReplicator(); + return Promise.resolve(true); + } + + async $allSuspendExtraSync() { + this.plugin.settings.P2P_Enabled = false; + this.plugin.settings.P2P_AutoAccepting = AutoAccepting.NONE; + this.plugin.settings.P2P_AutoBroadcast = false; + this.plugin.settings.P2P_AutoStart = false; + this.plugin.settings.P2P_AutoSyncPeers = ""; + this.plugin.settings.P2P_AutoWatchPeers = ""; + return await Promise.resolve(true); + } + + async $everyOnLoadStart() { + return await Promise.resolve(); + } + + async openPane() { + await this.plugin.$$showView(VIEW_TYPE_P2P); + } + + async $everyOnloadStart(): Promise { + this.plugin.registerView(VIEW_TYPE_P2P, (leaf) => new P2PReplicatorPaneView(leaf, 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 { + if (this.settings.P2P_Enabled && this.settings.P2P_AutoStart) { + setTimeout(() => void this.open(), 100); + } + return Promise.resolve(true); + } +} diff --git a/src/features/P2PSync/CmdP2PSync.ts b/src/features/P2PSync/CmdP2PSync.ts deleted file mode 100644 index 78b2ccf..0000000 --- a/src/features/P2PSync/CmdP2PSync.ts +++ /dev/null @@ -1,240 +0,0 @@ -import type { IObsidianModule } from "../../modules/AbstractObsidianModule"; -import { P2PReplicatorPaneView, VIEW_TYPE_P2P } from "./P2PReplicator/P2PReplicatorPaneView.ts"; -import { TrysteroReplicator } from "../../lib/src/replication/trystero/TrysteroReplicator.ts"; -import { - AutoAccepting, - DEFAULT_SETTINGS, - LOG_LEVEL_INFO, - LOG_LEVEL_NOTICE, - LOG_LEVEL_VERBOSE, - REMOTE_P2P, - type EntryDoc, - 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_DATABASE_REBUILT, - EVENT_PLUGIN_UNLOADED, - EVENT_REQUEST_OPEN_P2P, - EVENT_SETTING_SAVED, - eventHub, -} from "../../common/events.ts"; -import { - EVENT_ADVERTISEMENT_RECEIVED, - EVENT_DEVICE_LEAVED, - EVENT_P2P_REQUEST_FORCE_OPEN, - EVENT_REQUEST_STATUS, -} from "../../lib/src/replication/trystero/TrysteroReplicatorP2PServer.ts"; -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, CommandShim { - $anyNewReplicator(settingOverride: Partial = {}): Promise { - const settings = { ...this.settings, ...settingOverride }; - if (settings.remoteType == REMOTE_P2P) { - return Promise.resolve(new LiveSyncTrysteroReplicator(this.plugin)); - } - return undefined!; - } - - _replicatorInstance?: TrysteroReplicator; - onunload(): void { - setReplicatorFunc(() => undefined); - void this.close(); - } - - onload(): void | Promise { - setReplicatorFunc(() => this._replicatorInstance); - eventHub.onEvent(EVENT_ADVERTISEMENT_RECEIVED, (peerId) => this._replicatorInstance?.onNewPeer(peerId)); - eventHub.onEvent(EVENT_DEVICE_LEAVED, (info) => this._replicatorInstance?.onPeerLeaved(info)); - eventHub.onEvent(EVENT_REQUEST_STATUS, () => { - this._replicatorInstance?.requestStatus(); - }); - eventHub.onEvent(EVENT_P2P_REQUEST_FORCE_OPEN, () => { - void this.open(); - }); - eventHub.onEvent(EVENT_REQUEST_OPEN_P2P, () => { - void this.openPane(); - }); - eventHub.onEvent(EVENT_DATABASE_REBUILT, async () => { - await this.initialiseP2PReplicator(); - }); - 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 { - await this.initialiseP2PReplicator(); - return Promise.resolve(true); - } - - async $allSuspendExtraSync() { - this.plugin.settings.P2P_Enabled = false; - this.plugin.settings.P2P_AutoAccepting = AutoAccepting.NONE; - this.plugin.settings.P2P_AutoBroadcast = false; - this.plugin.settings.P2P_AutoStart = false; - this.plugin.settings.P2P_AutoSyncPeers = ""; - this.plugin.settings.P2P_AutoWatchPeers = ""; - return await Promise.resolve(true); - } - async $everyOnLoadStart() { - return await Promise.resolve(); - } - - async openPane() { - await this.plugin.$$showView(VIEW_TYPE_P2P); - } - - async $everyOnloadStart(): Promise { - this.plugin.registerView(VIEW_TYPE_P2P, (leaf) => new P2PReplicatorPaneView(leaf, 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 { - if (this.settings.P2P_Enabled && this.settings.P2P_AutoStart) { - setTimeout(() => void this.open(), 100); - } - return Promise.resolve(true); - } - async open() { - if (!this.settings.P2P_Enabled) { - this._notice($msg("P2P.NotEnabled")); - return; - } - - 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(); - } - } - async close() { - await this._replicatorInstance?.close(); - this._replicatorInstance = undefined; - } - getConfig(key: string) { - const vaultName = this.plugin.$$getVaultName(); - const dbKey = `${vaultName}-${key}`; - return localStorage.getItem(dbKey); - } - setConfig(key: string, value: string) { - const vaultName = this.plugin.$$getVaultName(); - const dbKey = `${vaultName}-${key}`; - localStorage.setItem(dbKey, value); - } - - async initialiseP2PReplicator(): Promise { - const getPlugin = () => this.plugin; - try { - // const plugin = this.plugin; - if (this._replicatorInstance) { - await this._replicatorInstance.close(); - this._replicatorInstance = undefined; - } - - if (!this.settings.P2P_AppID) { - this.settings.P2P_AppID = DEFAULT_SETTINGS.P2P_AppID; - } - - const initialDeviceName = this.getConfig("p2p_device_name") || this.plugin.$$getDeviceAndVaultName(); - const env = { - get db() { - return getPlugin().localDatabase.localDatabase; - }, - get confirm() { - return getPlugin().confirm; - }, - get deviceName() { - return initialDeviceName; - }, - platform: "wip", - get settings() { - return getPlugin().settings; - }, - async processReplicatedDocs(docs: EntryDoc[]): Promise { - return await getPlugin().$$parseReplicationResult( - docs as PouchDB.Core.ExistingDocument[] - ); - }, - simpleStore: getPlugin().$$getSimpleStore("p2p-sync"), - }; - this._replicatorInstance = new TrysteroReplicator(env); - if (this.settings.P2P_AutoStart && this.settings.P2P_Enabled) { - await this.open(); - } - 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; - } - } - enableBroadcastCastings() { - return this?._replicatorInstance?.enableBroadcastChanges(); - } - disableBroadcastCastings() { - return this?._replicatorInstance?.disableBroadcastChanges(); - } -} diff --git a/src/features/P2PSync/P2PReplicator/P2PReplicatorPane.svelte b/src/features/P2PSync/P2PReplicator/P2PReplicatorPane.svelte index 26b00c4..27aa541 100644 --- a/src/features/P2PSync/P2PReplicator/P2PReplicatorPane.svelte +++ b/src/features/P2PSync/P2PReplicator/P2PReplicatorPane.svelte @@ -7,7 +7,7 @@ type CommandShim, type PeerStatus, type PluginShim, - } from "./P2PReplicatorPaneCommon"; + } from "../../../lib/src/replication/trystero/P2PReplicatorPaneCommon"; import PeerStatusRow from "../P2PReplicator/PeerStatusRow.svelte"; import { EVENT_LAYOUT_READY, eventHub } from "../../../common/events"; import { @@ -294,7 +294,12 @@ Room ID @@ -320,8 +325,7 @@ This device name diff --git a/src/features/P2PSync/P2PReplicator/P2PReplicatorPaneCommon.ts b/src/features/P2PSync/P2PReplicator/P2PReplicatorPaneCommon.ts deleted file mode 100644 index bf2a330..0000000 --- a/src/features/P2PSync/P2PReplicator/P2PReplicatorPaneCommon.ts +++ /dev/null @@ -1,58 +0,0 @@ -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; - 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; - close(): Promise; - enableBroadcastCastings(): void; // cmdSync._replicatorInstance?.enableBroadcastChanges(); - disableBroadcastCastings(): void; ///cmdSync._replicatorInstance?.disableBroadcastChanges(); -} diff --git a/src/features/P2PSync/P2PReplicator/P2PReplicatorPaneView.ts b/src/features/P2PSync/P2PReplicator/P2PReplicatorPaneView.ts index eb5f74a..e4e5fe6 100644 --- a/src/features/P2PSync/P2PReplicator/P2PReplicatorPaneView.ts +++ b/src/features/P2PSync/P2PReplicator/P2PReplicatorPaneView.ts @@ -4,11 +4,15 @@ 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"; +import { P2PReplicator } from "../CmdP2PReplicator.ts"; +import { + EVENT_P2P_PEER_SHOW_EXTRA_MENU, + type PeerStatus, +} from "../../../lib/src/replication/trystero/P2PReplicatorPaneCommon.ts"; export const VIEW_TYPE_P2P = "p2p-replicator"; function addToList(item: string, list: string) { diff --git a/src/features/P2PSync/P2PReplicator/PeerStatusRow.svelte b/src/features/P2PSync/P2PReplicator/PeerStatusRow.svelte index fa0e162..2ee660a 100644 --- a/src/features/P2PSync/P2PReplicator/PeerStatusRow.svelte +++ b/src/features/P2PSync/P2PReplicator/PeerStatusRow.svelte @@ -1,9 +1,9 @@