fix(realdebrid): keep background downloads noninteractive
Restrict browser window creation to explicit login actions so account activation, status probes, and download retries never open or reopen Real-Debrid windows. Remove the automatic login polling loop from web unrestrict and return a clear login-required error instead. Cover concurrent fresh fallback instances, manually closed windows, and authenticated fair-use failures without popup creation.
This commit is contained in:
@@ -135,8 +135,6 @@ export class RealDebridWebFallback {
|
||||
|
||||
private onClosed?: () => void;
|
||||
|
||||
private interactiveLoginDismissed = false;
|
||||
|
||||
private persistentPartition: string;
|
||||
|
||||
private transientPartition: string;
|
||||
@@ -172,13 +170,12 @@ export class RealDebridWebFallback {
|
||||
if (initial.kind === "success") {
|
||||
return initial.value;
|
||||
}
|
||||
return this.waitForLoginAndGenerate(link, overallSignal);
|
||||
throw new Error("Real-Debrid Web-Login erforderlich");
|
||||
}, overallSignal);
|
||||
}
|
||||
|
||||
public async openLoginWindow(): Promise<void> {
|
||||
this.throwIfDisposed();
|
||||
this.interactiveLoginDismissed = false;
|
||||
const window = await this.ensureLoginWindow();
|
||||
if (window.isMinimized()) {
|
||||
window.restore();
|
||||
@@ -343,7 +340,6 @@ export class RealDebridWebFallback {
|
||||
let closingTokenProbe: Promise<void> = Promise.resolve();
|
||||
window.on("close", () => {
|
||||
if (!this.programmaticClosures.has(window)) {
|
||||
this.interactiveLoginDismissed = true;
|
||||
closingTokenProbe = this.primeTokenFromWindow(window, windowGeneration);
|
||||
}
|
||||
});
|
||||
@@ -376,7 +372,6 @@ export class RealDebridWebFallback {
|
||||
const changed = token !== this.cachedToken;
|
||||
this.cachedToken = token;
|
||||
this.cachedTokenAt = Date.now();
|
||||
this.interactiveLoginDismissed = false;
|
||||
if (changed && this.onAuthenticated) {
|
||||
void Promise.resolve().then(() => this.onAuthenticated?.());
|
||||
}
|
||||
@@ -586,36 +581,4 @@ export class RealDebridWebFallback {
|
||||
throw new Error("Real-Debrid Web: Unrestrict fehlgeschlagen");
|
||||
}
|
||||
|
||||
private async waitForLoginAndGenerate(link: string, signal?: AbortSignal): Promise<UnrestrictedLink | null> {
|
||||
if (this.interactiveLoginDismissed) {
|
||||
throw new Error("Real-Debrid Web-Login abgebrochen");
|
||||
}
|
||||
const window = await this.ensureLoginWindow();
|
||||
if (window.isMinimized()) {
|
||||
window.restore();
|
||||
}
|
||||
window.show();
|
||||
window.focus();
|
||||
|
||||
const startedAt = Date.now();
|
||||
while (Date.now() - startedAt < 10 * 60 * 1000) {
|
||||
throwIfAborted(signal);
|
||||
if (window.isDestroyed()) {
|
||||
throw new Error("Real-Debrid Web-Login abgebrochen");
|
||||
}
|
||||
|
||||
const outcome = await this.generate(link, signal);
|
||||
if (outcome.kind === "success") {
|
||||
if (!window.isDestroyed()) {
|
||||
this.programmaticClosures.add(window);
|
||||
window.close();
|
||||
}
|
||||
return outcome.value;
|
||||
}
|
||||
|
||||
await sleepWithSignal(1_500, signal);
|
||||
}
|
||||
|
||||
throw new Error("Real-Debrid Web-Login Timeout");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -166,6 +166,45 @@ describe("realdebrid-web", () => {
|
||||
expect(mockBrowserWindow.webContents.executeJavaScript).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("never opens a login window from a background unrestrict request", async () => {
|
||||
mockSessionFetch.mockImplementation(async () => new Response("<html>login</html>", { status: 200 }));
|
||||
const first = new RealDebridWebFallback("persist:realdebrid-web-rdw_background_first", () => true);
|
||||
const second = new RealDebridWebFallback("persist:realdebrid-web-rdw_background_second", () => true);
|
||||
|
||||
const firstController = new AbortController();
|
||||
const secondController = new AbortController();
|
||||
const abortTimer = setTimeout(() => {
|
||||
firstController.abort();
|
||||
secondController.abort();
|
||||
}, 50);
|
||||
try {
|
||||
await Promise.all([
|
||||
expect(first.unrestrict("https://rapidgator.net/file/background-first", firstController.signal))
|
||||
.rejects.toThrow("Login erforderlich"),
|
||||
expect(second.unrestrict("https://rapidgator.net/file/background-second", secondController.signal))
|
||||
.rejects.toThrow("Login erforderlich")
|
||||
]);
|
||||
} finally {
|
||||
clearTimeout(abortTimer);
|
||||
}
|
||||
|
||||
expect(mockBrowserWindowCtor).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("does not open a login window for an authenticated account with a fair-use error", async () => {
|
||||
mockSessionFetch.mockResolvedValue(new Response("<input name=\"private_token\" value=\"session-token\">", { status: 200 }));
|
||||
vi.stubGlobal("fetch", vi.fn().mockImplementation(async () => new Response(JSON.stringify({
|
||||
error: "fair_usage_limit",
|
||||
error_code: 36
|
||||
}), { status: 440 })));
|
||||
const fallback = new RealDebridWebFallback("persist:realdebrid-web-rdw_limited", () => true);
|
||||
|
||||
await expect(fallback.unrestrict("https://rapidgator.net/file/limited"))
|
||||
.rejects.toThrow("Real-Debrid Web HTTP 440");
|
||||
|
||||
expect(mockBrowserWindowCtor).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("checks the logged-in browser account without exposing its token", async () => {
|
||||
mockExecuteJavaScript.mockResolvedValue("token-from-window");
|
||||
const apiFetch = vi.fn().mockResolvedValue(new Response(JSON.stringify({
|
||||
@@ -197,7 +236,7 @@ describe("realdebrid-web", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("does not reopen an automatically required login after the user closed it", async () => {
|
||||
it("does not reopen a login window from downloads after the user closed it", async () => {
|
||||
mockExecuteJavaScript.mockResolvedValue("");
|
||||
mockSessionFetch.mockImplementation(async () => new Response("<html>login</html>", { status: 200 }));
|
||||
const fallback = new RealDebridWebFallback("persist:realdebrid-web-rdw_dismissed", () => true);
|
||||
@@ -205,8 +244,8 @@ describe("realdebrid-web", () => {
|
||||
await fallback.openLoginWindow();
|
||||
mockBrowserWindow.close();
|
||||
|
||||
await expect(fallback.unrestrict("https://rapidgator.net/file/first")).rejects.toThrow("abgebrochen");
|
||||
await expect(fallback.unrestrict("https://rapidgator.net/file/second")).rejects.toThrow("abgebrochen");
|
||||
await expect(fallback.unrestrict("https://rapidgator.net/file/first")).rejects.toThrow("Login erforderlich");
|
||||
await expect(fallback.unrestrict("https://rapidgator.net/file/second")).rejects.toThrow("Login erforderlich");
|
||||
expect(mockBrowserWindowCtor).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user