Skip to content

Conversation

@riebecj
Copy link
Contributor

@riebecj riebecj commented Nov 6, 2025

Ran into issues with formatting when downloading files with PEM and DER formats due to the data passing through urllib.parse.quote(data). So, I followed a similar approach to getBackendURL(env.UPLOAD) and add a DOWNLOAD_AS_BLOB: prepend to the data which is checked for and processed. If as_blob is set to True, it is the first thing processed, otherwise the function logic continues as previously designed.

Adds two new kwargs to rx.download():
- as_blob: tells the JS event to use const blob = new Blob(...) and URL.createObjectURL(blob) to download the object.
- mime_type: Optional mime-type specification when downloading as_blob. The default is "application/octet-stream" as JS blobs are typically byte data, but the user can get more specific (e.g. application/x-pem-file).

Also, there are no tests for rx.download() so no new tests were added.

NOTE: There's still an issue when taking something like a PEM or DER formatted string and passing it as bytes to rx.download() even if using as_blob. The issue is the data get's downloaded as a raw bytearray rather than the true data format prior to bytearray conversion. Still not sure how to fix it but the functionality I need is added with this PR.

All Submissions:

  • Have you followed the guidelines stated in CONTRIBUTING.md file?
  • Have you checked to ensure there aren't any other open Pull Requests for the desired changed?

Type of change

Please delete options that are not relevant.

  • New feature (non-breaking change which adds functionality)

New Feature Submission:

  • Does your submission pass the tests?
  • Have you linted your code locally prior to submission?

Changes To Core Features:

  • Have you added an explanation of what your changes do and why you'd like us to include them?
  • Have you written new tests for your core changes, as applicable?
  • Have you successfully ran tests with your changes locally?

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Greptile Overview

Greptile Summary

This PR enhances the rx.download() function to support blob-based downloads, addressing encoding issues encountered with binary data formats like PEM and DER files. The changes add two new parameters: as_blob (boolean) and mime_type (string, defaults to "application/octet-stream"). When as_blob=True, the implementation uses a "DOWNLOAD_AS_BLOB:" prefix pattern similar to the existing upload mechanism to signal the frontend to use JavaScript's Blob API and URL.createObjectURL() instead of direct URL assignment. This prevents data corruption that occurs when binary content passes through urllib.parse.quote(data) in the standard download path. The modification extends the existing download functionality while maintaining full backward compatibility - existing code continues to work unchanged, and the new blob approach is only activated when explicitly requested.

Important Files Changed

Filename Score Overview
reflex/event.py 4/5 Added as_blob and mime_type parameters to download function with proper validation and blob prefix logic
reflex/.templates/web/utils/state.js 4/5 Enhanced download event handler to detect blob prefix and create blob URLs using JavaScript Blob API

Confidence score: 4/5

  • This PR is safe to merge with minimal risk as it maintains backward compatibility and addresses a genuine technical limitation
  • Score reflects well-structured implementation following existing patterns, but lacks comprehensive testing coverage for the new functionality
  • Pay close attention to reflex/event.py for parameter validation logic and edge cases around the blob/URL mutual exclusivity

Sequence Diagram

sequenceDiagram
    participant User
    participant Frontend
    participant EventHandler
    participant StateJS
    participant Browser

    User->>Frontend: "Trigger download action"
    Frontend->>EventHandler: "Call rx.download() with as_blob=True"
    EventHandler->>EventHandler: "Check parameters and create download EventSpec"
    alt as_blob is True
        EventHandler->>EventHandler: "Prepend 'DOWNLOAD_AS_BLOB:' to data"
    else as_blob is False or None
        EventHandler->>EventHandler: "Use existing logic (data URI or URL encoding)"
    end
    EventHandler->>StateJS: "Emit '_download' event with payload"
    StateJS->>StateJS: "Process event in applyEvent function"
    alt URL starts with 'DOWNLOAD_AS_BLOB:'
        StateJS->>StateJS: "Extract data after 'DOWNLOAD_AS_BLOB:' prefix"
        StateJS->>Browser: "Create Blob with extracted data and mime type"
        StateJS->>Browser: "Generate object URL from blob"
        StateJS->>Browser: "Set anchor href to blob URL"
    else Regular URL or data URI
        StateJS->>Browser: "Set anchor href to provided URL"
        alt URL contains 'getBackendURL(env.UPLOAD)'
            StateJS->>StateJS: "Evaluate and replace with actual backend URL"
        end
    end
    StateJS->>Browser: "Set download filename on anchor element"
    StateJS->>Browser: "Trigger click on hidden anchor element"
    Browser->>User: "Download file to user's device"
    StateJS->>StateJS: "Remove anchor element from DOM"
Loading

2 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

@codspeed-hq
Copy link

codspeed-hq bot commented Nov 6, 2025

CodSpeed Performance Report

Merging #5954 will not alter performance

Comparing riebecj:main (9d51ebf) with main (579e922)

Summary

✅ 8 untouched

@adhami3310
Copy link
Member

closing in favor of #5957, thanks for the help!

@adhami3310 adhami3310 closed this Nov 6, 2025
@masenf
Copy link
Collaborator

masenf commented Nov 6, 2025

@riebecj we ended up taking a more generic fix, but I'm curious if you had a repro case to share where you were hitting issues.

I tried to reproduce this and wasn't able to.

"""Test files generated with:

ssh-keygen -t rsa -m PEM -f ./foo.pem
openssl rsa -outform der -in ./foo.pem -out foo.der
"""
from pathlib import Path

import reflex as rx


class State(rx.State):
    @rx.event
    def download_pem(self):
        return rx.download(
            data=Path("foo.pem").read_text(),
            filename="certificate.pem",
        )

    @rx.event
    def download_der(self):
        return rx.download(
            data=Path("foo.der").read_bytes(),
            filename="certificate.der",
        )



def index() -> rx.Component:
    return rx.fragment(
        rx.color_mode.button(),
        rx.vstack(
            rx.link("Download PEM file", on_click=State.download_pem),
            rx.link("Download DER file", on_click=State.download_der),
        ),
    )


app = rx.App()
app.add_page(index)

@riebecj
Copy link
Contributor Author

riebecj commented Nov 7, 2025

@riebecj we ended up taking a more generic fix, but I'm curious if you had a repro case to share where you were hitting issues.

I tried to reproduce this and wasn't able to.

"""Test files generated with:

ssh-keygen -t rsa -m PEM -f ./foo.pem
openssl rsa -outform der -in ./foo.pem -out foo.der
"""
from pathlib import Path

import reflex as rx


class State(rx.State):
    @rx.event
    def download_pem(self):
        return rx.download(
            data=Path("foo.pem").read_text(),
            filename="certificate.pem",
        )

    @rx.event
    def download_der(self):
        return rx.download(
            data=Path("foo.der").read_bytes(),
            filename="certificate.der",
        )



def index() -> rx.Component:
    return rx.fragment(
        rx.color_mode.button(),
        rx.vstack(
            rx.link("Download PEM file", on_click=State.download_pem),
            rx.link("Download DER file", on_click=State.download_der),
        ),
    )


app = rx.App()
app.add_page(index)

Instead of reading a file directly, pass is as an rx.Var like this:

rx.download(
    data=rx.Var.create(Path("foo.pem").read_text()),
    filename="certificate.pem",
)

You'll see what I mean.

@riebecj
Copy link
Contributor Author

riebecj commented Nov 7, 2025

@masenf @adhami3310 See my comment

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants