Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,13 @@
"expo-constants": "~18.0.9",
"expo-dev-client": "^6.0.17",
"expo-device": "~8.0.9",
"expo-file-system": "~19.0.21",
Comment thread
coderabbitai[bot] marked this conversation as resolved.
"expo-haptics": "^15.0.7",
"expo-local-authentication": "~17.0.7",
"expo-navigation-bar": "~5.0.8",
"expo-notifications": "~0.32.12",
"expo-secure-store": "~15.0.7",
"expo-sharing": "~14.0.8",
"expo-splash-screen": "~31.0.10",
"expo-status-bar": "^3.0.8",
"expo-store-review": "~9.0.8",
Expand Down
54 changes: 54 additions & 0 deletions src/lib/download.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/**
* Injected JavaScript to intercept blob URL creation for file downloads.
* When the web app creates a blob URL for a downloadable file (like CSV export),
* this script converts the blob to base64 and sends it to the native app
* via postMessage for proper file handling using expo-file-system and expo-sharing.
*/
export const injectedJavaScript = `
;(function() {
const originalCreateObjectURL = URL.createObjectURL;
URL.createObjectURL = function(blob) {
const url = originalCreateObjectURL.call(URL, blob);

// Check if this is a downloadable file type
const downloadableTypes = [
'text/csv',
'application/csv',
'application/octet-stream',
'text/plain',
'application/json'
];

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'
}));
}
}
};
reader.readAsDataURL(blob);
}

return url;
};
})();
`
29 changes: 29 additions & 0 deletions src/lib/getMessageManager.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
/* Register message handlers and injected JavaScript */
import * as Clipboard from 'expo-clipboard'
import { File as ExpoFile, Paths } from 'expo-file-system'
import * as Sharing from 'expo-sharing'
import once from 'lodash.once'
import { injectedJavaScript as injectedJavaScriptClipboard } from './clipboard'
import { injectedJavaScript as injectedJavaScriptDownload } from './download'
import * as StoreReview from 'expo-store-review'
import * as Application from 'expo-application'

Expand Down Expand Up @@ -32,6 +35,9 @@ export const getMessageManager = once(() => {
console.log('[App] Injecting clipboard JavaScript')
messageManager.registerInjectedJavaScript(injectedJavaScriptClipboard)

console.log('[App] Injecting download JavaScript')
messageManager.registerInjectedJavaScript(injectedJavaScriptDownload)

const walletManager = getWalletManager()

messageManager.on('console', onConsole)
Expand All @@ -56,6 +62,29 @@ export const getMessageManager = once(() => {
// clipboard
messageManager.on('setClipboard', evt => Clipboard.setStringAsync(evt.key))

// file download/sharing
messageManager.on('downloadFile', async evt => {
try {
const { data, filename, mimeType } = evt as unknown as {
data: string
filename: string
mimeType: string
}

const file = new ExpoFile(Paths.cache, filename)
file.write(data, { encoding: 'base64' })

if (await Sharing.isAvailableAsync()) {
await Sharing.shareAsync(file.uri, { mimeType })
}

return { success: true }
} catch (error) {
console.error('[downloadFile:Error]', error)
return { success: false, error: String(error) }
}
})

// expo token for push notifications
messageManager.on('getExpoToken', async () => {
try {
Expand Down
6 changes: 6 additions & 0 deletions src/lib/navigationFilter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,12 @@ export const shouldLoadFilter = (request: ShouldStartLoadRequest) => {
return true
}

// Handle blob URLs - these are handled by injected JavaScript in download.ts
// which intercepts URL.createObjectURL and sends file data via postMessage
if (request.url.startsWith('blob:')) {
return false
}

// External navigation
openBrowser(request.url).catch(r => {
console.error(`rejection opening in browser url "${request.url}": `, r)
Expand Down
21 changes: 21 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -10458,6 +10458,16 @@ __metadata:
languageName: node
linkType: hard

"expo-file-system@npm:~19.0.21":
version: 19.0.21
resolution: "expo-file-system@npm:19.0.21"
peerDependencies:
expo: "*"
react-native: "*"
checksum: 26b4c95c8d30580723995bf8e32287b72eadf0e3442ca14854c15dc47021c4c22eefbc490ab4fadf98b3f0b14847eba3012dfe663c8a8b42de7b1587f6811d8f
languageName: node
linkType: hard

"expo-font@npm:~14.0.9":
version: 14.0.9
resolution: "expo-font@npm:14.0.9"
Expand Down Expand Up @@ -10598,6 +10608,15 @@ __metadata:
languageName: node
linkType: hard

"expo-sharing@npm:~14.0.8":
version: 14.0.8
resolution: "expo-sharing@npm:14.0.8"
peerDependencies:
expo: "*"
checksum: 8ac54d82328141dc67add5b9db3f16253b42e0e0aa2d39589fffd4d9cf15067e10262b0ab21837b184caae2799de81d74f8b2d129886c50e25550ef4fc6919f3
languageName: node
linkType: hard

"expo-splash-screen@npm:~31.0.10":
version: 31.0.10
resolution: "expo-splash-screen@npm:31.0.10"
Expand Down Expand Up @@ -16520,11 +16539,13 @@ __metadata:
expo-constants: ~18.0.9
expo-dev-client: ^6.0.17
expo-device: ~8.0.9
expo-file-system: ~19.0.21
expo-haptics: ^15.0.7
expo-local-authentication: ~17.0.7
expo-navigation-bar: ~5.0.8
expo-notifications: ~0.32.12
expo-secure-store: ~15.0.7
expo-sharing: ~14.0.8
expo-splash-screen: ~31.0.10
expo-status-bar: ^3.0.8
expo-store-review: ~9.0.8
Expand Down