mirror of
https://github.com/nimbold/Firelink.git
synced 2026-09-02 14:07:57 +00:00
fix(downloads): allow confirmed credentialless resume
- distinguish redacted session credentials from intrinsic URL authentication - requeue confirmed resumes without saved secrets from the table and Properties window - scope batch overrides to approved rows and harden malformed bridge payloads - add regression coverage and localized confirmation copy
This commit is contained in:
@@ -1875,19 +1875,28 @@ export const DownloadTable: React.FC<DownloadTableProps> = ({ filter, onSummaryC
|
||||
|
||||
const handleResume = useCallback(async (item: DownloadItem) => {
|
||||
try {
|
||||
const resumed = await useDownloadStore.getState().resumeDownload(item.id);
|
||||
const current = useDownloadStore.getState().downloads.find(download => download.id === item.id);
|
||||
if (!current) return;
|
||||
let resumeWithoutCredentials = false;
|
||||
if (current.credentialsRequired === true) {
|
||||
resumeWithoutCredentials = window.confirm(t($ => $.properties.resumeWithoutCredentialsConfirm));
|
||||
if (!resumeWithoutCredentials) return;
|
||||
}
|
||||
const resumed = await useDownloadStore.getState().resumeDownload(item.id, {
|
||||
resumeWithoutCredentials
|
||||
});
|
||||
if (!resumed) {
|
||||
const current = useDownloadStore.getState().downloads.find(
|
||||
const latest = useDownloadStore.getState().downloads.find(
|
||||
download => download.id === item.id
|
||||
);
|
||||
const reason = current?.lastError?.trim();
|
||||
const reason = latest?.lastError?.trim();
|
||||
throw new Error(reason || t($ => $.downloadTable.backendRejectedStart));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to resume:", error);
|
||||
showInteractionError(t($ => $.downloadTable.resumeFailed, { fileName: item.fileName }), error);
|
||||
}
|
||||
}, [showInteractionError]);
|
||||
}, [showInteractionError, t]);
|
||||
|
||||
const getCurrentSelectedDownloads = useCallback(() => {
|
||||
const selected = selectedIdsRef.current;
|
||||
@@ -1919,7 +1928,26 @@ export const DownloadTable: React.FC<DownloadTableProps> = ({ filter, onSummaryC
|
||||
const handleResumeSelected = useCallback(() => {
|
||||
const ids = Array.from(selectedIdsRef.current);
|
||||
if (ids.length === 0) return;
|
||||
void startSelected(ids).catch(error => {
|
||||
const selected = useDownloadStore.getState().downloads.filter(download => ids.includes(download.id));
|
||||
const credentialMarkedIds = selected
|
||||
.filter(download => download.credentialsRequired === true)
|
||||
.map(download => download.id);
|
||||
if (credentialMarkedIds.length > 0
|
||||
&& !window.confirm(t($ => $.properties.resumeWithoutCredentialsConfirm))) {
|
||||
// Continue ordinary selected resumes. Credential-marked rows remain
|
||||
// fail-closed and can be handled individually after the user supplies
|
||||
// credentials or confirms a credentialless retry.
|
||||
const credentialMarkedIdSet = new Set(credentialMarkedIds);
|
||||
const ordinaryIds = ids.filter(id => !credentialMarkedIdSet.has(id));
|
||||
if (ordinaryIds.length === 0) return;
|
||||
void startSelected(ordinaryIds).catch(error => {
|
||||
showInteractionError(t($ => $.downloadTable.resumeFailed), error);
|
||||
});
|
||||
return;
|
||||
}
|
||||
void startSelected(ids, {
|
||||
resumeWithoutCredentialsIds: credentialMarkedIds
|
||||
}).catch(error => {
|
||||
showInteractionError(t($ => $.downloadTable.resumeFailed), error);
|
||||
});
|
||||
}, [showInteractionError, startSelected, t]);
|
||||
|
||||
@@ -1236,7 +1236,16 @@ export const PropertiesWindowApp = () => {
|
||||
&& !window.confirm(t($ => $.downloadTable.nonResumableOne))) {
|
||||
return;
|
||||
}
|
||||
void requestAction('pause-resume');
|
||||
const resumeWithoutCredentials = (lifecycleAction === 'resume' || lifecycleAction === 'retry')
|
||||
&& snapshot.credentialsRequired === true;
|
||||
if (resumeWithoutCredentials
|
||||
&& !window.confirm(t($ => $.properties.resumeWithoutCredentialsConfirm))) {
|
||||
return;
|
||||
}
|
||||
void requestAction(
|
||||
'pause-resume',
|
||||
resumeWithoutCredentials ? { resumeWithoutCredentials: true } : undefined,
|
||||
);
|
||||
}}
|
||||
>
|
||||
{lifecycleAction === 'pause' ? <Pause size={14} /> : <Play size={14} />}
|
||||
|
||||
@@ -476,7 +476,14 @@ export const PropertiesWindowBridgeHost = () => {
|
||||
throw new Error('The download did not reach a paused or terminal state');
|
||||
}
|
||||
} else {
|
||||
const resumed = await store.resumeDownload(request.downloadId);
|
||||
const resumeWithoutCredentials = typeof request.payload === 'object'
|
||||
&& request.payload !== null
|
||||
&& 'resumeWithoutCredentials' in request.payload
|
||||
&& request.payload.resumeWithoutCredentials === true;
|
||||
const resumed = await store.resumeDownload(
|
||||
request.downloadId,
|
||||
resumeWithoutCredentials ? { resumeWithoutCredentials: true } : undefined,
|
||||
);
|
||||
if (!resumed) {
|
||||
throw new Error(i18n.t($ => $.downloadTable.backendRejectedStart));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user