most-box 0.3.4 → 0.3.6
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/out/about/index.html +20 -0
- package/out/admin/index.html +0 -0
- package/out/app/index.html +0 -0
- package/out/assets/AccountMenu-B26qus-Q.js +1 -0
- package/out/assets/AppShell-BiYQsNO8.js +1 -0
- package/out/assets/AppTop-D-qYC0ep.js +1 -0
- package/out/assets/ChatRestoringIndicator-CLV78gNW.js +1 -0
- package/out/assets/CopyButton-BrEkzqEq.js +1 -0
- package/out/assets/MarketingHeader-CJXpogPY.js +1 -0
- package/out/assets/MarketingLayout-Ch4sw5Qm.js +1 -0
- package/out/assets/{MilkdownEditor-BzbYt-xb.js → MilkdownEditor-DfhuT9eU.js} +1 -1
- package/out/assets/OpenSidebarButton-Bvwr7B6k.js +1 -0
- package/out/assets/arrow-right-BsHzC0ZL.js +1 -0
- package/out/assets/{arrow-up-down-B-677ip8.js → arrow-up-down-DvLSz6Ma.js} +1 -1
- package/out/assets/{userProfile-BZvkCuet.js → channelApi-C5eHetoJ.js} +1 -1
- package/out/assets/chatJoinInvite-7tuD-STm.js +1 -0
- package/out/assets/{chatNoteDraft-CYbvcEvC.js → chatNoteDraft-DTy6In5R.js} +1 -1
- package/out/assets/{circle-alert-dfN4T6Nq.js → circle-alert-DXVkx8p8.js} +1 -1
- package/out/assets/{copy-B7pPZ2MP.js → copy-OwXDsstV.js} +1 -1
- package/out/assets/{download-COQOcPqI.js → download-Cy2BlfKM.js} +1 -1
- package/out/assets/downloadValidation-Dd43i9_9.js +1 -0
- package/out/assets/{external-link-D6tHlYgS.js → external-link-D_AQSgxW.js} +1 -1
- package/out/assets/{file-text-DiyIGL1I.js → file-text-zvTER2mN.js} +1 -1
- package/out/assets/fingerprint-pattern-BT73ASEB.js +1 -0
- package/out/assets/{folder-BS_GbpPQ.js → folder-6pYYnS2-.js} +1 -1
- package/out/assets/folder-open-DOJ4bB58.js +1 -0
- package/out/assets/format-CAKfypjK.js +1 -0
- package/out/assets/gamepad-2-BYqNgCQr.js +1 -0
- package/out/assets/{hard-drive-Cqa7Rstj.js → hard-drive-DL1KJHmG.js} +1 -1
- package/out/assets/i18n-DKsg67mH.js +1 -0
- package/out/assets/index-FEwag7ej.css +1 -0
- package/out/assets/index-mjFWLwue.js +31 -0
- package/out/assets/index.lazy-8GSHLIGt.js +3 -0
- package/out/assets/index.lazy-B3jH9yBA.js +1 -0
- package/out/assets/index.lazy-B5JMiv3z.js +1 -0
- package/out/assets/index.lazy-BAr7-dhf.js +1 -0
- package/out/assets/index.lazy-BMFwyE_Z.js +3 -0
- package/out/assets/index.lazy-BbA_1LnY.js +1 -0
- package/out/assets/index.lazy-BmpOpQqg.js +1 -0
- package/out/assets/index.lazy-CB3C6kMj.js +1 -0
- package/out/assets/index.lazy-CVOAOO0_.js +1 -0
- package/out/assets/index.lazy-CyFSwJuo.js +1 -0
- package/out/assets/index.lazy-D3iEq2ZW.js +2 -0
- package/out/assets/index.lazy-F3iHN_Ao.js +1 -0
- package/out/assets/index.lazy-PnZR3z1V.js +5 -0
- package/out/assets/index.lazy-qDpRLqXu.js +1 -0
- package/out/assets/index.lazy-s6Ok77GI.js +1 -0
- package/out/assets/{loader-CwyHYZdX.js → loader-C4pED_QP.js} +1 -1
- package/out/assets/lock-yG7M7GkB.js +1 -0
- package/out/assets/{message-square-BgsNXPft.js → message-square-CfApq8Ou.js} +1 -1
- package/out/assets/mp-cEQteKUC.js +1 -0
- package/out/assets/{music-Dn9nm9Qb.js → music-Br2x8TKX.js} +1 -1
- package/out/assets/notebook-pen-Bcd3MrtD.js +1 -0
- package/out/assets/play-DSHtNcOe.js +1 -0
- package/out/assets/{refresh-cw-D4BsHvhi.js → refresh-cw-CfkUlTK9.js} +1 -1
- package/out/assets/{save-B02sbO9Y.js → save-CfYzXcxH.js} +1 -1
- package/out/assets/saveLocalFile-BLTn3Z18.js +1 -0
- package/out/assets/search-DLBOUfny.js +1 -0
- package/out/assets/{send-4dhnJGuS.js → send-Bie9hgHI.js} +1 -1
- package/out/assets/{shield-check-Boae7Nc6.js → shield-check-2-h_eEXp.js} +1 -1
- package/out/assets/{smartphone-JXH-zQXB.js → smartphone-CrsocYJo.js} +1 -1
- package/out/assets/trash-2-B9gno5q6.js +1 -0
- package/out/assets/{triangle-alert-B9sMiA9l.js → triangle-alert-BhNtjKm2.js} +1 -1
- package/out/assets/{upload-DVuXJt_o.js → upload-BmufnMFu.js} +1 -1
- package/out/assets/{useAppStore-DOnk608w.js → useAppStore-DNhyhD20.js} +1 -1
- package/out/assets/useChannelMessages-DKlO_2oW.js +4 -0
- package/out/assets/useGameRoom-DLN636mz.js +1 -0
- package/out/assets/{wallet-D9Iw4YWE.js → wallet-D6HZocla.js} +1 -1
- package/out/assets/{wifi-ljEI_ySA.js → wifi-CILQyKk0.js} +1 -1
- package/out/chat/index.html +0 -0
- package/out/chat/join/demo/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 +1 -1
- package/server/index.js +18 -1
- package/server/src/core/channelVoice.js +164 -0
- package/server/src/core/mostLink.js +30 -13
- package/server/src/http/nodeStatus.js +2 -2
- package/server/src/http/routes/fileRoutes.js +10 -8
- package/server/src/http/uploads.js +53 -15
- package/server/src/index.js +317 -133
- package/server/src/node/config.js +4 -4
- package/server/src/utils/api.js +9 -1
- package/server/src/utils/avatar.js +1 -10
- package/server/src/utils/downloadMessages.js +7 -7
- package/server/src/utils/errors.js +11 -1
- package/server/src/utils/userIdentity.js +0 -1
- package/out/assets/AccountMenu-ptQklqvD.js +0 -1
- package/out/assets/AppTop-Cy0ZOCxO.js +0 -1
- package/out/assets/CopyButton-C8PIDSJZ.js +0 -1
- package/out/assets/FilePreviewOverlay-faaTnmlN.js +0 -1
- package/out/assets/MarketingHeader-CxJ_OVOW.js +0 -1
- package/out/assets/MarketingLayout-CiN5KYcE.js +0 -1
- package/out/assets/OpenSidebarButton-CGhXxQ5Z.js +0 -1
- package/out/assets/arrow-right-CXzRt0Fp.js +0 -1
- package/out/assets/chatJoinInvite-B-ucvYdg.js +0 -1
- package/out/assets/check-iBCQl0yr.js +0 -1
- package/out/assets/chevron-right-D75NRNNs.js +0 -1
- package/out/assets/downloadValidation-UPgpJbKz.js +0 -1
- package/out/assets/format-CR8oUWq6.js +0 -1
- package/out/assets/index-D0Uk9-58.js +0 -31
- package/out/assets/index-FV6RZlVI.css +0 -1
- package/out/assets/index.lazy-B6XDJ8fg.js +0 -1
- package/out/assets/index.lazy-BFdWbm3R.js +0 -1
- package/out/assets/index.lazy-BzIt7fP4.js +0 -1
- package/out/assets/index.lazy-CLkb32nv.js +0 -1
- package/out/assets/index.lazy-CNmqW4-r.js +0 -1
- package/out/assets/index.lazy-Cnkj-gAd.js +0 -3
- package/out/assets/index.lazy-Cx1bp84f.js +0 -5
- package/out/assets/index.lazy-CxLzR674.js +0 -2
- package/out/assets/index.lazy-D4RjTGu-.js +0 -3
- package/out/assets/index.lazy-D9xLqvqW.js +0 -1
- package/out/assets/index.lazy-DNCF6MTJ.js +0 -1
- package/out/assets/index.lazy-MY-mUG5Z.js +0 -1
- package/out/assets/index.lazy-erUKA2rI.js +0 -1
- package/out/assets/index.lazy-qTjgLKhY.js +0 -1
- package/out/assets/key-round-JR8M3aHl.js +0 -1
- package/out/assets/lock-CuwwlXlx.js +0 -1
- package/out/assets/mp-Dv12gDTx.js +0 -1
- package/out/assets/notebook-pen-BzrCXQJE.js +0 -1
- package/out/assets/play-CvWKSMwY.js +0 -1
- package/out/assets/search-HW4oPOBP.js +0 -1
- package/out/assets/trash-2-BLwoM0ME.js +0 -1
- package/out/assets/useChannelMessages-CDvw-f_x.js +0 -4
- package/out/assets/useGameRoom-CS1w4IqP.js +0 -1
package/server/index.js
CHANGED
|
@@ -143,6 +143,9 @@ function bindEngineEvents({
|
|
|
143
143
|
engine.on('channel:presence', data =>
|
|
144
144
|
wsSendToChannel(data.channelKey, 'channel:presence', data)
|
|
145
145
|
)
|
|
146
|
+
engine.on('channel:voice', data =>
|
|
147
|
+
wsSendToChannel(data.channelKey, 'channel:voice', data)
|
|
148
|
+
)
|
|
146
149
|
engine.on('channel:joined', data => wsBroadcast('channel:joined', data))
|
|
147
150
|
engine.on('channel:left', data => wsBroadcast('channel:left', data))
|
|
148
151
|
engine.on('user:metadata:updated', data => {
|
|
@@ -162,7 +165,7 @@ function bindEngineEvents({
|
|
|
162
165
|
}
|
|
163
166
|
}
|
|
164
167
|
|
|
165
|
-
function createWebSocketServer({
|
|
168
|
+
export function createWebSocketServer({
|
|
166
169
|
engine,
|
|
167
170
|
serverInstance,
|
|
168
171
|
validateWebSocketRequest,
|
|
@@ -247,6 +250,20 @@ function createWebSocketServer({
|
|
|
247
250
|
})
|
|
248
251
|
}
|
|
249
252
|
break
|
|
253
|
+
case 'channel:voice:join':
|
|
254
|
+
case 'channel:voice:state':
|
|
255
|
+
case 'channel:voice:heartbeat':
|
|
256
|
+
case 'channel:voice:leave':
|
|
257
|
+
case 'channel:voice:signal':
|
|
258
|
+
if (data.channel && ws.userAddress) {
|
|
259
|
+
engine.sendChannelVoiceEvent(data.channel, {
|
|
260
|
+
...data,
|
|
261
|
+
event: event.replace('channel:voice:', ''),
|
|
262
|
+
}, {
|
|
263
|
+
ownerAddress: ws.userAddress,
|
|
264
|
+
})
|
|
265
|
+
}
|
|
266
|
+
break
|
|
250
267
|
}
|
|
251
268
|
} catch (err) {
|
|
252
269
|
console.error('[WS Message Error]', err.message)
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import {
|
|
2
|
+
buildChannelKey,
|
|
3
|
+
normalizeChannelAvatar,
|
|
4
|
+
normalizeChannelDisplayName,
|
|
5
|
+
normalizeChannelId,
|
|
6
|
+
} from './channelIdentity.js'
|
|
7
|
+
import { normalizeOwnerAddress } from './ownerMetadata.js'
|
|
8
|
+
import { ValidationError } from '../utils/errors.js'
|
|
9
|
+
|
|
10
|
+
export const CHANNEL_VOICE_EVENTS = new Set([
|
|
11
|
+
'join',
|
|
12
|
+
'state',
|
|
13
|
+
'heartbeat',
|
|
14
|
+
'leave',
|
|
15
|
+
'signal',
|
|
16
|
+
])
|
|
17
|
+
|
|
18
|
+
const CHANNEL_VOICE_SIGNAL_TYPES = new Set(['offer', 'answer', 'candidate'])
|
|
19
|
+
const CHANNEL_VOICE_SESSION_REGEX = /^[a-zA-Z0-9_-]{3,80}$/
|
|
20
|
+
const CHANNEL_VOICE_SIGNAL_MAX_BYTES = 64 * 1024
|
|
21
|
+
|
|
22
|
+
function normalizeSessionId(value, label = 'voice session') {
|
|
23
|
+
const sessionId = String(value || '').trim()
|
|
24
|
+
if (!CHANNEL_VOICE_SESSION_REGEX.test(sessionId)) {
|
|
25
|
+
throw new ValidationError(`Invalid ${label}`)
|
|
26
|
+
}
|
|
27
|
+
return sessionId
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function assertPayloadSize(value, message) {
|
|
31
|
+
const size = Buffer.byteLength(JSON.stringify(value || {}), 'utf8')
|
|
32
|
+
if (size > CHANNEL_VOICE_SIGNAL_MAX_BYTES) {
|
|
33
|
+
throw new ValidationError(message)
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function normalizeSignal(input) {
|
|
38
|
+
if (!input || typeof input !== 'object' || Array.isArray(input)) {
|
|
39
|
+
throw new ValidationError('voice signal is required')
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
assertPayloadSize(input, 'voice signal is too large')
|
|
43
|
+
|
|
44
|
+
const type = String(input.type || '').trim()
|
|
45
|
+
if (!CHANNEL_VOICE_SIGNAL_TYPES.has(type)) {
|
|
46
|
+
throw new ValidationError('Invalid voice signal type')
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (type === 'offer' || type === 'answer') {
|
|
50
|
+
const sdp = String(input.sdp || '')
|
|
51
|
+
if (!sdp.trim()) {
|
|
52
|
+
throw new ValidationError('voice signal SDP is required')
|
|
53
|
+
}
|
|
54
|
+
return { type, sdp }
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (
|
|
58
|
+
!input.candidate ||
|
|
59
|
+
typeof input.candidate !== 'object' ||
|
|
60
|
+
Array.isArray(input.candidate)
|
|
61
|
+
) {
|
|
62
|
+
throw new ValidationError('voice signal candidate is required')
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const candidate = {
|
|
66
|
+
candidate: String(input.candidate.candidate || ''),
|
|
67
|
+
sdpMid:
|
|
68
|
+
input.candidate.sdpMid === null || input.candidate.sdpMid === undefined
|
|
69
|
+
? null
|
|
70
|
+
: String(input.candidate.sdpMid),
|
|
71
|
+
sdpMLineIndex:
|
|
72
|
+
input.candidate.sdpMLineIndex === null ||
|
|
73
|
+
input.candidate.sdpMLineIndex === undefined
|
|
74
|
+
? null
|
|
75
|
+
: Number(input.candidate.sdpMLineIndex),
|
|
76
|
+
}
|
|
77
|
+
if (
|
|
78
|
+
input.candidate.usernameFragment !== null &&
|
|
79
|
+
input.candidate.usernameFragment !== undefined
|
|
80
|
+
) {
|
|
81
|
+
candidate.usernameFragment = String(input.candidate.usernameFragment)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
type,
|
|
86
|
+
candidate,
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function normalizeChannelVoiceEvent(
|
|
91
|
+
channelInput,
|
|
92
|
+
input = {},
|
|
93
|
+
options = {}
|
|
94
|
+
) {
|
|
95
|
+
const channelId = normalizeChannelId(channelInput)
|
|
96
|
+
if (!channelId) {
|
|
97
|
+
throw new ValidationError('voice channel is required')
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const ownerAddress = normalizeOwnerAddress(options.ownerAddress)
|
|
101
|
+
const inputSender =
|
|
102
|
+
input.sender && typeof input.sender === 'object' && !Array.isArray(input.sender)
|
|
103
|
+
? input.sender
|
|
104
|
+
: {}
|
|
105
|
+
const senderAddress = ownerAddress || normalizeOwnerAddress(inputSender.address)
|
|
106
|
+
if (!senderAddress) {
|
|
107
|
+
throw new ValidationError('voice sender is required')
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const event = String(input.event || '').trim()
|
|
111
|
+
if (!CHANNEL_VOICE_EVENTS.has(event)) {
|
|
112
|
+
throw new ValidationError('Invalid voice event')
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const sessionId = normalizeSessionId(input.sessionId)
|
|
116
|
+
const channelKey = buildChannelKey(channelId)
|
|
117
|
+
const normalized = {
|
|
118
|
+
channel: channelKey,
|
|
119
|
+
channelKey,
|
|
120
|
+
channelId,
|
|
121
|
+
event,
|
|
122
|
+
sessionId,
|
|
123
|
+
sender: {
|
|
124
|
+
address: senderAddress,
|
|
125
|
+
},
|
|
126
|
+
timestamp: Number(options.timestamp) || Date.now(),
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const displayName = normalizeChannelDisplayName(
|
|
130
|
+
input.displayName ?? inputSender.displayName,
|
|
131
|
+
senderAddress
|
|
132
|
+
)
|
|
133
|
+
if (displayName) normalized.sender.displayName = displayName
|
|
134
|
+
|
|
135
|
+
const avatar = normalizeChannelAvatar(input.avatar ?? inputSender.avatar)
|
|
136
|
+
if (avatar) normalized.sender.avatar = avatar
|
|
137
|
+
|
|
138
|
+
const profileUpdatedAt = Number(
|
|
139
|
+
input.profileUpdatedAt ?? inputSender.profileUpdatedAt
|
|
140
|
+
)
|
|
141
|
+
if (Number.isFinite(profileUpdatedAt) && profileUpdatedAt > 0) {
|
|
142
|
+
normalized.sender.profileUpdatedAt = profileUpdatedAt
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (input.micMuted !== undefined) {
|
|
146
|
+
normalized.micMuted = Boolean(input.micMuted)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (event === 'signal') {
|
|
150
|
+
normalized.targetSessionId = normalizeSessionId(
|
|
151
|
+
input.targetSessionId,
|
|
152
|
+
'voice target session'
|
|
153
|
+
)
|
|
154
|
+
normalized.signal = normalizeSignal(input.signal)
|
|
155
|
+
} else if (input.targetSessionId) {
|
|
156
|
+
normalized.targetSessionId = normalizeSessionId(
|
|
157
|
+
input.targetSessionId,
|
|
158
|
+
'voice target session'
|
|
159
|
+
)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
assertPayloadSize(normalized, 'voice event is too large')
|
|
163
|
+
return normalized
|
|
164
|
+
}
|
|
@@ -24,6 +24,16 @@ function invalidLink(errorCode, details) {
|
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
function extractTailTarget(value) {
|
|
28
|
+
const queryStart = value.indexOf('?')
|
|
29
|
+
const pathPart = queryStart === -1 ? value : value.slice(0, queryStart)
|
|
30
|
+
const queryPart = queryStart === -1 ? '' : value.slice(queryStart)
|
|
31
|
+
const slashIndex = pathPart.lastIndexOf('/')
|
|
32
|
+
const tailPath = slashIndex === -1 ? pathPart : pathPart.slice(slashIndex + 1)
|
|
33
|
+
|
|
34
|
+
return `${tailPath}${queryPart}`
|
|
35
|
+
}
|
|
36
|
+
|
|
27
37
|
/**
|
|
28
38
|
* 验证 CID 字符串
|
|
29
39
|
* @param {string} cidString - 要验证的 CID 字符串
|
|
@@ -62,27 +72,34 @@ export function parseMostLink(link) {
|
|
|
62
72
|
return invalidLink(MOST_LINK_ERROR_CODES.LINK_EMPTY)
|
|
63
73
|
}
|
|
64
74
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
} catch {
|
|
69
|
-
return invalidLink(MOST_LINK_ERROR_CODES.INVALID_URL)
|
|
75
|
+
const value = link.trim()
|
|
76
|
+
if (!value) {
|
|
77
|
+
return invalidLink(MOST_LINK_ERROR_CODES.LINK_EMPTY)
|
|
70
78
|
}
|
|
71
79
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
80
|
+
const tailTarget = extractTailTarget(value)
|
|
81
|
+
const queryStart = tailTarget.indexOf('?')
|
|
82
|
+
const cidString =
|
|
83
|
+
queryStart === -1 ? tailTarget : tailTarget.slice(0, queryStart)
|
|
84
|
+
const queryString =
|
|
85
|
+
queryStart === -1 ? '' : tailTarget.slice(queryStart + 1)
|
|
75
86
|
|
|
76
|
-
if (
|
|
77
|
-
return invalidLink(MOST_LINK_ERROR_CODES.
|
|
87
|
+
if (!cidString) {
|
|
88
|
+
return invalidLink(MOST_LINK_ERROR_CODES.INVALID_CID_FORMAT)
|
|
78
89
|
}
|
|
79
90
|
|
|
80
|
-
const
|
|
81
|
-
const rawFileName =
|
|
82
|
-
const unsupportedParam = [...
|
|
91
|
+
const searchParams = new URLSearchParams(queryString)
|
|
92
|
+
const rawFileName = searchParams.get('filename')
|
|
93
|
+
const unsupportedParam = [...searchParams.keys()].find(
|
|
83
94
|
key => key !== 'filename'
|
|
84
95
|
)
|
|
85
96
|
|
|
97
|
+
if (queryString && ![...searchParams.keys()].length) {
|
|
98
|
+
return invalidLink(MOST_LINK_ERROR_CODES.UNSUPPORTED_QUERY_PARAM, {
|
|
99
|
+
param: '',
|
|
100
|
+
})
|
|
101
|
+
}
|
|
102
|
+
|
|
86
103
|
const validation = validateCidString(cidString)
|
|
87
104
|
if (!validation.valid) {
|
|
88
105
|
return invalidLink(validation.errorCode)
|
|
@@ -214,13 +214,13 @@ export function buildOpenApiSpec(appPort) {
|
|
|
214
214
|
'/api/download/check': {
|
|
215
215
|
post: {
|
|
216
216
|
summary:
|
|
217
|
-
'Check whether a
|
|
217
|
+
'Check whether a CID-tailed share target is locally available or discoverable',
|
|
218
218
|
responses: { 200: { description: 'Download availability result' } },
|
|
219
219
|
},
|
|
220
220
|
},
|
|
221
221
|
'/api/download': {
|
|
222
222
|
post: {
|
|
223
|
-
summary: 'Start downloading a
|
|
223
|
+
summary: 'Start downloading a CID-tailed share target',
|
|
224
224
|
responses: { 200: { description: 'Download task result' } },
|
|
225
225
|
},
|
|
226
226
|
},
|
|
@@ -15,10 +15,15 @@ export function registerFileRoutes(app, { engine, configStore, wsBroadcast }) {
|
|
|
15
15
|
|
|
16
16
|
app.post('/api/publish', async c => {
|
|
17
17
|
const req = c.env.incoming
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
18
|
+
let result
|
|
19
|
+
try {
|
|
20
|
+
result = await parseMultipartBusboy(
|
|
21
|
+
req,
|
|
22
|
+
configStore.getNodeConfig().maxFileSizeBytes
|
|
23
|
+
)
|
|
24
|
+
} catch (err) {
|
|
25
|
+
return badRequestOrAppError(c, err)
|
|
26
|
+
}
|
|
22
27
|
|
|
23
28
|
if (!result || !result.filename) {
|
|
24
29
|
return c.json({ error: 'No file provided' }, 400)
|
|
@@ -56,11 +61,8 @@ export function registerFileRoutes(app, { engine, configStore, wsBroadcast }) {
|
|
|
56
61
|
if (localAvailability) {
|
|
57
62
|
return c.json({
|
|
58
63
|
success: true,
|
|
59
|
-
|
|
60
|
-
cid: parsed.cid,
|
|
61
|
-
fileName: localAvailability.fileName,
|
|
64
|
+
...localAvailability,
|
|
62
65
|
size: Number(localAvailability.size) || null,
|
|
63
|
-
alreadyExists: true,
|
|
64
66
|
})
|
|
65
67
|
}
|
|
66
68
|
|
|
@@ -3,6 +3,7 @@ import path from 'node:path'
|
|
|
3
3
|
import os from 'node:os'
|
|
4
4
|
import Busboy from 'busboy'
|
|
5
5
|
import { MAX_FILE_SIZE } from '../config.js'
|
|
6
|
+
import { FileSizeError } from '../utils/errors.js'
|
|
6
7
|
|
|
7
8
|
export const UPLOAD_TMP_DIR = path.join(os.tmpdir(), 'most-box-uploads')
|
|
8
9
|
|
|
@@ -56,10 +57,34 @@ export async function parseMultipartBusboy(req, maxUploadSize = MAX_FILE_SIZE) {
|
|
|
56
57
|
|
|
57
58
|
const result = { filePath: null, filename: null }
|
|
58
59
|
let fileSize = 0
|
|
60
|
+
let activeFileStream = null
|
|
59
61
|
let writeStream = null
|
|
60
62
|
let tempPath = null
|
|
63
|
+
let settled = false
|
|
64
|
+
|
|
65
|
+
function cleanupTempFile() {
|
|
66
|
+
if (tempPath) fs.unlink(tempPath, () => {})
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function fail(err) {
|
|
70
|
+
if (settled) return
|
|
71
|
+
settled = true
|
|
72
|
+
if (activeFileStream && !activeFileStream.destroyed) {
|
|
73
|
+
activeFileStream.destroy()
|
|
74
|
+
}
|
|
75
|
+
if (writeStream && !writeStream.destroyed) writeStream.destroy()
|
|
76
|
+
cleanupTempFile()
|
|
77
|
+
reject(err)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function succeed(value) {
|
|
81
|
+
if (settled) return
|
|
82
|
+
settled = true
|
|
83
|
+
resolve(value)
|
|
84
|
+
}
|
|
61
85
|
|
|
62
86
|
busboy.on('file', (name, stream, info) => {
|
|
87
|
+
activeFileStream = stream
|
|
63
88
|
result.filename = decodeFilenameFromHeader(`filename="${info.filename}"`)
|
|
64
89
|
tempPath = path.join(
|
|
65
90
|
UPLOAD_TMP_DIR,
|
|
@@ -70,45 +95,58 @@ export async function parseMultipartBusboy(req, maxUploadSize = MAX_FILE_SIZE) {
|
|
|
70
95
|
stream.on('data', chunk => {
|
|
71
96
|
fileSize += chunk.length
|
|
72
97
|
if (fileSize > maxUploadSize) {
|
|
73
|
-
|
|
74
|
-
writeStream.destroy()
|
|
75
|
-
fs.unlink(tempPath, () => {})
|
|
76
|
-
reject(new Error('File too large'))
|
|
77
|
-
return
|
|
98
|
+
fail(new FileSizeError('File too large', fileSize, maxUploadSize))
|
|
78
99
|
}
|
|
79
100
|
})
|
|
80
101
|
|
|
81
|
-
stream.on('
|
|
82
|
-
|
|
102
|
+
stream.on('limit', () => {
|
|
103
|
+
fail(
|
|
104
|
+
new FileSizeError(
|
|
105
|
+
'File too large',
|
|
106
|
+
fileSize || maxUploadSize,
|
|
107
|
+
maxUploadSize
|
|
108
|
+
)
|
|
109
|
+
)
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
stream.on('error', err => {
|
|
113
|
+
fail(err)
|
|
83
114
|
})
|
|
84
115
|
|
|
85
116
|
stream.pipe(writeStream)
|
|
86
117
|
|
|
87
118
|
writeStream.on('finish', () => {
|
|
119
|
+
if (stream.truncated) {
|
|
120
|
+
fail(
|
|
121
|
+
new FileSizeError(
|
|
122
|
+
'File too large',
|
|
123
|
+
fileSize || maxUploadSize,
|
|
124
|
+
maxUploadSize
|
|
125
|
+
)
|
|
126
|
+
)
|
|
127
|
+
return
|
|
128
|
+
}
|
|
88
129
|
result.filePath = tempPath
|
|
89
|
-
|
|
130
|
+
succeed(result)
|
|
90
131
|
})
|
|
91
132
|
|
|
92
133
|
writeStream.on('error', err => {
|
|
93
|
-
|
|
94
|
-
reject(err)
|
|
134
|
+
fail(err)
|
|
95
135
|
})
|
|
96
136
|
})
|
|
97
137
|
|
|
98
138
|
busboy.on('error', err => {
|
|
99
|
-
|
|
100
|
-
reject(err)
|
|
139
|
+
fail(err)
|
|
101
140
|
})
|
|
102
141
|
|
|
103
142
|
busboy.on('close', () => {
|
|
104
143
|
if (!result.filename) {
|
|
105
|
-
|
|
144
|
+
succeed(null)
|
|
106
145
|
}
|
|
107
146
|
})
|
|
108
147
|
|
|
109
148
|
req.on('error', err => {
|
|
110
|
-
|
|
111
|
-
reject(err)
|
|
149
|
+
fail(err)
|
|
112
150
|
})
|
|
113
151
|
req.pipe(busboy)
|
|
114
152
|
})
|