fix: strengthen live recovery and support diagnostics

Apply account and key changes to active queues without a restart and isolate provider attempt cancellation so fallback accounts remain usable. Preserve pause ownership, bound persisted HTTP 416 recovery, reconcile resets with authoritative state, and stabilize package ordering and live update cadence. Correlate rotation, conversion, resume, disk, queue-control, clipboard, and support-export events while redacting sensitive data at every persistent boundary and again in generated bundles. Release as v2.0.31 with updated English documentation and regression coverage.
This commit is contained in:
Sucukdeluxe
2026-08-13 11:36:51 +02:00
parent 88399c5dd0
commit 25ebc55f4f
44 changed files with 5223 additions and 959 deletions
+63 -18
View File
@@ -79,35 +79,42 @@ function createSnapshot(running: boolean, paused: boolean): UiSnapshot {
};
}
function findStartAction(node: ReactNode): (() => void) | null {
interface DownloadActions {
onStartDownloads?: () => void;
onPauseDownloads?: () => void;
onStopDownloads?: () => void;
}
function findDownloadActions(node: ReactNode): DownloadActions | null {
if (Array.isArray(node)) {
for (const child of node) {
const action = findStartAction(child);
if (action) return action;
const actions = findDownloadActions(child);
if (actions) return actions;
}
return null;
}
if (!isValidElement(node)) return null;
const props = node.props as {
actions?: { onStartDownloads?: () => void };
actions?: DownloadActions;
children?: ReactNode;
toolbar?: ReactNode;
};
if (typeof props.actions?.onStartDownloads === "function") {
return props.actions.onStartDownloads;
if (props.actions && typeof props.actions.onStartDownloads === "function") {
return props.actions;
}
return findStartAction(props.toolbar) ?? findStartAction(props.children);
return findDownloadActions(props.toolbar) ?? findDownloadActions(props.children);
}
async function flushAsyncAction(): Promise<void> {
await new Promise<void>((resolve) => setImmediate(resolve));
}
function renderPausedStartAction(
function renderDownloadActions(
initialSnapshot: UiSnapshot,
togglePause: () => Promise<boolean>,
getSnapshot: () => Promise<UiSnapshot>
): () => void {
getSnapshot: () => Promise<UiSnapshot>,
stop: () => Promise<void> = async () => undefined
): DownloadActions {
hookState.capturedSnapshot = false;
hookState.initialSnapshot = initialSnapshot;
hookState.currentSnapshot = initialSnapshot;
@@ -125,14 +132,14 @@ function renderPausedStartAction(
devicePixelRatio: 1,
matchMedia: () => ({ matches: false }),
prompt: () => null,
rd: { getSnapshot, togglePause },
rd: { getSnapshot, togglePause, stop },
removeEventListener: () => {},
setInterval,
setTimeout
});
const action = findStartAction(App() as ReactElement);
if (!action) throw new Error("Download-Startaktion nicht gefunden");
return action;
const actions = findDownloadActions(App() as ReactElement);
if (!actions) throw new Error("Download-Aktionen nicht gefunden");
return actions;
}
describe("paused download resume reconciliation", () => {
@@ -149,13 +156,13 @@ describe("paused download resume reconciliation", () => {
it("restores the authoritative paused state when togglePause rejects without a state event", async () => {
const initial = createSnapshot(true, true);
const authoritative = createSnapshot(true, true);
const action = renderPausedStartAction(
const actions = renderDownloadActions(
initial,
async () => { throw new Error("Kein aktiver Download-Account verfügbar"); },
async () => authoritative
);
action();
actions.onStartDownloads?.();
await flushAsyncAction();
expect(hookState.currentSnapshot).toEqual(authoritative);
@@ -164,13 +171,51 @@ describe("paused download resume reconciliation", () => {
it("replaces stale running state when togglePause returns false without a state event", async () => {
const initial = createSnapshot(true, true);
const authoritative = createSnapshot(false, false);
const action = renderPausedStartAction(
const actions = renderDownloadActions(
initial,
async () => false,
async () => authoritative
);
action();
actions.onStartDownloads?.();
await flushAsyncAction();
expect(hookState.currentSnapshot).toEqual(authoritative);
});
it("replaces stale running state when pausing returns false without a state event", async () => {
const initial = createSnapshot(true, false);
const authoritative = createSnapshot(false, false);
const actions = renderDownloadActions(
initial,
async () => false,
async () => authoritative
);
actions.onPauseDownloads?.();
await flushAsyncAction();
expect(hookState.currentSnapshot).toEqual(authoritative);
});
it("loads the complete authoritative snapshot after stop succeeds without a state event", async () => {
const initial = createSnapshot(true, false);
const authoritative = {
...createSnapshot(false, false),
canStart: true,
stats: {
...createSnapshot(false, false).stats,
totalDownloaded: 4096
}
};
const actions = renderDownloadActions(
initial,
async () => false,
async () => authoritative,
async () => undefined
);
actions.onStopDownloads?.();
await flushAsyncAction();
expect(hookState.currentSnapshot).toEqual(authoritative);