most-box 0.2.0 → 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.
Files changed (166) hide show
  1. package/README.md +69 -9
  2. package/electron/deepLink.js +29 -0
  3. package/electron/main.js +87 -20
  4. package/out/admin/index.html +0 -0
  5. package/out/app/index.html +0 -0
  6. package/out/assets/AppShell-BOtfY12t.js +1 -0
  7. package/out/assets/FilePreviewOverlay-C5qK9HAE.js +1 -0
  8. package/out/assets/LanguageToggle-Di5b88mK.js +1 -0
  9. package/out/assets/LogoIcon-B2fFe0l1.js +1 -0
  10. package/out/assets/MarketingHeader-BOytKcCc.js +1 -0
  11. package/out/assets/MarketingLayout-B8m1Q7Pa.js +1 -0
  12. package/out/assets/{MarketingThemeToggle-DBaC9bjz.js → MarketingThemeToggle-C6fggkl7.js} +1 -1
  13. package/out/assets/{MilkdownEditor-BqJzntYE.js → MilkdownEditor-Cfze75zl.js} +1 -1
  14. package/out/assets/OpenSidebarButton-DDuALgJ2.js +1 -0
  15. package/out/assets/SidebarAccount-bo1ypzrJ.js +1 -0
  16. package/out/assets/arrow-right-RldN7Rwi.js +1 -0
  17. package/out/assets/circle-alert-BQoSDxUe.js +1 -0
  18. package/out/assets/cloud-DHoTMeZY.js +1 -0
  19. package/out/assets/copy-BtJHJnqH.js +1 -0
  20. package/out/assets/download-Bg-OdoxM.js +1 -0
  21. package/out/assets/downloadValidation-CUvbvj9f.js +1 -0
  22. package/out/assets/external-link-m8ZIQe4p.js +1 -0
  23. package/out/assets/file-text-DG1orIkZ.js +1 -0
  24. package/out/assets/game-s6irY8hS.js +1 -0
  25. package/out/assets/hard-drive-BB-sllXA.js +1 -0
  26. package/out/assets/index-3OD3Chi9.css +1 -0
  27. package/out/assets/index-BSOvFG3o.css +1 -0
  28. package/out/assets/index-C0xqKeu-.css +1 -0
  29. package/out/assets/index-CrAXrmfP.js +31 -0
  30. package/out/assets/index.lazy-0Njp0U6I.js +1 -0
  31. package/out/assets/index.lazy-BTndBeBF.js +1 -0
  32. package/out/assets/index.lazy-Bq39jYTl.js +1 -0
  33. package/out/assets/index.lazy-Bu93oGzJ.js +2 -0
  34. package/out/assets/index.lazy-BvdBwgHx.js +1 -0
  35. package/out/assets/index.lazy-CIx8ist6.js +3 -0
  36. package/out/assets/index.lazy-DK7R297q.js +1 -0
  37. package/out/assets/index.lazy-DVOcrcEB.js +2 -0
  38. package/out/assets/index.lazy-DejOniAm.js +1 -0
  39. package/out/assets/index.lazy-DfsgyiMN.js +1 -0
  40. package/out/assets/index.lazy-DpNIiSXF.js +1 -0
  41. package/out/assets/index.lazy-Vnvz-t7T.js +1 -0
  42. package/out/assets/index.lazy-v7nBJ-sF.js +1 -0
  43. package/out/assets/key-round-DIQ3Xt5F.js +1 -0
  44. package/out/assets/lock-tf1t2yuy.js +1 -0
  45. package/out/assets/message-square-DaQH7q-P.js +1 -0
  46. package/out/assets/mp-DvFTsIL9.js +1 -0
  47. package/out/assets/music-DaFvU2DL.js +1 -0
  48. package/out/assets/notebook-pen-CqEFOHKx.js +1 -0
  49. package/out/assets/play-B7q6F75-.js +1 -0
  50. package/out/assets/plus-H2i2mspM.js +1 -0
  51. package/out/assets/refresh-cw-roxAhABl.js +1 -0
  52. package/out/assets/save-DR0O9ReR.js +1 -0
  53. package/out/assets/search-ncblG-zw.js +1 -0
  54. package/out/assets/send-m1XCcuPn.js +1 -0
  55. package/out/assets/shield-check-BwcvTU4U.js +1 -0
  56. package/out/assets/trash-2-CNpsqYc1.js +1 -0
  57. package/out/assets/triangle-alert-DP9EP7IM.js +1 -0
  58. package/out/assets/upload-V--8p13l.js +1 -0
  59. package/out/assets/useChannelMessages-46C52EyL.js +3 -0
  60. package/out/assets/useGameRoom-BtxPpfck.js +1 -0
  61. package/out/assets/useNavigate-DWlBD_-b.js +1 -0
  62. package/out/assets/userStore-C4vdYsQp.js +4 -0
  63. package/out/assets/wallet-CozFU6yK.js +1 -0
  64. package/out/assets/wifi-DIR3g_8A.js +1 -0
  65. package/out/avatars/default/LICENSE.md +7 -0
  66. package/out/avatars/default/dolphin.svg +25 -0
  67. package/out/avatars/default/owl.svg +60 -0
  68. package/out/avatars/default/panda.svg +29 -0
  69. package/out/avatars/default/snow-mountain.svg +100 -0
  70. package/out/avatars/default/tiger.svg +55 -0
  71. package/out/avatars/default/turtle.svg +34 -0
  72. package/out/chat/index.html +0 -0
  73. package/out/chat/join/index.html +0 -0
  74. package/out/download/index.html +4 -3
  75. package/out/game/gandengyan/index.html +0 -0
  76. package/out/game/index.html +0 -0
  77. package/out/game/zhajinhua/index.html +0 -0
  78. package/out/index.html +4 -3
  79. package/out/note/index.html +0 -0
  80. package/out/ping/index.html +4 -3
  81. package/out/{demo → profile}/index.html +0 -0
  82. package/out/web3/index.html +0 -0
  83. package/package.json +9 -1
  84. package/public/avatars/default/LICENSE.md +7 -0
  85. package/public/avatars/default/dolphin.svg +25 -0
  86. package/public/avatars/default/owl.svg +60 -0
  87. package/public/avatars/default/panda.svg +29 -0
  88. package/public/avatars/default/snow-mountain.svg +100 -0
  89. package/public/avatars/default/tiger.svg +55 -0
  90. package/public/avatars/default/turtle.svg +34 -0
  91. package/server/index.js +36 -7
  92. package/server/src/core/channelIdentity.js +6 -11
  93. package/server/src/core/gameRoom.js +15 -5
  94. package/server/src/core/mostLink.js +8 -7
  95. package/server/src/core/zhajinhua.js +6 -0
  96. package/server/src/games/gandengyan.js +10 -0
  97. package/server/src/http/access.js +63 -12
  98. package/server/src/http/app.js +34 -840
  99. package/server/src/http/nodeStatus.js +101 -9
  100. package/server/src/http/routePolicy.js +2 -0
  101. package/server/src/http/routes/channelRoutes.js +163 -0
  102. package/server/src/http/routes/fileRoutes.js +345 -0
  103. package/server/src/http/routes/nodeRoutes.js +323 -0
  104. package/server/src/http/routes/seedRoutes.js +58 -0
  105. package/server/src/index.js +483 -265
  106. package/server/src/node/config.js +2 -6
  107. package/server/src/utils/avatar.js +59 -3
  108. package/server/src/utils/downloadMessages.js +0 -2
  109. package/out/assets/AppShell-CQhg6DJU.js +0 -1
  110. package/out/assets/ChatUi-BepWs-ZU.js +0 -1
  111. package/out/assets/LanguageToggle-CtzCCAYv.js +0 -1
  112. package/out/assets/LogoIcon-Dxto3Sb4.js +0 -1
  113. package/out/assets/MarketingLayout-BQw0IS2i.js +0 -1
  114. package/out/assets/MoveModal-4D9n11Kw.js +0 -1
  115. package/out/assets/Nav-9MDdvgNs.js +0 -1
  116. package/out/assets/NoteSidebar-C-rIt32H.js +0 -1
  117. package/out/assets/OpenSidebarButton-Dd0JmKuE.js +0 -1
  118. package/out/assets/PemBlock-C8dEIzu-.js +0 -1
  119. package/out/assets/SidebarAccount-ClS-N0lq.js +0 -1
  120. package/out/assets/arrow-right-urE9Rd7j.js +0 -1
  121. package/out/assets/channelApi-BwQU0-h1.js +0 -1
  122. package/out/assets/check-DUNsD2t6.js +0 -1
  123. package/out/assets/chevron-down-D6mpsfv4.js +0 -1
  124. package/out/assets/circle-alert-W0iyN4sC.js +0 -1
  125. package/out/assets/cloud-BMyOoC2x.js +0 -1
  126. package/out/assets/code-B1Cb_Icm.js +0 -1
  127. package/out/assets/copy-C1MttOli.js +0 -1
  128. package/out/assets/download-y7SZXu6E.js +0 -1
  129. package/out/assets/downloadValidation-B0p9Ai_9.js +0 -1
  130. package/out/assets/filePreview-UI9NH34f.js +0 -1
  131. package/out/assets/game-CdU3xnZo.js +0 -1
  132. package/out/assets/hard-drive-D13Qbobu.js +0 -1
  133. package/out/assets/image-DJCA16l_.js +0 -1
  134. package/out/assets/index-8eWJAjpY.css +0 -1
  135. package/out/assets/index-BZc4blbW.css +0 -1
  136. package/out/assets/index-BdaFEQG-.css +0 -1
  137. package/out/assets/index-QxXZzOUL.js +0 -33
  138. package/out/assets/index.lazy-BBTTFanX.js +0 -1
  139. package/out/assets/index.lazy-BG4ZylHD.js +0 -2
  140. package/out/assets/index.lazy-Bi-6ZXZX.js +0 -1
  141. package/out/assets/index.lazy-BixWVr0B.js +0 -1
  142. package/out/assets/index.lazy-BjFwNYy5.js +0 -3
  143. package/out/assets/index.lazy-C8EIQsXY.js +0 -2
  144. package/out/assets/index.lazy-CarNe2uu.js +0 -1
  145. package/out/assets/index.lazy-DEuGu3H3.js +0 -1
  146. package/out/assets/index.lazy-GPyILCA7.js +0 -3
  147. package/out/assets/index.lazy-I8ofndXl.js +0 -1
  148. package/out/assets/index.lazy-TxhWsA7y.js +0 -1
  149. package/out/assets/index.lazy-azfky8k7.js +0 -1
  150. package/out/assets/key-round-CZniN9lv.js +0 -1
  151. package/out/assets/lock-D5OSNhep.js +0 -1
  152. package/out/assets/log-out-B6phyZ5z.js +0 -1
  153. package/out/assets/music-CbUskKgg.js +0 -1
  154. package/out/assets/notebook-pen-DqKDQ6MJ.js +0 -1
  155. package/out/assets/play-BIl8q9eU.js +0 -1
  156. package/out/assets/plus-BxxbpH6Q.js +0 -1
  157. package/out/assets/save-DkH1n_Ov.js +0 -1
  158. package/out/assets/search-BQi5Z0E-.js +0 -1
  159. package/out/assets/send-Cl6NtD2T.js +0 -1
  160. package/out/assets/trash-2-BBjpgK_f.js +0 -1
  161. package/out/assets/triangle-alert-l98G8u9O.js +0 -1
  162. package/out/assets/upload-ByP6Ydde.js +0 -1
  163. package/out/assets/useChannelMessages-BgbYfF2c.js +0 -3
  164. package/out/assets/useGameRoom-DPmweWwe.js +0 -1
  165. package/out/assets/wallet-c7zIhNSM.js +0 -1
  166. package/out/assets/wifi-Bm4biAjc.js +0 -1
@@ -0,0 +1,323 @@
1
+ import { normalizeAddress } from '../../utils/auth.js'
2
+ import { evaluateStorageLimits } from '../../node/config.js'
3
+ import {
4
+ isLoopbackRemoteAddress,
5
+ isPublicListenHost,
6
+ remoteInviteConfigured,
7
+ } from '../access.js'
8
+ import { errorJson } from '../errors.js'
9
+ import { resolveDataPathForSave } from '../dataPath.js'
10
+ import { listFilteredNodeLogs } from '../nodeLogs.js'
11
+ import {
12
+ buildNodeStatus,
13
+ buildOpenApiSpec,
14
+ getNetworkAddresses,
15
+ getPackageVersion,
16
+ } from '../nodeStatus.js'
17
+
18
+ export function registerNodeRoutes(
19
+ app,
20
+ {
21
+ engine,
22
+ appPort,
23
+ appHost,
24
+ configStore,
25
+ nodeLogger,
26
+ getDataPath,
27
+ getRemoteInviteSet,
28
+ isRemoteRequest,
29
+ appendNodeLog,
30
+ broadcastNodeStatus,
31
+ wsBroadcast,
32
+ serverInstanceRef,
33
+ }
34
+ ) {
35
+ app.get('/api/node-id', c => {
36
+ return c.json({ id: engine.getNodeId() })
37
+ })
38
+
39
+ app.get('/api/remote/capabilities', c => {
40
+ const remoteInviteSet = getRemoteInviteSet()
41
+ return c.json({
42
+ remoteAccess:
43
+ isPublicListenHost(appHost) && remoteInviteConfigured(remoteInviteSet),
44
+ inviteRequired: true,
45
+ inviteConfigured: remoteInviteConfigured(remoteInviteSet),
46
+ authenticated: Boolean(c.get('userAddress')),
47
+ userAddress: c.get('userAddress') || null,
48
+ adminAvailable: !isRemoteRequest(c),
49
+ listenHost: appHost,
50
+ })
51
+ })
52
+
53
+ app.get('/api/config', c => {
54
+ const config = configStore.loadRawConfig()
55
+ return c.json({ dataPath: config.dataPath || '' })
56
+ })
57
+
58
+ app.post('/api/config', async c => {
59
+ const body = await c.req.json()
60
+ const patch = {}
61
+
62
+ if (body.resetStorage) {
63
+ patch.dataPath = ''
64
+ } else if (body.dataPath !== undefined) {
65
+ const resolved = resolveDataPathForSave(body.dataPath)
66
+ if (resolved.error) return c.json({ error: resolved.error }, 400)
67
+ patch.dataPath = resolved.dataPath
68
+ }
69
+
70
+ const { success } = configStore.saveNodeConfigPatch(patch)
71
+ appendNodeLog({
72
+ event: 'node:config:updated',
73
+ message: 'Node config updated',
74
+ data: { dataPath: getDataPath(configStore) },
75
+ })
76
+ await broadcastNodeStatus()
77
+ return c.json({ success, dataPath: getDataPath(configStore) })
78
+ })
79
+
80
+ app.get('/api/config/data-path', c => {
81
+ const config = configStore.getNodeConfig()
82
+ const isDefault = !config.dataPath
83
+ const dataPath = getDataPath(configStore)
84
+ return c.json({ dataPath, isDefault })
85
+ })
86
+
87
+ app.get('/api/node/status', async c => {
88
+ try {
89
+ return c.json(await buildNodeStatus(engine, configStore, appPort, appHost))
90
+ } catch (err) {
91
+ return errorJson(c, err)
92
+ }
93
+ })
94
+
95
+ app.get('/api/node/config', c => {
96
+ const config = configStore.getNodeConfig()
97
+ return c.json({
98
+ ...config,
99
+ dataPath: getDataPath(configStore),
100
+ configuredDataPath: config.dataPath,
101
+ isDefaultDataPath: !config.dataPath,
102
+ currentHost: appHost,
103
+ currentPort: appPort,
104
+ remoteInvites: config.remoteInvites,
105
+ })
106
+ })
107
+
108
+ app.post('/api/node/config', async c => {
109
+ const body = await c.req.json()
110
+ const patch = { ...body }
111
+
112
+ if (body.resetStorage) {
113
+ patch.dataPath = ''
114
+ } else if (body.dataPath !== undefined) {
115
+ const resolved = resolveDataPathForSave(body.dataPath)
116
+ if (resolved.error) return c.json({ error: resolved.error }, 400)
117
+ patch.dataPath = resolved.dataPath
118
+ }
119
+
120
+ const { success, config } = configStore.saveNodeConfigPatch(patch)
121
+ engine.setMaxFileSize(config.maxFileSizeBytes)
122
+ appendNodeLog({
123
+ event: 'node:config:updated',
124
+ message: 'Node daemon config updated',
125
+ data: {
126
+ dataPath: getDataPath(configStore),
127
+ port: config.port,
128
+ capacityBytes: config.capacityBytes,
129
+ remoteInviteCount: config.remoteInvites.length,
130
+ },
131
+ })
132
+ await broadcastNodeStatus()
133
+ return c.json({ success, ...config, dataPath: getDataPath(configStore) })
134
+ })
135
+
136
+ app.get('/api/node/policy', c => {
137
+ const config = configStore.getNodeConfig()
138
+ return c.json({
139
+ maxFileSizeBytes: config.maxFileSizeBytes,
140
+ })
141
+ })
142
+
143
+ app.post('/api/node/policy', async c => {
144
+ const body = await c.req.json()
145
+ const { success, config } = configStore.saveNodeConfigPatch({
146
+ maxFileSizeBytes: body.maxFileSizeBytes,
147
+ })
148
+ engine.setMaxFileSize(config.maxFileSizeBytes)
149
+ const policy = {
150
+ maxFileSizeBytes: config.maxFileSizeBytes,
151
+ }
152
+ appendNodeLog({
153
+ event: 'node:policy:updated',
154
+ message: 'Node storage limits updated',
155
+ data: policy,
156
+ })
157
+ await broadcastNodeStatus()
158
+ return c.json({ success, ...policy })
159
+ })
160
+
161
+ app.post('/api/node/policy/evaluate', async c => {
162
+ const body = await c.req.json()
163
+ const decision = evaluateStorageLimits(configStore.getNodeConfig(), body)
164
+ return c.json(decision)
165
+ })
166
+
167
+ app.get('/api/node/logs', c => {
168
+ const limit = Number(c.req.query('limit') || 100)
169
+ const filter = c.req.query('filter') || 'all'
170
+ const query = c.req.query('q') || ''
171
+ const result = listFilteredNodeLogs(nodeLogger, { limit, filter, query })
172
+ return c.json({
173
+ logFile: nodeLogger.logFile,
174
+ filter: result.filter,
175
+ query: result.query,
176
+ logs: result.logs,
177
+ })
178
+ })
179
+
180
+ app.delete('/api/node/logs', c => {
181
+ const success = nodeLogger.clear()
182
+ const clearedAt = new Date().toISOString()
183
+ wsBroadcast('node:logs:cleared', { clearedAt })
184
+ return c.json({ success, clearedAt })
185
+ })
186
+
187
+ app.get('/api/node/diagnostics', async c => {
188
+ try {
189
+ const status = await buildNodeStatus(
190
+ engine,
191
+ configStore,
192
+ appPort,
193
+ appHost
194
+ )
195
+ return c.json({
196
+ generatedAt: new Date().toISOString(),
197
+ packageVersion: getPackageVersion(),
198
+ platform: process.platform,
199
+ nodeVersion: process.version,
200
+ status,
201
+ logFile: nodeLogger.logFile,
202
+ logs: nodeLogger.list(200),
203
+ })
204
+ } catch (err) {
205
+ return errorJson(c, err)
206
+ }
207
+ })
208
+
209
+ app.get('/api/admin/users', c => {
210
+ return c.json({ users: engine.listUsers() })
211
+ })
212
+
213
+ app.post('/api/user/sync/start', async c => {
214
+ try {
215
+ const body = await c.req.json()
216
+ const result = await engine.startUserSync(c.get('userAddress'), body)
217
+ appendNodeLog({
218
+ event: 'node:user-sync:started',
219
+ message: 'User sync started',
220
+ data: {
221
+ ownerAddress: result.ownerAddress,
222
+ syncName: result.syncName,
223
+ },
224
+ })
225
+ return c.json({ success: true, ...result })
226
+ } catch (err) {
227
+ return errorJson(c, err)
228
+ }
229
+ })
230
+
231
+ app.get('/api/user/sync/status', c => {
232
+ try {
233
+ return c.json(engine.getUserSyncStatus(c.get('userAddress')))
234
+ } catch (err) {
235
+ return errorJson(c, err)
236
+ }
237
+ })
238
+
239
+ app.get('/api/user/profile', c => {
240
+ try {
241
+ return c.json(engine.getUserProfile(c.get('userAddress')))
242
+ } catch (err) {
243
+ return errorJson(c, err)
244
+ }
245
+ })
246
+
247
+ app.put('/api/user/profile', async c => {
248
+ try {
249
+ const body = await c.req.json()
250
+ const profile = engine.saveUserProfile(c.get('userAddress'), body)
251
+ return c.json({ success: true, profile })
252
+ } catch (err) {
253
+ return errorJson(c, err)
254
+ }
255
+ })
256
+
257
+ app.delete('/api/admin/users/:address/data', async c => {
258
+ const address = normalizeAddress(c.req.param('address'))
259
+ if (!address) {
260
+ return c.json({ error: 'valid address is required' }, 400)
261
+ }
262
+ try {
263
+ const result = await engine.clearUserData(address)
264
+ appendNodeLog({
265
+ event: 'node:user-data:cleared',
266
+ message: 'User data cleared',
267
+ data: result,
268
+ })
269
+ await broadcastNodeStatus()
270
+ return c.json({ success: true, ...result })
271
+ } catch (err) {
272
+ return errorJson(c, err)
273
+ }
274
+ })
275
+
276
+ app.get('/api/openapi.json', c => {
277
+ return c.json(buildOpenApiSpec(appPort))
278
+ })
279
+
280
+ app.get('/api/network-status', c => {
281
+ return c.json(engine.getNetworkStatus())
282
+ })
283
+
284
+ app.get('/api/network', c => {
285
+ return c.json(getNetworkAddresses(appPort))
286
+ })
287
+
288
+ app.get('/api/display-name', c => {
289
+ return c.json({ displayName: engine.getDisplayName() })
290
+ })
291
+
292
+ app.post('/api/display-name', async c => {
293
+ const body = await c.req.json()
294
+ if (!body.name || !body.name.trim()) {
295
+ return c.json({ error: 'name is required' }, 400)
296
+ }
297
+ const trimmed = body.name.trim()
298
+ if (trimmed.length > 100) {
299
+ return c.json({ error: 'Name too long (max 100 chars)' }, 400)
300
+ }
301
+ if (/[<>]/.test(trimmed)) {
302
+ return c.json({ error: 'Name contains invalid characters' }, 400)
303
+ }
304
+ const success = engine.setDisplayName(trimmed)
305
+ return c.json({ success, displayName: engine.getDisplayName() })
306
+ })
307
+
308
+ app.post('/api/shutdown', c => {
309
+ const clientIp = c.env.incoming?.socket?.remoteAddress || 'unknown'
310
+ if (!isLoopbackRemoteAddress(clientIp)) {
311
+ return c.json({ error: 'Forbidden' }, 403)
312
+ }
313
+ c.json({ success: true })
314
+ console.log('[MostBox] Shutdown requested via API...')
315
+ setTimeout(async () => {
316
+ await engine.stop()
317
+ if (serverInstanceRef.current) serverInstanceRef.current.close()
318
+ console.log('[MostBox] Server stopped.')
319
+ process.exit(0)
320
+ }, 100)
321
+ return c.body(null)
322
+ })
323
+ }
@@ -0,0 +1,58 @@
1
+ import { errorJson } from '../errors.js'
2
+
3
+ export function registerSeedRoutes(
4
+ app,
5
+ { engine, appendNodeLog, broadcastNodeStatus }
6
+ ) {
7
+ app.get('/api/node/holdings', c => {
8
+ try {
9
+ return c.json(engine.listHoldings())
10
+ } catch (err) {
11
+ return errorJson(c, err)
12
+ }
13
+ })
14
+
15
+ app.post('/api/node/holdings', async c => {
16
+ try {
17
+ const body = await c.req.json()
18
+ const holding = await engine.addHolding(body)
19
+ appendNodeLog({
20
+ event: 'node:holding:added',
21
+ message: 'Node holding added',
22
+ data: { cid: holding.cid, size: holding.size },
23
+ })
24
+ await broadcastNodeStatus()
25
+ return c.json({ success: true, holding })
26
+ } catch (err) {
27
+ return errorJson(c, err)
28
+ }
29
+ })
30
+
31
+ app.post('/api/p2p/pull', async c => {
32
+ try {
33
+ const body = await c.req.json()
34
+ const timeout =
35
+ body.timeout === undefined ? undefined : Number(body.timeout)
36
+ const result = await engine.pullByCid({
37
+ ...body,
38
+ ownerAddress: c.get('userAddress'),
39
+ timeout: Number.isFinite(timeout) && timeout > 0 ? timeout : undefined,
40
+ })
41
+ appendNodeLog({
42
+ event: 'node:pull:success',
43
+ message: 'P2P pull completed',
44
+ data: { cid: result.cid, taskId: result.taskId },
45
+ })
46
+ await broadcastNodeStatus()
47
+ return c.json({ success: true, ...result })
48
+ } catch (err) {
49
+ appendNodeLog({
50
+ level: 'error',
51
+ event: 'node:pull:error',
52
+ message: err.message,
53
+ data: { code: err.code || 'UNKNOWN' },
54
+ })
55
+ return errorJson(c, err)
56
+ }
57
+ })
58
+ }