Files
windmill/frontend/src/lib/utils/downloadFile.ts
T
Ruben Fiszel 07a4cb6872 branch download UI on shouldDownloadViaClient instead of onclick interception (#9118)
* fix(frontend): branch download UI on shouldDownloadViaClient instead of intercepting in onclick

When OpenAPI.TOKEN is set, several download links rendered an `<a href>`
to the API and relied on an `onclick` handler to call `e.preventDefault()`
and route the request through `downloadViaClient`. This is fragile in
embedded contexts (e.g. the whitelabel React SDK) where Svelte's hydrated
event listener may not intercept the click in time, so the browser
follows the unauthenticated `href` straight to the API.

Mirror the drawer pattern already used in `LogViewer` and
`FlowStatusViewerInner`: render a `<button>` calling `downloadViaClient`
when `shouldDownloadViaClient()` is true, and fall back to the plain
`<a href download>` otherwise. Affects the LogViewer top bar, the large-
result download in DisplayResult, the inline S3 link in ObjectViewer,
the CSV link in ParqetCsvTableRenderer, and FileDownload.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(frontend): treat custom HEADERS / basic auth as token equivalents in shouldDownloadViaClient

Customers wiring the SDK with cookie-bypass auth via custom request
headers (e.g. `OpenAPI.HEADERS = getAuthHeaders()` returning a Bearer
header) had `shouldDownloadViaClient()` return false because it only
checked `OpenAPI.TOKEN`. The plain `<a href download>` branch then
followed the link without those headers, so authenticated downloads
silently degraded to the cookie path (or failed when there is no cookie).

Widen the check to any non-cookie auth: TOKEN, HEADERS, or USERNAME
(basic auth). Route `downloadViaClient` through the generated client's
`getHeaders` so all configured auth schemes are applied consistently
instead of hand-building an Authorization header for TOKEN only.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 10:42:05 +00:00

44 lines
1.5 KiB
TypeScript

import { OpenAPI } from '$lib/gen'
import { getHeaders } from '$lib/gen/core/request'
import { sendUserToast } from '$lib/toast'
/**
* When OpenAPI is configured to authenticate via headers (TOKEN, basic auth,
* or arbitrary HEADERS) we cannot rely on a plain `<a href>` browser
* navigation because the browser does not attach those headers. In that case
* fetch the file via the OpenAPI client (which carries the configured auth)
* and trigger a download from a blob URL. Cookie-only auth still works with
* a plain link.
*
* `apiPath` should be the path relative to OpenAPI.BASE, starting with `/`
* (e.g. `/w/foo/job_helpers/download_s3_file?file_key=...`).
*/
export function shouldDownloadViaClient(): boolean {
return Boolean(OpenAPI.TOKEN || OpenAPI.HEADERS || OpenAPI.USERNAME)
}
export async function downloadViaClient(apiPath: string, filename: string): Promise<void> {
const url = `${OpenAPI.BASE}${apiPath}`
let response: Response
try {
const headers = await getHeaders(OpenAPI, { method: 'GET', url: apiPath })
response = await fetch(url, { headers, credentials: OpenAPI.CREDENTIALS })
} catch (e) {
sendUserToast(`Download failed: ${e}`, true)
return
}
if (!response.ok) {
sendUserToast(`Download failed: ${response.status} ${response.statusText}`, true)
return
}
const blob = await response.blob()
const blobUrl = URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = blobUrl
a.download = filename
document.body.appendChild(a)
a.click()
a.remove()
URL.revokeObjectURL(blobUrl)
}