-
Notifications
You must be signed in to change notification settings - Fork 7
fix: handle blob URL downloads for history CSV export #158
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
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>
📝 WalkthroughWalkthroughA 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
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
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Poem
Pre-merge checks and finishing touches✅ Passed checks (5 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this 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.createObjectURLis 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 thatapplication/octet-streammaps 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.
⛔ Files ignored due to path filters (1)
yarn.lockis excluded by!**/yarn.lock,!**/*.lock
📒 Files selected for processing (4)
package.jsonsrc/lib/download.tssrc/lib/getMessageManager.tssrc/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: TheExpoFileclass constructor andwrite()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", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 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.
Summary
expo-file-systemandexpo-sharingDetails
The download button wasn't working because iOS cannot open
blob:URLs viaLinking.openURL. This PR:URL.createObjectURL()callspostMessageexpo-file-systemto write the file to cacheexpo-sharingTest plan
npx expo run:ios --devicetransaction-history.csvCloses #159
🤖 Generated with Claude Code
Summary by CodeRabbit
Release Notes
New Features
Chores
✏️ Tip: You can customize this high-level summary in your review settings.