most-box 0.1.1 → 0.1.3
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 +39 -19
- package/electron/afterPack.cjs +87 -0
- package/electron/main.js +156 -8
- package/electron/preload.js +6 -0
- package/electron/updateChecker.js +97 -0
- package/electron/updateChecker.test.js +147 -0
- package/out/404/index.html +2 -2
- package/out/404.html +2 -2
- package/out/__next.__PAGE__.txt +6 -6
- package/out/__next._full.txt +16 -16
- package/out/__next._head.txt +3 -3
- package/out/__next._index.txt +8 -8
- package/out/__next._tree.txt +5 -5
- package/out/_next/static/chunks/04mo7rr..0_1q.js +1 -0
- package/out/_next/static/chunks/06rf3qq5ggs6v.js +1 -0
- package/out/_next/static/chunks/07td.jq7xff84.css +1 -0
- package/out/_next/static/chunks/0_0oph_z1az14.js +1 -0
- package/out/_next/static/chunks/{0qou.u2e2dy48.css → 0adx~d-j05c9d.css} +2 -2
- package/out/_next/static/chunks/0cl7d~7abnk_p.css +1 -0
- package/out/_next/static/chunks/0d306t1wvjpdx.js +1 -0
- package/out/_next/static/chunks/0g_a~e050bgzg.css +1 -0
- package/out/_next/static/chunks/{0o6lrkxy4jwag.js → 0gcsdf57gcm6h.js} +1 -1
- package/out/_next/static/chunks/0hpev4am9jpmu.css +1 -0
- package/out/_next/static/chunks/0m_5nb6x8qy._.js +1 -0
- package/out/_next/static/chunks/0n.ayxmsar6e5.js +1 -0
- package/out/_next/static/chunks/{0usvo~vu7r8np.js → 0o9ce4cyf76by.js} +1 -1
- package/out/_next/static/chunks/0olqjomda37-e.js +1 -0
- package/out/_next/static/chunks/{0o98f1yq..o.8.js → 0pt.5cg1t09qs.js} +1 -1
- package/out/_next/static/chunks/0qgx9t4jx16ua.css +1 -0
- package/out/_next/static/chunks/0s~g.l~x049o2.js +1 -0
- package/out/_next/static/chunks/0ukyg~tkm~h2m.css +1 -0
- package/out/_next/static/chunks/0voe1.ttrh84k.css +1 -0
- package/out/_next/static/chunks/0wtf0xsiicxx6.js +1 -0
- package/out/_next/static/chunks/0x.ky97owcxxs.js +1 -0
- package/out/_next/static/chunks/0xdwau5k2augv.css +4 -0
- package/out/_next/static/chunks/0ysj5b94vu4ri.js +1 -0
- package/out/_next/static/chunks/12nr19.nnn6s3.js +5 -0
- package/out/_next/static/chunks/{0qub_r0x_r-e9.css → 12pep-2t-qg4n.css} +1 -1
- package/out/_next/static/chunks/14_inksek_rth.js +2 -0
- package/out/_next/static/chunks/153-sz7s.qml2.js +1 -0
- package/out/_next/static/chunks/17cwkb2yn_akx.js +1 -0
- package/out/_next/static/chunks/184hxsuf-5c84.js +1 -0
- package/out/_next/static/chunks/{turbopack-0xs6mybc~5t_3.js → turbopack-0xta0kqwzkf28.js} +1 -1
- package/out/_not-found/__next._full.txt +13 -13
- package/out/_not-found/__next._head.txt +3 -3
- package/out/_not-found/__next._index.txt +8 -8
- package/out/_not-found/__next._not-found.__PAGE__.txt +4 -4
- package/out/_not-found/__next._not-found.txt +3 -3
- package/out/_not-found/__next._tree.txt +3 -3
- package/out/_not-found/index.html +2 -2
- package/out/_not-found/index.txt +13 -13
- package/out/admin/__next._full.txt +15 -15
- package/out/admin/__next._head.txt +3 -3
- package/out/admin/__next._index.txt +8 -8
- package/out/admin/__next._tree.txt +4 -4
- package/out/admin/__next.admin.__PAGE__.txt +4 -4
- package/out/admin/__next.admin.txt +4 -4
- package/out/admin/index.html +2 -2
- package/out/admin/index.txt +15 -15
- package/out/app/__next._full.txt +14 -14
- package/out/app/__next._head.txt +3 -3
- package/out/app/__next._index.txt +8 -8
- package/out/app/__next._tree.txt +3 -3
- package/out/app/__next.app.__PAGE__.txt +4 -4
- package/out/app/__next.app.txt +3 -3
- package/out/app/index.html +2 -2
- package/out/app/index.txt +14 -14
- package/out/chat/__next._full.txt +15 -15
- package/out/chat/__next._head.txt +3 -3
- package/out/chat/__next._index.txt +8 -8
- package/out/chat/__next._tree.txt +4 -4
- package/out/chat/__next.chat.__PAGE__.txt +4 -4
- package/out/chat/__next.chat.txt +4 -4
- package/out/chat/index.html +2 -2
- package/out/chat/index.txt +15 -15
- package/out/chat/join/__next._full.txt +25 -0
- package/out/chat/join/__next._head.txt +5 -0
- package/out/{changelog → chat/join}/__next._index.txt +8 -8
- package/out/chat/join/__next._tree.txt +5 -0
- package/out/chat/join/__next.chat.join.__PAGE__.txt +9 -0
- package/out/chat/join/__next.chat.join.txt +5 -0
- package/out/chat/join/__next.chat.txt +5 -0
- package/out/chat/join/index.html +15 -0
- package/out/chat/join/index.txt +25 -0
- package/out/download/__next._full.txt +37 -33
- package/out/download/__next._head.txt +3 -3
- package/out/download/__next._index.txt +8 -8
- package/out/download/__next._tree.txt +5 -5
- package/out/download/__next.download.__PAGE__.txt +9 -14
- package/out/download/__next.download.txt +3 -3
- package/out/download/index.html +2 -2
- package/out/download/index.txt +37 -33
- package/out/favicon.ico +0 -0
- package/out/gandengyan/__next._full.txt +25 -0
- package/out/{changelog → gandengyan}/__next._head.txt +3 -3
- package/out/{docs → gandengyan}/__next._index.txt +8 -8
- package/out/gandengyan/__next._tree.txt +5 -0
- package/out/gandengyan/__next.gandengyan.__PAGE__.txt +10 -0
- package/out/gandengyan/__next.gandengyan.txt +5 -0
- package/out/gandengyan/index.html +15 -0
- package/out/gandengyan/index.txt +25 -0
- package/out/index.html +2 -2
- package/out/index.txt +16 -16
- package/out/note/__next._full.txt +14 -14
- package/out/note/__next._head.txt +3 -3
- package/out/note/__next._index.txt +8 -8
- package/out/note/__next._tree.txt +3 -3
- package/out/note/__next.note.__PAGE__.txt +4 -4
- package/out/note/__next.note.txt +3 -3
- package/out/note/index.html +2 -2
- package/out/note/index.txt +14 -14
- package/out/ping/__next._full.txt +16 -16
- package/out/ping/__next._head.txt +3 -3
- package/out/ping/__next._index.txt +8 -8
- package/out/ping/__next._tree.txt +5 -5
- package/out/ping/__next.ping.__PAGE__.txt +5 -5
- package/out/ping/__next.ping.txt +4 -4
- package/out/ping/index.html +2 -2
- package/out/ping/index.txt +16 -16
- package/out/web3/__next._full.txt +15 -15
- package/out/web3/__next._head.txt +3 -3
- package/out/web3/__next._index.txt +8 -8
- package/out/web3/__next._tree.txt +4 -4
- package/out/web3/__next.web3.__PAGE__.txt +4 -4
- package/out/web3/__next.web3.txt +4 -4
- package/out/web3/ed25519/__next._full.txt +13 -13
- package/out/web3/ed25519/__next._head.txt +3 -3
- package/out/web3/ed25519/__next._index.txt +8 -8
- package/out/web3/ed25519/__next._tree.txt +4 -4
- package/out/web3/ed25519/__next.web3.ed25519.__PAGE__.txt +2 -2
- package/out/web3/ed25519/__next.web3.ed25519.txt +3 -3
- package/out/web3/ed25519/__next.web3.txt +4 -4
- package/out/web3/ed25519/index.html +1 -1
- package/out/web3/ed25519/index.txt +13 -13
- package/out/web3/index.html +2 -2
- package/out/web3/index.txt +15 -15
- package/out/web3/tools/__next._full.txt +13 -13
- package/out/web3/tools/__next._head.txt +3 -3
- package/out/web3/tools/__next._index.txt +8 -8
- package/out/web3/tools/__next._tree.txt +4 -4
- package/out/web3/tools/__next.web3.tools.__PAGE__.txt +2 -2
- package/out/web3/tools/__next.web3.tools.txt +3 -3
- package/out/web3/tools/__next.web3.txt +4 -4
- package/out/web3/tools/index.html +1 -1
- package/out/web3/tools/index.txt +13 -13
- package/package.json +44 -35
- package/public/favicon.ico +0 -0
- package/server/index.js +142 -1304
- package/server/src/config.js +1 -1
- package/server/src/core/channelAttachment.js +68 -0
- package/server/src/core/cid.js +2 -88
- package/server/src/core/cidTopic.js +29 -0
- package/server/src/core/mostLink.js +88 -0
- package/server/src/games/gandengyan.js +675 -0
- package/server/src/http/access.js +127 -0
- package/server/src/http/app.js +1102 -0
- package/server/src/http/errors.js +35 -0
- package/server/src/http/nodeLogs.js +53 -0
- package/server/src/http/nodeStatus.js +146 -0
- package/server/src/http/staticFiles.js +84 -0
- package/server/src/http/uploads.js +114 -0
- package/server/src/index.js +799 -211
- package/server/src/node/config.js +38 -6
- package/server/src/utils/api.js +305 -14
- package/server/src/utils/auth.js +63 -0
- package/server/src/utils/dateTime.js +30 -0
- package/server/src/utils/downloadMessages.js +89 -0
- package/server/src/utils/errors.js +7 -0
- package/server/src/utils/mostWallet.js +151 -0
- package/server/src/utils/mp.js +2 -26
- package/server/src/utils/noteBackup.js +2 -5
- package/server/src/utils/noteUtils.js +11 -3
- package/server/src/utils/userIdentity.js +0 -1
- package/out/_next/static/chunks/00-u5nq76f0.j.js +0 -1
- package/out/_next/static/chunks/00fm8lijienf1.js +0 -1
- package/out/_next/static/chunks/00o9ht.f2qm00.css +0 -4
- package/out/_next/static/chunks/00zi-erhjrny2.js +0 -2
- package/out/_next/static/chunks/084xf0edl9sfo.js +0 -1
- package/out/_next/static/chunks/09f1gfke9m5wg.css +0 -1
- package/out/_next/static/chunks/09xyi6fpro_d-.css +0 -1
- package/out/_next/static/chunks/0_npg_pcoywti.js +0 -5
- package/out/_next/static/chunks/0_r_mk1~6bosc.js +0 -1
- package/out/_next/static/chunks/0arm0a6adt7cc.css +0 -1
- package/out/_next/static/chunks/0c9j3eq_14vv2.css +0 -1
- package/out/_next/static/chunks/0d4bueddmcnca.js +0 -1
- package/out/_next/static/chunks/0gtwvy1z9ksa7.css +0 -1
- package/out/_next/static/chunks/0ho~log~~-jwp.css +0 -1
- package/out/_next/static/chunks/0j27tcmtt4ly7.js +0 -1
- package/out/_next/static/chunks/0j3v4mq67wtnh.js +0 -1
- package/out/_next/static/chunks/0lkmf5ry.s_7w.js +0 -1
- package/out/_next/static/chunks/0p486m03-zfoi.js +0 -1
- package/out/_next/static/chunks/0r1~k82nji8sf.js +0 -1
- package/out/_next/static/chunks/0v7qp4hv-_._r.js +0 -1
- package/out/_next/static/chunks/0wuwlgcn6gxqt.js +0 -1
- package/out/_next/static/chunks/0xl5_avhu._i8.js +0 -1
- package/out/_next/static/chunks/10kvl8vj_plm-.js +0 -1
- package/out/_next/static/chunks/16m27azcs4k6w.js +0 -1
- package/out/changelog/__next._full.txt +0 -25
- package/out/changelog/__next._tree.txt +0 -5
- package/out/changelog/__next.changelog.__PAGE__.txt +0 -10
- package/out/changelog/__next.changelog.txt +0 -5
- package/out/changelog/index.html +0 -15
- package/out/changelog/index.txt +0 -25
- package/out/docs/__next._full.txt +0 -25
- package/out/docs/__next._head.txt +0 -5
- package/out/docs/__next._tree.txt +0 -5
- package/out/docs/__next.docs.__PAGE__.txt +0 -10
- package/out/docs/__next.docs.txt +0 -5
- package/out/docs/getting-started/__next._full.txt +0 -25
- package/out/docs/getting-started/__next._head.txt +0 -5
- package/out/docs/getting-started/__next._index.txt +0 -9
- package/out/docs/getting-started/__next._tree.txt +0 -5
- package/out/docs/getting-started/__next.docs.getting-started.__PAGE__.txt +0 -10
- package/out/docs/getting-started/__next.docs.getting-started.txt +0 -5
- package/out/docs/getting-started/__next.docs.txt +0 -5
- package/out/docs/getting-started/index.html +0 -15
- package/out/docs/getting-started/index.txt +0 -25
- package/out/docs/index.html +0 -15
- package/out/docs/index.txt +0 -25
- package/out/note/edit/__next._full.txt +0 -24
- package/out/note/edit/__next._head.txt +0 -5
- package/out/note/edit/__next._index.txt +0 -9
- package/out/note/edit/__next._tree.txt +0 -4
- package/out/note/edit/__next.note.edit.__PAGE__.txt +0 -9
- package/out/note/edit/__next.note.edit.txt +0 -5
- package/out/note/edit/__next.note.txt +0 -5
- package/out/note/edit/index.html +0 -15
- package/out/note/edit/index.txt +0 -24
- /package/out/_next/static/{sIuUKxnnGU7K9Tu9UDKE8 → aPEZ4zaaR5W3WpSZ0dFsa}/_buildManifest.js +0 -0
- /package/out/_next/static/{sIuUKxnnGU7K9Tu9UDKE8 → aPEZ4zaaR5W3WpSZ0dFsa}/_clientMiddlewareManifest.js +0 -0
- /package/out/_next/static/{sIuUKxnnGU7K9Tu9UDKE8 → aPEZ4zaaR5W3WpSZ0dFsa}/_ssgManifest.js +0 -0
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
| 🚀 P2P直连,不限速 | ✅ | ❌ | 限流 |
|
|
15
15
|
| 💾 去中心化存储 | ✅ | ❌ | ❌ |
|
|
16
16
|
| 🌐 开源免费,自托管 | ✅ | ❌ | |
|
|
17
|
-
| 📦
|
|
17
|
+
| 📦 大文件分享 | ✅ | ❌ | 限流 |
|
|
18
18
|
|
|
19
19
|
## 演示
|
|
20
20
|
|
|
@@ -48,7 +48,8 @@ npx most-box@latest
|
|
|
48
48
|
git clone <your-repo-url>
|
|
49
49
|
cd most
|
|
50
50
|
npm i
|
|
51
|
-
npm
|
|
51
|
+
npm run dev
|
|
52
|
+
node server/index.js
|
|
52
53
|
```
|
|
53
54
|
|
|
54
55
|
## 测试
|
|
@@ -60,19 +61,24 @@ npm run test:unit # 只运行单元测试
|
|
|
60
61
|
|
|
61
62
|
## 访问场景
|
|
62
63
|
|
|
63
|
-
| 场景
|
|
64
|
-
|
|
|
65
|
-
| 本地
|
|
66
|
-
|
|
|
67
|
-
| 外网
|
|
64
|
+
| 场景 | 方式 | 访问地址 |
|
|
65
|
+
| -------- | ------------------------------------- | ----------------------- |
|
|
66
|
+
| 本地 | `npx most-box` | `http://localhost:1976` |
|
|
67
|
+
| 远程管理 | SSH 隧道 + `/admin/` | `http://localhost:1976` |
|
|
68
|
+
| 外网 | Caddy 反向代理 | `https://your-domain` |
|
|
68
69
|
|
|
69
|
-
###
|
|
70
|
+
### 远程管理节点
|
|
71
|
+
|
|
72
|
+
MostBox 默认只监听 `127.0.0.1`,无需开放端口即可安全运行。
|
|
73
|
+
|
|
74
|
+
要管理部署在远程服务器上的节点,使用 SSH 隧道将服务器的 1976 端口转发到本地:
|
|
70
75
|
|
|
71
76
|
```bash
|
|
72
|
-
|
|
73
|
-
npx most-box
|
|
77
|
+
ssh -L 1976:127.0.0.1:1976 user@your-server
|
|
74
78
|
```
|
|
75
79
|
|
|
80
|
+
然后在本地浏览器打开 `http://localhost:1976/admin/` 即可管理远程节点。
|
|
81
|
+
|
|
76
82
|
### 外网访问(Caddy)
|
|
77
83
|
|
|
78
84
|
```caddy
|
|
@@ -81,6 +87,8 @@ mostbox.example.com {
|
|
|
81
87
|
}
|
|
82
88
|
```
|
|
83
89
|
|
|
90
|
+
开放到局域网或公网时,在 `/admin/` 中配置邀请码,远程请求必须携带有效邀请码。
|
|
91
|
+
|
|
84
92
|
## 核心功能
|
|
85
93
|
|
|
86
94
|
1. **确定性 P2P 文件发布**
|
|
@@ -109,7 +117,7 @@ mostbox.example.com {
|
|
|
109
117
|
|
|
110
118
|
### 文件存储在哪里?
|
|
111
119
|
|
|
112
|
-
文件以 **P2P 方式**
|
|
120
|
+
文件以 **P2P 方式** 保存在分享者和接收者的设备上。每个做种节点都持有完整文件副本;MostBox 不会把文件集中上传到云端服务器。
|
|
113
121
|
|
|
114
122
|
### 如何分享文件给其他人?
|
|
115
123
|
|
|
@@ -124,7 +132,7 @@ mostbox.example.com {
|
|
|
124
132
|
|
|
125
133
|
### 支持大文件吗?
|
|
126
134
|
|
|
127
|
-
|
|
135
|
+
支持。目前默认单文件上限为 **10GB**,可在本地节点策略中调整;传输采用流式处理,内存占用低。
|
|
128
136
|
|
|
129
137
|
### 频道聊天是什么?
|
|
130
138
|
|
|
@@ -133,7 +141,7 @@ mostbox.example.com {
|
|
|
133
141
|
- 创建一个频道(如 `alice` 或 `team-project`)
|
|
134
142
|
- 将频道名称分享给朋友
|
|
135
143
|
- 朋友加入后即可实时聊天
|
|
136
|
-
- 消息通过 P2P
|
|
144
|
+
- 消息通过 P2P 通道复制,服务器或节点只负责连接与同步
|
|
137
145
|
|
|
138
146
|
### 如何使用频道聊天?
|
|
139
147
|
|
|
@@ -183,7 +191,6 @@ npx most-box
|
|
|
183
191
|
- **前端**: React 19, Next.js 16, TypeScript, Zustand, Lucide React
|
|
184
192
|
- **后端**: Hono + @hono/node-server + WebSocket
|
|
185
193
|
- **P2P**: Hyperswarm 4.x, Hyperdrive 13.x, Corestore 7.x
|
|
186
|
-
- **Web3**: ethers.js, Hardhat, Solidity, EIP-712
|
|
187
194
|
- **桌面**: Electron 41, electron-builder
|
|
188
195
|
- **测试**: Node.js built-in test runner
|
|
189
196
|
|
|
@@ -199,19 +206,32 @@ git push origin v0.0.7
|
|
|
199
206
|
触发后自动执行:
|
|
200
207
|
|
|
201
208
|
1. **npm 包发布** — 发布 `most-box` 到 npm registry
|
|
202
|
-
2. **Windows 打包** —
|
|
209
|
+
2. **Windows 打包** — 分别构建 `.exe` 安装包(x64 / arm64)并上传 Release
|
|
203
210
|
3. **macOS 打包** — 构建 `.dmg` 安装包(x64 + arm64)并上传 Release
|
|
204
211
|
4. **Linux 打包** — 构建 `.AppImage` 安装包(x64 + arm64)并上传 Release
|
|
212
|
+
5. **下载镜像** — 将 Release 资产同步到 Cloudflare R2,并生成 `releases/latest.json`
|
|
205
213
|
|
|
206
|
-
|
|
214
|
+
GitHub Release 是可信备用源;下载页优先读取 R2 的 `releases/latest.json` 并使用 R2 下载链接。
|
|
207
215
|
|
|
208
216
|
### 配置 Secrets
|
|
209
217
|
|
|
218
|
+
R2 发布资产使用独立公开桶,默认 bucket 为 `most-box-releases`,默认公开域名为
|
|
219
|
+
`https://download.most.box`。不要复用 `api.most.box` 项目的 `most-box-backup` 备份桶。
|
|
220
|
+
|
|
210
221
|
在仓库 Settings → Secrets and variables → Actions 中添加:
|
|
211
222
|
|
|
212
|
-
| Secret
|
|
213
|
-
|
|
|
214
|
-
| `NPM_TOKEN`
|
|
223
|
+
| Secret | 说明 |
|
|
224
|
+
| ---------------------- | --------------------------------------- |
|
|
225
|
+
| `NPM_TOKEN` | npm 发布令牌(`npm token create` 生成) |
|
|
226
|
+
| `R2_ACCOUNT_ID` | Cloudflare 账户 ID |
|
|
227
|
+
| `R2_ACCESS_KEY_ID` | R2 S3 API Access Key ID |
|
|
228
|
+
| `R2_SECRET_ACCESS_KEY` | R2 S3 API Secret Access Key |
|
|
229
|
+
| `R2_BUCKET` | 可选;默认 `most-box-releases` |
|
|
230
|
+
| `R2_PUBLIC_BASE_URL` | 可选;默认 `https://download.most.box` |
|
|
231
|
+
|
|
232
|
+
下载页默认读取 `https://download.most.box/releases/latest.json`。部署环境可额外配置
|
|
233
|
+
`NEXT_PUBLIC_R2_PUBLIC_BASE_URL` 覆盖公开域名,或直接配置
|
|
234
|
+
`NEXT_PUBLIC_RELEASE_MANIFEST_URL` 指向指定的 `latest.json`。
|
|
215
235
|
|
|
216
236
|
## 社区
|
|
217
237
|
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
const fs = require('node:fs')
|
|
2
|
+
const path = require('node:path')
|
|
3
|
+
|
|
4
|
+
const ARCH_BY_VALUE = new Map([
|
|
5
|
+
[0, 'ia32'],
|
|
6
|
+
[1, 'x64'],
|
|
7
|
+
[2, 'armv7l'],
|
|
8
|
+
[3, 'arm64'],
|
|
9
|
+
[4, 'universal'],
|
|
10
|
+
])
|
|
11
|
+
|
|
12
|
+
function normalizeArch(arch) {
|
|
13
|
+
if (typeof arch === 'string') return arch
|
|
14
|
+
if (typeof arch === 'number') return ARCH_BY_VALUE.get(arch) || String(arch)
|
|
15
|
+
return String(arch || '')
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function findPrebuildDirs(root) {
|
|
19
|
+
const matches = []
|
|
20
|
+
const stack = [root]
|
|
21
|
+
|
|
22
|
+
while (stack.length > 0) {
|
|
23
|
+
const dir = stack.pop()
|
|
24
|
+
let entries
|
|
25
|
+
try {
|
|
26
|
+
entries = fs.readdirSync(dir, { withFileTypes: true })
|
|
27
|
+
} catch {
|
|
28
|
+
continue
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
for (const entry of entries) {
|
|
32
|
+
if (!entry.isDirectory()) continue
|
|
33
|
+
|
|
34
|
+
const fullPath = path.join(dir, entry.name)
|
|
35
|
+
if (entry.name === 'prebuilds') {
|
|
36
|
+
matches.push(fullPath)
|
|
37
|
+
continue
|
|
38
|
+
}
|
|
39
|
+
stack.push(fullPath)
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return matches
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function shouldKeepPrebuild(name, target) {
|
|
47
|
+
return (
|
|
48
|
+
name === target ||
|
|
49
|
+
name.startsWith(`${target}-`) ||
|
|
50
|
+
name.startsWith(`${target}+`)
|
|
51
|
+
)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
module.exports = async function afterPack(context) {
|
|
55
|
+
const platform = context.electronPlatformName
|
|
56
|
+
const arch = normalizeArch(context.arch)
|
|
57
|
+
const target = `${platform}-${arch}`
|
|
58
|
+
const nodeModulesDir = path.join(
|
|
59
|
+
context.appOutDir,
|
|
60
|
+
'resources',
|
|
61
|
+
'app.asar.unpacked',
|
|
62
|
+
'node_modules'
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
if (!platform || !arch || arch === 'universal' || !fs.existsSync(nodeModulesDir)) {
|
|
66
|
+
return
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
let removed = 0
|
|
70
|
+
for (const prebuildDir of findPrebuildDirs(nodeModulesDir)) {
|
|
71
|
+
const entries = fs.readdirSync(prebuildDir, { withFileTypes: true })
|
|
72
|
+
for (const entry of entries) {
|
|
73
|
+
if (!entry.isDirectory()) continue
|
|
74
|
+
if (shouldKeepPrebuild(entry.name, target)) continue
|
|
75
|
+
|
|
76
|
+
fs.rmSync(path.join(prebuildDir, entry.name), {
|
|
77
|
+
force: true,
|
|
78
|
+
recursive: true,
|
|
79
|
+
})
|
|
80
|
+
removed += 1
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (removed > 0) {
|
|
85
|
+
console.log(`[MostBox] Pruned ${removed} native prebuild directories for ${target}`)
|
|
86
|
+
}
|
|
87
|
+
}
|
package/electron/main.js
CHANGED
|
@@ -1,12 +1,85 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
app,
|
|
3
|
+
BrowserWindow,
|
|
4
|
+
Menu,
|
|
5
|
+
Tray,
|
|
6
|
+
dialog,
|
|
7
|
+
nativeImage,
|
|
8
|
+
shell,
|
|
9
|
+
} from 'electron'
|
|
10
|
+
import fs from 'node:fs'
|
|
2
11
|
import path from 'node:path'
|
|
3
12
|
import { fileURLToPath } from 'node:url'
|
|
4
13
|
|
|
14
|
+
import {
|
|
15
|
+
formatBytes,
|
|
16
|
+
getAvailableUpdate,
|
|
17
|
+
getCurrentArch,
|
|
18
|
+
getCurrentPlatform,
|
|
19
|
+
getReleaseManifestUrl,
|
|
20
|
+
} from './updateChecker.js'
|
|
21
|
+
|
|
5
22
|
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
6
|
-
const PORT =
|
|
23
|
+
const PORT = 1976
|
|
7
24
|
|
|
8
25
|
let mainWindow = null
|
|
9
26
|
let engine = null
|
|
27
|
+
let tray = null
|
|
28
|
+
let isQuitting = false
|
|
29
|
+
let hasCheckedForUpdates = false
|
|
30
|
+
|
|
31
|
+
function getTrayIconPath() {
|
|
32
|
+
const candidates = [
|
|
33
|
+
path.join(__dirname, '..', 'out', 'favicon.ico'),
|
|
34
|
+
path.join(process.resourcesPath, 'app', 'out', 'favicon.ico'),
|
|
35
|
+
path.join(__dirname, '..', 'public', 'favicon.ico'),
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
return candidates.find(candidate => fs.existsSync(candidate))
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function showMainWindow() {
|
|
42
|
+
if (!mainWindow) {
|
|
43
|
+
createWindow()
|
|
44
|
+
return
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (mainWindow.isMinimized()) {
|
|
48
|
+
mainWindow.restore()
|
|
49
|
+
}
|
|
50
|
+
mainWindow.show()
|
|
51
|
+
mainWindow.focus()
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function quitFromTray() {
|
|
55
|
+
isQuitting = true
|
|
56
|
+
if (tray) {
|
|
57
|
+
tray.destroy()
|
|
58
|
+
tray = null
|
|
59
|
+
}
|
|
60
|
+
app.quit()
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function createTray() {
|
|
64
|
+
if (tray) {
|
|
65
|
+
return
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const iconPath = getTrayIconPath()
|
|
69
|
+
const image = iconPath ? nativeImage.createFromPath(iconPath) : null
|
|
70
|
+
|
|
71
|
+
tray = new Tray(image && !image.isEmpty() ? image : nativeImage.createEmpty())
|
|
72
|
+
tray.setToolTip('MostBox')
|
|
73
|
+
tray.setContextMenu(
|
|
74
|
+
Menu.buildFromTemplate([
|
|
75
|
+
{ label: '打开 MostBox', click: showMainWindow },
|
|
76
|
+
{ type: 'separator' },
|
|
77
|
+
{ label: '退出', click: quitFromTray },
|
|
78
|
+
])
|
|
79
|
+
)
|
|
80
|
+
tray.on('click', showMainWindow)
|
|
81
|
+
tray.on('double-click', showMainWindow)
|
|
82
|
+
}
|
|
10
83
|
|
|
11
84
|
function createWindow() {
|
|
12
85
|
mainWindow = new BrowserWindow({
|
|
@@ -25,6 +98,15 @@ function createWindow() {
|
|
|
25
98
|
|
|
26
99
|
mainWindow.loadURL(`http://localhost:${PORT}`)
|
|
27
100
|
|
|
101
|
+
mainWindow.on('close', event => {
|
|
102
|
+
if (isQuitting) {
|
|
103
|
+
return
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
event.preventDefault()
|
|
107
|
+
mainWindow.hide()
|
|
108
|
+
})
|
|
109
|
+
|
|
28
110
|
mainWindow.on('closed', () => {
|
|
29
111
|
mainWindow = null
|
|
30
112
|
})
|
|
@@ -37,30 +119,96 @@ async function startServer() {
|
|
|
37
119
|
engine = await main()
|
|
38
120
|
}
|
|
39
121
|
|
|
122
|
+
async function fetchReleaseManifest(manifestUrl) {
|
|
123
|
+
const controller = new AbortController()
|
|
124
|
+
const timeout = setTimeout(() => controller.abort(), 8000)
|
|
125
|
+
|
|
126
|
+
try {
|
|
127
|
+
const response = await fetch(manifestUrl, {
|
|
128
|
+
cache: 'no-store',
|
|
129
|
+
signal: controller.signal,
|
|
130
|
+
})
|
|
131
|
+
if (!response.ok) return null
|
|
132
|
+
return await response.json()
|
|
133
|
+
} finally {
|
|
134
|
+
clearTimeout(timeout)
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
async function checkForUpdates() {
|
|
139
|
+
if (hasCheckedForUpdates) return
|
|
140
|
+
hasCheckedForUpdates = true
|
|
141
|
+
|
|
142
|
+
try {
|
|
143
|
+
const platform = getCurrentPlatform()
|
|
144
|
+
const arch = getCurrentArch()
|
|
145
|
+
if (!platform || !arch) return
|
|
146
|
+
|
|
147
|
+
const manifest = await fetchReleaseManifest(getReleaseManifestUrl())
|
|
148
|
+
const update = getAvailableUpdate(manifest, {
|
|
149
|
+
currentVersion: app.getVersion(),
|
|
150
|
+
platform,
|
|
151
|
+
arch,
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
if (!update) return
|
|
155
|
+
|
|
156
|
+
const sizeText = formatBytes(update.asset.size)
|
|
157
|
+
const detail = [
|
|
158
|
+
`当前版本:${app.getVersion()}`,
|
|
159
|
+
`最新版本:${update.version}`,
|
|
160
|
+
sizeText ? `安装包大小:${sizeText}` : null,
|
|
161
|
+
'',
|
|
162
|
+
'是否现在打开浏览器下载更新?',
|
|
163
|
+
]
|
|
164
|
+
.filter(line => line !== null)
|
|
165
|
+
.join('\n')
|
|
166
|
+
|
|
167
|
+
const result = await dialog.showMessageBox(mainWindow, {
|
|
168
|
+
type: 'info',
|
|
169
|
+
buttons: ['立即下载', '稍后'],
|
|
170
|
+
defaultId: 0,
|
|
171
|
+
cancelId: 1,
|
|
172
|
+
title: '发现 MostBox 新版本',
|
|
173
|
+
message: `MostBox ${update.version} 已可下载`,
|
|
174
|
+
detail,
|
|
175
|
+
noLink: true,
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
if (result.response === 0) {
|
|
179
|
+
await shell.openExternal(update.downloadUrl)
|
|
180
|
+
}
|
|
181
|
+
} catch (error) {
|
|
182
|
+
console.warn('[Electron] Update check skipped:', error)
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
40
186
|
app.whenReady().then(async () => {
|
|
41
187
|
try {
|
|
42
188
|
await startServer()
|
|
43
189
|
createWindow()
|
|
190
|
+
createTray()
|
|
44
191
|
Menu.setApplicationMenu(null)
|
|
192
|
+
setTimeout(() => {
|
|
193
|
+
checkForUpdates()
|
|
194
|
+
}, 3000)
|
|
45
195
|
|
|
46
196
|
app.on('activate', () => {
|
|
47
|
-
|
|
48
|
-
createWindow()
|
|
49
|
-
}
|
|
197
|
+
showMainWindow()
|
|
50
198
|
})
|
|
51
199
|
} catch (err) {
|
|
52
200
|
console.error('[Electron] Failed to start server:', err)
|
|
201
|
+
isQuitting = true
|
|
53
202
|
app.quit()
|
|
54
203
|
}
|
|
55
204
|
})
|
|
56
205
|
|
|
57
206
|
app.on('window-all-closed', () => {
|
|
58
|
-
|
|
59
|
-
app.quit()
|
|
60
|
-
}
|
|
207
|
+
// Keep the daemon alive; users exit from the tray menu.
|
|
61
208
|
})
|
|
62
209
|
|
|
63
210
|
app.on('before-quit', () => {
|
|
211
|
+
isQuitting = true
|
|
64
212
|
if (engine && engine.stop) {
|
|
65
213
|
engine.stop().catch(() => {})
|
|
66
214
|
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
export const DEFAULT_RELEASE_MANIFEST_URL =
|
|
2
|
+
'https://download.most.box/releases/latest.json'
|
|
3
|
+
|
|
4
|
+
const PLATFORM_BY_PROCESS = {
|
|
5
|
+
darwin: 'macos',
|
|
6
|
+
linux: 'linux',
|
|
7
|
+
win32: 'windows',
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function getReleaseManifestUrl(env = process.env) {
|
|
11
|
+
return env.MOSTBOX_RELEASE_MANIFEST_URL || DEFAULT_RELEASE_MANIFEST_URL
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function getCurrentPlatform(processPlatform = process.platform) {
|
|
15
|
+
return PLATFORM_BY_PROCESS[processPlatform] || null
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function getCurrentArch(processArch = process.arch) {
|
|
19
|
+
if (processArch === 'x64' || processArch === 'arm64') return processArch
|
|
20
|
+
return null
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function parseVersion(version) {
|
|
24
|
+
if (typeof version !== 'string') return null
|
|
25
|
+
|
|
26
|
+
const match = version.trim().match(/^v?(\d+)\.(\d+)\.(\d+)(?:[-+].*)?$/)
|
|
27
|
+
if (!match) return null
|
|
28
|
+
|
|
29
|
+
return match.slice(1).map(part => Number(part))
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function isNewerVersion(candidateVersion, currentVersion) {
|
|
33
|
+
const candidate = parseVersion(candidateVersion)
|
|
34
|
+
const current = parseVersion(currentVersion)
|
|
35
|
+
|
|
36
|
+
if (!candidate || !current) return false
|
|
37
|
+
|
|
38
|
+
for (let index = 0; index < candidate.length; index += 1) {
|
|
39
|
+
if (candidate[index] > current[index]) return true
|
|
40
|
+
if (candidate[index] < current[index]) return false
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return false
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function isRecord(value) {
|
|
47
|
+
return typeof value === 'object' && value !== null
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function findUpdateAsset(manifest, platform, arch) {
|
|
51
|
+
if (!isRecord(manifest) || !Array.isArray(manifest.assets)) return null
|
|
52
|
+
|
|
53
|
+
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
|
|
63
|
+
)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function formatBytes(size) {
|
|
67
|
+
if (!Number.isFinite(size) || size <= 0) return ''
|
|
68
|
+
|
|
69
|
+
const mb = size / 1024 / 1024
|
|
70
|
+
return `${mb.toFixed(mb >= 100 ? 0 : 1)} MB`
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function getAvailableUpdate(manifest, options = {}) {
|
|
74
|
+
const currentVersion = options.currentVersion
|
|
75
|
+
const platform = options.platform
|
|
76
|
+
const arch = options.arch
|
|
77
|
+
|
|
78
|
+
if (
|
|
79
|
+
!isRecord(manifest) ||
|
|
80
|
+
typeof manifest.version !== 'string' ||
|
|
81
|
+
!isNewerVersion(manifest.version, currentVersion)
|
|
82
|
+
) {
|
|
83
|
+
return null
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const asset = findUpdateAsset(manifest, platform, arch)
|
|
87
|
+
if (!asset) return null
|
|
88
|
+
|
|
89
|
+
const downloadUrl = asset.r2Url || asset.githubUrl
|
|
90
|
+
if (!downloadUrl) return null
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
version: manifest.version,
|
|
94
|
+
asset,
|
|
95
|
+
downloadUrl,
|
|
96
|
+
}
|
|
97
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { describe, it } from 'node:test'
|
|
2
|
+
import assert from 'node:assert/strict'
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
findUpdateAsset,
|
|
6
|
+
formatBytes,
|
|
7
|
+
getAvailableUpdate,
|
|
8
|
+
getCurrentArch,
|
|
9
|
+
getCurrentPlatform,
|
|
10
|
+
getReleaseManifestUrl,
|
|
11
|
+
isNewerVersion,
|
|
12
|
+
} from './updateChecker.js'
|
|
13
|
+
|
|
14
|
+
const manifest = {
|
|
15
|
+
version: '0.1.3',
|
|
16
|
+
publishedAt: '2026-06-01T00:00:00.000Z',
|
|
17
|
+
assets: [
|
|
18
|
+
{
|
|
19
|
+
platform: 'windows',
|
|
20
|
+
arch: 'x64',
|
|
21
|
+
kind: 'installer',
|
|
22
|
+
filename: 'MostBox-0.1.3-win-x64-setup.exe',
|
|
23
|
+
size: 112197632,
|
|
24
|
+
sha256: 'hash',
|
|
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: 'arm64',
|
|
33
|
+
kind: 'installer',
|
|
34
|
+
filename: 'MostBox-0.1.3-win-arm64-setup.exe',
|
|
35
|
+
githubUrl:
|
|
36
|
+
'https://github.com/most-people/most/releases/download/v0.1.3/MostBox-0.1.3-win-arm64-setup.exe',
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
platform: 'macos',
|
|
40
|
+
arch: 'arm64',
|
|
41
|
+
kind: 'installer',
|
|
42
|
+
filename: 'MostBox-0.1.3-mac-arm64.dmg',
|
|
43
|
+
githubUrl:
|
|
44
|
+
'https://github.com/most-people/most/releases/download/v0.1.3/MostBox-0.1.3-mac-arm64.dmg',
|
|
45
|
+
},
|
|
46
|
+
],
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
describe('desktop update checker', () => {
|
|
50
|
+
it('uses the public R2 manifest by default and allows env override', () => {
|
|
51
|
+
assert.equal(
|
|
52
|
+
getReleaseManifestUrl({}),
|
|
53
|
+
'https://download.most.box/releases/latest.json'
|
|
54
|
+
)
|
|
55
|
+
assert.equal(
|
|
56
|
+
getReleaseManifestUrl({
|
|
57
|
+
MOSTBOX_RELEASE_MANIFEST_URL: 'http://localhost:9999/latest.json',
|
|
58
|
+
}),
|
|
59
|
+
'http://localhost:9999/latest.json'
|
|
60
|
+
)
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
it('maps process platform and arch to release manifest values', () => {
|
|
64
|
+
assert.equal(getCurrentPlatform('win32'), 'windows')
|
|
65
|
+
assert.equal(getCurrentPlatform('darwin'), 'macos')
|
|
66
|
+
assert.equal(getCurrentPlatform('linux'), 'linux')
|
|
67
|
+
assert.equal(getCurrentPlatform('freebsd'), null)
|
|
68
|
+
|
|
69
|
+
assert.equal(getCurrentArch('x64'), 'x64')
|
|
70
|
+
assert.equal(getCurrentArch('arm64'), 'arm64')
|
|
71
|
+
assert.equal(getCurrentArch('ia32'), null)
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
it('compares stable and prefixed semver strings safely', () => {
|
|
75
|
+
assert.equal(isNewerVersion('0.1.3', '0.1.2'), true)
|
|
76
|
+
assert.equal(isNewerVersion('v0.2.0', '0.1.9'), true)
|
|
77
|
+
assert.equal(isNewerVersion('0.1.2', '0.1.2'), false)
|
|
78
|
+
assert.equal(isNewerVersion('0.1.1', '0.1.2'), false)
|
|
79
|
+
assert.equal(isNewerVersion('0.1.3-beta.1', '0.1.2'), true)
|
|
80
|
+
assert.equal(isNewerVersion('not-a-version', '0.1.2'), false)
|
|
81
|
+
assert.equal(isNewerVersion('0.1.3', 'not-a-version'), false)
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
it('matches the current platform installer asset', () => {
|
|
85
|
+
assert.equal(
|
|
86
|
+
findUpdateAsset(manifest, 'windows', 'x64')?.filename,
|
|
87
|
+
'MostBox-0.1.3-win-x64-setup.exe'
|
|
88
|
+
)
|
|
89
|
+
assert.equal(
|
|
90
|
+
findUpdateAsset(manifest, 'windows', 'arm64')?.filename,
|
|
91
|
+
'MostBox-0.1.3-win-arm64-setup.exe'
|
|
92
|
+
)
|
|
93
|
+
assert.equal(findUpdateAsset(manifest, 'linux', 'x64'), null)
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
it('returns an available update only for newer compatible manifests', () => {
|
|
97
|
+
const update = getAvailableUpdate(manifest, {
|
|
98
|
+
currentVersion: '0.1.2',
|
|
99
|
+
platform: 'windows',
|
|
100
|
+
arch: 'x64',
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
assert.equal(update?.version, '0.1.3')
|
|
104
|
+
assert.equal(update?.downloadUrl, manifest.assets[0].r2Url)
|
|
105
|
+
|
|
106
|
+
assert.equal(
|
|
107
|
+
getAvailableUpdate(manifest, {
|
|
108
|
+
currentVersion: '0.1.3',
|
|
109
|
+
platform: 'windows',
|
|
110
|
+
arch: 'x64',
|
|
111
|
+
}),
|
|
112
|
+
null
|
|
113
|
+
)
|
|
114
|
+
assert.equal(
|
|
115
|
+
getAvailableUpdate(manifest, {
|
|
116
|
+
currentVersion: '0.1.2',
|
|
117
|
+
platform: 'linux',
|
|
118
|
+
arch: 'x64',
|
|
119
|
+
}),
|
|
120
|
+
null
|
|
121
|
+
)
|
|
122
|
+
assert.equal(
|
|
123
|
+
getAvailableUpdate({ version: '0.1.3', assets: [] }, {
|
|
124
|
+
currentVersion: '0.1.2',
|
|
125
|
+
platform: 'windows',
|
|
126
|
+
arch: 'x64',
|
|
127
|
+
}),
|
|
128
|
+
null
|
|
129
|
+
)
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
it('falls back to GitHub when an R2 URL is absent', () => {
|
|
133
|
+
const update = getAvailableUpdate(manifest, {
|
|
134
|
+
currentVersion: '0.1.2',
|
|
135
|
+
platform: 'windows',
|
|
136
|
+
arch: 'arm64',
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
assert.equal(update?.downloadUrl, manifest.assets[1].githubUrl)
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
it('formats installer sizes for update prompts', () => {
|
|
143
|
+
assert.equal(formatBytes(112197632), '107 MB')
|
|
144
|
+
assert.equal(formatBytes(2.5 * 1024 * 1024), '2.5 MB')
|
|
145
|
+
assert.equal(formatBytes(0), '')
|
|
146
|
+
})
|
|
147
|
+
})
|