mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2026-10-19 15:52:31 +00:00
Adopt focused P2P service views
This commit is contained in:
@@ -37,6 +37,7 @@ import { usePrepareDatabaseForUse } from "@vrtmrz/livesync-commonlib/compat/serv
|
|||||||
import type { Constructor } from "@vrtmrz/livesync-commonlib/compat/common/utils.type";
|
import type { Constructor } from "@vrtmrz/livesync-commonlib/compat/common/utils.type";
|
||||||
import {
|
import {
|
||||||
CAPABILITY_NOT_APPLICABLE,
|
CAPABILITY_NOT_APPLICABLE,
|
||||||
|
CENTRAL_REMOTE_REPLICATION_READINESS,
|
||||||
defineReplicatorProviderDefinitions,
|
defineReplicatorProviderDefinitions,
|
||||||
supportedOpenReplicationContinuous,
|
supportedOpenReplicationContinuous,
|
||||||
supportedOpenReplicationOneShot,
|
supportedOpenReplicationOneShot,
|
||||||
@@ -158,6 +159,7 @@ export class LiveSyncBaseCore<
|
|||||||
[REMOTE_COUCHDB]: {
|
[REMOTE_COUCHDB]: {
|
||||||
kind: REMOTE_COUCHDB,
|
kind: REMOTE_COUCHDB,
|
||||||
diagnosticName: "CouchDB",
|
diagnosticName: "CouchDB",
|
||||||
|
readiness: CENTRAL_REMOTE_REPLICATION_READINESS,
|
||||||
isConfigured: (settings) =>
|
isConfigured: (settings) =>
|
||||||
settings.remoteType === REMOTE_COUCHDB &&
|
settings.remoteType === REMOTE_COUCHDB &&
|
||||||
!!settings.couchDB_URI?.trim() &&
|
!!settings.couchDB_URI?.trim() &&
|
||||||
@@ -171,6 +173,7 @@ export class LiveSyncBaseCore<
|
|||||||
[REMOTE_MINIO]: {
|
[REMOTE_MINIO]: {
|
||||||
kind: REMOTE_MINIO,
|
kind: REMOTE_MINIO,
|
||||||
diagnosticName: "Object Storage",
|
diagnosticName: "Object Storage",
|
||||||
|
readiness: CENTRAL_REMOTE_REPLICATION_READINESS,
|
||||||
isConfigured: (settings) =>
|
isConfigured: (settings) =>
|
||||||
settings.remoteType === REMOTE_MINIO && !!settings.endpoint?.trim() && !!settings.bucket?.trim(),
|
settings.remoteType === REMOTE_MINIO && !!settings.endpoint?.trim() && !!settings.bucket?.trim(),
|
||||||
create: (_settings) => Promise.resolve(new LiveSyncJournalReplicator(this)),
|
create: (_settings) => Promise.resolve(new LiveSyncJournalReplicator(this)),
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ export class P2PCheckSession {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
await runtime.start();
|
await runtime.start();
|
||||||
await runtime.currentReplicator.makeSureOpened();
|
await runtime.p2p.transportLifecycle.connect();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
await this.stop();
|
await this.stop();
|
||||||
throw error;
|
throw error;
|
||||||
|
|||||||
@@ -3,10 +3,9 @@ import { compatGlobal } from "@vrtmrz/livesync-commonlib/compat/common/coreEnvFu
|
|||||||
import { EVENT_LAYOUT_READY } from "@vrtmrz/livesync-commonlib/compat/events/coreEvents";
|
import { EVENT_LAYOUT_READY } from "@vrtmrz/livesync-commonlib/compat/events/coreEvents";
|
||||||
import type { PeerStatus } from "@vrtmrz/livesync-commonlib/compat/replication/trystero/P2PReplicatorPaneCommon";
|
import type { PeerStatus } from "@vrtmrz/livesync-commonlib/compat/replication/trystero/P2PReplicatorPaneCommon";
|
||||||
import { P2PLogCollector } from "@vrtmrz/livesync-commonlib/compat/replication/trystero/P2PLogCollector";
|
import { P2PLogCollector } from "@vrtmrz/livesync-commonlib/compat/replication/trystero/P2PLogCollector";
|
||||||
import type { LiveSyncTrysteroReplicator } from "@vrtmrz/livesync-commonlib/compat/replication/trystero/LiveSyncTrysteroReplicator";
|
|
||||||
import type { UseP2PReplicatorResult } from "@vrtmrz/livesync-commonlib/compat/replication/trystero/UseP2PReplicatorResult";
|
|
||||||
import { useP2PReplicatorFeature } from "@vrtmrz/livesync-commonlib/compat/replication/trystero/useP2PReplicatorFeature";
|
import { useP2PReplicatorFeature } from "@vrtmrz/livesync-commonlib/compat/replication/trystero/useP2PReplicatorFeature";
|
||||||
import { ServiceContext, type LiveSyncEventHub } from "@vrtmrz/livesync-commonlib/context";
|
import { ServiceContext, type LiveSyncEventHub } from "@vrtmrz/livesync-commonlib/context";
|
||||||
|
import type { P2PServiceViews } from "@vrtmrz/livesync-commonlib/p2p";
|
||||||
import { unique } from "octagonal-wheels/collection";
|
import { unique } from "octagonal-wheels/collection";
|
||||||
import type { SimpleStore } from "octagonal-wheels/databases/SimpleStoreBase";
|
import type { SimpleStore } from "octagonal-wheels/databases/SimpleStoreBase";
|
||||||
|
|
||||||
@@ -48,7 +47,7 @@ function removeFromList(item: string, list: string): string {
|
|||||||
export class WebPeerRuntime {
|
export class WebPeerRuntime {
|
||||||
readonly context: ServiceContext;
|
readonly context: ServiceContext;
|
||||||
readonly services: LiveSyncBrowserServiceHub<ServiceContext>;
|
readonly services: LiveSyncBrowserServiceHub<ServiceContext>;
|
||||||
readonly p2p: UseP2PReplicatorResult;
|
readonly p2p: P2PServiceViews;
|
||||||
readonly p2pLogCollector: P2PLogCollector;
|
readonly p2pLogCollector: P2PLogCollector;
|
||||||
readonly paneHost: P2PReplicatorPaneHost;
|
readonly paneHost: P2PReplicatorPaneHost;
|
||||||
|
|
||||||
@@ -87,10 +86,6 @@ export class WebPeerRuntime {
|
|||||||
return this.context.events;
|
return this.context.events;
|
||||||
}
|
}
|
||||||
|
|
||||||
get currentReplicator(): LiveSyncTrysteroReplicator {
|
|
||||||
return this.p2p.replicator;
|
|
||||||
}
|
|
||||||
|
|
||||||
get settings(): P2PSyncSetting {
|
get settings(): P2PSyncSetting {
|
||||||
return this.services.setting.currentSettings();
|
return this.services.setting.currentSettings();
|
||||||
}
|
}
|
||||||
@@ -119,9 +114,7 @@ export class WebPeerRuntime {
|
|||||||
}
|
}
|
||||||
this.services.appLifecycle.markIsReady();
|
this.services.appLifecycle.markIsReady();
|
||||||
this.events.emitEvent(EVENT_LAYOUT_READY);
|
this.events.emitEvent(EVENT_LAYOUT_READY);
|
||||||
if (this.settings.P2P_AutoStart && this.settings.P2P_Enabled) {
|
await this.services.appLifecycle.onResumed();
|
||||||
compatGlobal.setTimeout(() => void this.currentReplicator.open(), 100);
|
|
||||||
}
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -151,12 +144,12 @@ export class WebPeerRuntime {
|
|||||||
this.menu = new Menu()
|
this.menu = new Menu()
|
||||||
.addItem((item) =>
|
.addItem((item) =>
|
||||||
item.setTitle("📥 Only fetch").onClick(async () => {
|
item.setTitle("📥 Only fetch").onClick(async () => {
|
||||||
await this.currentReplicator.replicateFrom(peer.peerId);
|
await this.p2p.targetedTransfer.pullFromPeer(peer.peerId);
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
.addItem((item) =>
|
.addItem((item) =>
|
||||||
item.setTitle("📤 Only send").onClick(async () => {
|
item.setTitle("📤 Only send").onClick(async () => {
|
||||||
await this.currentReplicator.requestSynchroniseToPeer(peer.peerId);
|
await this.p2p.targetedTransfer.requestPushToPeer(peer.peerId);
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
.addSeparator()
|
.addSeparator()
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { App, Modal } from "@/deps.ts";
|
import { App, Modal } from "@/deps.ts";
|
||||||
import P2POpenReplicationPane from "./P2POpenReplicationPane.svelte";
|
import P2POpenReplicationPane from "./P2POpenReplicationPane.svelte";
|
||||||
import { mount, unmount } from "svelte";
|
import { mount, unmount } from "svelte";
|
||||||
import type { LiveSyncTrysteroReplicator } from "@vrtmrz/livesync-commonlib/compat/replication/trystero/LiveSyncTrysteroReplicator";
|
import type { P2PServiceViews } from "@vrtmrz/livesync-commonlib/p2p";
|
||||||
|
|
||||||
export type P2POpenReplicationModalCallback = {
|
export type P2POpenReplicationModalCallback = {
|
||||||
onSync: (peerId: string) => Promise<void>;
|
onSync: (peerId: string) => Promise<void>;
|
||||||
@@ -9,7 +9,7 @@ export type P2POpenReplicationModalCallback = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export class P2POpenReplicationModal extends Modal {
|
export class P2POpenReplicationModal extends Modal {
|
||||||
liveSyncReplicator: LiveSyncTrysteroReplicator;
|
p2p: P2PServiceViews;
|
||||||
callback?: P2POpenReplicationModalCallback;
|
callback?: P2POpenReplicationModalCallback;
|
||||||
component?: ReturnType<typeof mount>;
|
component?: ReturnType<typeof mount>;
|
||||||
showResult: boolean;
|
showResult: boolean;
|
||||||
@@ -19,7 +19,7 @@ export class P2POpenReplicationModal extends Modal {
|
|||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
app: App,
|
app: App,
|
||||||
liveSyncReplicator: LiveSyncTrysteroReplicator,
|
p2p: P2PServiceViews,
|
||||||
callback?: P2POpenReplicationModalCallback,
|
callback?: P2POpenReplicationModalCallback,
|
||||||
showResult: boolean = false,
|
showResult: boolean = false,
|
||||||
title: string = "P2P Replication",
|
title: string = "P2P Replication",
|
||||||
@@ -27,7 +27,7 @@ export class P2POpenReplicationModal extends Modal {
|
|||||||
rebuildMode: boolean = false
|
rebuildMode: boolean = false
|
||||||
) {
|
) {
|
||||||
super(app);
|
super(app);
|
||||||
this.liveSyncReplicator = liveSyncReplicator;
|
this.p2p = p2p;
|
||||||
this.callback = callback;
|
this.callback = callback;
|
||||||
this.showResult = showResult;
|
this.showResult = showResult;
|
||||||
this.title = title;
|
this.title = title;
|
||||||
@@ -57,7 +57,7 @@ export class P2POpenReplicationModal extends Modal {
|
|||||||
this.component = mount(P2POpenReplicationPane, {
|
this.component = mount(P2POpenReplicationPane, {
|
||||||
target: contentEl,
|
target: contentEl,
|
||||||
props: {
|
props: {
|
||||||
liveSyncReplicator: this.liveSyncReplicator,
|
p2p: this.p2p,
|
||||||
onSync: (peerId: string) => this.onSync(peerId),
|
onSync: (peerId: string) => this.onSync(peerId),
|
||||||
onSyncAndClose: (peerId: string) => this.onSyncAndClose(peerId),
|
onSyncAndClose: (peerId: string) => this.onSyncAndClose(peerId),
|
||||||
onClose: () => this.close(),
|
onClose: () => this.close(),
|
||||||
|
|||||||
@@ -9,13 +9,13 @@
|
|||||||
// import type { TrysteroReplicator } from "@vrtmrz/livesync-commonlib/compat/replication/trystero/TrysteroReplicator";
|
// import type { TrysteroReplicator } from "@vrtmrz/livesync-commonlib/compat/replication/trystero/TrysteroReplicator";
|
||||||
import { LOG_LEVEL_NOTICE, LOG_LEVEL_INFO } from "@vrtmrz/livesync-commonlib/compat/common/types";
|
import { LOG_LEVEL_NOTICE, LOG_LEVEL_INFO } from "@vrtmrz/livesync-commonlib/compat/common/types";
|
||||||
import { Logger } from "@vrtmrz/livesync-commonlib/compat/common/logger";
|
import { Logger } from "@vrtmrz/livesync-commonlib/compat/common/logger";
|
||||||
import type { LiveSyncTrysteroReplicator } from "@vrtmrz/livesync-commonlib/compat/replication/trystero/LiveSyncTrysteroReplicator";
|
import type { P2PServiceViews } from "@vrtmrz/livesync-commonlib/p2p";
|
||||||
import { delay, fireAndForget } from "octagonal-wheels/promises";
|
import { delay, fireAndForget } from "octagonal-wheels/promises";
|
||||||
import P2PServerStatusCard from "./P2PServerStatusCard.svelte";
|
import P2PServerStatusCard from "./P2PServerStatusCard.svelte";
|
||||||
import { $msg as translateMessage } from "@/common/translation";
|
import { $msg as translateMessage } from "@/common/translation";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
liveSyncReplicator: LiveSyncTrysteroReplicator;
|
p2p: P2PServiceViews;
|
||||||
onSync: (_peerId: string) => Promise<void>;
|
onSync: (_peerId: string) => Promise<void>;
|
||||||
onSyncAndClose: (_peerId: string) => Promise<void>;
|
onSyncAndClose: (_peerId: string) => Promise<void>;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
@@ -23,15 +23,14 @@
|
|||||||
rebuildMode?: boolean;
|
rebuildMode?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
let { onSync, onSyncAndClose, onClose, showResult, liveSyncReplicator, rebuildMode = false }: Props = $props();
|
let { onSync, onSyncAndClose, onClose, showResult, p2p, rebuildMode = false }: Props = $props();
|
||||||
const getLiveSyncReplicator = () => liveSyncReplicator;
|
|
||||||
|
|
||||||
let serverInfo = $state<P2PServerInfo | undefined>(undefined);
|
let serverInfo = $state<P2PServerInfo | undefined>(undefined);
|
||||||
let syncingPeerId = $state<string | null>(null);
|
let syncingPeerId = $state<string | null>(null);
|
||||||
|
|
||||||
const logLevel = $derived(showResult ? LOG_LEVEL_NOTICE : LOG_LEVEL_INFO);
|
const logLevel = $derived(showResult ? LOG_LEVEL_NOTICE : LOG_LEVEL_INFO);
|
||||||
async function requestServerStatus() {
|
async function requestServerStatus() {
|
||||||
await liveSyncReplicator.requestStatus();
|
p2p.diagnostics.requestStatus();
|
||||||
eventHub.emitEvent(EVENT_REQUEST_STATUS);
|
eventHub.emitEvent(EVENT_REQUEST_STATUS);
|
||||||
}
|
}
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
@@ -73,7 +72,7 @@
|
|||||||
|
|
||||||
async function disconnect() {
|
async function disconnect() {
|
||||||
try {
|
try {
|
||||||
await liveSyncReplicator.close();
|
await p2p.transportLifecycle.disconnect();
|
||||||
Logger("Signalling connection closed.", logLevel);
|
Logger("Signalling connection closed.", logLevel);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
Logger(`Failed to close signalling connection: ${e instanceof Error ? e.message : String(e)}`, logLevel);
|
Logger(`Failed to close signalling connection: ${e instanceof Error ? e.message : String(e)}`, logLevel);
|
||||||
@@ -100,7 +99,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="p2p-container">
|
<div class="p2p-container">
|
||||||
<P2PServerStatusCard {getLiveSyncReplicator} showBroadcastToggle={false} />
|
<P2PServerStatusCard {p2p} showBroadcastToggle={false} />
|
||||||
|
|
||||||
<div class="peers-section">
|
<div class="peers-section">
|
||||||
<h3>{translateMessage("Available Peers")}</h3>
|
<h3>{translateMessage("Available Peers")}</h3>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import type { App } from "@/deps.ts";
|
|||||||
import { Logger } from "@vrtmrz/livesync-commonlib/compat/common/logger";
|
import { Logger } from "@vrtmrz/livesync-commonlib/compat/common/logger";
|
||||||
import { LOG_LEVEL_NOTICE, LOG_LEVEL_INFO } from "@vrtmrz/livesync-commonlib/compat/common/types";
|
import { LOG_LEVEL_NOTICE, LOG_LEVEL_INFO } from "@vrtmrz/livesync-commonlib/compat/common/types";
|
||||||
import type { LiveSyncTrysteroReplicator } from "@vrtmrz/livesync-commonlib/compat/replication/trystero/LiveSyncTrysteroReplicator";
|
import type { LiveSyncTrysteroReplicator } from "@vrtmrz/livesync-commonlib/compat/replication/trystero/LiveSyncTrysteroReplicator";
|
||||||
|
import type { P2PServiceViews } from "@vrtmrz/livesync-commonlib/p2p";
|
||||||
import { P2POpenReplicationModal } from "./P2POpenReplicationModal";
|
import { P2POpenReplicationModal } from "./P2POpenReplicationModal";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -15,8 +16,8 @@ import { P2POpenReplicationModal } from "./P2POpenReplicationModal";
|
|||||||
*/
|
*/
|
||||||
export function createOpenReplicationUI(
|
export function createOpenReplicationUI(
|
||||||
app: App
|
app: App
|
||||||
): (replicator: LiveSyncTrysteroReplicator) => (showResult: boolean) => Promise<boolean | void> {
|
): (replicator: LiveSyncTrysteroReplicator, p2p: P2PServiceViews) => (showResult: boolean) => Promise<boolean | void> {
|
||||||
return (replicator: LiveSyncTrysteroReplicator) =>
|
return (replicator: LiveSyncTrysteroReplicator, p2p: P2PServiceViews) =>
|
||||||
(showResult: boolean): Promise<boolean | void> => {
|
(showResult: boolean): Promise<boolean | void> => {
|
||||||
const logLevel = showResult ? LOG_LEVEL_NOTICE : LOG_LEVEL_INFO;
|
const logLevel = showResult ? LOG_LEVEL_NOTICE : LOG_LEVEL_INFO;
|
||||||
return new Promise<boolean | void>((resolve) => {
|
return new Promise<boolean | void>((resolve) => {
|
||||||
@@ -57,7 +58,7 @@ export function createOpenReplicationUI(
|
|||||||
};
|
};
|
||||||
const modal = new P2POpenReplicationModal(
|
const modal = new P2POpenReplicationModal(
|
||||||
app,
|
app,
|
||||||
replicator,
|
p2p,
|
||||||
{
|
{
|
||||||
onSync: (peerId: string) => synchronise(peerId, false),
|
onSync: (peerId: string) => synchronise(peerId, false),
|
||||||
onSyncAndClose: (peerId: string) => synchronise(peerId, true),
|
onSyncAndClose: (peerId: string) => synchronise(peerId, true),
|
||||||
@@ -85,8 +86,8 @@ export function createOpenReplicationUI(
|
|||||||
*/
|
*/
|
||||||
export function createOpenRebuildUI(
|
export function createOpenRebuildUI(
|
||||||
app: App
|
app: App
|
||||||
): (replicator: LiveSyncTrysteroReplicator) => (showResult: boolean) => Promise<boolean | void> {
|
): (replicator: LiveSyncTrysteroReplicator, p2p: P2PServiceViews) => (showResult: boolean) => Promise<boolean | void> {
|
||||||
return (replicator: LiveSyncTrysteroReplicator) =>
|
return (replicator: LiveSyncTrysteroReplicator, p2p: P2PServiceViews) =>
|
||||||
(showResult: boolean): Promise<boolean | void> => {
|
(showResult: boolean): Promise<boolean | void> => {
|
||||||
const logLevel = showResult ? LOG_LEVEL_NOTICE : LOG_LEVEL_INFO;
|
const logLevel = showResult ? LOG_LEVEL_NOTICE : LOG_LEVEL_INFO;
|
||||||
return new Promise<boolean | void>((resolve) => {
|
return new Promise<boolean | void>((resolve) => {
|
||||||
@@ -132,7 +133,7 @@ export function createOpenRebuildUI(
|
|||||||
|
|
||||||
const modal = new P2POpenReplicationModal(
|
const modal = new P2POpenReplicationModal(
|
||||||
app,
|
app,
|
||||||
replicator,
|
p2p,
|
||||||
{
|
{
|
||||||
onSync: doRebuild,
|
onSync: doRebuild,
|
||||||
onSyncAndClose: doRebuild,
|
onSyncAndClose: doRebuild,
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { beforeEach, describe, expect, it, vi } from "vitest";
|
|||||||
|
|
||||||
const modalState = vi.hoisted(() => ({
|
const modalState = vi.hoisted(() => ({
|
||||||
instances: [] as Array<{
|
instances: [] as Array<{
|
||||||
|
p2p: unknown;
|
||||||
callback: {
|
callback: {
|
||||||
onSync: (peerId: string) => Promise<void>;
|
onSync: (peerId: string) => Promise<void>;
|
||||||
onSyncAndClose: (peerId: string) => Promise<void>;
|
onSyncAndClose: (peerId: string) => Promise<void>;
|
||||||
@@ -15,18 +16,20 @@ vi.mock("@/deps.ts", () => ({ App: class {} }));
|
|||||||
|
|
||||||
vi.mock("./P2POpenReplicationModal", () => ({
|
vi.mock("./P2POpenReplicationModal", () => ({
|
||||||
P2POpenReplicationModal: class {
|
P2POpenReplicationModal: class {
|
||||||
|
p2p;
|
||||||
callback;
|
callback;
|
||||||
onClosed;
|
onClosed;
|
||||||
open = vi.fn();
|
open = vi.fn();
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
_app: unknown,
|
_app: unknown,
|
||||||
_replicator: unknown,
|
p2p: unknown,
|
||||||
callback: (typeof modalState.instances)[number]["callback"],
|
callback: (typeof modalState.instances)[number]["callback"],
|
||||||
_showResult: boolean,
|
_showResult: boolean,
|
||||||
_title?: string,
|
_title?: string,
|
||||||
onClosed?: () => void
|
onClosed?: () => void
|
||||||
) {
|
) {
|
||||||
|
this.p2p = p2p;
|
||||||
this.callback = callback;
|
this.callback = callback;
|
||||||
this.onClosed = onClosed;
|
this.onClosed = onClosed;
|
||||||
modalState.instances.push(this);
|
modalState.instances.push(this);
|
||||||
@@ -46,15 +49,21 @@ function createReplicator() {
|
|||||||
} as any;
|
} as any;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function createP2PServiceViews() {
|
||||||
|
return { transportLifecycle: {}, diagnostics: {} } as any;
|
||||||
|
}
|
||||||
|
|
||||||
describe("createOpenReplicationUI", () => {
|
describe("createOpenReplicationUI", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
modalState.instances.length = 0;
|
modalState.instances.length = 0;
|
||||||
});
|
});
|
||||||
|
|
||||||
it("settles a cancelled peer-selection session when the modal closes", async () => {
|
it("settles a cancelled peer-selection session when the modal closes", async () => {
|
||||||
const session = createOpenReplicationUI({} as any)(createReplicator())(true);
|
const p2p = createP2PServiceViews();
|
||||||
|
const session = createOpenReplicationUI({} as any)(createReplicator(), p2p)(true);
|
||||||
const modal = modalState.instances[0];
|
const modal = modalState.instances[0];
|
||||||
|
|
||||||
|
expect(modal.p2p).toBe(p2p);
|
||||||
expect(modal.onClosed).toBeTypeOf("function");
|
expect(modal.onClosed).toBeTypeOf("function");
|
||||||
modal.onClosed?.();
|
modal.onClosed?.();
|
||||||
|
|
||||||
@@ -63,7 +72,7 @@ describe("createOpenReplicationUI", () => {
|
|||||||
|
|
||||||
it("keeps repeated synchronisation inside the session boundary until the modal closes", async () => {
|
it("keeps repeated synchronisation inside the session boundary until the modal closes", async () => {
|
||||||
const replicator = createReplicator();
|
const replicator = createReplicator();
|
||||||
const session = createOpenReplicationUI({} as any)(replicator)(true);
|
const session = createOpenReplicationUI({} as any)(replicator, createP2PServiceViews())(true);
|
||||||
const modal = modalState.instances[0];
|
const modal = modalState.instances[0];
|
||||||
let settled = false;
|
let settled = false;
|
||||||
void session.finally(() => {
|
void session.finally(() => {
|
||||||
@@ -91,7 +100,7 @@ describe("createOpenReplicationUI", () => {
|
|||||||
finishPull = resolve;
|
finishPull = resolve;
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
const session = createOpenReplicationUI({} as any)(replicator)(true);
|
const session = createOpenReplicationUI({} as any)(replicator, createP2PServiceViews())(true);
|
||||||
const modal = modalState.instances[0];
|
const modal = modalState.instances[0];
|
||||||
let settled = false;
|
let settled = false;
|
||||||
void session.finally(() => {
|
void session.finally(() => {
|
||||||
@@ -111,7 +120,7 @@ describe("createOpenReplicationUI", () => {
|
|||||||
|
|
||||||
it("closes the P2P connection after a successful sync-and-close action", async () => {
|
it("closes the P2P connection after a successful sync-and-close action", async () => {
|
||||||
const replicator = createReplicator();
|
const replicator = createReplicator();
|
||||||
const session = createOpenReplicationUI({} as any)(replicator)(true);
|
const session = createOpenReplicationUI({} as any)(replicator, createP2PServiceViews())(true);
|
||||||
const modal = modalState.instances[0];
|
const modal = modalState.instances[0];
|
||||||
|
|
||||||
await modal.callback.onSyncAndClose("peer-a");
|
await modal.callback.onSyncAndClose("peer-a");
|
||||||
@@ -143,7 +152,7 @@ describe("createOpenRebuildUI", () => {
|
|||||||
finishPull = resolve;
|
finishPull = resolve;
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
const session = createOpenRebuildUI({} as any)(replicator)(true);
|
const session = createOpenRebuildUI({} as any)(replicator, createP2PServiceViews())(true);
|
||||||
const modal = modalState.instances[0];
|
const modal = modalState.instances[0];
|
||||||
let settled = false;
|
let settled = false;
|
||||||
void session.finally(() => {
|
void session.finally(() => {
|
||||||
@@ -166,7 +175,7 @@ describe("createOpenRebuildUI", () => {
|
|||||||
|
|
||||||
it("does not complete Fetch when the rebuild dialogue closes without selecting a peer", async () => {
|
it("does not complete Fetch when the rebuild dialogue closes without selecting a peer", async () => {
|
||||||
const replicator = createReplicator();
|
const replicator = createReplicator();
|
||||||
const session = createOpenRebuildUI({} as any)(replicator)(true);
|
const session = createOpenRebuildUI({} as any)(replicator, createP2PServiceViews())(true);
|
||||||
const modal = modalState.instances[0];
|
const modal = modalState.instances[0];
|
||||||
|
|
||||||
modal.onClosed?.();
|
modal.onClosed?.();
|
||||||
|
|||||||
@@ -13,7 +13,6 @@
|
|||||||
type PeerInfo,
|
type PeerInfo,
|
||||||
type P2PServerInfo,
|
type P2PServerInfo,
|
||||||
EVENT_SERVER_STATUS,
|
EVENT_SERVER_STATUS,
|
||||||
EVENT_REQUEST_STATUS,
|
|
||||||
EVENT_P2P_REPLICATOR_STATUS,
|
EVENT_P2P_REPLICATOR_STATUS,
|
||||||
} from "@vrtmrz/livesync-commonlib/compat/replication/trystero/TrysteroReplicatorP2PServer";
|
} from "@vrtmrz/livesync-commonlib/compat/replication/trystero/TrysteroReplicatorP2PServer";
|
||||||
import type { P2PReplicatorStatus } from "@vrtmrz/livesync-commonlib/compat/replication/trystero/TrysteroReplicator";
|
import type { P2PReplicatorStatus } from "@vrtmrz/livesync-commonlib/compat/replication/trystero/TrysteroReplicator";
|
||||||
@@ -29,7 +28,6 @@
|
|||||||
let services = $derived(host.services);
|
let services = $derived(host.services);
|
||||||
let events = $derived(services.context.events);
|
let events = $derived(services.context.events);
|
||||||
const currentSettings = () => services.setting.currentSettings() as P2PSyncSetting;
|
const currentSettings = () => services.setting.currentSettings() as P2PSyncSetting;
|
||||||
const currentReplicator = () => host.p2p.replicator;
|
|
||||||
const initialSettings = { ...currentSettings() } as P2PSyncSetting;
|
const initialSettings = { ...currentSettings() } as P2PSyncSetting;
|
||||||
|
|
||||||
let settings = $state<P2PSyncSetting>(initialSettings);
|
let settings = $state<P2PSyncSetting>(initialSettings);
|
||||||
@@ -146,7 +144,7 @@
|
|||||||
replicatorInfo = status;
|
replicatorInfo = status;
|
||||||
});
|
});
|
||||||
applyLoadSettings(currentSettings(), true);
|
applyLoadSettings(currentSettings(), true);
|
||||||
events.emitEvent(EVENT_REQUEST_STATUS);
|
host.p2p.diagnostics.requestStatus();
|
||||||
return () => {
|
return () => {
|
||||||
r();
|
r();
|
||||||
rx();
|
rx();
|
||||||
@@ -223,16 +221,16 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function openServer() {
|
async function openServer() {
|
||||||
await currentReplicator().open();
|
await host.p2p.transportLifecycle.connect();
|
||||||
}
|
}
|
||||||
async function closeServer() {
|
async function closeServer() {
|
||||||
await currentReplicator().close();
|
await host.p2p.transportLifecycle.disconnect();
|
||||||
}
|
}
|
||||||
function startBroadcasting() {
|
function startBroadcasting() {
|
||||||
currentReplicator().enableBroadcastChanges();
|
host.p2p.changeRelay.enableBroadcastChanges();
|
||||||
}
|
}
|
||||||
function stopBroadcasting() {
|
function stopBroadcasting() {
|
||||||
currentReplicator().disableBroadcastChanges();
|
host.p2p.changeRelay.disableBroadcastChanges();
|
||||||
}
|
}
|
||||||
|
|
||||||
const initialDialogStatusKey = `p2p-dialog-status`;
|
const initialDialogStatusKey = `p2p-dialog-status`;
|
||||||
|
|||||||
@@ -1,12 +1,20 @@
|
|||||||
import type { RequiredServices } from "@vrtmrz/livesync-commonlib/compat/interfaces/ServiceModule";
|
import type { RequiredServices } from "@vrtmrz/livesync-commonlib/compat/interfaces/ServiceModule";
|
||||||
|
import type { P2PServiceViews } from "@vrtmrz/livesync-commonlib/p2p";
|
||||||
import type { PeerStatus } from "@vrtmrz/livesync-commonlib/compat/replication/trystero/P2PReplicatorPaneCommon";
|
import type { PeerStatus } from "@vrtmrz/livesync-commonlib/compat/replication/trystero/P2PReplicatorPaneCommon";
|
||||||
import type { UseP2PReplicatorResult } from "@vrtmrz/livesync-commonlib/compat/replication/trystero/UseP2PReplicatorResult";
|
|
||||||
|
|
||||||
export type P2PReplicatorHandle = Pick<UseP2PReplicatorResult, "replicator">;
|
/**
|
||||||
|
* The shared pane only needs the contracts which represent its visible
|
||||||
|
* actions. In particular, it must not receive the compatibility Replicator
|
||||||
|
* facade, whose lifecycle methods can bypass the stable P2P service owner.
|
||||||
|
*/
|
||||||
|
export type P2PReplicatorPaneP2P = Pick<
|
||||||
|
P2PServiceViews,
|
||||||
|
"transportLifecycle" | "peerDirectory" | "peerAdmission" | "targetedTransfer" | "changeRelay" | "diagnostics"
|
||||||
|
>;
|
||||||
|
|
||||||
/** Host capabilities consumed by the shared P2P pane. */
|
/** Host capabilities consumed by the shared P2P pane. */
|
||||||
export interface P2PReplicatorPaneHost {
|
export interface P2PReplicatorPaneHost {
|
||||||
readonly services: RequiredServices<"API" | "config" | "setting" | "vault">;
|
readonly services: RequiredServices<"API" | "config" | "setting" | "vault">;
|
||||||
readonly p2p: P2PReplicatorHandle;
|
readonly p2p: P2PReplicatorPaneP2P;
|
||||||
readonly showPeerMenu?: (peer: PeerStatus, event: MouseEvent) => void;
|
readonly showPeerMenu?: (peer: PeerStatus, event: MouseEvent) => void;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { LOG_LEVEL_NOTICE, REMOTE_P2P } from "@vrtmrz/livesync-commonlib/compat/
|
|||||||
import { Logger } from "@vrtmrz/livesync-commonlib/compat/common/logger";
|
import { Logger } from "@vrtmrz/livesync-commonlib/compat/common/logger";
|
||||||
import type { PeerStatus } from "@vrtmrz/livesync-commonlib/compat/replication/trystero/P2PReplicatorPaneCommon";
|
import type { PeerStatus } from "@vrtmrz/livesync-commonlib/compat/replication/trystero/P2PReplicatorPaneCommon";
|
||||||
import type { LiveSyncBaseCore } from "@/LiveSyncBaseCore.ts";
|
import type { LiveSyncBaseCore } from "@/LiveSyncBaseCore.ts";
|
||||||
import type { P2PPaneParams } from "@vrtmrz/livesync-commonlib/compat/replication/trystero/UseP2PReplicatorResult";
|
import type { P2PServiceViews } from "@vrtmrz/livesync-commonlib/p2p";
|
||||||
export const VIEW_TYPE_P2P = "p2p-replicator";
|
export const VIEW_TYPE_P2P = "p2p-replicator";
|
||||||
|
|
||||||
function addToList(item: string, list: string) {
|
function addToList(item: string, list: string) {
|
||||||
@@ -31,7 +31,7 @@ function removeFromList(item: string, list: string) {
|
|||||||
|
|
||||||
export class P2PReplicatorPaneView extends SvelteItemView {
|
export class P2PReplicatorPaneView extends SvelteItemView {
|
||||||
core: LiveSyncBaseCore;
|
core: LiveSyncBaseCore;
|
||||||
private _p2pResult: P2PPaneParams;
|
private _p2p: P2PServiceViews;
|
||||||
override icon = "waypoints";
|
override icon = "waypoints";
|
||||||
title: string = "";
|
title: string = "";
|
||||||
override navigation = false;
|
override navigation = false;
|
||||||
@@ -39,21 +39,18 @@ export class P2PReplicatorPaneView extends SvelteItemView {
|
|||||||
override getIcon(): string {
|
override getIcon(): string {
|
||||||
return "waypoints";
|
return "waypoints";
|
||||||
}
|
}
|
||||||
get replicator() {
|
|
||||||
return this._p2pResult.replicator;
|
|
||||||
}
|
|
||||||
async replicateFrom(peer: PeerStatus) {
|
async replicateFrom(peer: PeerStatus) {
|
||||||
await this.replicator.replicateFrom(peer.peerId);
|
await this._p2p.targetedTransfer.pullFromPeer(peer.peerId);
|
||||||
}
|
}
|
||||||
async replicateTo(peer: PeerStatus) {
|
async replicateTo(peer: PeerStatus) {
|
||||||
await this.replicator.requestSynchroniseToPeer(peer.peerId);
|
await this._p2p.targetedTransfer.requestPushToPeer(peer.peerId);
|
||||||
}
|
}
|
||||||
async getRemoteConfig(peer: PeerStatus) {
|
async getRemoteConfig(peer: PeerStatus) {
|
||||||
Logger(
|
Logger(
|
||||||
`Requesting remote config for ${peer.name}. Please input the passphrase on the remote device`,
|
`Requesting remote config for ${peer.name}. Please input the passphrase on the remote device`,
|
||||||
LOG_LEVEL_NOTICE
|
LOG_LEVEL_NOTICE
|
||||||
);
|
);
|
||||||
const remoteConfig = await this.replicator.getRemoteConfig(peer.peerId);
|
const remoteConfig = await this._p2p.configurationExchange.getRemoteConfiguration(peer.peerId);
|
||||||
if (remoteConfig) {
|
if (remoteConfig) {
|
||||||
Logger(`Remote config for ${peer.name} is retrieved successfully`);
|
Logger(`Remote config for ${peer.name} is retrieved successfully`);
|
||||||
const DROP = "Yes, and drop local database";
|
const DROP = "Yes, and drop local database";
|
||||||
@@ -122,10 +119,10 @@ And you can also drop the local database to rebuild from the remote device.`,
|
|||||||
await this.core.services.setting.applyPartial(currentSetting, true);
|
await this.core.services.setting.applyPartial(currentSetting, true);
|
||||||
}
|
}
|
||||||
m?: Menu;
|
m?: Menu;
|
||||||
constructor(leaf: WorkspaceLeaf, core: LiveSyncBaseCore, p2pResult: P2PPaneParams) {
|
constructor(leaf: WorkspaceLeaf, core: LiveSyncBaseCore, p2p: P2PServiceViews) {
|
||||||
super(leaf);
|
super(leaf);
|
||||||
this.core = core;
|
this.core = core;
|
||||||
this._p2pResult = p2pResult;
|
this._p2p = p2p;
|
||||||
}
|
}
|
||||||
|
|
||||||
private showPeerMenu(peer: PeerStatus, event: MouseEvent): void {
|
private showPeerMenu(peer: PeerStatus, event: MouseEvent): void {
|
||||||
@@ -187,7 +184,7 @@ And you can also drop the local database to rebuild from the remote device.`,
|
|||||||
props: {
|
props: {
|
||||||
host: {
|
host: {
|
||||||
services: this.core.services,
|
services: this.core.services,
|
||||||
p2p: this._p2pResult,
|
p2p: this._p2p,
|
||||||
showPeerMenu: (peer: PeerStatus, event: MouseEvent) => this.showPeerMenu(peer, event),
|
showPeerMenu: (peer: PeerStatus, event: MouseEvent) => this.showPeerMenu(peer, event),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -9,19 +9,19 @@
|
|||||||
EVENT_P2P_REPLICATOR_STATUS,
|
EVENT_P2P_REPLICATOR_STATUS,
|
||||||
} from "@vrtmrz/livesync-commonlib/compat/replication/trystero/TrysteroReplicatorP2PServer";
|
} from "@vrtmrz/livesync-commonlib/compat/replication/trystero/TrysteroReplicatorP2PServer";
|
||||||
import { EVENT_SETTING_SAVED } from "@vrtmrz/livesync-commonlib/compat/events/coreEvents";
|
import { EVENT_SETTING_SAVED } from "@vrtmrz/livesync-commonlib/compat/events/coreEvents";
|
||||||
import type { LiveSyncTrysteroReplicator } from "@vrtmrz/livesync-commonlib/compat/replication/trystero/LiveSyncTrysteroReplicator";
|
import type { P2PServiceViews } from "@vrtmrz/livesync-commonlib/p2p";
|
||||||
import type { P2PReplicatorStatus } from "@vrtmrz/livesync-commonlib/compat/replication/trystero/TrysteroReplicator";
|
import type { P2PReplicatorStatus } from "@vrtmrz/livesync-commonlib/compat/replication/trystero/TrysteroReplicator";
|
||||||
import { extractP2PRoomSuffix } from "@vrtmrz/livesync-commonlib/compat/common/utils";
|
import { extractP2PRoomSuffix } from "@vrtmrz/livesync-commonlib/compat/common/utils";
|
||||||
import type { LiveSyncBaseCore } from "@/LiveSyncBaseCore";
|
import type { LiveSyncBaseCore } from "@/LiveSyncBaseCore";
|
||||||
import { $msg as translateMessage } from "@/common/translation";
|
import { $msg as translateMessage } from "@/common/translation";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
getLiveSyncReplicator: () => LiveSyncTrysteroReplicator;
|
p2p: P2PServiceViews;
|
||||||
showBroadcastToggle?: boolean;
|
showBroadcastToggle?: boolean;
|
||||||
core?: LiveSyncBaseCore;
|
core?: LiveSyncBaseCore;
|
||||||
}
|
}
|
||||||
|
|
||||||
let { getLiveSyncReplicator, showBroadcastToggle = true, core }: Props = $props();
|
let { p2p, showBroadcastToggle = true, core }: Props = $props();
|
||||||
let serverInfo = $state<P2PServerInfo | undefined>(undefined);
|
let serverInfo = $state<P2PServerInfo | undefined>(undefined);
|
||||||
let replicatorStatus = $state<P2PReplicatorStatus | undefined>(undefined);
|
let replicatorStatus = $state<P2PReplicatorStatus | undefined>(undefined);
|
||||||
// Later setting changes arrive through EVENT_SETTING_SAVED; these values only seed local state at mount time.
|
// Later setting changes arrive through EVENT_SETTING_SAVED; these values only seed local state at mount time.
|
||||||
@@ -31,25 +31,25 @@
|
|||||||
let useDiagRTC = $state<boolean>(initialSettings?.P2P_useDiagRTC ?? false);
|
let useDiagRTC = $state<boolean>(initialSettings?.P2P_useDiagRTC ?? false);
|
||||||
|
|
||||||
async function requestServerStatus() {
|
async function requestServerStatus() {
|
||||||
await Promise.resolve(getLiveSyncReplicator().requestStatus());
|
p2p.diagnostics.requestStatus();
|
||||||
eventHub.emitEvent(EVENT_REQUEST_STATUS);
|
eventHub.emitEvent(EVENT_REQUEST_STATUS);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function onOpenConnection() {
|
async function onOpenConnection() {
|
||||||
await getLiveSyncReplicator().makeSureOpened();
|
await p2p.transportLifecycle.connect();
|
||||||
await requestServerStatus();
|
await requestServerStatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function onDisconnect() {
|
async function onDisconnect() {
|
||||||
await getLiveSyncReplicator().close();
|
await p2p.transportLifecycle.disconnect();
|
||||||
await requestServerStatus();
|
await requestServerStatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleBroadcast() {
|
function toggleBroadcast() {
|
||||||
if (replicatorStatus?.isBroadcasting) {
|
if (replicatorStatus?.isBroadcasting) {
|
||||||
getLiveSyncReplicator().disableBroadcastChanges();
|
p2p.changeRelay.disableBroadcastChanges();
|
||||||
} else {
|
} else {
|
||||||
getLiveSyncReplicator().enableBroadcastChanges();
|
p2p.changeRelay.enableBroadcastChanges();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
EVENT_P2P_REPLICATOR_PROGRESS,
|
EVENT_P2P_REPLICATOR_PROGRESS,
|
||||||
type P2PServerInfo,
|
type P2PServerInfo,
|
||||||
} from "@vrtmrz/livesync-commonlib/compat/replication/trystero/TrysteroReplicatorP2PServer";
|
} from "@vrtmrz/livesync-commonlib/compat/replication/trystero/TrysteroReplicatorP2PServer";
|
||||||
import type { LiveSyncTrysteroReplicator } from "@vrtmrz/livesync-commonlib/compat/replication/trystero/LiveSyncTrysteroReplicator";
|
import type { P2PServiceViews } from "@vrtmrz/livesync-commonlib/p2p";
|
||||||
import type { P2PReplicatorStatus, P2PReplicationReport } from "@vrtmrz/livesync-commonlib/compat/replication/trystero/TrysteroReplicator";
|
import type { P2PReplicatorStatus, P2PReplicationReport } from "@vrtmrz/livesync-commonlib/compat/replication/trystero/TrysteroReplicator";
|
||||||
import { delay, fireAndForget } from "octagonal-wheels/promises";
|
import { delay, fireAndForget } from "octagonal-wheels/promises";
|
||||||
import P2PServerStatusCard from "./P2PServerStatusCard.svelte";
|
import P2PServerStatusCard from "./P2PServerStatusCard.svelte";
|
||||||
@@ -33,11 +33,11 @@
|
|||||||
} from "./p2pPeerSettings";
|
} from "./p2pPeerSettings";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
getLiveSyncReplicator: () => LiveSyncTrysteroReplicator;
|
p2p: P2PServiceViews;
|
||||||
core: LiveSyncBaseCore;
|
core: LiveSyncBaseCore;
|
||||||
}
|
}
|
||||||
|
|
||||||
let { getLiveSyncReplicator, core }: Props = $props();
|
let { p2p, core }: Props = $props();
|
||||||
let serverInfo = $state<P2PServerInfo | undefined>(undefined);
|
let serverInfo = $state<P2PServerInfo | undefined>(undefined);
|
||||||
let replicatorInfo = $state<P2PReplicatorStatus | undefined>(undefined);
|
let replicatorInfo = $state<P2PReplicatorStatus | undefined>(undefined);
|
||||||
let decidingPeerId = $state<string | null>(null);
|
let decidingPeerId = $state<string | null>(null);
|
||||||
@@ -121,7 +121,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function requestServerStatus() {
|
async function requestServerStatus() {
|
||||||
await getLiveSyncReplicator().requestStatus();
|
p2p.diagnostics.requestStatus();
|
||||||
eventHub.emitEvent(EVENT_REQUEST_STATUS);
|
eventHub.emitEvent(EVENT_REQUEST_STATUS);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -296,7 +296,7 @@
|
|||||||
) {
|
) {
|
||||||
decidingPeerId = peer.peerId;
|
decidingPeerId = peer.peerId;
|
||||||
try {
|
try {
|
||||||
await getLiveSyncReplicator().makeDecision({
|
await p2p.peerAdmission.makeDecision({
|
||||||
peerId: peer.peerId,
|
peerId: peer.peerId,
|
||||||
name: peer.name,
|
name: peer.name,
|
||||||
decision,
|
decision,
|
||||||
@@ -311,7 +311,7 @@
|
|||||||
async function revokeDecision(peer: P2PServerInfo["knownAdvertisements"][number]) {
|
async function revokeDecision(peer: P2PServerInfo["knownAdvertisements"][number]) {
|
||||||
decidingPeerId = peer.peerId;
|
decidingPeerId = peer.peerId;
|
||||||
try {
|
try {
|
||||||
await getLiveSyncReplicator().revokeDecision({
|
await p2p.peerAdmission.revokeDecision({
|
||||||
peerId: peer.peerId,
|
peerId: peer.peerId,
|
||||||
name: peer.name,
|
name: peer.name,
|
||||||
});
|
});
|
||||||
@@ -324,10 +324,7 @@
|
|||||||
async function startReplication(peer: P2PServerInfo["knownAdvertisements"][number]) {
|
async function startReplication(peer: P2PServerInfo["knownAdvertisements"][number]) {
|
||||||
replicatingPeerId = peer.peerId;
|
replicatingPeerId = peer.peerId;
|
||||||
try {
|
try {
|
||||||
const pullResult = await getLiveSyncReplicator().replicateFrom(peer.peerId, true);
|
await p2p.targetedTransfer.synchroniseWithPeer(peer.peerId, true);
|
||||||
if (pullResult?.ok) {
|
|
||||||
await getLiveSyncReplicator().requestSynchroniseToPeer(peer.peerId);
|
|
||||||
}
|
|
||||||
await requestServerStatus();
|
await requestServerStatus();
|
||||||
} finally {
|
} finally {
|
||||||
replicatingPeerId = null;
|
replicatingPeerId = null;
|
||||||
@@ -347,9 +344,9 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (isWatching(peerId)) {
|
if (isWatching(peerId)) {
|
||||||
getLiveSyncReplicator().unwatchPeer(peerId);
|
p2p.changeRelay.unwatchPeer(peerId);
|
||||||
} else {
|
} else {
|
||||||
getLiveSyncReplicator().watchPeer(peerId);
|
p2p.changeRelay.watchPeer(peerId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -455,7 +452,7 @@
|
|||||||
</p>
|
</p>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<P2PServerStatusCard {getLiveSyncReplicator} {core} />
|
<P2PServerStatusCard {p2p} {core} />
|
||||||
|
|
||||||
<div class="peers-section">
|
<div class="peers-section">
|
||||||
<div class="peers-header">
|
<div class="peers-header">
|
||||||
|
|||||||
@@ -2,21 +2,21 @@ import { WorkspaceLeaf } from "@/deps.ts";
|
|||||||
import { mount } from "svelte";
|
import { mount } from "svelte";
|
||||||
import { SvelteItemView } from "@/common/SvelteItemView.ts";
|
import { SvelteItemView } from "@/common/SvelteItemView.ts";
|
||||||
import type { LiveSyncBaseCore } from "@/LiveSyncBaseCore.ts";
|
import type { LiveSyncBaseCore } from "@/LiveSyncBaseCore.ts";
|
||||||
import type { P2PPaneParams } from "@vrtmrz/livesync-commonlib/compat/replication/trystero/UseP2PReplicatorResult";
|
import type { P2PServiceViews } from "@vrtmrz/livesync-commonlib/p2p";
|
||||||
import P2PServerStatusPane from "./P2PServerStatusPane.svelte";
|
import P2PServerStatusPane from "./P2PServerStatusPane.svelte";
|
||||||
|
|
||||||
export const VIEW_TYPE_P2P_SERVER_STATUS = "p2p-server-status";
|
export const VIEW_TYPE_P2P_SERVER_STATUS = "p2p-server-status";
|
||||||
|
|
||||||
export class P2PServerStatusPaneView extends SvelteItemView {
|
export class P2PServerStatusPaneView extends SvelteItemView {
|
||||||
core: LiveSyncBaseCore;
|
core: LiveSyncBaseCore;
|
||||||
private _p2pResult: P2PPaneParams;
|
private readonly p2p: P2PServiceViews;
|
||||||
override icon = "waypoints";
|
override icon = "waypoints";
|
||||||
override navigation = false;
|
override navigation = false;
|
||||||
|
|
||||||
constructor(leaf: WorkspaceLeaf, core: LiveSyncBaseCore, p2pResult: P2PPaneParams) {
|
constructor(leaf: WorkspaceLeaf, core: LiveSyncBaseCore, p2p: P2PServiceViews) {
|
||||||
super(leaf);
|
super(leaf);
|
||||||
this.core = core;
|
this.core = core;
|
||||||
this._p2pResult = p2pResult;
|
this.p2p = p2p;
|
||||||
}
|
}
|
||||||
|
|
||||||
override getIcon(): string {
|
override getIcon(): string {
|
||||||
@@ -35,7 +35,7 @@ export class P2PServerStatusPaneView extends SvelteItemView {
|
|||||||
return mount(P2PServerStatusPane, {
|
return mount(P2PServerStatusPane, {
|
||||||
target,
|
target,
|
||||||
props: {
|
props: {
|
||||||
getLiveSyncReplicator: () => this._p2pResult.replicator,
|
p2p: this.p2p,
|
||||||
core: this.core,
|
core: this.core,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,17 +1,16 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { AcceptedStatus, type PeerStatus } from "@vrtmrz/livesync-commonlib/compat/replication/trystero/P2PReplicatorPaneCommon";
|
import { AcceptedStatus, type PeerStatus } from "@vrtmrz/livesync-commonlib/compat/replication/trystero/P2PReplicatorPaneCommon";
|
||||||
import type { P2PReplicatorHandle } from "./P2PReplicatorPaneHost";
|
import type { P2PReplicatorPaneP2P } from "./P2PReplicatorPaneHost";
|
||||||
import { $msg as translateMessage } from "@/common/translation";
|
import { $msg as translateMessage } from "@/common/translation";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
peerStatus: PeerStatus;
|
peerStatus: PeerStatus;
|
||||||
p2p: P2PReplicatorHandle;
|
p2p: P2PReplicatorPaneP2P;
|
||||||
showPeerMenu?: (peer: PeerStatus, event: MouseEvent) => void;
|
showPeerMenu?: (peer: PeerStatus, event: MouseEvent) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
let { peerStatus, p2p, showPeerMenu }: Props = $props();
|
let { peerStatus, p2p, showPeerMenu }: Props = $props();
|
||||||
let peer = $derived(peerStatus);
|
let peer = $derived(peerStatus);
|
||||||
const currentReplicator = () => p2p.replicator;
|
|
||||||
|
|
||||||
function select<T extends PropertyKey, U, V = undefined>(
|
function select<T extends PropertyKey, U, V = undefined>(
|
||||||
d: T,
|
d: T,
|
||||||
@@ -72,7 +71,7 @@
|
|||||||
let isNew = $derived.by(() => peer.accepted === AcceptedStatus.UNKNOWN);
|
let isNew = $derived.by(() => peer.accepted === AcceptedStatus.UNKNOWN);
|
||||||
|
|
||||||
function makeDecision(isAccepted: boolean, isTemporary: boolean) {
|
function makeDecision(isAccepted: boolean, isTemporary: boolean) {
|
||||||
currentReplicator().makeDecision({
|
void p2p.peerAdmission.makeDecision({
|
||||||
peerId: peer.peerId,
|
peerId: peer.peerId,
|
||||||
name: peer.name,
|
name: peer.name,
|
||||||
decision: isAccepted,
|
decision: isAccepted,
|
||||||
@@ -80,7 +79,7 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
function revokeDecision() {
|
function revokeDecision() {
|
||||||
currentReplicator().revokeDecision({
|
void p2p.peerAdmission.revokeDecision({
|
||||||
peerId: peer.peerId,
|
peerId: peer.peerId,
|
||||||
name: peer.name,
|
name: peer.name,
|
||||||
});
|
});
|
||||||
@@ -99,14 +98,14 @@
|
|||||||
return attrs;
|
return attrs;
|
||||||
});
|
});
|
||||||
function startWatching() {
|
function startWatching() {
|
||||||
currentReplicator().watchPeer(peer.peerId);
|
p2p.changeRelay.watchPeer(peer.peerId);
|
||||||
}
|
}
|
||||||
function stopWatching() {
|
function stopWatching() {
|
||||||
currentReplicator().unwatchPeer(peer.peerId);
|
p2p.changeRelay.unwatchPeer(peer.peerId);
|
||||||
}
|
}
|
||||||
|
|
||||||
function sync() {
|
function sync() {
|
||||||
void currentReplicator().sync(peer.peerId, false);
|
void p2p.targetedTransfer.synchroniseWithPeer(peer.peerId, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
function moreMenu(evt: MouseEvent) {
|
function moreMenu(evt: MouseEvent) {
|
||||||
|
|||||||
@@ -369,7 +369,7 @@ Even if you choose to clean up, you will see this option again if you exit Obsid
|
|||||||
serviceModules: {},
|
serviceModules: {},
|
||||||
});
|
});
|
||||||
services.replication.onBeforeReplicate.addHandler(isOnlineAndCanReplicateWithHost, 10);
|
services.replication.onBeforeReplicate.addHandler(isOnlineAndCanReplicateWithHost, 10);
|
||||||
services.replication.onBeforeReplicate.addHandler(canReplicateWithPBKDF2WithHost, 20);
|
services.replication.onPrepareCentralRemoteReplication.addHandler(canReplicateWithPBKDF2WithHost);
|
||||||
// <-- End of handlers that can be separated.
|
// <-- End of handlers that can be separated.
|
||||||
services.replication.onBeforeReplicate.addHandler(this._everyBeforeReplicate.bind(this), 100);
|
services.replication.onBeforeReplicate.addHandler(this._everyBeforeReplicate.bind(this), 100);
|
||||||
services.replication.onReplicationFailed.addHandler(this.onReplicationFailed.bind(this));
|
services.replication.onReplicationFailed.addHandler(this.onReplicationFailed.bind(this));
|
||||||
|
|||||||
@@ -19,12 +19,7 @@ import { ModuleReplicator } from "./ModuleReplicator";
|
|||||||
describe("ModuleReplicator", () => {
|
describe("ModuleReplicator", () => {
|
||||||
it("refreshes the remote Security Seed before replication", async () => {
|
it("refreshes the remote Security Seed before replication", async () => {
|
||||||
const ensurePBKDF2Salt = vi.fn(async () => true);
|
const ensurePBKDF2Salt = vi.fn(async () => true);
|
||||||
let beforeReplicate: ((showMessage: boolean) => Promise<boolean>) | undefined;
|
let prepareCentralRemoteReplication: ((showMessage: boolean) => Promise<boolean>) | undefined;
|
||||||
const addHandler = vi.fn((handler: (showMessage: boolean) => Promise<boolean>, priority?: number) => {
|
|
||||||
if (priority === 20) {
|
|
||||||
beforeReplicate = handler;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
const services = {
|
const services = {
|
||||||
API: { isOnline: true },
|
API: { isOnline: true },
|
||||||
replicator: {
|
replicator: {
|
||||||
@@ -36,7 +31,12 @@ describe("ModuleReplicator", () => {
|
|||||||
appLifecycle: { onSettingLoaded: { addHandler: vi.fn() } },
|
appLifecycle: { onSettingLoaded: { addHandler: vi.fn() } },
|
||||||
replication: {
|
replication: {
|
||||||
parseSynchroniseResult: { addHandler: vi.fn() },
|
parseSynchroniseResult: { addHandler: vi.fn() },
|
||||||
onBeforeReplicate: { addHandler },
|
onBeforeReplicate: { addHandler: vi.fn() },
|
||||||
|
onPrepareCentralRemoteReplication: {
|
||||||
|
addHandler: vi.fn((handler: (showMessage: boolean) => Promise<boolean>) => {
|
||||||
|
prepareCentralRemoteReplication = handler;
|
||||||
|
}),
|
||||||
|
},
|
||||||
onReplicationFailed: { addHandler: vi.fn() },
|
onReplicationFailed: { addHandler: vi.fn() },
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -54,13 +54,72 @@ describe("ModuleReplicator", () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
ModuleReplicator.prototype.onBindFunction.call(module, {} as never, services as never);
|
ModuleReplicator.prototype.onBindFunction.call(module, {} as never, services as never);
|
||||||
expect(beforeReplicate).toBeDefined();
|
expect(prepareCentralRemoteReplication).toBeDefined();
|
||||||
|
|
||||||
await beforeReplicate!(false);
|
await prepareCentralRemoteReplication!(false);
|
||||||
|
|
||||||
expect(ensurePBKDF2Salt).toHaveBeenCalledWith({}, false, false);
|
expect(ensurePBKDF2Salt).toHaveBeenCalledWith({}, false, false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("keeps online and general pre-replication handlers for P2P while skipping central-remote Security Seed preparation", async () => {
|
||||||
|
const ensurePBKDF2Salt = vi.fn(async () => true);
|
||||||
|
const handlers = new Map<number, (...args: unknown[]) => Promise<boolean | void>>();
|
||||||
|
const centralRemoteHandlers: Array<(...args: unknown[]) => Promise<boolean | void>> = [];
|
||||||
|
const addHandler = vi.fn((handler: (...args: unknown[]) => Promise<boolean | void>, priority?: number) => {
|
||||||
|
handlers.set(priority ?? 0, handler);
|
||||||
|
});
|
||||||
|
const services = {
|
||||||
|
API: { isOnline: true },
|
||||||
|
replicator: {
|
||||||
|
onReplicatorInitialised: { addHandler: vi.fn() },
|
||||||
|
getActiveReplicator: () => ({ ensurePBKDF2Salt }),
|
||||||
|
},
|
||||||
|
setting: { currentSettings: () => ({}) },
|
||||||
|
databaseEvents: { onDatabaseInitialised: { addHandler: vi.fn() } },
|
||||||
|
appLifecycle: { onSettingLoaded: { addHandler: vi.fn() } },
|
||||||
|
replication: {
|
||||||
|
parseSynchroniseResult: { addHandler: vi.fn() },
|
||||||
|
onBeforeReplicate: { addHandler },
|
||||||
|
onPrepareCentralRemoteReplication: {
|
||||||
|
addHandler: vi.fn((handler: (...args: unknown[]) => Promise<boolean | void>) => {
|
||||||
|
centralRemoteHandlers.push(handler);
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
onReplicationFailed: { addHandler: vi.fn() },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const generalBeforeReplicate = vi.fn(async () => true);
|
||||||
|
const module = {
|
||||||
|
_unresolvedErrorManager: {
|
||||||
|
showError: vi.fn(),
|
||||||
|
clearError: vi.fn(),
|
||||||
|
},
|
||||||
|
_onReplicatorInitialised: vi.fn(),
|
||||||
|
_everyOnDatabaseInitialized: vi.fn(),
|
||||||
|
_everyOnloadAfterLoadSettings: vi.fn(),
|
||||||
|
_parseReplicationResult: vi.fn(),
|
||||||
|
_everyBeforeReplicate: generalBeforeReplicate,
|
||||||
|
onReplicationFailed: vi.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
ModuleReplicator.prototype.onBindFunction.call(module, {} as never, services as never);
|
||||||
|
const online = handlers.get(10);
|
||||||
|
const securitySeed = centralRemoteHandlers[0];
|
||||||
|
const general = handlers.get(100);
|
||||||
|
expect(online).toBeDefined();
|
||||||
|
expect(securitySeed).toBeDefined();
|
||||||
|
expect(general).toBeDefined();
|
||||||
|
|
||||||
|
await expect(online!(false)).resolves.toBe(true);
|
||||||
|
await expect(general!(false)).resolves.toBe(true);
|
||||||
|
|
||||||
|
expect(generalBeforeReplicate).toHaveBeenCalledOnce();
|
||||||
|
expect(ensurePBKDF2Salt).not.toHaveBeenCalled();
|
||||||
|
|
||||||
|
await expect(securitySeed!(false)).resolves.toBe(true);
|
||||||
|
expect(ensurePBKDF2Salt).toHaveBeenCalledOnce();
|
||||||
|
});
|
||||||
|
|
||||||
it("reprocesses stored documents when the normal-file target filters change", async () => {
|
it("reprocesses stored documents when the normal-file target filters change", async () => {
|
||||||
eventHub.offAll();
|
eventHub.offAll();
|
||||||
const settings = {
|
const settings = {
|
||||||
|
|||||||
@@ -96,10 +96,10 @@ export function useP2PReplicatorUI(
|
|||||||
};
|
};
|
||||||
|
|
||||||
const statusFactory = (leaf: WorkspaceLeaf) => {
|
const statusFactory = (leaf: WorkspaceLeaf) => {
|
||||||
return new P2PServerStatusPaneView(leaf, core, p2pParams);
|
return new P2PServerStatusPaneView(leaf, core, replicator);
|
||||||
};
|
};
|
||||||
const legacyStatusFactory = (leaf: WorkspaceLeaf) => {
|
const legacyStatusFactory = (leaf: WorkspaceLeaf) => {
|
||||||
return new LegacyP2PStatusPaneView(leaf, core, p2pParams);
|
return new LegacyP2PStatusPaneView(leaf, core, replicator);
|
||||||
};
|
};
|
||||||
const openStatusPane = () => {
|
const openStatusPane = () => {
|
||||||
if (api.showWindowOnRight) {
|
if (api.showWindowOnRight) {
|
||||||
@@ -167,7 +167,7 @@ export function useP2PReplicatorUI(
|
|||||||
const isAvailable =
|
const isAvailable =
|
||||||
hasP2PConfiguration(settings) &&
|
hasP2PConfiguration(settings) &&
|
||||||
settings.remoteType !== REMOTE_P2P &&
|
settings.remoteType !== REMOTE_P2P &&
|
||||||
(replicator.replicator?.server?.isServing ?? false);
|
replicator.transportLifecycle.isConnected;
|
||||||
if (!isAvailable) return false;
|
if (!isAvailable) return false;
|
||||||
if (!isChecking) {
|
if (!isChecking) {
|
||||||
runOpenReplication();
|
runOpenReplication();
|
||||||
@@ -183,7 +183,7 @@ export function useP2PReplicatorUI(
|
|||||||
const isAvailable =
|
const isAvailable =
|
||||||
hasP2PConfiguration(settings) &&
|
hasP2PConfiguration(settings) &&
|
||||||
settings.remoteType !== REMOTE_P2P &&
|
settings.remoteType !== REMOTE_P2P &&
|
||||||
(replicator.replicator?.server?.isServing ?? false);
|
replicator.transportLifecycle.isConnected;
|
||||||
if (!isAvailable) return false;
|
if (!isAvailable) return false;
|
||||||
if (!isChecking) {
|
if (!isChecking) {
|
||||||
runOpenReplication();
|
runOpenReplication();
|
||||||
@@ -198,10 +198,13 @@ export function useP2PReplicatorUI(
|
|||||||
checkCallback: (isChecking: boolean) => {
|
checkCallback: (isChecking: boolean) => {
|
||||||
const isAvailable =
|
const isAvailable =
|
||||||
hasP2PConfiguration(host.services.setting.currentSettings()) &&
|
hasP2PConfiguration(host.services.setting.currentSettings()) &&
|
||||||
(replicator.replicator?.server?.isServing ?? false);
|
replicator.transportLifecycle.isConnected;
|
||||||
if (!isAvailable) return false;
|
if (!isAvailable) return false;
|
||||||
if (!isChecking) {
|
if (!isChecking) {
|
||||||
void replicator.replicator?.replicateFromCommand(true);
|
void host.services.replicator.runFiniteReplicationActivity(
|
||||||
|
() => replicator.targetedTransfer.synchroniseConfiguredTargets(),
|
||||||
|
{ label: "replication" }
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -95,6 +95,7 @@ describe("useP2PReplicatorUI commands", () => {
|
|||||||
},
|
},
|
||||||
} as any;
|
} as any;
|
||||||
const p2p = {
|
const p2p = {
|
||||||
|
transportLifecycle: { isConnected: true },
|
||||||
replicator: {
|
replicator: {
|
||||||
server: { isServing: true },
|
server: { isServing: true },
|
||||||
openReplication,
|
openReplication,
|
||||||
@@ -209,6 +210,7 @@ describe("useP2PReplicatorUI commands", () => {
|
|||||||
remoteType: "COUCHDB",
|
remoteType: "COUCHDB",
|
||||||
remoteConfigurations: {},
|
remoteConfigurations: {},
|
||||||
};
|
};
|
||||||
|
const runFiniteReplicationActivity = vi.fn(async (task: () => unknown) => await task());
|
||||||
const host = {
|
const host = {
|
||||||
services: {
|
services: {
|
||||||
context: createServiceContext(),
|
context: createServiceContext(),
|
||||||
@@ -232,14 +234,18 @@ describe("useP2PReplicatorUI commands", () => {
|
|||||||
currentSettings: vi.fn(() => settings),
|
currentSettings: vi.fn(() => settings),
|
||||||
onSettingSaved: { addHandler: vi.fn() },
|
onSettingSaved: { addHandler: vi.fn() },
|
||||||
},
|
},
|
||||||
replicator: { runFiniteReplicationActivity: vi.fn() },
|
replicator: { runFiniteReplicationActivity },
|
||||||
},
|
},
|
||||||
} as any;
|
} as any;
|
||||||
|
const replicateFromCommand = vi.fn();
|
||||||
|
const synchroniseConfiguredTargets = vi.fn(async () => ({ status: "completed" }));
|
||||||
const p2p = {
|
const p2p = {
|
||||||
|
transportLifecycle: { isConnected: true },
|
||||||
|
targetedTransfer: { synchroniseConfiguredTargets },
|
||||||
replicator: {
|
replicator: {
|
||||||
server: { isServing: true },
|
server: { isServing: true },
|
||||||
openReplication: vi.fn(),
|
openReplication: vi.fn(),
|
||||||
replicateFromCommand: vi.fn(),
|
replicateFromCommand,
|
||||||
},
|
},
|
||||||
} as any;
|
} as any;
|
||||||
|
|
||||||
@@ -274,6 +280,13 @@ describe("useP2PReplicatorUI commands", () => {
|
|||||||
]) {
|
]) {
|
||||||
expect(commands.find(({ id }) => id === commandId)?.checkCallback?.(true)).toBe(true);
|
expect(commands.find(({ id }) => id === commandId)?.checkCallback?.(true)).toBe(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
commands.find(({ id }) => id === "p2p-sync-targets")?.checkCallback?.(false);
|
||||||
|
await vi.waitFor(() => expect(synchroniseConfiguredTargets).toHaveBeenCalledOnce());
|
||||||
|
expect(replicateFromCommand).not.toHaveBeenCalled();
|
||||||
|
expect(runFiniteReplicationActivity).toHaveBeenCalledWith(expect.any(Function), {
|
||||||
|
label: "replication",
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("does not open the P2P status pane automatically when the workspace becomes ready", async () => {
|
it("does not open the P2P status pane automatically when the workspace becomes ready", async () => {
|
||||||
|
|||||||
@@ -0,0 +1,57 @@
|
|||||||
|
import type { ObsidianLiveSyncSettings } from "@vrtmrz/livesync-commonlib/compat/common/types";
|
||||||
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
|
|
||||||
|
const runtimeMocks = vi.hoisted(() => {
|
||||||
|
const connect = vi.fn().mockResolvedValue(undefined);
|
||||||
|
const makeSureOpened = vi.fn().mockResolvedValue(undefined);
|
||||||
|
const start = vi.fn().mockResolvedValue(undefined);
|
||||||
|
const shutdown = vi.fn().mockResolvedValue(undefined);
|
||||||
|
const removeStatusListener = vi.fn();
|
||||||
|
const onEvent = vi.fn(() => removeStatusListener);
|
||||||
|
const WebPeerRuntime = vi.fn(function () {
|
||||||
|
return {
|
||||||
|
events: { onEvent },
|
||||||
|
p2p: {
|
||||||
|
transportLifecycle: { connect },
|
||||||
|
},
|
||||||
|
currentReplicator: { makeSureOpened },
|
||||||
|
start,
|
||||||
|
shutdown,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
WebPeerRuntime,
|
||||||
|
connect,
|
||||||
|
makeSureOpened,
|
||||||
|
onEvent,
|
||||||
|
removeStatusListener,
|
||||||
|
shutdown,
|
||||||
|
start,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
vi.mock("@/apps/webpeer/src/WebPeerRuntime", () => ({
|
||||||
|
WebPeerRuntime: runtimeMocks.WebPeerRuntime,
|
||||||
|
}));
|
||||||
|
|
||||||
|
import { P2PCheckSession } from "@/apps/webpeer/src/P2PCheckSession";
|
||||||
|
|
||||||
|
describe("P2P connection-check session", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("opens an explicit check through the P2P transport lifecycle", async () => {
|
||||||
|
const session = new P2PCheckSession();
|
||||||
|
|
||||||
|
await session.start({} as ObsidianLiveSyncSettings, "browser-check", vi.fn());
|
||||||
|
|
||||||
|
expect(runtimeMocks.start).toHaveBeenCalledOnce();
|
||||||
|
expect(runtimeMocks.connect).toHaveBeenCalledOnce();
|
||||||
|
expect(runtimeMocks.makeSureOpened).not.toHaveBeenCalled();
|
||||||
|
|
||||||
|
await session.stop();
|
||||||
|
expect(runtimeMocks.shutdown).toHaveBeenCalledOnce();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -34,7 +34,7 @@ describe("WebPeer runtime composition", () => {
|
|||||||
expect(runtime.context).toBe(context);
|
expect(runtime.context).toBe(context);
|
||||||
expect(runtime.services.context).toBe(context);
|
expect(runtime.services.context).toBe(context);
|
||||||
expect(runtime.events).toBe(context.events);
|
expect(runtime.events).toBe(context.events);
|
||||||
expect(runtime.currentReplicator).toBe(runtime.p2p.replicator);
|
expect(runtime.paneHost.p2p.transportLifecycle).toBe(runtime.p2p.transportLifecycle);
|
||||||
expect(runtime.paneHost.services).toBe(runtime.services);
|
expect(runtime.paneHost.services).toBe(runtime.services);
|
||||||
expect(runtime.paneHost.p2p).toBe(runtime.p2p);
|
expect(runtime.paneHost.p2p).toBe(runtime.p2p);
|
||||||
expect(runtime.paneHost.showPeerMenu).toBeTypeOf("function");
|
expect(runtime.paneHost.showPeerMenu).toBeTypeOf("function");
|
||||||
@@ -64,6 +64,34 @@ describe("WebPeer runtime composition", () => {
|
|||||||
expect(layoutReady).toHaveBeenCalledOnce();
|
expect(layoutReady).toHaveBeenCalledOnce();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("delegates automatic P2P startup to the resumed lifecycle handler", async () => {
|
||||||
|
vi.useFakeTimers();
|
||||||
|
try {
|
||||||
|
const runtime = new WebPeerRuntime({
|
||||||
|
store: createMemoryStore(),
|
||||||
|
});
|
||||||
|
vi.spyOn(runtime.services.setting, "loadSettings").mockResolvedValue(undefined);
|
||||||
|
vi.spyOn(runtime.services.setting, "currentSettings").mockReturnValue({
|
||||||
|
...DEFAULT_SETTINGS,
|
||||||
|
P2P_AutoStart: true,
|
||||||
|
P2P_Enabled: true,
|
||||||
|
});
|
||||||
|
vi.spyOn(runtime.services.database, "openDatabase").mockResolvedValue(true);
|
||||||
|
const onResumed = vi
|
||||||
|
.spyOn(runtime.services.appLifecycle, "onResumed")
|
||||||
|
.mockResolvedValue(true);
|
||||||
|
const open = vi.spyOn(runtime.p2p.transportLifecycle, "connect").mockResolvedValue(undefined);
|
||||||
|
|
||||||
|
await runtime.start();
|
||||||
|
|
||||||
|
expect(onResumed).toHaveBeenCalledOnce();
|
||||||
|
await vi.advanceTimersByTimeAsync(100);
|
||||||
|
expect(open).not.toHaveBeenCalled();
|
||||||
|
} finally {
|
||||||
|
vi.useRealTimers();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
it("rejects start when the browser-local database cannot be opened", async () => {
|
it("rejects start when the browser-local database cannot be opened", async () => {
|
||||||
const runtime = new WebPeerRuntime({
|
const runtime = new WebPeerRuntime({
|
||||||
store: createMemoryStore(),
|
store: createMemoryStore(),
|
||||||
|
|||||||
Reference in New Issue
Block a user