Fix stale-state when account credentials change at runtime
Reported user bug: "When I add an account, remove it, add a new one, the
new account is only really used after restart." Multiple cache layers were
not invalidated when settings changed, causing the system to keep using
stale state until the app was restarted.
Three layers of caching needed invalidation:
1. DebridService cached client instances (debrid.ts ~3067)
The cached DebridLinkClient / LinkSnappyClient / DdownloadClient hold
internal state (session cookies, auth tokens, parsed key lists). Without
explicit invalidation when credentials change, the OLD client instance
keeps serving requests until apiKeysRaw / login+password no longer match
the cache key - which doesn't always trigger because the cache key may
incidentally match (e.g. user removes and re-adds the same key).
Fix: setSettings() now compares previous vs next credentials per provider
and explicitly clears the cache when they differ.
2. MegaDebridClient.cachedApiTokens (debrid.ts ~1533)
Static module-level token cache keyed by login (lowercase). When a user
changes the password for an existing login (same login, new password),
the cached token was kept for up to 20 minutes and would only get cleared
after the API returned 401/403.
Fix: Two new static methods - pruneCachedTokensNotIn() removes entries
whose login is no longer in the active list, and clearCachedApiToken()
force-clears a specific login. Both called from setSettings() based on
diff between previous and next account list.
3. Module-level Debrid-Link cooldown maps (debrid.ts ~41-51)
debridLinkKeyCooldowns / debridLinkKeyCooldownDetails / runtime statuses
are keyed by API key ID (FNV-1a hash of the key). When a key was put
into cooldown then removed from settings then re-added later, the old
cooldown entry would still block it.
Fix: New pruneDebridLinkRuntimeStateForKeys() function called from
setSettings() removes cooldown entries for keys no longer in the active
set.
4. providerFailures circuit-breaker map (download-manager.ts ~1990)
Per-provider failure tracking with cooldownUntil. When a user removes a
failing account and adds a new one of the same provider, the cooldown
would carry over. Now setSettings() compares per-provider credentials
and clears matching providerFailures entries when they change.
Reproduction (now fixed):
1. Add Debrid-Link key A
2. Trigger a failure (e.g. invalid link) so A goes into cooldown
3. Remove A, add B in settings
4. Try a download immediately - previously the cooldown for A or the
cached client with A's state could still prevent B from being used.
After this fix B is used immediately.
Tests: 201/201 (debrid + download-manager) green.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
04413599d8
commit
c3590f08fc
@@ -1987,6 +1987,37 @@ export class DownloadManager extends EventEmitter {
|
||||
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();
|
||||
void this.cleanupExistingExtractedArchives().catch((err) => logger.warn(`cleanupExistingExtractedArchives Fehler (setSettings): ${compactErrorText(err)}`));
|
||||
if (next.completedCleanupPolicy !== "never") {
|
||||
|
||||
Reference in New Issue
Block a user