Skip to content

Conversation

@0xApotheosis
Copy link
Member

@0xApotheosis 0xApotheosis commented Jan 6, 2026

Summary

  • Fixes the dead click on the download button in the history tab on mobile
  • Implements native file download/sharing using expo-file-system and expo-sharing
  • Intercepts blob URL creation in the WebView and sends file data to native for proper handling
Xnapper-2026-01-06-18 35 46

Details

The download button wasn't working because iOS cannot open blob: URLs via Linking.openURL. This PR:

  1. Adds injected JavaScript to intercept URL.createObjectURL() calls
  2. Converts blob content to base64 and sends it to native via postMessage
  3. Uses expo-file-system to write the file to cache
  4. Opens the iOS/Android share sheet via expo-sharing

Test plan

  • Build dev client: npx expo run:ios --device
  • Navigate to History tab
  • Tap the download button
  • Verify share sheet appears with transaction-history.csv
  • Save/share the file and verify contents

Closes #159

🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes

  • New Features

    • Enabled file download functionality for CSV, JSON, and text file formats within the app.
    • Downloaded files are automatically saved and available for sharing.
  • Chores

    • Added dependencies for enhanced file system and sharing capabilities.

✏️ Tip: You can customize this high-level summary in your review settings.

The download button in the history tab was not working because blob URLs
cannot be opened via Linking.openURL on iOS. This implements native file
download/sharing by intercepting blob URL creation in the WebView and
using expo-file-system and expo-sharing to save and share the file.

Closes shapeshift/web#10731

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@coderabbitai
Copy link

coderabbitai bot commented Jan 6, 2026

📝 Walkthrough

Walkthrough

A download mechanism for mobile was implemented by intercepting blob URL creation through injected JavaScript, routing the data to native message handlers that write files to cache and invoke native sharing APIs. Two Expo dependencies (file-system and sharing) were added to support this functionality.

Changes

Cohort / File(s) Summary
Dependencies
package.json
Added expo-file-system (~19.0.21) and expo-sharing (~14.0.8) for native file and sharing operations on mobile.
Download Interception
src/lib/download.ts
New module exporting injectedJavaScript—a self-invoking script that overrides URL.createObjectURL to intercept blob creation for CSV, JSON, and text MIME types, extracting base64 data and posting a downloadFile message to the native side.
Message Handler & File Operations
src/lib/getMessageManager.ts
Added imports for Expo file/sharing APIs and injected download script. Introduced downloadFile message handler that decodes base64, writes to cache directory, and shares the file via native APIs.
Navigation Safety
src/lib/navigationFilter.ts
Added blob URL filtering in shouldLoadFilter to prevent navigation to blob URLs, ensuring they are handled by download interception logic instead.

Sequence Diagram(s)

sequenceDiagram
    participant WebView as WebView<br/>(JavaScript)
    participant Bridge as Native Bridge<br/>(Message Handler)
    participant FileAPI as Expo FileSystem
    participant ShareAPI as Expo Sharing
    
    rect rgb(240, 248, 255)
    Note over WebView: Injected Download Script
    WebView->>WebView: createObjectURL(blob)<br/>called for CSV/JSON/text
    WebView->>WebView: Override intercepts<br/>blob creation
    WebView->>WebView: Extract base64<br/>from blob data
    end
    
    rect rgb(230, 245, 230)
    Note over WebView,Bridge: Cross-Platform Communication
    WebView->>Bridge: postMessage({<br/>  command: 'downloadFile',<br/>  base64, filename, mimeType<br/>})
    end
    
    rect rgb(255, 250, 230)
    Note over Bridge: File & Share Operations
    Bridge->>FileAPI: Write base64 to<br/>cache directory
    FileAPI-->>Bridge: File URI
    Bridge->>ShareAPI: Share file<br/>(if available)
    ShareAPI-->>Bridge: Share success
    end
    
    Bridge-->>WebView: Success response
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 A button clicked, but nothing came,
So we bridged JavaScript's flame—
Blob to base64, cache to share,
Now downloads flow through the air! 📥✨

Pre-merge checks and finishing touches

✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: fixing blob URL download handling for CSV export in the history feature on mobile.
Linked Issues check ✅ Passed The PR fully addresses the linked issue #10731 by implementing a complete mobile download flow using injected JavaScript, Expo file-system, and sharing APIs.
Out of Scope Changes check ✅ Passed All changes are directly related to fixing the blob URL download issue; adding dependencies, injecting download interception logic, handling messages, and filtering blob URLs are all necessary components of the solution.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/history-download-button

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@0xApotheosis 0xApotheosis marked this pull request as ready for review January 6, 2026 07:38
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI Agents
In @package.json:
- Line 30: The package.json pins "expo-file-system" to ~19.0.21 which shipped
breaking API changes for Expo SDK 54; audit all imports of "expo-file-system"
(search for import statements referencing expo-file-system) and either migrate
call sites to the new API surface in v19 or change imports to
"expo-file-system/legacy" where you need the old behavior, then update any
affected functions (e.g., readAsStringAsync/writeAsStringAsync/fileSystem
constants) to match the chosen API; ensure tests/build run after switching to
confirm there are no unresolved API mismatches.
🧹 Nitpick comments (4)
src/lib/getMessageManager.ts (1)

77-79: Consider user experience when sharing is unavailable.

If Sharing.isAvailableAsync() returns false, the file is written to cache but not shared with the user. Consider whether this is the intended behavior or if an error should be returned to inform the user.

💡 Potential improvement
       if (await Sharing.isAvailableAsync()) {
         await Sharing.shareAsync(file.uri, { mimeType })
+      } else {
+        console.warn('[downloadFile] Sharing not available on this device')
+        return { success: false, error: 'Sharing not available' }
       }
src/lib/download.ts (3)

8-53: Consider potential side effects of global override.

The override of URL.createObjectURL is never restored to the original implementation. While this works for the current use case, it could potentially affect other code that relies on the original behavior. For this specific fix targeting history CSV export, this is acceptable, but be aware of the global scope impact.


22-49: Consider adding blob URL cleanup.

After the blob data is extracted and posted to the native side, consider revoking the blob URL using URL.revokeObjectURL(url) to prevent potential memory leaks from accumulating blob URLs that are no longer needed.

💡 Suggested improvement
       if (blob instanceof Blob && downloadableTypes.includes(blob.type)) {
         const reader = new FileReader();
         reader.onloadend = function() {
           if (reader.result && typeof reader.result === 'string') {
             const base64 = reader.result.split(',')[1];
             if (base64 && window.ReactNativeWebView) {
               // Determine filename and extension based on MIME type
               const mimeToExt = {
                 'text/csv': '.csv',
                 'application/csv': '.csv',
                 'application/json': '.json',
                 'text/plain': '.txt',
                 'application/octet-stream': '.csv' // Default to CSV for history export
               };
               const ext = mimeToExt[blob.type] || '.csv';
               const filename = 'transaction-history' + ext;

               window.ReactNativeWebView.postMessage(JSON.stringify({
                 cmd: 'downloadFile',
                 data: base64,
                 filename: filename,
                 mimeType: blob.type || 'application/octet-stream'
               }));
+              
+              // Clean up blob URL after extraction
+              URL.revokeObjectURL(url);
             }
           }
         };
         reader.readAsDataURL(blob);
       }

34-34: Consider making filename and MIME type mapping more flexible.

The hardcoded filename 'transaction-history' and the assumption that application/octet-stream maps to CSV are specific to the current use case. If this code is intended to handle other download scenarios in the future, consider making these more flexible or configurable.

Also applies to: 37-37

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between fa2a6bf and 7a1842c.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (4)
  • package.json
  • src/lib/download.ts
  • src/lib/getMessageManager.ts
  • src/lib/navigationFilter.ts
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: NeOMakinG
Repo: shapeshift/mobile-app PR: 131
File: app.json:3-3
Timestamp: 2025-08-14T09:58:07.026Z
Learning: In the shapeshift/mobile-app repository, they use Expo's remote managed builds for Android and iOS. Only the version and runtimeVersion fields in app.json are important - build numbers like android.versionCode are managed automatically by the build system and don't need manual updates.
📚 Learning: 2025-08-14T09:58:07.026Z
Learnt from: NeOMakinG
Repo: shapeshift/mobile-app PR: 131
File: app.json:3-3
Timestamp: 2025-08-14T09:58:07.026Z
Learning: In the shapeshift/mobile-app repository, they use Expo's remote managed builds for Android and iOS. Only the version and runtimeVersion fields in app.json are important - build numbers like android.versionCode are managed automatically by the build system and don't need manual updates.

Applied to files:

  • package.json
📚 Learning: 2025-08-11T08:19:29.564Z
Learnt from: NeOMakinG
Repo: shapeshift/mobile-app PR: 126
File: package.json:27-27
Timestamp: 2025-08-11T08:19:29.564Z
Learning: expo-haptics version ^14.1.4 is compatible with Expo SDK 53 and has been tested to work correctly, despite documentation suggesting ~13.0.0 as the recommended version.

Applied to files:

  • package.json
🔇 Additional comments (2)
src/lib/navigationFilter.ts (1)

41-45: LGTM!

The blob URL filtering logic correctly prevents navigation to blob: URLs and delegates handling to the injected download JavaScript. This ensures the native file handling flow is used instead of attempting to navigate to blob URLs directly.

src/lib/getMessageManager.ts (1)

74-75: The ExpoFile class constructor and write() method are valid for expo-file-system ~19.0.21. This is part of the modern File API (the recommended object-oriented approach), not deprecated code. The File class with its write() method is documented in v19 and is the preferred approach over the legacy FileSystem.writeAsStringAsync() function.

"expo-constants": "~18.0.9",
"expo-dev-client": "^6.0.17",
"expo-device": "~8.0.9",
"expo-file-system": "~19.0.21",
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

expo-file-system expo-sharing compatibility Expo SDK 54

💡 Result:

Short answer: expo-file-system had breaking API changes in Expo SDK 54 (the "next" rewrite became the default and the old API is available as expo-file-system/legacy). expo-sharing remains compatible with SDK 54 (no breaking changes called out).

Details and actions:

  • Expo SDK 54 made the new expo-file-system API the default; if your code (or libraries you depend on) expect the old API, switch imports to expo-file-system/legacy (or migrate to the new API). [1]
  • Some helpers/APIs were moved/changed (examples: some methods were moved to legacy and others behave differently — e.g. getContentUriAsync/other legacy APIs are available under the legacy package). Check and update any code that uses removed/renamed methods. [2]
  • expo-sharing has no SDK‑54-specific breaking notes in the changelog and its docs remain the same; it should continue to work on SDK 54. [3]

If you want, tell me whether you’re upgrading an app or a library and I’ll suggest the exact import changes or search your package.json for affected packages.

Sources: [1] Expo SDK 54 changelog; [2] github issues about expo-file-system in SDK 54; [3] expo-sharing docs.


🌐 Web query:

Expo SDK 54 dependencies compatibility matrix

💡 Result:

Here’s a concise compatibility summary for Expo SDK 54 (key dependencies, platform/tooling, and important notes):

  • Expo package: expo@^54.0.0. [1]
  • React Native: 0.81 (Expo SDK 54 includes RN 0.81). [1]
  • React: 19.1. [1]
  • iOS toolchain: Xcode 26 recommended / required for iOS 26 features; Expo Go/EAS will use Xcode 26 for SDK 54. [1]
  • Android target: Android 16 / API 36 (edge-to-edge enabled by default). [1]
  • iOS runtime: React Native shipped as precompiled XCFrameworks (affects builds when using use_frameworks!). [1]
  • Autolinking / native modules: updated autolinking behavior (React Native modules installed transitively are autolinked); see autolinking flags in app/package.json if you need legacy behavior. Run npx expo-modules-autolinking verify -v or expo-doctor to check native modules. [1]
  • Upgrade/install helper: use npx expo install --fix and follow the upgrade guide (npm/yarn/pnpm/bun installs of expo@^54.0.0). Run npx expo-doctor after upgrading. [2]
  • Migration caveats: many third‑party libs required updates (Reanimated, worklets, NativeWind, mmkv, etc.); check library changelogs and the Expo GitHub issues if you hit build/runtime errors. [1][3]

Sources:

  • Expo SDK 54 release notes / changelog. [1]
  • Expo upgrade walkthrough (how to upgrade and use npx expo install --fix / expo-doctor). [2]
  • Selected Expo SDK 54 issues / community reports (third‑party compatibility notes). [3]

If you want, I can generate a more detailed matrix listing specific Expo module package versions (expo-constants, expo-file-system, reanimated, react-native-screens, etc.) pulled from the SDK 54 bundled modules. Which exact packages would you like included?


🌐 Web query:

expo-file-system 19.0.21 Expo SDK 54

💡 Result:

Short answer: Expo SDK 54 ships with expo-file-system v19 (bundled ~19.0.21). The package’s new API is now the default; the old API is available under expo-file-system/legacy if you need it. Install with npx expo install expo-file-system and update imports (or use /legacy). [1][2]

Sources:

  • Expo SDK 54 changelog (notes about expo-file-system/new vs legacy). [1]
  • Expo FileSystem SDK 54 reference (bundled version ~19.0.21; install instructions). [2]

Review code using expo-file-system for Expo SDK 54 breaking changes.

expo-file-system ~19.0.21 is compatible with Expo SDK 54 but includes breaking API changes: the new API became the default, and the old API is available under expo-file-system/legacy. Verify that your code uses the correct API or update imports to expo-file-system/legacy if needed.

expo-sharing ~14.0.8 is fully compatible with SDK 54 with no breaking changes.

🤖 Prompt for AI Agents
In @package.json around line 30, The package.json pins "expo-file-system" to
~19.0.21 which shipped breaking API changes for Expo SDK 54; audit all imports
of "expo-file-system" (search for import statements referencing
expo-file-system) and either migrate call sites to the new API surface in v19 or
change imports to "expo-file-system/legacy" where you need the old behavior,
then update any affected functions (e.g.,
readAsStringAsync/writeAsStringAsync/fileSystem constants) to match the chosen
API; ensure tests/build run after switching to confirm there are no unresolved
API mismatches.

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.

Download button in history tab on mobile is dead click

2 participants