electron-updater-for-render 1.1.2-beta.5 → 1.1.2-beta.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.zh-CN.md +206 -62
- package/Readme.md +198 -50
- package/dist/main/index.cjs +256 -182
- package/dist/main/index.d.ts +17 -17
- package/dist/main/index.js +244 -170
- package/dist/main/router-handler.d.ts +1 -2
- package/dist/types.d.ts +8 -0
- package/package.json +1 -1
package/README.zh-CN.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# electron-updater-for-render
|
|
2
2
|
|
|
3
|
-
[English](./
|
|
3
|
+
[English](./Readme.md)
|
|
4
4
|
|
|
5
5
|
专为 Electron 渲染进程设计的工业级混合式增量热更新框架(支持 Vue / React / 纯 HTML)。
|
|
6
6
|
|
|
@@ -11,13 +11,17 @@
|
|
|
11
11
|
## 🚀 核心特性
|
|
12
12
|
|
|
13
13
|
- **双轨更新模式**:内置原生弹窗自动更新 + 暴露 IPC 接口支持前端自定义进度 UI,两种方案自由切换
|
|
14
|
+
- **三态检查系统**:`check()` 返回 `idle / available / ready` 三种状态,UI 始终知道应该显示"下载"还是"重启",彻底消除重复下载弹窗
|
|
15
|
+
- **就绪状态持久化**:用户点击"稍后安装"后,下载的更新会记录到磁盘。下次启动或刷新页面,UI 直接跳到"立即重启"——无需重新下载
|
|
16
|
+
- **实时状态推送**:`onStatusChanged` 钩子让主进程在更新就绪时(如静默后台下载完成)主动通知渲染层,UI 即时刷新无需用户手动操作
|
|
14
17
|
- **强制更新控制**:云端下发 `forceUpdate: 'prompt'` 或 `'silent'`,应对 P0 级线上事故时一键锁死用户退路
|
|
15
18
|
- **从容退场钩子**:`onBeforeRestart` 让渲染进程在 `app.relaunch()` 之前有充足时间保存草稿、提交日志
|
|
16
19
|
- **并发互斥锁**:内置 `isDownloading` 单例锁,防止手动触发与开机自检同时争写同一 ASAR 文件
|
|
17
|
-
- **智能冷库清道夫**:`maxVersionsToKeep`
|
|
20
|
+
- **智能冷库清道夫**:`maxVersionsToKeep` 自动淘汰最旧历史版本,保留可回滚备份,告别磁盘无限膨胀
|
|
18
21
|
- **Pipeline 流式下载**:Node.js `stream/promises pipeline` + `Transform` 管道,内存零泄漏,网络背压自动控制
|
|
19
22
|
- **零配置版本探测**:默认自动读取 `process.cwd()/package.json` 中的版本号,无需任何额外配置
|
|
20
23
|
- **独立打包命令**:普通 `npm run build` 不受任何影响,只有 `npm run build:update` 才会触发 ASAR 打包
|
|
24
|
+
- **History 模式 SPA 支持**:内置自定义协议处理器(`app://`),支持 Vue/React History 路由模式,零配置零样板代码
|
|
21
25
|
|
|
22
26
|
---
|
|
23
27
|
|
|
@@ -27,7 +31,7 @@
|
|
|
27
31
|
npm install electron-updater-for-render
|
|
28
32
|
|
|
29
33
|
# 打包阶段依赖(在渲染层项目中安装)
|
|
30
|
-
npm install -D asar
|
|
34
|
+
npm install -D @electron/asar
|
|
31
35
|
```
|
|
32
36
|
|
|
33
37
|
---
|
|
@@ -38,7 +42,7 @@ npm install -D asar
|
|
|
38
42
|
┌─────────────────────────────────────────────────────┐
|
|
39
43
|
│ 渲染层项目(如 Vue 工程) │
|
|
40
44
|
│ npm run build:update │
|
|
41
|
-
│ → 原生 build
|
|
45
|
+
│ → 原生 build → CLI 打包 ASAR → 生成 latest.json │
|
|
42
46
|
└───────────────┬─────────────────────────────────────┘
|
|
43
47
|
│ 上传 dist_updates/ 到服务器
|
|
44
48
|
▼
|
|
@@ -50,18 +54,30 @@ npm install -D asar
|
|
|
50
54
|
▼
|
|
51
55
|
┌─────────────────────────────────────────────────────┐
|
|
52
56
|
│ Electron 主进程 │
|
|
53
|
-
│ updater.
|
|
54
|
-
│ →
|
|
57
|
+
│ updater.check() → 'idle' | 'available' | 'ready' │
|
|
58
|
+
│ → 下载 ASAR → setUpdatePending() │
|
|
59
|
+
│ → onStatusChanged 推送 → 渲染层 UI 即时更新 │
|
|
60
|
+
│ → 重启时:应用更新 → app.relaunch() │
|
|
55
61
|
└─────────────────────────────────────────────────────┘
|
|
56
62
|
```
|
|
57
63
|
|
|
64
|
+
### 三态更新生命周期
|
|
65
|
+
|
|
66
|
+
| 状态 | 含义 | UI 行为 |
|
|
67
|
+
|---|---|---|
|
|
68
|
+
| `idle` | 未找到更新 | 显示"检查更新"按钮 |
|
|
69
|
+
| `available` | 发现新版本,尚未下载 | 显示"立即下载"按钮 |
|
|
70
|
+
| `ready` | 更新已下载,等待重启 | 显示"立即重启安装"按钮 |
|
|
71
|
+
|
|
72
|
+
`ready` 状态会**持久化到磁盘**(`current.json`)。即使用户刷新页面或重新打开应用,UI 也能正确显示"立即重启",而不会再次要求下载。
|
|
73
|
+
|
|
58
74
|
---
|
|
59
75
|
|
|
60
76
|
## 🛠️ 完整接入指南
|
|
61
77
|
|
|
62
78
|
### 第一步 — 渲染工程:配置并声明生成规则
|
|
63
79
|
|
|
64
|
-
在您的任何前端项目根目录下(无论 Vite
|
|
80
|
+
在您的任何前端项目根目录下(无论 Vite、Webpack,亦或普通网页)新建 `updater.config.ts`:
|
|
65
81
|
|
|
66
82
|
```typescript
|
|
67
83
|
import { defineConfig } from 'electron-updater-for-render/builder'
|
|
@@ -123,8 +139,8 @@ rsync -avz dist_updates/ user@your-server:/var/www/auto-updates/
|
|
|
123
139
|
```
|
|
124
140
|
|
|
125
141
|
客户端会依次请求:
|
|
126
|
-
- `GET https://your-server.com/latest.json
|
|
127
|
-
- `GET https://your-server.com/1.0.2/renderer.asar
|
|
142
|
+
- `GET https://your-server.com/latest.json`(获取版本清单)
|
|
143
|
+
- `GET https://your-server.com/1.0.2/renderer.asar`(下载更新包)
|
|
128
144
|
|
|
129
145
|
---
|
|
130
146
|
|
|
@@ -148,6 +164,15 @@ const updater = new RenderUpdater({
|
|
|
148
164
|
win.webContents.send('updater:before-restart')
|
|
149
165
|
await new Promise(resolve => setTimeout(resolve, 2000))
|
|
150
166
|
}
|
|
167
|
+
},
|
|
168
|
+
|
|
169
|
+
// 可选:当更新状态发生变化时(如后台下载完成),主动推送给渲染层
|
|
170
|
+
// 渲染层无需手动检查,按钮会自动切换到"立即重启"
|
|
171
|
+
onStatusChanged: (data) => {
|
|
172
|
+
const [win] = BrowserWindow.getAllWindows()
|
|
173
|
+
if (win) {
|
|
174
|
+
win.webContents.send('updater:status-changed', data)
|
|
175
|
+
}
|
|
151
176
|
}
|
|
152
177
|
})
|
|
153
178
|
|
|
@@ -166,12 +191,16 @@ app.whenReady().then(async () => {
|
|
|
166
191
|
|
|
167
192
|
// 暴露 IPC 接口,供前端界面主动控制更新流程
|
|
168
193
|
ipcMain.handle('updater:check', () => updater.check())
|
|
169
|
-
|
|
170
|
-
|
|
194
|
+
|
|
195
|
+
// ⚠️ 重要:第一个参数必须是前端传来的 UpdateInfo 对象,第二个才是进度回调
|
|
196
|
+
// 这样可以避免主进程重复请求 latest.json,也杜绝了参数错位导致的崩溃
|
|
197
|
+
ipcMain.handle('updater:download', async (event, info) =>
|
|
198
|
+
updater.download(info, (percent) => {
|
|
171
199
|
BrowserWindow.getAllWindows()[0]?.webContents.send('updater:progress', percent)
|
|
172
200
|
})
|
|
173
201
|
)
|
|
174
|
-
|
|
202
|
+
|
|
203
|
+
ipcMain.on('updater:install', () => {
|
|
175
204
|
app.relaunch()
|
|
176
205
|
app.quit()
|
|
177
206
|
})
|
|
@@ -187,8 +216,12 @@ import { contextBridge, ipcRenderer } from 'electron'
|
|
|
187
216
|
|
|
188
217
|
contextBridge.exposeInMainWorld('updater', {
|
|
189
218
|
check: () => ipcRenderer.invoke('updater:check'),
|
|
190
|
-
|
|
191
|
-
|
|
219
|
+
|
|
220
|
+
// 将 check() 返回的 UpdateInfo 对象透传给主进程
|
|
221
|
+
// 主进程凭此对象直接下载,无需重新请求 latest.json
|
|
222
|
+
download: (info?: any) => ipcRenderer.invoke('updater:download', info),
|
|
223
|
+
|
|
224
|
+
install: () => ipcRenderer.send('updater:install'),
|
|
192
225
|
|
|
193
226
|
onProgress: (callback: (percent: number) => void) => {
|
|
194
227
|
const fn = (_: any, p: number) => callback(p)
|
|
@@ -200,75 +233,170 @@ contextBridge.exposeInMainWorld('updater', {
|
|
|
200
233
|
const fn = () => callback()
|
|
201
234
|
ipcRenderer.on('updater:before-restart', fn)
|
|
202
235
|
return () => ipcRenderer.removeListener('updater:before-restart', fn)
|
|
236
|
+
},
|
|
237
|
+
|
|
238
|
+
// 监听主进程的主动状态推送
|
|
239
|
+
// 当后台下载完成(如用户点"稍后安装"之后)时,UI 会自动更新到"立即重启"
|
|
240
|
+
onStatusChanged: (callback: (data: { status: string; version: string }) => void) => {
|
|
241
|
+
const fn = (_: any, data: any) => callback(data)
|
|
242
|
+
ipcRenderer.on('updater:status-changed', fn)
|
|
243
|
+
return () => ipcRenderer.removeListener('updater:status-changed', fn)
|
|
203
244
|
}
|
|
204
245
|
})
|
|
205
246
|
```
|
|
206
247
|
|
|
207
248
|
---
|
|
208
249
|
|
|
209
|
-
### 第六步 — 渲染层 UI(Vue
|
|
250
|
+
### 第六步 — 渲染层 UI(Vue 完整示例)
|
|
251
|
+
|
|
252
|
+
以下示例展示了完整的三态更新流程,包含:
|
|
253
|
+
- **启动自动检测**——无需用户手动点击即可感知更新
|
|
254
|
+
- **三态精准识别**——根据 `check()` 返回的 `status` 正确显示"下载"或"重启"
|
|
255
|
+
- **实时状态同步**——后台下载完成后,主进程推送通知,UI 立即刷新
|
|
210
256
|
|
|
211
257
|
```vue
|
|
212
258
|
<script setup lang="ts">
|
|
213
259
|
import { ref, onMounted, onUnmounted } from 'vue'
|
|
214
260
|
|
|
215
|
-
|
|
261
|
+
// 状态:'idle' | 'checking' | 'available' | 'downloading' | 'ready' | 'no-update'
|
|
262
|
+
const status = ref<'idle' | 'checking' | 'available' | 'downloading' | 'ready' | 'no-update'>('idle')
|
|
216
263
|
const progress = ref(0)
|
|
217
|
-
const
|
|
264
|
+
const newVersion = ref('')
|
|
265
|
+
const updateInfo = ref<any>(null) // 存储完整的 UpdateInfo,供 download() 使用
|
|
266
|
+
const errMsg = ref('')
|
|
218
267
|
|
|
219
268
|
let removeProgress: (() => void) | null = null
|
|
220
269
|
let removeRestart: (() => void) | null = null
|
|
270
|
+
let removeStatusChanged: (() => void) | null = null
|
|
221
271
|
|
|
222
|
-
|
|
223
|
-
// 监听重启通知,提前保存状态
|
|
224
|
-
removeRestart = window.updater.onBeforeRestart(() => {
|
|
225
|
-
status.value = '即将重启,保存状态中...'
|
|
226
|
-
localStorage.setItem('draft', JSON.stringify({ /* 你的状态 */ }))
|
|
227
|
-
})
|
|
228
|
-
})
|
|
229
|
-
|
|
230
|
-
onUnmounted(() => {
|
|
231
|
-
removeProgress?.()
|
|
232
|
-
removeRestart?.()
|
|
233
|
-
})
|
|
272
|
+
const getUpdater = () => (window as any).updater
|
|
234
273
|
|
|
235
|
-
//
|
|
274
|
+
// ── 检查更新 ───────────────────────────────────────────────────────────────
|
|
275
|
+
// check() 返回 { status: 'idle' | 'available' | 'ready', version, info }
|
|
276
|
+
// 若状态为 'ready',说明更新已下载完毕只待重启,直接跳过下载流程
|
|
236
277
|
const checkUpdate = async () => {
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
278
|
+
const updater = getUpdater()
|
|
279
|
+
if (!updater) return
|
|
280
|
+
|
|
281
|
+
status.value = 'checking'
|
|
282
|
+
try {
|
|
283
|
+
const res = await updater.check()
|
|
284
|
+
if (res.updateAvailable) {
|
|
285
|
+
newVersion.value = res.version
|
|
286
|
+
updateInfo.value = res.info
|
|
287
|
+
// 精确识别:'ready' 直接进入重启状态,不再显示下载按钮
|
|
288
|
+
status.value = res.status === 'ready' ? 'ready' : 'available'
|
|
289
|
+
} else {
|
|
290
|
+
status.value = 'no-update'
|
|
291
|
+
}
|
|
292
|
+
} catch (err: any) {
|
|
293
|
+
errMsg.value = '检查失败: ' + err.message
|
|
294
|
+
status.value = 'idle'
|
|
244
295
|
}
|
|
245
296
|
}
|
|
246
297
|
|
|
247
|
-
//
|
|
298
|
+
// ── 下载更新 ───────────────────────────────────────────────────────────────
|
|
299
|
+
// 将 check() 返回的 info 对象传给 download(),避免主进程重复请求 latest.json
|
|
248
300
|
const downloadUpdate = async () => {
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
301
|
+
const updater = getUpdater()
|
|
302
|
+
if (!updater) return
|
|
303
|
+
|
|
304
|
+
status.value = 'downloading'
|
|
305
|
+
progress.value = 0
|
|
306
|
+
|
|
307
|
+
removeProgress = updater.onProgress((percent: number) => {
|
|
308
|
+
progress.value = percent
|
|
253
309
|
})
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
310
|
+
|
|
311
|
+
try {
|
|
312
|
+
await updater.download(updateInfo.value)
|
|
313
|
+
status.value = 'ready'
|
|
314
|
+
} catch (err: any) {
|
|
315
|
+
errMsg.value = '下载失败: ' + err.message
|
|
316
|
+
status.value = 'available'
|
|
317
|
+
} finally {
|
|
318
|
+
removeProgress?.()
|
|
319
|
+
}
|
|
257
320
|
}
|
|
258
321
|
|
|
259
|
-
// 安装并重启
|
|
322
|
+
// ── 安装并重启 ─────────────────────────────────────────────────────────────
|
|
260
323
|
const installUpdate = () => {
|
|
261
|
-
|
|
324
|
+
getUpdater()?.install()
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// ── 监听主进程主动推送的状态变更 ───────────────────────────────────────────
|
|
328
|
+
// 场景:用户点击"稍后安装"后,日后主进程 onStatusChanged 触发时
|
|
329
|
+
// UI 无需用户做任何操作,按钮自动变为"立即重启"
|
|
330
|
+
const initStatusListener = () => {
|
|
331
|
+
const updater = getUpdater()
|
|
332
|
+
if (updater?.onStatusChanged) {
|
|
333
|
+
return updater.onStatusChanged((data: { status: string; version: string }) => {
|
|
334
|
+
if (data.status === 'ready') {
|
|
335
|
+
status.value = 'ready'
|
|
336
|
+
newVersion.value = data.version
|
|
337
|
+
}
|
|
338
|
+
})
|
|
339
|
+
}
|
|
340
|
+
return null
|
|
262
341
|
}
|
|
342
|
+
|
|
343
|
+
// ── 监听重启前通知 ─────────────────────────────────────────────────────────
|
|
344
|
+
const initRestartListener = () => {
|
|
345
|
+
const updater = getUpdater()
|
|
346
|
+
if (updater?.onBeforeRestart) {
|
|
347
|
+
return updater.onBeforeRestart(() => {
|
|
348
|
+
// 在此保存未提交的表单状态
|
|
349
|
+
console.log('应用即将重启,请保存状态...')
|
|
350
|
+
})
|
|
351
|
+
}
|
|
352
|
+
return null
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// ── 生命周期 ───────────────────────────────────────────────────────────────
|
|
356
|
+
onMounted(() => {
|
|
357
|
+
removeStatusChanged = initStatusListener()
|
|
358
|
+
removeRestart = initRestartListener()
|
|
359
|
+
|
|
360
|
+
// 启动后 2 秒自动检查,不阻塞首屏渲染
|
|
361
|
+
setTimeout(checkUpdate, 2000)
|
|
362
|
+
})
|
|
363
|
+
|
|
364
|
+
onUnmounted(() => {
|
|
365
|
+
removeProgress?.()
|
|
366
|
+
removeRestart?.()
|
|
367
|
+
removeStatusChanged?.()
|
|
368
|
+
})
|
|
263
369
|
</script>
|
|
264
370
|
|
|
265
371
|
<template>
|
|
266
|
-
<div>
|
|
267
|
-
<
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
<button
|
|
271
|
-
|
|
372
|
+
<div class="updater">
|
|
373
|
+
<div v-if="errMsg" class="error">{{ errMsg }}</div>
|
|
374
|
+
|
|
375
|
+
<!-- 无活跃更新任务 -->
|
|
376
|
+
<button v-if="status === 'idle' || status === 'no-update'" @click="checkUpdate">
|
|
377
|
+
{{ status === 'no-update' ? '当前已是最新,重新检查' : '主动检查新版本' }}
|
|
378
|
+
</button>
|
|
379
|
+
|
|
380
|
+
<!-- 检查中 -->
|
|
381
|
+
<span v-if="status === 'checking'">正在拉取更新配置...</span>
|
|
382
|
+
|
|
383
|
+
<!-- 发现新版本,尚未下载 -->
|
|
384
|
+
<div v-if="status === 'available'">
|
|
385
|
+
<p>🔥 发现新版本: v{{ newVersion }}</p>
|
|
386
|
+
<button @click="downloadUpdate">立即下载并缓存</button>
|
|
387
|
+
</div>
|
|
388
|
+
|
|
389
|
+
<!-- 下载中 -->
|
|
390
|
+
<div v-if="status === 'downloading'">
|
|
391
|
+
<p>正在下载... {{ progress.toFixed(1) }}%</p>
|
|
392
|
+
<progress :value="progress" max="100" />
|
|
393
|
+
</div>
|
|
394
|
+
|
|
395
|
+
<!-- 更新已就绪(刷新页面后此状态依然保持)-->
|
|
396
|
+
<div v-if="status === 'ready'">
|
|
397
|
+
<p>✅ v{{ newVersion }} 已就绪</p>
|
|
398
|
+
<button @click="installUpdate">立即重启并安装</button>
|
|
399
|
+
</div>
|
|
272
400
|
</div>
|
|
273
401
|
</template>
|
|
274
402
|
```
|
|
@@ -287,7 +415,7 @@ const installUpdate = () => {
|
|
|
287
415
|
- **前端 (Router)**:使用 `createWebHashHistory()`。
|
|
288
416
|
|
|
289
417
|
### 2. History 模式(企业级方案)
|
|
290
|
-
支持美观的 URL 和标准浏览器重载行为。由于 `file://`
|
|
418
|
+
支持美观的 URL 和标准浏览器重载行为。由于 `file://` 不支持标准路由解析,本库会自动启用自定义协议(默认 `app://`)进行转发。
|
|
291
419
|
|
|
292
420
|
- **主进程**:
|
|
293
421
|
```typescript
|
|
@@ -298,9 +426,9 @@ const installUpdate = () => {
|
|
|
298
426
|
protocol: 'my-app' // 可选:自定义协议名 (默认 'app')
|
|
299
427
|
})
|
|
300
428
|
```
|
|
301
|
-
> 💡
|
|
429
|
+
> 💡 **全自动化**:协议注册和特权赋权逻辑已在库内部**全自动处理**,您无需编写任何 `registerSchemesAsPrivileged` 等繁琐代码。
|
|
302
430
|
|
|
303
|
-
-
|
|
431
|
+
- **前端构建工具(Vite、Webpack 等)**:**关键配置** —— 必须将 **公共路径 (Public Path / Base)** 设置为 `/`,确保资源引用为绝对路径,防止在深层路由下刷新出现 404。
|
|
304
432
|
- **Vite**:`base: '/'`
|
|
305
433
|
- **Webpack**:`output.publicPath: '/'`
|
|
306
434
|
- **前端路由 (Router)**:使用 `createWebHistory()`。
|
|
@@ -313,7 +441,7 @@ const installUpdate = () => {
|
|
|
313
441
|
|
|
314
442
|
| 参数 | 类型 | 必填 | 说明 |
|
|
315
443
|
|---|---|---|---|
|
|
316
|
-
| `outDir` | `string` | ✅ |
|
|
444
|
+
| `outDir` | `string` | ✅ | 构建输出目录 |
|
|
317
445
|
| `updatesDir` | `string` | — | 更新包输出目录,默认 `./dist_updates` |
|
|
318
446
|
| `version` | `string` | — | 显式指定版本号,优先级高于 `packageJsonPath` |
|
|
319
447
|
| `packageJsonPath` | `string` | — | 自定义 `package.json` 路径,不填则自动读取 `process.cwd()/package.json` |
|
|
@@ -331,14 +459,30 @@ const installUpdate = () => {
|
|
|
331
459
|
| `publicKey` | `string` | — | RSA 公钥(PEM 格式),用于验证 ASAR 签名 |
|
|
332
460
|
| `autoDownload` | `boolean` | — | 发现更新后自动下载,不弹窗询问。默认 `false` |
|
|
333
461
|
| `autoPrompt` | `boolean` | — | 使用内置原生弹窗引导更新。默认 `true` |
|
|
334
|
-
| `maxVersionsToKeep` | `number` | — |
|
|
462
|
+
| `maxVersionsToKeep` | `number` | — | 本地保留的旧版本数量,默认 `2` |
|
|
463
|
+
| `routerMode` | `'hash' \| 'history'` | — | 路由模式选择。默认 `'hash'` |
|
|
464
|
+
| `protocol` | `string` | — | History 模式下的自定义协议名。默认 `'app'` |
|
|
335
465
|
| `onUpdateAvailable` | `function` | — | 检测到更新时的自定义钩子:`(info, doDownload) => void` |
|
|
336
466
|
| `onDownloadProgress` | `function` | — | 下载进度回调:`(percent: number) => void` |
|
|
337
467
|
| `onDownloadComplete` | `function` | — | 下载完成钩子:`(info, doInstall) => void` |
|
|
468
|
+
| `onStatusChanged` | `function` | — | 状态变更推送钩子:`(data: { status: 'ready' \| 'available' \| 'idle', version: string }) => void` |
|
|
338
469
|
| `onError` | `function` | — | 错误回调:`(error: Error) => void` |
|
|
339
470
|
| `onBeforeRestart` | `async function` | — | `app.relaunch()` 前的异步钩子,可用于保存状态 |
|
|
340
|
-
|
|
341
|
-
|
|
471
|
+
|
|
472
|
+
### `RenderUpdater` 实例方法
|
|
473
|
+
|
|
474
|
+
| 方法 | 返回值 | 说明 |
|
|
475
|
+
|---|---|---|
|
|
476
|
+
| `check()` | `Promise<CheckResult>` | 检查更新。返回 `{ updateAvailable, status, version, info }`,其中 `status` 为 `'idle' \| 'available' \| 'ready'` |
|
|
477
|
+
| `download(info?, onProgress?)` | `Promise<void>` | 下载更新。将 `check()` 返回的 `info` 传入,可避免重复请求 latest.json |
|
|
478
|
+
| `getLoadUrl()` | `string` | 返回最新 ASAR 的加载地址(`app://renderer/` 或 `file://...`),未安装任何更新时返回空字符串 |
|
|
479
|
+
| `installAndRestart()` | `Promise<void>` | 应用待安装更新并重启应用 |
|
|
480
|
+
| `checkForUpdatesAndNotify()` | `Promise<void>` | 一键式:检查 + 弹窗 + 下载 + 安装,使用内置原生弹窗 |
|
|
481
|
+
| `setUpdatePending(version)` | `void` | 将某版本标记为已下载待重启,同时触发 `onStatusChanged` |
|
|
482
|
+
| `useVersion(version)` | `void` | 立即切换磁盘和内存双重版本指针 |
|
|
483
|
+
| `activeVersion` | `string`(getter) | 当前内存中正在运行的版本(本次会话) |
|
|
484
|
+
| `pendingVersion` | `string`(getter) | 磁盘上最新已下载版本 |
|
|
485
|
+
| `isUpdatePending` | `boolean`(getter) | 若 `pendingVersion > activeVersion` 则为 `true` |
|
|
342
486
|
|
|
343
487
|
---
|
|
344
488
|
|
|
@@ -347,7 +491,7 @@ const installUpdate = () => {
|
|
|
347
491
|
当线上出现资损、安全漏洞、核心功能崩溃等 P0 级故障时,通过 `forceUpdate` 剥夺用户的拒绝权:
|
|
348
492
|
|
|
349
493
|
```typescript
|
|
350
|
-
// updater.config.ts
|
|
494
|
+
// updater.config.ts
|
|
351
495
|
export default defineConfig({
|
|
352
496
|
outDir: './dist',
|
|
353
497
|
forceUpdate: 'prompt' // 或 'silent'
|
|
@@ -356,7 +500,7 @@ export default defineConfig({
|
|
|
356
500
|
|
|
357
501
|
| 模式 | 行为 |
|
|
358
502
|
|---|---|
|
|
359
|
-
| `'prompt'` |
|
|
503
|
+
| `'prompt'` | 弹出系统级警告弹窗,**只有一个确认按钮**,用户无法取消或跳过,点击后立即开始下载并重启 |
|
|
360
504
|
| `'silent'` | 完全静默:后台自动下载、自动安装、自动重启,用户毫无感知 |
|
|
361
505
|
|
|
362
506
|
> ⚠️ 本功能会**永久剥夺用户本次的延迟权**,请仅在真正的 P0 级生产事故中使用。
|