fix(xwebview): 将 react-native-blob-util 改为懒加载以兼容 RN 0.85 Bridgeless 模式

静态 import 会在模块评估时触发 fs.js:14 调用 ReactNativeBlobUtil.getConstants(),
在 Bridgeless 模式下 TurboModule 未注册时返回 null 导致崩溃。
改为在函数体内 require() 延迟至实际下载时才初始化。

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
这个提交包含在:
XuqmGroup 2026-06-21 09:23:17 +08:00
父节点 ff8f34e5a4
当前提交 51e016e185

查看文件

@ -1,5 +1,4 @@
import { Platform } from 'react-native' import { Platform } from 'react-native'
import RNFetchBlob from 'react-native-blob-util'
import type { import type {
XWebViewDownloadDecision, XWebViewDownloadDecision,
@ -8,6 +7,11 @@ import type {
XWebViewDownloadResult, XWebViewDownloadResult,
} from '@xuqm/rn-common' } from '@xuqm/rn-common'
// Lazy import: react-native-blob-util calls getConstants() at module evaluation time,
// which crashes in RN 0.85 Bridgeless mode. Defer the require until actual download use.
// eslint-disable-next-line @typescript-eslint/no-require-imports
const getRNFetchBlob = () => (require('react-native-blob-util') as { default: typeof import('react-native-blob-util').default }).default
function parseContentDispositionFilename(header: string): string | null { function parseContentDispositionFilename(header: string): string | null {
const rfc5987 = header.match(/filename\*=(?:[^']*'[^']*')?([^;\s]+)/i) const rfc5987 = header.match(/filename\*=(?:[^']*'[^']*')?([^;\s]+)/i)
if (rfc5987?.[1]) { if (rfc5987?.[1]) {
@ -70,7 +74,7 @@ async function resolveFilePath(
const full = `${dir}/${filename}` const full = `${dir}/${filename}`
if (conflict === 'overwrite') return full if (conflict === 'overwrite') return full
const exists = await RNFetchBlob.fs.exists(full) const exists = await getRNFetchBlob().fs.exists(full)
if (!exists) return full if (!exists) return full
const dot = filename.lastIndexOf('.') const dot = filename.lastIndexOf('.')
@ -82,7 +86,7 @@ async function resolveFilePath(
do { do {
candidate = `${dir}/${base}(${n})${ext}` candidate = `${dir}/${base}(${n})${ext}`
n++ n++
} while (await RNFetchBlob.fs.exists(candidate)) } while (await getRNFetchBlob().fs.exists(candidate))
return candidate return candidate
} }
@ -93,14 +97,14 @@ export async function saveBase64File(
savePath: string | undefined, savePath: string | undefined,
conflict: 'rename' | 'overwrite', conflict: 'rename' | 'overwrite',
): Promise<string> { ): Promise<string> {
const { dirs } = RNFetchBlob.fs const { dirs } = getRNFetchBlob().fs
const dir = const dir =
savePath ?? savePath ??
(Platform.OS === 'android' (Platform.OS === 'android'
? (dirs.DownloadDir ?? dirs.DocumentDir) ? (dirs.DownloadDir ?? dirs.DocumentDir)
: dirs.DocumentDir) : dirs.DocumentDir)
const filePath = await resolveFilePath(dir, filename, conflict) const filePath = await resolveFilePath(dir, filename, conflict)
await RNFetchBlob.fs.writeFile(filePath, base64, 'base64') await getRNFetchBlob().fs.writeFile(filePath, base64, 'base64')
return filePath return filePath
} }
@ -176,7 +180,7 @@ export function startDownload(
onComplete: (r: XWebViewDownloadResult) => void, onComplete: (r: XWebViewDownloadResult) => void,
onError: (error: string) => void, onError: (error: string) => void,
): DownloadHandle { ): DownloadHandle {
const { dirs } = RNFetchBlob.fs const { dirs } = getRNFetchBlob().fs
const dir = const dir =
savePath ?? savePath ??
(Platform.OS === 'android' (Platform.OS === 'android'
@ -192,7 +196,7 @@ export function startDownload(
.then(filePath => { .then(filePath => {
if (cancelled) return if (cancelled) return
const task = RNFetchBlob.config({ path: filePath }).fetch('GET', url) const task = getRNFetchBlob().config({ path: filePath }).fetch('GET', url)
cancelFn = () => task.cancel() cancelFn = () => task.cancel()
task.progress({ interval: 300 }, (received: number, total: number) => { task.progress({ interval: 300 }, (received: number, total: number) => {