Complete provider waits and lifecycle visibility
Release aborted Real-Debrid, AllDebrid, and BestDebrid web callers even when underlying requests ignore cancellation while retaining terminal rejection observers. Publish provider cooldown deadlines and emit a fresh idle snapshot at the earliest expiry. Abort and visibly drain post-processing before dispatching one pending restart. Surface lifecycle phase, reason, retry countdown, and remaining work in the download controls. Add focused regressions for provider abort races, cooldown expiry, post-processing drain, pending start visibility, and the updated rapid stop contract.
This commit is contained in:
@@ -42,7 +42,7 @@ function throwIfAborted(signal?: AbortSignal): void {
|
||||
}
|
||||
}
|
||||
|
||||
async function sleepWithSignal(ms: number, signal?: AbortSignal): Promise<void> {
|
||||
async function sleepWithSignal(ms: number, signal?: AbortSignal): Promise<void> {
|
||||
if (!signal) {
|
||||
await sleep(ms);
|
||||
return;
|
||||
@@ -68,8 +68,44 @@ async function sleepWithSignal(ms: number, signal?: AbortSignal): Promise<void>
|
||||
};
|
||||
|
||||
signal.addEventListener("abort", onAbort, { once: true });
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function raceWithAbort<T>(promise: Promise<T>, signal?: AbortSignal): Promise<T> {
|
||||
if (!signal) {
|
||||
return promise;
|
||||
}
|
||||
if (signal.aborted) {
|
||||
throw abortError();
|
||||
}
|
||||
return new Promise<T>((resolve, reject) => {
|
||||
let settled = false;
|
||||
const onAbort = (): void => {
|
||||
if (settled) {
|
||||
return;
|
||||
}
|
||||
settled = true;
|
||||
signal.removeEventListener("abort", onAbort);
|
||||
reject(abortError());
|
||||
};
|
||||
signal.addEventListener("abort", onAbort, { once: true });
|
||||
promise.then((value) => {
|
||||
if (settled) {
|
||||
return;
|
||||
}
|
||||
settled = true;
|
||||
signal.removeEventListener("abort", onAbort);
|
||||
resolve(value);
|
||||
}, (error) => {
|
||||
if (settled) {
|
||||
return;
|
||||
}
|
||||
settled = true;
|
||||
signal.removeEventListener("abort", onAbort);
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function asRecord(value: unknown): Record<string, unknown> | null {
|
||||
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
||||
@@ -280,10 +316,10 @@ export class AllDebridWebFallback {
|
||||
}
|
||||
return job();
|
||||
};
|
||||
const run = this.queue.then(guardedJob, guardedJob);
|
||||
this.queue = run.then(() => undefined, () => undefined);
|
||||
return run;
|
||||
}
|
||||
const run = this.queue.then(guardedJob, guardedJob);
|
||||
this.queue = run.then(() => undefined, () => undefined);
|
||||
return raceWithAbort(run, signal);
|
||||
}
|
||||
|
||||
private async ensureLoginWindow(): Promise<BrowserWindow> {
|
||||
const partition = this.getPartition();
|
||||
|
||||
Reference in New Issue
Block a user