most-box 0.0.4 → 0.0.7

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 (187) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +222 -182
  3. package/electron/main.js +67 -0
  4. package/out/404/index.html +2 -2
  5. package/out/404.html +2 -2
  6. package/out/__next.__PAGE__.txt +6 -4
  7. package/out/__next._full.txt +20 -17
  8. package/out/__next._head.txt +3 -3
  9. package/out/__next._index.txt +6 -5
  10. package/out/__next._tree.txt +4 -2
  11. package/out/_next/static/chunks/003jnm.v5tzw5.js +1 -0
  12. package/out/_next/static/chunks/00re8v.gbcywn.js +1 -0
  13. package/out/_next/static/chunks/00s106sbq8t9v.js +1 -0
  14. package/out/_next/static/chunks/012hi627qrdnn.js +1 -0
  15. package/out/_next/static/chunks/0174xh3wfsjm1.js +2 -0
  16. package/out/_next/static/chunks/01xlw8hd842-c.js +1 -0
  17. package/out/_next/static/chunks/02~o2nmo5pmy1.js +1 -0
  18. package/out/_next/static/chunks/07t.dhhokszz5.css +1 -0
  19. package/out/_next/static/chunks/0_wia9ofmsi1c.css +2 -0
  20. package/out/_next/static/chunks/0ah8fihozo2_u.js +5 -0
  21. package/out/_next/static/chunks/0bzupvr5gt3k9.js +31 -0
  22. package/out/_next/static/chunks/0d3shmwh5_nmn.js +1 -0
  23. package/out/_next/static/chunks/0e_h0d3ekzks8.css +1 -0
  24. package/out/_next/static/chunks/0gdluj423gso1.js +1 -0
  25. package/out/_next/static/chunks/0gmoiq06srjay.css +1 -0
  26. package/out/_next/static/chunks/0ho~log~~-jwp.css +1 -0
  27. package/out/_next/static/chunks/0imkasy7kb67u.js +1 -0
  28. package/out/_next/static/chunks/0jjc_b9q_ldi2.js +1 -0
  29. package/out/_next/static/chunks/0jl~j62iz2uvr.js +1 -0
  30. package/out/_next/static/chunks/0lqslm813wk_h.js +1 -0
  31. package/out/_next/static/chunks/{0bogtdbh.dcu1.js → 0n~dq4kpx9xxx.js} +1 -1
  32. package/out/_next/static/chunks/0pqt~8bl3ukh4.js +4 -0
  33. package/out/_next/static/chunks/0q782fxxd0lx~.js +1 -0
  34. package/out/_next/static/chunks/0qub_r0x_r-e9.css +1 -0
  35. package/out/_next/static/chunks/0slwj0c46k5cu.js +1 -0
  36. package/out/_next/static/chunks/0sorqk.oc6b7j.css +1 -0
  37. package/out/_next/static/chunks/0tapzqc6hgvx-.js +1 -0
  38. package/out/_next/static/chunks/0xsc7z5x8n7wg.js +1 -0
  39. package/out/_next/static/chunks/0zm~gys2jwl0g.js +1 -0
  40. package/out/_next/static/chunks/turbopack-0a_g3u0ud~jb8.js +1 -0
  41. package/out/_not-found/__next._full.txt +19 -15
  42. package/out/_not-found/__next._head.txt +3 -3
  43. package/out/_not-found/__next._index.txt +6 -5
  44. package/out/_not-found/__next._not-found.__PAGE__.txt +9 -0
  45. package/out/_not-found/__next._not-found.txt +3 -3
  46. package/out/_not-found/__next._tree.txt +2 -2
  47. package/out/_not-found/index.html +2 -2
  48. package/out/_not-found/index.txt +19 -15
  49. package/out/app/__next._full.txt +20 -0
  50. package/out/app/__next._head.txt +5 -0
  51. package/out/app/__next._index.txt +7 -0
  52. package/out/app/__next._tree.txt +2 -0
  53. package/out/app/__next.app.__PAGE__.txt +9 -0
  54. package/out/app/__next.app.txt +5 -0
  55. package/out/app/index.html +15 -0
  56. package/out/app/index.txt +20 -0
  57. package/out/changelog/__next._full.txt +22 -0
  58. package/out/changelog/__next._head.txt +5 -0
  59. package/out/changelog/__next._index.txt +7 -0
  60. package/out/changelog/__next._tree.txt +3 -0
  61. package/out/changelog/__next.changelog.__PAGE__.txt +10 -0
  62. package/out/changelog/__next.changelog.txt +5 -0
  63. package/out/changelog/index.html +15 -0
  64. package/out/changelog/index.txt +22 -0
  65. package/out/chat/__next._full.txt +20 -18
  66. package/out/chat/__next._head.txt +3 -3
  67. package/out/chat/__next._index.txt +6 -5
  68. package/out/chat/__next._tree.txt +3 -3
  69. package/out/chat/__next.chat.__PAGE__.txt +9 -0
  70. package/out/chat/__next.chat.txt +5 -4
  71. package/out/chat/index.html +2 -2
  72. package/out/chat/index.txt +20 -18
  73. package/out/docs/__next._full.txt +22 -0
  74. package/out/docs/__next._head.txt +5 -0
  75. package/out/docs/__next._index.txt +7 -0
  76. package/out/docs/__next._tree.txt +3 -0
  77. package/out/docs/__next.docs.__PAGE__.txt +10 -0
  78. package/out/docs/__next.docs.txt +5 -0
  79. package/out/docs/getting-started/__next._full.txt +22 -0
  80. package/out/docs/getting-started/__next._head.txt +5 -0
  81. package/out/docs/getting-started/__next._index.txt +7 -0
  82. package/out/docs/getting-started/__next._tree.txt +3 -0
  83. package/out/docs/getting-started/__next.docs.getting-started.__PAGE__.txt +10 -0
  84. package/out/docs/getting-started/__next.docs.getting-started.txt +5 -0
  85. package/out/docs/getting-started/__next.docs.txt +5 -0
  86. package/out/docs/getting-started/index.html +15 -0
  87. package/out/docs/getting-started/index.txt +22 -0
  88. package/out/docs/index.html +15 -0
  89. package/out/docs/index.txt +22 -0
  90. package/out/download/__next._full.txt +34 -0
  91. package/out/download/__next._head.txt +5 -0
  92. package/out/download/__next._index.txt +7 -0
  93. package/out/download/__next._tree.txt +4 -0
  94. package/out/download/__next.download.__PAGE__.txt +16 -0
  95. package/out/download/__next.download.txt +5 -0
  96. package/out/download/index.html +15 -0
  97. package/out/download/index.txt +34 -0
  98. package/out/fonts/jetbrains-mono-latin-400-normal.woff2 +0 -0
  99. package/out/fonts/jetbrains-mono-latin-500-normal.woff2 +0 -0
  100. package/out/fonts/jetbrains-mono-latin-600-normal.woff2 +0 -0
  101. package/out/fonts/jetbrains-mono-latin-700-normal.woff2 +0 -0
  102. package/out/index.html +2 -2
  103. package/out/index.txt +20 -17
  104. package/out/ping/__next._full.txt +21 -0
  105. package/out/ping/__next._head.txt +5 -0
  106. package/out/ping/__next._index.txt +7 -0
  107. package/out/ping/__next._tree.txt +4 -0
  108. package/out/ping/__next.ping.__PAGE__.txt +10 -0
  109. package/out/ping/__next.ping.txt +5 -0
  110. package/out/ping/index.html +15 -0
  111. package/out/ping/index.txt +21 -0
  112. package/out/pwa-512x512.png +0 -0
  113. package/out/web3/__next._full.txt +21 -0
  114. package/out/web3/__next._head.txt +5 -0
  115. package/out/web3/__next._index.txt +7 -0
  116. package/out/web3/__next._tree.txt +3 -0
  117. package/out/web3/__next.web3.__PAGE__.txt +9 -0
  118. package/out/web3/__next.web3.txt +6 -0
  119. package/out/web3/ed25519/__next._full.txt +20 -0
  120. package/out/web3/ed25519/__next._head.txt +5 -0
  121. package/out/web3/ed25519/__next._index.txt +7 -0
  122. package/out/web3/ed25519/__next._tree.txt +3 -0
  123. package/out/web3/ed25519/__next.web3.ed25519.__PAGE__.txt +6 -0
  124. package/out/web3/ed25519/__next.web3.ed25519.txt +5 -0
  125. package/out/web3/ed25519/__next.web3.txt +6 -0
  126. package/out/web3/ed25519/index.html +1 -0
  127. package/out/web3/ed25519/index.txt +20 -0
  128. package/out/web3/index.html +15 -0
  129. package/out/web3/index.txt +21 -0
  130. package/out/web3/tools/__next._full.txt +20 -0
  131. package/out/web3/tools/__next._head.txt +5 -0
  132. package/out/web3/tools/__next._index.txt +7 -0
  133. package/out/web3/tools/__next._tree.txt +3 -0
  134. package/out/web3/tools/__next.web3.tools.__PAGE__.txt +6 -0
  135. package/out/web3/tools/__next.web3.tools.txt +5 -0
  136. package/out/web3/tools/__next.web3.txt +6 -0
  137. package/out/web3/tools/index.html +1 -0
  138. package/out/web3/tools/index.txt +20 -0
  139. package/package.json +162 -53
  140. package/public/fonts/jetbrains-mono-latin-400-normal.woff2 +0 -0
  141. package/public/fonts/jetbrains-mono-latin-500-normal.woff2 +0 -0
  142. package/public/fonts/jetbrains-mono-latin-600-normal.woff2 +0 -0
  143. package/public/fonts/jetbrains-mono-latin-700-normal.woff2 +0 -0
  144. package/public/pwa-512x512.png +0 -0
  145. package/server/cli.js +3 -0
  146. package/server/index.js +995 -0
  147. package/{src → server/src}/config.js +51 -50
  148. package/{src → server/src}/core/cid.js +157 -150
  149. package/{src → server/src}/index.js +1952 -1669
  150. package/server/src/utils/api.js +68 -0
  151. package/server/src/utils/avatar.js +11 -0
  152. package/{src → server/src}/utils/errors.js +70 -66
  153. package/server/src/utils/mostWallet.js +42 -0
  154. package/server/src/utils/mp.js +105 -0
  155. package/{src → server/src}/utils/security.js +173 -169
  156. package/server/src/utils/userIdentity.js +93 -0
  157. package/cli.js +0 -2
  158. package/out/_next/static/chunks/00l-yd3t8dvwz.js +0 -5
  159. package/out/_next/static/chunks/03k8t3tgym~8~.js +0 -1
  160. package/out/_next/static/chunks/09vfh8lfuacc0.css +0 -1
  161. package/out/_next/static/chunks/0dbhjjzl8qfwv.js +0 -1
  162. package/out/_next/static/chunks/0f73psqhr8dre.css +0 -1
  163. package/out/_next/static/chunks/0fbi7z4_.4j1j.js +0 -1
  164. package/out/_next/static/chunks/0ht900cau6_ur.js +0 -31
  165. package/out/_next/static/chunks/0ohm.ia-4ec60.js +0 -1
  166. package/out/_next/static/chunks/0u5ydb-f0.vxl.js +0 -1
  167. package/out/_next/static/chunks/14t2m1on-s5v~.js +0 -1
  168. package/out/_next/static/chunks/turbopack-076ce9exut_h3.js +0 -1
  169. package/out/_not-found/__next._not-found/__PAGE__.txt +0 -5
  170. package/out/app.css +0 -1535
  171. package/out/bundle.js +0 -107
  172. package/out/bundle.js.map +0 -7
  173. package/out/chat/__next.chat/__PAGE__.txt +0 -9
  174. package/out/chat-page.js +0 -112
  175. package/out/chat.css +0 -378
  176. package/out/index.js +0 -148
  177. package/public/app.css +0 -1535
  178. package/public/bundle.js +0 -107
  179. package/public/bundle.js.map +0 -7
  180. package/public/chat-page.js +0 -112
  181. package/public/chat.css +0 -378
  182. package/public/index.js +0 -148
  183. package/server.js +0 -880
  184. package/src/utils/api.js +0 -6
  185. /package/out/_next/static/{0h4f4QFk_KC9FlSRfQACk → sV38nXrv3xVVO6wvSdFlZ}/_buildManifest.js +0 -0
  186. /package/out/_next/static/{0h4f4QFk_KC9FlSRfQACk → sV38nXrv3xVVO6wvSdFlZ}/_clientMiddlewareManifest.js +0 -0
  187. /package/out/_next/static/{0h4f4QFk_KC9FlSRfQACk → sV38nXrv3xVVO6wvSdFlZ}/_ssgManifest.js +0 -0
package/server.js DELETED
@@ -1,880 +0,0 @@
1
- import http from 'node:http'
2
- import fs from 'node:fs'
3
- import path from 'node:path'
4
- import os from 'node:os'
5
- import { fileURLToPath } from 'node:url'
6
- import { spawn } from 'node:child_process'
7
- import Busboy from 'busboy'
8
- import { WebSocketServer } from 'ws'
9
- import { MostBoxEngine } from './src/index.js'
10
- import { parseMostLink } from './src/core/cid.js'
11
- import { MAX_FILE_SIZE } from './src/config.js'
12
-
13
- const __dirname = path.dirname(fileURLToPath(import.meta.url))
14
- const PORT = Number(process.env.MOSTBOX_PORT || process.env.PORT) || 1976
15
- const HOST = process.env.MOSTBOX_HOST || '127.0.0.1'
16
-
17
- const MAX_JSON_BODY_SIZE = 10 * 1024 * 1024 // 10MB
18
- const MAX_UPLOAD_SIZE = MAX_FILE_SIZE
19
- const UPLOAD_TMP_DIR = path.join(os.tmpdir(), 'most-box-uploads')
20
-
21
- const rateLimitMap = new Map()
22
- const RATE_LIMIT_WINDOW = 60 * 1000
23
- const RATE_LIMIT_MAX_REQUESTS = 120
24
-
25
- let engine = null
26
- let serverInstance = null
27
- let wss = null
28
-
29
- // --- 配置 ---
30
- const CONFIG_DIR = path.join(os.homedir(), '.most-box')
31
- const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json')
32
-
33
- function loadConfig() {
34
- try {
35
- if (fs.existsSync(CONFIG_FILE)) {
36
- return JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf-8'))
37
- }
38
- } catch (err) {
39
- console.error('[Config] Load error:', err.message)
40
- }
41
- return {}
42
- }
43
-
44
- function saveConfig(config) {
45
- try {
46
- if (!fs.existsSync(CONFIG_DIR)) {
47
- fs.mkdirSync(CONFIG_DIR, { recursive: true })
48
- }
49
- const tmpPath = CONFIG_FILE + '.tmp'
50
- fs.writeFileSync(tmpPath, JSON.stringify(config, null, 2), 'utf-8')
51
- fs.renameSync(tmpPath, CONFIG_FILE)
52
- return true
53
- } catch (err) {
54
- console.error('[Config] Save error:', err.message)
55
- return false
56
- }
57
- }
58
-
59
- // --- 存储路径 ---
60
- function getDataPath() {
61
- const config = loadConfig()
62
- return config.dataPath || path.join(os.homedir(), 'most-data')
63
- }
64
-
65
- // --- 速率限制 ---
66
- function checkRateLimit(clientIp) {
67
- const now = Date.now()
68
- if (!rateLimitMap.has(clientIp)) {
69
- rateLimitMap.set(clientIp, [])
70
- }
71
- const requests = rateLimitMap.get(clientIp)
72
- while (requests.length > 0 && requests[0] < now - RATE_LIMIT_WINDOW) {
73
- requests.shift()
74
- }
75
- if (requests.length === 0) {
76
- rateLimitMap.delete(clientIp)
77
- }
78
- if (requests.length >= RATE_LIMIT_MAX_REQUESTS) {
79
- return false
80
- }
81
- requests.push(now)
82
- return true
83
- }
84
-
85
- // --- 静态文件服务 ---
86
- const MIME_TYPES = {
87
- '.html': 'text/html; charset=utf-8',
88
- '.js': 'application/javascript; charset=utf-8',
89
- '.css': 'text/css; charset=utf-8',
90
- '.json': 'application/json',
91
- '.png': 'image/png',
92
- '.jpg': 'image/jpeg',
93
- '.jpeg': 'image/jpeg',
94
- '.gif': 'image/gif',
95
- '.webp': 'image/webp',
96
- '.svg': 'image/svg+xml',
97
- '.ico': 'image/x-icon',
98
- '.mp4': 'video/mp4',
99
- '.webm': 'video/webm',
100
- '.ogg': 'video/ogg',
101
- '.mp3': 'audio/mpeg',
102
- '.wav': 'audio/wav',
103
- '.flac': 'audio/flac',
104
- '.aac': 'audio/aac',
105
- '.m4a': 'audio/mp4',
106
- '.opus': 'audio/opus',
107
- '.woff2': 'font/woff2',
108
- '.woff': 'font/woff'
109
- }
110
-
111
- function getMimeType(fileName) {
112
- const ext = path.extname(fileName).toLowerCase()
113
- return MIME_TYPES[ext] || 'application/octet-stream'
114
- }
115
-
116
- function serveStatic(req, res) {
117
- const publicDir = path.join(__dirname, 'out')
118
- let filePath = req.url === '/' ? '/index.html' : req.url
119
- filePath = filePath.split('?')[0]
120
-
121
- const fullPath = path.join(publicDir, filePath)
122
- const ext = path.extname(fullPath)
123
-
124
- if (!fullPath.startsWith(publicDir)) {
125
- res.writeHead(403)
126
- res.end('Forbidden')
127
- return
128
- }
129
-
130
- fs.readFile(fullPath, (err, data) => {
131
- if (err) {
132
- if (err.code === 'EISDIR') {
133
- const indexPath = path.join(fullPath, 'index.html')
134
- fs.readFile(indexPath, (err2, indexData) => {
135
- if (err2) {
136
- res.writeHead(404)
137
- res.end('Not found')
138
- return
139
- }
140
- res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' })
141
- res.end(indexData)
142
- })
143
- } else {
144
- const indexPath = path.join(publicDir, 'index.html')
145
- fs.readFile(indexPath, (err2, indexData) => {
146
- if (err2) {
147
- res.writeHead(404)
148
- res.end('Not found')
149
- return
150
- }
151
- res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' })
152
- res.end(indexData)
153
- })
154
- }
155
- return
156
- }
157
-
158
- const ext = path.extname(fullPath)
159
- res.writeHead(200, { 'Content-Type': MIME_TYPES[ext] || 'application/octet-stream' })
160
- res.end(data)
161
- })
162
- }
163
-
164
- function decodeFilenameFromHeader(headerStr) {
165
- if (!headerStr) return null
166
-
167
- const filenameStarMatch = headerStr.match(/filename\*=(?:UTF-8''|utf-8'')([^;\r\n]+)/i)
168
- if (filenameStarMatch) {
169
- return decodeURIComponent(filenameStarMatch[1])
170
- }
171
-
172
- const filenameMatch = headerStr.match(/filename="([^"]+)"/)
173
- if (filenameMatch) {
174
- const rawFilename = filenameMatch[1]
175
- try {
176
- const buf = Buffer.from(rawFilename, 'latin1')
177
- const decoded = buf.toString('utf8')
178
- if (decoded.includes('\ufffd')) {
179
- return rawFilename
180
- }
181
- return decoded
182
- } catch {
183
- return rawFilename
184
- }
185
- }
186
-
187
- const filenamePlainMatch = headerStr.match(/filename=([^;\r\n]+)/)
188
- if (filenamePlainMatch) {
189
- return filenamePlainMatch[1].trim()
190
- }
191
- return null
192
- }
193
-
194
- function parseMultipartBusboy(req) {
195
- return new Promise((resolve, reject) => {
196
- if (!fs.existsSync(UPLOAD_TMP_DIR)) {
197
- fs.mkdirSync(UPLOAD_TMP_DIR, { recursive: true })
198
- }
199
-
200
- const busboy = Busboy({
201
- headers: req.headers,
202
- limits: {
203
- fileSize: MAX_UPLOAD_SIZE,
204
- files: 1,
205
- fields: 0
206
- }
207
- })
208
-
209
- const result = { filePath: null, filename: null }
210
- let fileSize = 0
211
- let writeStream = null
212
- let tempPath = null
213
-
214
- busboy.on('file', (name, stream, info) => {
215
- result.filename = decodeFilenameFromHeader(`filename="${info.filename}"`)
216
- tempPath = path.join(UPLOAD_TMP_DIR, `upload_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`)
217
- writeStream = fs.createWriteStream(tempPath)
218
-
219
- stream.on('data', (chunk) => {
220
- fileSize += chunk.length
221
- if (fileSize > MAX_UPLOAD_SIZE) {
222
- stream.destroy()
223
- writeStream.destroy()
224
- fs.unlink(tempPath, () => {})
225
- reject(new Error('File too large'))
226
- return
227
- }
228
- })
229
-
230
- stream.on('error', () => {
231
- if (tempPath) fs.unlink(tempPath, () => {})
232
- })
233
-
234
- stream.pipe(writeStream)
235
-
236
- writeStream.on('finish', () => {
237
- result.filePath = tempPath
238
- resolve(result)
239
- })
240
-
241
- writeStream.on('error', (err) => {
242
- if (tempPath) fs.unlink(tempPath, () => {})
243
- reject(err)
244
- })
245
- })
246
-
247
- busboy.on('error', (err) => {
248
- if (tempPath) fs.unlink(tempPath, () => {})
249
- reject(err)
250
- })
251
-
252
- busboy.on('close', () => {
253
- if (!result.filename) {
254
- resolve(null)
255
- }
256
- })
257
-
258
- req.on('error', (err) => {
259
- if (tempPath) fs.unlink(tempPath, () => {})
260
- reject(err)
261
- })
262
- req.pipe(busboy)
263
- })
264
- }
265
-
266
- // --- JSON 请求体解析器(带大小限制) ---
267
- async function parseJSON(req) {
268
- const chunks = []
269
- let totalSize = 0
270
- for await (const chunk of req) {
271
- totalSize += chunk.length
272
- if (totalSize > MAX_JSON_BODY_SIZE) {
273
- throw new Error('Request body too large')
274
- }
275
- chunks.push(chunk)
276
- }
277
- const text = Buffer.concat(chunks).toString()
278
- if (!text.trim()) {
279
- throw new Error('Empty request body')
280
- }
281
- return JSON.parse(text)
282
- }
283
-
284
- // --- API 路由 ---
285
- async function handleAPI(req, res) {
286
- const url = new URL(req.url, `http://${HOST}:${PORT}`)
287
- const pathname = url.pathname
288
- const method = req.method
289
-
290
- const json = (data, status = 200) => {
291
- res.writeHead(status, { 'Content-Type': 'application/json; charset=utf-8' })
292
- res.end(JSON.stringify(data))
293
- }
294
-
295
- try {
296
- // GET /api/node-id
297
- if (pathname === '/api/node-id' && method === 'GET') {
298
- json({ id: engine.getNodeId() })
299
- return
300
- }
301
-
302
- // GET /api/peer-id
303
- if (pathname === '/api/peer-id' && method === 'GET') {
304
- json({ peerId: engine.getNodeId() })
305
- return
306
- }
307
-
308
- // GET /api/config
309
- if (pathname === '/api/config' && method === 'GET') {
310
- const config = loadConfig()
311
- json({ dataPath: config.dataPath || '' })
312
- return
313
- }
314
-
315
- // POST /api/config — 更新配置
316
- if (pathname === '/api/config' && method === 'POST') {
317
- const body = await parseJSON(req)
318
- const config = loadConfig()
319
-
320
- if (body.resetStorage) {
321
- config.dataPath = ''
322
- } else if (body.dataPath !== undefined) {
323
- let dataPath = body.dataPath.trim()
324
- let basePath = dataPath
325
-
326
- if (dataPath.match(/^[A-Za-z]:\\$/)) {
327
- basePath = dataPath
328
- dataPath = path.join(dataPath, 'most-data')
329
- }
330
-
331
- if (!fs.existsSync(basePath)) {
332
- json({ error: '目录不存在' }, 400)
333
- return
334
- }
335
-
336
- if (!fs.existsSync(dataPath)) {
337
- fs.mkdirSync(dataPath, { recursive: true })
338
- }
339
-
340
- config.dataPath = dataPath
341
- }
342
-
343
- const success = saveConfig(config)
344
- json({ success, dataPath: getDataPath() })
345
- return
346
- }
347
-
348
- // GET /api/config/data-path
349
- if (pathname === '/api/config/data-path' && method === 'GET') {
350
- const config = loadConfig()
351
- const isDefault = !config.dataPath
352
- const dataPath = getDataPath()
353
- json({ dataPath, isDefault })
354
- return
355
- }
356
-
357
- // GET /api/network-status
358
- if (pathname === '/api/network-status' && method === 'GET') {
359
- json(engine.getNetworkStatus())
360
- return
361
- }
362
-
363
- // GET /api/files
364
- if (pathname === '/api/files' && method === 'GET') {
365
- json(engine.listPublishedFiles())
366
- return
367
- }
368
-
369
- // POST /api/publish — multipart 文件上传
370
- if (pathname === '/api/publish' && method === 'POST') {
371
- const result = await parseMultipartBusboy(req)
372
-
373
- if (!result || !result.filename) {
374
- json({ error: 'No file provided' }, 400)
375
- return
376
- }
377
-
378
- try {
379
- const publishResult = await engine.publishFile(result.filePath, result.filename)
380
- json({ success: true, ...publishResult })
381
- } finally {
382
- fs.unlink(result.filePath, () => {})
383
- }
384
- return
385
- }
386
-
387
- // POST /api/download — 从 P2P 开始异步下载
388
- if (pathname === '/api/download' && method === 'POST') {
389
- const body = await parseJSON(req)
390
- if (!body.link) {
391
- json({ error: 'link is required' }, 400)
392
- return
393
- }
394
-
395
- const taskId = `dl_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`
396
-
397
- const parsed = parseMostLink(body.link)
398
- if (parsed.error) {
399
- json({ error: parsed.error }, 400)
400
- return
401
- }
402
-
403
- const existingFile = engine.getPublishedFiles().find(f => f.cid === parsed.cid)
404
- if (existingFile) {
405
- console.log(`[MostBox] File already exists: ${existingFile.fileName}`)
406
- json({ success: true, taskId, alreadyExists: true, fileName: existingFile.fileName })
407
- return
408
- }
409
-
410
- engine.downloadFile(body.link, taskId).catch(err => {
411
- if (err.message === 'Download cancelled') {
412
- wsBroadcast('download:cancelled', { taskId })
413
- } else {
414
- wsBroadcast('download:error', { taskId, error: err.message })
415
- }
416
- })
417
-
418
- json({ success: true, taskId })
419
- return
420
- }
421
-
422
- // POST /api/download/cancel — 取消活动下载
423
- if (pathname === '/api/download/cancel' && method === 'POST') {
424
- const body = await parseJSON(req)
425
- if (!body.taskId) {
426
- json({ error: 'taskId is required' }, 400)
427
- return
428
- }
429
- engine.cancelDownload(body.taskId)
430
- json({ success: true })
431
- return
432
- }
433
-
434
- // DELETE /api/files/:cid
435
- if (pathname.startsWith('/api/files/') && method === 'DELETE') {
436
- const cid = pathname.replace('/api/files/', '').replace(/\/$/, '')
437
- const result = await engine.deletePublishedFile(cid)
438
- json(result)
439
- return
440
- }
441
-
442
- // POST /api/move — 重命名/移动已发布文件
443
- if (pathname === '/api/move' && method === 'POST') {
444
- const body = await parseJSON(req)
445
- if (!body.cid || !body.newFileName) {
446
- json({ error: 'cid and newFileName are required' }, 400)
447
- return
448
- }
449
- try {
450
- const result = engine.moveFile(body.cid, body.newFileName)
451
- json({ success: true, ...result })
452
- } catch (err) {
453
- json({ error: err.message }, 400)
454
- }
455
- return
456
- }
457
-
458
- // POST /api/folder/rename — 重命名文件夹
459
- if (pathname === '/api/folder/rename' && method === 'POST') {
460
- const body = await parseJSON(req)
461
- if (!body.oldPath || !body.newPath) {
462
- json({ error: 'oldPath and newPath are required' }, 400)
463
- return
464
- }
465
- try {
466
- const result = engine.renameFolder(body.oldPath, body.newPath)
467
- json({ success: true, ...result })
468
- } catch (err) {
469
- json({ error: err.message }, 400)
470
- }
471
- return
472
- }
473
-
474
- // GET /api/files/:cid/download — 内联服务文件,支持 Range
475
- if (pathname.match(/^\/api\/files\/[^/]+\/download$/) && method === 'GET') {
476
- const cid = pathname.split('/')[3]
477
- const rangeHeader = req.headers['range']
478
-
479
- try {
480
- if (rangeHeader) {
481
- const rangeMatch = rangeHeader.match(/bytes=(\d+)-(\d*)/)
482
- if (rangeMatch) {
483
- const start = parseInt(rangeMatch[1], 10)
484
- const end = rangeMatch[2] ? parseInt(rangeMatch[2], 10) : undefined
485
-
486
- const offset = start
487
- const limit = end !== undefined ? end - start + 1 : undefined
488
-
489
- const result = await engine.readFileRaw(cid, { offset, limit })
490
- const contentType = getMimeType(result.fileName)
491
-
492
- res.writeHead(206, {
493
- 'Content-Type': contentType,
494
- 'Content-Length': result.buffer.length,
495
- 'Content-Range': `bytes ${offset}-${offset + result.buffer.length - 1}/${result.totalSize}`,
496
- 'Accept-Ranges': 'bytes'
497
- })
498
- res.end(result.buffer)
499
- return
500
- }
501
- }
502
-
503
- const result = await engine.readFileRaw(cid)
504
- const contentType = getMimeType(result.fileName)
505
- res.writeHead(200, {
506
- 'Content-Type': contentType,
507
- 'Content-Length': result.totalSize,
508
- 'Accept-Ranges': 'bytes',
509
- 'Content-Disposition': `inline; filename="${encodeURIComponent(result.fileName)}"`
510
- })
511
- res.end(result.buffer)
512
- } catch (err) {
513
- if (err.message === 'File not found') {
514
- json({ error: err.message }, 404)
515
- } else {
516
- json({ error: err.message }, 400)
517
- }
518
- }
519
- return
520
- }
521
-
522
- // POST /api/shutdown — 优雅关闭服务器(仅允许 localhost 连接)
523
- if (pathname === '/api/shutdown' && method === 'POST') {
524
- const clientIp = req.socket.remoteAddress
525
- const isLocalhost = clientIp === '127.0.0.1' || clientIp === '::1' || clientIp === '::ffff:127.0.0.1'
526
- if (!isLocalhost) {
527
- json({ error: 'Forbidden' }, 403)
528
- return
529
- }
530
- json({ success: true })
531
- console.log('[MostBox] Shutdown requested via API...')
532
- setTimeout(async () => {
533
- await engine.stop()
534
- serverInstance.close()
535
- console.log('[MostBox] Server stopped.')
536
- process.exit(0)
537
- }, 100)
538
- return
539
- }
540
-
541
- // GET /api/trash — 列出回收站文件
542
- if (pathname === '/api/trash' && method === 'GET') {
543
- json(engine.listTrashFiles())
544
- return
545
- }
546
-
547
- // POST /api/trash/:cid/restore — 从回收站恢复文件
548
- if (pathname.match(/^\/api\/trash\/[^/]+\/restore$/) && method === 'POST') {
549
- const cid = pathname.split('/')[3]
550
- try {
551
- const result = engine.restoreTrashFile(cid)
552
- json({ success: true, files: result })
553
- } catch (err) {
554
- json({ error: err.message }, 400)
555
- }
556
- return
557
- }
558
-
559
- // DELETE /api/trash/:cid — 永久删除回收站文件
560
- if (pathname.match(/^\/api\/trash\/[^/]+$/) && method === 'DELETE') {
561
- const cid = pathname.split('/')[3]
562
- const result = await engine.permanentDeleteTrashFile(cid)
563
- json({ success: true, trashFiles: result })
564
- return
565
- }
566
-
567
- // DELETE /api/trash — 清空回收站
568
- if (pathname === '/api/trash' && method === 'DELETE') {
569
- const result = await engine.emptyTrash()
570
- json({ success: true, trashFiles: result })
571
- return
572
- }
573
-
574
- // POST /api/files/:cid/star — 切换星标状态
575
- if (pathname.match(/^\/api\/files\/[^/]+\/star$/) && method === 'POST') {
576
- const cid = pathname.split('/')[3]
577
- try {
578
- const result = engine.toggleStarred(cid)
579
- json({ success: true, ...result })
580
- } catch (err) {
581
- json({ error: err.message }, 400)
582
- }
583
- return
584
- }
585
-
586
- // GET /api/storage — 获取存储统计信息
587
- if (pathname === '/api/storage' && method === 'GET') {
588
- const result = await engine.getStorageStats()
589
- json(result)
590
- return
591
- }
592
-
593
- // GET /api/display-name — 获取显示名
594
- if (pathname === '/api/display-name' && method === 'GET') {
595
- json({ displayName: engine.getDisplayName() })
596
- return
597
- }
598
-
599
- // POST /api/display-name — 设置显示名
600
- if (pathname === '/api/display-name' && method === 'POST') {
601
- const body = await parseJSON(req)
602
- if (!body.name || !body.name.trim()) {
603
- json({ error: 'name is required' }, 400)
604
- return
605
- }
606
- const success = engine.setDisplayName(body.name)
607
- json({ success, displayName: engine.getDisplayName() })
608
- return
609
- }
610
-
611
- // POST /api/channels — 创建/加入频道
612
- if (pathname === '/api/channels' && method === 'POST') {
613
- const body = await parseJSON(req)
614
- if (!body.name || !body.name.trim()) {
615
- json({ error: 'name is required' }, 400)
616
- return
617
- }
618
- try {
619
- const result = await engine.createChannel(body.name.trim(), body.type || 'personal')
620
- json({ success: true, ...result })
621
- } catch (err) {
622
- json({ error: err.message }, 400)
623
- }
624
- return
625
- }
626
-
627
- // GET /api/channels — 获取频道列表
628
- if (pathname === '/api/channels' && method === 'GET') {
629
- json(engine.listChannels())
630
- return
631
- }
632
-
633
- // DELETE /api/channels/:name — 离开频道
634
- if (pathname.startsWith('/api/channels/') && method === 'DELETE') {
635
- const name = pathname.split('/')[3]
636
- try {
637
- const result = await engine.leaveChannel(name)
638
- json({ success: true, channels: result })
639
- } catch (err) {
640
- json({ error: err.message }, 400)
641
- }
642
- return
643
- }
644
-
645
- // GET /api/channels/:name/messages — 获取频道消息
646
- if (pathname.match(/^\/api\/channels\/[^/]+\/messages$/) && method === 'GET') {
647
- const name = pathname.split('/')[3]
648
- const urlObj = new URL(req.url, `http://${HOST}:${PORT}`)
649
- const limit = parseInt(urlObj.searchParams.get('limit') || '100', 10)
650
- const offset = parseInt(urlObj.searchParams.get('offset') || '0', 10)
651
- try {
652
- const messages = await engine.getChannelMessages(name, { limit, offset })
653
- json(messages)
654
- } catch (err) {
655
- json({ error: err.message }, 400)
656
- }
657
- return
658
- }
659
-
660
- // POST /api/channels/:name/messages — 发送消息
661
- if (pathname.match(/^\/api\/channels\/[^/]+\/messages$/) && method === 'POST') {
662
- const name = pathname.split('/')[3]
663
- const body = await parseJSON(req)
664
- if (!body.content || !body.content.trim()) {
665
- json({ error: 'content is required' }, 400)
666
- return
667
- }
668
- try {
669
- const message = await engine.sendMessage(name, body.content, body.authorName)
670
- json({ success: true, message })
671
- } catch (err) {
672
- json({ error: err.message }, 400)
673
- }
674
- return
675
- }
676
-
677
- // GET /api/channels/:name/peers — 获取频道内在线用户
678
- if (pathname.match(/^\/api\/channels\/[^/]+\/peers$/) && method === 'GET') {
679
- const name = pathname.split('/')[3]
680
- json(engine.getChannelPeers(name))
681
- return
682
- }
683
-
684
- json({ error: 'Not found' }, 404)
685
- } catch (err) {
686
- console.error('[API Error]', err)
687
- json({ error: err.message, code: err.code }, 500)
688
- }
689
- }
690
-
691
- function wsBroadcast(event, data) {
692
- const payload = JSON.stringify({ event, data })
693
- if (wss) {
694
- wss.clients.forEach((client) => {
695
- if (client.readyState === 1) {
696
- try { client.send(payload) } catch {}
697
- }
698
- })
699
- }
700
- }
701
-
702
- const channelSubscriptions = new Map()
703
-
704
- function wsSendToChannel(channelName, event, data) {
705
- const payload = JSON.stringify({ event, data })
706
- const subscribers = channelSubscriptions.get(channelName)
707
- if (subscribers) {
708
- subscribers.forEach((ws) => {
709
- if (ws.readyState === 1) {
710
- try { ws.send(payload) } catch {}
711
- }
712
- })
713
- }
714
- }
715
-
716
- // --- 主函数 ---
717
- async function main() {
718
- console.log('[MostBox] Starting core daemon...')
719
-
720
- if (fs.existsSync(UPLOAD_TMP_DIR)) {
721
- const staleFiles = fs.readdirSync(UPLOAD_TMP_DIR)
722
- for (const file of staleFiles) {
723
- try { fs.unlinkSync(path.join(UPLOAD_TMP_DIR, file)) } catch {}
724
- }
725
- console.log(`[MostBox] Cleaned ${staleFiles.length} stale upload temp files`)
726
- }
727
-
728
- const dataPath = getDataPath()
729
- console.log(`[MostBox] Storage: ${dataPath}`)
730
-
731
- engine = new MostBoxEngine({ dataPath })
732
-
733
- engine.on('download:progress', (data) => wsBroadcast('download:progress', data))
734
- engine.on('download:status', (data) => wsBroadcast('download:status', data))
735
- engine.on('download:success', (data) => wsBroadcast('download:success', data))
736
- engine.on('download:cancelled', (data) => wsBroadcast('download:cancelled', data))
737
- engine.on('publish:progress', (data) => wsBroadcast('publish:progress', data))
738
- engine.on('publish:success', (data) => wsBroadcast('publish:success', data))
739
- engine.on('connection', () => {
740
- wsBroadcast('network:status', engine.getNetworkStatus())
741
- })
742
- engine.on('channel:message', (data) => wsSendToChannel(data.channel, 'channel:message', data))
743
- engine.on('channel:peer:online', (data) => wsBroadcast('channel:peer:online', data))
744
- engine.on('channel:peer:offline', (data) => wsBroadcast('channel:peer:offline', data))
745
- engine.on('channel:joined', (data) => wsBroadcast('channel:joined', data))
746
- engine.on('channel:left', (data) => wsBroadcast('channel:left', data))
747
-
748
- await engine.start()
749
- console.log('[MostBox] Engine ready')
750
-
751
- serverInstance = http.createServer((req, res) => {
752
- res.setHeader('Access-Control-Allow-Origin', `http://${HOST}:${PORT}`)
753
- res.setHeader('Access-Control-Allow-Methods', 'GET, POST, DELETE, OPTIONS')
754
- res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization')
755
-
756
- if (req.method === 'OPTIONS') {
757
- res.writeHead(204)
758
- res.end()
759
- return
760
- }
761
-
762
- const clientIp = req.socket.remoteAddress || 'unknown'
763
-
764
- if (!checkRateLimit(clientIp)) {
765
- res.writeHead(429, { 'Content-Type': 'application/json' })
766
- res.end(JSON.stringify({ error: 'Too many requests' }))
767
- return
768
- }
769
-
770
- if (req.url.startsWith('/api/')) {
771
- handleAPI(req, res).catch(err => {
772
- console.error('[Unhandled API Error]', err)
773
- if (!res.headersSent) {
774
- res.writeHead(500, { 'Content-Type': 'application/json' })
775
- res.end(JSON.stringify({ error: 'Internal server error' }))
776
- }
777
- })
778
- } else {
779
- serveStatic(req, res)
780
- }
781
- })
782
-
783
- wss = new WebSocketServer({ noServer: true })
784
- wss.on('connection', (ws) => {
785
- ws.on('error', () => {})
786
- ws.on('close', () => {
787
- for (const [channelName, subscribers] of channelSubscriptions) {
788
- if (subscribers.has(ws)) {
789
- subscribers.delete(ws)
790
- if (subscribers.size === 0) {
791
- channelSubscriptions.delete(channelName)
792
- }
793
- }
794
- }
795
- })
796
- ws.on('message', (raw) => {
797
- try {
798
- const msg = JSON.parse(raw)
799
- const { event, data } = msg
800
-
801
- switch (event) {
802
- case 'register':
803
- ws.peerId = data.peerId
804
- break
805
- case 'channel:subscribe':
806
- if (data.channel) {
807
- const channelName = data.channel
808
- if (!channelSubscriptions.has(channelName)) {
809
- channelSubscriptions.set(channelName, new Set())
810
- }
811
- channelSubscriptions.get(channelName).add(ws)
812
- }
813
- break
814
- case 'channel:unsubscribe':
815
- if (data.channel) {
816
- const channelName = data.channel
817
- const subscribers = channelSubscriptions.get(channelName)
818
- if (subscribers) {
819
- subscribers.delete(ws)
820
- if (subscribers.size === 0) {
821
- channelSubscriptions.delete(channelName)
822
- }
823
- }
824
- }
825
- break
826
- }
827
- } catch (err) {
828
- console.error('[WS Message Error]', err.message)
829
- }
830
- })
831
- })
832
-
833
- serverInstance.on('upgrade', (req, socket, head) => {
834
- if (req.url.startsWith('/ws')) {
835
- wss.handleUpgrade(req, socket, head, (ws) => {
836
- wss.emit('connection', ws, req)
837
- })
838
- } else {
839
- socket.destroy()
840
- }
841
- })
842
-
843
- serverInstance.listen(PORT, HOST, () => {
844
- const url = `http://${HOST}:${PORT}`
845
- console.log(`[MostBox] Server running at ${url}`)
846
-
847
- if (process.platform === 'win32') {
848
- spawn('cmd.exe', ['/c', 'start', '', url], {
849
- detached: true,
850
- stdio: 'ignore'
851
- }).unref()
852
- } else {
853
- const cmd = process.platform === 'darwin' ? 'open' : 'xdg-open'
854
- spawn(cmd, [url], {
855
- detached: true,
856
- stdio: 'ignore'
857
- }).unref()
858
- }
859
- })
860
-
861
- process.on('SIGINT', async () => {
862
- console.log('\n[MostBox] Shutting down...')
863
- await engine.stop()
864
- if (wss) wss.close()
865
- serverInstance.close()
866
- process.exit(0)
867
- })
868
-
869
- process.on('SIGTERM', async () => {
870
- await engine.stop()
871
- if (wss) wss.close()
872
- serverInstance.close()
873
- process.exit(0)
874
- })
875
- }
876
-
877
- main().catch(err => {
878
- console.error('[MostBox] Fatal error:', err)
879
- process.exit(1)
880
- })