mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2026-09-09 04:07:07 +00:00
103 lines
3.7 KiB
TypeScript
103 lines
3.7 KiB
TypeScript
import type { RemoteDBSettings } from "@vrtmrz/livesync-commonlib/compat/common/types";
|
|
|
|
type EndpointProjection = readonly [kind: "url" | "invalid-url", value: string];
|
|
|
|
/**
|
|
* Compare the effective endpoint rather than inconsequential URI spelling.
|
|
* Fragments are not sent, query order is immaterial, and redundant trailing
|
|
* slashes do not bind a different adapter. Invalid input is retained verbatim
|
|
* and tagged so comparison remains deterministic and fails closed.
|
|
*/
|
|
function projectEndpoint(value: string): EndpointProjection {
|
|
try {
|
|
const endpoint = new URL(value);
|
|
endpoint.hash = "";
|
|
endpoint.searchParams.sort();
|
|
while (endpoint.pathname.length > 1 && endpoint.pathname.endsWith("/")) {
|
|
endpoint.pathname = endpoint.pathname.slice(0, -1);
|
|
}
|
|
return ["url", endpoint.toString()];
|
|
} catch {
|
|
return ["invalid-url", value];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Mirror the effective custom-header parser: trim each first name/value pair,
|
|
* ignore incomplete lines, and let the last duplicate name win. Sorting the
|
|
* resulting entries prevents line order alone from replacing a Replicator.
|
|
*/
|
|
function projectHeaders(value: string): readonly (readonly [name: string, value: string])[] {
|
|
const headers = new Map<string, string>();
|
|
for (const line of value.split("\n")) {
|
|
const [name, headerValue] = line.split(":", 2).map((part) => part.trim());
|
|
if (name && headerValue) {
|
|
headers.set(name, headerValue);
|
|
}
|
|
}
|
|
return [...headers.entries()].sort(([leftName, leftValue], [rightName, rightValue]) => {
|
|
const nameOrder = leftName.localeCompare(rightName);
|
|
return nameOrder || leftValue.localeCompare(rightValue);
|
|
});
|
|
}
|
|
|
|
function projectRemoteSecurity(settings: RemoteDBSettings) {
|
|
return settings.encrypt
|
|
? ([
|
|
"encrypted",
|
|
settings.passphrase,
|
|
settings.useDynamicIterationCount,
|
|
settings.E2EEAlgorithm,
|
|
settings.permitEmptyPassphrase,
|
|
] as const)
|
|
: (["plain"] as const);
|
|
}
|
|
|
|
/**
|
|
* Project the effective CouchDB connection settings to a private comparison identity.
|
|
* The returned value can contain credentials and must not be logged, persisted, or displayed.
|
|
*/
|
|
export function getCouchDBReplicatorConfigurationIdentity(settings: RemoteDBSettings): string {
|
|
const authentication = settings.useJWT
|
|
? ([
|
|
"jwt",
|
|
settings.jwtAlgorithm,
|
|
settings.jwtKey,
|
|
settings.jwtKid,
|
|
settings.jwtSub,
|
|
settings.jwtExpDuration,
|
|
] as const)
|
|
: (["basic", settings.couchDB_USER, settings.couchDB_PASSWORD] as const);
|
|
return JSON.stringify([
|
|
"couchdb",
|
|
projectEndpoint(settings.couchDB_URI),
|
|
settings.couchDB_DBNAME,
|
|
authentication,
|
|
projectHeaders(settings.couchDB_CustomHeaders),
|
|
settings.useRequestAPI,
|
|
settings.disableRequestURI,
|
|
projectRemoteSecurity(settings),
|
|
settings.enableCompression,
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Project the effective Object Storage connection settings to a private comparison identity.
|
|
* The returned value can contain credentials and must not be logged, persisted, or displayed.
|
|
*/
|
|
export function getObjectStorageReplicatorConfigurationIdentity(settings: RemoteDBSettings): string {
|
|
return JSON.stringify([
|
|
"s3",
|
|
projectEndpoint(settings.endpoint),
|
|
settings.bucket,
|
|
settings.bucketPrefix,
|
|
settings.region,
|
|
settings.accessKey,
|
|
settings.secretKey,
|
|
settings.forcePathStyle,
|
|
settings.useCustomRequestHandler,
|
|
projectHeaders(settings.bucketCustomHeaders),
|
|
projectRemoteSecurity(settings),
|
|
]);
|
|
}
|