Skip to main content

Updater 版本更新

Rydeen 客户端更新组件(整包升级 + 资源热修复补丁)。
统一封装移动端(Android/iOS)基于 Capgo Capacitor Updater(@capgo/capacitor-updater)的资源更新能力,并预留 Electron 整包 + 文件级补丁的扩展位,以 一个接口 IUpdateService 打通多端更新。

功能特性概览​

  • 跨平台统一接口:通过 IUpdateService 抽象出平台无关的更新能力,Angular 中通过 UPDATER_SERVICE 注入。
  • 版本检查:从 checkDomain 拉取最新 VersionMetadata,支持 static / dynamic 两种模式。
  • 下载与安装(移动端):基于 Capgo 的 download + set / next 实现资源包的下载与切换。
  • 事件链路:封装 Capgo 的下载、更新可用、无需更新、完成、失败等事件,并支持 App 的 pause / resume。
  • 静默 / 非静默检查:checkVersion(silence) 支持静默自动升级或仅返回版本信息,由业务决定弹窗。
  • 定时检查:checkVersionTimer() 按 intervalHours 周期检查。
  • 回滚能力:rollbackPrev() / rollbackOrigin() 支持回滚到上一版本或原始版本。
  • 本地版本管理:基于 @capacitor-rydeen/preferences 存储本地版本,getLocalVersion() 统一读取。

安装与依赖​

npm install @rydeen/updater

@rydeen/updater 通过 peerDependencies 声明所需三方依赖,不会自动安装,需要在业务工程中手动安装到自己的 package.json 中。

  • 必需依赖(移动端能力)

    • @capgo/capacitor-updater
    • @capacitor-rydeen/preferences
    • @capacitor/app
    • @capacitor/dialog(用于版本更新相关弹窗等交互)
  • 必需依赖(Electron 整包 + 资源补丁能力)

    • electron-updater

典型安装方式如下:

# 仅集成移动端(Android / iOS)更新能力
npm install @rydeen/updater \
@capgo/capacitor-updater \
@capacitor-rydeen/preferences \
@capacitor/app \
@capacitor/dialog

# 同时集成移动端 + Electron 桌面整包更新
npm install @rydeen/updater \
@capgo/capacitor-updater \
@capacitor-rydeen/preferences \
@capacitor/app \
@capacitor/dialog \
electron-updater

上述依赖在 @rydeen/updater 的 package.json 中以 peerDependencies 声明,
业务应用需要根据自己的实际平台(仅移动端 / 移动端 + Electron)按需安装,否则运行时会出现“模块未找到”或原生插件未注册等错误。


快速开始(5 分钟上手)​

适用场景:Angular + Capacitor 移动端项目,想先跑通一个“手动检查更新”的最小实现。

  • 第 1 步:安装依赖
npm install @rydeen/updater @capgo/capacitor-updater @capacitor-rydeen/preferences @capacitor/app
  • 第 2 步:在 app.config.ts 中接入 provideRydeenUpdater
import { ApplicationConfig } from '@angular/core'
import { provideRydeenUpdater, type RydeenUpdaterOptions } from '@rydeen/updater'
import { environment } from './environments/environment'

const updaterOptions: RydeenUpdaterOptions = {
updateMode: 'dynamic',
enable: true,
autoCheck: false,
checkDomain: environment.checkPatchUrl,
downloadDomain: environment.patchDownloadDomain,
intervalHours: 24,
originVersion: {
hasAppUpdate: false,
hasAssetsUpdate: false,
assetsVersion: environment.assetsVersion,
checksum: ''
}
}

export const appConfig: ApplicationConfig = {
providers: [
// ...
...provideRydeenUpdater(updaterOptions)
]
}
  • 第 3 步:创建一个最简单的更新服务并在页面中调用
// app-update.service.ts
import { inject, Injectable } from '@angular/core'
import { UPDATER_SERVICE, type IUpdateService, type VersionMetadata } from '@rydeen/updater'

@Injectable({ providedIn: 'root' })
export class AppUpdateService {
private readonly updater = inject<IUpdateService>(UPDATER_SERVICE)

async init(): Promise<void> {
await this.updater.registerEvents()
}

async checkForUpdates(): Promise<void> {
const meta: VersionMetadata = await this.updater.checkVersion(false)
if (!meta.hasAssetsUpdate) {
console.info('[版本升级]', '已经是最新版本。')
return
}
const plan = await this.updater.download(meta)
await this.updater.upgrade(plan, true)
}
}
// 某个组件中
import { Component, OnInit, inject } from '@angular/core'
import { AppUpdateService } from './core/services/app-update.service'

@Component({ /* ... */ })
export class SettingsPage implements OnInit {
private readonly appUpdate = inject(AppUpdateService)

async ngOnInit(): Promise<void> {
await this.appUpdate.init()
}

async onCheckUpdateClick(): Promise<void> {
await this.appUpdate.checkForUpdates()
}
}

上述代码让你在设置页加一个“检查更新”按钮即可完成最小集成。
若需要更复杂的弹窗、统计与可控流程,请参见后文的「业务封装模板:AppUpdateService(带弹窗与可控流程)」章节。


平台支持与能力矩阵​

平台版本检查 (checkVersion)资源热修复(资产更新)整包升级(应用版本)当前实现说明
Android✅✅(基于 Capgo 资源包)⚠️ 仅通过 hasAppUpdate 标记,由业务决定跳转商店或其他逻辑使用 CapacitorUpdateService,完整事件链路与下载 / 升级能力
iOS✅✅(基于 Capgo 资源包)⚠️ 同 Android,需自行实现 App Store 升级同 Android,共用 CapacitorUpdateService
Electron✅(通过 IPC 调用)✅ 默认 ZIP 资源补丁(可传回调覆盖)✅ 基于 electron-updater 的整包升级(在主进程实现)渲染进程使用 ElectronUpdateService,主进程通过 registerRydeenElectronUpdaterIpc 内置整包 + 资源补丁逻辑
Web✅(始终返回本地版本)❌ 不支持❌ 不支持NoopUpdateService,仅用于保持接口一致性
  • 关键点
    • 移动端实际下载 / 升级由 Capgo 完成,@rydeen/updater 做流程编排与事件封装。
    • Electron 目前为占位实现,仅提供统一的 IUpdateService 接口和日志提示,真正的整包 / 文件级热修复需要在 Electron 主进程 + 渲染进程中按项目规范接入。
    • Web 仅用于开发阶段占位,不会执行任何真实更新逻辑。

我应该先看哪一节?​

  • 只做移动端(Android / iOS):
    • 先完成上面的「快速开始(5 分钟上手)」;
    • 然后阅读「Angular 集成方式(推荐)」和「业务封装模板:AppUpdateService(带弹窗与可控流程)」。
  • 有 Electron 桌面端:
    • 在完成移动端集成后,再阅读「跨端完整 Sample:移动端 + Electron」和其中的 Electron preload / 主进程示例。
  • 暂时不想深入类型细节:
    • 可以跳过「核心数据结构与标志位含义」「UpdateOptions」「核心接口 IUpdateService」等章节;
    • 等需要自定义后端协议或版本策略时再回来查阅。

核心数据结构与标志位含义​

本节为 进阶参考,用于理解和自定义版本协议。
如果你只是想先跑通基础能力,可以暂时跳过,先看后面的「Angular 集成方式」和「业务封装模板」。

VersionMetadata —— 云端 / 本地版本元数据​

用于描述“当前应用版本”和“云端最新版本”的关键信息:

  • hasAppUpdate: boolean
    • 含义:是否有“应用级”更新(例如 APK / IPA / Electron 安装包升级)。
    • 典型用法:当为 true 时,业务可提示用户去应用商店 / 下载页,或在 Electron 中触发整包升级流程。
  • hasAssetsUpdate: boolean
    • 含义:是否有“客户端资源包”更新(静态资源 / 客户端代码)。
    • 典型用法:移动端走 Capgo 资源更新;Electron 走文件级补丁或 asar 资源替换。
  • assetsVersion: string
    • 含义:资源包版本号(通常与 Capgo Bundle 版本或补丁版本一一对应)。
    • 典型用法:用于比对本地 / 云端资源版本,或作为下载参数。
  • newVersion?: string
    • 含义:应用的新版本号(如 1.2.0),通常对应原生应用版本。
    • 典型用法:在 UI 中展示“发现新版本 x.x.x”,或与本地 App.getInfo().version 对比。
  • filename?: string
    • 含义:资源包的文件名(如 bundle-1.2.0.zip),与 downloadDomain 组合成下载地址时使用。
  • checksum: string
    • 含义:资源包的校验值(通常是 HASH),用于防止下载包被篡改。
  • sign?: string
    • 含义:服务端返回的“加密下载地址”字段,内部会先解码出真实 URL;若不存在则退化为 downloadDomain + filename。

总结:

  • hasAppUpdate 控制“是否弹窗提示整包升级(跳转商店 / Electron 全量更新)”;
  • hasAssetsUpdate 控制“是否执行资源热修复(Capgo 资源包 / Electron 文件补丁)”;
  • assetsVersion 和 checksum 用于版本比对与下载校验;
  • sign / filename 控制资源下载地址来源。

UpdatePlan —— 单次更新任务结果​

由 download() 返回,描述一次“资源下载任务”的最终结果:

  • hasUpdate: boolean
    • 表示本次请求是否确实有可用更新(通常等同于 VersionMetadata.hasAssetsUpdate)。
  • status: BundleStatus
    • 更新过程的状态枚举,见下文 BundleStatus。
  • message?: string
    • 人类可读的提示信息,如“下载成功”“下载出错,请重试”等。
  • code: MessageCode
    • 机器可读的消息代码,便于客户端根据不同错误码做差异化处理。常见示例:
      • DOWNLOAD_SUCCESS:下载成功;
      • DOWNLOAD_ERROR:下载失败;
      • 其他业务自定义码。
  • data?: BundleInfo
    • 对应 Capgo 的 BundleInfo 对象(仅移动端可用),包含 bundle id / version / status 等信息。
  • metadata: VersionMetadata
    • 与本次下载对应的版本元数据。

BundleStatus —— 下载状态枚举​

  • SUCCESS:下载 / 准备成功,可以进入 upgrade() 流程。
  • ERROR:下载或校验失败,应提示用户或允许重试。
  • PENDING:任务已创建但尚未开始(当前实现中较少使用,预留扩展)。
  • DOWNLOADING:正在下载中,一般配合 download 事件中的进度使用。

UpdateOptions —— 组件初始化参数​

UpdateOptions 决定了整个更新系统的工作模式:

  • updateMode: 'static' | 'dynamic'
    • static:通过 GET checkDomain 获取版本信息,常用于静态 JSON 或 CDN 文件。
    • dynamic:通过 POST checkDomain 携带应用信息(包名、build、当前资源版本)获取个性化版本策略。
  • enable: boolean
    • 是否启用热更新能力。
    • 为 false 时,checkVersion() 直接返回一个“无更新”的 VersionMetadata。
  • autoCheck: boolean
    • 是否开启定时自动检查。
    • 配合 checkVersionTimer() 使用。
  • checkDomain: string
    • 版本检查接口地址(HTTP/HTTPS)。
  • downloadDomain: string
    • 资源包基础下载地址。当 VersionMetadata.sign 不存在时,使用 downloadDomain + '/' + filename 拼出最终 URL。
  • statisticsDomain?: string
    • 统计上报接口地址;若设置,report() 会向该地址上报设备 / 版本信息。
  • intervalHours: number
    • 自动检查间隔(小时为单位)。
  • originVersion: VersionMetadata
    • 应用原始版本信息(即未打补丁时的初始版本)。
    • 在本地无任何版本记录时作为默认“本地版本”写入 Preferences。
  • 事件回调(均为可选)
    • download?: DownloadProcessEvent:下载进度回调。
    • noNeed?: NoNeedEvent:无需更新。
    • updateAvailable?: UpdateAvailableEvent:发现可用更新。
    • downloadComplete?: DownloadCompleteEvent:下载完成。
    • updateFailed?: UpdateFailedEvent:更新失败。
    • downloadFailed?: DownloadFailedEvent:下载失败。
    • appReloaded?: AppReloadedEvent:应用被重载。
    • appReady?: AppReadyEvent:Capgo 通知“应用准备就绪”。

设计意图:通过 UpdateOptions 将“版本策略、网络入口、统计、交互回调”集中配置,运行时只关注 IUpdateService 的方法调用。


核心接口 IUpdateService​

IUpdateService 是跨平台统一的更新接口,主要方法含义如下(伪代码签名):

interface IUpdateService {
registerEvents(): Promise<void>
checkVersion(silence?: boolean): Promise<VersionMetadata>
download(cloudVersion: VersionMetadata): Promise<UpdatePlan>
upgrade(plan: UpdatePlan, updateNow?: boolean): Promise<void>
rollbackPrev(): Promise<void>
rollbackOrigin(): Promise<void>
getLocalVersion(): Promise<VersionMetadata>
setAutoCheck(autoCheck: boolean): void
setIntervalHours(hours: number): void
checkVersionTimer(): void
}
  • 移动端实现:CapacitorUpdateService implements IUpdateService
  • Electron 占位实现:ElectronUpdateService implements IUpdateService(内部多为日志 + 透传)
  • Web / 其他平台:NoopUpdateService implements IUpdateService

API 说明(以 CapacitorUpdateService / 移动端为例)​

  • new CapacitorUpdateService(options: UpdateOptions)

    • 构造函数,仅保存配置,不会自动发起网络请求。
  • registerEvents()

    • 注册 Capgo 的所有相关事件(download / noNeedUpdate / updateAvailable / downloadComplete / updateFailed / downloadFailed / appReloaded / appReady)。
    • 注册 App 的 pause / resume,在 resume 时重新启动定时检查。
    • 内部调用 CapacitorUpdater.notifyAppReady()。
  • checkVersion(silence?: boolean)

    • 调用内部 check(),根据 updateMode 请求 checkDomain。
    • silence === false:仅返回 VersionMetadata,不做任何下载或升级。
    • silence === true:当 hasAssetsUpdate === true 时,会自动执行 download() + upgrade() 实现“静默更新”。
  • download(cloudVersion: VersionMetadata)

    • 根据 cloudVersion.sign 或 downloadDomain + filename 计算下载 URL。
    • 调用 Capgo 的 download,返回封装好的 UpdatePlan。
  • upgrade(plan: UpdatePlan, updateNow = true)

    • 将下载好的资源设置为当前版本:
      • updateNow === true:立即切换(CapacitorUpdater.set)。
      • updateNow === false:下次启动生效(CapacitorUpdater.next)。
    • 写入本地 Preferences 的 localVersion。
  • rollbackPrev() / rollbackOrigin()

    • 封装 Capgo 的 reset 能力,实现到上一成功版本或原始版本的回滚。
  • getLocalVersion()

    • 从 Preferences 读取 localVersion;不存在时返回 originVersion。
  • setAutoCheck() / setIntervalHours() / checkVersionTimer()

    • 控制自动检查开关与间隔,并通过 setInterval 周期调用 checkVersion(true)。

Angular 集成方式(推荐)​

在 Angular 应用中推荐通过 provideRydeenUpdater + UPDATER_SERVICE 统一注入:

// app.config.ts
import { ApplicationConfig } from '@angular/core'
import {
provideRydeenUpdater,
UPDATER_SERVICE,
type RydeenUpdaterOptions,
type IUpdateService
} from '@rydeen/updater'

const hotfixOptions: RydeenUpdaterOptions = {
updateMode: 'static',
enable: true,
autoCheck: true,
checkDomain: 'https://your-server.com/version',
downloadDomain: 'https://your-server.com/assets',
intervalHours: 24,
originVersion: {
hasAppUpdate: false,
hasAssetsUpdate: false,
assetsVersion: '1.0.0',
checksum: ''
}
}

export const appConfig: ApplicationConfig = {
providers: [
// ...
...provideRydeenUpdater(hotfixOptions)
]
}

在业务服务 / 组件中使用:

import { inject, Injectable } from '@angular/core'
import { UPDATER_SERVICE, type IUpdateService } from '@rydeen/updater'

@Injectable({ providedIn: 'root' })
export class AppUpdateService {
private readonly updater = inject<IUpdateService>(UPDATER_SERVICE)

async init() {
await this.updater.registerEvents()
this.updater.checkVersionTimer()
}

async manualCheck() {
const meta = await this.updater.checkVersion(false)
if (meta.hasAssetsUpdate) {
const plan = await this.updater.download(meta)
await this.updater.upgrade(plan, true)
}
}
}

业务封装模板:AppUpdateService(带弹窗与可控流程)​

在实际项目中,通常会在插件之上再封装一层业务服务,例如 AppUpdateService:

  • 对外只暴露少量简单方法(initial、silentlyCheckForUpdates、checkForUpdates、reportStatisticInfo 等);
  • 内部统一处理版本检查、统计上报、进度上报、弹窗确认、是否立即重启等细节;
  • 其它模块只需要依赖这个业务服务即可,不需要直接操作底层 IUpdateService。

下面给出一个推荐的封装模板,开发者可以直接复制,根据自己项目结构调整 environment 与 commons 路径即可:

// app.config.ts - 与旧项目类似,从环境变量中读取配置
import { ApplicationConfig } from '@angular/core'
import { provideRydeenUpdater, type RydeenUpdaterOptions } from '@rydeen/updater'
import { environment } from '../environments/environment'

const updaterOptions: RydeenUpdaterOptions = {
updateMode: 'dynamic',
enable: true,
autoCheck: true,
checkDomain: environment.checkPatchUrl,
downloadDomain: environment.patchDownloadDomain,
statisticsDomain: environment.versionReportUrl,
intervalHours: 24,
originVersion: {
hasAppUpdate: false,
hasAssetsUpdate: false,
assetsVersion: environment.assetsVersion,
checksum: ''
}
}

export const appConfig: ApplicationConfig = {
providers: [
// ...
...provideRydeenUpdater(updaterOptions)
]
}
// app.update.service.ts - 业务层封装(带弹窗、可控流程)
import { Injectable, inject } from '@angular/core'
import {
UPDATER_SERVICE,
type IUpdateService,
type BundleInfo,
BundleStatus,
type UpdatePlan,
type VersionMetadata
} from '@rydeen/updater'
import { environment } from '../environments/environment'
import { ToastDuration, Tools } from '@capacitor-rydeen/tools'
import { Capacitor } from '@capacitor/core'
import { LocalStorage } from '../commons/local.storage'
import { CacheKey } from '../commons/cache.key'

@Injectable({ providedIn: 'root' })
export class AppUpdateService {
private readonly updater = inject<IUpdateService>(UPDATER_SERVICE)

// 1. 初始化入口:启动时调用一次
async initial(): Promise<void> {
await this.registerEvents()
// 如需自动检查,可在此开启定时任务:
// this.updater.setAutoCheck(true)
// this.updater.setIntervalHours(24)
// this.updater.checkVersionTimer()
}

// 2. 对外暴露获取本地版本
async getLocalVersion(): Promise<VersionMetadata> {
return this.updater.getLocalVersion()
}

async registerEvents(): Promise<void> {
await this.updater.registerEvents()
}

/**
* 下载进度事件(当开始下载更新包的时候触发的事件,通常用于显示更新进度)
*/
private downloadProcessEvent(percent: number, bundle: BundleInfo): void {
// 这里可以接入你自己的事件总线 / 全局 Loading 等
console.info('[版本升级][下载进度事件]', percent, bundle)
}

private downloadFailedEvent(version: string): void {
console.error('[版本升级][下载失败]', version)
}

private updateAvailableEvent(bundle: BundleInfo): void {
console.info('[版本升级][有可用更新]', bundle)
}

private updateFailedEvent(bundle: BundleInfo): void {
console.error('[版本升级][更新失败]', bundle)
}

private appReloadedEvent(): void {
console.info('[版本升级][应用重载]')
}

private appReadyEvent(status: string, bundle: BundleInfo): void {
console.info('[版本升级][应用准备就绪]', status, bundle)
}

/**
* 上报统计信息:24 小时节流一次
*/
async reportStatisticInfo(): Promise<void> {
console.log('[版本升级]', '版本统计上报')
const timestamp = await LocalStorage.getItem<number>(CacheKey.LAST_STATISTICS_TIME)
if (timestamp != null && new Date().getTime() - timestamp < 24 * 60 * 60 * 1000) {
return
}

const deviceId = await LocalStorage.getItem<string>(CacheKey.DEVICE_ID)
if (deviceId == null) {
console.error('设备ID为空,无法上报设备信息。')
return
}
console.log('[版本升级]', `设备ID:${deviceId}`)
const ver = await Tools.getClientVersionInfo()
ver.webviewVersion = !ver.webviewVersion ? '' : ver.webviewVersion

const statisticInfo = {
deviceId,
platform: Capacitor.getPlatform(),
osVersion: ver.osVersion,
osLanguage: navigator.language,
webviewVersion: ver.webviewVersion,
userAgent: navigator.userAgent
}

try {
await fetch(environment.versionReportUrl, {
method: 'POST',
body: JSON.stringify(statisticInfo),
headers: {
cache: 'no-cache',
'Content-Type': 'application/json'
}
})
await LocalStorage.setItem(CacheKey.LAST_STATISTICS_TIME, new Date().getTime())
console.log('[版本升级]', '版本统计上报完成')
} catch (e) {
console.error('[版本升级] 版本统计上报失败', e)
}
}

/**
* 静默检查更新:不弹窗,适合启动时、后台场景
*/
async silentlyCheckForUpdates(): Promise<void> {
const meta = await this.updater.checkVersion(false)
if (!meta.hasAssetsUpdate) {
console.info('[版本升级]', '没有新的资源包版本。')
return
}
console.info('[版本升级]', `检查到新版本,开始下载…… (${JSON.stringify(meta)})`)
const plan = await this.download(meta)
if (plan.status === BundleStatus.ERROR) {
console.error('[版本升级]', '静默下载失败', plan.message)
return
}
await this.apply(plan, false) // 静默下载,重启后生效
}

/**
* 手工检查更新:带交互提示、可控流程
*/
async checkForUpdates(): Promise<void> {
await this.registerEvents()
let meta: VersionMetadata
try {
if (Capacitor.getPlatform() === 'web') {
// Web 端仅做示意,占位返回一个假数据,方便联调 UI
meta = {
hasAssetsUpdate: true,
hasAppUpdate: true,
assetsVersion: '20240619',
newVersion: '3.0.1',
sign: 'mock-sign'
} as VersionMetadata
} else {
meta = await this.checkVersion()
}
await this.reportStatisticInfo()
if (!meta.hasAssetsUpdate) {
await Tools.toast({
message: '已经是最新版本',
duration: ToastDuration.LENGTH_LONG
})
return
}
} catch (e) {
console.error('[版本升级]', e)
await Tools.toast({
message: '检查更新失败,请稍后重试',
duration: ToastDuration.LENGTH_LONG
})
return
}

// 1)询问用户是否下载补丁
const confirmDownload = await this.confirm({
message: `发现新资源版本:${meta.assetsVersion},是否立即下载?`,
okText: '立即下载',
cancelText: '暂不'
})
if (!confirmDownload) {
return
}

await Tools.toast({
message: '开始下载更新包,请稍候…',
duration: ToastDuration.LENGTH_SHORT
})

// 2)执行下载
const plan = await this.download(meta)
if (plan.status === BundleStatus.ERROR) {
await Tools.toast({
message: plan.message ?? '下载失败,请稍后重试',
duration: ToastDuration.LENGTH_LONG
})
return
}

// 3)下载成功后,再询问用户是“稍后重启”还是“立即重启”
const confirmRestart = await this.confirm({
message: '更新包下载完成,是否立即重启应用以完成升级?',
okText: '立即重启',
cancelText: '稍后'
})

if (confirmRestart) {
await this.apply(plan, true)
} else {
await this.apply(plan, false)
}
}

/**
* 统一的确认弹窗封装
* 这里仅给出接口形态,具体实现请对接你自己的弹窗组件(如 Ionic Alert、Material Dialog 等)
*/
private async confirm(options: { message: string; okText?: string; cancelText?: string }): Promise<boolean> {
// TODO: 替换为你自己的 UI 实现
// 例如:
// const result = await this.dialog.openConfirm({ ...options })
// return result === 'ok'
console.info('[版本升级][确认弹窗占位]', options)
return true
}

private async checkVersion(): Promise<VersionMetadata> {
return this.updater.checkVersion(false)
}

private async download(meta: VersionMetadata): Promise<UpdatePlan> {
return this.updater.download(meta)
}

private async apply(plan: UpdatePlan, immediately: boolean = true): Promise<void> {
await this.updater.upgrade(plan, immediately)
}
}

通过上述封装,业务层可以继续只依赖 UpdateService,而无需直接感知 @rydeen/updater 的底层实现细节。
迁移时主要工作是:替换导入路径、调整类型名(HotfixOptions -> UpdateOptions 等),以及根据新接口稍作参数适配。


非 Angular / 直接使用示例​

import { CapacitorUpdateService, type UpdateOptions, type VersionMetadata } from '@rydeen/updater'

const options: UpdateOptions = {
updateMode: 'static',
enable: true,
autoCheck: true,
checkDomain: 'https://your-server.com/version',
downloadDomain: 'https://your-server.com/assets',
intervalHours: 24,
originVersion: {
hasAppUpdate: false,
hasAssetsUpdate: false,
assetsVersion: '1.0.0',
checksum: ''
}
}

const updater = new CapacitorUpdateService(options)
await updater.registerEvents()
updater.checkVersionTimer()

// 非静默检查:仅拿版本数据,由业务弹窗
const meta: VersionMetadata = await updater.checkVersion(false)
if (meta.hasAssetsUpdate) {
const plan = await updater.download(meta)
await updater.upgrade(plan, true)
}

跨端完整 Sample:移动端 + Electron​

下面给出一个“工程级”完整示例,展示如何在 同一套业务代码 中同时支持 Android/iOS(Capgo)+ Electron(electron-updater)。

1. 安装依赖(业务工程根目录)​

npm install @rydeen/updater @capgo/capacitor-updater @capacitor-rydeen/preferences @capacitor/app electron-updater

2. Angular 应用(渲染进程 / 移动端)接入​

app.config.ts:

import { ApplicationConfig } from '@angular/core'
import {
provideRydeenUpdater,
UPDATER_SERVICE,
type RydeenUpdaterOptions
} from '@rydeen/updater'

const updaterOptions: RydeenUpdaterOptions = {
updateMode: 'static',
enable: true,
autoCheck: true,
checkDomain: 'https://your-server.com/version',
downloadDomain: 'https://your-server.com/assets',
intervalHours: 12,
originVersion: {
hasAppUpdate: false,
hasAssetsUpdate: false,
assetsVersion: '1.0.0',
checksum: ''
}
}

export const appConfig: ApplicationConfig = {
providers: [
// ...
...provideRydeenUpdater(updaterOptions)
]
}

app-update.service.ts(统一的更新入口,移动端 / Electron 共用):

import { inject, Injectable } from '@angular/core'
import { UPDATER_SERVICE, type IUpdateService } from '@rydeen/updater'

@Injectable({ providedIn: 'root' })
export class AppUpdateService {
private readonly updater = inject<IUpdateService>(UPDATER_SERVICE)

async init() {
await this.updater.registerEvents()
this.updater.checkVersionTimer()
}

async manualCheck() {
const meta = await this.updater.checkVersion(false)
if (meta.hasAppUpdate || meta.hasAssetsUpdate) {
// 这里可以弹出统一的“发现新版本”对话框
const plan = await this.updater.download(meta)
await this.updater.upgrade(plan, true)
}
}
}

在移动端(Android/iOS)运行时:
createPlatformUpdateService() 返回 CapacitorUpdateService,内部使用 Capgo 进行资源热修复。
在 Electron 中运行时:
createPlatformUpdateService() 返回 ElectronUpdateService,通过 IPC 调用主进程的 electron-updater / 补丁逻辑。

3. Electron preload:暴露 ipcRenderer​

preload.ts:

import { contextBridge, ipcRenderer } from 'electron'

contextBridge.exposeInMainWorld('electron', {
ipcRenderer: {
invoke: (channel: string, ...args: any[]) => ipcRenderer.invoke(channel, ...args)
}
})

这样渲染进程中的 ElectronUpdateService 可以通过 window.electron.ipcRenderer.invoke(...) 调用主进程。

4. Electron 主进程:基于 electron-updater + 封装入口​

推荐使用 @rydeen/updater/electron-main 提供的主进程封装,而不是手写所有 IPC:

main.ts(仅示意关键部分,实际工程中请按你的启动流程组织):

import { app, BrowserWindow } from 'electron'
import { registerRydeenElectronUpdaterIpc } from '@rydeen/updater/electron-main'

function createWindow() {
const win = new BrowserWindow({
webPreferences: {
preload: /* 你的 preload 路径 */,
contextIsolation: true
}
})

win.loadURL(/* 你的前端入口地址 */)
}

app.whenReady().then(() => {
// 注册与渲染进程约定好的 IPC,内部使用 electron-updater 完成整包更新
registerRydeenElectronUpdaterIpc({
autoCheckDefault: true,
intervalHoursDefault: 24,
async onAssetsPatchApply(meta) {
// 可选:这里实现“方案 B”文件级补丁逻辑
// - 根据 meta.assetsVersion / meta.checksum 下载补丁到 app.getPath('userData')
// - 使用 protocol.interceptFileProtocol 覆盖静态资源
}
})

createWindow()

app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) {
createWindow()
}
})
})

app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit()
}
})
  • registerRydeenElectronUpdaterIpc 会在主进程中注册一组 rydeen-updater/* IPC 通道,
    与渲染进程的 ElectronUpdateService 一一对应,并通过 electron-updater 完成整包更新。
  • onAssetsPatchApply 回调预留给你实现文件级补丁(方案 B),
    例如下载补丁 zip 至 app.getPath('userData'),并用 protocol.interceptFileProtocol 将补丁中的静态资源“覆盖”内置 asar 资源。

资源补丁包制作与清单规范​

这一节主要回答两个问题:

  • 热修复补丁包(资源 ZIP)应该 怎么打?
  • 服务器端应该 怎么保存版本与文件信息,供客户端拉取?

Android / iOS:Capgo 资源补丁包制作思路​

移动端由 @capgo/capacitor-updater 接管实际的资源加载逻辑,本插件只负责 下载 + 调用 Capgo。典型流程如下:

  • 1. 本地构建前端资源

    • 使用你的前端构建命令(如 npm run build),生成 dist 目录。
    • 确保结构与 Capacitor 工程的 www 目录一致(index.html、assets/... 等),即「打包后的 H5 资源」。
  • 2. 生成 Capgo 兼容的 ZIP 包

    • 将构建产物打包为一个 ZIP:
      • ZIP 根目录下直接是 index.html、assets/ 等文件(不要再多加一层文件夹)。
      • 命名示例:app-frontend-20240701.zip。
    • 将 ZIP 上传到你的对象存储(OSS、COS、S3 等),得到一个 可公开访问或后端代理访问的 URL。
  • 3. 在版本中心登记资源版本

    • 数据库中维护一张「客户端资源版本表」,至少包含:
      • assetsVersion(如 20240701);
      • checksum(ZIP 的 HASH,用于校验完整性);
      • filename(可选,如 app-frontend-20240701.zip);
      • sign(可选,加密后的下载地址字符串,方便隐藏真实 URL);
      • 是否有 hasAppUpdate / hasAssetsUpdate 标记。
    • 当客户端调用 checkDomain 接口时,后端根据当前应用 ID / build 号 / 现有资源版本,返回一条 VersionMetadata 结构的数据(README 前文已有字段说明)。
  • 4. 客户端如何使用

    • 本插件在移动端的下载逻辑是:
      • 若 VersionMetadata.sign 存在:解码得到真实 URL,传给 CapacitorUpdater.download({ url, version });
      • 否则:用 downloadDomain + '/' + filename 拼出 URL 再下载。
    • Capgo 解压并管理资源包,不需要你在客户端额外解压或手工覆盖文件。

简单理解:移动端补丁包其实就是「一份新的 H5 资源 ZIP」,Capgo 负责将它挂载成当前 WebView 的资源根目录,本插件只负责下载和切换版本。

Electron:方案 B 资源 ZIP 补丁包制作​

Electron 端的「方案 B」是 只更新前端静态资源,而不重新安装整个 Electron 应用。
在本插件的默认实现中,补丁包的约定如下:

  • 1. 补丁资源的载入方式

    • 主进程会在 app.getPath('userData')/rydeen_updater/assets 下解压一个 ZIP 补丁包;
    • 注册一个自定义协议(默认 ryassets://),将该协议映射到上述 assets 目录;
    • 你的前端代码中,只要把需要支持热修复的静态资源改为用 ryassets:// 引用,例如:
      • 原来:<img src="assets/logo.png">
      • 现在:<img src="ryassets://assets/logo.png">
    • 补丁 ZIP 内只需要包含「有变更的文件」,并保持与协议路径一致的相对路径:
      • 若你希望覆盖 ryassets://assets/logo.png,则 ZIP 中应包含 assets/logo.png。
  • 2. 补丁 ZIP 的内容与命名

    • 目录结构示例:

      (ZIP root)
      ├─ assets/
      │ ├─ logo.png
      │ └─ main.css
      └─ js/
      └─ chunk-12345.js
    • 命名建议:electron-assets-<assetsVersion>.zip,例如:electron-assets-20240701.zip。

    • 上传到对象存储后,获得一个基础路径,如:https://cdn.example.com/patch/electron/。

  • 3. 版本与下载地址如何配置

    • 在 Electron 场景下,本插件会这样计算补丁下载 URL:

      • 若 VersionMetadata.sign 存在:直接视为完整 URL 使用(可由后端写入已经加密的真实地址);
      • 否则:使用 assetsPatchBaseUrl + '/' + (filename || (assetsVersion + '.zip')) 生成地址。
    • 也就是说,你需要在后端版本表中至少维护:

      • assetsVersion;
      • checksum(可用于服务端自行校验);
      • 可选 filename(如 electron-assets-20240701.zip);
      • 可选 sign(加密 URL)。
    • 同时,在 Electron 主进程调用 registerRydeenElectronUpdaterIpc 时提供:

      registerRydeenElectronUpdaterIpc({
      assetsPatchBaseUrl: 'https://cdn.example.com/patch/electron',
      assetsPatchProtocol: 'ryassets', // 可省略,默认即为 ryassets
      // 如需完全自定义补丁逻辑,也可以传 onAssetsPatchApply 覆盖默认实现
      })
  • 4. 主进程默认补丁逻辑回顾

    • 符合上述约定时,本插件会在 Electron 主进程自动执行以下步骤:
      1. 根据版本信息计算 ZIP 下载地址;
      2. 下载到 userData/rydeen_updater/patch.zip;
      3. 清空并重新创建 userData/rydeen_updater/assets 目录;
      4. 用 extract-zip 将 ZIP 解压到 assets 目录;
      5. 注册 assetsPatchProtocol(默认 ryassets)的 interceptFileProtocol,将协议路径映射到上述目录。

这样,前端只要约定好用 ryassets:// 访问需要热修复的资源文件,就可以在不重装 Electron 应用的情况下,通过下发 ZIP 补丁来动态覆盖这些资源。

若你需要更复杂的清单文件(例如记录每个补丁中包含哪些文件、回滚策略等),推荐在 服务端版本中心 中维护一份 JSON 清单(例如 manifest 字段),供管理平台使用。
当前插件的默认实现并不会在客户端解析这类清单,只依赖 VersionMetadata + ZIP 内容来完成补丁应用。

Electron:方案 A 整包更新(桌面应用新版本)​

对于 Electron,「方案 A」指的是 通过安装包整体升级应用(例如从 1.0.0 升级到 1.1.0),本插件在主进程中使用 electron-updater 实现:

  • 1. 打包与发布(electron-updater 标准流程)

    • 使用 electron-builder 或类似工具为桌面应用生成安装包(如 *.exe / *.dmg / *.AppImage)以及 latest.yml / app-update.yml 等元数据文件;
    • 在 electron-builder 配置中设置 publish 选项,指向你的更新服务器(generic HTTP、S3、OSS 等),确保 autoUpdater.checkForUpdates() 能从该地址获取到新版本信息;
    • 将安装包与对应的更新描述文件(如 latest.yml)一并上传到更新服务器,具体格式请参考 electron-updater 官方文档。
  • 2. 版本信息与 VersionMetadata.hasAppUpdate

    • electron-updater 自身会根据远程 latest.yml 判断是否有新版本;
    • 本插件在主进程的 checkVersion 中调用 autoUpdater.checkForUpdates(),并将结果转换为 VersionMetadata:
      • hasAppUpdate:如果存在新安装包则为 true;
      • newVersion:桌面应用的新版本号,例如 1.1.0。
    • 你可以在服务端的版本中心中也维护一份桌面版本信息(可选),用于前端展示和与移动端资源版本联动,但整包更新本身由 electron-updater 决定。
  • 3. 业务触发整包更新的典型流程

    • 在前端(Angular)侧,通过 AppUpdateService 或类似业务服务发起检查:

      const meta = await this.updater.checkVersion(false)
      if (meta.hasAppUpdate) {
      // 弹出对话框,提示有新的桌面版本,例如 meta.newVersion
      const ok = await this.confirm({
      message: `发现新的桌面版本:${meta.newVersion},是否立即下载并更新?`
      })
      if (ok) {
      const plan = await this.updater.download(meta)
      // 对于 Electron,download() 内部会调用 autoUpdater.downloadUpdate()
      // upgrade() 会调用 autoUpdater.quitAndInstall(),退出当前应用并启动安装程序
      await this.updater.upgrade(plan, true)
      }
      }
    • 需要注意:

      • 一旦调用 upgrade(plan, true),electron-updater 会关闭当前应用并运行安装程序,适合在用户确认后执行;
      • 若你希望「下载先完成,稍后再提醒用户安装」,可以在业务层自行拆分「只调用 download()」和「在合适时机再调用 upgrade()」,并配合自定义提示逻辑。

总结:

  • 方案 A(整包更新):由 electron-updater 根据发布的安装包与更新描述文件完成,插件负责触发 checkForUpdates / downloadUpdate / quitAndInstall;
  • 方案 B(资源补丁):由本插件默认或自定义的 ZIP 补丁逻辑完成,只覆盖前端静态资源,不重新安装应用本体。

平台差异与注意事项​

  • Android / iOS

    • 必须先正确集成 Capgo(@capgo/capacitor-updater)和 @capacitor-rydeen/preferences。
    • hasAppUpdate 仅作为业务提示标志,不会自动跳转商店或安装 APK。
    • hasAssetsUpdate 为 true 时才会触发资源下载与升级逻辑。
  • Electron

    • ElectronUpdateService 通过 IPC 调用主进程的 registerRydeenElectronUpdaterIpc 所注册的通道,主进程内部基于 electron-updater 完成整包更新:
      • checkVersion():调用 autoUpdater.checkForUpdates(),并返回对应的 VersionMetadata;
      • download():调用 autoUpdater.downloadUpdate(),并在有资源更新时可触发 ZIP 补丁下载与解压;
      • upgrade():调用 autoUpdater.quitAndInstall(),退出当前应用并运行安装程序。
    • 推荐做法:
      • 利用 VersionMetadata.hasAppUpdate 决定是否启动整包升级(结合 electron-updater);
      • 利用 hasAssetsUpdate / assetsVersion 触发文件级补丁下载到 app.getPath('userData') 并在主进程挂载协议覆盖资源。
  • Web / 其他平台

    • 使用 NoopUpdateService,所有操作为“空实现”,仅返回 originVersion,用于本地开发占位。

服务端接口与协议建议​

  • checkDomain 返回结构建议

    • 至少包含:
      • hasAppUpdate: boolean
      • hasAssetsUpdate: boolean
      • assetsVersion: string
      • checksum: string
    • 如需由服务器控制下载地址:
      • 推荐返回 sign(加密 URL),由客户端解码;
      • 或返回 filename,由客户端和 downloadDomain 拼接。
  • 版本策略

    • 对于 dynamic 模式,建议按客户端上传的 packageName、versionCode、assetsVersion 做灰度 / 分渠道策略。

使用注意事项(总结)​

  • Capgo 前置:移动端必须先集成并配置 Capgo,否则下载 / 安装会失败。
  • Preferences 依赖:本地版本信息依赖 @capacitor-rydeen/preferences,需要在 Native 层正确注册。
  • 静默更新策略:慎用 checkVersion(true),建议在良好网络 + 后台场景使用;前台交互推荐 checkVersion(false) 配合弹窗。
  • 标志位协同:后端在下发版本信息时,请确保 hasAppUpdate、hasAssetsUpdate、assetsVersion、checksum 等字段与实际资源一一对应,避免客户端误判。