diff --git a/reflex/.templates/web/utils/state.js b/reflex/.templates/web/utils/state.js index ef1dfcf6df5..2926a23c7fb 100644 --- a/reflex/.templates/web/utils/state.js +++ b/reflex/.templates/web/utils/state.js @@ -269,7 +269,19 @@ export const applyEvent = async (event, socket, navigate, params) => { if (event.name == "_download") { const a = document.createElement("a"); a.hidden = true; - a.href = event.payload.url; + if (event.payload.url.startsWith("DOWNLOAD_AS_BLOB:")) { + const blob = new Blob( + [event.payload.url.replace("DOWNLOAD_AS_BLOB:", "")], + { type: "application/octet-stream" }, + ); + const url = URL.createObjectURL(blob); + a.href = url; + window.setTimeout(function () { + window.URL.revokeObjectURL(url); + }, 60000); // Wait 60 seconds + } else { + a.href = event.payload.url; + } // Special case when linking to uploaded files if (a.href.includes("getBackendURL(env.UPLOAD)")) { a.href = eval?.( diff --git a/reflex/event.py b/reflex/event.py index 8c8691f9d16..f55cf28535b 100644 --- a/reflex/event.py +++ b/reflex/event.py @@ -1230,6 +1230,8 @@ def download( url: str | Var | None = None, filename: str | Var | None = None, data: str | bytes | Var | None = None, + as_blob: bool | None = None, + mime_type: str | Var | None = None, ) -> EventSpec: """Download the file at a given path or with the specified data. @@ -1237,6 +1239,8 @@ def download( url: The URL to the file to download. filename: The name that the file should be saved as after download. data: The data to download. + as_blob: Downloads the provided `data` via JS `new Blob(...)`. + mime_type: Optional blob mime type when `as_blob` is set to True. Default is `application/octet-stream`. Raises: ValueError: If the URL provided is invalid, both URL and data are provided, @@ -1258,13 +1262,20 @@ def download( if filename is None: filename = "" + if mime_type is None: + mime_type = "application/octet-stream" if data is not None: if url is not None: msg = "Cannot provide both URL and data to download." raise ValueError(msg) - if isinstance(data, str): + if as_blob: + url = f"DOWNLOAD_AS_BLOB:{data}" + if isinstance(data, Var): + url = f"DOWNLOAD_AS_BLOB:{data.to(str)}" + + elif isinstance(data, str): # Caller provided a plain text string to download. url = "data:text/plain," + urllib.parse.quote(data) elif isinstance(data, Var): @@ -1293,6 +1304,7 @@ def download( get_fn_signature(download), url=url, filename=filename, + mime_type=mime_type, )