mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2026-07-25 05:53:00 +00:00
Refine P2P and manual setup workflows
This commit is contained in:
@@ -1,8 +1,15 @@
|
||||
import { readFile } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
import { discoverObsidianCli, requireObsidianBinary } from "../runner/environment.ts";
|
||||
import { waitForLiveSyncCoreReady } from "../runner/liveSyncWorkflow.ts";
|
||||
import { createE2eCouchDbPluginData, waitForLiveSyncCoreReady } from "../runner/liveSyncWorkflow.ts";
|
||||
import { assertMobileDialogueLayout, assertMobileNoticeLayout, setObsidianMobileTestMode } from "../runner/mobileUi.ts";
|
||||
import { startObsidianLiveSyncSession, type ObsidianLiveSyncSession } from "../runner/session.ts";
|
||||
import { captureObsidianDialogue, obsidianRemoteDebuggingPort, withObsidianPage } from "../runner/ui.ts";
|
||||
import {
|
||||
captureObsidianDialogue,
|
||||
captureObsidianPage,
|
||||
obsidianRemoteDebuggingPort,
|
||||
withObsidianPage,
|
||||
} from "../runner/ui.ts";
|
||||
import { createTemporaryVault } from "../runner/vault.ts";
|
||||
|
||||
const dialogRunStateKey = "__livesyncE2EDialogMount";
|
||||
@@ -199,6 +206,12 @@ async function verifyRemoteSelectionDialogue(mode: DialogueMode): Promise<string
|
||||
for (const label of ["CouchDB", "S3/MinIO/R2 Object Storage", "Peer-to-Peer only"]) {
|
||||
await dialogue.getByText(label, { exact: true }).waitFor({ state: "visible", timeout: uiTimeoutMs });
|
||||
}
|
||||
await dialogue
|
||||
.getByText(
|
||||
"No central data-storage server is required, but a signalling relay is required for peer discovery.",
|
||||
{ exact: false }
|
||||
)
|
||||
.waitFor({ state: "visible", timeout: uiTimeoutMs });
|
||||
await modal
|
||||
.getByRole("button", { name: "No, please take me back" })
|
||||
.waitFor({ state: "visible", timeout: uiTimeoutMs });
|
||||
@@ -226,6 +239,78 @@ async function verifyRemoteSelectionDialogue(mode: DialogueMode): Promise<string
|
||||
return screenshotPath;
|
||||
}
|
||||
|
||||
async function verifyCouchDBSettingsDialogue(mode: DialogueMode): Promise<string> {
|
||||
await openRemoteSelectionDialogue();
|
||||
await withObsidianPage(obsidianRemoteDebuggingPort(), async (page) => {
|
||||
const remoteSelection = page.locator(".modal-container").filter({
|
||||
has: page.locator(".modal-title").filter({ hasText: "Enter Server Information" }),
|
||||
});
|
||||
await remoteSelection
|
||||
.locator("label")
|
||||
.filter({ hasText: "CouchDB" })
|
||||
.locator('input[type="radio"]')
|
||||
.first()
|
||||
.check({ timeout: uiTimeoutMs });
|
||||
await remoteSelection
|
||||
.getByRole("button", { name: "Continue to CouchDB setup", exact: true })
|
||||
.click({ timeout: uiTimeoutMs });
|
||||
});
|
||||
const screenshotPath = await captureObsidianDialogue(
|
||||
obsidianRemoteDebuggingPort(),
|
||||
`setup-couchdb-dialogue${mode === "mobile" ? "-mobile" : ""}.png`,
|
||||
async (page) => {
|
||||
const modal = page.locator(".modal-container").filter({
|
||||
has: page.locator(".modal-title").filter({ hasText: "CouchDB Configuration" }),
|
||||
});
|
||||
await modal.waitFor({ state: "visible", timeout: uiTimeoutMs });
|
||||
for (const label of [
|
||||
"Check server requirements",
|
||||
"Test connection and save",
|
||||
"Save without connecting",
|
||||
"Cancel",
|
||||
]) {
|
||||
await modal.getByRole("button", { name: label, exact: true }).waitFor({
|
||||
state: "visible",
|
||||
timeout: uiTimeoutMs,
|
||||
});
|
||||
}
|
||||
await modal
|
||||
.getByText(
|
||||
"This optional check uses Obsidian's internal request API and sends the credentials above to the CouchDB server.",
|
||||
{ exact: false }
|
||||
)
|
||||
.waitFor({ state: "visible", timeout: uiTimeoutMs });
|
||||
await modal
|
||||
.getByText("CouchDB validates the database name when you connect.", { exact: false })
|
||||
.waitFor({ state: "visible", timeout: uiTimeoutMs });
|
||||
if ((await modal.getByRole("button", { name: "Continue anyway", exact: true }).count()) !== 0) {
|
||||
throw new Error("CouchDB onboarding still exposes the ambiguous Continue anyway action.");
|
||||
}
|
||||
const buttonGroups = modal.locator(".button-group");
|
||||
for (let index = 0; index < (await buttonGroups.count()); index++) {
|
||||
const flexDirection = await buttonGroups.nth(index).evaluate((element) => {
|
||||
return getComputedStyle(element).flexDirection;
|
||||
});
|
||||
if (flexDirection !== "column") {
|
||||
throw new Error(`Expected vertical CouchDB actions, received ${flexDirection}.`);
|
||||
}
|
||||
}
|
||||
if (mode === "mobile") {
|
||||
await assertMobileDialogueLayout(page, modal, "CouchDB settings dialogue");
|
||||
}
|
||||
}
|
||||
);
|
||||
await withObsidianPage(obsidianRemoteDebuggingPort(), async (page) => {
|
||||
const modal = page.locator(".modal-container").filter({
|
||||
has: page.locator(".modal-title").filter({ hasText: "CouchDB Configuration" }),
|
||||
});
|
||||
await modal.getByRole("button", { name: "Cancel", exact: true }).click({ timeout: uiTimeoutMs });
|
||||
await modal.waitFor({ state: "hidden", timeout: uiTimeoutMs });
|
||||
});
|
||||
await assertDialogueRunCompleted();
|
||||
return screenshotPath;
|
||||
}
|
||||
|
||||
async function verifySetupUriDialogue(mode: DialogueMode): Promise<string> {
|
||||
await openSetupUriDialogue();
|
||||
const screenshotPath = await captureObsidianDialogue(
|
||||
@@ -337,10 +422,14 @@ async function main(): Promise<void> {
|
||||
cliBinary: cli.binary,
|
||||
vault,
|
||||
startupGraceMs: Number(process.env.E2E_OBSIDIAN_STARTUP_GRACE_MS ?? 1000),
|
||||
pluginData: {
|
||||
doctorProcessedVersion: "0.25.27",
|
||||
isConfigured: true,
|
||||
liveSync: false,
|
||||
pluginData: createE2eCouchDbPluginData(
|
||||
{
|
||||
uri: "http://127.0.0.1:5984",
|
||||
username: "",
|
||||
password: "",
|
||||
dbName: "dialog-mounts-ui-only",
|
||||
},
|
||||
{
|
||||
notifyThresholdOfRemoteStorageSize: -1,
|
||||
syncOnStart: false,
|
||||
syncOnSave: false,
|
||||
@@ -348,9 +437,35 @@ async function main(): Promise<void> {
|
||||
syncOnFileOpen: false,
|
||||
syncAfterMerge: false,
|
||||
periodicReplication: false,
|
||||
},
|
||||
}
|
||||
),
|
||||
});
|
||||
await waitForLiveSyncCoreReady(cli.binary, session.cliEnv);
|
||||
try {
|
||||
await waitForLiveSyncCoreReady(cli.binary, session.cliEnv);
|
||||
} catch (error) {
|
||||
const screenshot = await captureObsidianPage(
|
||||
obsidianRemoteDebuggingPort(),
|
||||
"dialog-mounts-core-not-ready.png",
|
||||
async () => undefined
|
||||
);
|
||||
console.error(`Core readiness diagnostic screenshot: ${screenshot}`);
|
||||
const persistedSettings = JSON.parse(
|
||||
await readFile(join(session.install.pluginDir, "data.json"), "utf8")
|
||||
) as Record<string, unknown>;
|
||||
console.error(
|
||||
`Persisted readiness settings: ${JSON.stringify({
|
||||
isConfigured: persistedSettings.isConfigured,
|
||||
remoteType: persistedSettings.remoteType,
|
||||
settingVersion: persistedSettings.settingVersion,
|
||||
couchDB_URI: persistedSettings.couchDB_URI,
|
||||
couchDB_DBNAME: persistedSettings.couchDB_DBNAME,
|
||||
remoteConfigurationCount: Object.keys(
|
||||
(persistedSettings.remoteConfigurations as Record<string, unknown> | undefined) ?? {}
|
||||
).length,
|
||||
})}`
|
||||
);
|
||||
throw error;
|
||||
}
|
||||
|
||||
const remoteSizeScreenshots = await verifyRemoteSizeNoticeAndDialogue();
|
||||
console.log(
|
||||
@@ -359,6 +474,10 @@ async function main(): Promise<void> {
|
||||
|
||||
const remoteScreenshot = await verifyRemoteSelectionDialogue("desktop");
|
||||
console.log(`Remote selection dialogue mounted and closed successfully. Screenshot: ${remoteScreenshot}`);
|
||||
const couchDBScreenshot = await verifyCouchDBSettingsDialogue("desktop");
|
||||
console.log(
|
||||
`CouchDB settings mode exposed explicit connection, unverified-save, and server-check actions. Screenshot: ${couchDBScreenshot}`
|
||||
);
|
||||
const setupUriScreenshot = await verifySetupUriDialogue("desktop");
|
||||
console.log(`Setup URI dialogue mounted and closed successfully. Screenshot: ${setupUriScreenshot}`);
|
||||
|
||||
@@ -372,6 +491,10 @@ async function main(): Promise<void> {
|
||||
console.log(
|
||||
`Mobile remote selection dialogue passed viewport, safe-area, touch-target, and close-control checks. Screenshot: ${mobileRemoteScreenshot}`
|
||||
);
|
||||
const mobileCouchDBScreenshot = await verifyCouchDBSettingsDialogue("mobile");
|
||||
console.log(
|
||||
`Mobile CouchDB settings dialogue passed viewport, touch-target, and vertical-action checks. Screenshot: ${mobileCouchDBScreenshot}`
|
||||
);
|
||||
const mobileSetupUriScreenshot = await verifySetupUriDialogue("mobile");
|
||||
console.log(
|
||||
`Mobile Setup URI dialogue passed viewport, safe-area, and touch-target checks. Screenshot: ${mobileSetupUriScreenshot}`
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
import { assertNoHorizontalOverflow } from "@vrtmrz/obsidian-test-session";
|
||||
import { assertLocatorWithinViewport, assertNoHorizontalOverflow } from "@vrtmrz/obsidian-test-session";
|
||||
import type { Page } from "playwright";
|
||||
import { discoverObsidianCli, requireObsidianBinary } from "../runner/environment.ts";
|
||||
import { createE2eObsidianDeviceLocalState, waitForLiveSyncCoreReady } from "../runner/liveSyncWorkflow.ts";
|
||||
import {
|
||||
createE2eCouchDbPluginData,
|
||||
createE2eObsidianDeviceLocalState,
|
||||
waitForLiveSyncCoreReady,
|
||||
} from "../runner/liveSyncWorkflow.ts";
|
||||
import { setObsidianMobileTestMode } from "../runner/mobileUi.ts";
|
||||
import { startObsidianLiveSyncSession, type ObsidianLiveSyncSession } from "../runner/session.ts";
|
||||
import { captureObsidianPage, obsidianRemoteDebuggingPort, withObsidianPage } from "../runner/ui.ts";
|
||||
import { createTemporaryVault } from "../runner/vault.ts";
|
||||
@@ -8,7 +14,10 @@ import { createTemporaryVault } from "../runner/vault.ts";
|
||||
const uiTimeoutMs = Number(process.env.E2E_OBSIDIAN_P2P_PANE_TIMEOUT_MS ?? 10000);
|
||||
|
||||
type ObsidianTestApp = {
|
||||
commands?: { executeCommandById(commandId: string): boolean };
|
||||
commands?: {
|
||||
commands?: Record<string, unknown>;
|
||||
executeCommandById(commandId: string): boolean;
|
||||
};
|
||||
};
|
||||
|
||||
type ObsidianTestGlobal = typeof globalThis & { app?: ObsidianTestApp };
|
||||
@@ -25,9 +34,9 @@ async function openP2PStatusPane(): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
async function verifyP2PStatusPane(): Promise<string> {
|
||||
async function verifyP2PStatusPane(filename: string, mobile: boolean): Promise<string> {
|
||||
await openP2PStatusPane();
|
||||
return await captureObsidianPage(obsidianRemoteDebuggingPort(), "p2p-status-pane.png", async (page) => {
|
||||
return await captureObsidianPage(obsidianRemoteDebuggingPort(), filename, async (page) => {
|
||||
const heading = page.getByRole("heading", { name: "Signalling Status" }).last();
|
||||
await heading.waitFor({ state: "visible", timeout: uiTimeoutMs });
|
||||
const pane = heading.locator(
|
||||
@@ -39,42 +48,113 @@ async function verifyP2PStatusPane(): Promise<string> {
|
||||
timeout: uiTimeoutMs,
|
||||
});
|
||||
await assertNoHorizontalOverflow(page, pane, { label: "P2P status pane" });
|
||||
if (mobile) {
|
||||
await assertLocatorWithinViewport(page, pane, { label: "mobile P2P status pane" });
|
||||
}
|
||||
await dismissOpenNotices(page);
|
||||
});
|
||||
}
|
||||
|
||||
async function main(): Promise<void> {
|
||||
const binary = requireObsidianBinary();
|
||||
const cli = discoverObsidianCli();
|
||||
if (!cli.binary) {
|
||||
throw new Error(`Could not find obsidian-cli. Checked paths: ${cli.checked.join(", ")}`);
|
||||
async function assertP2PUIIsOptIn(): Promise<void> {
|
||||
await withObsidianPage(obsidianRemoteDebuggingPort(), async (page) => {
|
||||
const state = await page.evaluate(() => {
|
||||
const commands = (globalThis as ObsidianTestGlobal).app?.commands?.commands ?? {};
|
||||
return {
|
||||
currentCommand: commands["obsidian-livesync:open-p2p-server-status"] !== undefined,
|
||||
legacyCommand: commands["obsidian-livesync:open-p2p-replicator"] !== undefined,
|
||||
};
|
||||
});
|
||||
if (!state.currentCommand) {
|
||||
throw new Error("The current P2P status command was not registered.");
|
||||
}
|
||||
if (state.legacyCommand) {
|
||||
throw new Error("The retired P2P pane command is still exposed.");
|
||||
}
|
||||
if ((await page.locator(".workspace-leaf-content[data-type='p2p-server-status']:visible").count()) !== 0) {
|
||||
throw new Error("The P2P status pane opened automatically for an unconfigured CouchDB user.");
|
||||
}
|
||||
if ((await page.locator(".livesync-ribbon-p2p-server-status").count()) !== 0) {
|
||||
throw new Error("The P2P ribbon icon was shown without a P2P configuration.");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function assertConfiguredP2PUIIsAvailable(): Promise<void> {
|
||||
await withObsidianPage(obsidianRemoteDebuggingPort(), async (page) => {
|
||||
await page.locator(".livesync-ribbon-p2p-server-status").waitFor({
|
||||
state: "visible",
|
||||
timeout: uiTimeoutMs,
|
||||
});
|
||||
if ((await page.locator(".workspace-leaf-content[data-type='p2p-server-status']:visible").count()) !== 0) {
|
||||
throw new Error("The configured P2P status pane opened before the user requested it.");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function dismissOpenNotices(page: Page): Promise<void> {
|
||||
const deadline = Date.now() + uiTimeoutMs;
|
||||
let quietSince = Date.now();
|
||||
while (Date.now() < deadline) {
|
||||
const notices = page.locator(".notice:visible");
|
||||
if ((await notices.count()) === 0) {
|
||||
if (Date.now() - quietSince >= 500) {
|
||||
return;
|
||||
}
|
||||
await page.waitForTimeout(100);
|
||||
continue;
|
||||
}
|
||||
quietSince = Date.now();
|
||||
const closeButton = notices.first().locator(".notice-close-button");
|
||||
if ((await closeButton.count()) > 0) {
|
||||
await closeButton.click({ force: true, timeout: uiTimeoutMs });
|
||||
} else {
|
||||
// Obsidian 1.12 does not render a separate close control for every
|
||||
// Notice; clicking the Notice itself is its standard dismiss action.
|
||||
await notices.first().click({ force: true, position: { x: 2, y: 2 }, timeout: uiTimeoutMs });
|
||||
}
|
||||
}
|
||||
throw new Error("Transient Obsidian notices did not become quiet before the P2P status screenshot.");
|
||||
}
|
||||
|
||||
const basePluginData = createE2eCouchDbPluginData(
|
||||
{
|
||||
uri: "http://127.0.0.1:5984",
|
||||
username: "",
|
||||
password: "",
|
||||
dbName: "p2p-pane-ui-only",
|
||||
},
|
||||
{
|
||||
notifyThresholdOfRemoteStorageSize: -1,
|
||||
periodicReplication: false,
|
||||
P2P_Enabled: false,
|
||||
P2P_AutoStart: false,
|
||||
syncAfterMerge: false,
|
||||
syncOnEditorSave: false,
|
||||
syncOnFileOpen: false,
|
||||
syncOnSave: false,
|
||||
syncOnStart: false,
|
||||
}
|
||||
);
|
||||
|
||||
async function withP2PSession(
|
||||
binary: string,
|
||||
cliBinary: string,
|
||||
pluginData: Record<string, unknown>,
|
||||
verify: () => Promise<void>
|
||||
): Promise<void> {
|
||||
const vault = await createTemporaryVault();
|
||||
let session: ObsidianLiveSyncSession | undefined;
|
||||
try {
|
||||
session = await startObsidianLiveSyncSession({
|
||||
binary,
|
||||
cliBinary: cli.binary,
|
||||
cliBinary,
|
||||
vault,
|
||||
startupGraceMs: Number(process.env.E2E_OBSIDIAN_STARTUP_GRACE_MS ?? 1000),
|
||||
pluginData: {
|
||||
doctorProcessedVersion: "0.25.27",
|
||||
isConfigured: true,
|
||||
liveSync: false,
|
||||
notifyThresholdOfRemoteStorageSize: -1,
|
||||
periodicReplication: false,
|
||||
P2P_Enabled: false,
|
||||
P2P_AutoStart: false,
|
||||
syncAfterMerge: false,
|
||||
syncOnEditorSave: false,
|
||||
syncOnFileOpen: false,
|
||||
syncOnSave: false,
|
||||
syncOnStart: false,
|
||||
},
|
||||
pluginData,
|
||||
localStorageEntries: createE2eObsidianDeviceLocalState(vault.name),
|
||||
});
|
||||
await waitForLiveSyncCoreReady(cli.binary, session.cliEnv);
|
||||
const screenshot = await verifyP2PStatusPane();
|
||||
console.log(`P2P status pane mounted without network fixtures. Screenshot: ${screenshot}`);
|
||||
await waitForLiveSyncCoreReady(cliBinary, session.cliEnv);
|
||||
await verify();
|
||||
} finally {
|
||||
if (session) {
|
||||
await session.app.stop();
|
||||
@@ -83,6 +163,41 @@ async function main(): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
async function main(): Promise<void> {
|
||||
const binary = requireObsidianBinary();
|
||||
const cli = discoverObsidianCli();
|
||||
if (!cli.binary) {
|
||||
throw new Error(`Could not find obsidian-cli. Checked paths: ${cli.checked.join(", ")}`);
|
||||
}
|
||||
|
||||
await withP2PSession(binary, cli.binary, basePluginData, async () => {
|
||||
await assertP2PUIIsOptIn();
|
||||
});
|
||||
|
||||
await withP2PSession(
|
||||
binary,
|
||||
cli.binary,
|
||||
{
|
||||
...basePluginData,
|
||||
P2P_roomID: "configured-p2p-room",
|
||||
P2P_passphrase: "configured-p2p-passphrase",
|
||||
},
|
||||
async () => {
|
||||
await assertConfiguredP2PUIIsAvailable();
|
||||
const desktopScreenshot = await verifyP2PStatusPane("p2p-status-pane.png", false);
|
||||
await setObsidianMobileTestMode(obsidianRemoteDebuggingPort(), true, uiTimeoutMs);
|
||||
try {
|
||||
const mobileScreenshot = await verifyP2PStatusPane("p2p-status-pane-mobile.png", true);
|
||||
console.log(
|
||||
`Configured P2P status UI remained opt-in and was reachable on desktop and mobile. Screenshots: ${desktopScreenshot}, ${mobileScreenshot}`
|
||||
);
|
||||
} finally {
|
||||
await setObsidianMobileTestMode(obsidianRemoteDebuggingPort(), false, uiTimeoutMs);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
main().catch((error: unknown) => {
|
||||
console.error(error instanceof Error ? error.stack : error);
|
||||
process.exit(1);
|
||||
|
||||
@@ -419,6 +419,47 @@ async function waitForDetectedPeer(port: number): Promise<void> {
|
||||
});
|
||||
}
|
||||
|
||||
async function capturePeerActionsMenu(port: number): Promise<string> {
|
||||
await withObsidianPage(port, async (page) => {
|
||||
const moreActions = page.getByRole("button", { name: /^More actions for /u }).first();
|
||||
await moreActions.waitFor({ state: "visible", timeout: uiTimeoutMs });
|
||||
await moreActions.click({ timeout: uiTimeoutMs });
|
||||
const menu = page.locator(".menu:visible").last();
|
||||
await menu.waitFor({ state: "visible", timeout: uiTimeoutMs });
|
||||
for (const label of [
|
||||
"Synchronise when this device connects",
|
||||
"Follow whenever this device connects",
|
||||
"Include in the P2P synchronisation command",
|
||||
]) {
|
||||
await menu.getByText(label, { exact: true }).waitFor({ state: "visible", timeout: uiTimeoutMs });
|
||||
}
|
||||
const layout = await menu.evaluate((element) => {
|
||||
const rect = element.getBoundingClientRect();
|
||||
return {
|
||||
insideViewport:
|
||||
rect.left >= 0 &&
|
||||
rect.top >= 0 &&
|
||||
rect.right <= document.documentElement.clientWidth &&
|
||||
rect.bottom <= document.documentElement.clientHeight,
|
||||
hasHorizontalOverflow: element.scrollWidth > element.clientWidth,
|
||||
};
|
||||
});
|
||||
if (!layout.insideViewport || layout.hasHorizontalOverflow) {
|
||||
throw new Error(`P2P peer actions menu did not fit the viewport: ${JSON.stringify(layout)}`);
|
||||
}
|
||||
});
|
||||
const screenshot = await captureObsidianElement(
|
||||
port,
|
||||
"guide-p2p-setup-peer-actions-menu.png",
|
||||
(page) => page.locator(".menu:visible").last()
|
||||
);
|
||||
await withObsidianPage(port, async (page) => {
|
||||
await page.keyboard.press("Escape");
|
||||
await page.locator(".menu:visible").waitFor({ state: "hidden", timeout: uiTimeoutMs });
|
||||
});
|
||||
return screenshot;
|
||||
}
|
||||
|
||||
async function captureNote(port: number, path: string, text: string, filename: string): Promise<string> {
|
||||
await withObsidianPage(port, async (page) => {
|
||||
await page.evaluate((notePath) => {
|
||||
@@ -496,12 +537,15 @@ async function main(): Promise<void> {
|
||||
screenshots.push(
|
||||
await captureNote(portB, noteFromFirst, "P2P from the first device", "guide-p2p-setup-first-to-second.png")
|
||||
);
|
||||
console.log("P2P workflow: initial Fetch from the first device completed.");
|
||||
|
||||
await writeNote(context.cliBinary, sessionB.cliEnv, noteFromSecond, secondContent);
|
||||
await reconnectP2PStatus(portA);
|
||||
await reconnectP2PStatus(portB);
|
||||
await waitForDetectedPeer(portA);
|
||||
screenshots.push(await openP2PStatus(portA, "guide-p2p-setup-devices-connected.png"));
|
||||
screenshots.push(await capturePeerActionsMenu(portA));
|
||||
console.log("P2P workflow: peer actions menu verified; starting the return journey.");
|
||||
let returnJourneyFinished = false;
|
||||
const returnJourneyAcceptor = acceptConnectionRequests(
|
||||
[portA, portB],
|
||||
@@ -510,10 +554,13 @@ async function main(): Promise<void> {
|
||||
);
|
||||
try {
|
||||
await replicateFromStatusPane(portA);
|
||||
console.log("P2P workflow: return replication requested; waiting for the second device's note.");
|
||||
await waitForPathContent(vaultA, noteFromSecond, secondContent);
|
||||
console.log("P2P workflow: return note reached the first device.");
|
||||
} finally {
|
||||
returnJourneyFinished = true;
|
||||
await returnJourneyAcceptor;
|
||||
console.log("P2P workflow: return connection approval loop stopped.");
|
||||
}
|
||||
screenshots.push(
|
||||
await captureNote(
|
||||
@@ -526,9 +573,11 @@ async function main(): Promise<void> {
|
||||
|
||||
console.log(`P2P Setup URI and two-device roundtrip succeeded. Screenshots: ${screenshots.join(", ")}`);
|
||||
} finally {
|
||||
console.log("P2P workflow: stopping tracked Obsidian sessions.");
|
||||
await stopSessions(context).catch((error: unknown) => {
|
||||
console.warn(error instanceof Error ? error.message : error);
|
||||
});
|
||||
console.log("P2P workflow: disposing temporary Vaults.");
|
||||
await vaultA.dispose();
|
||||
await vaultB.dispose();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user