mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2026-08-27 13:57:07 +00:00
feat: configure WebDAV journal remotes
This commit is contained in:
@@ -46,7 +46,6 @@ function serializeRemoteConfiguration(settings: ObsidianLiveSyncSettings): strin
|
||||
const configuration = defaultRemoteProviderRegistry.configurationFromSettings(type, settings);
|
||||
return defaultRemoteProviderRegistry.serialise(configuration);
|
||||
}
|
||||
|
||||
function setEmojiButton(button: ButtonComponent, emoji: string, tooltip: string) {
|
||||
button.setButtonText(emoji);
|
||||
button.setTooltip(tooltip, { delay: 10, placement: "top" });
|
||||
@@ -259,7 +258,7 @@ export function paneRemoteConfig(
|
||||
for (const config of Object.values(configs)) {
|
||||
const row = new Setting(listContainer)
|
||||
.setName(config.name)
|
||||
.setDesc(config.uri.split("@").pop() || ""); // Show host part for privacy
|
||||
.setDesc(describeRemoteConfiguration(config.uri));
|
||||
|
||||
if (config.id === this.editingSettings.activeConfigurationId) {
|
||||
row.nameEl.addClass("sls-active-remote-name");
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
import { pickBucketSyncSettings, pickCouchDBSyncSettings, pickP2PSyncSettings } from "@vrtmrz/livesync-commonlib/compat/common/utils";
|
||||
import {
|
||||
pickBucketSyncSettings,
|
||||
pickCouchDBSyncSettings,
|
||||
pickP2PSyncSettings,
|
||||
pickWebDAVSyncSettings,
|
||||
} from "@vrtmrz/livesync-commonlib/compat/common/utils";
|
||||
import type { ObsidianLiveSyncSettings } from "@vrtmrz/livesync-commonlib/compat/common/types";
|
||||
|
||||
// Keep the setting dialogue buffer aligned with the current core settings before persisting other dirty keys.
|
||||
@@ -11,6 +16,7 @@ export function syncActivatedRemoteSettings(
|
||||
remoteType: source.remoteType,
|
||||
activeConfigurationId: source.activeConfigurationId,
|
||||
...pickBucketSyncSettings(source),
|
||||
...pickWebDAVSyncSettings(source),
|
||||
...pickCouchDBSyncSettings(source),
|
||||
...pickP2PSyncSettings(source),
|
||||
});
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { DEFAULT_SETTINGS, REMOTE_COUCHDB, REMOTE_MINIO } from "@vrtmrz/livesync-commonlib/compat/common/types";
|
||||
import {
|
||||
DEFAULT_SETTINGS,
|
||||
REMOTE_COUCHDB,
|
||||
REMOTE_MINIO,
|
||||
REMOTE_WEBDAV,
|
||||
} from "@vrtmrz/livesync-commonlib/compat/common/types";
|
||||
import { syncActivatedRemoteSettings } from "./remoteConfigBuffer";
|
||||
|
||||
describe("syncActivatedRemoteSettings", () => {
|
||||
@@ -86,4 +91,34 @@ describe("syncActivatedRemoteSettings", () => {
|
||||
expect(target.couchDB_PASSWORD).toBe("current-pass");
|
||||
expect(target.couchDB_DBNAME).toBe("current-db");
|
||||
});
|
||||
|
||||
it("should copy the active WebDAV connection and Adaptive protocol into the editing buffer", () => {
|
||||
const target = {
|
||||
...DEFAULT_SETTINGS,
|
||||
remoteType: REMOTE_COUCHDB,
|
||||
activeConfigurationId: "old-remote",
|
||||
webDAVactiveConnectionURI: "sls+webdav://stale.invalid/",
|
||||
expectedRepositoryId: "",
|
||||
journalFormat: "opaque-v1" as const,
|
||||
packReadPolicy: "whole-pack" as const,
|
||||
};
|
||||
const source = {
|
||||
...DEFAULT_SETTINGS,
|
||||
remoteType: REMOTE_WEBDAV,
|
||||
activeConfigurationId: "remote-webdav",
|
||||
webDAVactiveConnectionURI: "sls+webdav://alice:secret@dav.example/dav?prefix=notes%2F",
|
||||
expectedRepositoryId: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
|
||||
journalFormat: "adaptive-v1" as const,
|
||||
packReadPolicy: "range" as const,
|
||||
};
|
||||
|
||||
syncActivatedRemoteSettings(target, source);
|
||||
|
||||
expect(target.remoteType).toBe(REMOTE_WEBDAV);
|
||||
expect(target.activeConfigurationId).toBe("remote-webdav");
|
||||
expect(target.webDAVactiveConnectionURI).toBe(source.webDAVactiveConnectionURI);
|
||||
expect(target.expectedRepositoryId).toBe(source.expectedRepositoryId);
|
||||
expect(target.journalFormat).toBe("adaptive-v1");
|
||||
expect(target.packReadPolicy).toBe("range");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
import {
|
||||
REMOTE_COUCHDB,
|
||||
REMOTE_MINIO,
|
||||
REMOTE_P2P,
|
||||
REMOTE_WEBDAV,
|
||||
type ObsidianLiveSyncSettings,
|
||||
} from "@vrtmrz/livesync-commonlib/compat/common/types";
|
||||
import {
|
||||
ConnectionStringParser,
|
||||
type RemoteConfigurationResult,
|
||||
} from "@vrtmrz/livesync-commonlib/compat/common/ConnectionString";
|
||||
import { parseWebDAVConnectionURI } from "@vrtmrz/livesync-commonlib/journal-storage";
|
||||
|
||||
export type ConfigurableRemoteType =
|
||||
| typeof REMOTE_COUCHDB
|
||||
| typeof REMOTE_MINIO
|
||||
| typeof REMOTE_WEBDAV
|
||||
| typeof REMOTE_P2P;
|
||||
|
||||
function publicOrigin(uri: string): string {
|
||||
const url = new URL(uri);
|
||||
return `${url.protocol}//${url.host}`;
|
||||
}
|
||||
|
||||
export function serializeRemoteConfiguration(settings: ObsidianLiveSyncSettings): string {
|
||||
switch (settings.remoteType) {
|
||||
case REMOTE_COUCHDB:
|
||||
return ConnectionStringParser.serialize({ type: "couchdb", settings });
|
||||
case REMOTE_MINIO:
|
||||
return ConnectionStringParser.serialize({ type: "s3", settings });
|
||||
case REMOTE_WEBDAV:
|
||||
return ConnectionStringParser.serialize({ type: "webdav", settings });
|
||||
case REMOTE_P2P:
|
||||
return ConnectionStringParser.serialize({ type: "p2p", settings });
|
||||
default:
|
||||
throw new Error("Unsupported remote type");
|
||||
}
|
||||
}
|
||||
|
||||
export function remoteTypeForRemoteConfiguration(parsed: RemoteConfigurationResult): ConfigurableRemoteType {
|
||||
switch (parsed.type) {
|
||||
case "couchdb":
|
||||
return REMOTE_COUCHDB;
|
||||
case "s3":
|
||||
return REMOTE_MINIO;
|
||||
case "webdav":
|
||||
return REMOTE_WEBDAV;
|
||||
case "p2p":
|
||||
return REMOTE_P2P;
|
||||
}
|
||||
}
|
||||
|
||||
export function suggestRemoteConfigurationName(parsed: RemoteConfigurationResult): string {
|
||||
if (parsed.type === "couchdb") {
|
||||
try {
|
||||
const url = new URL(parsed.settings.couchDB_URI);
|
||||
return `CouchDB ${url.host}`;
|
||||
} catch {
|
||||
return "Imported CouchDB";
|
||||
}
|
||||
}
|
||||
if (parsed.type === "s3") {
|
||||
return `S3 ${parsed.settings.bucket || parsed.settings.endpoint}`;
|
||||
}
|
||||
if (parsed.type === "webdav") {
|
||||
try {
|
||||
const endpoint = new URL(parseWebDAVConnectionURI(parsed.settings.webDAVactiveConnectionURI).endpoint);
|
||||
return `WebDAV ${endpoint.host}`;
|
||||
} catch {
|
||||
return "Imported WebDAV";
|
||||
}
|
||||
}
|
||||
return `P2P ${parsed.settings.P2P_roomID || "Remote"}`;
|
||||
}
|
||||
|
||||
export function describeRemoteConfiguration(uri: string): string {
|
||||
try {
|
||||
const parsed = ConnectionStringParser.parse(uri);
|
||||
if (parsed.type === "couchdb") return publicOrigin(parsed.settings.couchDB_URI);
|
||||
if (parsed.type === "s3") return publicOrigin(parsed.settings.endpoint);
|
||||
if (parsed.type === "webdav") {
|
||||
return publicOrigin(parseWebDAVConnectionURI(parsed.settings.webDAVactiveConnectionURI).endpoint);
|
||||
}
|
||||
return "P2P";
|
||||
} catch {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import {
|
||||
DEFAULT_SETTINGS,
|
||||
REMOTE_WEBDAV,
|
||||
type ObsidianLiveSyncSettings,
|
||||
} from "@vrtmrz/livesync-commonlib/compat/common/types";
|
||||
import { ConnectionStringParser } from "@vrtmrz/livesync-commonlib/compat/common/ConnectionString";
|
||||
import {
|
||||
describeRemoteConfiguration,
|
||||
remoteTypeForRemoteConfiguration,
|
||||
serializeRemoteConfiguration,
|
||||
suggestRemoteConfigurationName,
|
||||
} from "./remoteConfigurationEditor.ts";
|
||||
|
||||
describe("remote configuration editor helpers", () => {
|
||||
it("maps, names, and serialises an Adaptive WebDAV profile", () => {
|
||||
const repositoryId = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
|
||||
const settings = {
|
||||
...DEFAULT_SETTINGS,
|
||||
remoteType: REMOTE_WEBDAV,
|
||||
webDAVactiveConnectionURI:
|
||||
"sls+webdav://alice:secret@dav.example/remote.php/dav/files/alice?prefix=notes%2F",
|
||||
expectedRepositoryId: repositoryId,
|
||||
journalFormat: "adaptive-v1" as const,
|
||||
packReadPolicy: "range" as const,
|
||||
} as ObsidianLiveSyncSettings;
|
||||
|
||||
const uri = serializeRemoteConfiguration(settings);
|
||||
const parsed = ConnectionStringParser.parse(uri);
|
||||
|
||||
expect(parsed.type).toBe("webdav");
|
||||
expect(remoteTypeForRemoteConfiguration(parsed)).toBe(REMOTE_WEBDAV);
|
||||
expect(suggestRemoteConfigurationName(parsed)).toBe("WebDAV dav.example");
|
||||
expect(uri).toContain("journalFormat=adaptive-v1");
|
||||
expect(uri).toContain("packReadPolicy=range");
|
||||
expect(uri).toContain(`expectedRepositoryId=${repositoryId}`);
|
||||
});
|
||||
|
||||
it("does not expose WebDAV credentials or custom headers in the saved-connection description", () => {
|
||||
const uri =
|
||||
"sls+webdav://alice:secret@dav.example/remote.php/dav/files/alice" +
|
||||
"?prefix=notes%2F&headers=Authorization%3A+Bearer+private-token";
|
||||
|
||||
const description = describeRemoteConfiguration(uri);
|
||||
|
||||
expect(description).toBe("https://dav.example");
|
||||
expect(description).not.toContain("alice");
|
||||
expect(description).not.toContain("secret");
|
||||
expect(description).not.toContain("private-token");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user