import ReactNativeBlobUtil from "react-native-blob-util"; import FileViewer from "react-native-file-viewer"; import Toast from "react-native-toast-message"; import { version as currentVersion } from "../package.json"; import { UPDATE_CONFIG } from "../constants/UpdateConfig"; interface VersionInfo { version: string; downloadUrl: string; } class UpdateService { private static instance: UpdateService; static getInstance(): UpdateService { if (!UpdateService.instance) { UpdateService.instance = new UpdateService(); } return UpdateService.instance; } async checkVersion(): Promise { let retries = 0; const maxRetries = 3; while (retries < maxRetries) { try { const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), 10000); // 10秒超时 const response = await fetch(UPDATE_CONFIG.GITHUB_RAW_URL, { signal: controller.signal, }); clearTimeout(timeoutId); if (!response.ok) { throw new Error(`HTTP ${response.status}: Failed to fetch version info`); } const remotePackage = await response.json(); const remoteVersion = remotePackage.version; return { version: remoteVersion, downloadUrl: UPDATE_CONFIG.GITHUB_RELEASE_URL_TEMPLATE.replace(/{version}/g, remoteVersion), }; } catch (error) { retries++; console.info(`Error checking version (attempt ${retries}/${maxRetries}):`, error); if (retries === maxRetries) { Toast.show({ type: "error", text1: "检查更新失败", text2: "无法获取版本信息,请检查网络连接" }); throw error; } // 等待一段时间后重试 await new Promise(resolve => setTimeout(resolve, 2000 * retries)); } } throw new Error("Maximum retry attempts exceeded"); } // 清理旧的APK文件 private async cleanOldApkFiles(): Promise { try { const { dirs } = ReactNativeBlobUtil.fs; // 使用DocumentDir而不是DownloadDir const files = await ReactNativeBlobUtil.fs.ls(dirs.DocumentDir); // 查找所有OrionTV APK文件 const apkFiles = files.filter(file => file.startsWith('OrionTV_v') && file.endsWith('.apk')); // 保留最新的2个文件,删除其他的 if (apkFiles.length > 2) { const sortedFiles = apkFiles.sort((a, b) => { // 从文件名中提取时间戳进行排序 const timeA = a.match(/OrionTV_v(\d+)\.apk/)?.[1] || '0'; const timeB = b.match(/OrionTV_v(\d+)\.apk/)?.[1] || '0'; return parseInt(timeB) - parseInt(timeA); }); // 删除旧文件 const filesToDelete = sortedFiles.slice(2); for (const file of filesToDelete) { try { await ReactNativeBlobUtil.fs.unlink(`${dirs.DocumentDir}/${file}`); console.log(`Cleaned old APK file: ${file}`); } catch (deleteError) { console.warn(`Failed to delete old APK file ${file}:`, deleteError); } } } } catch (error) { console.warn('Failed to clean old APK files:', error); } } async downloadApk(url: string, onProgress?: (progress: number) => void): Promise { let retries = 0; const maxRetries = 3; // 清理旧文件 await this.cleanOldApkFiles(); while (retries < maxRetries) { try { const { dirs } = ReactNativeBlobUtil.fs; const timestamp = new Date().getTime(); const fileName = `OrionTV_v${timestamp}.apk`; // 使用应用的外部文件目录,而不是系统下载目录 const filePath = `${dirs.DocumentDir}/${fileName}`; const task = ReactNativeBlobUtil.config({ fileCache: true, path: filePath, timeout: UPDATE_CONFIG.DOWNLOAD_TIMEOUT, // 移除 addAndroidDownloads 配置,避免使用系统下载管理器 // addAndroidDownloads: { // useDownloadManager: true, // notification: true, // title: UPDATE_CONFIG.NOTIFICATION.TITLE, // description: UPDATE_CONFIG.NOTIFICATION.DOWNLOADING_TEXT, // mime: "application/vnd.android.package-archive", // mediaScannable: true, // }, }).fetch("GET", url); // 监听下载进度 if (onProgress) { task.progress((received: string, total: string) => { const receivedNum = parseInt(received, 10); const totalNum = parseInt(total, 10); const progress = Math.floor((receivedNum / totalNum) * 100); onProgress(progress); }); } const res = await task; console.log(`APK downloaded successfully: ${filePath}`); return res.path(); } catch (error) { retries++; console.info(`Error downloading APK (attempt ${retries}/${maxRetries}):`, error); if (retries === maxRetries) { Toast.show({ type: "error", text1: "下载失败", text2: "APK下载失败,请检查网络连接" }); throw new Error(`Download failed after ${maxRetries} attempts: ${error}`); } // 等待一段时间后重试 await new Promise(resolve => setTimeout(resolve, 3000 * retries)); } } throw new Error("Maximum retry attempts exceeded for download"); } async installApk(filePath: string): Promise { try { // 首先检查文件是否存在 const exists = await ReactNativeBlobUtil.fs.exists(filePath); if (!exists) { throw new Error(`APK file not found: ${filePath}`); } // 使用FileViewer打开APK文件进行安装 // 这会触发Android的包安装器 await FileViewer.open(filePath, { showOpenWithDialog: true, // 显示选择应用对话框 showAppsSuggestions: true, // 显示应用建议 displayName: "OrionTV Update", }); } catch (error) { console.info("Error installing APK:", error); // 提供更详细的错误信息 if (error instanceof Error) { if (error.message.includes('No app found')) { Toast.show({ type: "error", text1: "安装失败", text2: "未找到可安装APK的应用,请确保允许安装未知来源的应用" }); throw new Error('未找到可安装APK的应用,请确保允许安装未知来源的应用'); } else if (error.message.includes('permission')) { Toast.show({ type: "error", text1: "安装失败", text2: "没有安装权限,请在设置中允许此应用安装未知来源的应用" }); throw new Error('没有安装权限,请在设置中允许此应用安装未知来源的应用'); } else { Toast.show({ type: "error", text1: "安装失败", text2: "APK安装过程中出现错误" }); } } else { Toast.show({ type: "error", text1: "安装失败", text2: "APK安装过程中出现未知错误" }); } throw error; } } compareVersions(v1: string, v2: string): number { const parts1 = v1.split(".").map(Number); const parts2 = v2.split(".").map(Number); for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) { const part1 = parts1[i] || 0; const part2 = parts2[i] || 0; if (part1 > part2) return 1; if (part1 < part2) return -1; } return 0; } getCurrentVersion(): string { return currentVersion; } isUpdateAvailable(remoteVersion: string): boolean { return this.compareVersions(remoteVersion, currentVersion) > 0; } } export default UpdateService.getInstance();