203 lines
5.9 KiB
JavaScript
203 lines
5.9 KiB
JavaScript
const updateEndpoint = String(import.meta.env.VITE_PDA_UPDATE_URL || '').trim()
|
||
|
||
let checking = false
|
||
|
||
function request(options) {
|
||
return new Promise((resolve, reject) => {
|
||
uni.request({
|
||
...options,
|
||
success: resolve,
|
||
fail: reject
|
||
})
|
||
})
|
||
}
|
||
|
||
function getRuntimeInfo() {
|
||
return new Promise((resolve, reject) => {
|
||
if (typeof plus === 'undefined' || !plus.runtime) {
|
||
reject(new Error('当前环境不支持 App 在线更新'))
|
||
return
|
||
}
|
||
|
||
plus.runtime.getProperty(plus.runtime.appid, (properties) => {
|
||
resolve({
|
||
appid: plus.runtime.appid,
|
||
appVersion: String(plus.runtime.version || ''),
|
||
appVersionCode: Number(plus.runtime.versionCode || 0),
|
||
wgtVersion: String(properties?.version || plus.runtime.version || '')
|
||
})
|
||
})
|
||
})
|
||
}
|
||
|
||
function normalizeUpdate(payload, runtime) {
|
||
const value = payload && typeof payload === 'object' ? payload : {}
|
||
if (value.update !== true) return null
|
||
|
||
if (value.appid !== runtime.appid) throw new Error('更新包与当前应用不匹配')
|
||
const packageType = String(value.packageType || '').toLowerCase()
|
||
const downloadUrl = String(value.downloadUrl || '').trim()
|
||
const versionName = String(value.versionName || '').trim()
|
||
if (!['apk', 'wgt'].includes(packageType) || !downloadUrl || !/^\d+(\.\d+)+$/.test(versionName)) {
|
||
throw new Error('更新服务返回了无效的版本信息')
|
||
}
|
||
if (!/^https:\/\//i.test(downloadUrl)) throw new Error('更新包必须使用 HTTPS 地址')
|
||
const minAppVersionCode = Number(value.minAppVersionCode)
|
||
if (packageType === 'wgt' && (!Number.isInteger(minAppVersionCode) || minAppVersionCode < 1
|
||
|| minAppVersionCode > runtime.appVersionCode)) {
|
||
throw new Error('当前安装包不支持此热更新,请安装新版 APK')
|
||
}
|
||
|
||
return {
|
||
packageType,
|
||
downloadUrl,
|
||
versionName,
|
||
versionCode: Number(value.versionCode || 0),
|
||
minAppVersionCode,
|
||
mandatory: value.mandatory === true,
|
||
releaseNotes: String(value.releaseNotes || '').trim()
|
||
}
|
||
}
|
||
|
||
function compareVersions(left, right) {
|
||
const leftParts = String(left).split('.').map(Number)
|
||
const rightParts = String(right).split('.').map(Number)
|
||
const length = Math.max(leftParts.length, rightParts.length)
|
||
for (let index = 0; index < length; index += 1) {
|
||
const difference = (leftParts[index] || 0) - (rightParts[index] || 0)
|
||
if (difference !== 0) return difference
|
||
}
|
||
return 0
|
||
}
|
||
|
||
function isNewerUpdate(update, runtime) {
|
||
if (update.packageType === 'apk') {
|
||
return Number.isInteger(update.versionCode) && update.versionCode > runtime.appVersionCode
|
||
}
|
||
return compareVersions(update.versionName, runtime.wgtVersion) > 0
|
||
}
|
||
|
||
function confirmUpdate(update) {
|
||
const notes = update.releaseNotes || '修复已知问题并提升作业稳定性。'
|
||
return new Promise((resolve) => {
|
||
uni.showModal({
|
||
title: `${update.mandatory ? '必须' : '发现'}更新 ${update.versionName}`,
|
||
content: notes,
|
||
confirmText: '立即更新',
|
||
cancelText: '稍后再说',
|
||
showCancel: !update.mandatory,
|
||
success: ({ confirm }) => resolve(Boolean(confirm)),
|
||
fail: () => resolve(false)
|
||
})
|
||
})
|
||
}
|
||
|
||
function downloadUpdate(update) {
|
||
return new Promise((resolve, reject) => {
|
||
uni.showLoading({ title: '正在下载 0%', mask: true })
|
||
const task = uni.downloadFile({
|
||
url: update.downloadUrl,
|
||
success: (result) => {
|
||
if (result.statusCode === 200 && result.tempFilePath) {
|
||
resolve(result.tempFilePath)
|
||
return
|
||
}
|
||
reject(new Error(`更新包下载失败(${result.statusCode || '未知状态'})`))
|
||
},
|
||
fail: () => reject(new Error('更新包下载失败,请检查网络后重试')),
|
||
complete: () => uni.hideLoading()
|
||
})
|
||
|
||
task.onProgressUpdate?.(({ progress }) => {
|
||
uni.showLoading({ title: `正在下载 ${progress}%`, mask: true })
|
||
})
|
||
})
|
||
}
|
||
|
||
function installUpdate(filePath, update) {
|
||
return new Promise((resolve, reject) => {
|
||
plus.runtime.install(
|
||
filePath,
|
||
{ force: false },
|
||
resolve,
|
||
() => reject(new Error('更新包安装失败,请重新下载'))
|
||
)
|
||
})
|
||
}
|
||
|
||
function showFailure(error, mandatory) {
|
||
const message = error?.message || '检查更新失败,请稍后重试'
|
||
if (!mandatory) {
|
||
uni.showToast({ title: message, icon: 'none', duration: 3000 })
|
||
return Promise.resolve(false)
|
||
}
|
||
|
||
return new Promise((resolve) => {
|
||
uni.showModal({
|
||
title: '更新失败',
|
||
content: message,
|
||
confirmText: '重新尝试',
|
||
showCancel: false,
|
||
complete: () => resolve(true)
|
||
})
|
||
})
|
||
}
|
||
|
||
async function runUpdate(update) {
|
||
const accepted = await confirmUpdate(update)
|
||
if (!accepted) return
|
||
|
||
try {
|
||
const filePath = await downloadUpdate(update)
|
||
await installUpdate(filePath, update)
|
||
if (update.packageType === 'wgt') {
|
||
uni.showModal({
|
||
title: '更新完成',
|
||
content: '应用将立即重启以启用新版本。',
|
||
showCancel: false,
|
||
complete: () => plus.runtime.restart()
|
||
})
|
||
}
|
||
} catch (error) {
|
||
const retry = await showFailure(error, update.mandatory)
|
||
if (retry) await runUpdate(update)
|
||
}
|
||
}
|
||
|
||
export async function checkForAppUpdate({ manual = false } = {}) {
|
||
if (checking) return
|
||
if (!updateEndpoint) {
|
||
if (manual) uni.showToast({ title: '未配置更新服务', icon: 'none' })
|
||
return
|
||
}
|
||
|
||
checking = true
|
||
try {
|
||
if (!/^https:\/\//i.test(updateEndpoint)) throw new Error('更新服务必须使用 HTTPS 地址')
|
||
const runtime = await getRuntimeInfo()
|
||
const response = await request({
|
||
url: updateEndpoint,
|
||
method: 'GET',
|
||
data: {
|
||
appid: runtime.appid,
|
||
platform: 'android',
|
||
appVersion: runtime.appVersion,
|
||
appVersionCode: runtime.appVersionCode,
|
||
wgtVersion: runtime.wgtVersion
|
||
}
|
||
})
|
||
if (response.statusCode !== 200) throw new Error('更新服务暂时不可用')
|
||
|
||
const update = normalizeUpdate(response.data?.data ?? response.data, runtime)
|
||
if (update && isNewerUpdate(update, runtime)) {
|
||
await runUpdate(update)
|
||
} else if (manual) {
|
||
uni.showToast({ title: '当前已是最新版本', icon: 'none' })
|
||
}
|
||
} catch (error) {
|
||
if (manual) await showFailure(error, false)
|
||
} finally {
|
||
checking = false
|
||
}
|
||
}
|