feat: xwebview handleDownloadRequest + README 更新
- 新增 handleDownloadRequest() 统一下载处理函数 - 导出 download 相关工具函数 - 更新 rn-update 和 rn-xwebview README Co-Authored-By: Claude <noreply@anthropic.com>
这个提交包含在:
父节点
0431b3d694
当前提交
8f0a940830
@ -56,7 +56,7 @@ if (pluginInfo.needsUpdate) {
|
|||||||
| API | 说明 |
|
| API | 说明 |
|
||||||
|-----|------|
|
|-----|------|
|
||||||
| `UpdateSDK.checkAppUpdate(bypassIgnore?)` | 检查 App 更新(30 分钟缓存) |
|
| `UpdateSDK.checkAppUpdate(bypassIgnore?)` | 检查 App 更新(30 分钟缓存) |
|
||||||
| `UpdateSDK.downloadApk(info, options?)` | 下载 APK,返回 ArrayBuffer |
|
| `UpdateSDK.downloadApk(info, options?)` | 下载 APK,返回 ArrayBuffer(支持进度回调) |
|
||||||
| `UpdateSDK.downloadAndInstallApk(url, options?)` | 下载 APK 并调起系统安装器(Android) |
|
| `UpdateSDK.downloadAndInstallApk(url, options?)` | 下载 APK 并调起系统安装器(Android) |
|
||||||
| `UpdateSDK.openStore(appStoreUrl?, marketUrl?)` | 跳转应用商店 |
|
| `UpdateSDK.openStore(appStoreUrl?, marketUrl?)` | 跳转应用商店 |
|
||||||
| `UpdateSDK.getAppVersionCode()` | 获取当前 versionCode |
|
| `UpdateSDK.getAppVersionCode()` | 获取当前 versionCode |
|
||||||
@ -67,6 +67,7 @@ if (pluginInfo.needsUpdate) {
|
|||||||
| API | 说明 |
|
| API | 说明 |
|
||||||
|-----|------|
|
|-----|------|
|
||||||
| `UpdateSDK.checkPluginUpdate(moduleId)` | 检查插件更新(30 分钟缓存) |
|
| `UpdateSDK.checkPluginUpdate(moduleId)` | 检查插件更新(30 分钟缓存) |
|
||||||
|
| `UpdateSDK.downloadPlugin(moduleId, info, options?)` | 下载插件 bundle,返回 JS 源码(支持进度回调) |
|
||||||
| `UpdateSDK.updatePlugin(moduleId, options?)` | 一步完成:检查 → 下载 → 写文件 → 重载 |
|
| `UpdateSDK.updatePlugin(moduleId, options?)` | 一步完成:检查 → 下载 → 写文件 → 重载 |
|
||||||
|
|
||||||
### 类型定义
|
### 类型定义
|
||||||
@ -106,6 +107,31 @@ interface CachedRnBundle {
|
|||||||
downloadedAt: string
|
downloadedAt: string
|
||||||
source: 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' } }))")
|
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 RNFetchBlob from 'react-native-blob-util'
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
|
XWebViewDownloadDecision,
|
||||||
XWebViewDownloadProgress,
|
XWebViewDownloadProgress,
|
||||||
XWebViewDownloadRequest,
|
XWebViewDownloadRequest,
|
||||||
XWebViewDownloadResult,
|
XWebViewDownloadResult,
|
||||||
@ -105,6 +106,67 @@ export async function saveBase64File(
|
|||||||
|
|
||||||
export type DownloadHandle = { cancel: () => void }
|
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(
|
export function startDownload(
|
||||||
url: string,
|
url: string,
|
||||||
filename: string,
|
filename: string,
|
||||||
|
|||||||
@ -21,3 +21,5 @@ export type {
|
|||||||
export { default as XWebViewProgress } from './XWebViewProgress'
|
export { default as XWebViewProgress } from './XWebViewProgress'
|
||||||
export { XWebViewView } from './XWebViewView'
|
export { XWebViewView } from './XWebViewView'
|
||||||
export { XWebViewScreen } from './XWebViewScreen'
|
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 { PushSDK } from '@xuqm/rn-push'
|
||||||
export type { PushVendor } from '@xuqm/rn-push'
|
export type { PushVendor } from '@xuqm/rn-push'
|
||||||
export { UpdateSDK } from '@xuqm/rn-update'
|
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 {
|
export {
|
||||||
XWebViewControl,
|
XWebViewControl,
|
||||||
XWebViewScreen,
|
XWebViewScreen,
|
||||||
@ -31,8 +31,14 @@ export {
|
|||||||
getXWebViewConfig,
|
getXWebViewConfig,
|
||||||
openXWebView,
|
openXWebView,
|
||||||
setXWebViewController,
|
setXWebViewController,
|
||||||
|
setXWebViewScanQRCodeHandler,
|
||||||
|
handleDownloadRequest,
|
||||||
|
fetchDownloadInfo,
|
||||||
|
saveBase64File,
|
||||||
|
startDownload,
|
||||||
} from '@xuqm/rn-xwebview'
|
} from '@xuqm/rn-xwebview'
|
||||||
export type {
|
export type {
|
||||||
|
ScanQRCodeHandler,
|
||||||
XWebViewClickMenu,
|
XWebViewClickMenu,
|
||||||
XWebViewConfig,
|
XWebViewConfig,
|
||||||
XWebViewControllerAPI,
|
XWebViewControllerAPI,
|
||||||
@ -42,4 +48,5 @@ export type {
|
|||||||
XWebViewDownloadResult,
|
XWebViewDownloadResult,
|
||||||
XWebViewMessageEvent,
|
XWebViewMessageEvent,
|
||||||
XWebViewPermissionRequest,
|
XWebViewPermissionRequest,
|
||||||
|
DownloadHandle,
|
||||||
} from '@xuqm/rn-xwebview'
|
} from '@xuqm/rn-xwebview'
|
||||||
|
|||||||
正在加载...
在新工单中引用
屏蔽一个用户