most-box 0.2.2 → 0.2.4

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.
Files changed (109) hide show
  1. package/README.md +3 -26
  2. package/electron/main.js +366 -30
  3. package/electron/preload.js +16 -1
  4. package/electron/updateChecker.js +18 -11
  5. package/electron/updateChecker.test.js +23 -7
  6. package/out/admin/index.html +0 -0
  7. package/out/app/index.html +0 -0
  8. package/out/assets/AppShell-gILTcRs-.js +1 -0
  9. package/out/assets/FilePreviewOverlay-CM-fcIaD.js +1 -0
  10. package/out/assets/LanguageToggle-B99eQcTE.js +1 -0
  11. package/out/assets/MarketingHeader-Q2cpkqAM.js +1 -0
  12. package/out/assets/MarketingLayout-X3Nj2JvA.js +1 -0
  13. package/out/assets/{MarketingThemeToggle-C6fggkl7.js → MarketingThemeToggle-z4jr4-vt.js} +1 -1
  14. package/out/assets/{MilkdownEditor-Cfze75zl.js → MilkdownEditor-Bv2VA3bu.js} +1 -1
  15. package/out/assets/{OpenSidebarButton-DDuALgJ2.js → OpenSidebarButton-CaQJk5LD.js} +1 -1
  16. package/out/assets/{SidebarAccount-bo1ypzrJ.js → SidebarAccount-CpNVqNva.js} +1 -1
  17. package/out/assets/arrow-right-mNO53HQi.js +1 -0
  18. package/out/assets/{circle-alert-BQoSDxUe.js → circle-alert-CofRTRHr.js} +1 -1
  19. package/out/assets/cloud-DAGFCFIp.js +1 -0
  20. package/out/assets/{copy-BtJHJnqH.js → copy-5aEv78Eq.js} +1 -1
  21. package/out/assets/{download-Bg-OdoxM.js → download-Bhqn6hL1.js} +1 -1
  22. package/out/assets/{downloadValidation-CUvbvj9f.js → downloadValidation-xzpa5rap.js} +1 -1
  23. package/out/assets/{external-link-m8ZIQe4p.js → external-link-CNR21yFA.js} +1 -1
  24. package/out/assets/{file-text-DG1orIkZ.js → file-text-CFfvvCnM.js} +1 -1
  25. package/out/assets/folder-BGJCjyba.js +1 -0
  26. package/out/assets/{hard-drive-BB-sllXA.js → hard-drive-4vHpA5kz.js} +1 -1
  27. package/out/assets/index-CgErXULe.css +1 -0
  28. package/out/assets/index-DN0kD5zr.js +31 -0
  29. package/out/assets/{index.lazy-BTndBeBF.js → index.lazy-0DsJzEoI.js} +1 -1
  30. package/out/assets/{index.lazy-DfsgyiMN.js → index.lazy-CJBRtbWA.js} +1 -1
  31. package/out/assets/{index.lazy-DejOniAm.js → index.lazy-CPV-dZxo.js} +1 -1
  32. package/out/assets/{index.lazy-DVOcrcEB.js → index.lazy-CQavt0-9.js} +2 -2
  33. package/out/assets/index.lazy-CUq9lbK_.js +1 -0
  34. package/out/assets/index.lazy-CxpNXlwa.js +1 -0
  35. package/out/assets/index.lazy-DOgKxDuF.js +1 -0
  36. package/out/assets/index.lazy-Dzf-q-Vf.js +1 -0
  37. package/out/assets/index.lazy-GtegOPkJ.js +2 -0
  38. package/out/assets/index.lazy-H7NATHUs.js +1 -0
  39. package/out/assets/index.lazy-b_N8nrYw.js +1 -0
  40. package/out/assets/index.lazy-erexqght.js +3 -0
  41. package/out/assets/index.lazy-s3nUpCQ1.js +1 -0
  42. package/out/assets/{key-round-DIQ3Xt5F.js → key-round-BpSdpOHi.js} +1 -1
  43. package/out/assets/loader-ByMmV2_4.js +1 -0
  44. package/out/assets/{lock-tf1t2yuy.js → lock-C0pdaery.js} +1 -1
  45. package/out/assets/{message-square-DaQH7q-P.js → message-square-Eu86zZup.js} +1 -1
  46. package/out/assets/{mp-DvFTsIL9.js → mp-DjXDNjls.js} +1 -1
  47. package/out/assets/{music-DaFvU2DL.js → music-Btrhub6z.js} +1 -1
  48. package/out/assets/{notebook-pen-CqEFOHKx.js → notebook-pen-CfdFgaDE.js} +1 -1
  49. package/out/assets/play-D5r9Yvbq.js +1 -0
  50. package/out/assets/plus-Mlh7r_bm.js +1 -0
  51. package/out/assets/{refresh-cw-roxAhABl.js → refresh-cw--JB8k6mH.js} +1 -1
  52. package/out/assets/{save-DR0O9ReR.js → save-DN4eprSt.js} +1 -1
  53. package/out/assets/search-CbzMyENy.js +1 -0
  54. package/out/assets/{send-m1XCcuPn.js → send-D8p6f8OD.js} +1 -1
  55. package/out/assets/{shield-check-BwcvTU4U.js → shield-check-c0labdRH.js} +1 -1
  56. package/out/assets/{trash-2-CNpsqYc1.js → trash-2-zdhWec_U.js} +1 -1
  57. package/out/assets/{triangle-alert-DP9EP7IM.js → triangle-alert-D-qigSSn.js} +1 -1
  58. package/out/assets/upload-BD23c5Pz.js +1 -0
  59. package/out/assets/{useChannelMessages-46C52EyL.js → useChannelMessages-DS1ntAKN.js} +2 -2
  60. package/out/assets/useGameRoom-BSPkCY3n.js +1 -0
  61. package/out/assets/userProfile-BmzBA_Bk.js +1 -0
  62. package/out/assets/{userStore-C4vdYsQp.js → userStore-q0BBZsIV.js} +1 -1
  63. package/out/assets/{wallet-CozFU6yK.js → wallet-DmZMdK9A.js} +1 -1
  64. package/out/assets/{wifi-DIR3g_8A.js → wifi-CVqaPDcv.js} +1 -1
  65. package/out/chat/index.html +0 -0
  66. package/out/chat/join/index.html +0 -0
  67. package/out/download/index.html +2 -2
  68. package/out/game/gandengyan/index.html +0 -0
  69. package/out/game/index.html +0 -0
  70. package/out/game/zhajinhua/index.html +0 -0
  71. package/out/index.html +2 -2
  72. package/out/note/index.html +0 -0
  73. package/out/ping/index.html +2 -2
  74. package/out/profile/index.html +0 -0
  75. package/out/web3/index.html +0 -0
  76. package/package.json +8 -1
  77. package/server/index.js +2 -2
  78. package/server/src/core/syncTimestamp.js +10 -0
  79. package/server/src/http/nodeStatus.js +16 -16
  80. package/server/src/http/routePolicy.js +2 -2
  81. package/server/src/http/routes/nodeRoutes.js +21 -19
  82. package/server/src/index.js +578 -960
  83. package/server/src/utils/accountBackup.js +134 -0
  84. package/server/src/utils/api.js +27 -1
  85. package/out/assets/AppShell-BOtfY12t.js +0 -1
  86. package/out/assets/FilePreviewOverlay-C5qK9HAE.js +0 -1
  87. package/out/assets/LanguageToggle-Di5b88mK.js +0 -1
  88. package/out/assets/MarketingHeader-BOytKcCc.js +0 -1
  89. package/out/assets/MarketingLayout-B8m1Q7Pa.js +0 -1
  90. package/out/assets/arrow-right-RldN7Rwi.js +0 -1
  91. package/out/assets/cloud-DHoTMeZY.js +0 -1
  92. package/out/assets/index-C0xqKeu-.css +0 -1
  93. package/out/assets/index-CrAXrmfP.js +0 -31
  94. package/out/assets/index.lazy-0Njp0U6I.js +0 -1
  95. package/out/assets/index.lazy-Bq39jYTl.js +0 -1
  96. package/out/assets/index.lazy-Bu93oGzJ.js +0 -2
  97. package/out/assets/index.lazy-BvdBwgHx.js +0 -1
  98. package/out/assets/index.lazy-CIx8ist6.js +0 -3
  99. package/out/assets/index.lazy-DK7R297q.js +0 -1
  100. package/out/assets/index.lazy-DpNIiSXF.js +0 -1
  101. package/out/assets/index.lazy-Vnvz-t7T.js +0 -1
  102. package/out/assets/index.lazy-v7nBJ-sF.js +0 -1
  103. package/out/assets/play-B7q6F75-.js +0 -1
  104. package/out/assets/plus-H2i2mspM.js +0 -1
  105. package/out/assets/search-ncblG-zw.js +0 -1
  106. package/out/assets/upload-V--8p13l.js +0 -1
  107. package/out/assets/useGameRoom-BtxPpfck.js +0 -1
  108. package/server/src/core/userSyncKeys.js +0 -36
  109. package/server/src/utils/noteBackup.js +0 -116
package/README.md CHANGED
@@ -251,29 +251,6 @@ npx most-box@latest
251
251
 
252
252
  在线 Web 入口只能连接已有节点;它本身不提供本机发布、下载校验或持续做种能力。
253
253
 
254
- ## 路线图
255
-
256
- ### v0.2.1(当前版本)
257
-
258
- - ✅ P2P 文件上传与下载
259
- - ✅ 确定性 CID 生成
260
- - ✅ 大文件流式传输
261
- - ✅ most:// 链接分享
262
- - ✅ Web UI 界面
263
- - ✅ P2P 频道聊天
264
- - ✅ 网络连通性测试
265
- - ✅ Electron 桌面客户端
266
-
267
- ### 长期规划
268
-
269
- - [ ] 下载体验优化 - 检测是否可下载
270
- - [ ] P2P 多人视频/语音通话
271
- - 基于 WebRTC 的端到端加密通话
272
- - 频道内一键发起通话
273
- - 屏幕共享与文字聊天
274
- - [ ] 浏览器扩展
275
- - [ ] 移动端支持(iOS/Android)
276
-
277
254
  ## 技术栈
278
255
 
279
256
  - **前端**: React 19, Vite, TanStack Start static prerender, TanStack Router, TypeScript, Zustand, Lucide React
@@ -287,15 +264,15 @@ npx most-box@latest
287
264
  发布新版本时,推送 tag 即可自动构建:
288
265
 
289
266
  ```bash
290
- git tag v0.2.1
291
- git push origin v0.2.1
267
+ git tag vx.x.x
268
+ git push origin vx.x.x
292
269
  ```
293
270
 
294
271
  触发后自动执行:
295
272
 
296
273
  1. **npm 包发布** — 发布 `most-box` 到 npm registry
297
274
  2. **Windows 打包** — 分别构建 `.exe` 安装包(x64 / arm64)并上传 Release
298
- 3. **macOS 打包** — 构建 `.dmg` 安装包(x64 + arm64)并上传 Release
275
+ 3. **macOS 打包** — 构建 `.dmg` 安装包和 `.zip` 更新包(x64 + arm64)并上传 Release
299
276
  4. **Linux 打包** — 构建 `.AppImage` 安装包(x64 + arm64)并上传 Release
300
277
  5. **下载镜像** — 将 Release 资产同步到 Cloudflare R2,并生成 `releases/latest.json`
301
278
 
package/electron/main.js CHANGED
@@ -3,16 +3,17 @@ import {
3
3
  BrowserWindow,
4
4
  Menu,
5
5
  Tray,
6
- dialog,
6
+ ipcMain,
7
7
  nativeImage,
8
8
  shell,
9
9
  } from 'electron'
10
+ import { spawn } from 'node:child_process'
10
11
  import fs from 'node:fs'
11
12
  import path from 'node:path'
13
+ import { Readable } from 'node:stream'
12
14
  import { fileURLToPath } from 'node:url'
13
15
 
14
16
  import {
15
- formatBytes,
16
17
  getAvailableUpdate,
17
18
  getCurrentArch,
18
19
  getCurrentPlatform,
@@ -22,6 +23,7 @@ import {
22
23
  createMostDeepLinkTarget,
23
24
  findMostDeepLinkArg,
24
25
  } from './deepLink.js'
26
+ import { calculateCid } from '../server/src/core/cid.js'
25
27
 
26
28
  const __dirname = path.dirname(fileURLToPath(import.meta.url))
27
29
  const PORT = 1976
@@ -32,6 +34,16 @@ let tray = null
32
34
  let isQuitting = false
33
35
  let hasCheckedForUpdates = false
34
36
  let pendingDeepLinkUrl = ''
37
+ let updateState = {
38
+ status: 'idle',
39
+ version: '',
40
+ filename: '',
41
+ source: '',
42
+ progress: 0,
43
+ error: '',
44
+ path: '',
45
+ cid: '',
46
+ }
35
47
 
36
48
  const gotSingleInstanceLock = app.requestSingleInstanceLock()
37
49
 
@@ -89,6 +101,57 @@ function getInitialWindowUrl() {
89
101
  return initialUrl
90
102
  }
91
103
 
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
+
92
155
  function registerMostProtocolClient() {
93
156
  if (process.defaultApp && process.argv.length >= 2) {
94
157
  app.setAsDefaultProtocolClient('most', process.execPath, [
@@ -100,6 +163,14 @@ function registerMostProtocolClient() {
100
163
  app.setAsDefaultProtocolClient('most')
101
164
  }
102
165
 
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
+
103
174
  function openMostDeepLink(link) {
104
175
  const targetUrl = createMostDeepLinkTarget(
105
176
  link,
@@ -201,14 +272,294 @@ async function fetchReleaseManifest(manifestUrl) {
201
272
  }
202
273
  }
203
274
 
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 installWindowsUpdate(updatePath) {
438
+ const child = spawn(updatePath, ['/S'], {
439
+ detached: true,
440
+ stdio: 'ignore',
441
+ })
442
+ child.unref()
443
+ quitForUpdate()
444
+ }
445
+
446
+ function getMacAppBundlePath() {
447
+ return path.resolve(app.getPath('exe'), '../../..')
448
+ }
449
+
450
+ function installMacUpdate(updatePath) {
451
+ const scriptPath = path.join(app.getPath('userData'), 'updates', 'install-mac.sh')
452
+ const appBundlePath = getMacAppBundlePath()
453
+ const script = `#!/bin/bash
454
+ set -e
455
+ APP_PATH="$1"
456
+ ZIP_PATH="$2"
457
+ TMP_DIR="$(mktemp -d)"
458
+ cleanup() {
459
+ rm -rf "$TMP_DIR"
460
+ }
461
+ trap cleanup EXIT
462
+ while pgrep -f "$APP_PATH/Contents/MacOS" >/dev/null 2>&1; do
463
+ sleep 1
464
+ done
465
+ ditto -x -k "$ZIP_PATH" "$TMP_DIR"
466
+ NEW_APP="$(find "$TMP_DIR" -maxdepth 1 -name "*.app" -type d | head -n 1)"
467
+ if [ -z "$NEW_APP" ]; then
468
+ exit 1
469
+ fi
470
+ rm -rf "$APP_PATH"
471
+ ditto "$NEW_APP" "$APP_PATH"
472
+ open "$APP_PATH"
473
+ `
474
+
475
+ fs.mkdirSync(path.dirname(scriptPath), { recursive: true })
476
+ fs.writeFileSync(scriptPath, script, { mode: 0o755 })
477
+ const child = spawn('/bin/bash', [scriptPath, appBundlePath, updatePath], {
478
+ detached: true,
479
+ stdio: 'ignore',
480
+ })
481
+ child.unref()
482
+ quitForUpdate()
483
+ }
484
+
485
+ function installLinuxUpdate(updatePath) {
486
+ const currentAppImage = process.env.APPIMAGE
487
+ if (!currentAppImage) {
488
+ shell.openPath(updatePath).catch(() => {})
489
+ throw new Error('当前 Linux 运行方式不支持自动替换 AppImage')
490
+ }
491
+
492
+ const scriptPath = path.join(app.getPath('userData'), 'updates', 'install-linux.sh')
493
+ const script = `#!/bin/sh
494
+ set -e
495
+ APPIMAGE_PATH="$1"
496
+ UPDATE_PATH="$2"
497
+ APP_PID="$3"
498
+ while kill -0 "$APP_PID" >/dev/null 2>&1; do
499
+ sleep 1
500
+ done
501
+ cp "$UPDATE_PATH" "$APPIMAGE_PATH"
502
+ chmod +x "$APPIMAGE_PATH"
503
+ nohup "$APPIMAGE_PATH" >/dev/null 2>&1 &
504
+ `
505
+
506
+ fs.mkdirSync(path.dirname(scriptPath), { recursive: true })
507
+ fs.writeFileSync(scriptPath, script, { mode: 0o755 })
508
+ const child = spawn('/bin/sh', [scriptPath, currentAppImage, updatePath, String(process.pid)], {
509
+ detached: true,
510
+ stdio: 'ignore',
511
+ })
512
+ child.unref()
513
+ quitForUpdate()
514
+ }
515
+
516
+ async function installDownloadedUpdate() {
517
+ if (updateState.status !== 'downloaded' || !updateState.path) {
518
+ throw new Error('No downloaded update is ready to install')
519
+ }
520
+
521
+ setUpdateState({ status: 'installing', error: '' })
522
+
523
+ try {
524
+ if (process.platform === 'win32') {
525
+ installWindowsUpdate(updateState.path)
526
+ return
527
+ }
528
+ if (process.platform === 'darwin') {
529
+ installMacUpdate(updateState.path)
530
+ return
531
+ }
532
+ if (process.platform === 'linux') {
533
+ installLinuxUpdate(updateState.path)
534
+ return
535
+ }
536
+
537
+ throw new Error('当前平台不支持自动安装更新')
538
+ } catch (error) {
539
+ setUpdateState({
540
+ status: 'error',
541
+ error: error?.message || 'Update install failed',
542
+ })
543
+ throw error
544
+ }
545
+ }
546
+
204
547
  async function checkForUpdates() {
205
548
  if (hasCheckedForUpdates) return
206
549
  hasCheckedForUpdates = true
207
550
 
208
551
  try {
552
+ setUpdateState({
553
+ status: 'checking',
554
+ progress: 0,
555
+ error: '',
556
+ })
209
557
  const platform = getCurrentPlatform()
210
558
  const arch = getCurrentArch()
211
- if (!platform || !arch) return
559
+ if (!platform || !arch) {
560
+ setUpdateState({ status: 'idle' })
561
+ return
562
+ }
212
563
 
213
564
  const manifest = await fetchReleaseManifest(getReleaseManifestUrl())
214
565
  const update = getAvailableUpdate(manifest, {
@@ -217,35 +568,19 @@ async function checkForUpdates() {
217
568
  arch,
218
569
  })
219
570
 
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
- })
243
-
244
- if (result.response === 0) {
245
- await shell.openExternal(update.downloadUrl)
571
+ if (!update) {
572
+ setUpdateState({ status: 'idle' })
573
+ return
246
574
  }
575
+
576
+ await downloadUpdate(update)
247
577
  } catch (error) {
248
- console.warn('[Electron] Update check skipped:', error)
578
+ console.warn('[Electron] Update check failed:', error)
579
+ setUpdateState({
580
+ status: 'error',
581
+ error: error?.message || 'Update check failed',
582
+ progress: 0,
583
+ })
249
584
  }
250
585
  }
251
586
 
@@ -254,6 +589,7 @@ if (!gotSingleInstanceLock) {
254
589
  app.quit()
255
590
  } else {
256
591
  registerMostProtocolClient()
592
+ registerUpdateIpc()
257
593
  openMostDeepLink(findMostDeepLinkArg(process.argv))
258
594
 
259
595
  app.on('second-instance', (_event, commandLine) => {
@@ -1,6 +1,21 @@
1
- import { contextBridge } from 'electron'
1
+ import { contextBridge, ipcRenderer } 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
+ },
6
21
  })
@@ -50,16 +50,23 @@ function isRecord(value) {
50
50
  export function findUpdateAsset(manifest, platform, arch) {
51
51
  if (!isRecord(manifest) || !Array.isArray(manifest.assets)) return null
52
52
 
53
+ const compatibleAssets = manifest.assets.filter(
54
+ asset =>
55
+ isRecord(asset) &&
56
+ asset.platform === platform &&
57
+ asset.arch === arch &&
58
+ (asset.kind === 'updater' || asset.kind === 'installer') &&
59
+ typeof asset.cid === 'string' &&
60
+ (typeof asset.githubUrl === 'string' ||
61
+ typeof asset.r2Url === 'string' ||
62
+ typeof asset.githubUrl === 'undefined') &&
63
+ (typeof asset.r2Url === 'string' || typeof asset.r2Url === 'undefined')
64
+ )
65
+
53
66
  return (
54
- manifest.assets.find(
55
- asset =>
56
- isRecord(asset) &&
57
- asset.platform === platform &&
58
- asset.arch === arch &&
59
- asset.kind === 'installer' &&
60
- typeof asset.githubUrl === 'string' &&
61
- (typeof asset.r2Url === 'string' || typeof asset.r2Url === 'undefined')
62
- ) || null
67
+ compatibleAssets.find(asset => asset.kind === 'updater') ||
68
+ compatibleAssets.find(asset => asset.kind === 'installer') ||
69
+ null
63
70
  )
64
71
  }
65
72
 
@@ -86,12 +93,12 @@ export function getAvailableUpdate(manifest, options = {}) {
86
93
  const asset = findUpdateAsset(manifest, platform, arch)
87
94
  if (!asset) return null
88
95
 
89
- const downloadUrl = asset.r2Url || asset.githubUrl
90
- if (!downloadUrl) return null
96
+ const downloadUrl = asset.r2Url || asset.githubUrl || ''
91
97
 
92
98
  return {
93
99
  version: manifest.version,
94
100
  asset,
101
+ cid: asset.cid,
95
102
  downloadUrl,
96
103
  }
97
104
  }
@@ -21,7 +21,19 @@ const manifest = {
21
21
  kind: 'installer',
22
22
  filename: 'MostBox-0.1.3-win-x64-setup.exe',
23
23
  size: 112197632,
24
- sha256: 'hash',
24
+ cid: 'bafkreifzjut3te2nhyekklss27nh3k72ysco7y32koao5eei66wof36n5e',
25
+ r2Url:
26
+ 'https://download.most.box/releases/v0.1.3/MostBox-0.1.3-win-x64-setup.exe',
27
+ githubUrl:
28
+ 'https://github.com/most-people/most/releases/download/v0.1.3/MostBox-0.1.3-win-x64-setup.exe',
29
+ },
30
+ {
31
+ platform: 'windows',
32
+ arch: 'x64',
33
+ kind: 'updater',
34
+ filename: 'MostBox-0.1.3-win-x64-setup.exe',
35
+ size: 112197632,
36
+ cid: 'bafkreifzjut3te2nhyekklss27nh3k72ysco7y32koao5eei66wof36n5e',
25
37
  r2Url:
26
38
  'https://download.most.box/releases/v0.1.3/MostBox-0.1.3-win-x64-setup.exe',
27
39
  githubUrl:
@@ -30,18 +42,20 @@ const manifest = {
30
42
  {
31
43
  platform: 'windows',
32
44
  arch: 'arm64',
33
- kind: 'installer',
45
+ kind: 'updater',
34
46
  filename: 'MostBox-0.1.3-win-arm64-setup.exe',
47
+ cid: 'bafkreidsxk7x7eekhalvskbqtqnucird2p4eq3riz6ijmrswzm4miwq74m',
35
48
  githubUrl:
36
49
  'https://github.com/most-people/most/releases/download/v0.1.3/MostBox-0.1.3-win-arm64-setup.exe',
37
50
  },
38
51
  {
39
52
  platform: 'macos',
40
53
  arch: 'arm64',
41
- kind: 'installer',
42
- filename: 'MostBox-0.1.3-mac-arm64.dmg',
54
+ kind: 'updater',
55
+ filename: 'MostBox-0.1.3-mac-arm64.zip',
56
+ cid: 'bafkreicjs6qwdk7xuclzrkch7gx4vy3zc7y5dxrg4i764py54nk5kekbsm',
43
57
  githubUrl:
44
- 'https://github.com/most-people/most/releases/download/v0.1.3/MostBox-0.1.3-mac-arm64.dmg',
58
+ 'https://github.com/most-people/most/releases/download/v0.1.3/MostBox-0.1.3-mac-arm64.zip',
45
59
  },
46
60
  ],
47
61
  }
@@ -86,6 +100,7 @@ describe('desktop update checker', () => {
86
100
  findUpdateAsset(manifest, 'windows', 'x64')?.filename,
87
101
  'MostBox-0.1.3-win-x64-setup.exe'
88
102
  )
103
+ assert.equal(findUpdateAsset(manifest, 'windows', 'x64')?.kind, 'updater')
89
104
  assert.equal(
90
105
  findUpdateAsset(manifest, 'windows', 'arm64')?.filename,
91
106
  'MostBox-0.1.3-win-arm64-setup.exe'
@@ -101,7 +116,8 @@ describe('desktop update checker', () => {
101
116
  })
102
117
 
103
118
  assert.equal(update?.version, '0.1.3')
104
- assert.equal(update?.downloadUrl, manifest.assets[0].r2Url)
119
+ assert.equal(update?.cid, manifest.assets[1].cid)
120
+ assert.equal(update?.downloadUrl, manifest.assets[1].r2Url)
105
121
 
106
122
  assert.equal(
107
123
  getAvailableUpdate(manifest, {
@@ -136,7 +152,7 @@ describe('desktop update checker', () => {
136
152
  arch: 'arm64',
137
153
  })
138
154
 
139
- assert.equal(update?.downloadUrl, manifest.assets[1].githubUrl)
155
+ assert.equal(update?.downloadUrl, manifest.assets[2].githubUrl)
140
156
  })
141
157
 
142
158
  it('formats installer sizes for update prompts', () => {
Binary file
Binary file
@@ -0,0 +1 @@
1
+ import{n as e,o as t,t as n}from"./jsx-runtime-Bt-cYkS5.js";import{g as r}from"./userStore-q0BBZsIV.js";import{n as i,t as a}from"./LanguageToggle-B99eQcTE.js";import{I as o}from"./index-DN0kD5zr.js";var s=r(`menu`,[[`path`,{d:`M4 5h16`,key:`1tepv9`}],[`path`,{d:`M4 12h16`,key:`1lakjw`}],[`path`,{d:`M4 19h16`,key:`1djgab`}]]),c=t(e(),1);function l(e,t){return typeof t==`boolean`?t:typeof window<`u`&&`matchMedia`in window?window.matchMedia(e).matches:!1}function u(e,t,{getInitialValueInEffect:n}={getInitialValueInEffect:!0}){let[r,i]=(0,c.useState)(n?t:l(e));return(0,c.useEffect)(()=>{try{if(`matchMedia`in window){let t=window.matchMedia(e);i(t.matches);let n=e=>i(e.matches);return t.addEventListener(`change`,n),()=>{t.removeEventListener(`change`,n)}}}catch{return}},[e]),r||!1}function d(e=!1,t={}){let[n,r]=(0,c.useState)(e),i=(0,c.useCallback)(()=>{r(e=>e||(t.onOpen?.(),!0))},[t.onOpen]),a=(0,c.useCallback)(()=>{r(e=>e&&(t.onClose?.(),!1))},[t.onClose]);return[n,{open:i,close:a,toggle:(0,c.useCallback)(()=>{n?a():i()},[a,i,n]),set:r}]}var f=n(),p=(0,c.createContext)(null);function m(){let e=(0,c.useContext)(p);if(!e)throw Error(`useAppShell must be used within AppShell`);return e}function h({sidebar:e,className:t=``,headerTitle:n,headerRight:r,sidebarToggleReplacement:l,defaultHide:m=!1,children:h}){let[g,_]=d(!1),[v,y]=(0,c.useState)(m),b=(0,c.useRef)(m),x=u(`(max-width: 768px)`),{t:S}=o(),C=(0,c.useCallback)((e={})=>{_.close(),e.collapse&&y(!0)},[_]);(0,c.useEffect)(()=>{m!==b.current&&(m?C({collapse:!0}):y(!1),b.current=m)},[m,C]);let w=()=>{x?_.toggle():y(!v)},T=()=>{x?_.open():y(!1)},E=x?g:!v;return(0,f.jsx)(p.Provider,{value:{closeSidebar:C,openSidebar:T,isSidebarVisible:E},children:(0,f.jsxs)(`div`,{className:[`app-layout`,t].filter(Boolean).join(` `),children:[(0,f.jsx)(`div`,{className:`sidebar-overlay ${g?`visible`:``}`,onClick:()=>C()}),(0,f.jsx)(`div`,{className:`sidebar ${g?`open`:``} ${v?`collapsed`:``}`,children:e({closeSidebar:C,openSidebar:T})}),(0,f.jsxs)(`div`,{className:`main-content`,children:[(0,f.jsxs)(`header`,{className:`app-header`,children:[(0,f.jsxs)(`div`,{className:`header-left`,children:[l??(0,f.jsx)(`button`,{onClick:w,className:`btn btn-icon sidebar-toggle-btn`,"aria-label":S(x?`appShell.openMenu`:v?`appShell.expandSidebar`:`appShell.collapseSidebar`),children:(0,f.jsx)(s,{size:16})}),n]}),(0,f.jsxs)(`div`,{className:`header-right`,children:[(0,f.jsx)(i,{}),(0,f.jsx)(a,{className:`btn btn-icon app-language-toggle`}),r]})]}),h]})]})})}export{m as n,d as r,h as t};
@@ -0,0 +1 @@
1
+ import{n as e,o as t,t as n}from"./jsx-runtime-Bt-cYkS5.js";import{g as r}from"./userStore-q0BBZsIV.js";import{t as i}from"./file-text-CFfvvCnM.js";import{t as a}from"./loader-ByMmV2_4.js";import{t as o}from"./music-Btrhub6z.js";import{I as s,b as c,k as l}from"./index-DN0kD5zr.js";var u=r(`film`,[[`rect`,{width:`18`,height:`18`,x:`3`,y:`3`,rx:`2`,key:`afitv7`}],[`path`,{d:`M7 3v18`,key:`bbkbws`}],[`path`,{d:`M3 7.5h4`,key:`zfgn84`}],[`path`,{d:`M3 12h18`,key:`1i2n21`}],[`path`,{d:`M3 16.5h4`,key:`1230mu`}],[`path`,{d:`M17 3v18`,key:`in4fa5`}],[`path`,{d:`M17 7.5h4`,key:`myr1c1`}],[`path`,{d:`M17 16.5h4`,key:`go4c1d`}]]),d=r(`image`,[[`rect`,{width:`18`,height:`18`,x:`3`,y:`3`,rx:`2`,ry:`2`,key:`1m3agn`}],[`circle`,{cx:`9`,cy:`9`,r:`2`,key:`af1f0g`}],[`path`,{d:`m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21`,key:`1xmnt7`}]]),f=r(`pen`,[[`path`,{d:`M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z`,key:`1a8usu`}]]);function p(e){let t=String(e||``).split(`.`).pop()?.toLowerCase()||``,n=[`jpg`,`jpeg`,`png`,`gif`,`webp`,`svg`,`bmp`,`ico`,`tiff`,`heic`,`heif`],r=[`mp4`,`webm`,`mov`,`avi`,`mkv`,`flv`,`wmv`,`m4v`,`mpeg`,`3gp`],i=[`mp3`,`wav`,`ogg`,`flac`,`aac`,`m4a`,`wma`,`opus`],a=`txt.md.js.ts.jsx.tsx.css.scss.less.json.xml.html.htm.yaml.yml.toml.ini.cfg.conf.log.sh.bash.py.rb.go.rs.java.c.cpp.h.hpp.cs.php.sql.graphql.env.gitignore.dockerfile.readme`.split(`.`);return n.includes(t)?`image`:r.includes(t)?`video`:i.includes(t)?`audio`:a.includes(t)?`text`:`file`}var m=t(e(),1),h=n();function g(e){return e===`image`||e===`video`||e===`audio`}function _({item:e,isBackendReady:t,getFileDownloadUrl:n,onClose:r}){let{t:u}=s(),[d,f]=(0,m.useState)(``),[p,_]=(0,m.useState)(!1),[v,y]=(0,m.useState)(``),[b,x]=(0,m.useState)(``);return(0,m.useEffect)(()=>{if(e.subtype!==`text`)return;let r=!1;if(f(``),x(``),!t){f(u(`preview.loadFailed`));return}return _(!0),(async()=>{try{let t=await fetch(n(e.cid),{headers:{...await c(`GET`,`/api/files/${e.cid}/download`),Range:`bytes=0-9999`}});if(!t.ok)throw Error(u(`preview.loadFailed`));let i=await t.text();r||f(i||u(`preview.emptyFile`))}catch{r||f(u(`preview.loadFailed`))}finally{r||_(!1)}})(),()=>{r=!0}},[n,t,e.cid,e.subtype]),(0,m.useEffect)(()=>{if(!g(e.subtype)){y(``);return}if(y(``),x(``),!t){x(u(`preview.loadFailed`));return}let r=``,i=!1;return(async()=>{try{let t=await fetch(n(e.cid),{headers:await c(`GET`,`/api/files/${e.cid}/download`)});if(!t.ok)throw Error(u(`preview.loadFailed`));let a=URL.createObjectURL(await t.blob());r=a,i||y(a)}catch{i||x(u(`preview.loadFailed`))}})(),()=>{i=!0,r&&URL.revokeObjectURL(r)}},[n,t,e.cid,e.subtype]),(0,h.jsxs)(`div`,{className:`preview-overlay`,onClick:r,children:[(0,h.jsx)(`button`,{type:`button`,className:`preview-close`,onClick:r,"aria-label":u(`preview.close`),children:(0,h.jsx)(l,{size:20})}),(0,h.jsxs)(`div`,{onClick:e=>e.stopPropagation(),children:[e.subtype===`image`&&(0,h.jsx)(`div`,{className:`preview-media-wrapper`,children:v?(0,h.jsx)(`img`,{src:v,alt:e.fileName,translate:`no`}):b?(0,h.jsxs)(`div`,{className:`preview-unsupported`,children:[(0,h.jsx)(i,{size:48,className:`preview-file-icon`}),(0,h.jsx)(`p`,{translate:`no`,children:e.fileName}),(0,h.jsx)(`p`,{className:`preview-unsupported-hint`,children:b})]}):(0,h.jsx)(`div`,{className:`preview-loading`,children:(0,h.jsx)(`div`,{className:`preview-loading-spinner`})})}),e.subtype===`video`&&(0,h.jsx)(`div`,{className:`preview-media-wrapper`,children:v?(0,h.jsx)(`video`,{src:v,controls:!0}):b?(0,h.jsxs)(`div`,{className:`preview-unsupported`,children:[(0,h.jsx)(i,{size:48,className:`preview-file-icon`}),(0,h.jsx)(`p`,{translate:`no`,children:e.fileName}),(0,h.jsx)(`p`,{className:`preview-unsupported-hint`,children:b})]}):(0,h.jsx)(`div`,{className:`preview-loading`,children:(0,h.jsx)(`div`,{className:`preview-loading-spinner`})})}),e.subtype===`audio`&&(0,h.jsxs)(`div`,{className:`preview-audio`,children:[(0,h.jsx)(`div`,{className:`preview-audio-icon`,children:(0,h.jsx)(o,{size:36,color:`var(--accent)`})}),(0,h.jsx)(`p`,{className:`preview-audio-filename`,translate:`no`,children:e.fileName}),v?(0,h.jsx)(`audio`,{className:`preview-audio-player`,src:v,controls:!0}):(0,h.jsx)(`div`,{className:`preview-text-loading`,children:b?(0,h.jsx)(`p`,{children:b}):(0,h.jsxs)(h.Fragment,{children:[(0,h.jsx)(a,{size:24,className:`preview-text-spinner`}),(0,h.jsx)(`p`,{children:u(`preview.audioLoading`)})]})})]}),e.subtype===`file`&&(0,h.jsxs)(`div`,{className:`preview-unsupported`,children:[(0,h.jsx)(i,{size:48,className:`preview-file-icon`}),(0,h.jsx)(`p`,{translate:`no`,children:e.fileName}),(0,h.jsx)(`p`,{className:`preview-unsupported-hint`,children:u(`preview.unsupported`)})]}),e.subtype===`text`&&(0,h.jsxs)(`div`,{className:`preview-text-container`,children:[(0,h.jsx)(`div`,{className:`preview-text-header`,children:(0,h.jsx)(`span`,{translate:`no`,children:e.fileName})}),p?(0,h.jsxs)(`div`,{className:`preview-text-loading`,children:[(0,h.jsx)(a,{size:24,className:`preview-text-spinner`}),(0,h.jsx)(`p`,{children:u(`preview.textLoading`)}),(0,h.jsx)(`p`,{className:`preview-text-loading-hint`,children:u(`preview.firstSyncHint`)})]}):(0,h.jsx)(`pre`,{className:`preview-text`,translate:`no`,children:d||u(`preview.emptyFile`)})]})]})]})}export{u as a,d as i,p as n,f as r,_ as t};
@@ -0,0 +1 @@
1
+ import{t as e}from"./jsx-runtime-Bt-cYkS5.js";import{g as t}from"./userStore-q0BBZsIV.js";import{t as n}from"./refresh-cw--JB8k6mH.js";import{I as r,L as i,R as a,c as o,p as s}from"./index-DN0kD5zr.js";var c=t(`check`,[[`path`,{d:`M20 6 9 17l-5-5`,key:`1gmf2c`}]]),l=t(`languages`,[[`path`,{d:`m5 8 6 6`,key:`1wu5hv`}],[`path`,{d:`m4 14 6-6 2-3`,key:`1k1g8d`}],[`path`,{d:`M2 5h12`,key:`or177f`}],[`path`,{d:`M7 2h1`,key:`1t2jsx`}],[`path`,{d:`m22 22-5-10-5 10`,key:`don7ne`}],[`path`,{d:`M14 18h6`,key:`1m8k6r`}]]),u=t(`moon`,[[`path`,{d:`M20.985 12.486a9 9 0 1 1-9.473-9.472c.405-.022.617.46.402.803a6 6 0 0 0 8.268 8.268c.344-.215.825-.004.803.401`,key:`kfwtm`}]]),d=t(`sun`,[[`circle`,{cx:`12`,cy:`12`,r:`4`,key:`4exip2`}],[`path`,{d:`M12 2v2`,key:`tus03m`}],[`path`,{d:`M12 20v2`,key:`1lh1kg`}],[`path`,{d:`m4.93 4.93 1.41 1.41`,key:`149t6j`}],[`path`,{d:`m17.66 17.66 1.41 1.41`,key:`ptbguv`}],[`path`,{d:`M2 12h2`,key:`1t8f8n`}],[`path`,{d:`M20 12h2`,key:`1q8mjw`}],[`path`,{d:`m6.34 17.66-1.41 1.41`,key:`1m8zz5`}],[`path`,{d:`m19.07 4.93-1.41 1.41`,key:`1shlcs`}]]),f=e();function p(){let{t:e}=r(),{state:t,installAndRestart:i}=s(),a=t.status===`installing`;return t.status!==`downloaded`&&!a?null:(0,f.jsxs)(`button`,{type:`button`,className:`btn btn-primary desktop-update-button`,disabled:a,title:e(`desktopUpdate.readyTitle`,{version:t.version||t.filename}),onClick:()=>{i()},children:[(0,f.jsx)(n,{size:16}),e(a?`desktopUpdate.installing`:`desktopUpdate.install`)]})}function m({className:e=``}){let{locale:t,localeName:n,setLocale:s,t:u}=r(),d=u(`common.locale.choose`),p=`${d} (${u(`common.locale.current`)}: ${n})`;return(0,f.jsx)(o,{ariaLabel:d,placement:`bottom-end`,items:i.map(e=>({key:e,label:a[e],icon:e===t?(0,f.jsx)(c,{size:16}):(0,f.jsx)(`span`,{"aria-hidden":`true`}),onSelect:()=>{e!==t&&s(e)}})),renderTrigger:t=>(0,f.jsx)(`button`,{...t,className:[`language-toggle`,e].filter(Boolean).join(` `),"aria-label":d,title:p,children:(0,f.jsx)(l,{size:16})})})}export{c as a,u as i,p as n,d as r,m as t};
@@ -0,0 +1 @@
1
+ import{t as e}from"./jsx-runtime-Bt-cYkS5.js";import{n as t,t as n}from"./LanguageToggle-B99eQcTE.js";import{N as r,O as i}from"./index-DN0kD5zr.js";import{t as a}from"./MarketingThemeToggle-z4jr4-vt.js";var o=e();function s(){return(0,o.jsx)(`nav`,{className:`mkt-nav`,children:(0,o.jsxs)(`div`,{className:`mkt-nav-inner`,children:[(0,o.jsxs)(`button`,{type:`button`,className:`mkt-nav-logo`,onClick:i(),children:[(0,o.jsx)(r,{size:18}),(0,o.jsx)(`span`,{children:`MOST PEOPLE`})]}),(0,o.jsxs)(`div`,{className:`mkt-nav-cta`,children:[(0,o.jsx)(a,{}),(0,o.jsx)(n,{className:`mkt-theme-toggle`}),(0,o.jsx)(t,{})]})]})})}export{s as t};