most-box 0.0.1

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.
Binary file
@@ -0,0 +1,3 @@
1
+ <svg width="52" height="49" viewBox="0 0 52 49" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <path d="M12.6085 35.2507C20.0705 44.0321 28.8029 45.8682 39.3058 39.9408L46.3677 47.7741L51.0789 43.0741L44.017 36.0291C47.758 31.0397 49.8006 22.6726 47.5 14.5C45.1994 6.32739 38.8556 1.32293 31.4536 0.784076C44.017 10.1741 44.017 23.2741 37.7353 30.5507L18.07 10.1741L25.9221 3.5482L14.1789 0.774098L1.61554 13.2975L8.67745 20.3425L14.1689 14.8641L33.0741 34.4524C31.7048 35.576 30.0916 36.3653 28.3628 36.7575C21.3609 38.2943 12.9686 30.5308 12.6585 30.5308C12.3484 30.5308 2.45577 39.9308 2.45577 39.9308C0.805318 41.238 0.785313 42.3057 1.62554 43.2338L4.76639 45.4291C5.99673 45.7285 7.05701 45.2894 7.90724 43.8624L12.6085 35.2507Z" fill="black" stroke="black"/>
3
+ </svg>
Binary file
Binary file
Binary file
@@ -0,0 +1,15 @@
1
+ <!doctype html>
2
+ <html lang="zh-CN" data-theme="light">
3
+
4
+ <head>
5
+ <meta charset="UTF-8" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <title>MostBox 文件管理</title>
8
+ </head>
9
+
10
+ <body>
11
+ <div id="root"></div>
12
+ <script type="module" src="./bundle.js"></script>
13
+ </body>
14
+
15
+ </html>
@@ -0,0 +1,5 @@
1
+ import { createRoot } from 'react-dom/client'
2
+ import App from './app.jsx'
3
+
4
+ const root = createRoot(document.getElementById('root'))
5
+ root.render(<App />)
package/server.js ADDED
@@ -0,0 +1,615 @@
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 crypto from 'node:crypto'
7
+ import { exec } from 'node:child_process'
8
+ import { MostBoxEngine } from './src/index.js'
9
+ import { parseMostLink } from './src/core/cid.js'
10
+
11
+ const __dirname = path.dirname(fileURLToPath(import.meta.url))
12
+ const PORT = Number(process.env.MOSTBOX_PORT) || 1976
13
+ const HOST = '127.0.0.1'
14
+
15
+ const wsClients = new Set()
16
+ let engine = null
17
+ let serverInstance = null
18
+
19
+ // --- Config ---
20
+ const CONFIG_DIR = path.join(os.homedir(), '.most-box')
21
+ const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json')
22
+
23
+ function loadConfig() {
24
+ try {
25
+ if (fs.existsSync(CONFIG_FILE)) {
26
+ return JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf-8'))
27
+ }
28
+ } catch (err) {
29
+ console.error('[Config] Load error:', err.message)
30
+ }
31
+ return {}
32
+ }
33
+
34
+ function saveConfig(config) {
35
+ try {
36
+ if (!fs.existsSync(CONFIG_DIR)) {
37
+ fs.mkdirSync(CONFIG_DIR, { recursive: true })
38
+ }
39
+ fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), 'utf-8')
40
+ return true
41
+ } catch (err) {
42
+ console.error('[Config] Save error:', err.message)
43
+ return false
44
+ }
45
+ }
46
+
47
+ // --- Storage path ---
48
+ function getDataPath() {
49
+ const config = loadConfig()
50
+ return config.dataPath || path.join(os.homedir(), 'most-data')
51
+ }
52
+
53
+ // --- Static file serving ---
54
+ const MIME_TYPES = {
55
+ '.html': 'text/html; charset=utf-8',
56
+ '.js': 'application/javascript; charset=utf-8',
57
+ '.css': 'text/css; charset=utf-8',
58
+ '.json': 'application/json',
59
+ '.png': 'image/png',
60
+ '.jpg': 'image/jpeg',
61
+ '.jpeg': 'image/jpeg',
62
+ '.gif': 'image/gif',
63
+ '.webp': 'image/webp',
64
+ '.svg': 'image/svg+xml',
65
+ '.ico': 'image/x-icon',
66
+ '.mp4': 'video/mp4',
67
+ '.webm': 'video/webm',
68
+ '.ogg': 'video/ogg',
69
+ '.mp3': 'audio/mpeg',
70
+ '.wav': 'audio/wav',
71
+ '.flac': 'audio/flac',
72
+ '.aac': 'audio/aac',
73
+ '.m4a': 'audio/mp4',
74
+ '.opus': 'audio/opus',
75
+ '.woff2': 'font/woff2',
76
+ '.woff': 'font/woff'
77
+ }
78
+
79
+ function serveStatic(req, res) {
80
+ let filePath = req.url === '/' ? '/index.html' : req.url
81
+ filePath = filePath.split('?')[0]
82
+
83
+ const fullPath = path.join(__dirname, 'public', filePath)
84
+ const ext = path.extname(fullPath)
85
+ const publicDir = path.join(__dirname, 'public')
86
+
87
+ if (!fullPath.startsWith(publicDir)) {
88
+ res.writeHead(403)
89
+ res.end('Forbidden')
90
+ return
91
+ }
92
+
93
+ fs.readFile(fullPath, (err, data) => {
94
+ if (err) {
95
+ res.writeHead(404)
96
+ res.end('Not found')
97
+ return
98
+ }
99
+
100
+ let content = data
101
+ if (ext === '.html') {
102
+ content = data.toString()
103
+ }
104
+
105
+ res.writeHead(200, { 'Content-Type': MIME_TYPES[ext] || 'application/octet-stream' })
106
+ res.end(content)
107
+ })
108
+ }
109
+
110
+ // --- Streaming multipart parser for large files ---
111
+ async function parseMultipart(req) {
112
+ const boundaryMatch = req.headers['content-type']?.match(/boundary=(?:"([^"]+)"|([^\s;]+))/)
113
+ if (!boundaryMatch) throw new Error('No boundary in content-type')
114
+ const boundary = boundaryMatch[1] || boundaryMatch[2]
115
+
116
+ const chunks = []
117
+ for await (const chunk of req) {
118
+ chunks.push(chunk)
119
+ }
120
+ const buffer = Buffer.concat(chunks)
121
+
122
+ const parts = []
123
+ const boundaryBuf = Buffer.from('--' + boundary)
124
+ let start = 0
125
+
126
+ while (true) {
127
+ const idx = buffer.indexOf(boundaryBuf, start)
128
+ if (idx === -1) break
129
+
130
+ if (start > 0) {
131
+ // Handle both \r\n and \n line endings
132
+ let partStart = start
133
+ if (buffer[partStart] === 0x0d && buffer[partStart + 1] === 0x0a) {
134
+ partStart += 2
135
+ } else if (buffer[partStart] === 0x0a) {
136
+ partStart += 1
137
+ }
138
+
139
+ let partEnd = idx - 1
140
+ if (buffer[partEnd] === 0x0a) {
141
+ partEnd--
142
+ if (buffer[partEnd] === 0x0d) {
143
+ partEnd--
144
+ }
145
+ }
146
+
147
+ const partData = buffer.slice(partStart, partEnd + 1)
148
+
149
+ const headerEnd = partData.indexOf('\r\n\r\n')
150
+ const headerEndAlt = partData.indexOf('\n\n')
151
+
152
+ let headerEndIdx = -1
153
+ let bodyStart = -1
154
+
155
+ if (headerEnd !== -1) {
156
+ headerEndIdx = headerEnd
157
+ bodyStart = headerEnd + 4
158
+ } else if (headerEndAlt !== -1) {
159
+ headerEndIdx = headerEndAlt
160
+ bodyStart = headerEndAlt + 2
161
+ }
162
+
163
+ if (headerEndIdx !== -1) {
164
+ const headers = partData.slice(0, headerEndIdx).toString()
165
+ const body = partData.slice(bodyStart)
166
+
167
+ const nameMatch = headers.match(/name="([^"]+)"/)
168
+ const filenameMatch = headers.match(/filename="([^"]+)"/)
169
+ parts.push({
170
+ name: nameMatch?.[1],
171
+ filename: filenameMatch?.[1],
172
+ data: body,
173
+ headers
174
+ })
175
+ }
176
+ }
177
+
178
+ // Move to after the boundary
179
+ start = idx + boundaryBuf.length
180
+ // Skip optional whitespace after boundary
181
+ while (start < buffer.length && (buffer[start] === 0x20 || buffer[start] === 0x09)) {
182
+ start++
183
+ }
184
+ // Skip line ending
185
+ if (start < buffer.length && buffer[start] === 0x0d) {
186
+ start++
187
+ }
188
+ if (start < buffer.length && buffer[start] === 0x0a) {
189
+ start++
190
+ }
191
+ }
192
+
193
+ return parts
194
+ }
195
+
196
+ // --- JSON body parser ---
197
+ async function parseJSON(req) {
198
+ const chunks = []
199
+ for await (const chunk of req) {
200
+ chunks.push(chunk)
201
+ }
202
+ try {
203
+ return JSON.parse(Buffer.concat(chunks).toString())
204
+ } catch {
205
+ return {}
206
+ }
207
+ }
208
+
209
+ // --- API Routes ---
210
+ async function handleAPI(req, res) {
211
+ const url = new URL(req.url, `http://${HOST}:${PORT}`)
212
+ const pathname = url.pathname
213
+ const method = req.method
214
+
215
+ const json = (data, status = 200) => {
216
+ res.writeHead(status, { 'Content-Type': 'application/json' })
217
+ res.end(JSON.stringify(data))
218
+ }
219
+
220
+ try {
221
+ // GET /api/node-id
222
+ if (pathname === '/api/node-id' && method === 'GET') {
223
+ json({ id: engine.getNodeId() })
224
+ return
225
+ }
226
+
227
+ // GET /api/config
228
+ if (pathname === '/api/config' && method === 'GET') {
229
+ const config = loadConfig()
230
+ json({ dataPath: config.dataPath || '' })
231
+ return
232
+ }
233
+
234
+ // POST /api/config
235
+ if (pathname === '/api/config' && method === 'POST') {
236
+ const body = await parseJSON(req)
237
+ const config = loadConfig()
238
+
239
+ if (body.resetStorage) {
240
+ config.dataPath = ''
241
+ } else if (body.dataPath !== undefined) {
242
+ let dataPath = body.dataPath.trim()
243
+ let basePath = dataPath
244
+
245
+ if (dataPath.match(/^[A-Za-z]:\\$/)) {
246
+ basePath = dataPath
247
+ dataPath = path.join(dataPath, 'most-data')
248
+ }
249
+
250
+ if (!fs.existsSync(basePath)) {
251
+ json({ error: '目录不存在' }, 400)
252
+ return
253
+ }
254
+
255
+ if (!fs.existsSync(dataPath)) {
256
+ fs.mkdirSync(dataPath, { recursive: true })
257
+ }
258
+
259
+ config.dataPath = dataPath
260
+ }
261
+
262
+ const success = saveConfig(config)
263
+ json({ success, dataPath: getDataPath() })
264
+ return
265
+ }
266
+
267
+ // GET /api/config/data-path
268
+ if (pathname === '/api/config/data-path' && method === 'GET') {
269
+ const config = loadConfig()
270
+ const isDefault = !config.dataPath
271
+ const dataPath = getDataPath()
272
+ json({ dataPath, isDefault })
273
+ return
274
+ }
275
+
276
+ // GET /api/network-status
277
+ if (pathname === '/api/network-status' && method === 'GET') {
278
+ json(engine.getNetworkStatus())
279
+ return
280
+ }
281
+
282
+ // GET /api/files
283
+ if (pathname === '/api/files' && method === 'GET') {
284
+ json(engine.listPublishedFiles())
285
+ return
286
+ }
287
+
288
+ // POST /api/publish — multipart file upload
289
+ if (pathname === '/api/publish' && method === 'POST') {
290
+ const parts = await parseMultipart(req)
291
+
292
+ const filePart = parts.find(p => p.name === 'file')
293
+ if (!filePart || !filePart.filename) {
294
+ json({ error: 'No file provided' }, 400)
295
+ return
296
+ }
297
+
298
+ const result = await engine.publishFile(filePart.data, filePart.filename)
299
+
300
+ json({ success: true, ...result })
301
+ return
302
+ }
303
+
304
+ // POST /api/download — start async download from P2P
305
+ if (pathname === '/api/download' && method === 'POST') {
306
+ const body = await parseJSON(req)
307
+ if (!body.link) {
308
+ json({ error: 'link is required' }, 400)
309
+ return
310
+ }
311
+
312
+ const taskId = `dl_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`
313
+
314
+ // Parse link to check if file already exists
315
+ const parsed = parseMostLink(body.link)
316
+ if (parsed.error) {
317
+ json({ error: parsed.error }, 400)
318
+ return
319
+ }
320
+
321
+ // Check if file already exists in published files
322
+ const existingFile = engine.getPublishedFiles().find(f => f.cid === parsed.cid)
323
+ if (existingFile) {
324
+ console.log(`[MostBox] File already exists: ${existingFile.fileName}`)
325
+ json({ success: true, taskId, alreadyExists: true, fileName: existingFile.fileName })
326
+ return
327
+ }
328
+
329
+ // Async download — do not block HTTP response
330
+ engine.downloadFile(body.link, taskId).catch(err => {
331
+ if (err.message === 'Download cancelled') {
332
+ wsBroadcast('download:cancelled', { taskId })
333
+ } else {
334
+ wsBroadcast('download:error', { taskId, error: err.message })
335
+ }
336
+ })
337
+
338
+ json({ success: true, taskId })
339
+ return
340
+ }
341
+
342
+ // POST /api/download/cancel — cancel an active download
343
+ if (pathname === '/api/download/cancel' && method === 'POST') {
344
+ const body = await parseJSON(req)
345
+ if (!body.taskId) {
346
+ json({ error: 'taskId is required' }, 400)
347
+ return
348
+ }
349
+ engine.cancelDownload(body.taskId)
350
+ json({ success: true })
351
+ return
352
+ }
353
+
354
+ // DELETE /api/files/:cid
355
+ if (pathname.startsWith('/api/files/') && method === 'DELETE') {
356
+ const cid = pathname.replace('/api/files/', '').replace(/\/$/, '')
357
+ const result = await engine.deletePublishedFile(cid)
358
+ json(result)
359
+ return
360
+ }
361
+
362
+ // POST /api/move — rename/move a published file (changes path without re-uploading)
363
+ if (pathname === '/api/move' && method === 'POST') {
364
+ const body = await parseJSON(req)
365
+ if (!body.cid || !body.newFileName) {
366
+ json({ error: 'cid and newFileName are required' }, 400)
367
+ return
368
+ }
369
+ try {
370
+ const result = await engine.moveFile(body.cid, body.newFileName)
371
+ json({ success: true, ...result })
372
+ } catch (err) {
373
+ json({ error: err.message }, 400)
374
+ }
375
+ return
376
+ }
377
+
378
+ // POST /api/folder/rename — rename a folder (renames all files within)
379
+ if (pathname === '/api/folder/rename' && method === 'POST') {
380
+ const body = await parseJSON(req)
381
+ if (!body.oldPath || !body.newPath) {
382
+ json({ error: 'oldPath and newPath are required' }, 400)
383
+ return
384
+ }
385
+ try {
386
+ const result = engine.renameFolder(body.oldPath, body.newPath)
387
+ json({ success: true, ...result })
388
+ } catch (err) {
389
+ json({ error: err.message }, 400)
390
+ }
391
+ return
392
+ }
393
+
394
+ // GET /api/files/:cid/download — serve file inline for preview / download
395
+ if (pathname.match(/^\/api\/files\/[^/]+\/download$/) && method === 'GET') {
396
+ json({ error: 'Use P2P network to download this file' }, 400)
397
+ return
398
+ }
399
+
400
+ // POST /api/shutdown — graceful server shutdown
401
+ if (pathname === '/api/shutdown' && method === 'POST') {
402
+ json({ success: true })
403
+ console.log('[MostBox] Shutdown requested via API...')
404
+ setTimeout(async () => {
405
+ await engine.stop()
406
+ serverInstance.close()
407
+ console.log('[MostBox] Server stopped.')
408
+ process.exit(0)
409
+ }, 100)
410
+ return
411
+ }
412
+
413
+ // GET /api/trash — list trash files
414
+ if (pathname === '/api/trash' && method === 'GET') {
415
+ json(engine.listTrashFiles())
416
+ return
417
+ }
418
+
419
+ // POST /api/trash/:cid/restore — restore file from trash
420
+ if (pathname.match(/^\/api\/trash\/[^/]+\/restore$/) && method === 'POST') {
421
+ const cid = pathname.split('/')[3]
422
+ try {
423
+ const result = engine.restoreTrashFile(cid)
424
+ json({ success: true, files: result })
425
+ } catch (err) {
426
+ json({ error: err.message }, 400)
427
+ }
428
+ return
429
+ }
430
+
431
+ // DELETE /api/trash/:cid — permanently delete a trash file
432
+ if (pathname.match(/^\/api\/trash\/[^/]+$/) && method === 'DELETE') {
433
+ const cid = pathname.split('/')[3]
434
+ const result = await engine.permanentDeleteTrashFile(cid)
435
+ json({ success: true, trashFiles: result })
436
+ return
437
+ }
438
+
439
+ // DELETE /api/trash — empty trash
440
+ if (pathname === '/api/trash' && method === 'DELETE') {
441
+ const result = await engine.emptyTrash()
442
+ json({ success: true, trashFiles: result })
443
+ return
444
+ }
445
+
446
+ // POST /api/files/:cid/star — toggle starred status
447
+ if (pathname.match(/^\/api\/files\/[^/]+\/star$/) && method === 'POST') {
448
+ const cid = pathname.split('/')[3]
449
+ try {
450
+ const result = engine.toggleStarred(cid)
451
+ json({ success: true, ...result })
452
+ } catch (err) {
453
+ json({ error: err.message }, 400)
454
+ }
455
+ return
456
+ }
457
+
458
+ // GET /api/storage — get storage statistics
459
+ if (pathname === '/api/storage' && method === 'GET') {
460
+ const result = await engine.getStorageStats()
461
+ json(result)
462
+ return
463
+ }
464
+
465
+ json({ error: 'Not found' }, 404)
466
+ } catch (err) {
467
+ console.error('[API Error]', err)
468
+ json({ error: err.message, code: err.code }, 500)
469
+ }
470
+ }
471
+
472
+ // --- Minimal WebSocket (RFC 6455) ---
473
+ function upgradeToWebSocket(req, socket) {
474
+ const key = req.headers['sec-websocket-key']
475
+ if (!key) { socket.destroy(); return }
476
+
477
+ const MAGIC = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
478
+ const accept = crypto.createHash('sha1')
479
+ .update(key + MAGIC)
480
+ .digest('base64')
481
+
482
+ socket.write(
483
+ 'HTTP/1.1 101 Switching Protocols\r\n' +
484
+ 'Upgrade: websocket\r\n' +
485
+ 'Connection: Upgrade\r\n' +
486
+ `Sec-WebSocket-Accept: ${accept}\r\n` +
487
+ '\r\n'
488
+ )
489
+
490
+ wsClients.add(socket)
491
+ socket.on('close', () => wsClients.delete(socket))
492
+ socket.on('error', () => wsClients.delete(socket))
493
+
494
+ socket.on('data', (buf) => {
495
+ if (buf.length < 2) return
496
+ const opcode = buf[0] & 0x0f
497
+ if (opcode === 0x8) {
498
+ wsClients.delete(socket)
499
+ socket.end()
500
+ }
501
+ if (opcode === 0x9) {
502
+ const pong = Buffer.from(buf)
503
+ pong[0] = (pong[0] & 0xf0) | 0xa
504
+ socket.write(pong)
505
+ }
506
+ if (opcode === 0x1 || opcode === 0x2) {
507
+ // Text or binary message - could broadcast to other clients if needed
508
+ }
509
+ })
510
+ }
511
+
512
+ function wsBroadcast(event, data) {
513
+ const payload = JSON.stringify({ event, data })
514
+ const buf = Buffer.from(payload)
515
+
516
+ let frame
517
+ if (buf.length < 126) {
518
+ frame = Buffer.alloc(2 + buf.length)
519
+ frame[0] = 0x81
520
+ frame[1] = buf.length
521
+ buf.copy(frame, 2)
522
+ } else if (buf.length < 65536) {
523
+ frame = Buffer.alloc(4 + buf.length)
524
+ frame[0] = 0x81
525
+ frame[1] = 126
526
+ frame.writeUInt16BE(buf.length, 2)
527
+ buf.copy(frame, 4)
528
+ } else {
529
+ frame = Buffer.alloc(10 + buf.length)
530
+ frame[0] = 0x81
531
+ frame[1] = 127
532
+ frame.writeBigUInt64BE(BigInt(buf.length), 2)
533
+ buf.copy(frame, 10)
534
+ }
535
+
536
+ for (const client of wsClients) {
537
+ try { client.write(frame) } catch {}
538
+ }
539
+ }
540
+
541
+ // --- Main ---
542
+ async function main() {
543
+ console.log('[MostBox] Starting core daemon...')
544
+
545
+ const dataPath = getDataPath()
546
+ console.log(`[MostBox] Storage: ${dataPath}`)
547
+
548
+ engine = new MostBoxEngine({ dataPath })
549
+
550
+ engine.on('download:progress', (data) => wsBroadcast('download:progress', data))
551
+ engine.on('download:status', (data) => wsBroadcast('download:status', data))
552
+ engine.on('download:success', (data) => wsBroadcast('download:success', data))
553
+ engine.on('download:cancelled', (data) => wsBroadcast('download:cancelled', data))
554
+ engine.on('publish:progress', (data) => wsBroadcast('publish:progress', data))
555
+ engine.on('publish:success', (data) => wsBroadcast('publish:success', data))
556
+ engine.on('connection', () => {
557
+ wsBroadcast('network:status', engine.getNetworkStatus())
558
+ })
559
+
560
+ await engine.start()
561
+ console.log('[MostBox] Engine ready')
562
+
563
+ serverInstance = http.createServer((req, res) => {
564
+ res.setHeader('Access-Control-Allow-Origin', '*')
565
+ res.setHeader('Access-Control-Allow-Methods', 'GET, POST, DELETE, OPTIONS')
566
+ res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization')
567
+
568
+ if (req.method === 'OPTIONS') {
569
+ res.writeHead(204)
570
+ res.end()
571
+ return
572
+ }
573
+
574
+ if (req.url.startsWith('/api/')) {
575
+ handleAPI(req, res)
576
+ } else {
577
+ serveStatic(req, res)
578
+ }
579
+ })
580
+
581
+ serverInstance.on('upgrade', (req, socket) => {
582
+ if (req.url.startsWith('/ws')) {
583
+ upgradeToWebSocket(req, socket)
584
+ } else {
585
+ socket.destroy()
586
+ }
587
+ })
588
+
589
+ serverInstance.listen(PORT, HOST, () => {
590
+ const url = `http://${HOST}:${PORT}`
591
+ console.log(`[MostBox] Server running at ${url}`)
592
+
593
+ const cmd = process.platform === 'win32' ? 'start ""'
594
+ : process.platform === 'darwin' ? 'open' : 'xdg-open'
595
+ exec(`${cmd} "${url}"`)
596
+ })
597
+
598
+ process.on('SIGINT', async () => {
599
+ console.log('\n[MostBox] Shutting down...')
600
+ await engine.stop()
601
+ serverInstance.close()
602
+ process.exit(0)
603
+ })
604
+
605
+ process.on('SIGTERM', async () => {
606
+ await engine.stop()
607
+ serverInstance.close()
608
+ process.exit(0)
609
+ })
610
+ }
611
+
612
+ main().catch(err => {
613
+ console.error('[MostBox] Fatal error:', err)
614
+ process.exit(1)
615
+ })
package/src/config.js ADDED
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Application Configuration
3
+ */
4
+
5
+ // File size limits
6
+ export const MAX_FILE_SIZE = 100 * 1024 * 1024 * 1024 // 100 GB
7
+
8
+ // Network timeouts (ms)
9
+ export const CONNECTION_TIMEOUT = 120000
10
+ export const DOWNLOAD_TIMEOUT = 900000
11
+
12
+ // P2P settings
13
+ export const GLOBAL_SHARED_SEED_STRING = 'most-box-global-shared-seed-v1'
14
+
15
+ // DHT Bootstrap nodes for Hyperswarm/HyperDHT
16
+ // Using the same bootstrap nodes as Keet.io/HyperDHT for compatibility
17
+ // Format: [suggested-IP@]<host>:<port> to avoid DNS calls
18
+ export const SWARM_BOOTSTRAP = [
19
+ '88.99.3.86@node1.hyperdht.org:49737',
20
+ '142.93.90.113@node2.hyperdht.org:49737',
21
+ '138.68.147.8@node3.hyperdht.org:49737'
22
+ ]