mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-07-28 08:44:41 +08:00
fix: preserve WebDAV password display and validate MKCOL 405 (#1685)
* fix: preserve WebDAV password display and validate MKCOL 405 * fix: scope WebDAV password preservation to post-save refresh
This commit is contained in:
@@ -98,6 +98,20 @@ function formatDbCompatVersion(version?: number | null): string | null {
|
||||
return typeof version === "number" ? `db-v${version}` : null;
|
||||
}
|
||||
|
||||
function buildPasswordPreservationKey(values: {
|
||||
baseUrl?: string | null;
|
||||
username?: string | null;
|
||||
remoteRoot?: string | null;
|
||||
profile?: string | null;
|
||||
}) {
|
||||
return JSON.stringify({
|
||||
baseUrl: values.baseUrl ?? "",
|
||||
username: values.username ?? "",
|
||||
remoteRoot: values.remoteRoot ?? "cc-switch-sync",
|
||||
profile: values.profile ?? "default",
|
||||
});
|
||||
}
|
||||
|
||||
// ─── Types ──────────────────────────────────────────────────
|
||||
|
||||
type ActionState =
|
||||
@@ -167,6 +181,10 @@ export function WebdavSyncSection({
|
||||
const [passwordTouched, setPasswordTouched] = useState(false);
|
||||
const [justSaved, setJustSaved] = useState(false);
|
||||
const justSavedTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
const pendingPasswordPreservationRef = useRef<{
|
||||
key: string;
|
||||
password: string;
|
||||
} | null>(null);
|
||||
|
||||
// Local form state — credentials are only persisted on explicit "Save".
|
||||
const [form, setForm] = useState(() => ({
|
||||
@@ -205,13 +223,36 @@ export function WebdavSyncSection({
|
||||
// Sync form when config is loaded/updated from backend, but not while user is editing
|
||||
useEffect(() => {
|
||||
if (!config || dirty) return;
|
||||
setForm({
|
||||
baseUrl: config.baseUrl ?? "",
|
||||
username: config.username ?? "",
|
||||
password: config.password ?? "",
|
||||
remoteRoot: config.remoteRoot ?? "cc-switch-sync",
|
||||
profile: config.profile ?? "default",
|
||||
autoSync: config.autoSync ?? false,
|
||||
setForm(() => {
|
||||
const nextBaseUrl = config.baseUrl ?? "";
|
||||
const nextUsername = config.username ?? "";
|
||||
const nextRemoteRoot = config.remoteRoot ?? "cc-switch-sync";
|
||||
const nextProfile = config.profile ?? "default";
|
||||
const nextKey = buildPasswordPreservationKey({
|
||||
baseUrl: nextBaseUrl,
|
||||
username: nextUsername,
|
||||
remoteRoot: nextRemoteRoot,
|
||||
profile: nextProfile,
|
||||
});
|
||||
const shouldPreserveRedactedPassword =
|
||||
!config.password &&
|
||||
pendingPasswordPreservationRef.current?.key === nextKey &&
|
||||
!!pendingPasswordPreservationRef.current.password;
|
||||
|
||||
const nextPassword = shouldPreserveRedactedPassword
|
||||
? pendingPasswordPreservationRef.current!.password
|
||||
: (config.password ?? "");
|
||||
|
||||
pendingPasswordPreservationRef.current = null;
|
||||
|
||||
return {
|
||||
baseUrl: nextBaseUrl,
|
||||
username: nextUsername,
|
||||
password: nextPassword,
|
||||
remoteRoot: nextRemoteRoot,
|
||||
profile: nextProfile,
|
||||
autoSync: config.autoSync ?? false,
|
||||
};
|
||||
});
|
||||
setPasswordTouched(false);
|
||||
setPresetId(detectPreset(config.baseUrl ?? ""));
|
||||
@@ -289,12 +330,13 @@ export function WebdavSyncSection({
|
||||
enabled: true,
|
||||
baseUrl,
|
||||
username: form.username.trim(),
|
||||
password: form.password,
|
||||
// 未重新触碰密码时,提交空值让后端沿用已保存密码,表单里的值仅用于 UI 显示
|
||||
password: passwordTouched ? form.password : "",
|
||||
remoteRoot: form.remoteRoot.trim() || "cc-switch-sync",
|
||||
profile: form.profile.trim() || "default",
|
||||
autoSync: form.autoSync,
|
||||
};
|
||||
}, [form]);
|
||||
}, [form, passwordTouched]);
|
||||
|
||||
// ─── Handlers ───────────────────────────────────────────
|
||||
|
||||
@@ -326,6 +368,12 @@ export function WebdavSyncSection({
|
||||
return;
|
||||
}
|
||||
setActionState("saving");
|
||||
pendingPasswordPreservationRef.current = form.password
|
||||
? {
|
||||
key: buildPasswordPreservationKey(settings),
|
||||
password: form.password,
|
||||
}
|
||||
: null;
|
||||
try {
|
||||
await settingsApi.webdavSyncSaveSettings(settings, passwordTouched);
|
||||
setDirty(false);
|
||||
@@ -339,6 +387,7 @@ export function WebdavSyncSection({
|
||||
}, 2000);
|
||||
await queryClient.invalidateQueries();
|
||||
} catch (error) {
|
||||
pendingPasswordPreservationRef.current = null;
|
||||
toast.error(
|
||||
t("settings.webdavSync.saveFailed", {
|
||||
error: (error as Error)?.message ?? String(error),
|
||||
@@ -362,7 +411,7 @@ export function WebdavSyncSection({
|
||||
} finally {
|
||||
setActionState("idle");
|
||||
}
|
||||
}, [buildSettings, passwordTouched, queryClient, t]);
|
||||
}, [buildSettings, form.password, passwordTouched, queryClient, t]);
|
||||
|
||||
/** Fetch remote info, then open upload confirmation dialog. */
|
||||
const handleUploadClick = useCallback(async () => {
|
||||
|
||||
Reference in New Issue
Block a user