fix: harden account rotation and active download recovery

Apply account enablement changes optimistically and persist rapid toggles sequentially without losing clicks.

Refresh Mega-Debrid API and Web account pools during active downloads, isolate per-account cancellation budgets, rotate within the same conversion attempt, and keep pause, stop, resume, reset, and no-account gates consistent.

Use the native Electron clipboard under Remote Desktop and make support bundle export bounded, redacted, responsive, guarded, visible, and atomic.

Add end-to-end regressions for live account switching, persisted partial recovery, support export, native copy, availability preservation, and the 500 ms telemetry cadence.
This commit is contained in:
Sucukdeluxe
2026-08-13 05:14:44 +02:00
parent ca015553f3
commit e1b4708952
26 changed files with 2811 additions and 561 deletions
+197 -5
View File
@@ -45,7 +45,7 @@ describe("mega-web-fallback", () => {
generate: (link: string, cookie: string) => Promise<{ directUrl: string; fileName: string } | null>;
sessions: Map<string, { cookie: string; setAt: number }>;
};
vi.spyOn(internals, "login")
const login = vi.spyOn(internals, "login")
.mockResolvedValueOnce("first-stale-cookie")
.mockResolvedValueOnce("second-stale-cookie");
vi.spyOn(internals, "generate")
@@ -55,12 +55,58 @@ describe("mega-web-fallback", () => {
})
.mockResolvedValueOnce({ directUrl: "https://mega.direct/retry", fileName: "retry.bin" });
const result = await fallback.unrestrict("https://mega.debrid/retry", undefined, { login: "old-user", password: "old-pass" });
await expect(fallback.unrestrict("https://mega.debrid/retry", undefined, { login: "old-user", password: "old-pass" }))
.rejects.toThrow(/aborted/i);
expect(result?.directUrl).toBe("https://mega.direct/retry");
expect(login).toHaveBeenCalledTimes(1);
expect(internals.sessions.size).toBe(0);
});
it("keeps a fresh session when an invalidated older request finishes later", async () => {
let releaseOldGenerate: () => void = () => {};
let markOldGenerateStarted: () => void = () => {};
const oldGenerateGate = new Promise<void>((resolve) => {
releaseOldGenerate = resolve;
});
const oldGenerateStarted = new Promise<void>((resolve) => {
markOldGenerateStarted = resolve;
});
const fallback = new MegaWebFallback(() => ({ login: "same-user", password: "same-pass" }));
const internals = fallback as unknown as {
login: (login: string, password: string, signal?: AbortSignal) => Promise<string>;
generate: (link: string, cookie: string, signal?: AbortSignal) => Promise<{ directUrl: string; fileName: string } | null>;
sessions: Map<string, { cookie: string; setAt: number }>;
};
const login = vi.spyOn(internals, "login")
.mockResolvedValueOnce("old-cookie")
.mockResolvedValueOnce("fresh-cookie");
vi.spyOn(internals, "generate").mockImplementation(async (link) => {
if (link.includes("old")) {
markOldGenerateStarted();
await oldGenerateGate;
return null;
}
return { directUrl: "https://mega.direct/fresh", fileName: "fresh.bin" };
});
const oldRequest = fallback.unrestrict("https://mega.debrid/old", undefined, { login: "same-user", password: "same-pass" });
const oldSettled = oldRequest.catch((error: unknown) => error);
await oldGenerateStarted;
fallback.invalidateSession();
const freshRequest = fallback.unrestrict("https://mega.debrid/fresh", undefined, { login: "same-user", password: "same-pass" });
try {
await expect(freshRequest).resolves.toMatchObject({ directUrl: "https://mega.direct/fresh" });
expect(internals.sessions.get("same-user")?.cookie).toBe("fresh-cookie");
} finally {
releaseOldGenerate();
}
await oldSettled;
expect(internals.sessions.get("same-user")?.cookie).toBe("fresh-cookie");
expect(login).toHaveBeenCalledTimes(2);
});
it("does not cache old credentials from a queued request after invalidation", async () => {
let releaseFirstLogin: () => void = () => {};
let markFirstLoginStarted: () => void = () => {};
@@ -88,16 +134,162 @@ describe("mega-web-fallback", () => {
vi.spyOn(internals, "generate").mockResolvedValue({ directUrl: "https://mega.direct/queued", fileName: "queued.bin" });
const first = fallback.unrestrict("https://mega.debrid/first", undefined, { login: "old-user", password: "old-pass" });
const firstSettled = first.catch((error: unknown) => error);
await firstLoginStarted;
const queued = fallback.unrestrict("https://mega.debrid/queued", undefined, { login: "old-user", password: "old-pass" });
const queuedSettled = queued.catch((error: unknown) => error);
fallback.invalidateSession();
releaseFirstLogin();
await expect(Promise.all([first, queued])).resolves.toHaveLength(2);
expect(loginCount).toBe(2);
await expect(firstSettled).resolves.toBeInstanceOf(Error);
await expect(queuedSettled).resolves.toBeInstanceOf(Error);
expect(loginCount).toBe(1);
expect(internals.sessions.size).toBe(0);
});
it("does not let a blocked old account job block a new request after session invalidation", async () => {
let releaseOldLogin: () => void = () => {};
let markOldLoginStarted: () => void = () => {};
const oldLoginGate = new Promise<void>((resolve) => {
releaseOldLogin = resolve;
});
const oldLoginStarted = new Promise<void>((resolve) => {
markOldLoginStarted = resolve;
});
const fallback = new MegaWebFallback(() => ({ login: "same-user", password: "same-pass" }));
const internals = fallback as unknown as {
login: (login: string, password: string, signal?: AbortSignal) => Promise<string>;
generate: (link: string, cookie: string, signal?: AbortSignal) => Promise<{ directUrl: string; fileName: string }>;
};
const login = vi.spyOn(internals, "login")
.mockImplementationOnce(async () => {
markOldLoginStarted();
await oldLoginGate;
return "old-cookie";
})
.mockResolvedValueOnce("new-cookie");
vi.spyOn(internals, "generate").mockImplementation(async (link) => ({
directUrl: link.includes("fresh") ? "https://mega.direct/fresh" : "https://mega.direct/old",
fileName: link.includes("fresh") ? "fresh.bin" : "old.bin"
}));
const oldRequest = fallback.unrestrict("https://mega.debrid/old", undefined, { login: "same-user", password: "same-pass" });
const oldSettled = oldRequest.catch((error: unknown) => error);
await oldLoginStarted;
fallback.invalidateSession();
const freshRequest = fallback.unrestrict("https://mega.debrid/fresh", undefined, { login: "same-user", password: "same-pass" });
try {
await vi.waitFor(() => expect(login).toHaveBeenCalledTimes(2), { timeout: 500, interval: 5 });
await expect(freshRequest).resolves.toMatchObject({
directUrl: "https://mega.direct/fresh",
fileName: "fresh.bin"
});
} finally {
releaseOldLogin();
await Promise.allSettled([oldSettled, freshRequest]);
}
});
it("does not execute provider work for a queued request aborted before it starts", async () => {
let releaseFirstLogin: () => void = () => {};
let markFirstLoginStarted: () => void = () => {};
const firstLoginGate = new Promise<void>((resolve) => {
releaseFirstLogin = resolve;
});
const firstLoginStarted = new Promise<void>((resolve) => {
markFirstLoginStarted = resolve;
});
const generatedLinks: string[] = [];
const fallback = new MegaWebFallback(() => ({ login: "same-user", password: "same-pass" }));
const internals = fallback as unknown as {
login: (login: string, password: string, signal?: AbortSignal) => Promise<string>;
generate: (link: string, cookie: string, signal?: AbortSignal) => Promise<{ directUrl: string; fileName: string }>;
queues: Map<string, Promise<unknown>>;
};
const login = vi.spyOn(internals, "login").mockImplementation(async () => {
markFirstLoginStarted();
await firstLoginGate;
return "first-cookie";
});
vi.spyOn(internals, "generate").mockImplementation(async (link) => {
generatedLinks.push(link);
return { directUrl: "https://mega.direct/first", fileName: "first.bin" };
});
const firstRequest = fallback.unrestrict("https://mega.debrid/first", undefined, { login: "same-user", password: "same-pass" });
await firstLoginStarted;
const queuedController = new AbortController();
const queuedRequest = fallback.unrestrict("https://mega.debrid/aborted", queuedController.signal, { login: "same-user", password: "same-pass" });
const queueTail = internals.queues.get("same-user");
queuedController.abort("cancel-before-start");
await expect(queuedRequest).rejects.toThrow(/queue.?timeout/i);
releaseFirstLogin();
await expect(firstRequest).resolves.toMatchObject({ directUrl: "https://mega.direct/first" });
await queueTail;
expect(login).toHaveBeenCalledTimes(1);
expect(generatedLinks).toEqual(["https://mega.debrid/first"]);
});
it("starts a new request normally after invalidating a queue with an aborted waiter", async () => {
let releaseOldLogin: () => void = () => {};
let markOldLoginStarted: () => void = () => {};
const oldLoginGate = new Promise<void>((resolve) => {
releaseOldLogin = resolve;
});
const oldLoginStarted = new Promise<void>((resolve) => {
markOldLoginStarted = resolve;
});
const generatedLinks: string[] = [];
const fallback = new MegaWebFallback(() => ({ login: "same-user", password: "same-pass" }));
const internals = fallback as unknown as {
login: (login: string, password: string, signal?: AbortSignal) => Promise<string>;
generate: (link: string, cookie: string, signal?: AbortSignal) => Promise<{ directUrl: string; fileName: string }>;
queues: Map<string, Promise<unknown>>;
};
const login = vi.spyOn(internals, "login")
.mockImplementationOnce(async () => {
markOldLoginStarted();
await oldLoginGate;
return "old-cookie";
})
.mockResolvedValueOnce("fresh-cookie");
vi.spyOn(internals, "generate").mockImplementation(async (link) => {
generatedLinks.push(link);
return {
directUrl: link.includes("fresh") ? "https://mega.direct/fresh" : "https://mega.direct/old",
fileName: link.includes("fresh") ? "fresh.bin" : "old.bin"
};
});
const oldRequest = fallback.unrestrict("https://mega.debrid/old", undefined, { login: "same-user", password: "same-pass" });
const oldSettled = oldRequest.catch((error: unknown) => error);
await oldLoginStarted;
const queuedController = new AbortController();
const queuedRequest = fallback.unrestrict("https://mega.debrid/aborted", queuedController.signal, { login: "same-user", password: "same-pass" });
const staleQueueTail = internals.queues.get("same-user");
queuedController.abort("cancel-before-start");
await expect(queuedRequest).rejects.toThrow(/queue.?timeout/i);
fallback.invalidateSession();
const freshRequest = fallback.unrestrict("https://mega.debrid/fresh", undefined, { login: "same-user", password: "same-pass" });
try {
await vi.waitFor(() => expect(login).toHaveBeenCalledTimes(2), { timeout: 500, interval: 5 });
await expect(freshRequest).resolves.toMatchObject({
directUrl: "https://mega.direct/fresh",
fileName: "fresh.bin"
});
} finally {
releaseOldLogin();
await Promise.allSettled([oldSettled, freshRequest, staleQueueTail]);
}
expect(generatedLinks).toEqual(["https://mega.debrid/fresh"]);
});
it("logs in, fetches HTML, parses code, and polls AJAX for direct url", async () => {
let fetchCallCount = 0;
globalThis.fetch = vi.fn(async (url: string | URL | Request) => {