Skip to content

fix(media): force file download via fetch+blob instead of window.open (EVO-999)#44

Merged
dpaes merged 3 commits into
developfrom
fix/EVO-999
May 13, 2026
Merged

fix(media): force file download via fetch+blob instead of window.open (EVO-999)#44
dpaes merged 3 commits into
developfrom
fix/EVO-999

Conversation

@marcelogorutuba

@marcelogorutuba marcelogorutuba commented May 6, 2026

Copy link
Copy Markdown
Member

Summary

  • MessageFile.tsx: replaced window.open with fetch+blob download — forces real download instead of opening file in new browser tab
  • MessageFile.tsx: added toast.warning on both fallback paths (!response.ok and CORS/network catch) so the user knows the file is being opened in a new tab and why
  • i18n keys added for all 6 locales (downloadFallbackOpenedInNewTab, downloadFallbackReason.serverError, downloadFallbackReason.network)

Infrastructure prerequisite: this fix depends on CORS being configured on the storage backend (Access-Control-Allow-Origin allowing the front-end domain). Without it, the download falls back to opening the file in a new tab — but now with an informative toast. Validate in staging that direct download works before merging.

Validation

  • evo-ai-frontend-community: pnpm exec tsc -b --noEmit → no errors
  • evo-ai-frontend-community: pnpm exec eslint src/components/chat/messages/MessageFile.tsx → no errors

Changed Files

  • src/components/chat/messages/MessageFile.tsx
  • src/i18n/locales/en/chat.json
  • src/i18n/locales/es/chat.json
  • src/i18n/locales/fr/chat.json
  • src/i18n/locales/it/chat.json
  • src/i18n/locales/pt/chat.json
  • src/i18n/locales/pt-BR/chat.json

Related PRs

  • evo-ai-crm-community#47 (fix/EVO-999 — backend changes)

Linked Issue

  • EVO-999

… (EVO-999)

Replace openAttachmentInNewTab with fetch → blob → <a download> so that
clicking the download button on a file attachment triggers a real download
instead of opening the file in a new browser tab. Falls back to
openAttachmentInNewTab only when fetch fails (e.g. CORS).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@sourcery-ai

sourcery-ai Bot commented May 6, 2026

Copy link
Copy Markdown
Reviewer's guide (collapsed on small PRs)

Reviewer's Guide

Implements a more robust file download mechanism for chat message attachments by replacing window.open-based behavior with a fetch-to-blob download flow, with a graceful fallback to the previous implementation when fetch fails, and cleans up an unused import.

Sequence diagram for the new attachment download flow

sequenceDiagram
  actor User
  participant MessageFileComponent
  participant BrowserFetchAPI as BrowserFetchAPI
  participant Server
  participant BrowserDownload as BrowserDownload
  participant FallbackWindowOpen as FallbackWindowOpen

  User->>MessageFileComponent: click download button
  MessageFileComponent->>MessageFileComponent: downloadFile(attachment)
  MessageFileComponent->>MessageFileComponent: validate and trim url
  alt url is empty
    MessageFileComponent-->>User: no action
  else url is valid
    MessageFileComponent->>MessageFileComponent: resolve filename
    MessageFileComponent->>BrowserFetchAPI: fetch(url, mode: cors)
    alt fetch succeeds
      BrowserFetchAPI->>Server: HTTP GET url
      Server-->>BrowserFetchAPI: file response
      BrowserFetchAPI-->>MessageFileComponent: Response
      MessageFileComponent->>BrowserFetchAPI: response.blob()
      BrowserFetchAPI-->>MessageFileComponent: Blob
      MessageFileComponent->>BrowserDownload: URL.createObjectURL(blob)
      MessageFileComponent->>BrowserDownload: create <a> with href and download
      MessageFileComponent->>BrowserDownload: link.click()
      BrowserDownload-->>User: file is downloaded
      MessageFileComponent->>BrowserDownload: URL.revokeObjectURL(blobUrl)
    else fetch throws error
      MessageFileComponent->>FallbackWindowOpen: openAttachmentInNewTab(url, filename)
      FallbackWindowOpen-->>User: open file in new tab or previous behavior
    end
  end
Loading

Flow diagram for the updated downloadFile logic

flowchart TD
  A[Start downloadFile with attachment] --> B[Extract and trim url from attachment.data_url]
  B --> C{Is url non-empty?}
  C -- No --> D[Return without action]
  C -- Yes --> E[Determine filename from attachment.fallback_title or translation]
  E --> F[Call fetch with url and mode cors]
  F --> G{Did fetch succeed?}
  G -- Yes --> H[Get blob from response]
  H --> I[Create blobUrl with URL.createObjectURL]
  I --> J[Create anchor element]
  J --> K[Set anchor.href to blobUrl and anchor.download to filename]
  K --> L[Trigger anchor.click to start download]
  L --> M[Revoke blobUrl with URL.revokeObjectURL]
  M --> N[End]
  G -- No (throws) --> O[Call openAttachmentInNewTab with url and filename]
  O --> N[End]
Loading

File-Level Changes

Change Details Files
Replace window.open-based attachment handling with a fetch → blob → flow that triggers a direct file download, with a fallback to the previous behavior when fetch fails.
  • Make downloadFile async and guard against missing or whitespace-only attachment URLs
  • Derive a filename from the attachment fallback title or translation fallback key
  • Use fetch with CORS mode to retrieve the file, convert the response to a blob, and create an object URL
  • Programmatically create and click an element with the download attribute to trigger a download, then revoke the object URL
  • On fetch errors, call the existing openAttachmentInNewTab helper with the URL and filename to preserve previous behavior
src/components/chat/messages/MessageFile.tsx

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Hey - I've found 1 issue, and left some high level feedback:

  • In the new downloadFile implementation, consider checking response.ok (and possibly status codes) before calling response.blob() so that HTTP errors don’t result in attempting to download an error page as a file.
  • You might want to move the URL.revokeObjectURL(blobUrl) call into a finally block (after the link.click()) to ensure the object URL is always cleaned up even if an error occurs after it is created.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In the new `downloadFile` implementation, consider checking `response.ok` (and possibly status codes) before calling `response.blob()` so that HTTP errors don’t result in attempting to download an error page as a file.
- You might want to move the `URL.revokeObjectURL(blobUrl)` call into a `finally` block (after the `link.click()`) to ensure the object URL is always cleaned up even if an error occurs after it is created.

## Individual Comments

### Comment 1
<location path="src/components/chat/messages/MessageFile.tsx" line_range="30-31" />
<code_context>
+      link.download = filename;
+      link.click();
+      URL.revokeObjectURL(blobUrl);
+    } catch {
+      openAttachmentInNewTab({ url, filename });
     }
   };
</code_context>
<issue_to_address>
**question:** Consider whether to restore user feedback for the download fallback path.

Previously, when `openAttachmentInNewTab` fell back to a download flow, we showed a `downloadStarted` toast. With this change, that feedback is lost if the direct fetch fails and we call `openAttachmentInNewTab`. If you want to preserve that behavior, consider either propagating a signal from `openAttachmentInNewTab` or showing a generic info toast in this catch branch.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread src/components/chat/messages/MessageFile.tsx Outdated
marcelogorutuba and others added 2 commits May 13, 2026 15:20
- Check response.ok before response.blob() to handle HTTP error responses
- Add console.warn on non-ok response with status code and URL
- Add console.warn in catch block to surface CORS/network failures
- Fix URL.revokeObjectURL timing with setTimeout to prevent Safari/Firefox cancellation

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…geFile

When fetch fails due to non-ok HTTP response or CORS/network error, show
a warning toast informing the user that the file is being opened in a new
tab instead of downloading directly. Adds i18n keys for all 6 locales.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@dpaes dpaes merged commit 5a6598d into develop May 13, 2026
1 check passed
@dpaes dpaes deleted the fix/EVO-999 branch May 13, 2026 21:41
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.

2 participants