feat: xwebview handleDownloadRequest + README 更新

- 新增 handleDownloadRequest() 统一下载处理函数
- 导出 download 相关工具函数
- 更新 rn-update 和 rn-xwebview README

Co-Authored-By: Claude <noreply@anthropic.com>
这个提交包含在:
XuqmGroup 2026-06-16 13:25:46 +08:00
父节点 0431b3d694
当前提交 8f0a940830
共有 5 个文件被更改,包括 155 次插入3 次删除

查看文件

@ -56,7 +56,7 @@ if (pluginInfo.needsUpdate) {
| API | 说明 |
|-----|------|
| `UpdateSDK.checkAppUpdate(bypassIgnore?)` | 检查 App 更新30 分钟缓存) |
| `UpdateSDK.downloadApk(info, options?)` | 下载 APK,返回 ArrayBuffer |
| `UpdateSDK.downloadApk(info, options?)` | 下载 APK,返回 ArrayBuffer(支持进度回调) |
| `UpdateSDK.downloadAndInstallApk(url, options?)` | 下载 APK 并调起系统安装器Android |
| `UpdateSDK.openStore(appStoreUrl?, marketUrl?)` | 跳转应用商店 |
| `UpdateSDK.getAppVersionCode()` | 获取当前 versionCode |
@ -67,6 +67,7 @@ if (pluginInfo.needsUpdate) {
| API | 说明 |
|-----|------|
| `UpdateSDK.checkPluginUpdate(moduleId)` | 检查插件更新30 分钟缓存) |
| `UpdateSDK.downloadPlugin(moduleId, info, options?)` | 下载插件 bundle,返回 JS 源码(支持进度回调) |
| `UpdateSDK.updatePlugin(moduleId, options?)` | 一步完成:检查 → 下载 → 写文件 → 重载 |
### 类型定义
@ -106,6 +107,31 @@ interface CachedRnBundle {
downloadedAt: string
source: string
}
type UpdateDownloadProgress = {
bytesDownloaded: number
totalBytes: number
/** 0-100 */
percent: number
}
```
### 下载进度回调
`downloadApk()``downloadPlugin()` 支持 `onProgress` 回调,使用 `fetch + ReadableStream` 实现流式进度计算:
```ts
// 下载 APK 带进度
const buffer = await UpdateSDK.downloadApk(info, {
onProgress: ({ bytesDownloaded, totalBytes, percent }) => {
console.log(`下载进度: ${percent}% (${bytesDownloaded}/${totalBytes})`)
},
})
// 下载插件 bundle 带进度
const source = await UpdateSDK.downloadPlugin('szyx', pluginInfo, {
onProgress: ({ percent }) => setProgress(percent),
})
```
## 工作原理

查看文件

@ -75,4 +75,59 @@ window.XWebViewBridge.postMessage(JSON.stringify({ type: 'login', token: '...' }
controller.postMessageToWeb("window.dispatchEvent(new CustomEvent('nativeMsg', { detail: { key: 'value' } }))")
```
详细 handler 列表见 `@xuqm/rn-common``xwebview/` 目录和 `docs/XWebView-JSBridge.md`
### 内置 JSBridge Handler
| Action | 说明 | 参数 |
|--------|------|------|
| `xuqm.getDeviceInfo` | 获取设备信息 | — |
| `xuqm.getToken` | 获取当前用户 token | — |
| `xuqm.getUserInfo` | 获取用户信息 | — |
| `xuqm.openNativePage` | 打开原生页面 | `{ route, params? }` |
| `xuqm.closeWebView` | 关闭 WebView | — |
| `xuqm.showToast` | 显示 Toast | `{ message }` |
| `xuqm.scanQRCode` | 打开扫码页(需宿主注册 handler | — |
## 文件下载
XWebView 内置文件下载支持,自动拦截以下场景:
1. URL 以常见下载扩展名结尾(`.pdf`, `.zip`, `.apk` 等)
2. `<a download="...">` 标签点击
3. `blob:` URL 下载(通过 injected JS 拦截)
### 使用 `handleDownloadRequest`
```ts
import { handleDownloadRequest } from '@xuqm/rn-xwebview'
const handle = await handleDownloadRequest(url, hintFilename, {
autoDownload: true,
downloadConflict: 'rename',
onDownloadProgress: (p) => console.log(`${p.percentage}%`),
onDownloadComplete: (r) => console.log('saved to', r.filePath),
onDownloadError: (url, err) => console.error(err),
})
// handle?.cancel() 可取消下载
```
### XWebViewConfig 下载相关参数
| 参数 | 类型 | 说明 |
|------|------|------|
| `autoDownload` | boolean | 自动下载(默认 true,false 时需 onDownloadDecide |
| `downloadConflict` | `'rename' \| 'overwrite'` | 文件名冲突策略(默认 rename |
| `onDownloadStart` | (request) => void | 下载开始回调 |
| `onDownloadProgress` | (progress) => void | 下载进度回调 |
| `onDownloadComplete` | (result) => void | 下载完成回调 |
| `onDownloadError` | (url, error) => void | 下载失败回调 |
| `onDownloadDecide` | (request) => Decision | 非自动模式下的下载决策回调 |
### 低级 API
| API | 说明 |
|-----|------|
| `fetchDownloadInfo(url, hintFilename?)` | HEAD 请求获取文件名/MIME/大小 |
| `saveBase64File(base64, filename, savePath?, conflict)` | 保存 base64 数据为文件 |
| `startDownload(url, filename, savePath?, conflict, onProgress, onComplete, onError)` | 开始下载,返回 DownloadHandle |
详细 handler 列表见 `@xuqm/rn-common``xwebview/` 目录。

查看文件

@ -2,6 +2,7 @@ import { Platform } from 'react-native'
import RNFetchBlob from 'react-native-blob-util'
import type {
XWebViewDownloadDecision,
XWebViewDownloadProgress,
XWebViewDownloadRequest,
XWebViewDownloadResult,
@ -105,6 +106,67 @@ export async function saveBase64File(
export type DownloadHandle = { cancel: () => void }
/**
*
*
* fetchDownloadInfo onDownloadDecide startDownload
* XWebViewScreen / XWebViewView 使
*
* @returns DownloadHandle null
*/
export async function handleDownloadRequest(
url: string,
hintFilename: string | undefined,
options: {
autoDownload?: boolean
downloadConflict?: 'rename' | 'overwrite'
savePath?: string
onDownloadDecide?: (
request: XWebViewDownloadRequest,
) => XWebViewDownloadDecision | Promise<XWebViewDownloadDecision>
onDownloadStart?: (request: XWebViewDownloadRequest) => void
onDownloadProgress?: (progress: XWebViewDownloadProgress) => void
onDownloadComplete?: (result: XWebViewDownloadResult) => void
onDownloadError?: (url: string, error: string) => void
} = {},
): Promise<DownloadHandle | null> {
const {
autoDownload = true,
downloadConflict = 'rename',
savePath,
onDownloadDecide,
onDownloadStart,
onDownloadProgress,
onDownloadComplete,
onDownloadError,
} = options
const info = await fetchDownloadInfo(url, hintFilename)
let finalFilename = info.suggestedFilename
let finalSavePath = savePath
// 非自动下载模式:需要宿主决定是否允许
if (!autoDownload) {
if (!onDownloadDecide) return null
const decision = await Promise.resolve(onDownloadDecide(info))
if (!decision.allowed) return null
if (decision.filename) finalFilename = decision.filename
if (decision.savePath) finalSavePath = decision.savePath
}
onDownloadStart?.({ ...info, suggestedFilename: finalFilename })
return startDownload(
url,
finalFilename,
finalSavePath,
downloadConflict,
p => onDownloadProgress?.(p),
r => onDownloadComplete?.(r),
err => onDownloadError?.(url, err),
)
}
export function startDownload(
url: string,
filename: string,

查看文件

@ -21,3 +21,5 @@ export type {
export { default as XWebViewProgress } from './XWebViewProgress'
export { XWebViewView } from './XWebViewView'
export { XWebViewScreen } from './XWebViewScreen'
export { handleDownloadRequest, fetchDownloadInfo, saveBase64File, startDownload } from './XWebViewDownload'
export type { DownloadHandle } from './XWebViewDownload'

查看文件

@ -23,7 +23,7 @@ export type {
export { PushSDK } from '@xuqm/rn-push'
export type { PushVendor } from '@xuqm/rn-push'
export { UpdateSDK } from '@xuqm/rn-update'
export type { PluginRegistration, PluginMeta, AppUpdateInfo, PluginUpdateInfo, CachedRnBundle } from '@xuqm/rn-update'
export type { PluginRegistration, PluginMeta, AppUpdateInfo, PluginUpdateInfo, CachedRnBundle, UpdateDownloadProgress } from '@xuqm/rn-update'
export {
XWebViewControl,
XWebViewScreen,
@ -31,8 +31,14 @@ export {
getXWebViewConfig,
openXWebView,
setXWebViewController,
setXWebViewScanQRCodeHandler,
handleDownloadRequest,
fetchDownloadInfo,
saveBase64File,
startDownload,
} from '@xuqm/rn-xwebview'
export type {
ScanQRCodeHandler,
XWebViewClickMenu,
XWebViewConfig,
XWebViewControllerAPI,
@ -42,4 +48,5 @@ export type {
XWebViewDownloadResult,
XWebViewMessageEvent,
XWebViewPermissionRequest,
DownloadHandle,
} from '@xuqm/rn-xwebview'