mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2026-09-22 02:27:07 +00:00
Migrate LiveSync flows to provider-owned resources
This commit is contained in:
@@ -13,11 +13,10 @@ import {
|
||||
} from "@vrtmrz/livesync-commonlib/compat/common/types";
|
||||
import { delay, isObjectDifferent, sizeToHumanReadable } from "@vrtmrz/livesync-commonlib/compat/common/utils";
|
||||
import { Logger } from "@vrtmrz/livesync-commonlib/compat/common/logger";
|
||||
import { checkSyncInfo } from "@vrtmrz/livesync-commonlib/compat/pouchdb/negotiation";
|
||||
import { testCrypt } from "octagonal-wheels/encryption/encryption";
|
||||
import ObsidianLiveSyncPlugin from "@/main.ts";
|
||||
import { scheduleTask } from "@/common/utils.ts";
|
||||
import { LiveSyncCouchDBReplicator } from "@vrtmrz/livesync-commonlib/compat/replication/couchdb/LiveSyncReplicator";
|
||||
import { REMOTE_RESOURCE_KINDS } from "@vrtmrz/livesync-commonlib/replication";
|
||||
import {
|
||||
type AllSettingItemKey,
|
||||
type AllStringItemKey,
|
||||
@@ -78,6 +77,7 @@ import type {
|
||||
import { createExtraMenuSettingSpecGroup, createGeneralSettingSpecGroups } from "./GeneralSettingSpecs.ts";
|
||||
import { SetupManager } from "@/modules/features/SetupManager.ts";
|
||||
import { isP2PMainRemote } from "@/common/remoteConfiguration.ts";
|
||||
import { withOwnedRemoteResource } from "@/common/ownedRemoteResource.ts";
|
||||
|
||||
// For creating a document
|
||||
// const toc = new Set<string>();
|
||||
@@ -340,15 +340,18 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
|
||||
|
||||
async testConnection(settingOverride: Partial<ObsidianLiveSyncSettings> = {}): Promise<void> {
|
||||
const trialSetting = { ...this.editingSettings, ...settingOverride };
|
||||
const replicator = await this.services.replicator.getNewReplicator(trialSetting);
|
||||
if (!replicator) {
|
||||
Logger("No replicator available for the current settings.", LOG_LEVEL_NOTICE);
|
||||
const probe = await this.services.replicator.createRemoteResource(
|
||||
REMOTE_RESOURCE_KINDS.CONNECTION,
|
||||
trialSetting
|
||||
);
|
||||
if (!probe) {
|
||||
Logger("Connection testing is unavailable for the current settings.", LOG_LEVEL_NOTICE);
|
||||
return;
|
||||
}
|
||||
await replicator.tryConnectRemote(trialSetting);
|
||||
const status = await replicator.getRemoteStatus(trialSetting);
|
||||
if (status) {
|
||||
if (status.estimatedSize) {
|
||||
await withOwnedRemoteResource(probe, async (ownedProbe) => {
|
||||
await ownedProbe.check({ createIfMissing: true, showResult: true });
|
||||
const status = await ownedProbe.getStatus();
|
||||
if (status && status.estimatedSize) {
|
||||
Logger(
|
||||
$msg("obsidianLiveSyncSettingTab.logEstimatedSize", {
|
||||
size: sizeToHumanReadable(status.estimatedSize),
|
||||
@@ -356,7 +359,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
|
||||
LOG_LEVEL_NOTICE
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
closeSetting() {
|
||||
@@ -954,27 +957,23 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
|
||||
visibility:
|
||||
this.isConfiguredAs("remoteType", REMOTE_COUCHDB) || this.isConfiguredAs("remoteType", REMOTE_MINIO),
|
||||
}) as OnUpdateResult;
|
||||
// E2EE Function
|
||||
/**
|
||||
* Checks the edited CouchDB passphrase through an owned synchronisation-
|
||||
* information resource. A missing document may be created by the check.
|
||||
*/
|
||||
checkWorkingPassphrase = async (): Promise<boolean> => {
|
||||
if (this.editingSettings.remoteType == REMOTE_MINIO) return true;
|
||||
|
||||
const settingForCheck: RemoteDBSettings = {
|
||||
...this.editingSettings,
|
||||
};
|
||||
const replicator = this.services.replicator.getNewReplicator(settingForCheck);
|
||||
if (!(replicator instanceof LiveSyncCouchDBReplicator)) return true;
|
||||
|
||||
const db = await replicator.connectRemoteCouchDBWithSetting(
|
||||
settingForCheck,
|
||||
this.services.API.isMobile(),
|
||||
true
|
||||
const resource = await this.services.replicator.createRemoteResource(
|
||||
REMOTE_RESOURCE_KINDS.SYNCHRONISATION_INFORMATION,
|
||||
settingForCheck
|
||||
);
|
||||
if (typeof db === "string") {
|
||||
Logger($msg("obsidianLiveSyncSettingTab.logCheckPassphraseFailed", { db }), LOG_LEVEL_NOTICE);
|
||||
return false;
|
||||
}
|
||||
if (!resource) return true;
|
||||
try {
|
||||
if (await checkSyncInfo(db.db)) {
|
||||
if (await resource.check()) {
|
||||
// Logger($msg("obsidianLiveSyncSettingTab.logDatabaseConnected"), LOG_LEVEL_NOTICE);
|
||||
return true;
|
||||
} else {
|
||||
@@ -982,7 +981,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
|
||||
return false;
|
||||
}
|
||||
} finally {
|
||||
await db.db.close();
|
||||
await resource.dispose();
|
||||
}
|
||||
};
|
||||
isPassphraseValid = async () => {
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { DEFAULT_SETTINGS, REMOTE_COUCHDB } from "@vrtmrz/livesync-commonlib/compat/common/types";
|
||||
import { REMOTE_RESOURCE_KINDS } from "@vrtmrz/livesync-commonlib/replication";
|
||||
|
||||
const negotiationMocks = vi.hoisted(() => ({
|
||||
checkSyncInfo: vi.fn(async () => true),
|
||||
}));
|
||||
const settingsInitialisationMocks = vi.hoisted(() => ({
|
||||
applySettingsWithInitialisationChoice: vi.fn(),
|
||||
}));
|
||||
@@ -38,10 +36,6 @@ vi.mock("@/common/events.ts", () => ({
|
||||
eventHub: { emitEvent: vi.fn(), onEvent: vi.fn() },
|
||||
}));
|
||||
vi.mock("@/modules/features/SetupManager.ts", () => ({ SetupManager: class {} }));
|
||||
vi.mock("@vrtmrz/livesync-commonlib/compat/pouchdb/negotiation", () => negotiationMocks);
|
||||
vi.mock("@vrtmrz/livesync-commonlib/compat/replication/couchdb/LiveSyncReplicator", () => ({
|
||||
LiveSyncCouchDBReplicator: class {},
|
||||
}));
|
||||
vi.mock("./LiveSyncSetting.ts", () => ({ LiveSyncSetting: class {} }));
|
||||
vi.mock("./SettingPane.ts", () => ({
|
||||
enableOnly: vi.fn(() => vi.fn()),
|
||||
@@ -63,7 +57,6 @@ vi.mock("./PanePowerUsers.ts", () => ({ panePowerUsers: vi.fn() }));
|
||||
vi.mock("./PanePatches.ts", () => ({ panePatches: vi.fn() }));
|
||||
vi.mock("./PaneMaintenance.ts", () => ({ paneMaintenance: vi.fn() }));
|
||||
|
||||
import { LiveSyncCouchDBReplicator } from "@vrtmrz/livesync-commonlib/compat/replication/couchdb/LiveSyncReplicator";
|
||||
import { ObsidianLiveSyncSettingTab } from "./ObsidianLiveSyncSettingTab";
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -71,19 +64,15 @@ beforeEach(() => {
|
||||
});
|
||||
|
||||
describe("ObsidianLiveSyncSettingTab passphrase verification", () => {
|
||||
it("closes the finite remote connection after checking synchronisation information", async () => {
|
||||
const remoteDatabase = {
|
||||
close: vi.fn(async () => undefined),
|
||||
};
|
||||
const replicator = Object.assign(new LiveSyncCouchDBReplicator({} as never), {
|
||||
connectRemoteCouchDBWithSetting: vi.fn(async () => ({ db: remoteDatabase })),
|
||||
});
|
||||
it("awaits and disposes the owned synchronisation-information resource", async () => {
|
||||
const check = vi.fn(async () => true);
|
||||
const dispose = vi.fn(async () => undefined);
|
||||
const createRemoteResource = vi.fn(async () => ({ check, dispose }));
|
||||
const plugin = {
|
||||
app: {},
|
||||
core: {
|
||||
services: {
|
||||
API: { isMobile: vi.fn(() => false) },
|
||||
replicator: { getNewReplicator: vi.fn(() => replicator) },
|
||||
replicator: { createRemoteResource },
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -97,8 +86,76 @@ describe("ObsidianLiveSyncSettingTab passphrase verification", () => {
|
||||
|
||||
await expect(tab.checkWorkingPassphrase()).resolves.toBe(true);
|
||||
|
||||
expect(negotiationMocks.checkSyncInfo).toHaveBeenCalledWith(remoteDatabase);
|
||||
expect(remoteDatabase.close).toHaveBeenCalledOnce();
|
||||
expect(createRemoteResource).toHaveBeenCalledWith(
|
||||
REMOTE_RESOURCE_KINDS.SYNCHRONISATION_INFORMATION,
|
||||
expect.objectContaining({ remoteType: REMOTE_COUCHDB })
|
||||
);
|
||||
expect(check).toHaveBeenCalledOnce();
|
||||
expect(dispose).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it("does not use the general Replicator factory solely to verify synchronisation information", async () => {
|
||||
const getNewReplicator = vi.fn(() => Promise.reject(new Error("must not construct a Replicator")));
|
||||
const createRemoteResource = vi.fn(async () => ({
|
||||
check: vi.fn(async () => true),
|
||||
dispose: vi.fn(async () => undefined),
|
||||
}));
|
||||
const plugin = {
|
||||
app: {},
|
||||
core: {
|
||||
services: {
|
||||
replicator: { createRemoteResource, getNewReplicator },
|
||||
},
|
||||
},
|
||||
};
|
||||
const tab = new ObsidianLiveSyncSettingTab({} as never, plugin as never);
|
||||
Object.assign(tab, {
|
||||
_editingSettings: {
|
||||
...DEFAULT_SETTINGS,
|
||||
remoteType: REMOTE_COUCHDB,
|
||||
},
|
||||
});
|
||||
|
||||
await expect(tab.checkWorkingPassphrase()).resolves.toBe(true);
|
||||
|
||||
expect(getNewReplicator).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("ObsidianLiveSyncSettingTab connection testing", () => {
|
||||
it("uses and disposes the flow-specific connection probe without borrowing a Replicator", async () => {
|
||||
const check = vi.fn(async () => ({ ok: true as const }));
|
||||
const getStatus = vi.fn(async () => ({ estimatedSize: 1024 }));
|
||||
const dispose = vi.fn(async () => undefined);
|
||||
const createRemoteResource = vi.fn(async () => ({ check, getStatus, dispose }));
|
||||
const getNewReplicator = vi.fn(() => Promise.reject(new Error("must not borrow a Replicator")));
|
||||
const plugin = {
|
||||
app: {},
|
||||
core: {
|
||||
services: {
|
||||
replicator: { createRemoteResource, getNewReplicator },
|
||||
},
|
||||
},
|
||||
};
|
||||
const tab = new ObsidianLiveSyncSettingTab({} as never, plugin as never);
|
||||
Object.assign(tab, {
|
||||
_editingSettings: {
|
||||
...DEFAULT_SETTINGS,
|
||||
remoteType: REMOTE_COUCHDB,
|
||||
couchDB_DBNAME: "saved",
|
||||
},
|
||||
});
|
||||
|
||||
await expect(tab.testConnection({ couchDB_DBNAME: "trial" })).resolves.toBeUndefined();
|
||||
|
||||
expect(createRemoteResource).toHaveBeenCalledWith(
|
||||
REMOTE_RESOURCE_KINDS.CONNECTION,
|
||||
expect.objectContaining({ remoteType: REMOTE_COUCHDB, couchDB_DBNAME: "trial" })
|
||||
);
|
||||
expect(check).toHaveBeenCalledWith({ createIfMissing: true, showResult: true });
|
||||
expect(getStatus).toHaveBeenCalledOnce();
|
||||
expect(dispose).toHaveBeenCalledOnce();
|
||||
expect(getNewReplicator).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user