Compare commits

..

No commits in common. "7be2d2e14839a79bff6142f759e5d6263862e778" and "04413599d8df41d744d2107c79c13f65a1677767" have entirely different histories.

3 changed files with 1 additions and 122 deletions

View File

@ -1,6 +1,6 @@
{ {
"name": "real-debrid-downloader", "name": "real-debrid-downloader",
"version": "1.7.140", "version": "1.7.139",
"description": "Desktop downloader", "description": "Desktop downloader",
"main": "build/main/main/main.js", "main": "build/main/main/main.js",
"author": "Sucukdeluxe", "author": "Sucukdeluxe",

View File

@ -59,23 +59,6 @@ export function resetDebridLinkRuntimeStateForTests(): void {
debridLinkKeyRuntimeStatuses.clear(); debridLinkKeyRuntimeStatuses.clear();
} }
/** Drop all Debrid-Link cooldown/runtime entries for key IDs that are no
* longer in the active key set. Called when settings change so removed
* keys don't keep blocking the system if they're re-added later. */
export function pruneDebridLinkRuntimeStateForKeys(activeKeyIds: Set<string>): void {
for (const keyId of debridLinkKeyCooldowns.keys()) {
if (!activeKeyIds.has(keyId)) {
debridLinkKeyCooldowns.delete(keyId);
debridLinkKeyCooldownDetails.delete(keyId);
}
}
for (const keyId of debridLinkKeyRuntimeStatuses.keys()) {
if (!activeKeyIds.has(keyId)) {
debridLinkKeyRuntimeStatuses.delete(keyId);
}
}
}
/** Periodic cleanup of expired Debrid-Link cooldown/runtime entries. /** Periodic cleanup of expired Debrid-Link cooldown/runtime entries.
* Without this, module-level Maps grow unbounded over 24/7 operation. * Without this, module-level Maps grow unbounded over 24/7 operation.
* Removes entries whose cooldown expired more than 1 hour ago. */ * Removes entries whose cooldown expired more than 1 hour ago. */
@ -1552,33 +1535,6 @@ class MegaDebridClient {
/** Per-account pending connect deduplication: login (lowercase) → promise */ /** Per-account pending connect deduplication: login (lowercase) → promise */
private static pendingConnects = new Map<string, Promise<string | null>>(); private static pendingConnects = new Map<string, Promise<string | null>>();
/** Clear cached tokens for accounts whose login is no longer in the given set.
* Called when settings change so removed accounts don't keep stale tokens. */
public static pruneCachedTokensNotIn(activeLogins: Iterable<string>): void {
const keep = new Set<string>();
for (const login of activeLogins) {
keep.add(String(login || "").toLowerCase());
}
for (const login of MegaDebridClient.cachedApiTokens.keys()) {
if (!keep.has(login)) {
MegaDebridClient.cachedApiTokens.delete(login);
}
}
for (const login of MegaDebridClient.pendingConnects.keys()) {
if (!keep.has(login)) {
MegaDebridClient.pendingConnects.delete(login);
}
}
}
/** Force-clear the API token for a specific login (e.g. when its password
* changes same login, but cached token is now invalid for new password). */
public static clearCachedApiToken(login: string): void {
const key = String(login || "").toLowerCase();
MegaDebridClient.cachedApiTokens.delete(key);
MegaDebridClient.pendingConnects.delete(key);
}
public constructor(login: string, password: string, mode: "api" | "web", allowApiFallback: boolean, megaWebUnrestrict?: MegaWebUnrestrictor) { public constructor(login: string, password: string, mode: "api" | "web", allowApiFallback: boolean, megaWebUnrestrict?: MegaWebUnrestrictor) {
this.login = login; this.login = login;
this.password = password; this.password = password;
@ -3109,53 +3065,7 @@ export class DebridService {
} }
public setSettings(next: AppSettings): void { public setSettings(next: AppSettings): void {
const prev = this.settings;
this.settings = cloneSettings(next); this.settings = cloneSettings(next);
// Invalidate cached provider clients whose credentials/keys changed.
// Without this, switching API keys or session-cookie-bound accounts
// (LinkSnappy, Ddownload) would keep using the previous Client instance
// — which holds the OLD session cookies — until the app is restarted.
if (prev.debridLinkApiKeys !== next.debridLinkApiKeys) {
this.cachedDebridLinkClient = null;
this.cachedDebridLinkKey = "";
}
if (prev.linkSnappyLogin !== next.linkSnappyLogin || prev.linkSnappyPassword !== next.linkSnappyPassword) {
this.cachedLinkSnappyClient = null;
this.cachedLinkSnappyKey = "";
}
if (prev.ddownloadLogin !== next.ddownloadLogin || prev.ddownloadPassword !== next.ddownloadPassword) {
this.cachedDdownloadClient = null;
this.cachedDdownloadKey = "";
}
// Mega-Debrid token cache (static, module-level): tokens are keyed by
// login (lowercase). When credentials change, drop tokens for logins
// that are no longer in the active account list, AND force-clear any
// login whose password changed. Otherwise stale tokens linger up to
// 20 minutes and the new credentials won't be tried until the cached
// token starts returning 401/403.
const prevAccounts = parseMegaDebridAccounts(prev.megaCredentials || "", prev.megaPassword || "");
const nextAccounts = parseMegaDebridAccounts(next.megaCredentials || "", next.megaPassword || "");
const nextLogins = new Set<string>();
const nextPasswordByLogin = new Map<string, string>();
for (const acc of nextAccounts) {
nextLogins.add(acc.login.toLowerCase());
nextPasswordByLogin.set(acc.login.toLowerCase(), acc.password);
}
// Drop tokens for logins no longer present
MegaDebridClient.pruneCachedTokensNotIn(nextLogins);
// For logins still present but with a changed password, force-clear the token
for (const prevAcc of prevAccounts) {
const loginKey = prevAcc.login.toLowerCase();
if (nextLogins.has(loginKey) && nextPasswordByLogin.get(loginKey) !== prevAcc.password) {
MegaDebridClient.clearCachedApiToken(prevAcc.login);
}
}
// Also prune module-level Debrid-Link cooldowns for keys that no longer exist —
// otherwise a key removed and re-added later would still show its old cooldown.
const nextDebridLinkKeyIds = new Set<string>(parseDebridLinkApiKeys(next.debridLinkApiKeys || "").map((entry) => entry.id));
pruneDebridLinkRuntimeStateForKeys(nextDebridLinkKeyIds);
} }
private getDebridLinkClient(apiKeysRaw: string): DebridLinkClient { private getDebridLinkClient(apiKeysRaw: string): DebridLinkClient {

View File

@ -1987,37 +1987,6 @@ export class DownloadManager extends EventEmitter {
this.hybridFailedArchives.clear(); this.hybridFailedArchives.clear();
} }
// When account credentials change, clear the provider-failure circuit-breaker
// for affected providers. Otherwise a freshly added account would inherit
// the cooldown that the previous (now removed) account had triggered, and
// the user would be confused why "their new account doesn't work right away".
const credChanges: Array<{ prev: string; next: string; providers: string[] }> = [
{ prev: previous.token || "", next: next.token || "", providers: ["realdebrid"] },
{ prev: previous.allDebridToken || "", next: next.allDebridToken || "", providers: ["alldebrid"] },
{ prev: previous.bestDebridApiKey || "", next: next.bestDebridApiKey || "", providers: ["bestdebrid"] },
{ prev: previous.debridLinkApiKeys || "", next: next.debridLinkApiKeys || "", providers: ["debridlink"] },
{ prev: previous.linkSnappyLogin + "|" + previous.linkSnappyPassword, next: next.linkSnappyLogin + "|" + next.linkSnappyPassword, providers: ["linksnappy"] },
{ prev: previous.ddownloadLogin + "|" + previous.ddownloadPassword, next: next.ddownloadLogin + "|" + next.ddownloadPassword, providers: ["ddownload"] },
{ prev: previous.megaCredentials + "|" + previous.megaPassword, next: next.megaCredentials + "|" + next.megaPassword, providers: ["megadebrid", "megadebrid-api", "megadebrid-web"] }
];
let clearedProviderFailures = 0;
for (const change of credChanges) {
if (change.prev === change.next) continue;
for (const provider of change.providers) {
// Provider failure keys are sometimes "provider" alone, sometimes "provider:hoster".
// Clear all entries that start with the provider name.
for (const key of [...this.providerFailures.keys()]) {
if (key === provider || key.startsWith(`${provider}:`)) {
this.providerFailures.delete(key);
clearedProviderFailures += 1;
}
}
}
}
if (clearedProviderFailures > 0) {
logger.info(`Settings-Update: ${clearedProviderFailures} Provider-Failure(s) gecleart wegen geaenderter Credentials`);
}
this.resolveExistingQueuedOpaqueFilenames(); this.resolveExistingQueuedOpaqueFilenames();
void this.cleanupExistingExtractedArchives().catch((err) => logger.warn(`cleanupExistingExtractedArchives Fehler (setSettings): ${compactErrorText(err)}`)); void this.cleanupExistingExtractedArchives().catch((err) => logger.warn(`cleanupExistingExtractedArchives Fehler (setSettings): ${compactErrorText(err)}`));
if (next.completedCleanupPolicy !== "never") { if (next.completedCleanupPolicy !== "never") {