most-box 0.2.6 → 0.2.8
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.md +15 -1
- package/electron/main.js +30 -420
- package/electron/preload.js +1 -16
- package/electron/updateChecker.js +12 -30
- package/electron/updateChecker.test.js +14 -55
- package/out/admin/index.html +0 -0
- package/out/app/index.html +0 -0
- package/out/assets/{FilePreviewOverlay-DqH7uKb7.js → FilePreviewOverlay-D8H61qbp.js} +1 -1
- package/out/assets/LanguageToggle-CSQtRZ3A.js +1 -0
- package/out/assets/MarketingHeader-CrYsQsQ5.js +1 -0
- package/out/assets/MarketingLayout-CQurMOSD.js +1 -0
- package/out/assets/{MarketingThemeToggle-BLuQ_uE-.js → MarketingThemeToggle-CIKP8x9W.js} +1 -1
- package/out/assets/{MilkdownEditor-f2vRIY21.js → MilkdownEditor-DrsyaR4h.js} +1 -1
- package/out/assets/{OpenSidebarButton-BKX4B_yG.js → OpenSidebarButton-DnEgqP0S.js} +1 -1
- package/out/assets/{SidebarAccount-D6wj_ijc.js → SidebarAccount-B4OAl0as.js} +1 -1
- package/out/assets/SidebarHomeLink-BdsUM4Kt.js +1 -0
- package/out/assets/arrow-right-BU9c_SL4.js +1 -0
- package/out/assets/{circle-alert-Dyc-4wje.js → circle-alert-iuHzciph.js} +1 -1
- package/out/assets/{copy-D8HSH5Ki.js → copy-DEzFUzeX.js} +1 -1
- package/out/assets/{download-Q6jKMPIG.js → download-jEPn2njL.js} +1 -1
- package/out/assets/{downloadValidation-B-w8D_0W.js → downloadValidation-DbVOwdmC.js} +1 -1
- package/out/assets/{external-link-GQcZwDTT.js → external-link-BFaoZeK0.js} +1 -1
- package/out/assets/{file-text-CVl5SZPJ.js → file-text-DPA1riJ_.js} +1 -1
- package/out/assets/{folder-BtIdk-YN.js → folder-Cfgc70_F.js} +1 -1
- package/out/assets/{hard-drive-Cew_LD0N.js → hard-drive-Dar_AZEq.js} +1 -1
- package/out/assets/i18n-NjvOSuV6.js +1 -0
- package/out/assets/index-BZ-f0CZd.js +31 -0
- package/out/assets/index-Cde4g_dk.css +1 -0
- package/out/assets/index.lazy-B7AbDiMU.js +3 -0
- package/out/assets/{index.lazy-DujygWms.js → index.lazy-BsciBHf_.js} +1 -1
- package/out/assets/index.lazy-BxoGfWEW.js +1 -0
- package/out/assets/index.lazy-CKbrHko5.js +1 -0
- package/out/assets/index.lazy-CgKPe02l.js +1 -0
- package/out/assets/index.lazy-Citucq1G.js +1 -0
- package/out/assets/index.lazy-CvKEcWlH.js +2 -0
- package/out/assets/{index.lazy-WXDn5r_l.js → index.lazy-DB9Ht9LJ.js} +1 -1
- package/out/assets/index.lazy-DVeqmqaA.js +1 -0
- package/out/assets/index.lazy-hF807bh0.js +1 -0
- package/out/assets/index.lazy-oFaoa4qT.js +1 -0
- package/out/assets/index.lazy-rfgjFZNH.js +1 -0
- package/out/assets/index.lazy-zpQXhgmn.js +2 -0
- package/out/assets/{key-round-D1zfZzvP.js → key-round-DNcpSuMw.js} +1 -1
- package/out/assets/{loader-BSclU8WE.js → loader-BXQtdens.js} +1 -1
- package/out/assets/{lock-C9VP0ezG.js → lock-CLIE8cct.js} +1 -1
- package/out/assets/{message-square-Dg3YjjmO.js → message-square-Dp09WY1I.js} +1 -1
- package/out/assets/{mp-FQvFEcYM.js → mp-B6kdA94c.js} +1 -1
- package/out/assets/{music-BeCPak70.js → music-B19Tg557.js} +1 -1
- package/out/assets/{notebook-pen-DO1CJiqn.js → notebook-pen-C8TlK5sB.js} +1 -1
- package/out/assets/play-B-BjsTZz.js +1 -0
- package/out/assets/plus-4hygj-1Z.js +1 -0
- package/out/assets/{save-BVeMjaW1.js → save-BjrbPwhJ.js} +1 -1
- package/out/assets/search-DSkiWdvX.js +1 -0
- package/out/assets/{send-C1J-eGNs.js → send-BL4d5iOt.js} +1 -1
- package/out/assets/{shield-check-CZYDVJoh.js → shield-check-DzDuWO8r.js} +1 -1
- package/out/assets/smartphone-tRRFFO0a.js +1 -0
- package/out/assets/{trash-2-C_LtCiNh.js → trash-2-V12tJ8lI.js} +1 -1
- package/out/assets/triangle-alert-DGWc8ZHu.js +1 -0
- package/out/assets/{ui-PnUvY9zL.js → ui-BpDGD3IR.js} +2 -2
- package/out/assets/{upload-DgxxldtV.js → upload-CoCptKya.js} +1 -1
- package/out/assets/{useChannelMessages-CZHmH5n-.js → useChannelMessages-B6RMaNDH.js} +1 -1
- package/out/assets/useGameRoom-CRJL0kcm.js +1 -0
- package/out/assets/userProfile-DBkH4s29.js +1 -0
- package/out/assets/{wallet-DBTWBi6K.js → wallet-T7ONZ3Kg.js} +1 -1
- package/out/assets/{wifi-pP6SJLcu.js → wifi-Bgo8eHY0.js} +1 -1
- package/out/chat/index.html +0 -0
- package/out/chat/join/index.html +0 -0
- package/out/download/index.html +2 -2
- package/out/game/gandengyan/index.html +0 -0
- package/out/game/index.html +0 -0
- package/out/game/zhajinhua/index.html +0 -0
- package/out/index.html +2 -2
- package/out/note/index.html +0 -0
- package/out/ping/index.html +2 -2
- package/out/profile/index.html +0 -0
- package/out/web3/index.html +0 -0
- package/package.json +5 -8
- package/server/src/core/channelIdentity.js +3 -5
- package/server/src/core/gameRoom.js +18 -43
- package/server/src/core/ownerMetadata.js +3 -4
- package/server/src/core/releaseManifest.js +91 -0
- package/server/src/core/shared.js +50 -0
- package/server/src/core/zhajinhua.js +12 -33
- package/server/src/games/gandengyan.js +7 -14
- package/server/src/http/nodeStatus.js +0 -6
- package/server/src/http/routes/channelRoutes.js +1 -13
- package/server/src/http/routes/nodeRoutes.js +1 -1
- package/server/src/index.js +44 -298
- package/server/src/utils/api.js +47 -0
- package/server/src/utils/auth.js +2 -5
- package/server/src/utils/dateTime.js +1 -3
- package/out/assets/LanguageToggle-BoBNesNU.js +0 -1
- package/out/assets/MarketingHeader-Derv67Vs.js +0 -1
- package/out/assets/MarketingLayout-fc2mymJm.js +0 -1
- package/out/assets/SidebarHomeLink-ByOHwlH8.js +0 -1
- package/out/assets/arrow-right-BCi9KXDX.js +0 -1
- package/out/assets/cloud-DfaFxhLj.js +0 -1
- package/out/assets/i18n--maTEG0d.js +0 -1
- package/out/assets/index-B2-kpfu5.css +0 -1
- package/out/assets/index-Cg4kEvx8.js +0 -31
- package/out/assets/index.lazy-BAq__8ON.js +0 -2
- package/out/assets/index.lazy-BPh8D_H9.js +0 -1
- package/out/assets/index.lazy-Bn4kDToA.js +0 -1
- package/out/assets/index.lazy-BqHgW5fE.js +0 -1
- package/out/assets/index.lazy-CLYRM_kh.js +0 -1
- package/out/assets/index.lazy-CMSCQucz.js +0 -1
- package/out/assets/index.lazy-Cut4XkPH.js +0 -1
- package/out/assets/index.lazy-DKOSJyaQ.js +0 -2
- package/out/assets/index.lazy-DjPbRI_L.js +0 -1
- package/out/assets/index.lazy-DxAGUwlf.js +0 -3
- package/out/assets/index.lazy-ghLI7p1x.js +0 -1
- package/out/assets/play-CnhsLC5C.js +0 -1
- package/out/assets/plus-BpjwXzbO.js +0 -1
- package/out/assets/search-CsAnnJyH.js +0 -1
- package/out/assets/triangle-alert-DOwc-41Y.js +0 -1
- package/out/assets/useGameRoom-B8Ds_PKe.js +0 -1
- package/out/assets/userProfile-CRGZ8PUF.js +0 -1
package/README.md
CHANGED
|
@@ -30,6 +30,12 @@
|
|
|
30
30
|
|
|
31
31
|
前往 [Most.Box 下载页](https://Most.Box/download) 下载客户端,支持 Windows、macOS 和 Linux。桌面端内置本地 MostBox 节点,提供完整 P2P 文件分享、下载校验和持续做种能力,无需单独安装 Node.js。
|
|
32
32
|
|
|
33
|
+
### 移动端 App 计划
|
|
34
|
+
|
|
35
|
+
移动端优先按 Android 前台完整种子 MVP 推进,参考 Keet/Pear 的“P2P 核心端 + 平台 UI 壳”分层:手机端先验证自己能发布、下载、校验并在前台继续做种,再扩展后台能力、iOS 和商店分发。详细范围见 [docs/mobile-android-plan.md](docs/mobile-android-plan.md)。
|
|
36
|
+
|
|
37
|
+
Android 工程入口在 `mobile/android/`。本地开发可运行 `npm run android` 启动 Expo Dev Client 并打开 Android 真机/模拟器;首次安装开发版或原生依赖变更后运行 `npm run android:build`。
|
|
38
|
+
|
|
33
39
|
### 方式二:npm 包
|
|
34
40
|
|
|
35
41
|
适合开发、自托管或临时启动本机节点。请先安装 Node.js >= 22.12,然后运行:
|
|
@@ -272,7 +278,7 @@ git push origin vx.x.x
|
|
|
272
278
|
|
|
273
279
|
1. **npm 包发布** — 发布 `most-box` 到 npm registry
|
|
274
280
|
2. **Windows 打包** — 分别构建 `.exe` 安装包(x64 / arm64)并上传 Release
|
|
275
|
-
3. **macOS 打包** — 构建 `.dmg`
|
|
281
|
+
3. **macOS 打包** — 构建 `.dmg` 安装包(x64 + arm64)并上传 Release
|
|
276
282
|
4. **Linux 打包** — 构建 `.AppImage` 安装包(x64 + arm64)并上传 Release
|
|
277
283
|
5. **下载镜像** — 将 Release 资产同步到 Cloudflare R2,并生成 `releases/latest.json`
|
|
278
284
|
|
|
@@ -282,6 +288,14 @@ GitHub Release 是可信备用源;下载页优先读取 R2 的 `releases/lates
|
|
|
282
288
|
|
|
283
289
|
R2 发布资产使用独立公开桶,默认 bucket 为 `most-box-releases`,默认公开域名为
|
|
284
290
|
`https://download.most.box`。不要复用 `api.most.box` 项目的 `most-box-backup` 备份桶。
|
|
291
|
+
Release workflow 不设置 Infrequent Access,R2 对象保持默认 Standard 存储层,并在上传后用
|
|
292
|
+
`head-object` 校验存储层与缓存头。版本化安装包使用
|
|
293
|
+
`public, max-age=31536000, immutable`;`releases/latest.json` 使用
|
|
294
|
+
`public, max-age=60, stale-while-revalidate=300`。
|
|
295
|
+
新发版只在 manifest 和 R2 中发布当前系统可手动打开的 installer(Windows `.exe`、
|
|
296
|
+
macOS `.dmg`、Linux `.AppImage`),不再发布 updater / blockmap 资产。
|
|
297
|
+
需要临时复查线上对象时,可手动运行 GitHub Actions 的 `Verify R2 Release` workflow;
|
|
298
|
+
它复用仓库 R2 secrets 做只读 `head-object` 检查,不需要在本机输入 R2 密钥。
|
|
285
299
|
|
|
286
300
|
在仓库 Settings → Secrets and variables → Actions 中添加:
|
|
287
301
|
|
package/electron/main.js
CHANGED
|
@@ -3,17 +3,16 @@ import {
|
|
|
3
3
|
BrowserWindow,
|
|
4
4
|
Menu,
|
|
5
5
|
Tray,
|
|
6
|
-
|
|
6
|
+
dialog,
|
|
7
7
|
nativeImage,
|
|
8
8
|
shell,
|
|
9
9
|
} from 'electron'
|
|
10
|
-
import { spawn } from 'node:child_process'
|
|
11
10
|
import fs from 'node:fs'
|
|
12
11
|
import path from 'node:path'
|
|
13
|
-
import { Readable } from 'node:stream'
|
|
14
12
|
import { fileURLToPath } from 'node:url'
|
|
15
13
|
|
|
16
14
|
import {
|
|
15
|
+
formatBytes,
|
|
17
16
|
getAvailableUpdate,
|
|
18
17
|
getCurrentArch,
|
|
19
18
|
getCurrentPlatform,
|
|
@@ -23,7 +22,6 @@ import {
|
|
|
23
22
|
createMostDeepLinkTarget,
|
|
24
23
|
findMostDeepLinkArg,
|
|
25
24
|
} from './deepLink.js'
|
|
26
|
-
import { calculateCid } from '../server/src/core/cid.js'
|
|
27
25
|
|
|
28
26
|
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
29
27
|
const PORT = 1976
|
|
@@ -34,16 +32,6 @@ let tray = null
|
|
|
34
32
|
let isQuitting = false
|
|
35
33
|
let hasCheckedForUpdates = false
|
|
36
34
|
let pendingDeepLinkUrl = ''
|
|
37
|
-
let updateState = {
|
|
38
|
-
status: 'idle',
|
|
39
|
-
version: '',
|
|
40
|
-
filename: '',
|
|
41
|
-
source: '',
|
|
42
|
-
progress: 0,
|
|
43
|
-
error: '',
|
|
44
|
-
path: '',
|
|
45
|
-
cid: '',
|
|
46
|
-
}
|
|
47
35
|
|
|
48
36
|
const gotSingleInstanceLock = app.requestSingleInstanceLock()
|
|
49
37
|
|
|
@@ -101,57 +89,6 @@ function getInitialWindowUrl() {
|
|
|
101
89
|
return initialUrl
|
|
102
90
|
}
|
|
103
91
|
|
|
104
|
-
function getPublicUpdateState() {
|
|
105
|
-
return {
|
|
106
|
-
status: updateState.status,
|
|
107
|
-
version: updateState.version,
|
|
108
|
-
filename: updateState.filename,
|
|
109
|
-
source: updateState.source,
|
|
110
|
-
progress: updateState.progress,
|
|
111
|
-
error: updateState.error,
|
|
112
|
-
cid: updateState.cid,
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
function setUpdateState(patch) {
|
|
117
|
-
updateState = {
|
|
118
|
-
...updateState,
|
|
119
|
-
...patch,
|
|
120
|
-
}
|
|
121
|
-
mainWindow?.webContents.send('updates:state', getPublicUpdateState())
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
function getUpdateAssetFilename(update) {
|
|
125
|
-
return path.basename(
|
|
126
|
-
update.asset.filename ||
|
|
127
|
-
`MostBox-${update.version}-${getCurrentPlatform()}-${getCurrentArch()}`
|
|
128
|
-
)
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
function getSafeVersionDir(version) {
|
|
132
|
-
return String(version || 'unknown').replace(/[^a-zA-Z0-9._-]/g, '_')
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
function getUpdateCachePath(update) {
|
|
136
|
-
const filename = getUpdateAssetFilename(update)
|
|
137
|
-
return path.join(
|
|
138
|
-
app.getPath('userData'),
|
|
139
|
-
'updates',
|
|
140
|
-
getSafeVersionDir(update.version),
|
|
141
|
-
filename
|
|
142
|
-
)
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
async function verifyFileCid(filePath, expectedCid) {
|
|
146
|
-
const { cid } = await calculateCid(filePath)
|
|
147
|
-
const actualCid = cid.toString()
|
|
148
|
-
if (actualCid !== expectedCid) {
|
|
149
|
-
throw new Error(
|
|
150
|
-
`Update CID mismatch. Expected ${expectedCid}, got ${actualCid}.`
|
|
151
|
-
)
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
|
|
155
92
|
function registerMostProtocolClient() {
|
|
156
93
|
if (process.defaultApp && process.argv.length >= 2) {
|
|
157
94
|
app.setAsDefaultProtocolClient('most', process.execPath, [
|
|
@@ -163,14 +100,6 @@ function registerMostProtocolClient() {
|
|
|
163
100
|
app.setAsDefaultProtocolClient('most')
|
|
164
101
|
}
|
|
165
102
|
|
|
166
|
-
function registerUpdateIpc() {
|
|
167
|
-
ipcMain.handle('updates:get-state', () => getPublicUpdateState())
|
|
168
|
-
ipcMain.handle('updates:install-and-restart', async () => {
|
|
169
|
-
await installDownloadedUpdate()
|
|
170
|
-
return getPublicUpdateState()
|
|
171
|
-
})
|
|
172
|
-
}
|
|
173
|
-
|
|
174
103
|
function openMostDeepLink(link) {
|
|
175
104
|
const targetUrl = createMostDeepLinkTarget(
|
|
176
105
|
link,
|
|
@@ -272,348 +201,14 @@ async function fetchReleaseManifest(manifestUrl) {
|
|
|
272
201
|
}
|
|
273
202
|
}
|
|
274
203
|
|
|
275
|
-
async function downloadUpdateFromHttp(update, targetPath) {
|
|
276
|
-
if (!update.downloadUrl) {
|
|
277
|
-
throw new Error('No HTTP fallback URL is available for this update')
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
const tempPath = `${targetPath}.http.part`
|
|
281
|
-
fs.rmSync(tempPath, { force: true })
|
|
282
|
-
fs.mkdirSync(path.dirname(targetPath), { recursive: true })
|
|
283
|
-
|
|
284
|
-
const response = await fetch(update.downloadUrl, { cache: 'no-store' })
|
|
285
|
-
if (!response.ok || !response.body) {
|
|
286
|
-
throw new Error(`HTTP update download failed: ${response.status}`)
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
const total = Number(response.headers.get('content-length')) || 0
|
|
290
|
-
let loaded = 0
|
|
291
|
-
|
|
292
|
-
await new Promise((resolve, reject) => {
|
|
293
|
-
const rs = Readable.fromWeb(response.body)
|
|
294
|
-
const ws = fs.createWriteStream(tempPath)
|
|
295
|
-
|
|
296
|
-
const cleanup = err => {
|
|
297
|
-
rs.destroy(err)
|
|
298
|
-
ws.destroy()
|
|
299
|
-
fs.unlink(tempPath, () => {})
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
rs.on('data', chunk => {
|
|
303
|
-
loaded += chunk.length
|
|
304
|
-
if (total > 0) {
|
|
305
|
-
setUpdateState({
|
|
306
|
-
status: 'downloading',
|
|
307
|
-
source: 'http',
|
|
308
|
-
progress: Math.min(99, Math.round((loaded / total) * 100)),
|
|
309
|
-
})
|
|
310
|
-
}
|
|
311
|
-
})
|
|
312
|
-
|
|
313
|
-
rs.on('error', err => {
|
|
314
|
-
cleanup(err)
|
|
315
|
-
reject(err)
|
|
316
|
-
})
|
|
317
|
-
ws.on('error', err => {
|
|
318
|
-
cleanup(err)
|
|
319
|
-
reject(err)
|
|
320
|
-
})
|
|
321
|
-
ws.on('finish', resolve)
|
|
322
|
-
rs.pipe(ws)
|
|
323
|
-
})
|
|
324
|
-
|
|
325
|
-
try {
|
|
326
|
-
await verifyFileCid(tempPath, update.cid)
|
|
327
|
-
fs.rmSync(targetPath, { force: true })
|
|
328
|
-
fs.renameSync(tempPath, targetPath)
|
|
329
|
-
} catch (error) {
|
|
330
|
-
fs.rmSync(tempPath, { force: true })
|
|
331
|
-
throw error
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
try {
|
|
335
|
-
await engine?.seedCidFileFromPath(update.cid, targetPath, {
|
|
336
|
-
fileName: getUpdateAssetFilename(update),
|
|
337
|
-
source: 'update',
|
|
338
|
-
})
|
|
339
|
-
} catch (error) {
|
|
340
|
-
console.warn('[Electron] Update downloaded but seeding failed:', error)
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
async function downloadUpdateByCid(update, targetPath) {
|
|
345
|
-
if (!engine?.downloadCidToPath) {
|
|
346
|
-
throw new Error('MostBox engine update download API is unavailable')
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
const taskId = `update_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`
|
|
350
|
-
const filename = getUpdateAssetFilename(update)
|
|
351
|
-
|
|
352
|
-
const onProgress = data => {
|
|
353
|
-
if (data?.taskId !== taskId) return
|
|
354
|
-
setUpdateState({
|
|
355
|
-
status: 'downloading',
|
|
356
|
-
source: 'cid',
|
|
357
|
-
progress: Math.min(99, Number(data.percent) || 0),
|
|
358
|
-
})
|
|
359
|
-
}
|
|
360
|
-
const onStatus = data => {
|
|
361
|
-
if (data?.taskId !== taskId) return
|
|
362
|
-
setUpdateState({
|
|
363
|
-
status: 'downloading',
|
|
364
|
-
source: 'cid',
|
|
365
|
-
filename,
|
|
366
|
-
})
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
engine.on('download:progress', onProgress)
|
|
370
|
-
engine.on('download:status', onStatus)
|
|
371
|
-
try {
|
|
372
|
-
await engine.downloadCidToPath({
|
|
373
|
-
cid: update.cid,
|
|
374
|
-
fileName: filename,
|
|
375
|
-
targetPath,
|
|
376
|
-
taskId,
|
|
377
|
-
timeout: 30000,
|
|
378
|
-
overwrite: true,
|
|
379
|
-
source: 'update',
|
|
380
|
-
})
|
|
381
|
-
} finally {
|
|
382
|
-
engine.off('download:progress', onProgress)
|
|
383
|
-
engine.off('download:status', onStatus)
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
async function downloadUpdate(update) {
|
|
388
|
-
const targetPath = getUpdateCachePath(update)
|
|
389
|
-
const filename = getUpdateAssetFilename(update)
|
|
390
|
-
|
|
391
|
-
setUpdateState({
|
|
392
|
-
status: 'downloading',
|
|
393
|
-
version: update.version,
|
|
394
|
-
filename,
|
|
395
|
-
source: 'cid',
|
|
396
|
-
progress: 0,
|
|
397
|
-
error: '',
|
|
398
|
-
path: '',
|
|
399
|
-
cid: update.cid,
|
|
400
|
-
})
|
|
401
|
-
|
|
402
|
-
try {
|
|
403
|
-
await downloadUpdateByCid(update, targetPath)
|
|
404
|
-
} catch (cidError) {
|
|
405
|
-
console.warn('[Electron] CID update download failed, trying fallback:', cidError)
|
|
406
|
-
setUpdateState({
|
|
407
|
-
status: 'downloading',
|
|
408
|
-
source: 'http',
|
|
409
|
-
progress: 0,
|
|
410
|
-
error: '',
|
|
411
|
-
})
|
|
412
|
-
await downloadUpdateFromHttp(update, targetPath)
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
await verifyFileCid(targetPath, update.cid)
|
|
416
|
-
setUpdateState({
|
|
417
|
-
status: 'downloaded',
|
|
418
|
-
version: update.version,
|
|
419
|
-
filename,
|
|
420
|
-
source: updateState.source || 'cid',
|
|
421
|
-
progress: 100,
|
|
422
|
-
error: '',
|
|
423
|
-
path: targetPath,
|
|
424
|
-
cid: update.cid,
|
|
425
|
-
})
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
function quitForUpdate() {
|
|
429
|
-
isQuitting = true
|
|
430
|
-
if (tray) {
|
|
431
|
-
tray.destroy()
|
|
432
|
-
tray = null
|
|
433
|
-
}
|
|
434
|
-
app.quit()
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
function getPowerShellPath() {
|
|
438
|
-
if (process.env.SystemRoot) {
|
|
439
|
-
const candidate = path.join(
|
|
440
|
-
process.env.SystemRoot,
|
|
441
|
-
'System32',
|
|
442
|
-
'WindowsPowerShell',
|
|
443
|
-
'v1.0',
|
|
444
|
-
'powershell.exe'
|
|
445
|
-
)
|
|
446
|
-
if (fs.existsSync(candidate)) return candidate
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
return 'powershell.exe'
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
function installWindowsUpdate(updatePath) {
|
|
453
|
-
const scriptPath = path.join(
|
|
454
|
-
app.getPath('userData'),
|
|
455
|
-
'updates',
|
|
456
|
-
'install-windows.ps1'
|
|
457
|
-
)
|
|
458
|
-
const exePath = app.getPath('exe')
|
|
459
|
-
const script = `param(
|
|
460
|
-
[Parameter(Mandatory=$true)][string]$UpdatePath,
|
|
461
|
-
[Parameter(Mandatory=$true)][int]$AppPid,
|
|
462
|
-
[Parameter(Mandatory=$true)][string]$ExePath
|
|
463
|
-
)
|
|
464
|
-
|
|
465
|
-
$ErrorActionPreference = "Stop"
|
|
466
|
-
try {
|
|
467
|
-
Wait-Process -Id $AppPid -ErrorAction SilentlyContinue
|
|
468
|
-
} catch {}
|
|
469
|
-
|
|
470
|
-
$installer = Start-Process -FilePath $UpdatePath -ArgumentList "/S" -Wait -PassThru
|
|
471
|
-
if (($null -eq $installer.ExitCode -or $installer.ExitCode -eq 0) -and (Test-Path -LiteralPath $ExePath)) {
|
|
472
|
-
Start-Process -FilePath $ExePath
|
|
473
|
-
}
|
|
474
|
-
`
|
|
475
|
-
|
|
476
|
-
fs.mkdirSync(path.dirname(scriptPath), { recursive: true })
|
|
477
|
-
fs.writeFileSync(scriptPath, script, 'utf8')
|
|
478
|
-
const child = spawn(
|
|
479
|
-
getPowerShellPath(),
|
|
480
|
-
[
|
|
481
|
-
'-NoProfile',
|
|
482
|
-
'-ExecutionPolicy',
|
|
483
|
-
'Bypass',
|
|
484
|
-
'-File',
|
|
485
|
-
scriptPath,
|
|
486
|
-
updatePath,
|
|
487
|
-
String(process.pid),
|
|
488
|
-
exePath,
|
|
489
|
-
],
|
|
490
|
-
{
|
|
491
|
-
detached: true,
|
|
492
|
-
stdio: 'ignore',
|
|
493
|
-
windowsHide: true,
|
|
494
|
-
}
|
|
495
|
-
)
|
|
496
|
-
child.unref()
|
|
497
|
-
quitForUpdate()
|
|
498
|
-
}
|
|
499
|
-
|
|
500
|
-
function getMacAppBundlePath() {
|
|
501
|
-
return path.resolve(app.getPath('exe'), '../../..')
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
function installMacUpdate(updatePath) {
|
|
505
|
-
const scriptPath = path.join(app.getPath('userData'), 'updates', 'install-mac.sh')
|
|
506
|
-
const appBundlePath = getMacAppBundlePath()
|
|
507
|
-
const script = `#!/bin/bash
|
|
508
|
-
set -e
|
|
509
|
-
APP_PATH="$1"
|
|
510
|
-
ZIP_PATH="$2"
|
|
511
|
-
TMP_DIR="$(mktemp -d)"
|
|
512
|
-
cleanup() {
|
|
513
|
-
rm -rf "$TMP_DIR"
|
|
514
|
-
}
|
|
515
|
-
trap cleanup EXIT
|
|
516
|
-
while pgrep -f "$APP_PATH/Contents/MacOS" >/dev/null 2>&1; do
|
|
517
|
-
sleep 1
|
|
518
|
-
done
|
|
519
|
-
ditto -x -k "$ZIP_PATH" "$TMP_DIR"
|
|
520
|
-
NEW_APP="$(find "$TMP_DIR" -maxdepth 1 -name "*.app" -type d | head -n 1)"
|
|
521
|
-
if [ -z "$NEW_APP" ]; then
|
|
522
|
-
exit 1
|
|
523
|
-
fi
|
|
524
|
-
rm -rf "$APP_PATH"
|
|
525
|
-
ditto "$NEW_APP" "$APP_PATH"
|
|
526
|
-
open "$APP_PATH"
|
|
527
|
-
`
|
|
528
|
-
|
|
529
|
-
fs.mkdirSync(path.dirname(scriptPath), { recursive: true })
|
|
530
|
-
fs.writeFileSync(scriptPath, script, { mode: 0o755 })
|
|
531
|
-
const child = spawn('/bin/bash', [scriptPath, appBundlePath, updatePath], {
|
|
532
|
-
detached: true,
|
|
533
|
-
stdio: 'ignore',
|
|
534
|
-
})
|
|
535
|
-
child.unref()
|
|
536
|
-
quitForUpdate()
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
function installLinuxUpdate(updatePath) {
|
|
540
|
-
const currentAppImage = process.env.APPIMAGE
|
|
541
|
-
if (!currentAppImage) {
|
|
542
|
-
shell.openPath(updatePath).catch(() => {})
|
|
543
|
-
throw new Error('当前 Linux 运行方式不支持自动替换 AppImage')
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
const scriptPath = path.join(app.getPath('userData'), 'updates', 'install-linux.sh')
|
|
547
|
-
const script = `#!/bin/sh
|
|
548
|
-
set -e
|
|
549
|
-
APPIMAGE_PATH="$1"
|
|
550
|
-
UPDATE_PATH="$2"
|
|
551
|
-
APP_PID="$3"
|
|
552
|
-
while kill -0 "$APP_PID" >/dev/null 2>&1; do
|
|
553
|
-
sleep 1
|
|
554
|
-
done
|
|
555
|
-
cp "$UPDATE_PATH" "$APPIMAGE_PATH"
|
|
556
|
-
chmod +x "$APPIMAGE_PATH"
|
|
557
|
-
nohup "$APPIMAGE_PATH" >/dev/null 2>&1 &
|
|
558
|
-
`
|
|
559
|
-
|
|
560
|
-
fs.mkdirSync(path.dirname(scriptPath), { recursive: true })
|
|
561
|
-
fs.writeFileSync(scriptPath, script, { mode: 0o755 })
|
|
562
|
-
const child = spawn('/bin/sh', [scriptPath, currentAppImage, updatePath, String(process.pid)], {
|
|
563
|
-
detached: true,
|
|
564
|
-
stdio: 'ignore',
|
|
565
|
-
})
|
|
566
|
-
child.unref()
|
|
567
|
-
quitForUpdate()
|
|
568
|
-
}
|
|
569
|
-
|
|
570
|
-
async function installDownloadedUpdate() {
|
|
571
|
-
if (updateState.status !== 'downloaded' || !updateState.path) {
|
|
572
|
-
throw new Error('No downloaded update is ready to install')
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
setUpdateState({ status: 'installing', error: '' })
|
|
576
|
-
|
|
577
|
-
try {
|
|
578
|
-
if (process.platform === 'win32') {
|
|
579
|
-
installWindowsUpdate(updateState.path)
|
|
580
|
-
return
|
|
581
|
-
}
|
|
582
|
-
if (process.platform === 'darwin') {
|
|
583
|
-
installMacUpdate(updateState.path)
|
|
584
|
-
return
|
|
585
|
-
}
|
|
586
|
-
if (process.platform === 'linux') {
|
|
587
|
-
installLinuxUpdate(updateState.path)
|
|
588
|
-
return
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
throw new Error('当前平台不支持自动安装更新')
|
|
592
|
-
} catch (error) {
|
|
593
|
-
setUpdateState({
|
|
594
|
-
status: 'error',
|
|
595
|
-
error: error?.message || 'Update install failed',
|
|
596
|
-
})
|
|
597
|
-
throw error
|
|
598
|
-
}
|
|
599
|
-
}
|
|
600
|
-
|
|
601
204
|
async function checkForUpdates() {
|
|
602
205
|
if (hasCheckedForUpdates) return
|
|
603
206
|
hasCheckedForUpdates = true
|
|
604
207
|
|
|
605
208
|
try {
|
|
606
|
-
setUpdateState({
|
|
607
|
-
status: 'checking',
|
|
608
|
-
progress: 0,
|
|
609
|
-
error: '',
|
|
610
|
-
})
|
|
611
209
|
const platform = getCurrentPlatform()
|
|
612
210
|
const arch = getCurrentArch()
|
|
613
|
-
if (!platform || !arch)
|
|
614
|
-
setUpdateState({ status: 'idle' })
|
|
615
|
-
return
|
|
616
|
-
}
|
|
211
|
+
if (!platform || !arch) return
|
|
617
212
|
|
|
618
213
|
const manifest = await fetchReleaseManifest(getReleaseManifestUrl())
|
|
619
214
|
const update = getAvailableUpdate(manifest, {
|
|
@@ -622,19 +217,35 @@ async function checkForUpdates() {
|
|
|
622
217
|
arch,
|
|
623
218
|
})
|
|
624
219
|
|
|
625
|
-
if (!update)
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
220
|
+
if (!update) return
|
|
221
|
+
|
|
222
|
+
const sizeText = formatBytes(update.asset.size)
|
|
223
|
+
const detail = [
|
|
224
|
+
`当前版本:${app.getVersion()}`,
|
|
225
|
+
`最新版本:${update.version}`,
|
|
226
|
+
sizeText ? `安装包大小:${sizeText}` : null,
|
|
227
|
+
'',
|
|
228
|
+
'是否现在下载当前系统版本的安装包?下载完成后请手动打开安装。',
|
|
229
|
+
]
|
|
230
|
+
.filter(line => line !== null)
|
|
231
|
+
.join('\n')
|
|
232
|
+
|
|
233
|
+
const result = await dialog.showMessageBox(mainWindow, {
|
|
234
|
+
type: 'info',
|
|
235
|
+
buttons: ['立即下载', '稍后'],
|
|
236
|
+
defaultId: 0,
|
|
237
|
+
cancelId: 1,
|
|
238
|
+
title: '发现 MostBox 新版本',
|
|
239
|
+
message: `MostBox ${update.version} 已可下载`,
|
|
240
|
+
detail,
|
|
241
|
+
noLink: true,
|
|
242
|
+
})
|
|
629
243
|
|
|
630
|
-
|
|
244
|
+
if (result.response === 0) {
|
|
245
|
+
await shell.openExternal(update.downloadUrl)
|
|
246
|
+
}
|
|
631
247
|
} catch (error) {
|
|
632
|
-
console.warn('[Electron] Update check
|
|
633
|
-
setUpdateState({
|
|
634
|
-
status: 'error',
|
|
635
|
-
error: error?.message || 'Update check failed',
|
|
636
|
-
progress: 0,
|
|
637
|
-
})
|
|
248
|
+
console.warn('[Electron] Update check skipped:', error)
|
|
638
249
|
}
|
|
639
250
|
}
|
|
640
251
|
|
|
@@ -643,7 +254,6 @@ if (!gotSingleInstanceLock) {
|
|
|
643
254
|
app.quit()
|
|
644
255
|
} else {
|
|
645
256
|
registerMostProtocolClient()
|
|
646
|
-
registerUpdateIpc()
|
|
647
257
|
openMostDeepLink(findMostDeepLinkArg(process.argv))
|
|
648
258
|
|
|
649
259
|
app.on('second-instance', (_event, commandLine) => {
|
package/electron/preload.js
CHANGED
|
@@ -1,21 +1,6 @@
|
|
|
1
|
-
import { contextBridge
|
|
1
|
+
import { contextBridge } from 'electron'
|
|
2
2
|
|
|
3
3
|
contextBridge.exposeInMainWorld('electronAPI', {
|
|
4
4
|
platform: process.platform,
|
|
5
5
|
isElectron: true,
|
|
6
|
-
updates: {
|
|
7
|
-
getState: () => ipcRenderer.invoke('updates:get-state'),
|
|
8
|
-
installAndRestart: () => ipcRenderer.invoke('updates:install-and-restart'),
|
|
9
|
-
onStateChange: callback => {
|
|
10
|
-
if (typeof callback !== 'function') return () => {}
|
|
11
|
-
|
|
12
|
-
const listener = (_event, state) => {
|
|
13
|
-
callback(state)
|
|
14
|
-
}
|
|
15
|
-
ipcRenderer.on('updates:state', listener)
|
|
16
|
-
return () => {
|
|
17
|
-
ipcRenderer.off('updates:state', listener)
|
|
18
|
-
}
|
|
19
|
-
},
|
|
20
|
-
},
|
|
21
6
|
})
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getInstallerReleaseAssets,
|
|
3
|
+
isReleaseManifest,
|
|
4
|
+
resolveReleaseAssetDownload,
|
|
5
|
+
} from '../server/src/core/releaseManifest.js'
|
|
6
|
+
|
|
1
7
|
export const DEFAULT_RELEASE_MANIFEST_URL =
|
|
2
8
|
'https://download.most.box/releases/latest.json'
|
|
3
9
|
|
|
@@ -43,34 +49,11 @@ export function isNewerVersion(candidateVersion, currentVersion) {
|
|
|
43
49
|
return false
|
|
44
50
|
}
|
|
45
51
|
|
|
46
|
-
function isRecord(value) {
|
|
47
|
-
return typeof value === 'object' && value !== null
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
function hasDownloadUrl(asset) {
|
|
51
|
-
return typeof asset.r2Url === 'string' || typeof asset.githubUrl === 'string'
|
|
52
|
-
}
|
|
53
|
-
|
|
54
52
|
export function findUpdateAsset(manifest, platform, arch) {
|
|
55
|
-
if (!isRecord(manifest) || !Array.isArray(manifest.assets)) return null
|
|
56
|
-
|
|
57
|
-
const compatibleAssets = manifest.assets.filter(
|
|
58
|
-
asset =>
|
|
59
|
-
isRecord(asset) &&
|
|
60
|
-
asset.platform === platform &&
|
|
61
|
-
asset.arch === arch &&
|
|
62
|
-
(asset.kind === 'updater' || asset.kind === 'installer') &&
|
|
63
|
-
typeof asset.cid === 'string' &&
|
|
64
|
-
hasDownloadUrl(asset) &&
|
|
65
|
-
(typeof asset.githubUrl === 'string' ||
|
|
66
|
-
typeof asset.githubUrl === 'undefined') &&
|
|
67
|
-
(typeof asset.r2Url === 'string' || typeof asset.r2Url === 'undefined')
|
|
68
|
-
)
|
|
69
|
-
|
|
70
53
|
return (
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
null
|
|
54
|
+
getInstallerReleaseAssets(manifest).find(
|
|
55
|
+
asset => asset.platform === platform && asset.arch === arch
|
|
56
|
+
) || null
|
|
74
57
|
)
|
|
75
58
|
}
|
|
76
59
|
|
|
@@ -87,8 +70,7 @@ export function getAvailableUpdate(manifest, options = {}) {
|
|
|
87
70
|
const arch = options.arch
|
|
88
71
|
|
|
89
72
|
if (
|
|
90
|
-
!
|
|
91
|
-
typeof manifest.version !== 'string' ||
|
|
73
|
+
!isReleaseManifest(manifest) ||
|
|
92
74
|
!isNewerVersion(manifest.version, currentVersion)
|
|
93
75
|
) {
|
|
94
76
|
return null
|
|
@@ -97,12 +79,12 @@ export function getAvailableUpdate(manifest, options = {}) {
|
|
|
97
79
|
const asset = findUpdateAsset(manifest, platform, arch)
|
|
98
80
|
if (!asset) return null
|
|
99
81
|
|
|
100
|
-
const downloadUrl = asset
|
|
82
|
+
const { url: downloadUrl } = resolveReleaseAssetDownload(asset, 'r2')
|
|
83
|
+
if (!downloadUrl) return null
|
|
101
84
|
|
|
102
85
|
return {
|
|
103
86
|
version: manifest.version,
|
|
104
87
|
asset,
|
|
105
|
-
cid: asset.cid,
|
|
106
88
|
downloadUrl,
|
|
107
89
|
}
|
|
108
90
|
}
|