most-box 0.2.1 → 0.2.2
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 +66 -6
- package/electron/deepLink.js +29 -0
- package/electron/main.js +87 -20
- package/out/admin/index.html +0 -0
- package/out/app/index.html +0 -0
- package/out/assets/AppShell-BOtfY12t.js +1 -0
- package/out/assets/FilePreviewOverlay-C5qK9HAE.js +1 -0
- package/out/assets/LanguageToggle-Di5b88mK.js +1 -0
- package/out/assets/MarketingHeader-BOytKcCc.js +1 -0
- package/out/assets/MarketingLayout-B8m1Q7Pa.js +1 -0
- package/out/assets/{MarketingThemeToggle-qlwCZU1o.js → MarketingThemeToggle-C6fggkl7.js} +1 -1
- package/out/assets/{MilkdownEditor-_TGlDyA_.js → MilkdownEditor-Cfze75zl.js} +1 -1
- package/out/assets/OpenSidebarButton-DDuALgJ2.js +1 -0
- package/out/assets/SidebarAccount-bo1ypzrJ.js +1 -0
- package/out/assets/arrow-right-RldN7Rwi.js +1 -0
- package/out/assets/circle-alert-BQoSDxUe.js +1 -0
- package/out/assets/cloud-DHoTMeZY.js +1 -0
- package/out/assets/copy-BtJHJnqH.js +1 -0
- package/out/assets/download-Bg-OdoxM.js +1 -0
- package/out/assets/downloadValidation-CUvbvj9f.js +1 -0
- package/out/assets/external-link-m8ZIQe4p.js +1 -0
- package/out/assets/file-text-DG1orIkZ.js +1 -0
- package/out/assets/game-s6irY8hS.js +1 -0
- package/out/assets/hard-drive-BB-sllXA.js +1 -0
- package/out/assets/index-3OD3Chi9.css +1 -0
- package/out/assets/index-BSOvFG3o.css +1 -0
- package/out/assets/index-C0xqKeu-.css +1 -0
- package/out/assets/index-CrAXrmfP.js +31 -0
- package/out/assets/index.lazy-0Njp0U6I.js +1 -0
- package/out/assets/index.lazy-BTndBeBF.js +1 -0
- package/out/assets/index.lazy-Bq39jYTl.js +1 -0
- package/out/assets/index.lazy-Bu93oGzJ.js +2 -0
- package/out/assets/index.lazy-BvdBwgHx.js +1 -0
- package/out/assets/index.lazy-CIx8ist6.js +3 -0
- package/out/assets/index.lazy-DK7R297q.js +1 -0
- package/out/assets/index.lazy-DVOcrcEB.js +2 -0
- package/out/assets/index.lazy-DejOniAm.js +1 -0
- package/out/assets/index.lazy-DfsgyiMN.js +1 -0
- package/out/assets/index.lazy-DpNIiSXF.js +1 -0
- package/out/assets/index.lazy-Vnvz-t7T.js +1 -0
- package/out/assets/index.lazy-v7nBJ-sF.js +1 -0
- package/out/assets/key-round-DIQ3Xt5F.js +1 -0
- package/out/assets/lock-tf1t2yuy.js +1 -0
- package/out/assets/message-square-DaQH7q-P.js +1 -0
- package/out/assets/mp-DvFTsIL9.js +1 -0
- package/out/assets/music-DaFvU2DL.js +1 -0
- package/out/assets/notebook-pen-CqEFOHKx.js +1 -0
- package/out/assets/play-B7q6F75-.js +1 -0
- package/out/assets/plus-H2i2mspM.js +1 -0
- package/out/assets/refresh-cw-roxAhABl.js +1 -0
- package/out/assets/save-DR0O9ReR.js +1 -0
- package/out/assets/search-ncblG-zw.js +1 -0
- package/out/assets/send-m1XCcuPn.js +1 -0
- package/out/assets/shield-check-BwcvTU4U.js +1 -0
- package/out/assets/trash-2-CNpsqYc1.js +1 -0
- package/out/assets/triangle-alert-DP9EP7IM.js +1 -0
- package/out/assets/upload-V--8p13l.js +1 -0
- package/out/assets/useChannelMessages-46C52EyL.js +3 -0
- package/out/assets/useGameRoom-BtxPpfck.js +1 -0
- package/out/assets/useNavigate-DWlBD_-b.js +1 -0
- package/out/assets/userStore-C4vdYsQp.js +4 -0
- package/out/assets/wallet-CozFU6yK.js +1 -0
- package/out/assets/wifi-DIR3g_8A.js +1 -0
- package/out/chat/index.html +0 -0
- package/out/chat/join/index.html +0 -0
- package/out/download/index.html +2 -2
- package/out/game/gandengyan/index.html +0 -0
- package/out/game/index.html +0 -0
- package/out/game/zhajinhua/index.html +0 -0
- package/out/index.html +2 -2
- package/out/note/index.html +0 -0
- package/out/ping/index.html +2 -2
- package/out/profile/index.html +0 -0
- package/out/web3/index.html +0 -0
- package/package.json +9 -1
- package/server/index.js +36 -7
- package/server/src/core/channelIdentity.js +6 -0
- package/server/src/core/gameRoom.js +15 -5
- package/server/src/core/mostLink.js +8 -7
- package/server/src/core/zhajinhua.js +6 -0
- package/server/src/games/gandengyan.js +10 -0
- package/server/src/http/access.js +63 -12
- package/server/src/http/app.js +34 -838
- package/server/src/http/nodeStatus.js +101 -9
- package/server/src/http/routePolicy.js +2 -0
- package/server/src/http/routes/channelRoutes.js +163 -0
- package/server/src/http/routes/fileRoutes.js +345 -0
- package/server/src/http/routes/nodeRoutes.js +323 -0
- package/server/src/http/routes/seedRoutes.js +58 -0
- package/server/src/index.js +197 -16
- package/server/src/node/config.js +2 -6
- package/server/src/utils/avatar.js +30 -15
- package/server/src/utils/downloadMessages.js +0 -2
- package/out/assets/AppShell-OiOEqXPr.js +0 -1
- package/out/assets/ChatUi-Cif5LRF3.js +0 -1
- package/out/assets/CopyButton-Dm7krgbq.js +0 -1
- package/out/assets/LanguageToggle-B4ZNuBCV.js +0 -1
- package/out/assets/MarketingHeader-yIZuQP7m.js +0 -1
- package/out/assets/MarketingLayout-DVH0Nx7S.js +0 -1
- package/out/assets/MoveModal-BVr4Q7-b.js +0 -1
- package/out/assets/Nav-5xeettNJ.js +0 -1
- package/out/assets/NoteSidebar-DpniUKmy.js +0 -1
- package/out/assets/OpenSidebarButton-BfgG2HIT.js +0 -1
- package/out/assets/PemBlock-CxwIepth.js +0 -1
- package/out/assets/SidebarAccount-Zg5DZblE.js +0 -1
- package/out/assets/arrow-right-CL9YSDVS.js +0 -1
- package/out/assets/channelApi-DNdJfsJ-.js +0 -1
- package/out/assets/chevron-down-CnLh_-aO.js +0 -1
- package/out/assets/circle-alert-oiiRDvhx.js +0 -1
- package/out/assets/cloud-BEe2N89j.js +0 -1
- package/out/assets/code-9LB8QqxL.js +0 -1
- package/out/assets/copy-giX4rmFJ.js +0 -1
- package/out/assets/download-D0oMEYQZ.js +0 -1
- package/out/assets/downloadValidation-Bk1VsBBo.js +0 -1
- package/out/assets/external-link-Cm2WCUxv.js +0 -1
- package/out/assets/filePreview-BZ50vZZf.js +0 -1
- package/out/assets/game-Bvz4dspe.js +0 -1
- package/out/assets/hard-drive-B3CQbcp2.js +0 -1
- package/out/assets/index-8eWJAjpY.css +0 -1
- package/out/assets/index-BZc4blbW.css +0 -1
- package/out/assets/index-BkZvz4WA.css +0 -1
- package/out/assets/index-WCK14Vja.js +0 -34
- package/out/assets/index.lazy-5Q6GuMNT.js +0 -1
- package/out/assets/index.lazy-5jq6EFXa.js +0 -3
- package/out/assets/index.lazy-7n1Q-NrA.js +0 -3
- package/out/assets/index.lazy-BFnOyQFj.js +0 -1
- package/out/assets/index.lazy-B_oPp6qK.js +0 -1
- package/out/assets/index.lazy-BvY50KVz.js +0 -1
- package/out/assets/index.lazy-C0Kn_amZ.js +0 -1
- package/out/assets/index.lazy-C3cek3Gn.js +0 -1
- package/out/assets/index.lazy-CLpPkdy1.js +0 -1
- package/out/assets/index.lazy-Cpr1kApf.js +0 -2
- package/out/assets/index.lazy-CuwLZiUK.js +0 -1
- package/out/assets/index.lazy-DDc3Ylgf.js +0 -2
- package/out/assets/index.lazy-Dg3aqOss.js +0 -1
- package/out/assets/key-round-CzuljhND.js +0 -1
- package/out/assets/lock-D2NhNoJW.js +0 -1
- package/out/assets/message-square-DwBq_Go5.js +0 -1
- package/out/assets/mp-Bln2MB9G.js +0 -1
- package/out/assets/music-CB73K5Gz.js +0 -1
- package/out/assets/notebook-pen-Up7r5zoI.js +0 -1
- package/out/assets/play-OszVgROb.js +0 -1
- package/out/assets/plus-BbxQG_Ai.js +0 -1
- package/out/assets/save-CiqyiifY.js +0 -1
- package/out/assets/search-gqAPOsgS.js +0 -1
- package/out/assets/send-vwCWsZGP.js +0 -1
- package/out/assets/shield-check-CxWxsNLc.js +0 -1
- package/out/assets/trash-2-DNGr8IgF.js +0 -1
- package/out/assets/triangle-alert-B_1BlX1b.js +0 -1
- package/out/assets/upload-Dxl7GUzb.js +0 -1
- package/out/assets/useChannelMessages-7bYKXU_R.js +0 -3
- package/out/assets/useGameRoom-DqA1mkfk.js +0 -1
- package/out/assets/wallet-DlkawdPJ.js +0 -1
- package/out/assets/wifi-sBOKcPFM.js +0 -1
- package/out/demo/index.html +0 -0
- /package/out/avatars/default/{ocean.svg → dolphin.svg} +0 -0
- /package/out/avatars/default/{violet.svg → owl.svg} +0 -0
- /package/out/avatars/default/{mint.svg → panda.svg} +0 -0
- /package/out/avatars/default/{dusk.svg → snow-mountain.svg} +0 -0
- /package/out/avatars/default/{ember.svg → tiger.svg} +0 -0
- /package/out/avatars/default/{sage.svg → turtle.svg} +0 -0
- /package/public/avatars/default/{ocean.svg → dolphin.svg} +0 -0
- /package/public/avatars/default/{violet.svg → owl.svg} +0 -0
- /package/public/avatars/default/{mint.svg → panda.svg} +0 -0
- /package/public/avatars/default/{dusk.svg → snow-mountain.svg} +0 -0
- /package/public/avatars/default/{ember.svg → tiger.svg} +0 -0
- /package/public/avatars/default/{sage.svg → turtle.svg} +0 -0
package/server/src/http/app.js
CHANGED
|
@@ -2,14 +2,11 @@ import fs from 'node:fs'
|
|
|
2
2
|
import path from 'node:path'
|
|
3
3
|
import { Hono } from 'hono'
|
|
4
4
|
import { cors } from 'hono/cors'
|
|
5
|
-
import {
|
|
6
|
-
import { sanitizeFilename } from '../utils/security.js'
|
|
7
|
-
import { normalizeAddress, verifyAuthHeader } from '../utils/auth.js'
|
|
5
|
+
import { verifyAuthHeader } from '../utils/auth.js'
|
|
8
6
|
import {
|
|
9
7
|
DEFAULT_NODE_HOST,
|
|
10
8
|
DEFAULT_NODE_PORT,
|
|
11
9
|
createNodeConfigStore,
|
|
12
|
-
evaluateStorageLimits,
|
|
13
10
|
normalizeRemoteInvites,
|
|
14
11
|
} from '../node/config.js'
|
|
15
12
|
import { createNodeLogger } from '../node/logs.js'
|
|
@@ -20,29 +17,21 @@ import {
|
|
|
20
17
|
hasValidInvite,
|
|
21
18
|
isLocalRequest,
|
|
22
19
|
isLocalUpgradeRequest,
|
|
23
|
-
isLoopbackRemoteAddress,
|
|
24
20
|
isPublicListenHost,
|
|
25
21
|
isRemoteAccessRequest,
|
|
26
|
-
remoteInviteConfigured,
|
|
27
22
|
} from './access.js'
|
|
28
|
-
import {
|
|
29
|
-
import { resolveDataPathForSave } from './dataPath.js'
|
|
30
|
-
import { listFilteredNodeLogs } from './nodeLogs.js'
|
|
31
|
-
import {
|
|
32
|
-
buildNodeStatus,
|
|
33
|
-
buildOpenApiSpec,
|
|
34
|
-
getNetworkAddresses,
|
|
35
|
-
getPackageVersion,
|
|
36
|
-
} from './nodeStatus.js'
|
|
23
|
+
import { buildNodeStatus } from './nodeStatus.js'
|
|
37
24
|
import { createRateLimitMiddleware } from './rateLimit.js'
|
|
38
25
|
import {
|
|
39
|
-
validationErrorPayload,
|
|
40
26
|
isPublicFileDownloadPath,
|
|
41
27
|
requiresUserAuth,
|
|
42
28
|
isAdminApi,
|
|
43
29
|
} from './routePolicy.js'
|
|
44
|
-
import {
|
|
45
|
-
import {
|
|
30
|
+
import { registerStaticRoutes } from './staticFiles.js'
|
|
31
|
+
import { registerChannelRoutes } from './routes/channelRoutes.js'
|
|
32
|
+
import { registerFileRoutes } from './routes/fileRoutes.js'
|
|
33
|
+
import { registerNodeRoutes } from './routes/nodeRoutes.js'
|
|
34
|
+
import { registerSeedRoutes } from './routes/seedRoutes.js'
|
|
46
35
|
|
|
47
36
|
export { UPLOAD_TMP_DIR } from './uploads.js'
|
|
48
37
|
|
|
@@ -65,6 +54,8 @@ export function createApp(engine, options = {}) {
|
|
|
65
54
|
options.nodeLogger || createNodeLogger(configStore.configDir || CONFIG_DIR)
|
|
66
55
|
const wssRef = options.wssRef || { current: null }
|
|
67
56
|
const serverInstanceRef = options.serverInstanceRef || { current: null }
|
|
57
|
+
const trustPrivateNetwork =
|
|
58
|
+
options.trustPrivateNetwork ?? isPublicListenHost(appHost)
|
|
68
59
|
function getRemoteInviteSet() {
|
|
69
60
|
const invites =
|
|
70
61
|
options.remoteInvites === undefined
|
|
@@ -83,7 +74,7 @@ export function createApp(engine, options = {}) {
|
|
|
83
74
|
invite: c.req.header('x-mostbox-invite'),
|
|
84
75
|
origin: c.req.header('origin'),
|
|
85
76
|
listenHost: appHost,
|
|
86
|
-
local: isLocalRequest(c),
|
|
77
|
+
local: isLocalRequest(c, { trustPrivateNetwork }),
|
|
87
78
|
})
|
|
88
79
|
}
|
|
89
80
|
|
|
@@ -148,7 +139,12 @@ export function createApp(engine, options = {}) {
|
|
|
148
139
|
|
|
149
140
|
async function broadcastNodeStatus() {
|
|
150
141
|
try {
|
|
151
|
-
const status = await buildNodeStatus(
|
|
142
|
+
const status = await buildNodeStatus(
|
|
143
|
+
engine,
|
|
144
|
+
configStore,
|
|
145
|
+
appPort,
|
|
146
|
+
appHost
|
|
147
|
+
)
|
|
152
148
|
wsBroadcast('node:status', status)
|
|
153
149
|
return status
|
|
154
150
|
} catch (err) {
|
|
@@ -220,7 +216,7 @@ export function createApp(engine, options = {}) {
|
|
|
220
216
|
invite,
|
|
221
217
|
origin: req.headers.origin,
|
|
222
218
|
listenHost: appHost,
|
|
223
|
-
local: isLocalUpgradeRequest(req),
|
|
219
|
+
local: isLocalUpgradeRequest(req, { trustPrivateNetwork }),
|
|
224
220
|
})
|
|
225
221
|
if (!remote) return true
|
|
226
222
|
|
|
@@ -283,824 +279,24 @@ export function createApp(engine, options = {}) {
|
|
|
283
279
|
return c.json({ error: err.message, code: err.code }, 500)
|
|
284
280
|
})
|
|
285
281
|
|
|
286
|
-
// ---
|
|
287
|
-
app
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
adminAvailable: !isRemoteRequest(c),
|
|
301
|
-
listenHost: appHost,
|
|
302
|
-
})
|
|
303
|
-
})
|
|
304
|
-
|
|
305
|
-
app.get('/api/config', c => {
|
|
306
|
-
const config = configStore.loadRawConfig()
|
|
307
|
-
return c.json({ dataPath: config.dataPath || '' })
|
|
308
|
-
})
|
|
309
|
-
|
|
310
|
-
app.post('/api/config', async c => {
|
|
311
|
-
const body = await c.req.json()
|
|
312
|
-
const patch = {}
|
|
313
|
-
|
|
314
|
-
if (body.resetStorage) {
|
|
315
|
-
patch.dataPath = ''
|
|
316
|
-
} else if (body.dataPath !== undefined) {
|
|
317
|
-
const resolved = resolveDataPathForSave(body.dataPath)
|
|
318
|
-
if (resolved.error) return c.json({ error: resolved.error }, 400)
|
|
319
|
-
patch.dataPath = resolved.dataPath
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
const { success } = configStore.saveNodeConfigPatch(patch)
|
|
323
|
-
appendNodeLog({
|
|
324
|
-
event: 'node:config:updated',
|
|
325
|
-
message: 'Node config updated',
|
|
326
|
-
data: { dataPath: getDataPath(configStore) },
|
|
327
|
-
})
|
|
328
|
-
await broadcastNodeStatus()
|
|
329
|
-
return c.json({ success, dataPath: getDataPath(configStore) })
|
|
330
|
-
})
|
|
331
|
-
|
|
332
|
-
app.get('/api/config/data-path', c => {
|
|
333
|
-
const config = configStore.getNodeConfig()
|
|
334
|
-
const isDefault = !config.dataPath
|
|
335
|
-
const dataPath = getDataPath(configStore)
|
|
336
|
-
return c.json({ dataPath, isDefault })
|
|
337
|
-
})
|
|
338
|
-
|
|
339
|
-
app.get('/api/node/status', async c => {
|
|
340
|
-
try {
|
|
341
|
-
return c.json(await buildNodeStatus(engine, configStore, appPort))
|
|
342
|
-
} catch (err) {
|
|
343
|
-
return errorJson(c, err)
|
|
344
|
-
}
|
|
345
|
-
})
|
|
346
|
-
|
|
347
|
-
app.get('/api/node/config', c => {
|
|
348
|
-
const config = configStore.getNodeConfig()
|
|
349
|
-
return c.json({
|
|
350
|
-
...config,
|
|
351
|
-
dataPath: getDataPath(configStore),
|
|
352
|
-
configuredDataPath: config.dataPath,
|
|
353
|
-
isDefaultDataPath: !config.dataPath,
|
|
354
|
-
currentHost: appHost,
|
|
355
|
-
currentPort: appPort,
|
|
356
|
-
remoteInvites: config.remoteInvites,
|
|
357
|
-
})
|
|
358
|
-
})
|
|
359
|
-
|
|
360
|
-
app.post('/api/node/config', async c => {
|
|
361
|
-
const body = await c.req.json()
|
|
362
|
-
const patch = { ...body }
|
|
363
|
-
|
|
364
|
-
if (body.resetStorage) {
|
|
365
|
-
patch.dataPath = ''
|
|
366
|
-
} else if (body.dataPath !== undefined) {
|
|
367
|
-
const resolved = resolveDataPathForSave(body.dataPath)
|
|
368
|
-
if (resolved.error) return c.json({ error: resolved.error }, 400)
|
|
369
|
-
patch.dataPath = resolved.dataPath
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
const { success, config } = configStore.saveNodeConfigPatch(patch)
|
|
373
|
-
engine.setMaxFileSize(config.maxFileSizeBytes)
|
|
374
|
-
appendNodeLog({
|
|
375
|
-
event: 'node:config:updated',
|
|
376
|
-
message: 'Node daemon config updated',
|
|
377
|
-
data: {
|
|
378
|
-
dataPath: getDataPath(configStore),
|
|
379
|
-
port: config.port,
|
|
380
|
-
capacityBytes: config.capacityBytes,
|
|
381
|
-
remoteInviteCount: config.remoteInvites.length,
|
|
382
|
-
},
|
|
383
|
-
})
|
|
384
|
-
await broadcastNodeStatus()
|
|
385
|
-
return c.json({ success, ...config, dataPath: getDataPath(configStore) })
|
|
386
|
-
})
|
|
387
|
-
|
|
388
|
-
app.get('/api/node/policy', c => {
|
|
389
|
-
const config = configStore.getNodeConfig()
|
|
390
|
-
return c.json({
|
|
391
|
-
maxFileSizeBytes: config.maxFileSizeBytes,
|
|
392
|
-
})
|
|
393
|
-
})
|
|
394
|
-
|
|
395
|
-
app.post('/api/node/policy', async c => {
|
|
396
|
-
const body = await c.req.json()
|
|
397
|
-
const { success, config } = configStore.saveNodeConfigPatch({
|
|
398
|
-
maxFileSizeBytes: body.maxFileSizeBytes,
|
|
399
|
-
})
|
|
400
|
-
engine.setMaxFileSize(config.maxFileSizeBytes)
|
|
401
|
-
const policy = {
|
|
402
|
-
maxFileSizeBytes: config.maxFileSizeBytes,
|
|
403
|
-
}
|
|
404
|
-
appendNodeLog({
|
|
405
|
-
event: 'node:policy:updated',
|
|
406
|
-
message: 'Node storage limits updated',
|
|
407
|
-
data: policy,
|
|
408
|
-
})
|
|
409
|
-
await broadcastNodeStatus()
|
|
410
|
-
return c.json({ success, ...policy })
|
|
411
|
-
})
|
|
412
|
-
|
|
413
|
-
app.post('/api/node/policy/evaluate', async c => {
|
|
414
|
-
const body = await c.req.json()
|
|
415
|
-
const decision = evaluateStorageLimits(configStore.getNodeConfig(), body)
|
|
416
|
-
return c.json(decision)
|
|
417
|
-
})
|
|
418
|
-
|
|
419
|
-
app.get('/api/node/logs', c => {
|
|
420
|
-
const limit = Number(c.req.query('limit') || 100)
|
|
421
|
-
const filter = c.req.query('filter') || 'all'
|
|
422
|
-
const query = c.req.query('q') || ''
|
|
423
|
-
const result = listFilteredNodeLogs(nodeLogger, { limit, filter, query })
|
|
424
|
-
return c.json({
|
|
425
|
-
logFile: nodeLogger.logFile,
|
|
426
|
-
filter: result.filter,
|
|
427
|
-
query: result.query,
|
|
428
|
-
logs: result.logs,
|
|
429
|
-
})
|
|
430
|
-
})
|
|
431
|
-
|
|
432
|
-
app.delete('/api/node/logs', c => {
|
|
433
|
-
const success = nodeLogger.clear()
|
|
434
|
-
const clearedAt = new Date().toISOString()
|
|
435
|
-
wsBroadcast('node:logs:cleared', { clearedAt })
|
|
436
|
-
return c.json({ success, clearedAt })
|
|
437
|
-
})
|
|
438
|
-
|
|
439
|
-
app.get('/api/node/diagnostics', async c => {
|
|
440
|
-
try {
|
|
441
|
-
const status = await buildNodeStatus(engine, configStore, appPort)
|
|
442
|
-
return c.json({
|
|
443
|
-
generatedAt: new Date().toISOString(),
|
|
444
|
-
packageVersion: getPackageVersion(),
|
|
445
|
-
platform: process.platform,
|
|
446
|
-
nodeVersion: process.version,
|
|
447
|
-
status,
|
|
448
|
-
logFile: nodeLogger.logFile,
|
|
449
|
-
logs: nodeLogger.list(200),
|
|
450
|
-
})
|
|
451
|
-
} catch (err) {
|
|
452
|
-
return errorJson(c, err)
|
|
453
|
-
}
|
|
454
|
-
})
|
|
455
|
-
|
|
456
|
-
app.get('/api/admin/users', c => {
|
|
457
|
-
return c.json({ users: engine.listUsers() })
|
|
458
|
-
})
|
|
459
|
-
|
|
460
|
-
app.post('/api/user/sync/start', async c => {
|
|
461
|
-
try {
|
|
462
|
-
const body = await c.req.json()
|
|
463
|
-
const result = await engine.startUserSync(c.get('userAddress'), body)
|
|
464
|
-
appendNodeLog({
|
|
465
|
-
event: 'node:user-sync:started',
|
|
466
|
-
message: 'User sync started',
|
|
467
|
-
data: {
|
|
468
|
-
ownerAddress: result.ownerAddress,
|
|
469
|
-
syncName: result.syncName,
|
|
470
|
-
},
|
|
471
|
-
})
|
|
472
|
-
return c.json({ success: true, ...result })
|
|
473
|
-
} catch (err) {
|
|
474
|
-
return errorJson(c, err)
|
|
475
|
-
}
|
|
476
|
-
})
|
|
477
|
-
|
|
478
|
-
app.get('/api/user/sync/status', c => {
|
|
479
|
-
try {
|
|
480
|
-
return c.json(engine.getUserSyncStatus(c.get('userAddress')))
|
|
481
|
-
} catch (err) {
|
|
482
|
-
return errorJson(c, err)
|
|
483
|
-
}
|
|
484
|
-
})
|
|
485
|
-
|
|
486
|
-
app.delete('/api/admin/users/:address/data', async c => {
|
|
487
|
-
const address = normalizeAddress(c.req.param('address'))
|
|
488
|
-
if (!address) {
|
|
489
|
-
return c.json({ error: 'valid address is required' }, 400)
|
|
490
|
-
}
|
|
491
|
-
try {
|
|
492
|
-
const result = await engine.clearUserData(address)
|
|
493
|
-
appendNodeLog({
|
|
494
|
-
event: 'node:user-data:cleared',
|
|
495
|
-
message: 'User data cleared',
|
|
496
|
-
data: result,
|
|
497
|
-
})
|
|
498
|
-
await broadcastNodeStatus()
|
|
499
|
-
return c.json({ success: true, ...result })
|
|
500
|
-
} catch (err) {
|
|
501
|
-
return errorJson(c, err)
|
|
502
|
-
}
|
|
503
|
-
})
|
|
504
|
-
|
|
505
|
-
app.get('/api/openapi.json', c => {
|
|
506
|
-
return c.json(buildOpenApiSpec(appPort))
|
|
507
|
-
})
|
|
508
|
-
|
|
509
|
-
// --- 网络路由 ---
|
|
510
|
-
app.get('/api/network-status', c => {
|
|
511
|
-
return c.json(engine.getNetworkStatus())
|
|
512
|
-
})
|
|
513
|
-
|
|
514
|
-
app.get('/api/network', c => {
|
|
515
|
-
return c.json(getNetworkAddresses(appPort))
|
|
516
|
-
})
|
|
517
|
-
|
|
518
|
-
// --- 节点保种路由 ---
|
|
519
|
-
app.get('/api/node/holdings', c => {
|
|
520
|
-
try {
|
|
521
|
-
return c.json(engine.listHoldings())
|
|
522
|
-
} catch (err) {
|
|
523
|
-
return errorJson(c, err)
|
|
524
|
-
}
|
|
525
|
-
})
|
|
526
|
-
|
|
527
|
-
app.post('/api/node/holdings', async c => {
|
|
528
|
-
try {
|
|
529
|
-
const body = await c.req.json()
|
|
530
|
-
const holding = await engine.addHolding(body)
|
|
531
|
-
appendNodeLog({
|
|
532
|
-
event: 'node:holding:added',
|
|
533
|
-
message: 'Node holding added',
|
|
534
|
-
data: { cid: holding.cid, size: holding.size },
|
|
535
|
-
})
|
|
536
|
-
await broadcastNodeStatus()
|
|
537
|
-
return c.json({ success: true, holding })
|
|
538
|
-
} catch (err) {
|
|
539
|
-
return errorJson(c, err)
|
|
540
|
-
}
|
|
541
|
-
})
|
|
542
|
-
|
|
543
|
-
app.post('/api/p2p/pull', async c => {
|
|
544
|
-
try {
|
|
545
|
-
const body = await c.req.json()
|
|
546
|
-
const timeout =
|
|
547
|
-
body.timeout === undefined ? undefined : Number(body.timeout)
|
|
548
|
-
const result = await engine.pullByCid({
|
|
549
|
-
...body,
|
|
550
|
-
timeout: Number.isFinite(timeout) && timeout > 0 ? timeout : undefined,
|
|
551
|
-
})
|
|
552
|
-
appendNodeLog({
|
|
553
|
-
event: 'node:pull:success',
|
|
554
|
-
message: 'P2P pull completed',
|
|
555
|
-
data: { cid: result.cid, taskId: result.taskId },
|
|
556
|
-
})
|
|
557
|
-
await broadcastNodeStatus()
|
|
558
|
-
return c.json({ success: true, ...result })
|
|
559
|
-
} catch (err) {
|
|
560
|
-
appendNodeLog({
|
|
561
|
-
level: 'error',
|
|
562
|
-
event: 'node:pull:error',
|
|
563
|
-
message: err.message,
|
|
564
|
-
data: { code: err.code || 'UNKNOWN' },
|
|
565
|
-
})
|
|
566
|
-
return errorJson(c, err)
|
|
567
|
-
}
|
|
568
|
-
})
|
|
569
|
-
|
|
570
|
-
// --- 文件路由 ---
|
|
571
|
-
app.get('/api/files', c => {
|
|
572
|
-
return c.json(
|
|
573
|
-
engine.listPublishedFiles({ ownerAddress: c.get('userAddress') })
|
|
574
|
-
)
|
|
575
|
-
})
|
|
576
|
-
|
|
577
|
-
app.post('/api/publish', async c => {
|
|
578
|
-
const req = c.env.incoming
|
|
579
|
-
const result = await parseMultipartBusboy(
|
|
580
|
-
req,
|
|
581
|
-
configStore.getNodeConfig().maxFileSizeBytes
|
|
582
|
-
)
|
|
583
|
-
|
|
584
|
-
if (!result || !result.filename) {
|
|
585
|
-
return c.json({ error: 'No file provided' }, 400)
|
|
586
|
-
}
|
|
587
|
-
|
|
588
|
-
try {
|
|
589
|
-
const publishResult = await engine.publishFile(
|
|
590
|
-
result.filePath,
|
|
591
|
-
result.filename,
|
|
592
|
-
{ ownerAddress: c.get('userAddress') }
|
|
593
|
-
)
|
|
594
|
-
return c.json({ success: true, ...publishResult })
|
|
595
|
-
} finally {
|
|
596
|
-
fs.unlink(result.filePath, () => {})
|
|
597
|
-
}
|
|
598
|
-
})
|
|
599
|
-
|
|
600
|
-
app.post('/api/download/check', async c => {
|
|
601
|
-
const body = await c.req.json()
|
|
602
|
-
if (!body.link) {
|
|
603
|
-
return c.json({ error: 'link is required' }, 400)
|
|
604
|
-
}
|
|
605
|
-
|
|
606
|
-
const parsed = parseMostLink(body.link)
|
|
607
|
-
if (parsed.errorCode) {
|
|
608
|
-
return c.json(
|
|
609
|
-
validationErrorPayload(parsed.errorCode, parsed.details),
|
|
610
|
-
400
|
|
611
|
-
)
|
|
612
|
-
}
|
|
613
|
-
|
|
614
|
-
const localAvailability = await engine.getLocalCidAvailability(body.link, {
|
|
615
|
-
ownerAddress: c.get('userAddress'),
|
|
616
|
-
})
|
|
617
|
-
if (localAvailability) {
|
|
618
|
-
return c.json({
|
|
619
|
-
success: true,
|
|
620
|
-
available: true,
|
|
621
|
-
cid: parsed.cid,
|
|
622
|
-
fileName: localAvailability.fileName,
|
|
623
|
-
size: Number(localAvailability.size) || null,
|
|
624
|
-
alreadyExists: true,
|
|
625
|
-
})
|
|
626
|
-
}
|
|
627
|
-
|
|
628
|
-
if (engine.hasDownloadNameConflict(parsed.fileName)) {
|
|
629
|
-
return c.json(
|
|
630
|
-
{
|
|
631
|
-
error: `已有同名文件: ${parsed.fileName}`,
|
|
632
|
-
code: 'CONFLICT',
|
|
633
|
-
},
|
|
634
|
-
409
|
|
635
|
-
)
|
|
636
|
-
}
|
|
637
|
-
|
|
638
|
-
try {
|
|
639
|
-
const timeout =
|
|
640
|
-
body.timeout === undefined ? undefined : Number(body.timeout)
|
|
641
|
-
const result = await engine.checkDownloadAvailability(body.link, {
|
|
642
|
-
ownerAddress: c.get('userAddress'),
|
|
643
|
-
timeout: Number.isFinite(timeout) && timeout > 0 ? timeout : undefined,
|
|
644
|
-
})
|
|
645
|
-
return c.json({ success: true, ...result })
|
|
646
|
-
} catch (err) {
|
|
647
|
-
return errorJson(c, err)
|
|
648
|
-
}
|
|
649
|
-
})
|
|
650
|
-
|
|
651
|
-
app.post('/api/download', async c => {
|
|
652
|
-
const body = await c.req.json()
|
|
653
|
-
if (!body.link) {
|
|
654
|
-
return c.json({ error: 'link is required' }, 400)
|
|
655
|
-
}
|
|
656
|
-
|
|
657
|
-
const taskId = `dl_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`
|
|
658
|
-
|
|
659
|
-
const parsed = parseMostLink(body.link)
|
|
660
|
-
if (parsed.errorCode) {
|
|
661
|
-
return c.json(
|
|
662
|
-
validationErrorPayload(parsed.errorCode, parsed.details),
|
|
663
|
-
400
|
|
664
|
-
)
|
|
665
|
-
}
|
|
666
|
-
|
|
667
|
-
const localAvailability = await engine.getLocalCidAvailability(body.link, {
|
|
668
|
-
ownerAddress: c.get('userAddress'),
|
|
669
|
-
})
|
|
670
|
-
if (localAvailability) {
|
|
671
|
-
console.log(
|
|
672
|
-
`[MostBox] CID content already exists locally: ${parsed.cid}`
|
|
673
|
-
)
|
|
674
|
-
try {
|
|
675
|
-
const result = await engine.downloadFile(body.link, taskId, {
|
|
676
|
-
ownerAddress: c.get('userAddress'),
|
|
677
|
-
})
|
|
678
|
-
return c.json({ success: true, ...result })
|
|
679
|
-
} catch (err) {
|
|
680
|
-
return errorJson(c, err)
|
|
681
|
-
}
|
|
682
|
-
}
|
|
683
|
-
|
|
684
|
-
if (engine.hasDownloadNameConflict(parsed.fileName)) {
|
|
685
|
-
return c.json(
|
|
686
|
-
{
|
|
687
|
-
error: `已有同名文件: ${parsed.fileName}`,
|
|
688
|
-
code: 'CONFLICT',
|
|
689
|
-
},
|
|
690
|
-
409
|
|
691
|
-
)
|
|
692
|
-
}
|
|
693
|
-
|
|
694
|
-
engine
|
|
695
|
-
.downloadFile(body.link, taskId, { ownerAddress: c.get('userAddress') })
|
|
696
|
-
.catch(err => {
|
|
697
|
-
if (err.message === 'Download cancelled') {
|
|
698
|
-
wsBroadcast('download:cancelled', { taskId })
|
|
699
|
-
} else {
|
|
700
|
-
wsBroadcast('download:error', { taskId, error: err.message })
|
|
701
|
-
}
|
|
702
|
-
})
|
|
703
|
-
|
|
704
|
-
return c.json({ success: true, taskId })
|
|
705
|
-
})
|
|
706
|
-
|
|
707
|
-
app.post('/api/download/cancel', async c => {
|
|
708
|
-
const body = await c.req.json()
|
|
709
|
-
if (!body.taskId) {
|
|
710
|
-
return c.json({ error: 'taskId is required' }, 400)
|
|
711
|
-
}
|
|
712
|
-
engine.cancelDownload(body.taskId)
|
|
713
|
-
return c.json({ success: true })
|
|
714
|
-
})
|
|
715
|
-
|
|
716
|
-
app.delete('/api/files/:cid', async c => {
|
|
717
|
-
const cid = c.req.param('cid')
|
|
718
|
-
const cidValidation = validateCidString(cid)
|
|
719
|
-
if (!cidValidation.valid) {
|
|
720
|
-
return c.json(validationErrorPayload(cidValidation.errorCode), 400)
|
|
721
|
-
}
|
|
722
|
-
const result = await engine.deletePublishedFile(cid, {
|
|
723
|
-
ownerAddress: c.get('userAddress'),
|
|
724
|
-
})
|
|
725
|
-
return c.json(result)
|
|
726
|
-
})
|
|
727
|
-
|
|
728
|
-
app.post('/api/files/:cid/cache', async c => {
|
|
729
|
-
const cid = c.req.param('cid')
|
|
730
|
-
const cidValidation = validateCidString(cid)
|
|
731
|
-
if (!cidValidation.valid) {
|
|
732
|
-
return c.json(validationErrorPayload(cidValidation.errorCode), 400)
|
|
733
|
-
}
|
|
734
|
-
try {
|
|
735
|
-
const body = await c.req.json().catch(() => ({}))
|
|
736
|
-
const timeout =
|
|
737
|
-
body.timeout === undefined ? undefined : Number(body.timeout)
|
|
738
|
-
const result = await engine.cacheFile(cid, {
|
|
739
|
-
ownerAddress: c.get('userAddress'),
|
|
740
|
-
timeout: Number.isFinite(timeout) && timeout > 0 ? timeout : undefined,
|
|
741
|
-
taskId: body.taskId,
|
|
742
|
-
})
|
|
743
|
-
return c.json({ success: true, ...result })
|
|
744
|
-
} catch (err) {
|
|
745
|
-
return badRequestOrAppError(c, err)
|
|
746
|
-
}
|
|
747
|
-
})
|
|
748
|
-
|
|
749
|
-
app.post('/api/move', async c => {
|
|
750
|
-
const body = await c.req.json()
|
|
751
|
-
if (!body.cid || !body.newFileName) {
|
|
752
|
-
return c.json({ error: 'cid and newFileName are required' }, 400)
|
|
753
|
-
}
|
|
754
|
-
const cidValidation = validateCidString(body.cid)
|
|
755
|
-
if (!cidValidation.valid) {
|
|
756
|
-
return c.json(validationErrorPayload(cidValidation.errorCode), 400)
|
|
757
|
-
}
|
|
758
|
-
const cleanFileName = sanitizeFilename(body.newFileName)
|
|
759
|
-
if (
|
|
760
|
-
!cleanFileName ||
|
|
761
|
-
cleanFileName === 'unnamed' ||
|
|
762
|
-
body.newFileName.length > 255
|
|
763
|
-
) {
|
|
764
|
-
return c.json({ error: 'Invalid filename' }, 400)
|
|
765
|
-
}
|
|
766
|
-
try {
|
|
767
|
-
const result = engine.moveFile(body.cid, cleanFileName, {
|
|
768
|
-
ownerAddress: c.get('userAddress'),
|
|
769
|
-
})
|
|
770
|
-
return c.json({ success: true, ...result })
|
|
771
|
-
} catch (err) {
|
|
772
|
-
return badRequestOrAppError(c, err)
|
|
773
|
-
}
|
|
774
|
-
})
|
|
775
|
-
|
|
776
|
-
app.get('/api/files/:cid/download', async c => {
|
|
777
|
-
const cid = c.req.param('cid')
|
|
778
|
-
const cidValidation = validateCidString(cid)
|
|
779
|
-
if (!cidValidation.valid) {
|
|
780
|
-
return c.json(validationErrorPayload(cidValidation.errorCode), 400)
|
|
781
|
-
}
|
|
782
|
-
|
|
783
|
-
const rangeHeader = c.req.header('range')
|
|
784
|
-
|
|
785
|
-
try {
|
|
786
|
-
if (rangeHeader) {
|
|
787
|
-
const rangeMatch = rangeHeader.match(/bytes=(\d+)-(\d*)/)
|
|
788
|
-
if (rangeMatch) {
|
|
789
|
-
const start = parseInt(rangeMatch[1], 10)
|
|
790
|
-
const end = rangeMatch[2] ? parseInt(rangeMatch[2], 10) : undefined
|
|
791
|
-
const offset = start
|
|
792
|
-
const limit = end !== undefined ? end - start + 1 : undefined
|
|
793
|
-
|
|
794
|
-
const result = await engine.readFileRaw(cid, {
|
|
795
|
-
offset,
|
|
796
|
-
limit,
|
|
797
|
-
public: true,
|
|
798
|
-
})
|
|
799
|
-
const contentType = getMimeType(result.fileName)
|
|
800
|
-
|
|
801
|
-
c.header('Content-Type', contentType)
|
|
802
|
-
c.header('Content-Length', String(result.buffer.length))
|
|
803
|
-
c.header(
|
|
804
|
-
'Content-Range',
|
|
805
|
-
`bytes ${offset}-${offset + result.buffer.length - 1}/${result.totalSize}`
|
|
806
|
-
)
|
|
807
|
-
c.header('Accept-Ranges', 'bytes')
|
|
808
|
-
c.status(206)
|
|
809
|
-
return c.body(result.buffer)
|
|
810
|
-
}
|
|
811
|
-
}
|
|
812
|
-
|
|
813
|
-
const result = await engine.readFileRaw(cid, {
|
|
814
|
-
public: true,
|
|
815
|
-
})
|
|
816
|
-
const contentType = getMimeType(result.fileName)
|
|
817
|
-
c.header('Content-Type', contentType)
|
|
818
|
-
c.header('Content-Length', String(result.totalSize))
|
|
819
|
-
c.header('Accept-Ranges', 'bytes')
|
|
820
|
-
c.header(
|
|
821
|
-
'Content-Disposition',
|
|
822
|
-
`inline; filename="${encodeURIComponent(result.fileName)}"`
|
|
823
|
-
)
|
|
824
|
-
return c.body(result.buffer)
|
|
825
|
-
} catch (err) {
|
|
826
|
-
if (err.message === 'File not found') {
|
|
827
|
-
return c.json({ error: err.message }, 404)
|
|
828
|
-
}
|
|
829
|
-
return c.json({ error: err.message }, 400)
|
|
830
|
-
}
|
|
831
|
-
})
|
|
832
|
-
|
|
833
|
-
// --- 回收站路由 ---
|
|
834
|
-
app.get('/api/trash', c => {
|
|
835
|
-
return c.json(engine.listTrashFiles({ ownerAddress: c.get('userAddress') }))
|
|
836
|
-
})
|
|
837
|
-
|
|
838
|
-
app.post('/api/trash/:cid/restore', async c => {
|
|
839
|
-
const cid = c.req.param('cid')
|
|
840
|
-
const cidValidation = validateCidString(cid)
|
|
841
|
-
if (!cidValidation.valid) {
|
|
842
|
-
return c.json(validationErrorPayload(cidValidation.errorCode), 400)
|
|
843
|
-
}
|
|
844
|
-
try {
|
|
845
|
-
const result = await engine.restoreTrashFile(cid, {
|
|
846
|
-
ownerAddress: c.get('userAddress'),
|
|
847
|
-
})
|
|
848
|
-
return c.json({ success: true, files: result })
|
|
849
|
-
} catch (err) {
|
|
850
|
-
return c.json({ error: err.message }, 400)
|
|
851
|
-
}
|
|
852
|
-
})
|
|
853
|
-
|
|
854
|
-
app.delete('/api/trash/:cid', async c => {
|
|
855
|
-
const cid = c.req.param('cid')
|
|
856
|
-
const cidValidation = validateCidString(cid)
|
|
857
|
-
if (!cidValidation.valid) {
|
|
858
|
-
return c.json(validationErrorPayload(cidValidation.errorCode), 400)
|
|
859
|
-
}
|
|
860
|
-
const result = await engine.permanentDeleteTrashFile(cid, {
|
|
861
|
-
ownerAddress: c.get('userAddress'),
|
|
862
|
-
})
|
|
863
|
-
return c.json({ success: true, trashFiles: result })
|
|
864
|
-
})
|
|
865
|
-
|
|
866
|
-
app.delete('/api/trash', async c => {
|
|
867
|
-
const result = await engine.emptyTrash({
|
|
868
|
-
ownerAddress: c.get('userAddress'),
|
|
869
|
-
})
|
|
870
|
-
return c.json({ success: true, trashFiles: result })
|
|
871
|
-
})
|
|
872
|
-
|
|
873
|
-
// --- 收藏路由 ---
|
|
874
|
-
app.post('/api/files/:cid/star', async c => {
|
|
875
|
-
const cid = c.req.param('cid')
|
|
876
|
-
const cidValidation = validateCidString(cid)
|
|
877
|
-
if (!cidValidation.valid) {
|
|
878
|
-
return c.json(validationErrorPayload(cidValidation.errorCode), 400)
|
|
879
|
-
}
|
|
880
|
-
try {
|
|
881
|
-
const result = engine.toggleStarred(cid, {
|
|
882
|
-
ownerAddress: c.get('userAddress'),
|
|
883
|
-
})
|
|
884
|
-
return c.json({ success: true, ...result })
|
|
885
|
-
} catch (err) {
|
|
886
|
-
return c.json({ error: err.message }, 400)
|
|
887
|
-
}
|
|
888
|
-
})
|
|
889
|
-
|
|
890
|
-
// --- 显示名路由 ---
|
|
891
|
-
app.get('/api/display-name', c => {
|
|
892
|
-
return c.json({ displayName: engine.getDisplayName() })
|
|
893
|
-
})
|
|
894
|
-
|
|
895
|
-
app.post('/api/display-name', async c => {
|
|
896
|
-
const body = await c.req.json()
|
|
897
|
-
if (!body.name || !body.name.trim()) {
|
|
898
|
-
return c.json({ error: 'name is required' }, 400)
|
|
899
|
-
}
|
|
900
|
-
const trimmed = body.name.trim()
|
|
901
|
-
if (trimmed.length > 100) {
|
|
902
|
-
return c.json({ error: 'Name too long (max 100 chars)' }, 400)
|
|
903
|
-
}
|
|
904
|
-
if (/[<>]/.test(trimmed)) {
|
|
905
|
-
return c.json({ error: 'Name contains invalid characters' }, 400)
|
|
906
|
-
}
|
|
907
|
-
const success = engine.setDisplayName(trimmed)
|
|
908
|
-
return c.json({ success, displayName: engine.getDisplayName() })
|
|
909
|
-
})
|
|
910
|
-
|
|
911
|
-
// --- 频道路由 ---
|
|
912
|
-
app.post('/api/channels', async c => {
|
|
913
|
-
const body = await c.req.json()
|
|
914
|
-
if (!body.name || !body.name.trim()) {
|
|
915
|
-
return c.json({ error: 'name is required' }, 400)
|
|
916
|
-
}
|
|
917
|
-
try {
|
|
918
|
-
const result = await engine.createChannel(
|
|
919
|
-
body.name.trim(),
|
|
920
|
-
body.type || 'personal',
|
|
921
|
-
{
|
|
922
|
-
ownerAddress: c.get('userAddress'),
|
|
923
|
-
displayName: body.displayName,
|
|
924
|
-
avatar: body.avatar,
|
|
925
|
-
discover: true,
|
|
926
|
-
}
|
|
927
|
-
)
|
|
928
|
-
return c.json({ success: true, ...result })
|
|
929
|
-
} catch (err) {
|
|
930
|
-
return c.json({ error: err.message }, 400)
|
|
931
|
-
}
|
|
932
|
-
})
|
|
933
|
-
|
|
934
|
-
app.get('/api/channels', c => {
|
|
935
|
-
return c.json(
|
|
936
|
-
engine.listChannels({
|
|
937
|
-
ownerAddress: c.get('userAddress'),
|
|
938
|
-
type: c.req.query('type'),
|
|
939
|
-
excludeType: c.req.query('excludeType'),
|
|
940
|
-
})
|
|
941
|
-
)
|
|
942
|
-
})
|
|
943
|
-
|
|
944
|
-
const leaveChannelForRequest = async (c, channelIdentifier) => {
|
|
945
|
-
const name = String(channelIdentifier || '').trim()
|
|
946
|
-
if (!name) {
|
|
947
|
-
return c.json({ error: '频道标识不能为空' }, 400)
|
|
948
|
-
}
|
|
949
|
-
try {
|
|
950
|
-
const result = await engine.leaveChannel(name, {
|
|
951
|
-
ownerAddress: c.get('userAddress'),
|
|
952
|
-
})
|
|
953
|
-
return c.json({ success: true, channels: result })
|
|
954
|
-
} catch (err) {
|
|
955
|
-
return c.json({ error: err.message }, 400)
|
|
956
|
-
}
|
|
957
|
-
}
|
|
958
|
-
|
|
959
|
-
app.delete('/api/channels', async c => {
|
|
960
|
-
const body = await c.req.json().catch(() => ({}))
|
|
961
|
-
return leaveChannelForRequest(c, body.channelKey || body.name)
|
|
962
|
-
})
|
|
963
|
-
|
|
964
|
-
app.get('/api/channels/:name/messages', async c => {
|
|
965
|
-
const name = c.req.param('name')
|
|
966
|
-
const limit = parseInt(c.req.query('limit') || '100', 10)
|
|
967
|
-
const offset = parseInt(c.req.query('offset') || '0', 10)
|
|
968
|
-
try {
|
|
969
|
-
const messages = await engine.getChannelMessages(name, {
|
|
970
|
-
limit,
|
|
971
|
-
offset,
|
|
972
|
-
ownerAddress: c.get('userAddress'),
|
|
973
|
-
})
|
|
974
|
-
return c.json(messages)
|
|
975
|
-
} catch (err) {
|
|
976
|
-
return badRequestOrAppError(c, err)
|
|
977
|
-
}
|
|
978
|
-
})
|
|
979
|
-
|
|
980
|
-
app.get('/api/channels/:name/members', c => {
|
|
981
|
-
try {
|
|
982
|
-
return c.json(
|
|
983
|
-
engine.getChannelMembers(c.req.param('name'), {
|
|
984
|
-
ownerAddress: c.get('userAddress'),
|
|
985
|
-
})
|
|
986
|
-
)
|
|
987
|
-
} catch (err) {
|
|
988
|
-
return badRequestOrAppError(c, err)
|
|
989
|
-
}
|
|
990
|
-
})
|
|
991
|
-
|
|
992
|
-
app.post('/api/channels/:name/messages', async c => {
|
|
993
|
-
const name = c.req.param('name')
|
|
994
|
-
const body = await c.req.json()
|
|
995
|
-
if (!body.content || !body.content.trim()) {
|
|
996
|
-
return c.json({ error: 'content is required' }, 400)
|
|
997
|
-
}
|
|
998
|
-
if (!body.author || !body.authorName) {
|
|
999
|
-
return c.json({ error: 'author and authorName are required' }, 400)
|
|
1000
|
-
}
|
|
1001
|
-
if (!/^0x[a-fA-F0-9]{40}$/.test(body.author)) {
|
|
1002
|
-
return c.json({ error: 'Invalid author format' }, 400)
|
|
1003
|
-
}
|
|
1004
|
-
if (normalizeAddress(body.author) !== c.get('userAddress')) {
|
|
1005
|
-
return c.json({ error: 'message author must match logged-in user' }, 403)
|
|
1006
|
-
}
|
|
1007
|
-
if (body.authorName.length > 50) {
|
|
1008
|
-
return c.json({ error: 'authorName too long' }, 400)
|
|
1009
|
-
}
|
|
1010
|
-
try {
|
|
1011
|
-
const message = await engine.sendMessage(
|
|
1012
|
-
name,
|
|
1013
|
-
body.content,
|
|
1014
|
-
body.author,
|
|
1015
|
-
body.authorName,
|
|
1016
|
-
{
|
|
1017
|
-
ownerAddress: c.get('userAddress'),
|
|
1018
|
-
attachment: body.attachment,
|
|
1019
|
-
avatar: body.avatar,
|
|
1020
|
-
}
|
|
1021
|
-
)
|
|
1022
|
-
return c.json({ success: true, message })
|
|
1023
|
-
} catch (err) {
|
|
1024
|
-
return badRequestOrAppError(c, err)
|
|
1025
|
-
}
|
|
1026
|
-
})
|
|
1027
|
-
|
|
1028
|
-
app.get('/api/channels/:name/peers', c => {
|
|
1029
|
-
try {
|
|
1030
|
-
return c.json(
|
|
1031
|
-
engine.getChannelPeers(c.req.param('name'), {
|
|
1032
|
-
ownerAddress: c.get('userAddress'),
|
|
1033
|
-
})
|
|
1034
|
-
)
|
|
1035
|
-
} catch (err) {
|
|
1036
|
-
return badRequestOrAppError(c, err)
|
|
1037
|
-
}
|
|
1038
|
-
})
|
|
1039
|
-
|
|
1040
|
-
app.put('/api/channels/:name/remark', async c => {
|
|
1041
|
-
const name = c.req.param('name')
|
|
1042
|
-
const body = await c.req.json()
|
|
1043
|
-
try {
|
|
1044
|
-
const remark = engine.setChannelRemark(name, body.remark, {
|
|
1045
|
-
ownerAddress: c.get('userAddress'),
|
|
1046
|
-
})
|
|
1047
|
-
return c.json({ success: true, remark })
|
|
1048
|
-
} catch (err) {
|
|
1049
|
-
return c.json({ error: err.message }, 400)
|
|
1050
|
-
}
|
|
1051
|
-
})
|
|
1052
|
-
|
|
1053
|
-
app.put('/api/channels/:name/pin', async c => {
|
|
1054
|
-
const name = c.req.param('name')
|
|
1055
|
-
const body = await c.req.json()
|
|
1056
|
-
try {
|
|
1057
|
-
const pinned = engine.setChannelPinned(name, Boolean(body.pinned), {
|
|
1058
|
-
ownerAddress: c.get('userAddress'),
|
|
1059
|
-
})
|
|
1060
|
-
return c.json({ success: true, pinned })
|
|
1061
|
-
} catch (err) {
|
|
1062
|
-
return c.json({ error: err.message }, 400)
|
|
1063
|
-
}
|
|
1064
|
-
})
|
|
1065
|
-
|
|
1066
|
-
// --- 文件夹重命名 ---
|
|
1067
|
-
app.post('/api/folder/rename', async c => {
|
|
1068
|
-
const body = await c.req.json()
|
|
1069
|
-
if (!body.oldPath || !body.newPath) {
|
|
1070
|
-
return c.json({ error: 'oldPath and newPath are required' }, 400)
|
|
1071
|
-
}
|
|
1072
|
-
if (body.oldPath.length > 500 || body.newPath.length > 500) {
|
|
1073
|
-
return c.json({ error: 'Path too long' }, 400)
|
|
1074
|
-
}
|
|
1075
|
-
if (body.oldPath.includes('..') || body.newPath.includes('..')) {
|
|
1076
|
-
return c.json({ error: 'Path traversal not allowed' }, 400)
|
|
1077
|
-
}
|
|
1078
|
-
try {
|
|
1079
|
-
const result = engine.renameFolder(body.oldPath, body.newPath, {
|
|
1080
|
-
ownerAddress: c.get('userAddress'),
|
|
1081
|
-
})
|
|
1082
|
-
return c.json({ success: true, ...result })
|
|
1083
|
-
} catch (err) {
|
|
1084
|
-
return badRequestOrAppError(c, err)
|
|
1085
|
-
}
|
|
1086
|
-
})
|
|
1087
|
-
|
|
1088
|
-
// --- 关机路由 ---
|
|
1089
|
-
app.post('/api/shutdown', c => {
|
|
1090
|
-
const clientIp = c.env.incoming?.socket?.remoteAddress || 'unknown'
|
|
1091
|
-
if (!isLoopbackRemoteAddress(clientIp)) {
|
|
1092
|
-
return c.json({ error: 'Forbidden' }, 403)
|
|
1093
|
-
}
|
|
1094
|
-
c.json({ success: true })
|
|
1095
|
-
console.log('[MostBox] Shutdown requested via API...')
|
|
1096
|
-
setTimeout(async () => {
|
|
1097
|
-
await engine.stop()
|
|
1098
|
-
if (serverInstanceRef.current) serverInstanceRef.current.close()
|
|
1099
|
-
console.log('[MostBox] Server stopped.')
|
|
1100
|
-
process.exit(0)
|
|
1101
|
-
}, 100)
|
|
1102
|
-
return c.body(null)
|
|
282
|
+
// --- API 路由注册 ---
|
|
283
|
+
registerNodeRoutes(app, {
|
|
284
|
+
engine,
|
|
285
|
+
appPort,
|
|
286
|
+
appHost,
|
|
287
|
+
configStore,
|
|
288
|
+
nodeLogger,
|
|
289
|
+
getDataPath,
|
|
290
|
+
getRemoteInviteSet,
|
|
291
|
+
isRemoteRequest,
|
|
292
|
+
appendNodeLog,
|
|
293
|
+
broadcastNodeStatus,
|
|
294
|
+
wsBroadcast,
|
|
295
|
+
serverInstanceRef,
|
|
1103
296
|
})
|
|
297
|
+
registerSeedRoutes(app, { engine, appendNodeLog, broadcastNodeStatus })
|
|
298
|
+
registerFileRoutes(app, { engine, configStore, wsBroadcast })
|
|
299
|
+
registerChannelRoutes(app, { engine })
|
|
1104
300
|
|
|
1105
301
|
registerStaticRoutes(app)
|
|
1106
302
|
|