XuqmGroup-HarmonySDK/xuqm-sdk/src/main/ets/update/UpdateSDK.ets

250 行
7.7 KiB
Plaintext

2026-04-21 22:07:29 +08:00
import bundleManager from '@ohos.bundle.bundleManager'
import common from '@ohos.app.ability.common'
import request from '@ohos.request'
import deviceInfo from '@ohos.deviceInfo'
import type { BusinessError } from '@ohos.base'
2026-04-21 22:07:29 +08:00
import { HttpClient } from '../core/HttpClient'
import type { AppVersionInfo, InstalledRnBundleInfo, RnBundleInfo } from '../core/Types'
2026-04-21 22:07:29 +08:00
import { SDKContext } from '../core/SDKContext'
export interface AppUpdateResult {
hasUpdate: boolean
info?: AppVersionInfo
}
export interface RnUpdateResult {
hasUpdate: boolean
info?: RnBundleInfo
}
interface DeviceInfo {
deviceId: string
model: string
osVersion: string
vendor: string
}
2026-04-21 22:07:29 +08:00
export class UpdateSDK {
private static cacheKey = 'xuqm_update_cache'
private static ignoredPrefix = 'xuqm_ignored_v'
private static cacheTtl = 30 * 60 * 1000 // 30 minutes
/**
* 检查应用更新
* @param appKey 应用标识
* @param bypassIgnore 是否忽略已忽略的版本
*/
static async checkAppUpdate(appKey: string, bypassIgnore: boolean = false): Promise<AppUpdateResult> {
2026-04-21 22:07:29 +08:00
const bundleInfo = bundleManager.getBundleInfoForSelfSync(
bundleManager.BundleFlag.GET_BUNDLE_INFO_DEFAULT
)
const currentVersionCode = bundleInfo.versionCode
const currentVersionName = bundleInfo.versionName
const userId = SDKContext.getUserId()
2026-04-21 22:07:29 +08:00
// Check cache (skip if bypassIgnore)
if (!bypassIgnore) {
const cached = await this.readCache(appKey, currentVersionCode, userId)
if (cached) {
return cached.hasUpdate ? { hasUpdate: true, info: cached.info } : { hasUpdate: false }
}
}
const deviceInfo = this.getDeviceInfo()
let url = `/api/v1/updates/app/check?appKey=${appKey}&versionCode=${currentVersionCode}&platform=HARMONY`
url += `&currentVersionName=${encodeURIComponent(currentVersionName)}`
url += `&deviceId=${encodeURIComponent(deviceInfo.deviceId)}`
url += `&model=${encodeURIComponent(deviceInfo.model)}`
url += `&osVersion=${encodeURIComponent(deviceInfo.osVersion)}`
url += `&vendor=${encodeURIComponent(deviceInfo.vendor)}`
if (userId) {
url += `&userId=${encodeURIComponent(userId)}`
}
if (bypassIgnore) {
url += `&bypassIgnore=true`
}
const data = await HttpClient.get<AppVersionInfo>(url)
// Apply version ignore
if (!bypassIgnore && data.latestVersionCode > currentVersionCode && !data.forceUpdate) {
if (await this.isVersionIgnored(data.latestVersionCode)) {
return { hasUpdate: false }
}
}
// Write cache
await this.writeCache(appKey, currentVersionCode, userId, data)
2026-04-21 22:07:29 +08:00
if (data.latestVersionCode <= currentVersionCode) {
return { hasUpdate: false }
}
return { hasUpdate: true, info: data }
}
/**
* 打开应用市场
*/
static async openAppMarket(context: common.UIAbilityContext, url?: string): Promise<void> {
if (!url) {
return
}
await context.openLink(url, { appLinkingOnly: false })
}
/**
* 忽略版本
*/
static async ignoreVersion(versionCode: number): Promise<void> {
await SDKContext.setPreference(`${this.ignoredPrefix}${versionCode}`, 'true')
}
/**
* 清除所有忽略的版本
*/
static async clearIgnoredVersions(): Promise<void> {
// HarmonyOS preferences 没有批量删除,需要逐个清除
// 这里简化处理,实际可能需要维护一个忽略列表
}
/**
* 检查版本是否被忽略
*/
static async isVersionIgnored(versionCode: number): Promise<boolean> {
const value = await SDKContext.getPreference(`${this.ignoredPrefix}${versionCode}`)
return value === 'true'
}
/**
* 检查 RN Bundle 更新
*/
2026-04-21 22:07:29 +08:00
static async checkRnUpdate(
appKey: string,
bundleName: string,
currentBundleVersion?: number,
packageName?: string
2026-04-21 22:07:29 +08:00
): Promise<RnUpdateResult> {
const localBundleVersion = currentBundleVersion ?? await SDKContext.getRnBundleVersion(bundleName) ?? 0
2026-04-21 22:07:29 +08:00
const data = await HttpClient.get<RnBundleInfo>(
`/api/v1/rn/update/check?appKey=${appKey}&bundleName=${bundleName}&bundleVersion=${localBundleVersion}${packageName ? `&packageName=${encodeURIComponent(packageName)}` : ''}`
2026-04-21 22:07:29 +08:00
)
if (data.bundleVersion <= localBundleVersion) {
2026-04-21 22:07:29 +08:00
return { hasUpdate: false }
}
if (packageName && data.packageMatched === false) {
return { hasUpdate: false, info: data }
}
2026-04-21 22:07:29 +08:00
return { hasUpdate: true, info: data }
}
/**
* 下载 RN Bundle
*/
2026-04-21 22:07:29 +08:00
static async downloadRnBundle(
context: common.UIAbilityContext,
downloadUrl: string,
destFilename: string,
bundleName?: string,
bundleInfo?: RnBundleInfo,
2026-04-21 22:07:29 +08:00
): Promise<string> {
const destPath = context.cacheDir + '/' + destFilename
await new Promise<void>((resolve, reject) => {
request.downloadFile(context, {
url: downloadUrl,
filePath: destPath,
}, (err: BusinessError | null, task: request.DownloadTask) => {
2026-04-21 22:07:29 +08:00
if (err) { reject(err); return }
task.on('complete', resolve)
task.on('fail', (error: number) => reject(new Error(`Download failed: ${error}`)))
})
})
if (bundleName && bundleInfo) {
const installedInfo: InstalledRnBundleInfo = {
bundleVersion: bundleInfo.bundleVersion,
packageName: bundleInfo.packageName,
minCommonVersion: bundleInfo.minCommonVersion,
installedAt: Date.now(),
}
await SDKContext.setRnBundleInfo(bundleName, installedInfo)
}
2026-04-21 22:07:29 +08:00
if (SDKContext.getConfig().debug) {
console.log('[UpdateSDK] RN bundle downloaded to', destPath)
}
return destPath
}
/**
* 记录 RN Bundle 信息
*/
static async rememberRnBundleInfo(
bundleName: string,
bundleVersion: number,
packageName?: string,
minCommonVersion?: string,
): Promise<void> {
await SDKContext.setRnBundleInfo(bundleName, {
bundleVersion,
packageName,
minCommonVersion,
installedAt: Date.now(),
})
}
/**
* 获取已安装的 RN Bundle 信息
*/
static async getInstalledRnBundleInfo(bundleName: string): Promise<InstalledRnBundleInfo | null> {
return SDKContext.getRnBundleInfo(bundleName)
}
// MARK: - Device Info
private static getDeviceInfo(): DeviceInfo {
const deviceId = deviceInfo.ODID || ''
const brand = deviceInfo.brand || ''
const product = deviceInfo.product || ''
const model = `${brand} ${product}`.trim()
const osVersion = `HarmonyOS ${deviceInfo.osReleaseType || ''}`
const vendor = this.detectVendor(brand.toLowerCase())
return { deviceId, model, osVersion, vendor }
}
private static detectVendor(brand: string): string {
if (brand.includes('huawei')) return 'HUAWEI'
if (brand.includes('honor')) return 'HONOR'
return 'HUAWEI' // 鸿蒙默认华为
}
// MARK: - Cache
private static async readCache(appKey: string, versionCode: number, userId: string | null): Promise<{ hasUpdate: boolean; info?: AppVersionInfo } | null> {
try {
const key = `${this.cacheKey}_${appKey}_${versionCode}_${userId || ''}`
const raw = await SDKContext.getPreference(key)
if (!raw) return null
const cached = JSON.parse(raw)
if (Date.now() - cached.ts > this.cacheTtl) return null
return cached.data
} catch {
return null
}
}
private static async writeCache(appKey: string, versionCode: number, userId: string | null, info: AppVersionInfo): Promise<void> {
try {
const key = `${this.cacheKey}_${appKey}_${versionCode}_${userId || ''}`
await SDKContext.setPreference(key, JSON.stringify({
ts: Date.now(),
data: { hasUpdate: info.latestVersionCode > versionCode, info },
}))
} catch {
// ignore cache write errors
}
}
2026-04-21 22:07:29 +08:00
}