Share application adapter and diagnostic helpers

This commit is contained in:
vorotamoroz committed 2026-10-29 15:04:09 +00:00
1 parent 4027417bf5
commit b9dcb0d4c7
26 files changed
+937 -305

No files matched your search

@@ -13,6 +13,7 @@ import { generateCredentialObject } from "@vrtmrz/livesync-commonlib/compat/repl
import { compatGlobal } from "@vrtmrz/livesync-commonlib/compat/common/coreEnvFunctions";
import { isUnauthorizedError } from "@vrtmrz/livesync-commonlib/compat/common/utils.doc";
import { normaliseCouchDBConfiguration } from "@/common/couchdbConfiguration";
import { isCouchDBVersionAtLeast, normaliseResponseHeaderKeys } from "@/modules/features/couchDBDiagnosticValues";
export const checkConfig = async (
checkResultDiv: HTMLDivElement | undefined,
@@ -89,19 +90,6 @@ export const checkConfig = async (
addResult($msg("obsidianLiveSyncSettingTab.serverVersion", { info: serverBanner }));
const versionMatch = serverBanner.match(/CouchDB(\/([0-9.]+))?/);
const versionStr = versionMatch ? versionMatch[2] : "0.0.0";
const versionParts = `${versionStr}.0.0.0`.split(".");
// Compare version string with the target version.
// version must be a string like "3.2.1" or "3.10.2", and must be two or three parts.
function isGreaterThanOrEqual(version: string) {
const targetParts = version.split(".");
for (let i = 0; i < targetParts.length; i++) {
// compare as number if possible (so 3.10 > 3.2, 3.10.1b > 3.10.1a)
const result = versionParts[i].localeCompare(targetParts[i], undefined, { numeric: true });
if (result > 0) return true;
if (result < 0) return false;
}
return true;
}
// Admin check
// for database creation and deletion
if (!(editingSettings.couchDB_USER in responseConfig.admins)) {
@@ -109,7 +97,7 @@ export const checkConfig = async (
} else {
addResult($msg("obsidianLiveSyncSettingTab.okAdminPrivileges"));
}
if (isGreaterThanOrEqual("3.2.0")) {
if (isCouchDBVersionAtLeast(versionStr, "3.2.0")) {
// HTTP user-authorization check
if (responseConfig?.chttpd?.require_valid_user != "true") {
isSuccessful = false;
@@ -148,7 +136,7 @@ export const checkConfig = async (
} else {
addResult($msg("obsidianLiveSyncSettingTab.okWwwAuth"));
}
if (isGreaterThanOrEqual("3.2.0")) {
if (isCouchDBVersionAtLeast(versionStr, "3.2.0")) {
if (responseConfig?.chttpd?.enable_cors != "true") {
isSuccessful = false;
addResult($msg("obsidianLiveSyncSettingTab.errEnableCorsChttpd"));
@@ -240,12 +228,7 @@ export const checkConfig = async (
undefined,
customHeaders
);
const responseHeaders = Object.fromEntries(
Object.entries(rr.headers).map((e) => {
e[0] = `${e[0]}`.toLowerCase();
return e;
})
);
const responseHeaders = normaliseResponseHeaderKeys(rr.headers);
addResult($msg("obsidianLiveSyncSettingTab.msgOriginCheck", { org }));
if (responseHeaders["access-control-allow-credentials"] != "true") {
addResult($msg("obsidianLiveSyncSettingTab.errCorsNotAllowingCredentials"));
@@ -8,6 +8,7 @@ import { generateCredentialObject } from "@vrtmrz/livesync-commonlib/compat/repl
import { compatGlobal } from "@vrtmrz/livesync-commonlib/compat/common/coreEnvFunctions";
import { isUnauthorizedError } from "@vrtmrz/livesync-commonlib/compat/common/utils.doc";
import { normaliseCouchDBConfiguration } from "@/common/couchdbConfiguration";
import { isCouchDBVersionAtLeast, normaliseResponseHeaderKeys } from "@/modules/features/couchDBDiagnosticValues";
export type ResultMessage = { message: string; classes: string[] };
export type ResultErrorMessage = { message: string; result: "error"; classes: string[] };
@@ -26,24 +27,6 @@ export type ConfigCheckResult<T = unknown, U = unknown> =
| ResultError<U>
| ResultMessage
| ResultErrorMessage;
/**
* Compares two version strings to determine if the baseVersion is greater than or equal to the version.
* @param baseVersion a.b.c format
* @param version a.b.c format
* @returns true if baseVersion is greater than or equal to version, false otherwise
*/
function isGreaterThanOrEqual(baseVersion: string, version: string) {
const versionParts = `${baseVersion}.0.0.0`.split(".");
const targetParts = version.split(".");
for (let i = 0; i < targetParts.length; i++) {
// compare as number if possible (so 3.10 > 3.2, 3.10.1b > 3.10.1a)
const result = versionParts[i].localeCompare(targetParts[i], undefined, { numeric: true });
if (result > 0) return true;
if (result < 0) return false;
}
return true;
}
/**
* Updates the remote CouchDB setting with the given key and value.
* @param setting Connection settings
@@ -153,7 +136,7 @@ export const checkConfig = async (editingSettings: ObsidianLiveSyncSettings) =>
} else {
addSuccess($msg("obsidianLiveSyncSettingTab.okAdminPrivileges"));
}
if (isGreaterThanOrEqual(versionStr, "3.2.0")) {
if (isCouchDBVersionAtLeast(versionStr, "3.2.0")) {
// HTTP user-authorization check
if (responseConfig?.chttpd?.require_valid_user != "true") {
addError(
@@ -189,7 +172,7 @@ export const checkConfig = async (editingSettings: ObsidianLiveSyncSettings) =>
} else {
addSuccess($msg("obsidianLiveSyncSettingTab.okWwwAuth"));
}
if (isGreaterThanOrEqual(versionStr, "3.2.0")) {
if (isCouchDBVersionAtLeast(versionStr, "3.2.0")) {
if (responseConfig?.chttpd?.enable_cors != "true") {
addError(
$msg("obsidianLiveSyncSettingTab.errEnableCorsChttpd"),
@@ -287,12 +270,7 @@ export const checkConfig = async (editingSettings: ObsidianLiveSyncSettings) =>
undefined,
customHeaders
);
const responseHeaders = Object.fromEntries(
Object.entries(rr.headers).map((e) => {
e[0] = `${e[0]}`.toLowerCase();
return e;
})
);
const responseHeaders = normaliseResponseHeaderKeys(rr.headers);
addMessage($msg("obsidianLiveSyncSettingTab.msgOriginCheck", { org }));
if (responseHeaders["access-control-allow-credentials"] != "true") {
addErrorMessage($msg("obsidianLiveSyncSettingTab.errCorsNotAllowingCredentials"));
@@ -0,0 +1,19 @@
export function isCouchDBVersionAtLeast(baseVersion: string, targetVersion: string) {
const versionParts = `${baseVersion}.0.0.0`.split(".");
const targetParts = targetVersion.split(".");
for (let i = 0; i < targetParts.length; i++) {
const result = versionParts[i].localeCompare(targetParts[i], undefined, { numeric: true });
if (result > 0) return true;
if (result < 0) return false;
}
return true;
}
export function normaliseResponseHeaderKeys<T>(headers: Record<string, T>) {
return Object.fromEntries(
Object.entries(headers).map((entry) => {
entry[0] = `${entry[0]}`.toLowerCase();
return entry;
})
);
}
@@ -0,0 +1,29 @@
import { describe, expect, it } from "vitest";
import { isCouchDBVersionAtLeast, normaliseResponseHeaderKeys } from "./couchDBDiagnosticValues";
describe("CouchDB diagnostic values", () => {
describe("version comparison", () => {
it.each([
["3.10.2", "3.2.0", true],
["3.2.0", "3.10.0", false],
["3.2", "3.2.0", true],
["3.10.1b", "3.10.1a", true],
["3.10.1a", "3.10.1b", false],
["3.x", "3.2", true],
])("compares %s with %s", (baseVersion, targetVersion, expected) => {
expect(isCouchDBVersionAtLeast(baseVersion, targetVersion)).toBe(expected);
});
});
describe("response header keys", () => {
it("lower-cases keys while retaining last collision values and entry order", () => {
const headers = { Server: "first", server: "last", Zed: "z", Alpha: "a" };
const normalised = normaliseResponseHeaderKeys(headers);
expect(normalised).toEqual({ server: "last", zed: "z", alpha: "a" });
expect(Object.keys(normalised)).toEqual(["server", "zed", "alpha"]);
expect(headers).toEqual({ Server: "first", server: "last", Zed: "z", Alpha: "a" });
});
});
});