mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2026-05-20 22:31:44 +00:00
v0.25.23.beta1
### Fixed (This should be backported to 0.25.22 if the beta phase is prolonged) - No longer larger files will not create a chunks during preparing `Reset Synchronisation on This Device`. ### Behaviour changes - Setup wizard is now more `goal-oriented`. Brand-new screens are introduced. - `Fetch everything` and `Rebuild everything` is now `Reset Synchronisation on This Device` and `Overwrite Server Data with This Device's Files`. - Remote configuration and E2EE settings are now separated to each modal dialogue. - Peer-to-Peer settings is also separated into its own modal dialogue. - Setup-URI, and Report for the Issue are now not copied to clipboard automatically. Instead, there are copy dialogue and buttons to copy them explicitly. - No longer optional features are introduced during the setup or `Reset Synchronisation on This Device`, `Overwrite Server Data with This Device's Files`. - We cannot preform `Fetch everything` and `Rebuild everything` (Removed, so the old name) without restarting Obsidian now. ### Miscellaneous - Setup QR Code generation is separated into a src/lib/src/API/processSetting.ts file. Please use it as a subrepository if you want to generate QR codes in your own application. - Setup-URI is also separated into a src/lib/src/API/processSetting.ts - Some direct access to web-APIs are now wrapped into the services layer. ### Dependency updates - Many dependencies are updated. Please see `package.json`. - As upgrading TypeScript, Fixed many UInt8Array<ArrayBuffer> and Uint8Array type mismatches.
This commit is contained in:
293
src/modules/features/SetupWizard/dialogs/utilCheckCouchDB.ts
Normal file
293
src/modules/features/SetupWizard/dialogs/utilCheckCouchDB.ts
Normal file
@@ -0,0 +1,293 @@
|
||||
import { requestToCouchDBWithCredentials } from "../../../../common/utils";
|
||||
import { $msg } from "../../../../lib/src/common/i18n";
|
||||
import { Logger } from "../../../../lib/src/common/logger";
|
||||
import type { ObsidianLiveSyncSettings } from "../../../../lib/src/common/types";
|
||||
import { parseHeaderValues } from "../../../../lib/src/common/utils";
|
||||
import { isCloudantURI } from "../../../../lib/src/pouchdb/utils_couchdb";
|
||||
import { generateCredentialObject } from "../../../../lib/src/replication/httplib";
|
||||
export type ResultMessage = { message: string; classes: string[] };
|
||||
export type ResultErrorMessage = { message: string; result: "error"; classes: string[] };
|
||||
export type ResultOk = { message: string; result: "ok"; value?: any };
|
||||
export type ResultError = { message: string; result: "error"; value: any; fixMessage: string; fix(): Promise<void> };
|
||||
export type ConfigCheckResult = ResultOk | ResultError | 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
|
||||
* @param key setting key to update
|
||||
* @param value setting value to update
|
||||
* @returns true if the update was successful, false otherwise
|
||||
*/
|
||||
async function updateRemoteSetting(setting: ObsidianLiveSyncSettings, key: string, value: any) {
|
||||
const customHeaders = parseHeaderValues(setting.couchDB_CustomHeaders);
|
||||
const credential = generateCredentialObject(setting);
|
||||
const res = await requestToCouchDBWithCredentials(
|
||||
setting.couchDB_URI,
|
||||
credential,
|
||||
undefined,
|
||||
key,
|
||||
value,
|
||||
undefined,
|
||||
customHeaders
|
||||
);
|
||||
if (res.status == 200) {
|
||||
return true;
|
||||
} else {
|
||||
return res.text || "Unknown error";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the CouchDB configuration and returns the results.
|
||||
* @param editingSettings
|
||||
* @returns Array of ConfigCheckResult
|
||||
*/
|
||||
export const checkConfig = async (editingSettings: ObsidianLiveSyncSettings) => {
|
||||
const result = [] as ConfigCheckResult[];
|
||||
const addMessage = (msg: string, classes: string[] = []) => {
|
||||
result.push({ message: msg, classes });
|
||||
};
|
||||
const addSuccess = (msg: string, value?: any) => {
|
||||
result.push({ message: msg, result: "ok", value });
|
||||
};
|
||||
const _addError = (message: string, fixMessage: string, fix: () => Promise<void>, value?: any) => {
|
||||
result.push({ message, result: "error", fixMessage, fix, value });
|
||||
};
|
||||
const addErrorMessage = (msg: string, classes: string[] = []) => {
|
||||
result.push({ message: msg, result: "error", classes });
|
||||
};
|
||||
|
||||
const addError = (message: string, fixMessage: string, key: string, expected: any) => {
|
||||
_addError(message, fixMessage, async () => {
|
||||
await updateRemoteSetting(editingSettings, key, expected);
|
||||
});
|
||||
};
|
||||
|
||||
addMessage($msg("obsidianLiveSyncSettingTab.logCheckingDbConfig"));
|
||||
|
||||
try {
|
||||
if (isCloudantURI(editingSettings.couchDB_URI)) {
|
||||
addMessage($msg("obsidianLiveSyncSettingTab.logCannotUseCloudant"));
|
||||
return result;
|
||||
}
|
||||
// Tip: Add log for cloudant as Logger($msg("obsidianLiveSyncSettingTab.logServerConfigurationCheck"));
|
||||
const customHeaders = parseHeaderValues(editingSettings.couchDB_CustomHeaders);
|
||||
const credential = generateCredentialObject(editingSettings);
|
||||
const r = await requestToCouchDBWithCredentials(
|
||||
editingSettings.couchDB_URI,
|
||||
credential,
|
||||
window.origin,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
customHeaders
|
||||
);
|
||||
const responseConfig = r.json;
|
||||
addMessage($msg("obsidianLiveSyncSettingTab.msgNotice"), ["ob-btn-config-head"]);
|
||||
addMessage($msg("obsidianLiveSyncSettingTab.msgIfConfigNotPersistent"), ["ob-btn-config-info"]);
|
||||
addMessage($msg("obsidianLiveSyncSettingTab.msgConfigCheck"), ["ob-btn-config-head"]);
|
||||
|
||||
const serverBanner = r.headers["server"] ?? r.headers["Server"] ?? "unknown";
|
||||
addMessage($msg("obsidianLiveSyncSettingTab.serverVersion", { info: serverBanner }));
|
||||
const versionMatch = serverBanner.match(/CouchDB(\/([0-9.]+))?/);
|
||||
const versionStr = versionMatch ? versionMatch[2] : "0.0.0";
|
||||
|
||||
// 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.
|
||||
|
||||
// Admin check
|
||||
// for database creation and deletion
|
||||
if (!(editingSettings.couchDB_USER in responseConfig.admins)) {
|
||||
addSuccess($msg("obsidianLiveSyncSettingTab.warnNoAdmin"));
|
||||
} else {
|
||||
addSuccess($msg("obsidianLiveSyncSettingTab.okAdminPrivileges"));
|
||||
}
|
||||
if (isGreaterThanOrEqual(versionStr, "3.2.0")) {
|
||||
// HTTP user-authorization check
|
||||
if (responseConfig?.chttpd?.require_valid_user != "true") {
|
||||
addError(
|
||||
$msg("obsidianLiveSyncSettingTab.errRequireValidUser"),
|
||||
$msg("obsidianLiveSyncSettingTab.msgSetRequireValidUser"),
|
||||
"chttpd/require_valid_user",
|
||||
"true"
|
||||
);
|
||||
} else {
|
||||
addSuccess($msg("obsidianLiveSyncSettingTab.okRequireValidUser"));
|
||||
}
|
||||
} else {
|
||||
if (responseConfig?.chttpd_auth?.require_valid_user != "true") {
|
||||
addError(
|
||||
$msg("obsidianLiveSyncSettingTab.errRequireValidUserAuth"),
|
||||
$msg("obsidianLiveSyncSettingTab.msgSetRequireValidUserAuth"),
|
||||
"chttpd_auth/require_valid_user",
|
||||
"true"
|
||||
);
|
||||
} else {
|
||||
addSuccess($msg("obsidianLiveSyncSettingTab.okRequireValidUserAuth"));
|
||||
}
|
||||
}
|
||||
// HTTPD check
|
||||
// Check Authentication header
|
||||
if (!responseConfig?.httpd["WWW-Authenticate"]) {
|
||||
addError(
|
||||
$msg("obsidianLiveSyncSettingTab.errMissingWwwAuth"),
|
||||
$msg("obsidianLiveSyncSettingTab.msgSetWwwAuth"),
|
||||
"httpd/WWW-Authenticate",
|
||||
'Basic realm="couchdb"'
|
||||
);
|
||||
} else {
|
||||
addSuccess($msg("obsidianLiveSyncSettingTab.okWwwAuth"));
|
||||
}
|
||||
if (isGreaterThanOrEqual(versionStr, "3.2.0")) {
|
||||
if (responseConfig?.chttpd?.enable_cors != "true") {
|
||||
addError(
|
||||
$msg("obsidianLiveSyncSettingTab.errEnableCorsChttpd"),
|
||||
$msg("obsidianLiveSyncSettingTab.msgEnableCorsChttpd"),
|
||||
"chttpd/enable_cors",
|
||||
"true"
|
||||
);
|
||||
} else {
|
||||
addSuccess($msg("obsidianLiveSyncSettingTab.okEnableCorsChttpd"));
|
||||
}
|
||||
} else {
|
||||
if (responseConfig?.httpd?.enable_cors != "true") {
|
||||
addError(
|
||||
$msg("obsidianLiveSyncSettingTab.errEnableCors"),
|
||||
$msg("obsidianLiveSyncSettingTab.msgEnableCors"),
|
||||
"httpd/enable_cors",
|
||||
"true"
|
||||
);
|
||||
} else {
|
||||
addSuccess($msg("obsidianLiveSyncSettingTab.okEnableCors"));
|
||||
}
|
||||
}
|
||||
// If the server is not cloudant, configure request size
|
||||
if (!isCloudantURI(editingSettings.couchDB_URI)) {
|
||||
// REQUEST SIZE
|
||||
if (Number(responseConfig?.chttpd?.max_http_request_size ?? 0) < 4294967296) {
|
||||
addError(
|
||||
$msg("obsidianLiveSyncSettingTab.errMaxRequestSize"),
|
||||
$msg("obsidianLiveSyncSettingTab.msgSetMaxRequestSize"),
|
||||
"chttpd/max_http_request_size",
|
||||
"4294967296"
|
||||
);
|
||||
} else {
|
||||
addSuccess($msg("obsidianLiveSyncSettingTab.okMaxRequestSize"));
|
||||
}
|
||||
if (Number(responseConfig?.couchdb?.max_document_size ?? 0) < 50000000) {
|
||||
addError(
|
||||
$msg("obsidianLiveSyncSettingTab.errMaxDocumentSize"),
|
||||
$msg("obsidianLiveSyncSettingTab.msgSetMaxDocSize"),
|
||||
"couchdb/max_document_size",
|
||||
"50000000"
|
||||
);
|
||||
} else {
|
||||
addSuccess($msg("obsidianLiveSyncSettingTab.okMaxDocumentSize"));
|
||||
}
|
||||
}
|
||||
// CORS check
|
||||
// checking connectivity for mobile
|
||||
if (responseConfig?.cors?.credentials != "true") {
|
||||
addError(
|
||||
$msg("obsidianLiveSyncSettingTab.errCorsCredentials"),
|
||||
$msg("obsidianLiveSyncSettingTab.msgSetCorsCredentials"),
|
||||
"cors/credentials",
|
||||
"true"
|
||||
);
|
||||
} else {
|
||||
addSuccess($msg("obsidianLiveSyncSettingTab.okCorsCredentials"));
|
||||
}
|
||||
const ConfiguredOrigins = ((responseConfig?.cors?.origins ?? "") + "").split(",");
|
||||
if (
|
||||
responseConfig?.cors?.origins == "*" ||
|
||||
(ConfiguredOrigins.indexOf("app://obsidian.md") !== -1 &&
|
||||
ConfiguredOrigins.indexOf("capacitor://localhost") !== -1 &&
|
||||
ConfiguredOrigins.indexOf("http://localhost") !== -1)
|
||||
) {
|
||||
addSuccess($msg("obsidianLiveSyncSettingTab.okCorsOrigins"));
|
||||
} else {
|
||||
const fixedValue = [
|
||||
...new Set([
|
||||
...ConfiguredOrigins.map((e) => e.trim()),
|
||||
"app://obsidian.md",
|
||||
"capacitor://localhost",
|
||||
"http://localhost",
|
||||
]),
|
||||
].join(",");
|
||||
addError(
|
||||
$msg("obsidianLiveSyncSettingTab.errCorsOrigins"),
|
||||
$msg("obsidianLiveSyncSettingTab.msgSetCorsOrigins"),
|
||||
"cors/origins",
|
||||
fixedValue
|
||||
);
|
||||
}
|
||||
addMessage($msg("obsidianLiveSyncSettingTab.msgConnectionCheck"), ["ob-btn-config-head"]);
|
||||
addMessage($msg("obsidianLiveSyncSettingTab.msgCurrentOrigin", { origin: window.location.origin }));
|
||||
|
||||
// Request header check
|
||||
const origins = ["app://obsidian.md", "capacitor://localhost", "http://localhost"];
|
||||
for (const org of origins) {
|
||||
const rr = await requestToCouchDBWithCredentials(
|
||||
editingSettings.couchDB_URI,
|
||||
credential,
|
||||
org,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
customHeaders
|
||||
);
|
||||
const responseHeaders = Object.fromEntries(
|
||||
Object.entries(rr.headers).map((e) => {
|
||||
e[0] = `${e[0]}`.toLowerCase();
|
||||
return e;
|
||||
})
|
||||
);
|
||||
addMessage($msg("obsidianLiveSyncSettingTab.msgOriginCheck", { org }));
|
||||
if (responseHeaders["access-control-allow-credentials"] != "true") {
|
||||
addErrorMessage($msg("obsidianLiveSyncSettingTab.errCorsNotAllowingCredentials"));
|
||||
} else {
|
||||
addSuccess($msg("obsidianLiveSyncSettingTab.okCorsCredentialsForOrigin"));
|
||||
}
|
||||
if (responseHeaders["access-control-allow-origin"] != org) {
|
||||
addErrorMessage(
|
||||
$msg("obsidianLiveSyncSettingTab.warnCorsOriginUnmatched", {
|
||||
from: origin,
|
||||
to: responseHeaders["access-control-allow-origin"],
|
||||
})
|
||||
);
|
||||
} else {
|
||||
addSuccess($msg("obsidianLiveSyncSettingTab.okCorsOriginMatched"));
|
||||
}
|
||||
}
|
||||
addMessage($msg("obsidianLiveSyncSettingTab.msgDone"), ["ob-btn-config-head"]);
|
||||
addMessage($msg("obsidianLiveSyncSettingTab.msgConnectionProxyNote"), ["ob-btn-config-info"]);
|
||||
addMessage($msg("obsidianLiveSyncSettingTab.logCheckingConfigDone"));
|
||||
} catch (ex: any) {
|
||||
if (ex?.status == 401) {
|
||||
addErrorMessage($msg("obsidianLiveSyncSettingTab.errAccessForbidden"));
|
||||
addErrorMessage($msg("obsidianLiveSyncSettingTab.errCannotContinueTest"));
|
||||
addMessage($msg("obsidianLiveSyncSettingTab.logCheckingConfigDone"));
|
||||
} else {
|
||||
addErrorMessage($msg("obsidianLiveSyncSettingTab.logCheckingConfigFailed"));
|
||||
Logger(ex);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
};
|
||||
Reference in New Issue
Block a user