electron-updater-for-render 2.0.0-beta.2 → 2.0.1
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 +51 -27
- package/Readme.md +45 -21
- package/dist/main/index.cjs +48 -10
- package/dist/main/index.d.ts +6 -1
- package/dist/main/index.js +48 -10
- package/dist/renderer/index.cjs +11 -0
- package/dist/renderer/index.d.ts +7 -0
- package/dist/renderer/index.js +10 -0
- package/package.json +1 -1
package/README.zh-CN.md
CHANGED
|
@@ -21,8 +21,9 @@
|
|
|
21
21
|
- **Pipeline 流式下载**:Node.js `stream/promises pipeline` + `Transform` 管道,内存零泄漏,网络背压自动控制
|
|
22
22
|
- **零配置版本探测**:默认自动读取 `process.cwd()/package.json` 中的版本号,无需任何额外配置
|
|
23
23
|
- **独立打包命令**:普通 `npm run build` 不受任何影响,只有 `npm run build:update` 才会触发 ASAR 打包
|
|
24
|
-
- **History
|
|
25
|
-
-
|
|
24
|
+
- **History 模式支持**:内置支持 Vue/React 的 History 路由模式。
|
|
25
|
+
- **多页应用 (MPA) 支持**:支持通过 `getLoadUrl(entry)` 加载不同的入口文件。
|
|
26
|
+
- **极简 SDK 接入**:提供 `/main`, `/preload`, `/renderer` 导出,内置 `getRouterBase` 路由基准路径探测工具。
|
|
26
27
|
- **精准灰度分发**:支持基于 `deviceId` 的环境身份匹配,配合 `updater.config.ts` 中的 `rolloutRule` 可实现百分百定点静默空投。
|
|
27
28
|
- **Semver 内测通道**:基于 `allowPrerelease` 提供生产环境与内测环境的天然逻辑隔离,保障正式服用户免受 Beta 版干扰。
|
|
28
29
|
- **动态网关鉴权**:通过 `requestOptions` 可在 HTTP 请求中自定义 Headers(如 Oauth / Bearer Token)及 Query 参数。
|
|
@@ -189,9 +190,10 @@ app.whenReady().then(async () => {
|
|
|
189
190
|
webPreferences: { preload: join(__dirname, '../preload/index.js') }
|
|
190
191
|
})
|
|
191
192
|
|
|
192
|
-
//
|
|
193
|
-
|
|
194
|
-
|
|
193
|
+
// 加载最新下载好的 ASAR,若无更新则回退至本地 dist。
|
|
194
|
+
// 支持多页应用入口传入 (默认为 'index.html')
|
|
195
|
+
const loadUrl = updater.getLoadUrl('login/index.html')
|
|
196
|
+
mainWindow.loadURL(loadUrl || `file://${join(__dirname, '../renderer/login/index.html')}`)
|
|
195
197
|
|
|
196
198
|
// 延迟触发自动检测(支持拦截强制更新,autoPrompt 为 true 时会弹窗提示)
|
|
197
199
|
setTimeout(() => updater.checkForUpdatesAndNotify(), 3000)
|
|
@@ -223,7 +225,11 @@ contextBridge.exposeInMainWorld('updaterAPI', exposeUpdaterPreload(ipcRenderer))
|
|
|
223
225
|
```vue
|
|
224
226
|
<script setup lang="ts">
|
|
225
227
|
import { ref, onMounted, onUnmounted } from 'vue'
|
|
226
|
-
import { getUpdater } from 'electron-updater-for-render/renderer'
|
|
228
|
+
import { getUpdater, getRouterBase } from 'electron-updater-for-render/renderer'
|
|
229
|
+
|
|
230
|
+
// 自动探测当前 Window 上下文对应的 History Base
|
|
231
|
+
const routerBase = getRouterBase()
|
|
232
|
+
const updater = getUpdater()
|
|
227
233
|
|
|
228
234
|
// 状态:'idle' | 'checking' | 'available' | 'downloading' | 'ready' | 'no-update'
|
|
229
235
|
const status = ref<'idle' | 'checking' | 'available' | 'downloading' | 'ready' | 'no-update'>('idle')
|
|
@@ -366,31 +372,49 @@ onUnmounted(() => {
|
|
|
366
372
|
|
|
367
373
|
本库完美支持 Electron 常用的文件加载模式(Hash)和现代单页应用路由模式(History)。
|
|
368
374
|
|
|
369
|
-
### 1. Hash
|
|
370
|
-
|
|
375
|
+
### 1. Hash 模式 (推荐方案:零配置、零侵入)
|
|
376
|
+
这是 Electron 环境下的**工业级标准实践**。它对物理路径不敏感,不需要配置任何 Base,前端项目完全不需要引入插件的任何 JS 逻辑。
|
|
371
377
|
|
|
372
|
-
-
|
|
378
|
+
- **主进程**:无需额外配置(`routerMode` 默认为 `'hash'`)。
|
|
373
379
|
- **前端 (Vite)**:`vite.config.ts` 中的 `base` 设为 `'./'`(或不填)。
|
|
374
380
|
- **前端 (Router)**:使用 `createWebHashHistory()`。
|
|
381
|
+
- **优点**:真正的“零感知”,前端代码与热更新逻辑完全解耦。
|
|
375
382
|
|
|
376
|
-
### 2. History
|
|
377
|
-
支持美观的 URL
|
|
383
|
+
### 2. History 模式 (高级方案)
|
|
384
|
+
支持美观的 URL 和标准浏览器刷新行为。由于 MPA 存在物理路径认知偏差,需要对齐基准路径(Base)。
|
|
378
385
|
|
|
379
386
|
- **主进程**:
|
|
380
387
|
```typescript
|
|
381
388
|
new RenderUpdater({
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
389
|
+
// ...
|
|
390
|
+
routerMode: 'history',
|
|
391
|
+
protocol: 'my-app' // 可选:自定义协议名 (默认 'app')
|
|
392
|
+
})
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
- **渲染层 (Router) - 选项 A:使用自动化助手 (推荐)**
|
|
396
|
+
引入插件提供的轻量级工具函数,自动探测当前窗口的物理基准:
|
|
397
|
+
```typescript
|
|
398
|
+
import { getRouterBase } from 'electron-updater-for-render/renderer'
|
|
399
|
+
const router = createRouter({
|
|
400
|
+
history: createWebHistory(getRouterBase()), // 自动对齐 app:// 协议下的物理路径
|
|
401
|
+
routes: [...]
|
|
402
|
+
})
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
- **渲染层 (Router) - 选项 B:手动对齐 (零侵入)**
|
|
406
|
+
如果您不想在前端引入任何插件代码,可以手动指定与 `getLoadUrl()` 传入路径一致的字符串(需以 `.html/` 结尾):
|
|
407
|
+
```typescript
|
|
408
|
+
const router = createRouter({
|
|
409
|
+
history: createWebHistory('/index.html/'),
|
|
410
|
+
routes: [...]
|
|
386
411
|
})
|
|
387
412
|
```
|
|
388
|
-
> 💡 **全自动化**:协议注册和特权赋权逻辑已在库内部**全自动处理**,您无需编写任何 `registerSchemesAsPrivileged` 等繁琐代码。
|
|
389
413
|
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
-
|
|
414
|
+
### 3. 多页应用 (MPA) 支持
|
|
415
|
+
- **多窗口多入口**:支持为不同窗口指定不同的 HTML 入口。
|
|
416
|
+
- **History 模式刷新**:支持在 History 模式下进行页面刷新。
|
|
417
|
+
- **基准路径探测**:配合 `getRouterBase()` 自动识别当前的路由基准路径。
|
|
394
418
|
|
|
395
419
|
---
|
|
396
420
|
|
|
@@ -434,14 +458,14 @@ onUnmounted(() => {
|
|
|
434
458
|
|
|
435
459
|
### `RenderUpdater` 实例方法
|
|
436
460
|
|
|
437
|
-
| 方法 | 返回值 |
|
|
461
|
+
| 方法 | 返回值 | 描述 |
|
|
438
462
|
|---|---|---|
|
|
439
|
-
| `check()` | `Promise<CheckResult>` | 检查更新。返回 `{ updateAvailable, status, version, info }
|
|
440
|
-
| `download(info?, onProgress?)` | `Promise<void>` |
|
|
441
|
-
| `getLoadUrl()` | `string` |
|
|
442
|
-
| `installAndRestart()` | `Promise<void>` |
|
|
443
|
-
| `checkForUpdatesAndNotify()` | `Promise<void>` |
|
|
444
|
-
| `setUpdatePending(version)` | `void` |
|
|
463
|
+
| `check()` | `Promise<CheckResult>` | 检查更新。返回 `{ updateAvailable, status, version, info }`。状态为 `'idle' \| 'available' \| 'ready'` |
|
|
464
|
+
| `download(info?, onProgress?)` | `Promise<void>` | 下载更新。建议传入 `check()` 返回的 `info` 对象以防止版本竞争 |
|
|
465
|
+
| `getLoadUrl(entry?)` | `string` | 返回加载最新 ASAR 的 URL (`app://renderer/` 或 `file://...`)。`entry` 默认为 `index.html`。若无更新则返回空字符串 |
|
|
466
|
+
| `installAndRestart()` | `Promise<void>` | 应用更新并重启应用 |
|
|
467
|
+
| `checkForUpdatesAndNotify()` | `Promise<void>` | 一站式:检查 + 提示 + 下载 + 安装(使用内置原生对话框) |
|
|
468
|
+
| `setUpdatePending(version)` | `void` | 将某版本标记为已就绪并等待重启。会触发 `onStatusChanged` |
|
|
445
469
|
| `useVersion(version)` | `void` | 立即切换磁盘和内存双重版本指针 |
|
|
446
470
|
| `activeVersion` | `string`(getter) | 当前内存中正在运行的版本(本次会话) |
|
|
447
471
|
| `pendingVersion` | `string`(getter) | 磁盘上最新已下载版本 |
|
package/Readme.md
CHANGED
|
@@ -21,8 +21,9 @@ Unlike `electron-updater` which re-downloads the entire app installer, this libr
|
|
|
21
21
|
- **Stream Pipeline Download**: Node.js `stream/promises pipeline` + `Transform` — zero memory leaks, full backpressure support
|
|
22
22
|
- **Zero-config Version Detection**: Auto-reads version from `process.cwd()/package.json` — no extra config required
|
|
23
23
|
- **Separate Build Commands**: Normal `npm run build` is untouched. Only `npm run build:update` triggers ASAR packaging
|
|
24
|
-
- **History Mode
|
|
25
|
-
- **
|
|
24
|
+
- **History Mode Support**: Built-in support for Vue/React History router mode.
|
|
25
|
+
- **Multi-Page Application (MPA)**: Support for loading multiple entry points via `getLoadUrl(entry)`.
|
|
26
|
+
- **Simplified SDK**: Unified exports for `/main`, `/preload`, and `/renderer`, including `getRouterBase`.
|
|
26
27
|
- **Precision Canary Releases**: Use `identity.deviceId` and `rolloutRule` to drop updates silently to a whitelist of device IDs.
|
|
27
28
|
- **Semver Beta Channels**: Built-in Beta/RC channel toggling via `allowPrerelease` guarantees Stable users are isolated from beta patches.
|
|
28
29
|
- **Dynamic Request Gateways**: Send custom Authenticated Headers (e.g., Oauth/Bearer tokens) and Queries via `requestOptions` when checking for updates.
|
|
@@ -187,9 +188,10 @@ app.whenReady().then(async () => {
|
|
|
187
188
|
webPreferences: { preload: join(__dirname, '../preload/index.js') }
|
|
188
189
|
})
|
|
189
190
|
|
|
190
|
-
// Load the latest downloaded ASAR, fallback to
|
|
191
|
-
|
|
192
|
-
|
|
191
|
+
// Load the latest downloaded ASAR, fallback to local dist if no update is available
|
|
192
|
+
// Supports multi-page entries (default: 'index.html')
|
|
193
|
+
const loadUrl = updater.getLoadUrl('login/index.html')
|
|
194
|
+
mainWindow.loadURL(loadUrl || `file://${join(__dirname, '../renderer/login/index.html')}`)
|
|
193
195
|
|
|
194
196
|
// Auto-check on startup (respects forceUpdate, shows dialogs if autoPrompt: true)
|
|
195
197
|
setTimeout(() => updater.checkForUpdatesAndNotify(), 3000)
|
|
@@ -221,7 +223,11 @@ The following example demonstrates the complete update flow with:
|
|
|
221
223
|
```vue
|
|
222
224
|
<script setup lang="ts">
|
|
223
225
|
import { ref, onMounted, onUnmounted } from 'vue'
|
|
224
|
-
import { getUpdater } from 'electron-updater-for-render/renderer'
|
|
226
|
+
import { getUpdater, getRouterBase } from 'electron-updater-for-render/renderer'
|
|
227
|
+
|
|
228
|
+
// Automatically detect the correct context-aware History base for MPA
|
|
229
|
+
const routerBase = getRouterBase()
|
|
230
|
+
const updater = getUpdater()
|
|
225
231
|
|
|
226
232
|
// Status: 'idle' | 'checking' | 'available' | 'downloading' | 'ready' | 'no-update'
|
|
227
233
|
const status = ref<'idle' | 'checking' | 'available' | 'downloading' | 'ready' | 'no-update'>('idle')
|
|
@@ -365,31 +371,49 @@ onUnmounted(() => {
|
|
|
365
371
|
|
|
366
372
|
The library supports both standard Electron file-loading (Hash) and modern SPA routing (History) via custom protocols.
|
|
367
373
|
|
|
368
|
-
### 1. Hash Mode (
|
|
369
|
-
|
|
374
|
+
### 1. Hash Mode (Recommended: Zero-Config, Zero-Intrusion)
|
|
375
|
+
The **industry standard** for Electron applications. It is resilient to physical path changes, requires no base configuration, and needs zero plugin-related JS in your frontend.
|
|
370
376
|
|
|
371
|
-
- **Main Process**: No extra config needed (defaults to `hash`).
|
|
377
|
+
- **Main Process**: No extra config needed (defaults to `'hash'`).
|
|
372
378
|
- **Renderer (Vite)**: Set `base: './'` (or omit) in `vite.config.ts`.
|
|
373
379
|
- **Renderer (Router)**: Use `createWebHashHistory()`.
|
|
380
|
+
- **Pros**: Pure decoupling; your frontend project remains completely unaware of the updater's existence.
|
|
374
381
|
|
|
375
|
-
### 2. History Mode (
|
|
376
|
-
Supports clean URLs and
|
|
382
|
+
### 2. History Mode (Advanced)
|
|
383
|
+
Supports clean URLs and native browser refresh behavior. Due to physical path context in MPAs, the router needs to be aligned with the base path.
|
|
377
384
|
|
|
378
385
|
- **Main Process**:
|
|
379
386
|
```typescript
|
|
380
387
|
new RenderUpdater({
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
388
|
+
// ...
|
|
389
|
+
routerMode: 'history',
|
|
390
|
+
protocol: 'my-app' // Optional: custom protocol name (default: 'app')
|
|
391
|
+
})
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
- **Renderer (Router) - Option A: Use Automated Helper (Recommended)**
|
|
395
|
+
Use our lightweight helper to automatically sense the physical base of the current window:
|
|
396
|
+
```typescript
|
|
397
|
+
import { getRouterBase } from 'electron-updater-for-render/renderer'
|
|
398
|
+
const router = createRouter({
|
|
399
|
+
history: createWebHistory(getRouterBase()), // Auto-aligns with the app:// protocol path
|
|
400
|
+
routes: [...]
|
|
401
|
+
})
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
- **Renderer (Router) - Option B: Manual Alignment (Zero-Intrusion)**
|
|
405
|
+
If you prefer not to import any plugin JS, you can manually provide a string that matches the path passed to `getLoadUrl()` (must end with `.html/`):
|
|
406
|
+
```typescript
|
|
407
|
+
const router = createRouter({
|
|
408
|
+
history: createWebHistory('/index.html/'),
|
|
409
|
+
routes: [...]
|
|
385
410
|
})
|
|
386
411
|
```
|
|
387
|
-
> 💡 **No Boilerplate**: Protocol registration and privileged scheme setup are **automated** internally. You don't need to call `registerSchemesAsPrivileged` manually.
|
|
388
412
|
|
|
389
|
-
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
- **
|
|
413
|
+
### 3. Multi-Page Application (MPA)
|
|
414
|
+
- **Multi-window Support**: Load different entry points for different windows.
|
|
415
|
+
- **History Refresh Support**: Supports browser refresh in History mode.
|
|
416
|
+
- **Base Path Detection**: Use `getRouterBase()` to automatically identify the current routing base.
|
|
393
417
|
|
|
394
418
|
---
|
|
395
419
|
|
|
@@ -437,7 +461,7 @@ Supports clean URLs and standard browser refresh behavior. Uses a custom protoco
|
|
|
437
461
|
|---|---|---|
|
|
438
462
|
| `check()` | `Promise<CheckResult>` | Check for updates. Returns `{ updateAvailable, status, version, info }`. Status is `'idle' \| 'available' \| 'ready'` |
|
|
439
463
|
| `download(info?, onProgress?)` | `Promise<void>` | Download the update. Pass the `info` object from `check()` to avoid redundant network requests |
|
|
440
|
-
| `getLoadUrl()` | `string` | Returns the URL to load the latest ASAR (`app://renderer/` or `file://...`).
|
|
464
|
+
| `getLoadUrl(entry?)` | `string` | Returns the URL to load the latest ASAR (`app://renderer/` or `file://...`). `entry` defaults to `index.html`. Returns empty string if no update downloaded |
|
|
441
465
|
| `installAndRestart()` | `Promise<void>` | Apply the pending update and relaunch the app |
|
|
442
466
|
| `checkForUpdatesAndNotify()` | `Promise<void>` | All-in-one: check + prompt + download + install with built-in native dialogs |
|
|
443
467
|
| `setUpdatePending(version)` | `void` | Mark a version as downloaded and pending restart. Also fires `onStatusChanged` |
|
package/dist/main/index.cjs
CHANGED
|
@@ -103,13 +103,42 @@ var RouterHandler = class _RouterHandler {
|
|
|
103
103
|
return new Response("ASAR Not Found", { status: 404 });
|
|
104
104
|
}
|
|
105
105
|
let targetFile = import_path.default.join(asarPath, relativePath);
|
|
106
|
-
const
|
|
107
|
-
if (!
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
106
|
+
const isFileExist = import_fs.default.existsSync(targetFile) && import_fs.default.statSync(targetFile).isFile();
|
|
107
|
+
if (!isFileExist) {
|
|
108
|
+
let rawPath = relativePath.endsWith("/") ? relativePath.slice(0, -1) : relativePath;
|
|
109
|
+
let ext = import_path.default.extname(rawPath).toLowerCase();
|
|
110
|
+
if (ext && ext !== ".html") {
|
|
111
|
+
return new Response("Not Found", { status: 404 });
|
|
112
|
+
}
|
|
113
|
+
let fallbackSearchPath = rawPath;
|
|
114
|
+
let foundFallback = false;
|
|
115
|
+
while (true) {
|
|
116
|
+
let checkFile = "";
|
|
117
|
+
if (fallbackSearchPath !== "") {
|
|
118
|
+
checkFile = import_path.default.join(asarPath, fallbackSearchPath.endsWith(".html") ? fallbackSearchPath : fallbackSearchPath + ".html");
|
|
119
|
+
if (import_fs.default.existsSync(checkFile) && import_fs.default.statSync(checkFile).isFile()) {
|
|
120
|
+
targetFile = checkFile;
|
|
121
|
+
foundFallback = true;
|
|
122
|
+
break;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
checkFile = import_path.default.join(asarPath, fallbackSearchPath, "index.html");
|
|
126
|
+
if (import_fs.default.existsSync(checkFile) && import_fs.default.statSync(checkFile).isFile()) {
|
|
127
|
+
targetFile = checkFile;
|
|
128
|
+
foundFallback = true;
|
|
129
|
+
break;
|
|
130
|
+
}
|
|
131
|
+
if (fallbackSearchPath === "") break;
|
|
132
|
+
const lastSlashIndex = fallbackSearchPath.lastIndexOf("/");
|
|
133
|
+
if (lastSlashIndex === -1) {
|
|
134
|
+
fallbackSearchPath = "";
|
|
135
|
+
} else {
|
|
136
|
+
fallbackSearchPath = fallbackSearchPath.substring(0, lastSlashIndex);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
if (!foundFallback) {
|
|
140
|
+
return new Response("Not Found", { status: 404 });
|
|
141
|
+
}
|
|
113
142
|
}
|
|
114
143
|
const response = await import_electron.net.fetch((0, import_url.pathToFileURL)(targetFile).toString());
|
|
115
144
|
const headers = new Headers(response.headers);
|
|
@@ -322,15 +351,24 @@ var RenderUpdater = class {
|
|
|
322
351
|
get isUpdatePending() {
|
|
323
352
|
return import_semver.default.gt(this._diskVersion || "0.0.0", this._activeVersion || "0.0.0");
|
|
324
353
|
}
|
|
325
|
-
|
|
354
|
+
/**
|
|
355
|
+
* 获取加载 URL。
|
|
356
|
+
* @param entry 入口文件路径,默认为 'index.html'。
|
|
357
|
+
* 对于多页应用 (MPA),可以传入 'login/index.html' 等。
|
|
358
|
+
*/
|
|
359
|
+
getLoadUrl(entry = "index.html") {
|
|
326
360
|
try {
|
|
327
361
|
if (this._activeVersion && this._activeVersion !== "0.0.0") {
|
|
328
362
|
const asarPath = import_path2.default.join(this.versionsDir, this._activeVersion, "renderer.asar");
|
|
329
363
|
if (import_original_fs.default.existsSync(asarPath)) {
|
|
330
364
|
if (this.routerMode === "history") {
|
|
331
|
-
|
|
365
|
+
if (entry === "index.html" || entry === "/index.html") {
|
|
366
|
+
return `${this.protocolName}://renderer/`;
|
|
367
|
+
}
|
|
368
|
+
const normalizedEntry = entry.startsWith("/") ? entry.slice(1) : entry;
|
|
369
|
+
return `${this.protocolName}://renderer/${normalizedEntry}/`;
|
|
332
370
|
}
|
|
333
|
-
return `file://${asarPath}
|
|
371
|
+
return `file://${asarPath}/${entry}`;
|
|
334
372
|
}
|
|
335
373
|
}
|
|
336
374
|
} catch (e) {
|
package/dist/main/index.d.ts
CHANGED
|
@@ -30,7 +30,12 @@ export declare class RenderUpdater {
|
|
|
30
30
|
get activeVersion(): string;
|
|
31
31
|
get pendingVersion(): string;
|
|
32
32
|
get isUpdatePending(): boolean;
|
|
33
|
-
|
|
33
|
+
/**
|
|
34
|
+
* 获取加载 URL。
|
|
35
|
+
* @param entry 入口文件路径,默认为 'index.html'。
|
|
36
|
+
* 对于多页应用 (MPA),可以传入 'login/index.html' 等。
|
|
37
|
+
*/
|
|
38
|
+
getLoadUrl(entry?: string): string;
|
|
34
39
|
check(): Promise<{
|
|
35
40
|
updateAvailable: boolean;
|
|
36
41
|
version?: string;
|
package/dist/main/index.js
CHANGED
|
@@ -75,13 +75,42 @@ var RouterHandler = class _RouterHandler {
|
|
|
75
75
|
return new Response("ASAR Not Found", { status: 404 });
|
|
76
76
|
}
|
|
77
77
|
let targetFile = path.join(asarPath, relativePath);
|
|
78
|
-
const
|
|
79
|
-
if (!
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
78
|
+
const isFileExist = fs.existsSync(targetFile) && fs.statSync(targetFile).isFile();
|
|
79
|
+
if (!isFileExist) {
|
|
80
|
+
let rawPath = relativePath.endsWith("/") ? relativePath.slice(0, -1) : relativePath;
|
|
81
|
+
let ext = path.extname(rawPath).toLowerCase();
|
|
82
|
+
if (ext && ext !== ".html") {
|
|
83
|
+
return new Response("Not Found", { status: 404 });
|
|
84
|
+
}
|
|
85
|
+
let fallbackSearchPath = rawPath;
|
|
86
|
+
let foundFallback = false;
|
|
87
|
+
while (true) {
|
|
88
|
+
let checkFile = "";
|
|
89
|
+
if (fallbackSearchPath !== "") {
|
|
90
|
+
checkFile = path.join(asarPath, fallbackSearchPath.endsWith(".html") ? fallbackSearchPath : fallbackSearchPath + ".html");
|
|
91
|
+
if (fs.existsSync(checkFile) && fs.statSync(checkFile).isFile()) {
|
|
92
|
+
targetFile = checkFile;
|
|
93
|
+
foundFallback = true;
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
checkFile = path.join(asarPath, fallbackSearchPath, "index.html");
|
|
98
|
+
if (fs.existsSync(checkFile) && fs.statSync(checkFile).isFile()) {
|
|
99
|
+
targetFile = checkFile;
|
|
100
|
+
foundFallback = true;
|
|
101
|
+
break;
|
|
102
|
+
}
|
|
103
|
+
if (fallbackSearchPath === "") break;
|
|
104
|
+
const lastSlashIndex = fallbackSearchPath.lastIndexOf("/");
|
|
105
|
+
if (lastSlashIndex === -1) {
|
|
106
|
+
fallbackSearchPath = "";
|
|
107
|
+
} else {
|
|
108
|
+
fallbackSearchPath = fallbackSearchPath.substring(0, lastSlashIndex);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
if (!foundFallback) {
|
|
112
|
+
return new Response("Not Found", { status: 404 });
|
|
113
|
+
}
|
|
85
114
|
}
|
|
86
115
|
const response = await net.fetch(pathToFileURL(targetFile).toString());
|
|
87
116
|
const headers = new Headers(response.headers);
|
|
@@ -294,15 +323,24 @@ var RenderUpdater = class {
|
|
|
294
323
|
get isUpdatePending() {
|
|
295
324
|
return semver.gt(this._diskVersion || "0.0.0", this._activeVersion || "0.0.0");
|
|
296
325
|
}
|
|
297
|
-
|
|
326
|
+
/**
|
|
327
|
+
* 获取加载 URL。
|
|
328
|
+
* @param entry 入口文件路径,默认为 'index.html'。
|
|
329
|
+
* 对于多页应用 (MPA),可以传入 'login/index.html' 等。
|
|
330
|
+
*/
|
|
331
|
+
getLoadUrl(entry = "index.html") {
|
|
298
332
|
try {
|
|
299
333
|
if (this._activeVersion && this._activeVersion !== "0.0.0") {
|
|
300
334
|
const asarPath = path2.join(this.versionsDir, this._activeVersion, "renderer.asar");
|
|
301
335
|
if (fs2.existsSync(asarPath)) {
|
|
302
336
|
if (this.routerMode === "history") {
|
|
303
|
-
|
|
337
|
+
if (entry === "index.html" || entry === "/index.html") {
|
|
338
|
+
return `${this.protocolName}://renderer/`;
|
|
339
|
+
}
|
|
340
|
+
const normalizedEntry = entry.startsWith("/") ? entry.slice(1) : entry;
|
|
341
|
+
return `${this.protocolName}://renderer/${normalizedEntry}/`;
|
|
304
342
|
}
|
|
305
|
-
return `file://${asarPath}
|
|
343
|
+
return `file://${asarPath}/${entry}`;
|
|
306
344
|
}
|
|
307
345
|
}
|
|
308
346
|
} catch (e) {
|
package/dist/renderer/index.cjs
CHANGED
|
@@ -20,6 +20,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/renderer/index.ts
|
|
21
21
|
var renderer_exports = {};
|
|
22
22
|
__export(renderer_exports, {
|
|
23
|
+
getRouterBase: () => getRouterBase,
|
|
23
24
|
getUpdater: () => getUpdater
|
|
24
25
|
});
|
|
25
26
|
module.exports = __toCommonJS(renderer_exports);
|
|
@@ -33,7 +34,17 @@ function getUpdater(namespace = "updaterAPI") {
|
|
|
33
34
|
}
|
|
34
35
|
return api;
|
|
35
36
|
}
|
|
37
|
+
function getRouterBase() {
|
|
38
|
+
if (typeof window === "undefined") return "/";
|
|
39
|
+
const pathname = window.location.pathname;
|
|
40
|
+
const htmlIndex = pathname.toLowerCase().lastIndexOf(".html");
|
|
41
|
+
if (htmlIndex !== -1) {
|
|
42
|
+
return pathname.substring(0, htmlIndex + 6);
|
|
43
|
+
}
|
|
44
|
+
return "/";
|
|
45
|
+
}
|
|
36
46
|
// Annotate the CommonJS export names for ESM import in node:
|
|
37
47
|
0 && (module.exports = {
|
|
48
|
+
getRouterBase,
|
|
38
49
|
getUpdater
|
|
39
50
|
});
|
package/dist/renderer/index.d.ts
CHANGED
|
@@ -21,3 +21,10 @@ export interface UpdaterClientAPI {
|
|
|
21
21
|
* This should only be used in the renderer process (e.g. standard Vue/React contexts).
|
|
22
22
|
*/
|
|
23
23
|
export declare function getUpdater(namespace?: string): UpdaterClientAPI;
|
|
24
|
+
/**
|
|
25
|
+
* 自动探测当前页面的路由基准路径 (Base URL)
|
|
26
|
+
* 专门用于解决多页面 (MPA) 在 History 模式下刷新后 404 的问题。
|
|
27
|
+
* 例如:当 Electron 加载的是 app://renderer/login/index.html/ 时,
|
|
28
|
+
* 本函数将返回 "/login/index.html/",您可以直接将其传给 createWebHistory()。
|
|
29
|
+
*/
|
|
30
|
+
export declare function getRouterBase(): string;
|
package/dist/renderer/index.js
CHANGED
|
@@ -9,6 +9,16 @@ function getUpdater(namespace = "updaterAPI") {
|
|
|
9
9
|
}
|
|
10
10
|
return api;
|
|
11
11
|
}
|
|
12
|
+
function getRouterBase() {
|
|
13
|
+
if (typeof window === "undefined") return "/";
|
|
14
|
+
const pathname = window.location.pathname;
|
|
15
|
+
const htmlIndex = pathname.toLowerCase().lastIndexOf(".html");
|
|
16
|
+
if (htmlIndex !== -1) {
|
|
17
|
+
return pathname.substring(0, htmlIndex + 6);
|
|
18
|
+
}
|
|
19
|
+
return "/";
|
|
20
|
+
}
|
|
12
21
|
export {
|
|
22
|
+
getRouterBase,
|
|
13
23
|
getUpdater
|
|
14
24
|
};
|