electron-updater-for-render 1.1.2-beta.3 → 1.1.2-beta.5
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 +34 -0
- package/Readme.md +34 -0
- package/dist/bin/cli.cjs +2 -2
- package/dist/bin/cli.js +2 -2
- package/dist/builder/index.cjs +2 -2
- package/dist/builder/index.js +2 -2
- package/dist/main/index.cjs +161 -50
- package/dist/main/index.d.ts +5 -3
- package/dist/main/index.js +157 -46
- package/dist/main/router-handler.d.ts +20 -0
- package/dist/types.d.ts +12 -0
- package/package.json +7 -5
package/README.zh-CN.md
CHANGED
|
@@ -275,6 +275,38 @@ const installUpdate = () => {
|
|
|
275
275
|
|
|
276
276
|
---
|
|
277
277
|
|
|
278
|
+
## 🛣️ 路由模式配置 (Hash vs History)
|
|
279
|
+
|
|
280
|
+
本库完美支持 Electron 常用的文件加载模式(Hash)和现代单页应用路由模式(History)。
|
|
281
|
+
|
|
282
|
+
### 1. Hash 模式(默认推荐)
|
|
283
|
+
这是最稳健的选择,使用 Electron 标准的 `file://` 协议。
|
|
284
|
+
|
|
285
|
+
- **主进程**:无需任何额外配置(`routerMode` 默认为 `'hash'`)。
|
|
286
|
+
- **前端 (Vite)**:`vite.config.ts` 中的 `base` 设为 `'./'`(或不填)。
|
|
287
|
+
- **前端 (Router)**:使用 `createWebHashHistory()`。
|
|
288
|
+
|
|
289
|
+
### 2. History 模式(企业级方案)
|
|
290
|
+
支持美观的 URL 和标准浏览器重载行为。由于 `file://` 不支持标准路解析,本库会自动启用自定义协议(默认 `app://`)进行转发。
|
|
291
|
+
|
|
292
|
+
- **主进程**:
|
|
293
|
+
```typescript
|
|
294
|
+
new RenderUpdater({
|
|
295
|
+
updateUrl: '...',
|
|
296
|
+
versionsDir: '...',
|
|
297
|
+
routerMode: 'history', // 开启 History 路由支持
|
|
298
|
+
protocol: 'my-app' // 可选:自定义协议名 (默认 'app')
|
|
299
|
+
})
|
|
300
|
+
```
|
|
301
|
+
> 💡 **全自动化:** 协议注册和特权赋权逻辑已在库内部**全自动处理**,您无需在顶层编写任何 `registerSchemesAsPrivileged` 等繁琐代码。
|
|
302
|
+
|
|
303
|
+
- **前端构建工具 (Vite, Webpack, 等)**:**关键配置** —— 您必须将构建工具的 **公共路径 (Public Path / Base)** 设置为 `/`。这是为了确保生成的资源引用(JS/CSS/图片)为绝对路径,防止在深层路由(如 `/home/settings`)下刷新页面时出现资源加载 404。
|
|
304
|
+
- **Vite**:`base: '/'`
|
|
305
|
+
- **Webpack**:`output.publicPath: '/'`
|
|
306
|
+
- **前端路由 (Router)**:使用 `createWebHistory()`。
|
|
307
|
+
|
|
308
|
+
---
|
|
309
|
+
|
|
278
310
|
## ⚙️ API 参考
|
|
279
311
|
|
|
280
312
|
### `BuilderOptions`(CLI 配置参数)
|
|
@@ -305,6 +337,8 @@ const installUpdate = () => {
|
|
|
305
337
|
| `onDownloadComplete` | `function` | — | 下载完成钩子:`(info, doInstall) => void` |
|
|
306
338
|
| `onError` | `function` | — | 错误回调:`(error: Error) => void` |
|
|
307
339
|
| `onBeforeRestart` | `async function` | — | `app.relaunch()` 前的异步钩子,可用于保存状态 |
|
|
340
|
+
| `routerMode` | `'hash' \| 'history'` | — | 路由模式选择。默认 `'hash'` |
|
|
341
|
+
| `protocol` | `string` | — | History 模式下的自定义协议名。默认 `'app'` |
|
|
308
342
|
|
|
309
343
|
---
|
|
310
344
|
|
package/Readme.md
CHANGED
|
@@ -271,6 +271,38 @@ const installUpdate = () => {
|
|
|
271
271
|
|
|
272
272
|
---
|
|
273
273
|
|
|
274
|
+
## 🛣️ Routing Modes (Hash vs History)
|
|
275
|
+
|
|
276
|
+
The library supports both standard Electron file-loading (Hash) and modern SPA routing (History) via custom protocols.
|
|
277
|
+
|
|
278
|
+
### 1. Hash Mode (Default)
|
|
279
|
+
Recommended for simple apps. It uses standard `file://` protocol.
|
|
280
|
+
|
|
281
|
+
- **Main Process**: No extra config needed (defaults to `hash`).
|
|
282
|
+
- **Renderer (Vite)**: Set `base: './'` (or omit) in `vite.config.ts`.
|
|
283
|
+
- **Renderer (Router)**: Use `createWebHashHistory()`.
|
|
284
|
+
|
|
285
|
+
### 2. History Mode (Modern)
|
|
286
|
+
Supports clean URLs and standard browser refresh behavior. It uses a custom protocol (default `app://`) to bypass `file://` limitations.
|
|
287
|
+
|
|
288
|
+
- **Main Process**:
|
|
289
|
+
```typescript
|
|
290
|
+
new RenderUpdater({
|
|
291
|
+
updateUrl: '...',
|
|
292
|
+
versionsDir: '...',
|
|
293
|
+
routerMode: 'history', // Enable history mode
|
|
294
|
+
protocol: 'my-app' // Optional: custom protocol name (default: 'app')
|
|
295
|
+
})
|
|
296
|
+
```
|
|
297
|
+
> 💡 **No Boilerplate**: Protocol registration and privileged scheme setup are **automated** internally. You don't need to call `registerSchemesAsPrivileged` manually.
|
|
298
|
+
|
|
299
|
+
- **Renderer (Build Tool: Vite, Webpack, etc.)**: **CRITICAL** — You must set your build tool's **Public Path** or **Base** to `/` to ensure absolute asset URLs. This prevents 404 errors for assets when refreshing the page on deep-nested routes.
|
|
300
|
+
- **Vite**: `base: '/'`
|
|
301
|
+
- **Webpack**: `output.publicPath: '/'`
|
|
302
|
+
- **Renderer (Router)**: Use `createWebHistory()`.
|
|
303
|
+
|
|
304
|
+
---
|
|
305
|
+
|
|
274
306
|
## ⚙️ API Reference
|
|
275
307
|
|
|
276
308
|
### `BuilderOptions` (CLI Config)
|
|
@@ -301,6 +333,8 @@ const installUpdate = () => {
|
|
|
301
333
|
| `onDownloadComplete` | `function` | — | Completion hook: `(info, doInstall) => void` |
|
|
302
334
|
| `onError` | `function` | — | Error handler: `(error: Error) => void` |
|
|
303
335
|
| `onBeforeRestart` | `async function` | — | Called before `app.relaunch()`. Await-able for graceful shutdown |
|
|
336
|
+
| `routerMode` | `'hash' \| 'history'` | — | Routing mode. Default: `'hash'` |
|
|
337
|
+
| `protocol` | `string` | — | Custom protocol name for history mode. Default: `'app'` |
|
|
304
338
|
|
|
305
339
|
---
|
|
306
340
|
|
package/dist/bin/cli.cjs
CHANGED
|
@@ -39,8 +39,8 @@ var import_fs = __toESM(require("fs"), 1);
|
|
|
39
39
|
var import_path = __toESM(require("path"), 1);
|
|
40
40
|
var import_crypto = __toESM(require("crypto"), 1);
|
|
41
41
|
async function createUpdatePackage(options) {
|
|
42
|
-
const asar = await import("asar").catch(() => {
|
|
43
|
-
throw new Error('Please install "asar" as a devDependency to use the builder.');
|
|
42
|
+
const asar = await import("@electron/asar").catch(() => {
|
|
43
|
+
throw new Error('Please install "@electron/asar" as a devDependency to use the builder.');
|
|
44
44
|
});
|
|
45
45
|
const {
|
|
46
46
|
outDir,
|
package/dist/bin/cli.js
CHANGED
|
@@ -12,8 +12,8 @@ import fs from "fs";
|
|
|
12
12
|
import path from "path";
|
|
13
13
|
import crypto from "crypto";
|
|
14
14
|
async function createUpdatePackage(options) {
|
|
15
|
-
const asar = await import("asar").catch(() => {
|
|
16
|
-
throw new Error('Please install "asar" as a devDependency to use the builder.');
|
|
15
|
+
const asar = await import("@electron/asar").catch(() => {
|
|
16
|
+
throw new Error('Please install "@electron/asar" as a devDependency to use the builder.');
|
|
17
17
|
});
|
|
18
18
|
const {
|
|
19
19
|
outDir,
|
package/dist/builder/index.cjs
CHANGED
|
@@ -41,8 +41,8 @@ function defineConfig(config) {
|
|
|
41
41
|
return config;
|
|
42
42
|
}
|
|
43
43
|
async function createUpdatePackage(options) {
|
|
44
|
-
const asar = await import("asar").catch(() => {
|
|
45
|
-
throw new Error('Please install "asar" as a devDependency to use the builder.');
|
|
44
|
+
const asar = await import("@electron/asar").catch(() => {
|
|
45
|
+
throw new Error('Please install "@electron/asar" as a devDependency to use the builder.');
|
|
46
46
|
});
|
|
47
47
|
const {
|
|
48
48
|
outDir,
|
package/dist/builder/index.js
CHANGED
|
@@ -6,8 +6,8 @@ function defineConfig(config) {
|
|
|
6
6
|
return config;
|
|
7
7
|
}
|
|
8
8
|
async function createUpdatePackage(options) {
|
|
9
|
-
const asar = await import("asar").catch(() => {
|
|
10
|
-
throw new Error('Please install "asar" as a devDependency to use the builder.');
|
|
9
|
+
const asar = await import("@electron/asar").catch(() => {
|
|
10
|
+
throw new Error('Please install "@electron/asar" as a devDependency to use the builder.');
|
|
11
11
|
});
|
|
12
12
|
const {
|
|
13
13
|
outDir,
|
package/dist/main/index.cjs
CHANGED
|
@@ -33,12 +33,118 @@ __export(main_exports, {
|
|
|
33
33
|
RenderUpdater: () => RenderUpdater
|
|
34
34
|
});
|
|
35
35
|
module.exports = __toCommonJS(main_exports);
|
|
36
|
-
var
|
|
37
|
-
var
|
|
36
|
+
var import_path2 = __toESM(require("path"), 1);
|
|
37
|
+
var import_original_fs2 = __toESM(require("original-fs"), 1);
|
|
38
38
|
var import_crypto = __toESM(require("crypto"), 1);
|
|
39
|
-
var
|
|
39
|
+
var import_semver = __toESM(require("semver"), 1);
|
|
40
|
+
var import_electron2 = require("electron");
|
|
40
41
|
var import_promises = require("stream/promises");
|
|
41
42
|
var import_stream = require("stream");
|
|
43
|
+
|
|
44
|
+
// src/main/router-handler.ts
|
|
45
|
+
var import_electron = require("electron");
|
|
46
|
+
var import_url = require("url");
|
|
47
|
+
var import_path = __toESM(require("path"), 1);
|
|
48
|
+
var import_original_fs = __toESM(require("original-fs"), 1);
|
|
49
|
+
var RouterHandler = class _RouterHandler {
|
|
50
|
+
static protocolName = "app";
|
|
51
|
+
static isSchemaRegistered = false;
|
|
52
|
+
static isHandlerInited = false;
|
|
53
|
+
versionsDir;
|
|
54
|
+
getActiveVersion;
|
|
55
|
+
constructor(options) {
|
|
56
|
+
this.versionsDir = options.versionsDir;
|
|
57
|
+
this.getActiveVersion = options.getActiveVersion;
|
|
58
|
+
this.init();
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* 零摩擦初始化入口
|
|
62
|
+
* 自动处理 Electron 生命周期:准备前注册特权,准备后挂载拦截器。
|
|
63
|
+
*/
|
|
64
|
+
static setup(options) {
|
|
65
|
+
const { protocol: protocolName, versionsDir, getActiveVersion } = options;
|
|
66
|
+
if (!this.isSchemaRegistered) {
|
|
67
|
+
if (!import_electron.app.isReady()) {
|
|
68
|
+
this.protocolName = protocolName;
|
|
69
|
+
import_electron.protocol.registerSchemesAsPrivileged([
|
|
70
|
+
{
|
|
71
|
+
scheme: protocolName,
|
|
72
|
+
privileges: {
|
|
73
|
+
standard: true,
|
|
74
|
+
secure: true,
|
|
75
|
+
supportFetchAPI: true,
|
|
76
|
+
allowServiceWorkers: true,
|
|
77
|
+
corsEnabled: true
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
]);
|
|
81
|
+
this.isSchemaRegistered = true;
|
|
82
|
+
} else {
|
|
83
|
+
console.warn(`[RouterHandler] App already ready. Cannot register scheme "${protocolName}".`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
if (!this.isHandlerInited) {
|
|
87
|
+
import_electron.app.whenReady().then(() => {
|
|
88
|
+
new _RouterHandler({ versionsDir, getActiveVersion });
|
|
89
|
+
this.isHandlerInited = true;
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
init() {
|
|
94
|
+
const protocolName = _RouterHandler.protocolName;
|
|
95
|
+
import_electron.protocol.handle(protocolName, async (request) => {
|
|
96
|
+
const url = new URL(request.url);
|
|
97
|
+
const { host, pathname } = url;
|
|
98
|
+
if (host === "renderer") {
|
|
99
|
+
const relativePath = pathname.startsWith("/") ? pathname.slice(1) : pathname;
|
|
100
|
+
const activeVersion = this.getActiveVersion();
|
|
101
|
+
const asarPath = import_path.default.join(this.versionsDir, activeVersion, "renderer.asar");
|
|
102
|
+
if (!import_original_fs.default.existsSync(asarPath)) {
|
|
103
|
+
return new Response("Not Found", { status: 404 });
|
|
104
|
+
}
|
|
105
|
+
const ext = import_path.default.extname(relativePath);
|
|
106
|
+
const isRequestingFile = ext !== "" && !relativePath.endsWith("/");
|
|
107
|
+
let targetFile = import_path.default.join(asarPath, relativePath);
|
|
108
|
+
if (!isRequestingFile || relativePath === "index.html" || relativePath === "") {
|
|
109
|
+
targetFile = import_path.default.join(asarPath, "index.html");
|
|
110
|
+
}
|
|
111
|
+
const response = await import_electron.net.fetch((0, import_url.pathToFileURL)(targetFile).toString());
|
|
112
|
+
const headers = new Headers(response.headers);
|
|
113
|
+
headers.set("Content-Type", this.getMimeType(targetFile));
|
|
114
|
+
headers.set("Access-Control-Allow-Origin", "*");
|
|
115
|
+
return new Response(response.body, {
|
|
116
|
+
status: response.status,
|
|
117
|
+
statusText: response.statusText,
|
|
118
|
+
headers
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
return new Response("Not Found", { status: 404 });
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
getMimeType(filePath) {
|
|
125
|
+
const ext = import_path.default.extname(filePath).toLowerCase();
|
|
126
|
+
const mimeMap = {
|
|
127
|
+
".html": "text/html",
|
|
128
|
+
".js": "application/javascript",
|
|
129
|
+
".mjs": "application/javascript",
|
|
130
|
+
".css": "text/css",
|
|
131
|
+
".json": "application/json",
|
|
132
|
+
".png": "image/png",
|
|
133
|
+
".jpg": "image/jpeg",
|
|
134
|
+
".jpeg": "image/jpeg",
|
|
135
|
+
".gif": "image/gif",
|
|
136
|
+
".svg": "image/svg+xml",
|
|
137
|
+
".ico": "image/x-icon",
|
|
138
|
+
".woff": "font/woff",
|
|
139
|
+
".woff2": "font/woff2",
|
|
140
|
+
".ttf": "font/ttf",
|
|
141
|
+
".otf": "font/otf"
|
|
142
|
+
};
|
|
143
|
+
return mimeMap[ext] || "application/octet-stream";
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
// src/main/index.ts
|
|
42
148
|
var RenderUpdater = class {
|
|
43
149
|
versionsDir;
|
|
44
150
|
currentVersionFile;
|
|
@@ -46,6 +152,8 @@ var RenderUpdater = class {
|
|
|
46
152
|
activeVersion;
|
|
47
153
|
publicKey;
|
|
48
154
|
isDownloading = false;
|
|
155
|
+
routerMode;
|
|
156
|
+
protocolName;
|
|
49
157
|
autoDownload;
|
|
50
158
|
autoPrompt;
|
|
51
159
|
maxVersionsToKeep;
|
|
@@ -56,7 +164,7 @@ var RenderUpdater = class {
|
|
|
56
164
|
onBeforeRestart;
|
|
57
165
|
constructor(options) {
|
|
58
166
|
this.versionsDir = options.versionsDir;
|
|
59
|
-
this.currentVersionFile =
|
|
167
|
+
this.currentVersionFile = import_path2.default.join(this.versionsDir, "current.json");
|
|
60
168
|
this.baseUrl = options.updateUrl;
|
|
61
169
|
this.publicKey = options.publicKey;
|
|
62
170
|
this.autoDownload = options.autoDownload ?? false;
|
|
@@ -67,25 +175,34 @@ var RenderUpdater = class {
|
|
|
67
175
|
this.onDownloadComplete = options.onDownloadComplete;
|
|
68
176
|
this.onError = options.onError;
|
|
69
177
|
this.onBeforeRestart = options.onBeforeRestart;
|
|
70
|
-
|
|
71
|
-
|
|
178
|
+
this.routerMode = options.routerMode ?? "hash";
|
|
179
|
+
this.protocolName = options.protocol ?? "app";
|
|
180
|
+
if (!import_original_fs2.default.existsSync(this.versionsDir)) {
|
|
181
|
+
import_original_fs2.default.mkdirSync(this.versionsDir, { recursive: true });
|
|
72
182
|
}
|
|
73
183
|
this.activeVersion = this.readCurrentVersionFromFile();
|
|
74
184
|
this.cleanOldVersions();
|
|
185
|
+
if (this.routerMode === "history") {
|
|
186
|
+
RouterHandler.setup({
|
|
187
|
+
protocol: this.protocolName,
|
|
188
|
+
versionsDir: this.versionsDir,
|
|
189
|
+
getActiveVersion: () => this.activeVersion
|
|
190
|
+
});
|
|
191
|
+
}
|
|
75
192
|
}
|
|
76
193
|
cleanOldVersions() {
|
|
77
194
|
try {
|
|
78
|
-
if (!
|
|
79
|
-
const items =
|
|
195
|
+
if (!import_original_fs2.default.existsSync(this.versionsDir)) return;
|
|
196
|
+
const items = import_original_fs2.default.readdirSync(this.versionsDir);
|
|
80
197
|
const versionDirs = [];
|
|
81
198
|
for (const item of items) {
|
|
82
199
|
if (item === "current.json") continue;
|
|
83
|
-
const fullPath =
|
|
84
|
-
if (
|
|
200
|
+
const fullPath = import_path2.default.join(this.versionsDir, item);
|
|
201
|
+
if (import_original_fs2.default.statSync(fullPath).isDirectory()) {
|
|
85
202
|
versionDirs.push(item);
|
|
86
203
|
}
|
|
87
204
|
}
|
|
88
|
-
versionDirs.sort((a, b) =>
|
|
205
|
+
versionDirs.sort((a, b) => import_semver.default.compare(import_semver.default.coerce(b) ?? b, import_semver.default.coerce(a) ?? a));
|
|
89
206
|
const activeIdx = versionDirs.indexOf(this.activeVersion);
|
|
90
207
|
const safeVersions = /* @__PURE__ */ new Set();
|
|
91
208
|
if (activeIdx !== -1) safeVersions.add(this.activeVersion);
|
|
@@ -100,7 +217,7 @@ var RenderUpdater = class {
|
|
|
100
217
|
for (const v of versionDirs) {
|
|
101
218
|
if (!safeVersions.has(v)) {
|
|
102
219
|
console.info(`[RenderUpdater] Cleaning up old version: ${v}`);
|
|
103
|
-
|
|
220
|
+
import_original_fs2.default.rmSync(import_path2.default.join(this.versionsDir, v), { recursive: true, force: true });
|
|
104
221
|
}
|
|
105
222
|
}
|
|
106
223
|
} catch (e) {
|
|
@@ -108,9 +225,9 @@ var RenderUpdater = class {
|
|
|
108
225
|
}
|
|
109
226
|
}
|
|
110
227
|
readCurrentVersionFromFile() {
|
|
111
|
-
if (
|
|
228
|
+
if (import_original_fs2.default.existsSync(this.currentVersionFile)) {
|
|
112
229
|
try {
|
|
113
|
-
const current = JSON.parse(
|
|
230
|
+
const current = JSON.parse(import_original_fs2.default.readFileSync(this.currentVersionFile, "utf-8"));
|
|
114
231
|
return current.version;
|
|
115
232
|
} catch {
|
|
116
233
|
return "0.0.0";
|
|
@@ -119,19 +236,23 @@ var RenderUpdater = class {
|
|
|
119
236
|
return "0.0.0";
|
|
120
237
|
}
|
|
121
238
|
hasAnyVersion() {
|
|
122
|
-
return
|
|
239
|
+
return import_original_fs2.default.existsSync(this.currentVersionFile);
|
|
123
240
|
}
|
|
124
241
|
/**
|
|
125
|
-
* Returns the
|
|
126
|
-
*
|
|
242
|
+
* Returns the correct URL to load in the BrowserWindow.
|
|
243
|
+
* If history mode is enabled, it returns the custom protocol URL.
|
|
244
|
+
* Otherwise, it returns the local file URL with # hash.
|
|
127
245
|
*/
|
|
128
246
|
getLoadUrl() {
|
|
129
247
|
try {
|
|
130
|
-
if (
|
|
131
|
-
const current = JSON.parse(
|
|
248
|
+
if (import_original_fs2.default.existsSync(this.currentVersionFile)) {
|
|
249
|
+
const current = JSON.parse(import_original_fs2.default.readFileSync(this.currentVersionFile, "utf-8"));
|
|
132
250
|
const version = current.version;
|
|
133
|
-
const asarPath =
|
|
134
|
-
if (
|
|
251
|
+
const asarPath = import_path2.default.join(this.versionsDir, version, "renderer.asar");
|
|
252
|
+
if (import_original_fs2.default.existsSync(asarPath)) {
|
|
253
|
+
if (this.routerMode === "history") {
|
|
254
|
+
return `${this.protocolName}://renderer/`;
|
|
255
|
+
}
|
|
135
256
|
return `file://${asarPath}/index.html`;
|
|
136
257
|
}
|
|
137
258
|
}
|
|
@@ -146,7 +267,7 @@ var RenderUpdater = class {
|
|
|
146
267
|
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
|
|
147
268
|
const remoteInfo = await response.json();
|
|
148
269
|
const currentVersion = this.activeVersion;
|
|
149
|
-
if (
|
|
270
|
+
if (import_semver.default.gt(remoteInfo.version, currentVersion)) {
|
|
150
271
|
return { updateAvailable: true, version: remoteInfo.version, info: remoteInfo };
|
|
151
272
|
}
|
|
152
273
|
return { updateAvailable: false };
|
|
@@ -165,19 +286,19 @@ var RenderUpdater = class {
|
|
|
165
286
|
const response = await fetch(`${this.baseUrl}/latest.json?t=${Date.now()}`);
|
|
166
287
|
if (!response.ok) throw new Error("[RenderUpdater] Cannot fetch latest.json for download");
|
|
167
288
|
const info = await response.json();
|
|
168
|
-
const versionDir =
|
|
169
|
-
const asarPath =
|
|
170
|
-
if (
|
|
289
|
+
const versionDir = import_path2.default.join(this.versionsDir, info.version);
|
|
290
|
+
const asarPath = import_path2.default.join(versionDir, "renderer.asar");
|
|
291
|
+
if (import_original_fs2.default.existsSync(asarPath) && this.verifyFile(asarPath, info)) {
|
|
171
292
|
onProgress?.(100);
|
|
172
293
|
this.useVersion(info.version);
|
|
173
294
|
return;
|
|
174
295
|
}
|
|
175
|
-
if (!
|
|
176
|
-
|
|
296
|
+
if (!import_original_fs2.default.existsSync(versionDir)) {
|
|
297
|
+
import_original_fs2.default.mkdirSync(versionDir, { recursive: true });
|
|
177
298
|
}
|
|
178
299
|
let downloadedBytes = 0;
|
|
179
|
-
if (
|
|
180
|
-
downloadedBytes =
|
|
300
|
+
if (import_original_fs2.default.existsSync(asarPath)) {
|
|
301
|
+
downloadedBytes = import_original_fs2.default.statSync(asarPath).size;
|
|
181
302
|
}
|
|
182
303
|
const fetchOptions = {};
|
|
183
304
|
if (downloadedBytes > 0) {
|
|
@@ -198,7 +319,7 @@ var RenderUpdater = class {
|
|
|
198
319
|
const incomingTotal = contentLength ? parseInt(contentLength, 10) : 0;
|
|
199
320
|
const total = downloadedBytes + incomingTotal;
|
|
200
321
|
if (!downloadResponse.body) throw new Error("Response body is empty");
|
|
201
|
-
const fileStream =
|
|
322
|
+
const fileStream = import_original_fs2.default.createWriteStream(asarPath, { flags: downloadResponse.status === 206 ? "a" : "w" });
|
|
202
323
|
const progressTransform = new import_stream.Transform({
|
|
203
324
|
transform(chunk, _encoding, callback) {
|
|
204
325
|
downloadedBytes += chunk.length;
|
|
@@ -214,7 +335,7 @@ var RenderUpdater = class {
|
|
|
214
335
|
fileStream
|
|
215
336
|
);
|
|
216
337
|
if (!this.verifyFile(asarPath, info)) {
|
|
217
|
-
|
|
338
|
+
import_original_fs2.default.rmSync(versionDir, { recursive: true, force: true });
|
|
218
339
|
throw new Error("[RenderUpdater] Verification failed after download (SHA256 or RSA Mismatch)");
|
|
219
340
|
}
|
|
220
341
|
this.useVersion(info.version);
|
|
@@ -224,7 +345,7 @@ var RenderUpdater = class {
|
|
|
224
345
|
}
|
|
225
346
|
}
|
|
226
347
|
verifyFile(filePath, info) {
|
|
227
|
-
const fileBuffer =
|
|
348
|
+
const fileBuffer = import_original_fs2.default.readFileSync(filePath);
|
|
228
349
|
const hashSum = import_crypto.default.createHash("sha256");
|
|
229
350
|
hashSum.update(fileBuffer);
|
|
230
351
|
const sha256 = hashSum.digest("hex");
|
|
@@ -250,23 +371,13 @@ var RenderUpdater = class {
|
|
|
250
371
|
return true;
|
|
251
372
|
}
|
|
252
373
|
useVersion(version) {
|
|
253
|
-
|
|
374
|
+
import_original_fs2.default.writeFileSync(
|
|
254
375
|
this.currentVersionFile,
|
|
255
376
|
JSON.stringify({ version, date: (/* @__PURE__ */ new Date()).toISOString() })
|
|
256
377
|
);
|
|
257
378
|
this.activeVersion = version;
|
|
258
379
|
}
|
|
259
|
-
compareVersions
|
|
260
|
-
const parts1 = v1.split(".").map(Number);
|
|
261
|
-
const parts2 = v2.split(".").map(Number);
|
|
262
|
-
for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
|
|
263
|
-
const n1 = parts1[i] || 0;
|
|
264
|
-
const n2 = parts2[i] || 0;
|
|
265
|
-
if (n1 > n2) return 1;
|
|
266
|
-
if (n1 < n2) return -1;
|
|
267
|
-
}
|
|
268
|
-
return 0;
|
|
269
|
-
}
|
|
380
|
+
// compareVersions 已由 semver 库替代,此方法已移除
|
|
270
381
|
/**
|
|
271
382
|
* One-stop method to check for updates, show dialogs (if autoPrompt=true),
|
|
272
383
|
* download, and restart application.
|
|
@@ -285,8 +396,8 @@ var RenderUpdater = class {
|
|
|
285
396
|
if (this.onBeforeRestart) {
|
|
286
397
|
await this.onBeforeRestart();
|
|
287
398
|
}
|
|
288
|
-
|
|
289
|
-
|
|
399
|
+
import_electron2.app.relaunch();
|
|
400
|
+
import_electron2.app.quit();
|
|
290
401
|
};
|
|
291
402
|
if (this.onDownloadComplete) {
|
|
292
403
|
this.onDownloadComplete(info, () => {
|
|
@@ -295,7 +406,7 @@ var RenderUpdater = class {
|
|
|
295
406
|
} else {
|
|
296
407
|
if (info.forceUpdate) {
|
|
297
408
|
if (info.forceUpdate === "prompt") {
|
|
298
|
-
|
|
409
|
+
import_electron2.dialog.showMessageBoxSync({
|
|
299
410
|
type: "warning",
|
|
300
411
|
title: "\u4E0B\u8F7D\u5B8C\u6210",
|
|
301
412
|
message: "\u6700\u65B0\u7248\u672C\u5DF2\u4E0B\u8F7D\u5B8C\u6BD5\u3002\u5373\u5C06\u91CD\u542F\u7A0B\u5E8F\u4EE5\u5E94\u7528\u66F4\u65B0\u3002",
|
|
@@ -304,7 +415,7 @@ var RenderUpdater = class {
|
|
|
304
415
|
}
|
|
305
416
|
doInstall().catch(console.error);
|
|
306
417
|
} else if (this.autoPrompt) {
|
|
307
|
-
|
|
418
|
+
import_electron2.dialog.showMessageBoxSync({
|
|
308
419
|
type: "info",
|
|
309
420
|
title: "\u4E0B\u8F7D\u5B8C\u6210",
|
|
310
421
|
message: "\u6700\u65B0\u7248\u672C\u5DF2\u4E0B\u8F7D\u5B8C\u6BD5\u3002\u70B9\u51FB\u786E\u5B9A\u91CD\u542F\u7A0B\u5E8F\u4EE5\u5B89\u88C5\u66F4\u65B0\u3002",
|
|
@@ -327,7 +438,7 @@ var RenderUpdater = class {
|
|
|
327
438
|
} else {
|
|
328
439
|
if (info.forceUpdate) {
|
|
329
440
|
if (info.forceUpdate === "prompt") {
|
|
330
|
-
|
|
441
|
+
import_electron2.dialog.showMessageBoxSync({
|
|
331
442
|
type: "warning",
|
|
332
443
|
title: "\u53D1\u73B0\u65B0\u7248\u672C",
|
|
333
444
|
message: `\u53D1\u73B0\u65B0\u7248\u672C (v${info.version})\u3002
|
|
@@ -341,7 +452,7 @@ ${info.releaseNotes || "\u672C\u6B21\u66F4\u65B0\u4E3A\u5F3A\u5236\u66F4\u65B0\u
|
|
|
341
452
|
} else if (this.autoDownload) {
|
|
342
453
|
await doDownload();
|
|
343
454
|
} else if (this.autoPrompt) {
|
|
344
|
-
const { response } = await
|
|
455
|
+
const { response } = await import_electron2.dialog.showMessageBox({
|
|
345
456
|
type: "info",
|
|
346
457
|
title: "\u53D1\u73B0\u65B0\u7248\u672C",
|
|
347
458
|
message: `\u53D1\u73B0\u53EF\u7528\u66F4\u65B0 (v${info.version})\u3002\u662F\u5426\u7ACB\u5373\u4E0B\u8F7D\u66F4\u65B0\uFF1F
|
package/dist/main/index.d.ts
CHANGED
|
@@ -6,6 +6,8 @@ export declare class RenderUpdater {
|
|
|
6
6
|
private activeVersion;
|
|
7
7
|
private publicKey?;
|
|
8
8
|
private isDownloading;
|
|
9
|
+
private routerMode;
|
|
10
|
+
private protocolName;
|
|
9
11
|
private autoDownload;
|
|
10
12
|
private autoPrompt;
|
|
11
13
|
private maxVersionsToKeep;
|
|
@@ -19,8 +21,9 @@ export declare class RenderUpdater {
|
|
|
19
21
|
private readCurrentVersionFromFile;
|
|
20
22
|
hasAnyVersion(): boolean;
|
|
21
23
|
/**
|
|
22
|
-
* Returns the
|
|
23
|
-
*
|
|
24
|
+
* Returns the correct URL to load in the BrowserWindow.
|
|
25
|
+
* If history mode is enabled, it returns the custom protocol URL.
|
|
26
|
+
* Otherwise, it returns the local file URL with # hash.
|
|
24
27
|
*/
|
|
25
28
|
getLoadUrl(): string;
|
|
26
29
|
check(): Promise<{
|
|
@@ -34,7 +37,6 @@ export declare class RenderUpdater {
|
|
|
34
37
|
download(onProgress?: (percent: number) => void): Promise<void>;
|
|
35
38
|
private verifyFile;
|
|
36
39
|
private useVersion;
|
|
37
|
-
private compareVersions;
|
|
38
40
|
/**
|
|
39
41
|
* One-stop method to check for updates, show dialogs (if autoPrompt=true),
|
|
40
42
|
* download, and restart application.
|
package/dist/main/index.js
CHANGED
|
@@ -1,10 +1,116 @@
|
|
|
1
1
|
// src/main/index.ts
|
|
2
|
-
import
|
|
3
|
-
import
|
|
2
|
+
import path2 from "path";
|
|
3
|
+
import fs2 from "original-fs";
|
|
4
4
|
import crypto from "crypto";
|
|
5
|
-
import
|
|
5
|
+
import semver from "semver";
|
|
6
|
+
import { app as app2, dialog } from "electron";
|
|
6
7
|
import { pipeline } from "stream/promises";
|
|
7
8
|
import { Readable, Transform } from "stream";
|
|
9
|
+
|
|
10
|
+
// src/main/router-handler.ts
|
|
11
|
+
import { protocol, net, app } from "electron";
|
|
12
|
+
import { pathToFileURL } from "url";
|
|
13
|
+
import path from "path";
|
|
14
|
+
import fs from "original-fs";
|
|
15
|
+
var RouterHandler = class _RouterHandler {
|
|
16
|
+
static protocolName = "app";
|
|
17
|
+
static isSchemaRegistered = false;
|
|
18
|
+
static isHandlerInited = false;
|
|
19
|
+
versionsDir;
|
|
20
|
+
getActiveVersion;
|
|
21
|
+
constructor(options) {
|
|
22
|
+
this.versionsDir = options.versionsDir;
|
|
23
|
+
this.getActiveVersion = options.getActiveVersion;
|
|
24
|
+
this.init();
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* 零摩擦初始化入口
|
|
28
|
+
* 自动处理 Electron 生命周期:准备前注册特权,准备后挂载拦截器。
|
|
29
|
+
*/
|
|
30
|
+
static setup(options) {
|
|
31
|
+
const { protocol: protocolName, versionsDir, getActiveVersion } = options;
|
|
32
|
+
if (!this.isSchemaRegistered) {
|
|
33
|
+
if (!app.isReady()) {
|
|
34
|
+
this.protocolName = protocolName;
|
|
35
|
+
protocol.registerSchemesAsPrivileged([
|
|
36
|
+
{
|
|
37
|
+
scheme: protocolName,
|
|
38
|
+
privileges: {
|
|
39
|
+
standard: true,
|
|
40
|
+
secure: true,
|
|
41
|
+
supportFetchAPI: true,
|
|
42
|
+
allowServiceWorkers: true,
|
|
43
|
+
corsEnabled: true
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
]);
|
|
47
|
+
this.isSchemaRegistered = true;
|
|
48
|
+
} else {
|
|
49
|
+
console.warn(`[RouterHandler] App already ready. Cannot register scheme "${protocolName}".`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
if (!this.isHandlerInited) {
|
|
53
|
+
app.whenReady().then(() => {
|
|
54
|
+
new _RouterHandler({ versionsDir, getActiveVersion });
|
|
55
|
+
this.isHandlerInited = true;
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
init() {
|
|
60
|
+
const protocolName = _RouterHandler.protocolName;
|
|
61
|
+
protocol.handle(protocolName, async (request) => {
|
|
62
|
+
const url = new URL(request.url);
|
|
63
|
+
const { host, pathname } = url;
|
|
64
|
+
if (host === "renderer") {
|
|
65
|
+
const relativePath = pathname.startsWith("/") ? pathname.slice(1) : pathname;
|
|
66
|
+
const activeVersion = this.getActiveVersion();
|
|
67
|
+
const asarPath = path.join(this.versionsDir, activeVersion, "renderer.asar");
|
|
68
|
+
if (!fs.existsSync(asarPath)) {
|
|
69
|
+
return new Response("Not Found", { status: 404 });
|
|
70
|
+
}
|
|
71
|
+
const ext = path.extname(relativePath);
|
|
72
|
+
const isRequestingFile = ext !== "" && !relativePath.endsWith("/");
|
|
73
|
+
let targetFile = path.join(asarPath, relativePath);
|
|
74
|
+
if (!isRequestingFile || relativePath === "index.html" || relativePath === "") {
|
|
75
|
+
targetFile = path.join(asarPath, "index.html");
|
|
76
|
+
}
|
|
77
|
+
const response = await net.fetch(pathToFileURL(targetFile).toString());
|
|
78
|
+
const headers = new Headers(response.headers);
|
|
79
|
+
headers.set("Content-Type", this.getMimeType(targetFile));
|
|
80
|
+
headers.set("Access-Control-Allow-Origin", "*");
|
|
81
|
+
return new Response(response.body, {
|
|
82
|
+
status: response.status,
|
|
83
|
+
statusText: response.statusText,
|
|
84
|
+
headers
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
return new Response("Not Found", { status: 404 });
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
getMimeType(filePath) {
|
|
91
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
92
|
+
const mimeMap = {
|
|
93
|
+
".html": "text/html",
|
|
94
|
+
".js": "application/javascript",
|
|
95
|
+
".mjs": "application/javascript",
|
|
96
|
+
".css": "text/css",
|
|
97
|
+
".json": "application/json",
|
|
98
|
+
".png": "image/png",
|
|
99
|
+
".jpg": "image/jpeg",
|
|
100
|
+
".jpeg": "image/jpeg",
|
|
101
|
+
".gif": "image/gif",
|
|
102
|
+
".svg": "image/svg+xml",
|
|
103
|
+
".ico": "image/x-icon",
|
|
104
|
+
".woff": "font/woff",
|
|
105
|
+
".woff2": "font/woff2",
|
|
106
|
+
".ttf": "font/ttf",
|
|
107
|
+
".otf": "font/otf"
|
|
108
|
+
};
|
|
109
|
+
return mimeMap[ext] || "application/octet-stream";
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
// src/main/index.ts
|
|
8
114
|
var RenderUpdater = class {
|
|
9
115
|
versionsDir;
|
|
10
116
|
currentVersionFile;
|
|
@@ -12,6 +118,8 @@ var RenderUpdater = class {
|
|
|
12
118
|
activeVersion;
|
|
13
119
|
publicKey;
|
|
14
120
|
isDownloading = false;
|
|
121
|
+
routerMode;
|
|
122
|
+
protocolName;
|
|
15
123
|
autoDownload;
|
|
16
124
|
autoPrompt;
|
|
17
125
|
maxVersionsToKeep;
|
|
@@ -22,7 +130,7 @@ var RenderUpdater = class {
|
|
|
22
130
|
onBeforeRestart;
|
|
23
131
|
constructor(options) {
|
|
24
132
|
this.versionsDir = options.versionsDir;
|
|
25
|
-
this.currentVersionFile =
|
|
133
|
+
this.currentVersionFile = path2.join(this.versionsDir, "current.json");
|
|
26
134
|
this.baseUrl = options.updateUrl;
|
|
27
135
|
this.publicKey = options.publicKey;
|
|
28
136
|
this.autoDownload = options.autoDownload ?? false;
|
|
@@ -33,25 +141,34 @@ var RenderUpdater = class {
|
|
|
33
141
|
this.onDownloadComplete = options.onDownloadComplete;
|
|
34
142
|
this.onError = options.onError;
|
|
35
143
|
this.onBeforeRestart = options.onBeforeRestart;
|
|
36
|
-
|
|
37
|
-
|
|
144
|
+
this.routerMode = options.routerMode ?? "hash";
|
|
145
|
+
this.protocolName = options.protocol ?? "app";
|
|
146
|
+
if (!fs2.existsSync(this.versionsDir)) {
|
|
147
|
+
fs2.mkdirSync(this.versionsDir, { recursive: true });
|
|
38
148
|
}
|
|
39
149
|
this.activeVersion = this.readCurrentVersionFromFile();
|
|
40
150
|
this.cleanOldVersions();
|
|
151
|
+
if (this.routerMode === "history") {
|
|
152
|
+
RouterHandler.setup({
|
|
153
|
+
protocol: this.protocolName,
|
|
154
|
+
versionsDir: this.versionsDir,
|
|
155
|
+
getActiveVersion: () => this.activeVersion
|
|
156
|
+
});
|
|
157
|
+
}
|
|
41
158
|
}
|
|
42
159
|
cleanOldVersions() {
|
|
43
160
|
try {
|
|
44
|
-
if (!
|
|
45
|
-
const items =
|
|
161
|
+
if (!fs2.existsSync(this.versionsDir)) return;
|
|
162
|
+
const items = fs2.readdirSync(this.versionsDir);
|
|
46
163
|
const versionDirs = [];
|
|
47
164
|
for (const item of items) {
|
|
48
165
|
if (item === "current.json") continue;
|
|
49
|
-
const fullPath =
|
|
50
|
-
if (
|
|
166
|
+
const fullPath = path2.join(this.versionsDir, item);
|
|
167
|
+
if (fs2.statSync(fullPath).isDirectory()) {
|
|
51
168
|
versionDirs.push(item);
|
|
52
169
|
}
|
|
53
170
|
}
|
|
54
|
-
versionDirs.sort((a, b) =>
|
|
171
|
+
versionDirs.sort((a, b) => semver.compare(semver.coerce(b) ?? b, semver.coerce(a) ?? a));
|
|
55
172
|
const activeIdx = versionDirs.indexOf(this.activeVersion);
|
|
56
173
|
const safeVersions = /* @__PURE__ */ new Set();
|
|
57
174
|
if (activeIdx !== -1) safeVersions.add(this.activeVersion);
|
|
@@ -66,7 +183,7 @@ var RenderUpdater = class {
|
|
|
66
183
|
for (const v of versionDirs) {
|
|
67
184
|
if (!safeVersions.has(v)) {
|
|
68
185
|
console.info(`[RenderUpdater] Cleaning up old version: ${v}`);
|
|
69
|
-
|
|
186
|
+
fs2.rmSync(path2.join(this.versionsDir, v), { recursive: true, force: true });
|
|
70
187
|
}
|
|
71
188
|
}
|
|
72
189
|
} catch (e) {
|
|
@@ -74,9 +191,9 @@ var RenderUpdater = class {
|
|
|
74
191
|
}
|
|
75
192
|
}
|
|
76
193
|
readCurrentVersionFromFile() {
|
|
77
|
-
if (
|
|
194
|
+
if (fs2.existsSync(this.currentVersionFile)) {
|
|
78
195
|
try {
|
|
79
|
-
const current = JSON.parse(
|
|
196
|
+
const current = JSON.parse(fs2.readFileSync(this.currentVersionFile, "utf-8"));
|
|
80
197
|
return current.version;
|
|
81
198
|
} catch {
|
|
82
199
|
return "0.0.0";
|
|
@@ -85,19 +202,23 @@ var RenderUpdater = class {
|
|
|
85
202
|
return "0.0.0";
|
|
86
203
|
}
|
|
87
204
|
hasAnyVersion() {
|
|
88
|
-
return
|
|
205
|
+
return fs2.existsSync(this.currentVersionFile);
|
|
89
206
|
}
|
|
90
207
|
/**
|
|
91
|
-
* Returns the
|
|
92
|
-
*
|
|
208
|
+
* Returns the correct URL to load in the BrowserWindow.
|
|
209
|
+
* If history mode is enabled, it returns the custom protocol URL.
|
|
210
|
+
* Otherwise, it returns the local file URL with # hash.
|
|
93
211
|
*/
|
|
94
212
|
getLoadUrl() {
|
|
95
213
|
try {
|
|
96
|
-
if (
|
|
97
|
-
const current = JSON.parse(
|
|
214
|
+
if (fs2.existsSync(this.currentVersionFile)) {
|
|
215
|
+
const current = JSON.parse(fs2.readFileSync(this.currentVersionFile, "utf-8"));
|
|
98
216
|
const version = current.version;
|
|
99
|
-
const asarPath =
|
|
100
|
-
if (
|
|
217
|
+
const asarPath = path2.join(this.versionsDir, version, "renderer.asar");
|
|
218
|
+
if (fs2.existsSync(asarPath)) {
|
|
219
|
+
if (this.routerMode === "history") {
|
|
220
|
+
return `${this.protocolName}://renderer/`;
|
|
221
|
+
}
|
|
101
222
|
return `file://${asarPath}/index.html`;
|
|
102
223
|
}
|
|
103
224
|
}
|
|
@@ -112,7 +233,7 @@ var RenderUpdater = class {
|
|
|
112
233
|
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
|
|
113
234
|
const remoteInfo = await response.json();
|
|
114
235
|
const currentVersion = this.activeVersion;
|
|
115
|
-
if (
|
|
236
|
+
if (semver.gt(remoteInfo.version, currentVersion)) {
|
|
116
237
|
return { updateAvailable: true, version: remoteInfo.version, info: remoteInfo };
|
|
117
238
|
}
|
|
118
239
|
return { updateAvailable: false };
|
|
@@ -131,19 +252,19 @@ var RenderUpdater = class {
|
|
|
131
252
|
const response = await fetch(`${this.baseUrl}/latest.json?t=${Date.now()}`);
|
|
132
253
|
if (!response.ok) throw new Error("[RenderUpdater] Cannot fetch latest.json for download");
|
|
133
254
|
const info = await response.json();
|
|
134
|
-
const versionDir =
|
|
135
|
-
const asarPath =
|
|
136
|
-
if (
|
|
255
|
+
const versionDir = path2.join(this.versionsDir, info.version);
|
|
256
|
+
const asarPath = path2.join(versionDir, "renderer.asar");
|
|
257
|
+
if (fs2.existsSync(asarPath) && this.verifyFile(asarPath, info)) {
|
|
137
258
|
onProgress?.(100);
|
|
138
259
|
this.useVersion(info.version);
|
|
139
260
|
return;
|
|
140
261
|
}
|
|
141
|
-
if (!
|
|
142
|
-
|
|
262
|
+
if (!fs2.existsSync(versionDir)) {
|
|
263
|
+
fs2.mkdirSync(versionDir, { recursive: true });
|
|
143
264
|
}
|
|
144
265
|
let downloadedBytes = 0;
|
|
145
|
-
if (
|
|
146
|
-
downloadedBytes =
|
|
266
|
+
if (fs2.existsSync(asarPath)) {
|
|
267
|
+
downloadedBytes = fs2.statSync(asarPath).size;
|
|
147
268
|
}
|
|
148
269
|
const fetchOptions = {};
|
|
149
270
|
if (downloadedBytes > 0) {
|
|
@@ -164,7 +285,7 @@ var RenderUpdater = class {
|
|
|
164
285
|
const incomingTotal = contentLength ? parseInt(contentLength, 10) : 0;
|
|
165
286
|
const total = downloadedBytes + incomingTotal;
|
|
166
287
|
if (!downloadResponse.body) throw new Error("Response body is empty");
|
|
167
|
-
const fileStream =
|
|
288
|
+
const fileStream = fs2.createWriteStream(asarPath, { flags: downloadResponse.status === 206 ? "a" : "w" });
|
|
168
289
|
const progressTransform = new Transform({
|
|
169
290
|
transform(chunk, _encoding, callback) {
|
|
170
291
|
downloadedBytes += chunk.length;
|
|
@@ -180,7 +301,7 @@ var RenderUpdater = class {
|
|
|
180
301
|
fileStream
|
|
181
302
|
);
|
|
182
303
|
if (!this.verifyFile(asarPath, info)) {
|
|
183
|
-
|
|
304
|
+
fs2.rmSync(versionDir, { recursive: true, force: true });
|
|
184
305
|
throw new Error("[RenderUpdater] Verification failed after download (SHA256 or RSA Mismatch)");
|
|
185
306
|
}
|
|
186
307
|
this.useVersion(info.version);
|
|
@@ -190,7 +311,7 @@ var RenderUpdater = class {
|
|
|
190
311
|
}
|
|
191
312
|
}
|
|
192
313
|
verifyFile(filePath, info) {
|
|
193
|
-
const fileBuffer =
|
|
314
|
+
const fileBuffer = fs2.readFileSync(filePath);
|
|
194
315
|
const hashSum = crypto.createHash("sha256");
|
|
195
316
|
hashSum.update(fileBuffer);
|
|
196
317
|
const sha256 = hashSum.digest("hex");
|
|
@@ -216,23 +337,13 @@ var RenderUpdater = class {
|
|
|
216
337
|
return true;
|
|
217
338
|
}
|
|
218
339
|
useVersion(version) {
|
|
219
|
-
|
|
340
|
+
fs2.writeFileSync(
|
|
220
341
|
this.currentVersionFile,
|
|
221
342
|
JSON.stringify({ version, date: (/* @__PURE__ */ new Date()).toISOString() })
|
|
222
343
|
);
|
|
223
344
|
this.activeVersion = version;
|
|
224
345
|
}
|
|
225
|
-
compareVersions
|
|
226
|
-
const parts1 = v1.split(".").map(Number);
|
|
227
|
-
const parts2 = v2.split(".").map(Number);
|
|
228
|
-
for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
|
|
229
|
-
const n1 = parts1[i] || 0;
|
|
230
|
-
const n2 = parts2[i] || 0;
|
|
231
|
-
if (n1 > n2) return 1;
|
|
232
|
-
if (n1 < n2) return -1;
|
|
233
|
-
}
|
|
234
|
-
return 0;
|
|
235
|
-
}
|
|
346
|
+
// compareVersions 已由 semver 库替代,此方法已移除
|
|
236
347
|
/**
|
|
237
348
|
* One-stop method to check for updates, show dialogs (if autoPrompt=true),
|
|
238
349
|
* download, and restart application.
|
|
@@ -251,8 +362,8 @@ var RenderUpdater = class {
|
|
|
251
362
|
if (this.onBeforeRestart) {
|
|
252
363
|
await this.onBeforeRestart();
|
|
253
364
|
}
|
|
254
|
-
|
|
255
|
-
|
|
365
|
+
app2.relaunch();
|
|
366
|
+
app2.quit();
|
|
256
367
|
};
|
|
257
368
|
if (this.onDownloadComplete) {
|
|
258
369
|
this.onDownloadComplete(info, () => {
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export interface RouterHandlerOptions {
|
|
2
|
+
protocol: string;
|
|
3
|
+
versionsDir: string;
|
|
4
|
+
getActiveVersion: () => string;
|
|
5
|
+
}
|
|
6
|
+
export declare class RouterHandler {
|
|
7
|
+
private static protocolName;
|
|
8
|
+
private static isSchemaRegistered;
|
|
9
|
+
private static isHandlerInited;
|
|
10
|
+
private versionsDir;
|
|
11
|
+
private getActiveVersion;
|
|
12
|
+
private constructor();
|
|
13
|
+
/**
|
|
14
|
+
* 零摩擦初始化入口
|
|
15
|
+
* 自动处理 Electron 生命周期:准备前注册特权,准备后挂载拦截器。
|
|
16
|
+
*/
|
|
17
|
+
static setup(options: RouterHandlerOptions): void;
|
|
18
|
+
private init;
|
|
19
|
+
private getMimeType;
|
|
20
|
+
}
|
package/dist/types.d.ts
CHANGED
|
@@ -64,6 +64,18 @@ export interface UpdaterOptions {
|
|
|
64
64
|
* Useful for telling the Vue renderer to save state and cleanup.
|
|
65
65
|
*/
|
|
66
66
|
onBeforeRestart?: () => void | Promise<void>;
|
|
67
|
+
/**
|
|
68
|
+
* Defines the routing mode of the renderer project.
|
|
69
|
+
* 'hash': Use default file:// loading (Zero performance overhead).
|
|
70
|
+
* 'history': Use custom app:// protocol with try-files redirection to support History mode.
|
|
71
|
+
* Defaults to 'hash'.
|
|
72
|
+
*/
|
|
73
|
+
routerMode?: 'hash' | 'history';
|
|
74
|
+
/**
|
|
75
|
+
* Custom protocol name for history mode or asset serving.
|
|
76
|
+
* Defaults to 'app'.
|
|
77
|
+
*/
|
|
78
|
+
protocol?: string;
|
|
67
79
|
}
|
|
68
80
|
export interface BuilderOptions {
|
|
69
81
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "electron-updater-for-render",
|
|
3
|
-
"version": "1.1.2-beta.
|
|
3
|
+
"version": "1.1.2-beta.5",
|
|
4
4
|
"description": "A lightweight incremental updater for Electron renderer processes",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -27,20 +27,22 @@
|
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
29
|
"jiti": "^2.6.1",
|
|
30
|
-
"original-fs": "^1.2.0"
|
|
30
|
+
"original-fs": "^1.2.0",
|
|
31
|
+
"semver": "^7.7.4"
|
|
31
32
|
},
|
|
32
33
|
"peerDependencies": {
|
|
33
|
-
"asar": "^3.
|
|
34
|
+
"@electron/asar": "^3.3.0",
|
|
34
35
|
"electron": ">=20.0.0"
|
|
35
36
|
},
|
|
36
37
|
"peerDependenciesMeta": {
|
|
37
|
-
"asar": {
|
|
38
|
+
"@electron/asar": {
|
|
38
39
|
"optional": true
|
|
39
40
|
}
|
|
40
41
|
},
|
|
41
42
|
"devDependencies": {
|
|
43
|
+
"@electron/asar": "^4.2.0",
|
|
42
44
|
"@types/node": "^22.0.0",
|
|
43
|
-
"
|
|
45
|
+
"@types/semver": "^7.7.1",
|
|
44
46
|
"tsup": "^8.0.2",
|
|
45
47
|
"typescript": "^5.0.0"
|
|
46
48
|
},
|