most-box 0.0.2 → 0.0.4

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 (68) hide show
  1. package/README.md +26 -0
  2. package/cli.js +2 -2
  3. package/out/404/index.html +15 -0
  4. package/out/404.html +15 -0
  5. package/out/__next.__PAGE__.txt +9 -0
  6. package/out/__next._full.txt +18 -0
  7. package/out/__next._head.txt +5 -0
  8. package/out/__next._index.txt +6 -0
  9. package/out/__next._tree.txt +2 -0
  10. package/out/_next/static/0h4f4QFk_KC9FlSRfQACk/_buildManifest.js +11 -0
  11. package/out/_next/static/0h4f4QFk_KC9FlSRfQACk/_clientMiddlewareManifest.js +1 -0
  12. package/out/_next/static/0h4f4QFk_KC9FlSRfQACk/_ssgManifest.js +1 -0
  13. package/out/_next/static/chunks/00l-yd3t8dvwz.js +5 -0
  14. package/out/_next/static/chunks/03k8t3tgym~8~.js +1 -0
  15. package/out/_next/static/chunks/03~yq9q893hmn.js +1 -0
  16. package/out/_next/static/chunks/09vfh8lfuacc0.css +1 -0
  17. package/out/_next/static/chunks/0bogtdbh.dcu1.js +1 -0
  18. package/out/_next/static/chunks/0dbhjjzl8qfwv.js +1 -0
  19. package/out/_next/static/chunks/0f73psqhr8dre.css +1 -0
  20. package/out/_next/static/chunks/0fbi7z4_.4j1j.js +1 -0
  21. package/out/_next/static/chunks/0ht900cau6_ur.js +31 -0
  22. package/out/_next/static/chunks/0ohm.ia-4ec60.js +1 -0
  23. package/out/_next/static/chunks/0u5ydb-f0.vxl.js +1 -0
  24. package/out/_next/static/chunks/14t2m1on-s5v~.js +1 -0
  25. package/out/_next/static/chunks/turbopack-076ce9exut_h3.js +1 -0
  26. package/out/_not-found/__next._full.txt +16 -0
  27. package/out/_not-found/__next._head.txt +5 -0
  28. package/out/_not-found/__next._index.txt +6 -0
  29. package/out/_not-found/__next._not-found/__PAGE__.txt +5 -0
  30. package/out/_not-found/__next._not-found.txt +5 -0
  31. package/out/_not-found/__next._tree.txt +2 -0
  32. package/out/_not-found/index.html +15 -0
  33. package/out/_not-found/index.txt +16 -0
  34. package/out/app.css +1535 -0
  35. package/out/bundle.js +107 -0
  36. package/out/bundle.js.map +7 -0
  37. package/out/chat/__next._full.txt +19 -0
  38. package/out/chat/__next._head.txt +5 -0
  39. package/out/chat/__next._index.txt +6 -0
  40. package/out/chat/__next._tree.txt +3 -0
  41. package/out/chat/__next.chat/__PAGE__.txt +9 -0
  42. package/out/chat/__next.chat.txt +5 -0
  43. package/out/chat/index.html +15 -0
  44. package/out/chat/index.txt +19 -0
  45. package/out/chat-page.js +112 -0
  46. package/out/chat.css +378 -0
  47. package/out/favicon.ico +0 -0
  48. package/out/index.html +15 -0
  49. package/out/index.js +148 -0
  50. package/out/index.txt +18 -0
  51. package/package.json +11 -6
  52. package/public/app.css +20 -4
  53. package/public/bundle.js +1 -1
  54. package/public/bundle.js.map +7 -0
  55. package/public/chat-page.js +112 -0
  56. package/public/chat.css +378 -0
  57. package/public/index.js +148 -0
  58. package/server.js +188 -6
  59. package/src/config.js +12 -1
  60. package/src/core/cid.js +7 -3
  61. package/src/index.js +475 -7
  62. package/src/utils/api.js +6 -0
  63. package/build.mjs +0 -40
  64. package/public/app.jsx +0 -1543
  65. package/public/bundle.css +0 -1
  66. package/public/error-boundary.jsx +0 -50
  67. package/public/index.html +0 -16
  68. package/public/index.jsx +0 -20
package/server.js CHANGED
@@ -114,12 +114,12 @@ function getMimeType(fileName) {
114
114
  }
115
115
 
116
116
  function serveStatic(req, res) {
117
+ const publicDir = path.join(__dirname, 'out')
117
118
  let filePath = req.url === '/' ? '/index.html' : req.url
118
119
  filePath = filePath.split('?')[0]
119
120
 
120
- const fullPath = path.join(__dirname, 'public', filePath)
121
+ const fullPath = path.join(publicDir, filePath)
121
122
  const ext = path.extname(fullPath)
122
- const publicDir = path.join(__dirname, 'public')
123
123
 
124
124
  if (!fullPath.startsWith(publicDir)) {
125
125
  res.writeHead(403)
@@ -129,11 +129,33 @@ function serveStatic(req, res) {
129
129
 
130
130
  fs.readFile(fullPath, (err, data) => {
131
131
  if (err) {
132
- res.writeHead(404)
133
- res.end('Not found')
132
+ if (err.code === 'EISDIR') {
133
+ const indexPath = path.join(fullPath, 'index.html')
134
+ fs.readFile(indexPath, (err2, indexData) => {
135
+ if (err2) {
136
+ res.writeHead(404)
137
+ res.end('Not found')
138
+ return
139
+ }
140
+ res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' })
141
+ res.end(indexData)
142
+ })
143
+ } else {
144
+ const indexPath = path.join(publicDir, 'index.html')
145
+ fs.readFile(indexPath, (err2, indexData) => {
146
+ if (err2) {
147
+ res.writeHead(404)
148
+ res.end('Not found')
149
+ return
150
+ }
151
+ res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' })
152
+ res.end(indexData)
153
+ })
154
+ }
134
155
  return
135
156
  }
136
157
 
158
+ const ext = path.extname(fullPath)
137
159
  res.writeHead(200, { 'Content-Type': MIME_TYPES[ext] || 'application/octet-stream' })
138
160
  res.end(data)
139
161
  })
@@ -277,6 +299,12 @@ async function handleAPI(req, res) {
277
299
  return
278
300
  }
279
301
 
302
+ // GET /api/peer-id
303
+ if (pathname === '/api/peer-id' && method === 'GET') {
304
+ json({ peerId: engine.getNodeId() })
305
+ return
306
+ }
307
+
280
308
  // GET /api/config
281
309
  if (pathname === '/api/config' && method === 'GET') {
282
310
  const config = loadConfig()
@@ -562,6 +590,97 @@ async function handleAPI(req, res) {
562
590
  return
563
591
  }
564
592
 
593
+ // GET /api/display-name — 获取显示名
594
+ if (pathname === '/api/display-name' && method === 'GET') {
595
+ json({ displayName: engine.getDisplayName() })
596
+ return
597
+ }
598
+
599
+ // POST /api/display-name — 设置显示名
600
+ if (pathname === '/api/display-name' && method === 'POST') {
601
+ const body = await parseJSON(req)
602
+ if (!body.name || !body.name.trim()) {
603
+ json({ error: 'name is required' }, 400)
604
+ return
605
+ }
606
+ const success = engine.setDisplayName(body.name)
607
+ json({ success, displayName: engine.getDisplayName() })
608
+ return
609
+ }
610
+
611
+ // POST /api/channels — 创建/加入频道
612
+ if (pathname === '/api/channels' && method === 'POST') {
613
+ const body = await parseJSON(req)
614
+ if (!body.name || !body.name.trim()) {
615
+ json({ error: 'name is required' }, 400)
616
+ return
617
+ }
618
+ try {
619
+ const result = await engine.createChannel(body.name.trim(), body.type || 'personal')
620
+ json({ success: true, ...result })
621
+ } catch (err) {
622
+ json({ error: err.message }, 400)
623
+ }
624
+ return
625
+ }
626
+
627
+ // GET /api/channels — 获取频道列表
628
+ if (pathname === '/api/channels' && method === 'GET') {
629
+ json(engine.listChannels())
630
+ return
631
+ }
632
+
633
+ // DELETE /api/channels/:name — 离开频道
634
+ if (pathname.startsWith('/api/channels/') && method === 'DELETE') {
635
+ const name = pathname.split('/')[3]
636
+ try {
637
+ const result = await engine.leaveChannel(name)
638
+ json({ success: true, channels: result })
639
+ } catch (err) {
640
+ json({ error: err.message }, 400)
641
+ }
642
+ return
643
+ }
644
+
645
+ // GET /api/channels/:name/messages — 获取频道消息
646
+ if (pathname.match(/^\/api\/channels\/[^/]+\/messages$/) && method === 'GET') {
647
+ const name = pathname.split('/')[3]
648
+ const urlObj = new URL(req.url, `http://${HOST}:${PORT}`)
649
+ const limit = parseInt(urlObj.searchParams.get('limit') || '100', 10)
650
+ const offset = parseInt(urlObj.searchParams.get('offset') || '0', 10)
651
+ try {
652
+ const messages = await engine.getChannelMessages(name, { limit, offset })
653
+ json(messages)
654
+ } catch (err) {
655
+ json({ error: err.message }, 400)
656
+ }
657
+ return
658
+ }
659
+
660
+ // POST /api/channels/:name/messages — 发送消息
661
+ if (pathname.match(/^\/api\/channels\/[^/]+\/messages$/) && method === 'POST') {
662
+ const name = pathname.split('/')[3]
663
+ const body = await parseJSON(req)
664
+ if (!body.content || !body.content.trim()) {
665
+ json({ error: 'content is required' }, 400)
666
+ return
667
+ }
668
+ try {
669
+ const message = await engine.sendMessage(name, body.content, body.authorName)
670
+ json({ success: true, message })
671
+ } catch (err) {
672
+ json({ error: err.message }, 400)
673
+ }
674
+ return
675
+ }
676
+
677
+ // GET /api/channels/:name/peers — 获取频道内在线用户
678
+ if (pathname.match(/^\/api\/channels\/[^/]+\/peers$/) && method === 'GET') {
679
+ const name = pathname.split('/')[3]
680
+ json(engine.getChannelPeers(name))
681
+ return
682
+ }
683
+
565
684
  json({ error: 'Not found' }, 404)
566
685
  } catch (err) {
567
686
  console.error('[API Error]', err)
@@ -580,6 +699,20 @@ function wsBroadcast(event, data) {
580
699
  }
581
700
  }
582
701
 
702
+ const channelSubscriptions = new Map()
703
+
704
+ function wsSendToChannel(channelName, event, data) {
705
+ const payload = JSON.stringify({ event, data })
706
+ const subscribers = channelSubscriptions.get(channelName)
707
+ if (subscribers) {
708
+ subscribers.forEach((ws) => {
709
+ if (ws.readyState === 1) {
710
+ try { ws.send(payload) } catch {}
711
+ }
712
+ })
713
+ }
714
+ }
715
+
583
716
  // --- 主函数 ---
584
717
  async function main() {
585
718
  console.log('[MostBox] Starting core daemon...')
@@ -606,13 +739,17 @@ async function main() {
606
739
  engine.on('connection', () => {
607
740
  wsBroadcast('network:status', engine.getNetworkStatus())
608
741
  })
742
+ engine.on('channel:message', (data) => wsSendToChannel(data.channel, 'channel:message', data))
743
+ engine.on('channel:peer:online', (data) => wsBroadcast('channel:peer:online', data))
744
+ engine.on('channel:peer:offline', (data) => wsBroadcast('channel:peer:offline', data))
745
+ engine.on('channel:joined', (data) => wsBroadcast('channel:joined', data))
746
+ engine.on('channel:left', (data) => wsBroadcast('channel:left', data))
609
747
 
610
748
  await engine.start()
611
749
  console.log('[MostBox] Engine ready')
612
750
 
613
751
  serverInstance = http.createServer((req, res) => {
614
- const allowedOrigin = `http://${HOST}:${PORT}`
615
- res.setHeader('Access-Control-Allow-Origin', allowedOrigin)
752
+ res.setHeader('Access-Control-Allow-Origin', `http://${HOST}:${PORT}`)
616
753
  res.setHeader('Access-Control-Allow-Methods', 'GET, POST, DELETE, OPTIONS')
617
754
  res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization')
618
755
 
@@ -646,6 +783,51 @@ async function main() {
646
783
  wss = new WebSocketServer({ noServer: true })
647
784
  wss.on('connection', (ws) => {
648
785
  ws.on('error', () => {})
786
+ ws.on('close', () => {
787
+ for (const [channelName, subscribers] of channelSubscriptions) {
788
+ if (subscribers.has(ws)) {
789
+ subscribers.delete(ws)
790
+ if (subscribers.size === 0) {
791
+ channelSubscriptions.delete(channelName)
792
+ }
793
+ }
794
+ }
795
+ })
796
+ ws.on('message', (raw) => {
797
+ try {
798
+ const msg = JSON.parse(raw)
799
+ const { event, data } = msg
800
+
801
+ switch (event) {
802
+ case 'register':
803
+ ws.peerId = data.peerId
804
+ break
805
+ case 'channel:subscribe':
806
+ if (data.channel) {
807
+ const channelName = data.channel
808
+ if (!channelSubscriptions.has(channelName)) {
809
+ channelSubscriptions.set(channelName, new Set())
810
+ }
811
+ channelSubscriptions.get(channelName).add(ws)
812
+ }
813
+ break
814
+ case 'channel:unsubscribe':
815
+ if (data.channel) {
816
+ const channelName = data.channel
817
+ const subscribers = channelSubscriptions.get(channelName)
818
+ if (subscribers) {
819
+ subscribers.delete(ws)
820
+ if (subscribers.size === 0) {
821
+ channelSubscriptions.delete(channelName)
822
+ }
823
+ }
824
+ }
825
+ break
826
+ }
827
+ } catch (err) {
828
+ console.error('[WS Message Error]', err.message)
829
+ }
830
+ })
649
831
  })
650
832
 
651
833
  serverInstance.on('upgrade', (req, socket, head) => {
package/src/config.js CHANGED
@@ -36,4 +36,15 @@ export const SWARM_BOOTSTRAP = [
36
36
  '88.99.3.86@node1.hyperdht.org:49737',
37
37
  '142.93.90.113@node2.hyperdht.org:49737',
38
38
  '138.68.147.8@node3.hyperdht.org:49737'
39
- ]
39
+ ]
40
+
41
+ export const FRIEND_CODE_LENGTH = 16
42
+ export const FRIEND_CODE_PREFIX = 'MOST'
43
+
44
+ export const CHANNEL_NAME_MIN_LENGTH = 3
45
+ export const CHANNEL_NAME_MAX_LENGTH = 20
46
+ export const CHANNEL_NAME_REGEX = /^[a-zA-Z0-9_-]+$/
47
+ export const CHANNEL_NAME_PREFIX = 'most-box-room-'
48
+ export const CHANNEL_TOPIC_STRING = 'most-box-channels-v1'
49
+ export const CHANNEL_MESSAGE_LIMIT = 100
50
+ export const MAX_MESSAGE_LENGTH = 10000
package/src/core/cid.js CHANGED
@@ -127,9 +127,13 @@ export function parseMostLink(link) {
127
127
  // 移除尾部斜杠和空白
128
128
  cidString = cidString.trim().replace(/\/+$/, '')
129
129
 
130
- // 移除 query string
130
+ // 移除 query string,提取 filename
131
+ let fileName = null
131
132
  if (cidString.includes('?')) {
132
- cidString = cidString.split('?')[0]
133
+ const [cidPart, queryPart] = cidString.split('?')
134
+ cidString = cidPart
135
+ const params = new URLSearchParams(queryPart)
136
+ fileName = params.get('filename')
133
137
  }
134
138
 
135
139
  // 处理可能的额外路径的 URL 解析
@@ -142,5 +146,5 @@ export function parseMostLink(link) {
142
146
  return { cid: '', error: validation.error }
143
147
  }
144
148
 
145
- return { cid: cidString }
149
+ return { cid: cidString, fileName }
146
150
  }