fix(settings): restore archive password list

Load the stored archive password list through a dedicated trusted IPC channel when extraction settings are opened. Preserve write-only drafts across settings and account updates, guard unsaved edits against late responses, and invalidate stale loads during backup imports. Prepare version 2.0.46 with public release notes.
This commit is contained in:
Sucukdeluxe
2026-08-20 00:45:46 +02:00
parent 09ea7803f3
commit 08f017e8c4
16 changed files with 164 additions and 14 deletions
+8
View File
@@ -13,6 +13,7 @@ import {
AccountCredentialCheckInput,
AccountSecretRequest,
AccountSecretResult,
ArchivePasswordListResult,
DebridAccountStatus,
DebridProvider,
DuplicatePolicy,
@@ -562,6 +563,13 @@ export class AppController {
return { secret };
}
public getArchivePasswordList(): ArchivePasswordListResult {
const passwords = this.settings.archivePasswordList;
const entryCount = passwords.split(/\r?\n/).filter((entry) => entry.trim().length > 0).length;
this.audit("INFO", "Archiv-Passwortliste explizit angezeigt", { entryCount });
return { passwords };
}
public async checkAccountCredentials(input: AccountCredentialCheckInput): Promise<DebridAccountStatus> {
const redactions = collectAccountStatusRedactionValues(this.settings, input);
if (input.kind === "realdebrid-api" || input.kind === "realdebrid-web") {
+2 -1
View File
@@ -451,7 +451,8 @@ function registerIpcHandlers(): void {
handleTrusted(IPC_CHANNELS.REVEAL_ACCOUNT_SECRET, (_event: IpcMainInvokeEvent, rawRequest: unknown) => {
return controller.revealAccountSecret(validateAccountSecretRequest(rawRequest));
});
handleTrusted(IPC_CHANNELS.ADD_LINKS, (_event: IpcMainInvokeEvent, payload: AddLinksPayload) => {
handleTrusted(IPC_CHANNELS.GET_ARCHIVE_PASSWORD_LIST, () => controller.getArchivePasswordList());
handleTrusted(IPC_CHANNELS.ADD_LINKS, (_event: IpcMainInvokeEvent, payload: AddLinksPayload) => {
validatePlainObject(payload ?? {}, "payload");
validateString(payload?.rawText, "rawText");
if (payload.packageName !== undefined) {
+1
View File
@@ -110,6 +110,7 @@ const api: ElectronApi = {
checkDebridAccounts: (scope: AccountCheckScope = "active"): Promise<DebridAccountStatus[]> => ipcRenderer.invoke(IPC_CHANNELS.CHECK_DEBRID_ACCOUNTS, scope),
checkAccountCredentials: (input: AccountCredentialCheckInput): Promise<DebridAccountStatus> => ipcRenderer.invoke(IPC_CHANNELS.CHECK_ACCOUNT_CREDENTIALS, input),
revealAccountSecret: (input: AccountSecretRequest): Promise<AccountSecretResult> => ipcRenderer.invoke(IPC_CHANNELS.REVEAL_ACCOUNT_SECRET, input),
getArchivePasswordList: () => ipcRenderer.invoke(IPC_CHANNELS.GET_ARCHIVE_PASSWORD_LIST),
retryExtraction: (packageId: string): Promise<void> => ipcRenderer.invoke(IPC_CHANNELS.RETRY_EXTRACTION, packageId),
extractNow: (packageId: string): Promise<void> => ipcRenderer.invoke(IPC_CHANNELS.EXTRACT_NOW, packageId),
resetPackage: (packageId: string): Promise<void> => ipcRenderer.invoke(IPC_CHANNELS.RESET_PACKAGE, packageId),
+42 -8
View File
@@ -1536,6 +1536,7 @@ export function App(): ReactElement {
const updateCheckGenerationRef = useRef(0);
const settingsDirtyRef = useRef(false);
const writeOnlySettingsDirtyRef = useRef(new Set<"archivePasswordList" | "notifyUrl">());
const archivePasswordLoadGenerationRef = useRef(0);
const settingsDraftRevisionRef = useRef(0);
useEffect(() => {
@@ -1770,6 +1771,32 @@ export function App(): ReactElement {
}, timeoutMs);
}, []);
useEffect(() => {
if (settingsSubTab !== "extract" || writeOnlySettingsDirtyRef.current.has("archivePasswordList")) {
archivePasswordLoadGenerationRef.current += 1;
return;
}
const generation = archivePasswordLoadGenerationRef.current + 1;
archivePasswordLoadGenerationRef.current = generation;
void window.rd.getArchivePasswordList().then(({ passwords }) => {
if (archivePasswordLoadGenerationRef.current !== generation || writeOnlySettingsDirtyRef.current.has("archivePasswordList")) {
return;
}
setSettingsDraft((current) => current.archivePasswordList === passwords
? current
: { ...current, archivePasswordList: passwords });
}).catch(() => {
if (archivePasswordLoadGenerationRef.current === generation) {
showToast("Archiv-Passwortliste konnte nicht geladen werden", 2800);
}
});
return () => {
if (archivePasswordLoadGenerationRef.current === generation) {
archivePasswordLoadGenerationRef.current += 1;
}
};
}, [settingsSubTab, snapshot.settings.archivePasswordListConfigured, showToast]);
const applyHistoryEntries = useCallback((entries: HistoryEntry[]): void => {
const availableIds = entries.map((entry) => entry.id);
const availableSet = new Set(availableIds);
@@ -1913,7 +1940,7 @@ export function App(): ReactElement {
if (state.settings.columnOrder?.length > 0) {
setColumnOrder(state.settings.columnOrder);
}
setSettingsDraft(createSettingsDraft(state.settings));
setSettingsDraft((current) => createSettingsDraft(state.settings, current));
writeOnlySettingsDirtyRef.current.clear();
settingsDirtyRef.current = false;
panelDirtyRevisionRef.current = 0;
@@ -1969,9 +1996,9 @@ export function App(): ReactElement {
if (next.settings.columnOrder?.length > 0) {
setColumnOrder(next.settings.columnOrder);
}
if (!settingsDirtyRef.current) {
setSettingsDraft(createSettingsDraft(next.settings));
}
if (!settingsDirtyRef.current) {
setSettingsDraft((current) => createSettingsDraft(next.settings, current));
}
latestStateRef.current = null;
}
}, flushDelay);
@@ -2627,8 +2654,11 @@ export function App(): ReactElement {
});
};
const applyPersistedSettings = (result: RendererSettings): void => {
setSettingsDraft(createSettingsDraft(result));
const applyPersistedSettings = (result: RendererSettings, preserveWriteOnlyValues = true): void => {
if (!preserveWriteOnlyValues) {
archivePasswordLoadGenerationRef.current += 1;
}
setSettingsDraft((current) => createSettingsDraft(result, preserveWriteOnlyValues ? current : undefined));
writeOnlySettingsDirtyRef.current.clear();
settingsDirtyRef.current = false;
panelDirtyRevisionRef.current = 0;
@@ -2783,6 +2813,7 @@ export function App(): ReactElement {
return;
}
await performQuickAction(async () => {
await persistDraftSettings();
const result = await window.rd.replaceAccount(buildAccountReplaceCommand(editSnapshot));
setSnapshot((current) => ({ ...current, settings: result.settings, accounts: result.accounts }));
applyPersistedSettings(result.settings);
@@ -2813,6 +2844,7 @@ export function App(): ReactElement {
}
const selectedOption = dialogSnapshot.kind ? findAccountOption(dialogSnapshot.kind) : null;
await performQuickAction(async () => {
await persistDraftSettings();
if (dialogSnapshot.kind === "realdebrid-web") {
const accountId = `rdw_${crypto.randomUUID().replace(/-/g, "")}`;
const request = buildRealDebridWebCreateLoginRequest(dialogSnapshot, accountId);
@@ -2966,6 +2998,7 @@ export function App(): ReactElement {
const confirmed = await askConfirmPrompt({ title: "Key entfernen", message: `Soll der Debrid-Link-Key ${key.masked} wirklich entfernt werden?`, confirmLabel: "Entfernen", danger: true });
if (!confirmed) return;
await performQuickAction(async () => {
await persistDraftSettings();
const result = await window.rd.deleteAccount({ action: "delete", kind: "debridlink-api", accountId: key.id });
setSnapshot((current) => ({ ...current, settings: result.settings, accounts: result.accounts }));
applyPersistedSettings(result.settings);
@@ -3055,6 +3088,7 @@ export function App(): ReactElement {
return;
}
await performQuickAction(async () => {
await persistDraftSettings();
for (const selectedRow of rows) {
const result = await window.rd.deleteAccount(buildAccountDeleteCommand(selectedRow.editTarget));
setSnapshot((current) => ({ ...current, settings: result.settings, accounts: result.accounts }));
@@ -4304,7 +4338,7 @@ export function App(): ReactElement {
// fresh settings and re-seed the draft so the UI reflects the import.
if (!result.relaunch) {
const fresh = await window.rd.getSnapshot();
applyPersistedSettings(fresh.settings);
applyPersistedSettings(fresh.settings, false);
}
} else if (result.message !== "Abgebrochen") {
showToast(`Sicherung laden fehlgeschlagen: ${result.message}`, 3000);
@@ -4338,7 +4372,7 @@ export function App(): ReactElement {
try {
const result = await window.rd.importOnlineBackup(key);
const fresh = await window.rd.getSnapshot();
applyPersistedSettings(fresh.settings);
applyPersistedSettings(fresh.settings, false);
setOnlineBackupDialog(null);
showToast(result.message, 4000);
} catch {
+1
View File
@@ -74,6 +74,7 @@ export const IPC_CHANNELS = {
CHECK_DEBRID_ACCOUNTS: "app:check-debrid-accounts",
CHECK_ACCOUNT_CREDENTIALS: "app:check-account-credentials",
REVEAL_ACCOUNT_SECRET: "app:reveal-account-secret",
GET_ARCHIVE_PASSWORD_LIST: "app:get-archive-password-list",
RETRY_EXTRACTION: "queue:retry-extraction",
EXTRACT_NOW: "queue:extract-now",
RESET_PACKAGE: "queue:reset-package",
+2
View File
@@ -5,6 +5,7 @@ import type {
AccountCredentialCheckInput,
AccountSecretRequest,
AccountSecretResult,
ArchivePasswordListResult,
AccountCreateCommand,
AccountDeleteCommand,
AccountReplaceCommand,
@@ -136,6 +137,7 @@ export interface ElectronApi {
checkDebridAccounts: (scope?: AccountCheckScope) => Promise<DebridAccountStatus[]>;
checkAccountCredentials: (input: AccountCredentialCheckInput) => Promise<DebridAccountStatus>;
revealAccountSecret: (input: AccountSecretRequest) => Promise<AccountSecretResult>;
getArchivePasswordList: () => Promise<ArchivePasswordListResult>;
retryExtraction: (packageId: string) => Promise<void>;
extractNow: (packageId: string) => Promise<void>;
resetPackage: (packageId: string) => Promise<void>;
+6 -2
View File
@@ -389,8 +389,12 @@ export interface AccountSecretRequest {
export interface AccountSecretResult {
secret: string;
}
export interface DownloadItem {
export interface ArchivePasswordListResult {
passwords: string;
}
export interface DownloadItem {
id: string;
packageId: string;
url: string;